mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-07-03 20:16:02 +02:00
routing: modify FindRoutes to take a max number of routes to return
This commit is contained in:
@ -778,7 +778,7 @@ func TestPathFindSpecExample(t *testing.T) {
|
|||||||
// Query for a route of 4,999,999 mSAT to carol.
|
// Query for a route of 4,999,999 mSAT to carol.
|
||||||
carol := ctx.aliases["C"]
|
carol := ctx.aliases["C"]
|
||||||
const amt lnwire.MilliSatoshi = 4999999
|
const amt lnwire.MilliSatoshi = 4999999
|
||||||
routes, err := ctx.router.FindRoutes(carol, amt)
|
routes, err := ctx.router.FindRoutes(carol, amt, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find route: %v", err)
|
t.Fatalf("unable to find route: %v", err)
|
||||||
}
|
}
|
||||||
@ -838,7 +838,7 @@ func TestPathFindSpecExample(t *testing.T) {
|
|||||||
|
|
||||||
// We'll now request a route from A -> B -> C.
|
// We'll now request a route from A -> B -> C.
|
||||||
ctx.router.routeCache = make(map[routeTuple][]*Route)
|
ctx.router.routeCache = make(map[routeTuple][]*Route)
|
||||||
routes, err = ctx.router.FindRoutes(carol, amt)
|
routes, err = ctx.router.FindRoutes(carol, amt, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find routes: %v", err)
|
t.Fatalf("unable to find routes: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1157,7 +1157,6 @@ func pruneNodeFromRoutes(routes []*Route, skipNode Vertex) []*Route {
|
|||||||
// pruneChannelFromRoutes accepts a set of routes, and returns a new set of
|
// pruneChannelFromRoutes accepts a set of routes, and returns a new set of
|
||||||
// routes with the target channel filtered out.
|
// routes with the target channel filtered out.
|
||||||
func pruneChannelFromRoutes(routes []*Route, skipChan uint64) []*Route {
|
func pruneChannelFromRoutes(routes []*Route, skipChan uint64) []*Route {
|
||||||
|
|
||||||
prunedRoutes := make([]*Route, 0, len(routes))
|
prunedRoutes := make([]*Route, 0, len(routes))
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route.containsChannel(skipChan) {
|
if route.containsChannel(skipChan) {
|
||||||
@ -1173,17 +1172,70 @@ func pruneChannelFromRoutes(routes []*Route, skipChan uint64) []*Route {
|
|||||||
return prunedRoutes
|
return prunedRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindRoutes attempts to query the ChannelRouter for the all available paths
|
// pathsToFeeSortedRoutes takes a set of paths, and returns a corresponding set
|
||||||
// to a particular target destination which is able to send `amt` after
|
// of of routes. A route differs from a path in that it has full time-lock and
|
||||||
// factoring in channel capacities and cumulative fees along each route route.
|
// fee information attached. The set of routes return ed may be less than the
|
||||||
// To find all eligible paths, we use a modified version of Yen's algorithm
|
// initial set of paths as it's possible we drop a route if it can't handle the
|
||||||
// which itself uses a modified version of Dijkstra's algorithm within its
|
// total payment flow after fees are calculated.
|
||||||
// inner loop. Once we have a set of candidate routes, we calculate the
|
func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop, finalCLTVDelta uint16,
|
||||||
// required fee and time lock values running backwards along the route. The
|
amt lnwire.MilliSatoshi, currentHeight uint32) ([]*Route, error) {
|
||||||
|
|
||||||
|
validRoutes := make([]*Route, 0, len(paths))
|
||||||
|
for _, path := range paths {
|
||||||
|
// Attempt to make the path into a route. We snip off the first
|
||||||
|
// hop in the path as it contains a "self-hop" that is inserted
|
||||||
|
// by our KSP algorithm.
|
||||||
|
route, err := newRoute(
|
||||||
|
amt, source, path[1:], currentHeight, finalCLTVDelta,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(roasbeef): report straw breaking edge?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path as enough total flow to support the computed
|
||||||
|
// route, then we'll add it to our set of valid routes.
|
||||||
|
validRoutes = append(validRoutes, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all our perspective routes were eliminating during the transition
|
||||||
|
// from path to route, then we'll return an error to the caller
|
||||||
|
if len(validRoutes) == 0 {
|
||||||
|
return nil, newErr(ErrNoPathFound, "unable to find a path to "+
|
||||||
|
"destination")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we'll sort the set of validate routes to optimize for
|
||||||
|
// lowest total fees, using the required time-lock within the route as
|
||||||
|
// a tie-breaker.
|
||||||
|
sort.Slice(validRoutes, func(i, j int) bool {
|
||||||
|
// To make this decision we first check if the total fees
|
||||||
|
// required for both routes are equal. If so, then we'll let
|
||||||
|
// the total time lock be the tie breaker. Otherwise, we'll put
|
||||||
|
// the route with the lowest total fees first.
|
||||||
|
if validRoutes[i].TotalFees == validRoutes[j].TotalFees {
|
||||||
|
timeLockI := validRoutes[i].TotalTimeLock
|
||||||
|
timeLockJ := validRoutes[j].TotalTimeLock
|
||||||
|
return timeLockI < timeLockJ
|
||||||
|
}
|
||||||
|
|
||||||
|
return validRoutes[i].TotalFees < validRoutes[j].TotalFees
|
||||||
|
})
|
||||||
|
|
||||||
|
return validRoutes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindRoutes attempts to query the ChannelRouter for a bounded number
|
||||||
|
// available paths to a particular target destination which is able to send
|
||||||
|
// `amt` after factoring in channel capacities and cumulative fees along each
|
||||||
|
// route route. To `numPaths eligible paths, we use a modified version of
|
||||||
|
// Yen's algorithm which itself uses a modified version of Dijkstra's algorithm
|
||||||
|
// within its inner loop. Once we have a set of candidate routes, we calculate
|
||||||
|
// the required fee and time lock values running backwards along the route. The
|
||||||
// route that will be ranked the highest is the one with the lowest cumulative
|
// route that will be ranked the highest is the one with the lowest cumulative
|
||||||
// fee along the route.
|
// fee along the route.
|
||||||
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
||||||
amt lnwire.MilliSatoshi, finalExpiry ...uint16) ([]*Route, error) {
|
amt lnwire.MilliSatoshi, numPaths uint32, finalExpiry ...uint16) ([]*Route, error) {
|
||||||
|
|
||||||
var finalCLTVDelta uint16
|
var finalCLTVDelta uint16
|
||||||
if len(finalExpiry) == 0 {
|
if len(finalExpiry) == 0 {
|
||||||
@ -1192,8 +1244,6 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
|||||||
finalCLTVDelta = finalExpiry[0]
|
finalCLTVDelta = finalExpiry[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): make num routes a param
|
|
||||||
|
|
||||||
dest := target.SerializeCompressed()
|
dest := target.SerializeCompressed()
|
||||||
log.Debugf("Searching for path to %x, sending %v", dest, amt)
|
log.Debugf("Searching for path to %x, sending %v", dest, amt)
|
||||||
|
|
||||||
@ -1242,8 +1292,9 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
|||||||
// Now that we know the destination is reachable within the graph,
|
// Now that we know the destination is reachable within the graph,
|
||||||
// we'll execute our KSP algorithm to find the k-shortest paths from
|
// we'll execute our KSP algorithm to find the k-shortest paths from
|
||||||
// our source to the destination.
|
// our source to the destination.
|
||||||
shortestPaths, err := findPaths(tx, r.cfg.Graph, r.selfNode, target,
|
shortestPaths, err := findPaths(
|
||||||
amt)
|
tx, r.cfg.Graph, r.selfNode, target, amt, numPaths,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1256,55 +1307,23 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
|||||||
// each path. During this process, some paths may be discarded if they
|
// each path. During this process, some paths may be discarded if they
|
||||||
// aren't able to support the total satoshis flow once fees have been
|
// aren't able to support the total satoshis flow once fees have been
|
||||||
// factored in.
|
// factored in.
|
||||||
validRoutes := make([]*Route, 0, len(shortestPaths))
|
|
||||||
sourceVertex := Vertex(r.selfNode.PubKeyBytes)
|
sourceVertex := Vertex(r.selfNode.PubKeyBytes)
|
||||||
for _, path := range shortestPaths {
|
validRoutes, err := pathsToFeeSortedRoutes(
|
||||||
// Attempt to make the path into a route. We snip off the first
|
sourceVertex, shortestPaths, finalCLTVDelta, amt,
|
||||||
// hop in the path as it contains a "self-hop" that is inserted
|
uint32(currentHeight),
|
||||||
// by our KSP algorithm.
|
)
|
||||||
route, err := newRoute(amt, sourceVertex, path[1:],
|
if err != nil {
|
||||||
uint32(currentHeight), finalCLTVDelta)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the path as enough total flow to support the computed
|
|
||||||
// route, then we'll add it to our set of valid routes.
|
|
||||||
validRoutes = append(validRoutes, route)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all our perspective routes were eliminating during the transition
|
|
||||||
// from path to route, then we'll return an error to the caller
|
|
||||||
if len(validRoutes) == 0 {
|
|
||||||
return nil, newErr(ErrNoPathFound, "unable to find a path to "+
|
|
||||||
"destination")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, we'll sort the set of validate routes to optimize for
|
|
||||||
// lowest total fees, using the required time-lock within the
|
|
||||||
// route as a tie-breaker.
|
|
||||||
sort.Slice(validRoutes, func(i, j int) bool {
|
|
||||||
// To make this decision we first check if the total fees
|
|
||||||
// required for both routes are equal. If so, then we'll let
|
|
||||||
// the total time lock be the tie breaker. Otherwise, we'll
|
|
||||||
// put the route with the lowest total fees first.
|
|
||||||
if validRoutes[i].TotalFees == validRoutes[j].TotalFees {
|
|
||||||
timeLockI := validRoutes[i].TotalTimeLock
|
|
||||||
timeLockJ := validRoutes[j].TotalTimeLock
|
|
||||||
return timeLockI < timeLockJ
|
|
||||||
}
|
|
||||||
|
|
||||||
return validRoutes[i].TotalFees < validRoutes[j].TotalFees
|
|
||||||
})
|
|
||||||
|
|
||||||
go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes),
|
go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes),
|
||||||
amt, dest, newLogClosure(func() string {
|
amt, dest, newLogClosure(func() string {
|
||||||
return spew.Sdump(validRoutes)
|
return spew.Sdump(validRoutes)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Populate the cache with this set of fresh routes so we can
|
// Populate the cache with this set of fresh routes so we can reuse
|
||||||
// reuse them in the future.
|
// them in the future.
|
||||||
r.routeCacheMtx.Lock()
|
r.routeCacheMtx.Lock()
|
||||||
r.routeCache[rt] = validRoutes
|
r.routeCache[rt] = validRoutes
|
||||||
r.routeCacheMtx.Unlock()
|
r.routeCacheMtx.Unlock()
|
||||||
|
Reference in New Issue
Block a user