lnrpc+routing: remove k shortest path finding

This commit is contained in:
Joost Jager 2019-05-07 17:01:01 +02:00
parent c4415f0400
commit 7a5bd29a69
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
13 changed files with 714 additions and 1228 deletions

View File

@ -2977,11 +2977,6 @@ var queryRoutesCommand = cli.Command{
Usage: "percentage of the payment's amount used as the " +
"maximum fee allowed when sending the payment",
},
cli.Int64Flag{
Name: "num_max_routes",
Usage: "the max number of routes to be returned",
Value: 10,
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "(optional) number of blocks the last hop has to reveal " +
@ -3035,7 +3030,6 @@ func queryRoutes(ctx *cli.Context) error {
PubKey: dest,
Amt: amt,
FeeLimit: feeLimit,
NumRoutes: int32(ctx.Int("num_max_routes")),
FinalCltvDelta: int32(ctx.Int("final_cltv_delta")),
}

View File

@ -1460,7 +1460,6 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: carol.PubKeyStr,
Amt: int64(payAmt),
NumRoutes: 1,
FinalCltvDelta: defaultTimeLockDelta,
}
@ -4270,7 +4269,6 @@ func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: net.Bob.PubKeyStr,
Amt: paymentAmt,
NumRoutes: 1,
FinalCltvDelta: defaultBitcoinTimeLockDelta,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
@ -4442,7 +4440,6 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: carol.PubKeyStr,
Amt: paymentAmt,
NumRoutes: 1,
FinalCltvDelta: defaultBitcoinTimeLockDelta,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
@ -4603,9 +4600,8 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest)
// Query routes from Carol to Charlie which will be an invalid route
// for Alice -> Bob.
fakeReq := &lnrpc.QueryRoutesRequest{
PubKey: charlie.PubKeyStr,
Amt: int64(1),
NumRoutes: 1,
PubKey: charlie.PubKeyStr,
Amt: int64(1),
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq)
@ -12342,9 +12338,8 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) {
// Query for routes to pay from Alice to Dave.
const paymentAmt = 1000
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
NumRoutes: 1,
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq)
@ -12646,10 +12641,9 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
// payments.
testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) {
queryRoutesReq := &lnrpc.QueryRoutesRequest{
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
FeeLimit: feeLimit,
NumRoutes: 2,
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
FeeLimit: feeLimit,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq)
@ -12657,11 +12651,6 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to get routes: %v", err)
}
if len(routesResp.Routes) != 1 {
t.Fatalf("expected one route, got %d",
len(routesResp.Routes))
}
checkRoute(routesResp.Routes[0])
invoice := &lnrpc.Invoice{Value: paymentAmt}

View File

