Skip to content

Commit

Permalink
routing: add inbound fee support to pathfinding
Browse files Browse the repository at this point in the history
Add sender-side support for inbound fees in pathfinding
and route building.
  • Loading branch information
joostjager committed Oct 19, 2022
1 parent 32fd65a commit cbcde71
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 62 deletions.
11 changes: 11 additions & 0 deletions channeldb/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,24 @@ func (c *ChannelGraph) ForEachNodeChannel(tx kvdb.RTx, node route.Vertex,
cachedInPolicy.ToNodeFeatures = toNodeFeatures
}

var inboundFee lnwire.Fee
if p1 != nil {
// Extract inbound fee. If there is a decoding error,
// skip this edge.
_, err := p1.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
return nil
}
}

directedChannel := &DirectedChannel{
ChannelID: e.ChannelID,
IsNode1: node == e.NodeKey1Bytes,
OtherNode: e.NodeKey2Bytes,
Capacity: e.Capacity,
OutPolicySet: p1 != nil,
InPolicy: cachedInPolicy,
InboundFee: inboundFee,
}

if node == e.NodeKey2Bytes {
Expand Down
13 changes: 13 additions & 0 deletions channeldb/graph_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ type DirectedChannel struct {
// source, so we're always interested in the edge that arrives to us
// from the other node.
InPolicy *CachedEdgePolicy

// Inbound fees of this node.
InboundFee lnwire.Fee
}

// DeepCopy creates a deep copy of the channel, including the incoming policy.
Expand Down Expand Up @@ -304,6 +307,14 @@ func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
func (c *GraphCache) UpdatePolicy(policy *ChannelEdgePolicy, fromNode,
toNode route.Vertex, edge1 bool) {

// Extract inbound fee if possible and available. If there is a decoding
// error, ignore this policy.
var inboundFee lnwire.Fee
_, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
return
}

c.mtx.Lock()
defer c.mtx.Unlock()

Expand All @@ -324,11 +335,13 @@ func (c *GraphCache) UpdatePolicy(policy *ChannelEdgePolicy, fromNode,
// policy for node 1.
case channel.IsNode1 && edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee

// This is node 2, and it is edge 2, so this is the outgoing
// policy for node 2.
case !channel.IsNode1 && !edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee

// The other two cases left mean it's the inbound policy for the
// node.
Expand Down
4 changes: 2 additions & 2 deletions channeldb/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *LightningNode) (*ChannelEd
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 3452352,
Node: secondNode,
ExtraOpaqueData: []byte("new unknown feature2"),
ExtraOpaqueData: []byte{1, 0},
db: db,
}
edge2 := &ChannelEdgePolicy{
Expand All @@ -688,7 +688,7 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *LightningNode) (*ChannelEd
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 90392423,
Node: firstNode,
ExtraOpaqueData: []byte("new unknown feature1"),
ExtraOpaqueData: []byte{1, 0},
db: db,
}

Expand Down
26 changes: 16 additions & 10 deletions lntest/itest/lnd_multi-hop-payments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,16 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
aliceFundPoint, expectedAmountPaidAtoBMsat/1000, int64(0))

// To forward a payment of 1000 sat, Alice is charging a fee of 1 sat +
// 10% = 101 sat. Note that this does not include the inbound fee
// (discount) because there is no sender support yet.
const aliceFeePerPaymentMsat = aliceBaseFeeMsat +
// 10% = 101 sat, plus the inbound fee over 1101 (= 1000 + 101) sat of
// -1 msat - 1% = 11011 msat.
const aliceOutFeePerPaymentMsat = aliceBaseFeeMsat +
(paymentAmtMsat * aliceFeeRatePPM / 1_000_000)
const expectedFeeAliceMsat = numPayments * aliceFeePerPaymentMsat
const aliceInFeePerPaymentMsat = aliceInboundBaseFeeMsat +
((paymentAmtMsat + aliceOutFeePerPaymentMsat) *
aliceInboundFeeRate / 1_000_000)

