routing: backward pass determines unified edges

We shift the duty of determining the policies to the backward pass as
the forward pass will only be responsible for finding the corrected
receiver amount.

Note that this is not a pure refactor as demonstrated in the test, as
the forward pass doesn't select new policies anymore, which is less
flexible and doesn't lead to the highest possible receiver amount. This
is however neccessary as we otherwise won't be able to compute
forwarding amounts involving inbound fees and this edge case is unlikely
to occur, because we search for a min amount for a route that was most
likely constructed for a larger amount.
This commit is contained in:
bitromortac
2024-06-25 11:45:36 +02:00
parent e4c7c10618
commit c18694ff5e
2 changed files with 215 additions and 68 deletions

View File

@@ -1451,35 +1451,29 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
// the destination. There are nodes in the wild that have a
// min_htlc channel policy of zero, which could lead to a zero
// amount payment being made.
senderAmt, err := senderAmtBackwardPass(
var senderAmt lnwire.MilliSatoshi
pathEdges, senderAmt, err = senderAmtBackwardPass(
unifiers, useMinAmt, 1, bandwidthHints,
)
if err != nil {
return nil, err
}
pathEdges, receiverAmt, err = getPathEdges(
senderAmt, unifiers, bandwidthHints,
)
receiverAmt, err = receiverAmtForwardPass(senderAmt, pathEdges)
if err != nil {
return nil, err
}
} else {
// If an amount is specified, we need to build a route that
// delivers exactly this amount to the final destination.
senderAmt, err := senderAmtBackwardPass(
pathEdges, _, err = senderAmtBackwardPass(
unifiers, useMinAmt, *amt, bandwidthHints,
)
if err != nil {
return nil, err
}
pathEdges, receiverAmt, err = getPathEdges(
senderAmt, unifiers, bandwidthHints,
)
if err != nil {
return nil, err
}
receiverAmt = *amt
}
// Fetch the current block height outside the routing transaction, to
@@ -1547,12 +1541,15 @@ func getEdgeUnifiers(source route.Vertex, hops []route.Vertex,
return unifiers, nil
}
// senderAmtBackwardPass determines the amount that should be sent to fulfill
// min HTLC requirements. The minimal sender amount can be searched for by
// activating useMinAmt.
// senderAmtBackwardPass returns a list of unified edges for the given route and
// determines the amount that should be sent to fulfill min HTLC requirements.
// The minimal sender amount can be searched for by activating useMinAmt.
func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool,
runningAmt lnwire.MilliSatoshi,
bandwidthHints *bandwidthManager) (lnwire.MilliSatoshi, error) {
bandwidthHints bandwidthHints) ([]*unifiedEdge, lnwire.MilliSatoshi,
error) {
var unifiedEdges = make([]*unifiedEdge, len(unifiers))
// Traverse hops backwards to accumulate fees in the running amounts.
for i := len(unifiers) - 1; i >= 0; i-- {
@@ -1573,7 +1570,7 @@ func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool,
log.Errorf("Cannot find policy with amt=%v for hop %v",
runningAmt, i)
return 0, ErrNoChannel{
return nil, 0, ErrNoChannel{
position: i,
}
}
@@ -1585,37 +1582,37 @@ func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool,
log.Tracef("Select channel %v at position %v",
edge.policy.ChannelID, i)
unifiedEdges[i] = edge
}
return runningAmt, nil
return unifiedEdges, runningAmt, nil
}
// getPathEdges returns the edges that make up the path and the total amount,
// including fees, to send the payment.
func getPathEdges(receiverAmt lnwire.MilliSatoshi, unifiers []*edgeUnifier,
bandwidthHints *bandwidthManager) ([]*unifiedEdge, lnwire.MilliSatoshi,
error) {
// receiverAmtForwardPass returns the amount that a receiver will receive after
// deducting all fees from the sender amount.
func receiverAmtForwardPass(runningAmt lnwire.MilliSatoshi,
unifiedEdges []*unifiedEdge) (lnwire.MilliSatoshi, error) {
// Now that we arrived at the start of the route and found out the route
// total amount, we make a forward pass. Because the amount may have
// been increased in the backward pass, fees need to be recalculated and
// amount ranges re-checked.
var pathEdges []*unifiedEdge
for i, unifier := range unifiers {
edge := unifier.getEdge(receiverAmt, bandwidthHints, 0)
if edge == nil {
return nil, 0, ErrNoChannel{position: i}
}
for i, edge := range unifiedEdges {
if i > 0 {
// Decrease the amount to send while going forward.
receiverAmt -= edge.policy.ComputeFeeFromIncoming(
receiverAmt,
runningAmt -= edge.policy.ComputeFeeFromIncoming(
runningAmt,
)
}
pathEdges = append(pathEdges, edge)
if !edge.amtInRange(runningAmt) {
log.Errorf("Amount %v not in range for hop %v",
runningAmt, i)
return 0, ErrNoChannel{position: i}
}
}
return pathEdges, receiverAmt, nil
return runningAmt, nil
}