@ -28,10 +28,9 @@ type RouterBackend struct {
// FindRoutes is a closure that abstracts away how we locate/query for
// routes.
FindRoutes func(source, target route.Vertex,
FindRoute func(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
numPaths uint32, finalExpiry ...uint16) (
[]*route.Route, error)
finalExpiry ...uint16) (*route.Route, error)
}
// QueryRoutes attempts to query the daemons' Channel Router for a possible
@ -122,53 +121,35 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
IgnoredEdges: ignoredEdges,
}
// numRoutes will default to 10 if not specified explicitly.
numRoutesIn := uint32(in.NumRoutes)
if numRoutesIn == 0 {
numRoutesIn = 10
}
// Query the channel router for a possible path to the destination that
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route.
var (
routes []*route.Route
route *route.Route
findErr error
)
if in.FinalCltvDelta == 0 {
routes, findErr = r.FindRoutes(
route, findErr = r.FindRoute(
sourcePubKey, targetPubKey, amtMSat, restrictions,
numRoutesIn,
)
} else {
routes, findErr = r.FindRoutes(
route, findErr = r.FindRoute(
sourcePubKey, targetPubKey, amtMSat, restrictions,
numRoutesIn, uint16(in.FinalCltvDelta),
uint16(in.FinalCltvDelta),
)
}
if findErr != nil {
return nil, findErr
}
// As the number of returned routes can be less than the number of
// requested routes, we'll clamp down the length of the response to the
// minimum of the two.
numRoutes := uint32(len(routes))
if numRoutesIn < numRoutes {
numRoutes = numRoutesIn
}
// For each valid route, we'll convert the result into the format
// required by the RPC system.
rpcRoute := r.MarshallRoute(route)
routeResp := &lnrpc.QueryRoutesResponse{
Routes: make([]*lnrpc.Route, 0, in.NumRoutes),
}
for i := uint32(0); i < numRoutes; i++ {
routeResp.Routes = append(
routeResp.Routes,
r.MarshallRoute(routes[i]),
)
Routes: []*lnrpc.Route{rpcRoute},
}
return routeResp, nil

View File

@ -42,7 +42,6 @@ func TestQueryRoutes(t *testing.T) {
request := &lnrpc.QueryRoutesRequest{
PubKey: destKey,
Amt: 100000,
NumRoutes: 1,
FinalCltvDelta: 100,
FeeLimit: &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
@ -58,19 +57,14 @@ func TestQueryRoutes(t *testing.T) {
rt := &route.Route{}
findRoutes := func(source, target route.Vertex,
findRoute := func(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
numPaths uint32, finalExpiry ...uint16) (
[]*route.Route, error) {
finalExpiry ...uint16) (*route.Route, error) {
if int64(amt) != request.Amt*1000 {
t.Fatal("unexpected amount")
}
if numPaths != 1 {
t.Fatal("unexpected number of routes")
}
if source != sourceKey {
t.Fatal("unexpected source key")
}
@ -101,14 +95,12 @@ func TestQueryRoutes(t *testing.T) {
t.Fatal("unexpected ignored node")
}
return []*route.Route{
rt,
}, nil
return rt, nil
}
backend := &RouterBackend{
MaxPaymentMSat: lnwire.NewMSatFromSatoshis(1000000),
FindRoutes: findRoutes,
FindRoute: findRoute,
SelfNode: route.Vertex{1, 2, 3},
FetchChannelCapacity: func(chanID uint64) (
btcutil.Amount, error) {

View File

@ -247,22 +247,18 @@ func (s *Server) EstimateRouteFee(ctx context.Context,
// Finally, we'll query for a route to the destination that can carry
// that target amount, we'll only request a single route.
routes, err := s.cfg.Router.FindRoutes(
route, err := s.cfg.Router.FindRoute(
s.cfg.RouterBackend.SelfNode, destNode, amtMsat,
&routing.RestrictParams{
FeeLimit: feeLimit,
}, 1,
},
)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, fmt.Errorf("unable to find route to dest: %v", err)
}
return &RouteFeeResponse{
RoutingFeeMsat: int64(routes[0].TotalFees),
TimeLockDelay: int64(routes[0].TotalTimeLock),
RoutingFeeMsat: int64(route.TotalFees),
TimeLockDelay: int64(route.TotalTimeLock),
}, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1606,11 +1606,7 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
int64 amt = 2;
/**
Deprecated. The max number of routes to return. In the future, QueryRoutes
will only return a single route.
*/
int32 num_routes = 3 [deprecated = true];
reserved 3;
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop
int32 final_cltv_delta = 4;

View File

@ -673,14 +673,6 @@
"type": "string",
"format": "int64"
},
{
"name": "num_routes",
"description": "*\nDeprecated. The max number of routes to return. In the future, QueryRoutes\nwill only return a single route.",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "final_cltv_delta",
"description": "/ An optional CLTV delta from the current height that should be used for the timelock of the final hop.",

View File

@ -616,180 +616,3 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
return pathEdges, nil
}
// findPaths implements a k-shortest paths algorithm to find all the reachable
// paths between the passed source and target. The algorithm will continue to
// traverse the graph until all possible candidate paths have been depleted.
// This function implements a modified version of Yen's. To find each path
// itself, we utilize our modified version of Dijkstra's found above. When
// examining possible spur and root paths, rather than removing edges or
// Vertexes from the graph, we instead utilize a Vertex+edge black-list that
// will be ignored by our modified Dijkstra's algorithm. With this approach, we
// make our inner path finding algorithm aware of our k-shortest paths
// algorithm, rather than attempting to use an unmodified path finding
// algorithm in a block box manner.
func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
source, target route.Vertex, amt lnwire.MilliSatoshi,
restrictions *RestrictParams, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) (
[][]*channeldb.ChannelEdgePolicy, error) {
// TODO(roasbeef): modifying ordering within heap to eliminate final
// sorting step?
var (
shortestPaths [][]*channeldb.ChannelEdgePolicy
candidatePaths pathHeap
)
// First we'll find a single shortest path from the source (our
// selfNode) to the target destination that's capable of carrying amt
// satoshis along the path before fees are calculated.
startingPath, err := findPath(
&graphParams{
tx: tx,
graph: graph,
bandwidthHints: bandwidthHints,
},
restrictions, source, target, amt,
)
if err != nil {
log.Errorf("Unable to find path: %v", err)
return nil, err
}
// Manually insert a "self" edge emanating from ourselves. This
// self-edge is required in order for the path finding algorithm to
// function properly.
firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1)
firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{
Node: &channeldb.LightningNode{PubKeyBytes: source},
})
firstPath = append(firstPath, startingPath...)
shortestPaths = append(shortestPaths, firstPath)
// While we still have candidate paths to explore we'll keep exploring
// the sub-graphs created to find the next k-th shortest path.
for k := uint32(1); k < numPaths; k++ {
prevShortest := shortestPaths[k-1]
// We'll examine each edge in the previous iteration's shortest
// path in order to find path deviations from each node in the
// path.
for i := 0; i < len(prevShortest)-1; i++ {
// These two maps will mark the edges and Vertexes
// we'll exclude from the next path finding attempt.
// These are required to ensure the paths are unique
// and loopless.
ignoredEdges := make(map[EdgeLocator]struct{})
ignoredVertexes := make(map[route.Vertex]struct{})
for e := range restrictions.IgnoredEdges {
ignoredEdges[e] = struct{}{}
}
for n := range restrictions.IgnoredNodes {
ignoredVertexes[n] = struct{}{}
}
// Our spur node is the i-th node in the prior shortest
// path, and our root path will be all nodes in the
// path leading up to our spurNode.
spurNode := prevShortest[i].Node
rootPath := prevShortest[:i+1]
// Before we kickoff our next path finding iteration,
// we'll find all the edges we need to ignore in this
// next round. This ensures that we create a new unique
// path.
for _, path := range shortestPaths {
// If our current rootPath is a prefix of this
// shortest path, then we'll remove the edge
// directly _after_ our spur node from the
// graph so we don't repeat paths.
if len(path) > i+1 &&
isSamePath(rootPath, path[:i+1]) {
locator := newEdgeLocator(path[i+1])
ignoredEdges[*locator] = struct{}{}
}
}
// Next we'll remove all entries in the root path that
// aren't the current spur node from the graph. This
// ensures we don't create a path with loops.
for _, hop := range rootPath {
node := hop.Node.PubKeyBytes
if node == spurNode.PubKeyBytes {
continue
}
ignoredVertexes[route.Vertex(node)] = struct{}{}
}
// With the edges that are part of our root path, and
// the Vertexes (other than the spur path) within the
// root path removed, we'll attempt to find another
// shortest path from the spur node to the destination.
//
// TODO: Fee limit passed to spur path finding isn't
// correct, because it doesn't take into account the
// fees already paid on the root path.
//
// TODO: Outgoing channel restriction isn't obeyed for
// spur paths.
spurRestrictions := &RestrictParams{
IgnoredEdges: ignoredEdges,
IgnoredNodes: ignoredVertexes,
FeeLimit: restrictions.FeeLimit,
}
spurPath, err := findPath(
&graphParams{
tx: tx,
graph: graph,
bandwidthHints: bandwidthHints,
},
spurRestrictions, spurNode.PubKeyBytes,
target, amt,
)
// If we weren't able to find a path, we'll continue to
// the next round.
if IsError(err, ErrNoPathFound) {
continue
} else if err != nil {
return nil, err
}
// Create the new combined path by concatenating the
// rootPath to the spurPath.
newPathLen := len(rootPath) + len(spurPath)
newPath := path{
hops: make([]*channeldb.ChannelEdgePolicy, 0, newPathLen),
dist: newPathLen,
}
newPath.hops = append(newPath.hops, rootPath...)
newPath.hops = append(newPath.hops, spurPath...)
// TODO(roasbeef): add and consult path finger print
// We'll now add this newPath to the heap of candidate
// shortest paths.
heap.Push(&candidatePaths, newPath)
}
// If our min-heap of candidate paths is empty, then we can
// exit early.
if candidatePaths.Len() == 0 {
break
}
// To conclude this latest iteration, we'll take the shortest
// path in our set of candidate paths and add it to our
// shortestPaths list as the *next* shortest path.
nextShortestPath := heap.Pop(&candidatePaths).(path).hops
shortestPaths = append(shortestPaths, nextShortestPath)
}
return shortestPaths, nil
}

View File

@ -948,63 +948,6 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
}
func TestKShortestPathFinding(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
defer graph.cleanUp()
sourceNode, err := graph.graph.SourceNode()
if err != nil {
t.Fatalf("unable to fetch source node: %v", err)
}
// In this test we'd like to ensure that our algorithm to find the
// k-shortest paths from a given source node to any destination node
// works as expected.
// In our basic_graph.json, there exist two paths from roasbeef to luo
// ji. Our algorithm should properly find both paths, and also rank
// them in order of their total "distance".
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := graph.aliasMap["luoji"]
restrictions := &RestrictParams{
FeeLimit: noFeeLimit,
}
paths, err := findPaths(
nil, graph.graph, sourceNode.PubKeyBytes, target, paymentAmt,
restrictions, 100, nil,
)
if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+
"luo ji: %v", err)
}
// The algorithm should have found two paths from roasbeef to luo ji.
if len(paths) != 2 {
t.Fatalf("two path shouldn't been found, instead %v were",
len(paths))
}
// Additionally, the total hop length of the first path returned should
// be _less_ than that of the second path returned.
if len(paths[0]) > len(paths[1]) {
t.Fatalf("paths found not ordered properly")
}
// The first route should be a direct route to luo ji.
assertExpectedPath(t, graph.aliasMap, paths[0], "roasbeef", "luoji")
// The second route should be a route to luo ji via satoshi.
assertExpectedPath(
t, graph.aliasMap, paths[1], "roasbeef", "satoshi", "luoji",
)
}
// TestNewRoute tests whether the construction of hop payloads by newRoute
// is executed correctly.
func TestNewRoute(t *testing.T) {
@ -1732,45 +1675,38 @@ func TestPathFindSpecExample(t *testing.T) {
// Query for a route of 4,999,999 mSAT to carol.
carol := ctx.aliases["C"]
const amt lnwire.MilliSatoshi = 4999999
routes, err := ctx.router.FindRoutes(
bobNode.PubKeyBytes, carol, amt, noRestrictions, 100,
route, err := ctx.router.FindRoute(
bobNode.PubKeyBytes, carol, amt, noRestrictions,
)
if err != nil {
t.Fatalf("unable to find route: %v", err)
}
// We should come back with _exactly_ two routes.
if len(routes) != 2 {
t.Fatalf("expected %v routes, instead have: %v", 2,
len(routes))
}
// Now we'll examine the first route returned for correctness.
// Now we'll examine the route returned for correctness.
//
// It should be sending the exact payment amount as there are no
// additional hops.
firstRoute := routes[0]
if firstRoute.TotalAmount != amt {
if route.TotalAmount != amt {
t.Fatalf("wrong total amount: got %v, expected %v",
firstRoute.TotalAmount, amt)
route.TotalAmount, amt)
}
if firstRoute.Hops[0].AmtToForward != amt {
if route.Hops[0].AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
firstRoute.Hops[0].AmtToForward, amt)
route.Hops[0].AmtToForward, amt)
}
fee := firstRoute.HopFee(0)
fee := route.HopFee(0)
if fee != 0 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
}
// The CLTV expiry should be the current height plus 9 (the expiry for
// the B -> C channel.
if firstRoute.TotalTimeLock !=
if route.TotalTimeLock !=
startingHeight+zpay32.DefaultFinalCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
firstRoute.TotalTimeLock,
route.TotalTimeLock,
startingHeight+zpay32.DefaultFinalCLTVDelta)
}
@ -1798,48 +1734,38 @@ func TestPathFindSpecExample(t *testing.T) {
}
// We'll now request a route from A -> B -> C.
routes, err = ctx.router.FindRoutes(
source.PubKeyBytes, carol, amt, noRestrictions, 100,
route, err = ctx.router.FindRoute(
source.PubKeyBytes, carol, amt, noRestrictions,
)
if err != nil {
t.Fatalf("unable to find routes: %v", err)
}
// We should come back with _exactly_ two routes.
if len(routes) != 2 {
t.Fatalf("expected %v routes, instead have: %v", 2,
len(routes))
}
// Both routes should be two hops.
if len(routes[0].Hops) != 2 {
// The route should be two hops.
if len(route.Hops) != 2 {
t.Fatalf("route should be %v hops, is instead %v", 2,
len(routes[0].Hops))
}
if len(routes[1].Hops) != 2 {
t.Fatalf("route should be %v hops, is instead %v", 2,
len(routes[1].Hops))
len(route.Hops))
}
// The total amount should factor in a fee of 10199 and also use a CLTV
// delta total of 29 (20 + 9),
expectedAmt := lnwire.MilliSatoshi(5010198)
if routes[0].TotalAmount != expectedAmt {
if route.TotalAmount != expectedAmt {
t.Fatalf("wrong amount: got %v, expected %v",
routes[0].TotalAmount, expectedAmt)
route.TotalAmount, expectedAmt)
}
if routes[0].TotalTimeLock != startingHeight+29 {
if route.TotalTimeLock != startingHeight+29 {
t.Fatalf("wrong total time lock: got %v, expecting %v",
routes[0].TotalTimeLock, startingHeight+29)
route.TotalTimeLock, startingHeight+29)
}
// Ensure that the hops of the first route are properly crafted.
// Ensure that the hops of the route are properly crafted.
//
// After taking the fee, Bob should be forwarding the remainder which
// is the exact payment to Bob.
if routes[0].Hops[0].AmtToForward != amt {
if route.Hops[0].AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
routes[0].Hops[0].AmtToForward, amt)
route.Hops[0].AmtToForward, amt)
}
// We shouldn't pay any fee for the first, hop, but the fee for the
@ -1850,70 +1776,31 @@ func TestPathFindSpecExample(t *testing.T) {
//
// * 200 + 4999999 * 2000 / 1000000 = 10199
fee = routes[0].HopFee(0)
fee = route.HopFee(0)
if fee != 10199 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 10199)
}
// While for the final hop, as there's no additional hop afterwards, we
// pay no fee.
fee = routes[0].HopFee(1)
fee = route.HopFee(1)
if fee != 0 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
}
// The outgoing CLTV value itself should be the current height plus 30
// to meet Carol's requirements.
if routes[0].Hops[0].OutgoingTimeLock !=
if route.Hops[0].OutgoingTimeLock !=
startingHeight+zpay32.DefaultFinalCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
routes[0].Hops[0].OutgoingTimeLock,
route.Hops[0].OutgoingTimeLock,
startingHeight+zpay32.DefaultFinalCLTVDelta)
}
// For B -> C, we assert that the final hop also has the proper
// parameters.
lastHop := routes[0].Hops[1]
if lastHop.AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
lastHop.AmtToForward, amt)
}
if lastHop.OutgoingTimeLock !=
startingHeight+zpay32.DefaultFinalCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
lastHop.OutgoingTimeLock,
startingHeight+zpay32.DefaultFinalCLTVDelta)
}
// We'll also make similar assertions for the second route from A to C
// via D.
secondRoute := routes[1]
expectedAmt = 5020398
if secondRoute.TotalAmount != expectedAmt {
t.Fatalf("wrong amount: got %v, expected %v",
secondRoute.TotalAmount, expectedAmt)
}
expectedTimeLock := startingHeight + daveFinalCLTV + zpay32.DefaultFinalCLTVDelta
if secondRoute.TotalTimeLock != uint32(expectedTimeLock) {
t.Fatalf("wrong total time lock: got %v, expecting %v",
secondRoute.TotalTimeLock, expectedTimeLock)
}
onionPayload := secondRoute.Hops[0]
if onionPayload.AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
onionPayload.AmtToForward, amt)
}
expectedTimeLock = startingHeight + zpay32.DefaultFinalCLTVDelta
if onionPayload.OutgoingTimeLock != uint32(expectedTimeLock) {
t.Fatalf("wrong outgoing time lock: got %v, expecting %v",
onionPayload.OutgoingTimeLock,
expectedTimeLock)
}
// The B -> C hop should also be identical as the prior cases.
lastHop = secondRoute.Hops[1]
lastHop := route.Hops[1]
if lastHop.AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
lastHop.AmtToForward, amt)

