Skip to content

Commit 8b32e3e

Browse files
committed
routing: add inbound fee support for BuildRoute
1 parent 36cd036 commit 8b32e3e

File tree

5 files changed

+105
-43
lines changed

5 files changed

+105
-43
lines changed

channeldb/graph_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3688,7 +3688,7 @@ func TestLightningNodeSigVerification(t *testing.T) {
36883688
}
36893689
}
36903690

3691-
// TestComputeFee tests fee calculation based on both in- and outgoing amt.
3691+
// TestComputeFee tests fee calculation based on the outgoing amt.
36923692
func TestComputeFee(t *testing.T) {
36933693
var (
36943694
policy = models.ChannelEdgePolicy{
@@ -3703,11 +3703,6 @@ func TestComputeFee(t *testing.T) {
37033703
if fee != expectedFee {
37043704
t.Fatalf("expected fee %v, got %v", expectedFee, fee)
37053705
}
3706-
3707-
fwdFee := policy.ComputeFeeFromIncoming(outgoingAmt + fee)
3708-
if fwdFee != expectedFee {
3709-
t.Fatalf("expected fee %v, but got %v", fee, fwdFee)
3710-
}
37113706
}
37123707

37133708
// TestBatchedAddChannelEdge asserts that BatchedAddChannelEdge properly

channeldb/models/cached_edge_policy.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,6 @@ func (c *CachedEdgePolicy) ComputeFee(
7171
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
7272
}
7373

74-
// ComputeFeeFromIncoming computes the fee to forward an HTLC given the incoming
75-
// amount.
76-
func (c *CachedEdgePolicy) ComputeFeeFromIncoming(
77-
incomingAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
78-
79-
return incomingAmt - divideCeil(
80-
feeRateParts*(incomingAmt-c.FeeBaseMSat),
81-
feeRateParts+c.FeeProportionalMillionths,
82-
)
83-
}
84-
8574
// NewCachedPolicy turns a full policy into a minimal one that can be cached.
8675
func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy {
8776
return &CachedEdgePolicy{

channeldb/models/channel_edge_policy.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,3 @@ func (c *ChannelEdgePolicy) ComputeFee(
113113

114114
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
115115
}
116-
117-
// divideCeil divides dividend by factor and rounds the result up.
118-
func divideCeil(dividend, factor lnwire.MilliSatoshi) lnwire.MilliSatoshi {
119-
return (dividend + factor - 1) / factor
120-
}
121-
122-
// ComputeFeeFromIncoming computes the fee to forward an HTLC given the incoming
123-
// amount.
124-
func (c *ChannelEdgePolicy) ComputeFeeFromIncoming(
125-
incomingAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
126-
127-
return incomingAmt - divideCeil(
128-
feeRateParts*(incomingAmt-c.FeeBaseMSat),
129-
feeRateParts+c.FeeProportionalMillionths,
130-
)
131-
}

routing/router.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,11 +1518,11 @@ func getEdgeUnifiers(source route.Vertex, hops []route.Vertex,
15181518
}
15191519

15201520
// Build unified policies for this hop based on the channels
1521-
// known in the graph. Don't use inbound fees.
1522-
//
1523-
// TODO: Add inbound fees support for BuildRoute.
1521+
// known in the graph. Inbound fees are only active if the edge
1522+
// is not the last hop.
1523+
isExitHop := i == len(hops)-1
15241524
u := newNodeEdgeUnifier(
1525-
source, toNode, false, outgoingChans,
1525+
source, toNode, !isExitHop, outgoingChans,
15261526
)
15271527

15281528
err := u.addGraphPolicies(graph)
@@ -1617,7 +1617,13 @@ func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool,
16171617
return nil, 0, ErrNoChannel{position: i}
16181618
}
16191619

1620-
fee := outboundFee
1620+
// The fee paid to B depends on the current hop's inbound fee
1621+
// policy and on the outbound fee for the next hop as any
1622+
// inbound fee discount is capped by the outbound fee such that
1623+
// the total fee for B can't become negative.
1624+
inboundFee := calcCappedInboundFee(edge, netAmount, outboundFee)
1625+
1626+
fee := lnwire.MilliSatoshi(int64(outboundFee) + inboundFee)
16211627

16221628
log.Tracef("Select channel %v at position %v",
16231629
edge.policy.ChannelID, i)
@@ -1655,12 +1661,11 @@ func receiverAmtForwardPass(runningAmt lnwire.MilliSatoshi,
16551661
// been increased in the backward pass, fees need to be recalculated and
16561662
// amount ranges re-checked.
16571663
for i := 1; i < len(unifiedEdges); i++ {
1664+
inEdge := unifiedEdges[i-1]
16581665
outEdge := unifiedEdges[i]
16591666

16601667
// Decrease the amount to send while going forward.
1661-
runningAmt -= outEdge.policy.ComputeFeeFromIncoming(
1662-
runningAmt,
1663-
)
1668+
runningAmt = outgoingFromIncoming(runningAmt, inEdge, outEdge)
16641669

16651670
if !outEdge.amtInRange(runningAmt) {
16661671
log.Errorf("Amount %v not in range for hop index %v",

routing/router_test.go

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,28 @@ func TestBuildRoute(t *testing.T) {
15881588
MaxHTLC: lnwire.MilliSatoshi(20100),
15891589
Features: paymentAddrFeatures,
15901590
}, 8),
1591+
1592+
// Create a route with inbound fees.
1593+
symmetricTestChannel("a", "d", chanCapSat, &testChannelPolicy{
1594+
Expiry: 144,
1595+
FeeRate: 20000,
1596+
MinHTLC: lnwire.NewMSatFromSatoshis(5),
1597+
MaxHTLC: lnwire.NewMSatFromSatoshis(
1598+
chanCapSat,
1599+
),
1600+
InboundFeeBaseMsat: -1000,
1601+
InboundFeeRate: -1000,
1602+
}, 9),
1603+
symmetricTestChannel("d", "f", chanCapSat, &testChannelPolicy{
1604+
Expiry: 144,
1605+
FeeRate: 60000,
1606+
MinHTLC: lnwire.NewMSatFromSatoshis(20),
1607+
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
1608+
Features: paymentAddrFeatures,
1609+
// The inbound fee will not be active for the last hop.
1610+
InboundFeeBaseMsat: 2000,
1611+
InboundFeeRate: 2000,
1612+
}, 10),
15911613
}
15921614

15931615
testGraph, err := createTestGraphFromChannels(
@@ -1686,6 +1708,30 @@ func TestBuildRoute(t *testing.T) {
16861708
require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount)
16871709
require.Equal(t, lnwire.MilliSatoshi(20000), rt.Hops[1].AmtToForward)
16881710

1711+
// Check that we compute a correct forwarding amount that involves
1712+
// inbound fees. We expect a similar amount as for the above case of
1713+
// b->c, but reduced by the inbound discount on the channel a->d.
1714+
// We get 106000 - 1000 (base in) - 0.001 * 106000 (rate in) = 104894.
1715+
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
1716+
amt = lnwire.NewMSatFromSatoshis(100)
1717+
rt, err = ctx.router.BuildRoute(&amt, hops, nil, 40, &payAddr)
1718+
require.NoError(t, err)
1719+
checkHops(rt, []uint64{9, 10}, payAddr)
1720+
require.EqualValues(t, 104894, rt.TotalAmount)
1721+
1722+
// Also check the min amount with inbound fees. The min amount bumps
1723+
// this to 20000 msat for the last hop. The outbound fee is 1200 msat,
1724+
// the inbound fee is -1021.2 msat (rounded down). This results in a
1725+
// total fee of 179 msat, giving a sender amount of 20179 msat. The
1726+
// determined receiver amount however reduces this to 20001 msat again
1727+
// due to rounding. This would not be compatible with the sender amount
1728+
// of 20179 msat, which results in underpayment of 1 msat in fee. There
1729+
// is a third pass through newRoute in which this gets corrected to end
1730+
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
1731+
rt, err = ctx.router.BuildRoute(nil, hops, nil, 40, &payAddr)
1732+
require.NoError(t, err)
1733+
checkHops(rt, []uint64{9, 10}, payAddr)
1734+
require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount)
16891735
}
16901736

16911737
// TestReceiverAmtForwardPass tests that the forward pass returns the expected
@@ -1831,6 +1877,9 @@ func TestSenderAmtBackwardPass(t *testing.T) {
18311877
policy: &models.CachedEdgePolicy{
18321878
FeeBaseMSat: 112,
18331879
},
1880+
inboundFees: models.InboundFee{
1881+
Base: 111,
1882+
},
18341883
capacity: capacity,
18351884
},
18361885
},
@@ -1841,6 +1890,9 @@ func TestSenderAmtBackwardPass(t *testing.T) {
18411890
policy: &models.CachedEdgePolicy{
18421891
FeeBaseMSat: 222,
18431892
},
1893+
inboundFees: models.InboundFee{
1894+
Base: 222,
1895+
},
18441896
capacity: capacity,
18451897
},
18461898
},
@@ -1852,6 +1904,12 @@ func TestSenderAmtBackwardPass(t *testing.T) {
18521904
FeeBaseMSat: 333,
18531905
MinHTLC: minHTLC,
18541906
},
1907+
// In pathfinding, inbound fees are not
1908+
// populated for exit hops because the
1909+
// newNodeEdgeUnifier enforces this.
1910+
// This is important as otherwise we
1911+
// would not fail the min HTLC check in
1912+
// getEdge.
18551913
capacity: capacity,
18561914
},
18571915
},
@@ -1870,20 +1928,51 @@ func TestSenderAmtBackwardPass(t *testing.T) {
18701928
edgeUnifiers, true, 1, &bandwidthHints,
18711929
)
18721930
require.NoError(t, err)
1873-
require.Equal(t, minHTLC+333+222, senderAmount)
1931+
require.Equal(t, minHTLC+333+222+222+111, senderAmount)
18741932

18751933
// Do a search for a specific amount.
18761934
unifiedEdges, senderAmount, err = senderAmtBackwardPass(
18771935
edgeUnifiers, false, testReceiverAmt, &bandwidthHints,
18781936
)
18791937
require.NoError(t, err)
1880-
require.Equal(t, testReceiverAmt+333+222, senderAmount)
1938+
require.Equal(t, testReceiverAmt+333+222+222+111, senderAmount)
18811939

18821940
// Check that we arrive at the same receiver amount by doing a forward
18831941
// pass.
18841942
receiverAmt, err := receiverAmtForwardPass(senderAmount, unifiedEdges)
18851943
require.NoError(t, err)
18861944
require.Equal(t, testReceiverAmt, receiverAmt)
1945+
1946+
// Insert a policy that leads to rounding.
1947+
edgeUnifiers[1] = &edgeUnifier{
1948+
edges: []*unifiedEdge{
1949+
{
1950+
policy: &models.CachedEdgePolicy{
1951+
FeeBaseMSat: 20,
1952+
FeeProportionalMillionths: 100,
1953+
},
1954+
inboundFees: models.InboundFee{
1955+
Base: -10,
1956+
Rate: -50,
1957+
},
1958+
capacity: capacity,
1959+
},
1960+
},
1961+
}
1962+
1963+
unifiedEdges, senderAmount, err = senderAmtBackwardPass(
1964+
edgeUnifiers, false, testReceiverAmt, &bandwidthHints,
1965+
)
1966+
require.NoError(t, err)
1967+
1968+
// For this route, we have some rounding errors, so we can't expect the
1969+
// exact amount, but it should be higher than the exact amount, to not
1970+
// end up below a min HTLC constraint.
1971+
receiverAmt, err = receiverAmtForwardPass(senderAmount, unifiedEdges)
1972+
require.NoError(t, err)
1973+
require.NotEqual(t, testReceiverAmt, receiverAmt)
1974+
require.InDelta(t, int64(testReceiverAmt), int64(receiverAmt), 1)
1975+
require.GreaterOrEqual(t, int64(receiverAmt), int64(testReceiverAmt))
18871976
}
18881977

18891978
// TestInboundOutbound tests the functions that computes the incoming and

0 commit comments

Comments
 (0)