Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

routing: rewrite BuildRoute via pathfinding #6921

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 26 additions & 132 deletions routing/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
goErrors "errors"
"fmt"
"math"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -2700,147 +2701,40 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
hops []route.Vertex, outgoingChan *uint64,
finalCltvDelta int32, payAddr *[32]byte) (*route.Route, error) {

log.Tracef("BuildRoute called: hopsCount=%v, amt=%v",
len(hops), amt)

var outgoingChans map[uint64]struct{}
if outgoingChan != nil {
outgoingChans = map[uint64]struct{}{
*outgoingChan: {},
}
}

// If no amount is specified, we need to build a route for the minimum
// amount that this route can carry.
useMinAmt := amt == nil

// We'll attempt to obtain a set of bandwidth hints that helps us select
// the best outgoing channel to use in case no outgoing channel is set.
bandwidthHints, err := newBandwidthManager(
r.cachedGraph, r.selfNode.PubKeyBytes, r.cfg.GetLink,
)
if err != nil {
return nil, err
// TODO: Allow min amount route building via path finding.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required fix is to let pathfinding increase the running amount to match min_htlc. Useful independently too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6927 explores the idea

if amt == nil {
return nil, errors.New("Not implemented")
}

// Fetch the current block height outside the routing transaction, to
// prevent the rpc call blocking the database.
_, height, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
// Build last hops restriction all the way back to the source.
var lastHops []route.Vertex
target := hops[len(hops)-1]
for i := len(hops) - 2; i >= 0; i-- {
lastHops = append(lastHops, hops[i])
}
lastHops = append(lastHops, r.selfNode.PubKeyBytes)

// Allocate a list that will contain the unified policies for this
// route.
edges := make([]*unifiedPolicy, len(hops))
restrictions := &RestrictParams{
ProbabilitySource: func(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 {

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.
runningAmt = 1
} else {
// If an amount is specified, we need to build a route that
// delivers exactly this amount to the final destination.
runningAmt = *amt
return 1
},
FeeLimit: lnwire.MaxMilliSatoshi,
CltvLimit: math.MaxUint32,
PaymentAddr: payAddr,
LastHops: lastHops,
}

// Traverse hops backwards to accumulate fees in the running amounts.
source := r.selfNode.PubKeyBytes
for i := len(hops) - 1; i >= 0; i-- {
toNode := hops[i]

var fromNode route.Vertex
if i == 0 {
fromNode = source
} else {
fromNode = hops[i-1]
}

localChan := i == 0

// Build unified policies for this hop based on the channels
// known in the graph.
u := newUnifiedPolicies(source, toNode, outgoingChans)

err := u.addGraphPolicies(r.cachedGraph)
if err != nil {
return nil, err
}

// Exit if there are no channels.
unifiedPolicy, ok := u.policies[fromNode]
if !ok {
return nil, ErrNoChannel{
fromNode: fromNode,
position: i,
}
}

// If using min amt, increase amt if needed.
if useMinAmt {
min := unifiedPolicy.minAmt()
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
if outgoingChan != nil {
restrictions.OutgoingChannelIDs = []uint64{*outgoingChan}
}

// 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.CachedEdgePolicy
receiverAmt := runningAmt
for i, edge := range edges {
policy := edge.getPolicy(receiverAmt, bandwidthHints)
if policy == nil {
return nil, ErrNoChannel{
fromNode: hops[i-1],
position: i,
}
}
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v, target=%v",
len(hops), amt, target)

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.
return newRoute(
source, pathEdges, uint32(height),
finalHopParams{
amt: receiverAmt,
totalAmt: receiverAmt,
cltvDelta: uint16(finalCltvDelta),
records: nil,
paymentAddr: payAddr,
},
return r.FindRoute(
r.selfNode.PubKeyBytes, target, *amt, 0,
restrictions, nil, nil, uint16(finalCltvDelta),
)
}
42 changes: 5 additions & 37 deletions routing/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3001,7 +3001,10 @@ func TestBuildRoute(t *testing.T) {
// Setup a three node network.
chanCapSat := btcutil.Amount(100000)
paymentAddrFeatures := lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(lnwire.PaymentAddrOptional),
lnwire.NewRawFeatureVector(
lnwire.PaymentAddrOptional,
lnwire.TLVOnionPayloadOptional,
),
lnwire.Features,
)
testChannels := []*testChannel{
Expand Down Expand Up @@ -3115,42 +3118,7 @@ func TestBuildRoute(t *testing.T) {
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
}

// Build the route for the minimum amount.
rt, err = ctx.router.BuildRoute(
nil, hops, nil, 40, &payAddr,
)
if err != nil {
t.Fatal(err)
}

// Check that we get the expected route back. The minimum that we can
// 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}, payAddr)
if rt.TotalAmount != 21200 {
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
}

// Test a route that contains incompatible channel htlc constraints.
// There is no amount that can pass through both channel 5 and 4.
hops = []route.Vertex{
ctx.aliases["e"], ctx.aliases["c"],
}
_, err = ctx.router.BuildRoute(
nil, hops, nil, 40, nil,
)
errNoChannel, ok := err.(ErrNoChannel)
if !ok {
t.Fatalf("expected incompatible policies error, but got %v",
err)
}
if errNoChannel.position != 0 {
t.Fatalf("unexpected no channel error position")
}
if errNoChannel.fromNode != ctx.aliases["a"] {
t.Fatalf("unexpected no channel error node")
}
// TODO: Min amount test.
}

// edgeCreationModifier is an enum-like type used to modify steps that are
Expand Down