View File

@ -5,7 +5,6 @@ import (
"crypto/sha256"
"fmt"
"runtime"
"sort"
"sync"
"sync/atomic"
"time"
@ -1312,72 +1311,12 @@ type routingMsg struct {
err chan error
}
// pathsToFeeSortedRoutes takes a set of paths, and returns a corresponding set
// of routes. A route differs from a path in that it has full time-lock and
// fee information attached. The set of routes returned may be less than the
// initial set of paths as it's possible we drop a route if it can't handle the
// total payment flow after fees are calculated.
func pathsToFeeSortedRoutes(source route.Vertex, paths [][]*channeldb.ChannelEdgePolicy,
finalCLTVDelta uint16, amt lnwire.MilliSatoshi,
currentHeight uint32) ([]*route.Route, error) {
validRoutes := make([]*route.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. 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
// fee along the route.
func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *RestrictParams, numPaths uint32,
finalExpiry ...uint16) ([]*route.Route, error) {
// FindRoute attempts to query the ChannelRouter for the optimum path to a
// particular target destination to which it is able to send `amt` after
// factoring in channel capacities and cumulative fees along the route.
func (r *ChannelRouter) FindRoute(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *RestrictParams,
finalExpiry ...uint16) (*route.Route, error) {
var finalCLTVDelta uint16
if len(finalExpiry) == 0 {
@ -1388,11 +1327,6 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
log.Debugf("Searching for path to %x, sending %v", target, amt)
// If we don't have a set of routes cached, we'll query the graph for a
// set of potential routes to the destination node that can support our
// payment amount. If no such routes can be found then an error will be
// returned.
// We can short circuit the routing by opportunistically checking to
// see if the target vertex event exists in the current graph.
if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
@ -1402,16 +1336,8 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
return nil, newErrf(ErrTargetNotInNetwork, "target not found")
}
// We'll also fetch the current block height so we can properly
// calculate the required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Before we open the db transaction below, we'll attempt to obtain a
// set of bandwidth hints that can help us eliminate certain routes
// early on in the path finding process.
// We'll attempt to obtain a set of bandwidth hints that can help us
// eliminate certain routes early on in the path finding process.
bandwidthHints, err := generateBandwidthHints(
r.selfNode, r.cfg.QueryBandwidth,
)
@ -1419,47 +1345,38 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
return nil, err
}
tx, err := r.cfg.Graph.Database().Begin(false)
if err != nil {
tx.Rollback()
return nil, err
}
// Now that we know the destination is reachable within the graph,
// we'll execute our KSP algorithm to find the k-shortest paths from
// our source to the destination.
shortestPaths, err := findPaths(
tx, r.cfg.Graph, source, target, amt, restrictions,
numPaths, bandwidthHints,
// Now that we know the destination is reachable within the graph, we'll
// execute our path finding algorithm.
path, err := findPath(
&graphParams{
graph: r.cfg.Graph,
bandwidthHints: bandwidthHints,
},
restrictions, source, target, amt,
)
// We'll fetch the current block height so we can properly calculate the
// required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
tx.Rollback()
return nil, err
}
tx.Rollback()
// Now that we have a set of paths, we'll need to turn them into
// *routes* by computing the required time-lock and fee information for
// 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
// factored in.
sourceVertex := route.Vertex(r.selfNode.PubKeyBytes)
validRoutes, err := pathsToFeeSortedRoutes(
sourceVertex, shortestPaths, finalCLTVDelta, amt,
uint32(currentHeight),
// Create the route with absolute time lock values.
route, err := newRoute(
amt, source, path, uint32(currentHeight), finalCLTVDelta,
)
if err != nil {
return nil, err
}
go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes),
go log.Tracef("Obtained path to send %v to %x: %v",
amt, target, newLogClosure(func() string {
return spew.Sdump(validRoutes)
return spew.Sdump(route)
}),
)
return validRoutes, nil
return route, nil
}
// generateSphinxPacket generates then encodes a sphinx packet which encodes

View File

@ -23,10 +23,6 @@ import (
"github.com/lightningnetwork/lnd/zpay32"
)
// defaultNumRoutes is the default value for the maximum number of routes to
// be returned by FindRoutes
const defaultNumRoutes = 10
type testCtx struct {
router *ChannelRouter
@ -165,60 +161,6 @@ func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, f
return createTestCtxFromGraphInstance(startingHeight, graphInstance)
}
// TestFindRoutesFeeSorting asserts that routes found by the FindRoutes method
// within the channel router are properly returned in a sorted order, with the
// lowest fee route coming first.
func TestFindRoutesFeeSorting(t *testing.T) {
t.Parallel()
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create router: %v", err)
}
defer cleanUp()
// In this test we'd like to ensure proper integration of the various
// functions that are involved in path finding, and also route
// selection.
// Execute a query for all possible routes between roasbeef and luo ji.
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.aliases["luoji"]
routes, err := ctx.router.FindRoutes(
ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, noRestrictions, defaultNumRoutes,
zpay32.DefaultFinalCLTVDelta,
)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
// Exactly, two such paths should be found.
if len(routes) != 2 {
t.Fatalf("2 routes should've been selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}
// We shouldn't pay a fee for the fist route, but the second route
// should have a fee intact.
if routes[0].TotalFees != 0 {
t.Fatalf("incorrect fees for first route, expected 0 got: %v",
routes[0].TotalFees)
}
if routes[1].TotalFees == 0 {
t.Fatalf("total fees not set in second route: %v",
spew.Sdump(routes[0]))
}
// The paths should properly be ranked according to their total fee
// rate.
if routes[0].TotalFees > routes[1].TotalFees {
t.Fatalf("routes not ranked by total fee: %v",
spew.Sdump(routes))
}
}
// TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method
// within the channel router contain a total fee less than or equal to the fee
// limit.
@ -247,24 +189,20 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
FeeLimit: lnwire.NewMSatFromSatoshis(10),
}
routes, err := ctx.router.FindRoutes(
route, err := ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, restrictions, defaultNumRoutes,
target, paymentAmt, restrictions,
zpay32.DefaultFinalCLTVDelta,
)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
if len(routes) != 1 {
t.Fatalf("expected 1 route, got %d", len(routes))
if route.TotalFees > restrictions.FeeLimit {
t.Fatalf("route exceeded fee limit: %v", spew.Sdump(route))
}
if routes[0].TotalFees > restrictions.FeeLimit {
t.Fatalf("route exceeded fee limit: %v", spew.Sdump(routes[0]))
}
hops := routes[0].Hops
hops := route.Hops
if len(hops) != 2 {
t.Fatalf("expected 2 hops, got %d", len(hops))
}
@ -1339,22 +1277,19 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("unable to update edge policy: %v", err)
}
// We should now be able to find two routes to node 2.
// We should now be able to find a route to node 2.
paymentAmt := lnwire.NewMSatFromSatoshis(100)
targetNode := priv2.PubKey()
var targetPubKeyBytes route.Vertex
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
routes, err := ctx.router.FindRoutes(
_, err = ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
targetPubKeyBytes, paymentAmt, noRestrictions,
zpay32.DefaultFinalCLTVDelta,
)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
if len(routes) != 2 {
t.Fatalf("expected to find 2 route, found: %v", len(routes))
}
// Now check that we can update the node info for the partial node
// without messing up the channel graph.
@ -1388,19 +1323,16 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("could not add node: %v", err)
}
// Should still be able to find the routes, and the info should be
// Should still be able to find the route, and the info should be
// updated.
routes, err = ctx.router.FindRoutes(
_, err = ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
targetPubKeyBytes, paymentAmt, noRestrictions,
zpay32.DefaultFinalCLTVDelta,
)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
if len(routes) != 2 {
t.Fatalf("expected to find 2 route, found: %v", len(routes))
}
copy1, err := ctx.graph.FetchLightningNode(priv1.PubKey())
if err != nil {

View File

@ -462,7 +462,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
}
return info.Capacity, nil
},
FindRoutes: s.chanRouter.FindRoutes,
FindRoute: s.chanRouter.FindRoute,
}
var (