const expectedFeeAliceMsat = numPayments *
(aliceOutFeePerPaymentMsat + aliceInFeePerPaymentMsat)

// Dave needs to pay what Alice pays plus Alice's fee.
expectedAmountPaidDtoAMsat := expectedAmountPaidAtoBMsat +
Expand All @@ -248,12 +253,13 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
assertAmountPaid(t, "Dave(local) => Alice(remote)", dave,
daveFundPoint, expectedAmountPaidDtoAMsat/1000, int64(0))

// To forward a payment of 1101 sat, Dave is charging a fee of
// 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170.
const davePaymentAmtMsat = paymentAmtMsat + aliceFeePerPaymentMsat
const daveFeePerPaymentMsat = daveBaseFeeMsat +
(davePaymentAmtMsat * daveFeeRatePPM / 1_000_000)
const expectedFeeDaveMsat = numPayments * daveFeePerPaymentMsat
// To forward a payment of 1011011 msat, Dave is charging a fee of
// 5 sat + 15% = 156651 msat.
const davePaymentAmt = paymentAmtMsat +
aliceInFeePerPaymentMsat + aliceOutFeePerPaymentMsat
const daveFeePerPayment = daveBaseFeeMsat +
(davePaymentAmt * daveFeeRatePPM / 1_000_000)
const expectedFeeDaveMsat = numPayments * daveFeePerPayment

