routing: use unified policy for build route
This commit is contained in:
parent
a347237e7a
commit
729c3a6bd6
@ -2254,90 +2254,6 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode,
|
||||
return bandwidthHints, nil
|
||||
}
|
||||
|
||||
// runningAmounts keeps running amounts while the route is traversed.
|
||||
type runningAmounts struct {
|
||||
// amt is the intended amount to send via the route.
|
||||
amt lnwire.MilliSatoshi
|
||||
|
||||
// max is the running maximum that the route can carry.
|
||||
max lnwire.MilliSatoshi
|
||||
}
|
||||
|
||||
// prependChannel returns a new set of running amounts that would result from
|
||||
// prepending the given channel to the route. If canIncreaseAmt is set, the
|
||||
// amount may be increased if it is too small to satisfy the channel's minimum
|
||||
// htlc amount.
|
||||
func (r *runningAmounts) prependChannel(policy *channeldb.ChannelEdgePolicy,
|
||||
capacity btcutil.Amount, localChan bool, canIncreaseAmt bool) (
|
||||
runningAmounts, error) {
|
||||
|
||||
// Determine max htlc value.
|
||||
maxHtlc := lnwire.NewMSatFromSatoshis(capacity)
|
||||
if policy.MessageFlags.HasMaxHtlc() {
|
||||
maxHtlc = policy.MaxHTLC
|
||||
}
|
||||
|
||||
amt := r.amt
|
||||
|
||||
// If we have a specific amount for which we are building the route,
|
||||
// validate it against the channel constraints and return the new
|
||||
// running amount.
|
||||
if !canIncreaseAmt {
|
||||
if amt < policy.MinHTLC || amt > maxHtlc {
|
||||
return runningAmounts{}, fmt.Errorf("channel htlc "+
|
||||
"constraints [%v - %v] violated with amt %v",
|
||||
policy.MinHTLC, maxHtlc, amt)
|
||||
}
|
||||
|
||||
// Update running amount by adding the fee for non-local
|
||||
// channels.
|
||||
if !localChan {
|
||||
amt += policy.ComputeFee(amt)
|
||||
}
|
||||
|
||||
return runningAmounts{
|
||||
amt: amt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Adapt the minimum amount to what this channel allows.
|
||||
if policy.MinHTLC > r.amt {
|
||||
amt = policy.MinHTLC
|
||||
}
|
||||
|
||||
// Update the maximum amount too to be able to detect incompatible
|
||||
// channels.
|
||||
max := r.max
|
||||
if maxHtlc < r.max {
|
||||
max = maxHtlc
|
||||
}
|
||||
|
||||
// If we get in the situation that the minimum amount exceeds the
|
||||
// maximum amount (enforced further down stream), we have incompatible
|
||||
// channel policies.
|
||||
//
|
||||
// There is possibility with pubkey addressing that we should have
|
||||
// selected a different channel downstream, but we don't backtrack to
|
||||
// try to fix that. It would complicate path finding while we expect
|
||||
// this situation to be rare. The spec recommends to keep all policies
|
||||
// towards a peer identical. If that is the case, there isn't a better
|
||||
// channel that we should have selected.
|
||||
if amt > max {
|
||||
return runningAmounts{},
|
||||
fmt.Errorf("incompatible channel policies: %v "+
|
||||
"exceeds %v", amt, max)
|
||||
}
|
||||
|
||||
// Add fees to the running amounts. Skip the source node fees as
|
||||
// those do not need to be paid.
|
||||
if !localChan {
|
||||
amt += policy.ComputeFee(amt)
|
||||
max += policy.ComputeFee(max)
|
||||
}
|
||||
|
||||
return runningAmounts{amt: amt, max: max}, nil
|
||||
}
|
||||
|
||||
// ErrNoChannel is returned when a route cannot be built because there are no
|
||||
// channels that satisfy all requirements.
|
||||
type ErrNoChannel struct {
|
||||
@ -2374,24 +2290,21 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Allocate a list that will contain the selected channels for this
|
||||
// Allocate a list that will contain the unified policies for this
|
||||
// route.
|
||||
edges := make([]*channeldb.ChannelEdgePolicy, len(hops))
|
||||
edges := make([]*unifiedPolicy, len(hops))
|
||||
|
||||
// Keep a running amount and the maximum for this route.
|
||||
amts := runningAmounts{
|
||||
max: lnwire.MilliSatoshi(^uint64(0)),
|
||||
}
|
||||
var runningAmt lnwire.MilliSatoshi
|
||||
if useMinAmt {
|
||||
// For minimum amount routes, aim to deliver at least 1 msat to
|
||||
// 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.
|
||||
amts.amt = 1
|
||||
runningAmt = 1
|
||||
} else {
|
||||
// If an amount is specified, we need to build a route that
|
||||
// delivers exactly this amount to the final destination.
|
||||
amts.amt = *amt
|
||||
runningAmt = *amt
|
||||
}
|
||||
|
||||
// Traverse hops backwards to accumulate fees in the running amounts.
|
||||
@ -2408,142 +2321,85 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
||||
|
||||
localChan := i == 0
|
||||
|
||||
// Iterate over candidate channels to select the channel
|
||||
// to use for the final route.
|
||||
var (
|
||||
bestEdge *channeldb.ChannelEdgePolicy
|
||||
bestAmts *runningAmounts
|
||||
bestBandwidth lnwire.MilliSatoshi
|
||||
)
|
||||
// Build unified policies for this hop based on the channels
|
||||
// known in the graph.
|
||||
u := newUnifiedPolicies(source, toNode, outgoingChan)
|
||||
|
||||
cb := func(tx *bbolt.Tx,
|
||||
edgeInfo *channeldb.ChannelEdgeInfo,
|
||||
_, inEdge *channeldb.ChannelEdgePolicy) error {
|
||||
|
||||
chanID := edgeInfo.ChannelID
|
||||
|
||||
// Apply outgoing channel restriction is active.
|
||||
if localChan && outgoingChan != nil &&
|
||||
chanID != *outgoingChan {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// No unknown policy channels.
|
||||
if inEdge == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Before we can process the edge, we'll need to
|
||||
// fetch the node on the _other_ end of this
|
||||
// channel as we may later need to iterate over
|
||||
// the incoming edges of this node if we explore
|
||||
// it further.
|
||||
chanFromNode, err := edgeInfo.FetchOtherNode(
|
||||
tx, toNode[:],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Continue searching if this channel doesn't
|
||||
// connect with the previous hop.
|
||||
if chanFromNode.PubKeyBytes != fromNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate whether this channel's policy is satisfied
|
||||
// and obtain the new running amounts if this channel
|
||||
// was to be selected.
|
||||
newAmts, err := amts.prependChannel(
|
||||
inEdge, edgeInfo.Capacity, localChan,
|
||||
useMinAmt,
|
||||
)
|
||||
if err != nil {
|
||||
log.Tracef("Skipping chan %v: %v",
|
||||
inEdge.ChannelID, err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we already have a best edge, check whether this
|
||||
// edge is better.
|
||||
bandwidth := bandwidthHints[chanID]
|
||||
if bestEdge != nil {
|
||||
if localChan {
|
||||
// For local channels, better is defined
|
||||
// as having more bandwidth. We try to
|
||||
// maximize the chance that the returned
|
||||
// route succeeds.
|
||||
if bandwidth < bestBandwidth {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// For other channels, better is defined
|
||||
// as lower fees for the amount to send.
|
||||
// Normally all channels between two
|
||||
// nodes should have the same policy,
|
||||
// but in case not we minimize our cost
|
||||
// here. Regular path finding would do
|
||||
// the same.
|
||||
if newAmts.amt > bestAmts.amt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, the current edge is better. Replace
|
||||
// the best.
|
||||
bestEdge = inEdge
|
||||
bestAmts = &newAmts
|
||||
bestBandwidth = bandwidth
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := r.cfg.Graph.ForEachNodeChannel(nil, toNode[:], cb)
|
||||
err := u.addGraphPolicies(r.cfg.Graph, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// There is no matching channel. Stop building the route here.
|
||||
if bestEdge == nil {
|
||||
// Exit if there are no channels.
|
||||
unifiedPolicy, ok := u.policies[fromNode]
|
||||
if !ok {
|
||||
return nil, ErrNoChannel{
|
||||
fromNode: fromNode,
|
||||
position: i,
|
||||
}
|
||||
}
|
||||
|
||||
log.Tracef("Select channel %v at position %v", bestEdge.ChannelID, i)
|
||||
// If using min amt, increase amt if needed.
|
||||
if useMinAmt {
|
||||
min := unifiedPolicy.minAmt()
|
||||
if min > runningAmt {
|
||||
runningAmt = min
|
||||
}
|
||||
}
|
||||
|
||||
edges[i] = bestEdge
|
||||
amts = *bestAmts
|
||||
// Get a forwarding policy for the specific amount that we want
|
||||
// to forward.
|
||||
policy := unifiedPolicy.getPolicy(runningAmt, bandwidthHints)
|
||||
if policy == nil {
|
||||
return nil, ErrNoChannel{
|
||||
fromNode: fromNode,
|
||||
position: i,
|
||||
}
|
||||
}
|
||||
|
||||
// Add fee for this hop.
|
||||
if !localChan {
|
||||
runningAmt += policy.ComputeFee(runningAmt)
|
||||
}
|
||||
|
||||
log.Tracef("Select channel %v at position %v", policy.ChannelID, i)
|
||||
|
||||
edges[i] = unifiedPolicy
|
||||
}
|
||||
|
||||
// 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 []*channeldb.ChannelEdgePolicy
|
||||
receiverAmt := runningAmt
|
||||
for i, edge := range edges {
|
||||
policy := edge.getPolicy(receiverAmt, bandwidthHints)
|
||||
if policy == nil {
|
||||
return nil, ErrNoChannel{
|
||||
fromNode: hops[i-1],
|
||||
position: i,
|
||||
}
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
// Decrease the amount to send while going forward.
|
||||
receiverAmt -= policy.ComputeFeeFromIncoming(
|
||||
receiverAmt,
|
||||
)
|
||||
}
|
||||
|
||||
pathEdges = append(pathEdges, policy)
|
||||
}
|
||||
|
||||
// Build and return the final route.
|
||||
_, height, err := r.cfg.Chain.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var receiverAmt lnwire.MilliSatoshi
|
||||
if useMinAmt {
|
||||
// We've calculated the minimum amount for the htlc that the
|
||||
// source node hands out. The newRoute call below expects the
|
||||
// amount that must reach the receiver after subtraction of fees
|
||||
// along the way. Iterate over all edges to calculate the
|
||||
// receiver amount.
|
||||
receiverAmt = amts.amt
|
||||
for _, edge := range edges[1:] {
|
||||
receiverAmt -= edge.ComputeFeeFromIncoming(receiverAmt)
|
||||
}
|
||||
} else {
|
||||
// Deliver the specified amount to the receiver.
|
||||
receiverAmt = *amt
|
||||
}
|
||||
|
||||
// Build and return the final route.
|
||||
return newRoute(
|
||||
receiverAmt, source, edges, uint32(height),
|
||||
receiverAmt, source, pathEdges, uint32(height),
|
||||
uint16(finalCltvDelta), nil,
|
||||
)
|
||||
}
|
||||
|
@ -3434,10 +3434,10 @@ func TestBuildRoute(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check that we get the expected route back. The total amount should be
|
||||
// the amount to deliver to hop c (100 sats) plus the fee for hop b (5
|
||||
// sats).
|
||||
checkHops(rt, []uint64{1, 2})
|
||||
if rt.TotalAmount != 105000 {
|
||||
// the amount to deliver to hop c (100 sats) plus the max fee for the
|
||||
// connection b->c (6 sats).
|
||||
checkHops(rt, []uint64{1, 7})
|
||||
if rt.TotalAmount != 106000 {
|
||||
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
|
||||
}
|
||||
|
||||
@ -3450,11 +3450,11 @@ func TestBuildRoute(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check that we get the expected route back. The minimum that we can
|
||||
// send from b to c is 20 sats. Hop b charges 1 sat for the forwarding.
|
||||
// The channel between hop a and b can carry amounts in the range [5,
|
||||
// 100], so 21 sats is the minimum amount for this route.
|
||||
checkHops(rt, []uint64{1, 2})
|
||||
if rt.TotalAmount != 21000 {
|
||||
// send from b to c is 20 sats. Hop b charges 1200 msat for the
|
||||
// forwarding. The channel between hop a and b can carry amounts in the
|
||||
// range [5, 100], so 21200 msats is the minimum amount for this route.
|
||||
checkHops(rt, []uint64{1, 7})
|
||||
if rt.TotalAmount != 21200 {
|
||||
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
|
||||
}
|
||||
|
||||
|
@ -267,3 +267,15 @@ func (u *unifiedPolicy) getPolicyNetwork(
|
||||
|
||||
return &modifiedPolicy
|
||||
}
|
||||
|
||||
// minAmt returns the minimum amount that can be forwarded on this connection.
|
||||
func (u *unifiedPolicy) minAmt() lnwire.MilliSatoshi {
|
||||
min := lnwire.MaxMilliSatoshi
|
||||
for _, edge := range u.edges {
|
||||
if edge.policy.MinHTLC < min {
|
||||
min = edge.policy.MinHTLC
|
||||
}
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user