routing: fix route fee calculation and channel capacity check
This commit fixes the logic inside the newRoute function to address the following problems: - Fee calculation for a hop does not include the fee that needs to be paid to the next hop. - The incoming channel capacity "sanity" check does not include the fee to be paid to the current hop.
This commit is contained in:
1 parent
70ff4414d9
commit
d4471878df
2 files changed
+224
-63
No files matched your search
+63
-63
@@ -50,10 +50,11 @@ type HopHint struct {
|
||||
CLTVExpiryDelta uint16
|
||||
}
|
||||
|
||||
// ChannelHop is an intermediate hop within the network with a greater
|
||||
// multi-hop payment route. This struct contains the relevant routing policy of
|
||||
// the particular edge, as well as the total capacity, and origin chain of the
|
||||
// channel itself.
|
||||
// ChannelHop describes the channel through which an intermediate or final
|
||||
// hop can be reached. This struct contains the relevant routing policy of
|
||||
// the particular edge (which is a property of the source node of the channel
|
||||
// edge), as well as the total capacity. It also includes the origin chain of
|
||||
// the channel itself.
|
||||
type ChannelHop struct {
|
||||
// Capacity is the total capacity of the channel being traversed. This
|
||||
// value is expressed for stability in satoshis.
|
||||
@@ -69,13 +70,15 @@ type ChannelHop struct {
|
||||
*channeldb.ChannelEdgePolicy
|
||||
}
|
||||
|
||||
// Hop represents the forwarding details at a particular position within the
|
||||
// final route. This struct houses the values necessary to create the HTLC
|
||||
// which will travel along this hop, and also encode the per-hop payload
|
||||
// included within the Sphinx packet.
|
||||
// Hop represents an intermediate or final node of the route. This naming
|
||||
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
||||
// The struct houses the channel along which this hop can be reached and
|
||||
// the values necessary to create the HTLC that needs to be sent to the
|
||||
// next hop. It is also used to encode the per-hop payload included within
|
||||
// the Sphinx packet.
|
||||
type Hop struct {
|
||||
// Channel is the active payment channel edge that this hop will travel
|
||||
// along.
|
||||
// Channel is the active payment channel edge along which the packet
|
||||
// travels to reach this hop. This is the _incoming_ channel to this hop.
|
||||
Channel *ChannelHop
|
||||
|
||||
// OutgoingTimeLock is the timelock value that should be used when
|
||||
@@ -269,11 +272,6 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
// information for the first hop so the mapping is sound.
|
||||
route.nextHopMap[sourceVertex] = pathEdges[0]
|
||||
|
||||
// The running amount is the total amount of satoshis required at this
|
||||
// point in the route. We start this value at the amount we want to
|
||||
// send to the destination. This value will then get successively
|
||||
// larger as we compute the fees going backwards.
|
||||
runningAmt := amtToSend
|
||||
pathLength := len(pathEdges)
|
||||
for i := pathLength - 1; i >= 0; i-- {
|
||||
edge := pathEdges[i]
|
||||
@@ -285,59 +283,56 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
route.nodeIndex[v] = struct{}{}
|
||||
route.chanIndex[edge.ChannelID] = struct{}{}
|
||||
|
||||
// If this isn't a direct payment, and this isn't the last hop
|
||||
// in the route, then we'll also populate the nextHop map to
|
||||
// allow easy route traversal by callers.
|
||||
// If this isn't a direct payment, and this isn't the edge to
|
||||
// the last hop in the route, then we'll also populate the
|
||||
// nextHop map to allow easy route traversal by callers.
|
||||
if len(pathEdges) > 1 && i != len(pathEdges)-1 {
|
||||
route.nextHopMap[v] = route.Hops[i+1].Channel
|
||||
}
|
||||
|
||||
// Now we'll start to calculate the items within the per-hop
|
||||
// payload for this current hop.
|
||||
//
|
||||
// If this is the last hop, then we send the exact amount and
|
||||
// pay no fee, as we're paying directly to the receiver, and
|
||||
// there're no additional hops.
|
||||
amtToForward := runningAmt
|
||||
// payload for the hop this edge is leading to. This hop will
|
||||
// be called the 'current hop'.
|
||||
|
||||
// If it is the last hop, then the hop payload will contain
|
||||
// the exact amount. In BOLT #4: Onion Routing
|
||||
// Protocol / "Payload for the Last Node", this is detailed.
|
||||
amtToForward := amtToSend
|
||||
|
||||
// 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)
|
||||
|
||||
// If this isn't the last hop, to add enough funds to pay for
|
||||
// transit over the next link.
|
||||
// If the current hop isn't the last hop, then add enough funds
|
||||
// to pay for transit over the next link.
|
||||
if i != len(pathEdges)-1 {
|
||||
// We'll grab the edge policy and per-hop payload of
|
||||
// the prior hop so we can calculate fees properly.
|
||||
prevEdge := pathEdges[i+1]
|
||||
prevHop := route.Hops[i+1]
|
||||
// We'll grab the per-hop payload of the next hop (the
|
||||
// hop _after_ the hop this edge leads to) in the
|
||||
// route so we can calculate fees properly.
|
||||
nextHop := route.Hops[i+1]
|
||||
|
||||
// The fee for this hop, will be based on how much the
|
||||
// prior hop carried, as we'll need to increase the
|
||||
// amount of satoshis incoming into this hop to
|
||||
// properly pay the required fees.
|
||||
prevAmount := prevHop.AmtToForward
|
||||
fee = computeFee(prevAmount, prevEdge.ChannelEdgePolicy)
|
||||
// The amount that the current hop needs to forward is
|
||||
// based on how much the next hop forwards plus the fee
|
||||
// that needs to be paid to the next hop.
|
||||
amtToForward = nextHop.AmtToForward + nextHop.Fee
|
||||
|
||||
// With the fee computed, we increment the total amount
|
||||
// as we need to pay this fee. This value represents
|
||||
// the amount of funds that will come _into_ this edge.
|
||||
runningAmt += fee
|
||||
|
||||
// Otherwise, for a node to forward an HTLC, then
|
||||
// following inequality most hold true:
|
||||
//
|
||||
// * amt_in - fee >= amt_to_forward
|
||||
amtToForward = runningAmt - fee
|
||||
// The fee that needs to be paid to the current hop is
|
||||
// based on the amount that this hop needs to forward
|
||||
// and its policy for the outgoing channel. This policy
|
||||
// is stored as part of the incoming channel of
|
||||
// the next hop.
|
||||
fee = computeFee(amtToForward, nextHop.Channel.ChannelEdgePolicy)
|
||||
}
|
||||
|
||||
// Now we create the hop struct for this point in the route.
|
||||
// The amount to forward is the running amount, and we compute
|
||||
// the required fee based on this amount.
|
||||
nextHop := &Hop{
|
||||
// Now we create the hop struct for the current hop.
|
||||
currentHop := &Hop{
|
||||
Channel: edge,
|
||||
AmtToForward: amtToForward,
|
||||
Fee: fee,
|
||||
}
|
||||
|
||||
route.TotalFees += nextHop.Fee
|
||||
// Accumulate all fees.
|
||||
route.TotalFees += currentHop.Fee
|
||||
|
||||
// Invalidate this route if its total fees exceed our fee limit.
|
||||
if route.TotalFees > feeLimit {
|
||||
@@ -346,14 +341,17 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
return nil, newErrf(ErrFeeLimitExceeded, err)
|
||||
}
|
||||
|
||||
// As a sanity check, we ensure that the selected channel has
|
||||
// enough capacity to forward the required amount which
|
||||
// includes the fee dictated at each hop.
|
||||
if nextHop.AmtToForward.ToSatoshis() > nextHop.Channel.Capacity {
|
||||
// As a sanity check, we ensure that the incoming channel has
|
||||
// enough capacity to carry the required amount which
|
||||
// includes the fee dictated at each hop. Make the comparison
|
||||
// in msat to prevent rounding errors.
|
||||
if currentHop.AmtToForward + fee > lnwire.NewMSatFromSatoshis(
|
||||
currentHop.Channel.Capacity) {
|
||||
|
||||
err := fmt.Sprintf("channel graph has insufficient "+
|
||||
"capacity for the payment: need %v, have %v",
|
||||
nextHop.AmtToForward.ToSatoshis(),
|
||||
nextHop.Channel.Capacity)
|
||||
currentHop.AmtToForward.ToSatoshis(),
|
||||
currentHop.Channel.Capacity)
|
||||
|
||||
return nil, newErrf(ErrInsufficientCapacity, err)
|
||||
}
|
||||
@@ -367,7 +365,7 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
// last link in the route.
|
||||
route.TotalTimeLock += uint32(finalCLTVDelta)
|
||||
|
||||
nextHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||
currentHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||
} else {
|
||||
// Next, increment the total timelock of the entire
|
||||
// route such that each hops time lock increases as we
|
||||
@@ -380,10 +378,10 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
// be the value of the time-lock for the _outgoing_
|
||||
// HTLC, so we factor in their specified grace period
|
||||
// (time lock delta).
|
||||
nextHop.OutgoingTimeLock = route.TotalTimeLock - delta
|
||||
currentHop.OutgoingTimeLock = route.TotalTimeLock - delta
|
||||
}
|
||||
|
||||
route.Hops[i] = nextHop
|
||||
route.Hops[i] = currentHop
|
||||
}
|
||||
|
||||
// We'll then make a second run through our route in order to set up
|
||||
@@ -393,9 +391,11 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
route.prevHopMap[vertex] = hop.Channel
|
||||
}
|
||||
|
||||
// The total amount required for this route will be the value the
|
||||
// source extends to the first hop in the route.
|
||||
route.TotalAmount = runningAmt
|
||||
// The total amount required for this route will be the value
|
||||
// that the first hop needs to forward plus the fee that
|
||||
// the first hop charges for this. Note that the sender of the
|
||||
// payment is not a hop in the route.
|
||||
route.TotalAmount = route.Hops[0].AmtToForward + route.Hops[0].Fee
|
||||
|
||||
return route, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user