// Carol needs to pay what Dave pays plus Dave's fee.
expectedAmountPaidCtoDMsat := expectedAmountPaidDtoAMsat +
Expand Down
76 changes: 58 additions & 18 deletions routing/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
Expand Down Expand Up @@ -137,7 +138,7 @@ func newRoute(sourceVertex route.Vertex,
// we compute the route in reverse.
var (
amtToForward lnwire.MilliSatoshi
fee lnwire.MilliSatoshi
fee int64
outgoingTimeLock uint32
tlvPayload bool
customRecords record.CustomSet
Expand Down Expand Up @@ -174,7 +175,7 @@ func newRoute(sourceVertex route.Vertex,

// Fee is not part of the hop payload, but only used for
// reporting through RPC. Set to zero for the final hop.
fee = lnwire.MilliSatoshi(0)
fee = 0

// As this is the last hop, we'll use the specified
// final CLTV delta value instead of the value from the
Expand Down Expand Up @@ -219,15 +220,21 @@ func newRoute(sourceVertex route.Vertex,
// and its policy for the outgoing channel. This policy
// is stored as part of the incoming channel of
// the next hop.
fee = pathEdges[i+1].policy.ComputeFee(amtToForward)
outboundFee := pathEdges[i+1].policy.ComputeFee(
amtToForward,
)

inboundFee := pathEdges[i].inboundFees.CalcFee(
amtToForward + outboundFee,
)

fee = int64(outboundFee) + inboundFee

// We'll take the total timelock of the preceding hop as
// the outgoing timelock or this hop. Then we'll
// increment the total timelock incurred by this hop.
outgoingTimeLock = totalTimeLock
totalTimeLock += uint32(
pathEdges[i+1].policy.TimeLockDelta,
)
totalTimeLock += uint32(pathEdges[i+1].policy.TimeLockDelta)
}

// Since we're traversing the path backwards atm, we prepend
Expand All @@ -249,7 +256,7 @@ func newRoute(sourceVertex route.Vertex,
// Finally, we update the amount that needs to flow into the
// *next* hop, which is the amount this hop needs to forward,
// accounting for the fee that it takes.
nextIncomingAmount = amtToForward + fee
nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
}

// With the base routing data expressed as hops, build the full route
Expand All @@ -270,7 +277,7 @@ func newRoute(sourceVertex route.Vertex,
// channels with shorter time lock deltas and shorter (hops) routes in general.
// RiskFactor controls the influence of time lock on route selection. This is
// currently a fixed value, but might be configurable in the future.
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee int64,
timeLockDelta uint16) int64 {
// timeLockPenalty is the penalty for the time lock delta of this channel.
// It is controlled by RiskFactorBillionths and scales proportional
Expand All @@ -279,7 +286,7 @@ func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
RiskFactorBillionths / 1000000000

return int64(fee) + timeLockPenalty
return fee + timeLockPenalty
}

// graphParams wraps the set of graph parameters passed to findPath.
Expand Down Expand Up @@ -626,9 +633,20 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,

edgesExpanded++

// Calculate inbound fee charged by "to" node, if it's not the
// exit hop.
var inboundFee int64
isExitHop := toNodeDist.nextHop == nil
if !isExitHop {
inboundFee = edge.inboundFees.CalcFee(
toNodeDist.amountToReceive,
)
}

// Calculate amount that the candidate node would have to send
// out.
amountToSend := toNodeDist.amountToReceive
amountToSend := toNodeDist.amountToReceive +
lnwire.MilliSatoshi(inboundFee)

// Request the success probability for this edge.
edgeProbability := r.ProbabilitySource(
Expand Down Expand Up @@ -657,10 +675,13 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// Also determine the time lock delta that will be added to the
// route if fromVertex is selected. If fromVertex is the source
// node, no additional timelock is required.
var fee lnwire.MilliSatoshi
var timeLockDelta uint16
var (
timeLockDelta uint16
outboundFee int64
)

if fromVertex != source {
fee = edge.policy.ComputeFee(amountToSend)
outboundFee = int64(edge.policy.ComputeFee(amountToSend))
timeLockDelta = edge.policy.TimeLockDelta
}

Expand All @@ -676,12 +697,13 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// previous node in the route. That previous node will need to
// pay the amount that this node forwards plus the fee it
// charges.
amountToReceive := amountToSend + fee
amountToReceive := amountToSend +
lnwire.MilliSatoshi(outboundFee)

// Check if accumulated fees would exceed fee limit when this
// node would be added to the path.
totalFee := amountToReceive - amt
if totalFee > r.FeeLimit {
totalFee := int64(amountToReceive) - int64(amt)
if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
return
}

Expand All @@ -697,6 +719,9 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
return
}

// Calculate total fee for this edge.
fee := inboundFee + outboundFee

// By adding fromVertex in the route, there will be an extra
// weight composed of the fee that this node will charge and
// the amount that will be locked for timeLockDelta blocks in
Expand All @@ -715,6 +740,12 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
absoluteAttemptCost,
)

// Make sure the distance never goes down, because dijkstra
// doesn't work with negative weights.
if tempDist < toNodeDist.dist {
tempDist = toNodeDist.dist
}

// If there is already a best route stored, compare this
// candidate route with the best route so far.
current, ok := distance[fromVertex]
Expand Down Expand Up @@ -842,17 +873,26 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
nodesVisited++

pivot := partialPath.node
isExitHop := partialPath.nextHop == nil

// Create unified policies for all incoming connections.
u := newUnifiedPolicies(self, pivot, outgoingChanMap)
u := newUnifiedPolicies(self, pivot, isExitHop, outgoingChanMap)

err := u.addGraphPolicies(g.graph)
if err != nil {
return nil, err
}

for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
u.addPolicy(reverseEdge.sourceNode, reverseEdge.edge, 0)
// Assume zero inbound fees for route hints. If inbound
// fees would apply, they couldn't be communicated in
// bolt11 invoices currently.
inboundFee := htlcswitch.InboundFee{}

u.addPolicy(
reverseEdge.sourceNode, reverseEdge.edge,
inboundFee, 0,
)
}

amtToSend := partialPath.amountToReceive
Expand Down

0 comments on commit cbcde71

Please sign in to comment.