mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-04 03:59:58 +02:00
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
|
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
|
// ErrNoChannel is returned when a route cannot be built because there are no
|
||||||
// channels that satisfy all requirements.
|
// channels that satisfy all requirements.
|
||||||
type ErrNoChannel struct {
|
type ErrNoChannel struct {
|
||||||
@ -2374,24 +2290,21 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
return nil, err
|
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.
|
// route.
|
||||||
edges := make([]*channeldb.ChannelEdgePolicy, len(hops))
|
edges := make([]*unifiedPolicy, len(hops))
|
||||||
|
|
||||||
// Keep a running amount and the maximum for this route.
|
var runningAmt lnwire.MilliSatoshi
|
||||||
amts := runningAmounts{
|
|
||||||
max: lnwire.MilliSatoshi(^uint64(0)),
|
|
||||||
}
|
|
||||||
if useMinAmt {
|
if useMinAmt {
|
||||||
// For minimum amount routes, aim to deliver at least 1 msat to
|
// For minimum amount routes, aim to deliver at least 1 msat to
|
||||||
// the destination. There are nodes in the wild that have a
|
// the destination. There are nodes in the wild that have a
|
||||||
// min_htlc channel policy of zero, which could lead to a zero
|
// min_htlc channel policy of zero, which could lead to a zero
|
||||||
// amount payment being made.
|
// amount payment being made.
|
||||||
amts.amt = 1
|
runningAmt = 1
|
||||||
} else {
|
} else {
|
||||||
// If an amount is specified, we need to build a route that
|
// If an amount is specified, we need to build a route that
|
||||||
// delivers exactly this amount to the final destination.
|
// delivers exactly this amount to the final destination.
|
||||||
amts.amt = *amt
|
runningAmt = *amt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse hops backwards to accumulate fees in the running amounts.
|
// Traverse hops backwards to accumulate fees in the running amounts.
|
||||||
@ -2408,142 +2321,85 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
|
|
||||||
localChan := i == 0
|
localChan := i == 0
|
||||||
|
|
||||||
// Iterate over candidate channels to select the channel
|
// Build unified policies for this hop based on the channels
|
||||||
// to use for the final route.
|
// known in the graph.
|
||||||
var (
|
u := newUnifiedPolicies(source, toNode, outgoingChan)
|
||||||
bestEdge *channeldb.ChannelEdgePolicy
|
|
||||||
bestAmts *runningAmounts
|
|
||||||
bestBandwidth lnwire.MilliSatoshi
|
|
||||||
)
|
|
||||||
|
|
||||||
cb := func(tx *bbolt.Tx,
|
err := u.addGraphPolicies(r.cfg.Graph, nil)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is no matching channel. Stop building the route here.
|
// Exit if there are no channels.
|
||||||
if bestEdge == nil {
|
unifiedPolicy, ok := u.policies[fromNode]
|
||||||
|
if !ok {
|
||||||
return nil, ErrNoChannel{
|
return nil, ErrNoChannel{
|
||||||
fromNode: fromNode,
|
fromNode: fromNode,
|
||||||
position: i,
|
position: i,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("Select channel %v at position %v", bestEdge.ChannelID, i)
|
// If using min amt, increase amt if needed.
|
||||||
|
if useMinAmt {
|
||||||
edges[i] = bestEdge
|
min := unifiedPolicy.minAmt()
|
||||||
amts = *bestAmts
|
if min > runningAmt {
|
||||||
|
runningAmt = min
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
_, height, err := r.cfg.Chain.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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(
|
return newRoute(
|
||||||
receiverAmt, source, edges, uint32(height),
|
receiverAmt, source, pathEdges, uint32(height),
|
||||||
uint16(finalCltvDelta), nil,
|
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
|
// 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
|
// the amount to deliver to hop c (100 sats) plus the max fee for the
|
||||||
// sats).
|
// connection b->c (6 sats).
|
||||||
checkHops(rt, []uint64{1, 2})
|
checkHops(rt, []uint64{1, 7})
|
||||||
if rt.TotalAmount != 105000 {
|
if rt.TotalAmount != 106000 {
|
||||||
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
|
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
|
// 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.
|
// send from b to c is 20 sats. Hop b charges 1200 msat for the
|
||||||
// The channel between hop a and b can carry amounts in the range [5,
|
// forwarding. The channel between hop a and b can carry amounts in the
|
||||||
// 100], so 21 sats is the minimum amount for this route.
|
// range [5, 100], so 21200 msats is the minimum amount for this route.
|
||||||
checkHops(rt, []uint64{1, 2})
|
checkHops(rt, []uint64{1, 7})
|
||||||
if rt.TotalAmount != 21000 {
|
if rt.TotalAmount != 21200 {
|
||||||
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
|
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,3 +267,15 @@ func (u *unifiedPolicy) getPolicyNetwork(
|
|||||||
|
|
||||||
return &modifiedPolicy
|
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…
x
Reference in New Issue
Block a user