mirror of
https://github.com/aljazceru/breez-lnd.git
synced 2025-12-18 22:54:26 +01:00
Merge pull request #3054 from joostjager/remove-k-shortest
lnrpc+routing: remove k shortest path finding
This commit is contained in:
@@ -2977,11 +2977,6 @@ var queryRoutesCommand = cli.Command{
|
|||||||
Usage: "percentage of the payment's amount used as the " +
|
Usage: "percentage of the payment's amount used as the " +
|
||||||
"maximum fee allowed when sending the payment",
|
"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{
|
cli.Int64Flag{
|
||||||
Name: "final_cltv_delta",
|
Name: "final_cltv_delta",
|
||||||
Usage: "(optional) number of blocks the last hop has to reveal " +
|
Usage: "(optional) number of blocks the last hop has to reveal " +
|
||||||
@@ -3035,7 +3030,6 @@ func queryRoutes(ctx *cli.Context) error {
|
|||||||
PubKey: dest,
|
PubKey: dest,
|
||||||
Amt: amt,
|
Amt: amt,
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
NumRoutes: int32(ctx.Int("num_max_routes")),
|
|
||||||
FinalCltvDelta: int32(ctx.Int("final_cltv_delta")),
|
FinalCltvDelta: int32(ctx.Int("final_cltv_delta")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
lnd_test.go
25
lnd_test.go
@@ -1460,7 +1460,6 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
routesReq := &lnrpc.QueryRoutesRequest{
|
routesReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: carol.PubKeyStr,
|
PubKey: carol.PubKeyStr,
|
||||||
Amt: int64(payAmt),
|
Amt: int64(payAmt),
|
||||||
NumRoutes: 1,
|
|
||||||
FinalCltvDelta: defaultTimeLockDelta,
|
FinalCltvDelta: defaultTimeLockDelta,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4270,7 +4269,6 @@ func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
routesReq := &lnrpc.QueryRoutesRequest{
|
routesReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: net.Bob.PubKeyStr,
|
PubKey: net.Bob.PubKeyStr,
|
||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
NumRoutes: 1,
|
|
||||||
FinalCltvDelta: defaultBitcoinTimeLockDelta,
|
FinalCltvDelta: defaultBitcoinTimeLockDelta,
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
@@ -4442,7 +4440,6 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
routesReq := &lnrpc.QueryRoutesRequest{
|
routesReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: carol.PubKeyStr,
|
PubKey: carol.PubKeyStr,
|
||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
NumRoutes: 1,
|
|
||||||
FinalCltvDelta: defaultBitcoinTimeLockDelta,
|
FinalCltvDelta: defaultBitcoinTimeLockDelta,
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
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
|
// Query routes from Carol to Charlie which will be an invalid route
|
||||||
// for Alice -> Bob.
|
// for Alice -> Bob.
|
||||||
fakeReq := &lnrpc.QueryRoutesRequest{
|
fakeReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: charlie.PubKeyStr,
|
PubKey: charlie.PubKeyStr,
|
||||||
Amt: int64(1),
|
Amt: int64(1),
|
||||||
NumRoutes: 1,
|
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq)
|
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.
|
// Query for routes to pay from Alice to Dave.
|
||||||
const paymentAmt = 1000
|
const paymentAmt = 1000
|
||||||
routesReq := &lnrpc.QueryRoutesRequest{
|
routesReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: dave.PubKeyStr,
|
PubKey: dave.PubKeyStr,
|
||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
NumRoutes: 1,
|
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq)
|
routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq)
|
||||||
@@ -12646,10 +12641,9 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// payments.
|
// payments.
|
||||||
testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) {
|
testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) {
|
||||||
queryRoutesReq := &lnrpc.QueryRoutesRequest{
|
queryRoutesReq := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: dave.PubKeyStr,
|
PubKey: dave.PubKeyStr,
|
||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
NumRoutes: 2,
|
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq)
|
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)
|
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])
|
checkRoute(routesResp.Routes[0])
|
||||||
|
|
||||||
invoice := &lnrpc.Invoice{Value: paymentAmt}
|
invoice := &lnrpc.Invoice{Value: paymentAmt}
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ type RouterBackend struct {
|
|||||||
|
|
||||||
// FindRoutes is a closure that abstracts away how we locate/query for
|
// FindRoutes is a closure that abstracts away how we locate/query for
|
||||||
// routes.
|
// routes.
|
||||||
FindRoutes func(source, target route.Vertex,
|
FindRoute func(source, target route.Vertex,
|
||||||
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
||||||
numPaths uint32, finalExpiry ...uint16) (
|
finalExpiry ...uint16) (*route.Route, error)
|
||||||
[]*route.Route, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRoutes attempts to query the daemons' Channel Router for a possible
|
// QueryRoutes attempts to query the daemons' Channel Router for a possible
|
||||||
@@ -122,53 +121,35 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
|
|||||||
IgnoredEdges: ignoredEdges,
|
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
|
// Query the channel router for a possible path to the destination that
|
||||||
// can carry `in.Amt` satoshis _including_ the total fee required on
|
// can carry `in.Amt` satoshis _including_ the total fee required on
|
||||||
// the route.
|
// the route.
|
||||||
var (
|
var (
|
||||||
routes []*route.Route
|
route *route.Route
|
||||||
findErr error
|
findErr error
|
||||||
)
|
)
|
||||||
|
|
||||||
if in.FinalCltvDelta == 0 {
|
if in.FinalCltvDelta == 0 {
|
||||||
routes, findErr = r.FindRoutes(
|
route, findErr = r.FindRoute(
|
||||||
sourcePubKey, targetPubKey, amtMSat, restrictions,
|
sourcePubKey, targetPubKey, amtMSat, restrictions,
|
||||||
numRoutesIn,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
routes, findErr = r.FindRoutes(
|
route, findErr = r.FindRoute(
|
||||||
sourcePubKey, targetPubKey, amtMSat, restrictions,
|
sourcePubKey, targetPubKey, amtMSat, restrictions,
|
||||||
numRoutesIn, uint16(in.FinalCltvDelta),
|
uint16(in.FinalCltvDelta),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if findErr != nil {
|
if findErr != nil {
|
||||||
return nil, findErr
|
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
|
// For each valid route, we'll convert the result into the format
|
||||||
// required by the RPC system.
|
// required by the RPC system.
|
||||||
|
|
||||||
|
rpcRoute := r.MarshallRoute(route)
|
||||||
|
|
||||||
routeResp := &lnrpc.QueryRoutesResponse{
|
routeResp := &lnrpc.QueryRoutesResponse{
|
||||||
Routes: make([]*lnrpc.Route, 0, in.NumRoutes),
|
Routes: []*lnrpc.Route{rpcRoute},
|
||||||
}
|
|
||||||
for i := uint32(0); i < numRoutes; i++ {
|
|
||||||
routeResp.Routes = append(
|
|
||||||
routeResp.Routes,
|
|
||||||
r.MarshallRoute(routes[i]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeResp, nil
|
return routeResp, nil
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ func TestQueryRoutes(t *testing.T) {
|
|||||||
request := &lnrpc.QueryRoutesRequest{
|
request := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: destKey,
|
PubKey: destKey,
|
||||||
Amt: 100000,
|
Amt: 100000,
|
||||||
NumRoutes: 1,
|
|
||||||
FinalCltvDelta: 100,
|
FinalCltvDelta: 100,
|
||||||
FeeLimit: &lnrpc.FeeLimit{
|
FeeLimit: &lnrpc.FeeLimit{
|
||||||
Limit: &lnrpc.FeeLimit_Fixed{
|
Limit: &lnrpc.FeeLimit_Fixed{
|
||||||
@@ -58,19 +57,14 @@ func TestQueryRoutes(t *testing.T) {
|
|||||||
|
|
||||||
rt := &route.Route{}
|
rt := &route.Route{}
|
||||||
|
|
||||||
findRoutes := func(source, target route.Vertex,
|
findRoute := func(source, target route.Vertex,
|
||||||
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
||||||
numPaths uint32, finalExpiry ...uint16) (
|
finalExpiry ...uint16) (*route.Route, error) {
|
||||||
[]*route.Route, error) {
|
|
||||||
|
|
||||||
if int64(amt) != request.Amt*1000 {
|
if int64(amt) != request.Amt*1000 {
|
||||||
t.Fatal("unexpected amount")
|
t.Fatal("unexpected amount")
|
||||||
}
|
}
|
||||||
|
|
||||||
if numPaths != 1 {
|
|
||||||
t.Fatal("unexpected number of routes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if source != sourceKey {
|
if source != sourceKey {
|
||||||
t.Fatal("unexpected source key")
|
t.Fatal("unexpected source key")
|
||||||
}
|
}
|
||||||
@@ -101,14 +95,12 @@ func TestQueryRoutes(t *testing.T) {
|
|||||||
t.Fatal("unexpected ignored node")
|
t.Fatal("unexpected ignored node")
|
||||||
}
|
}
|
||||||
|
|
||||||
return []*route.Route{
|
return rt, nil
|
||||||
rt,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backend := &RouterBackend{
|
backend := &RouterBackend{
|
||||||
MaxPaymentMSat: lnwire.NewMSatFromSatoshis(1000000),
|
MaxPaymentMSat: lnwire.NewMSatFromSatoshis(1000000),
|
||||||
FindRoutes: findRoutes,
|
FindRoute: findRoute,
|
||||||
SelfNode: route.Vertex{1, 2, 3},
|
SelfNode: route.Vertex{1, 2, 3},
|
||||||
FetchChannelCapacity: func(chanID uint64) (
|
FetchChannelCapacity: func(chanID uint64) (
|
||||||
btcutil.Amount, error) {
|
btcutil.Amount, error) {
|
||||||
|
|||||||
@@ -247,22 +247,18 @@ func (s *Server) EstimateRouteFee(ctx context.Context,
|
|||||||
|
|
||||||
// Finally, we'll query for a route to the destination that can carry
|
// Finally, we'll query for a route to the destination that can carry
|
||||||
// that target amount, we'll only request a single route.
|
// 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,
|
s.cfg.RouterBackend.SelfNode, destNode, amtMsat,
|
||||||
&routing.RestrictParams{
|
&routing.RestrictParams{
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
}, 1,
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return nil, fmt.Errorf("unable to find route to dest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RouteFeeResponse{
|
return &RouteFeeResponse{
|
||||||
RoutingFeeMsat: int64(routes[0].TotalFees),
|
RoutingFeeMsat: int64(route.TotalFees),
|
||||||
TimeLockDelay: int64(routes[0].TotalTimeLock),
|
TimeLockDelay: int64(route.TotalTimeLock),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
1259
lnrpc/rpc.pb.go
1259
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@@ -1606,11 +1606,7 @@ message QueryRoutesRequest {
|
|||||||
/// The amount to send expressed in satoshis
|
/// The amount to send expressed in satoshis
|
||||||
int64 amt = 2;
|
int64 amt = 2;
|
||||||
|
|
||||||
/**
|
reserved 3;
|
||||||
Deprecated. The max number of routes to return. In the future, QueryRoutes
|
|
||||||
will only return a single route.
|
|
||||||
*/
|
|
||||||
int32 num_routes = 3 [deprecated = true];
|
|
||||||
|
|
||||||
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop
|
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop
|
||||||
int32 final_cltv_delta = 4;
|
int32 final_cltv_delta = 4;
|
||||||
|
|||||||
@@ -673,14 +673,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "int64"
|
"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",
|
"name": "final_cltv_delta",
|
||||||
"description": "/ An optional CLTV delta from the current height that should be used for the timelock of the final hop.",
|
"description": "/ An optional CLTV delta from the current height that should be used for the timelock of the final hop.",
|
||||||
|
|||||||
@@ -616,180 +616,3 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
|
|||||||
|
|
||||||
return pathEdges, nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -948,63 +948,6 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
|||||||
assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
|
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
|
// TestNewRoute tests whether the construction of hop payloads by newRoute
|
||||||
// is executed correctly.
|
// is executed correctly.
|
||||||
func TestNewRoute(t *testing.T) {
|
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.
|
// 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(
|
route, err := ctx.router.FindRoute(
|
||||||
bobNode.PubKeyBytes, carol, amt, noRestrictions, 100,
|
bobNode.PubKeyBytes, carol, amt, noRestrictions,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find route: %v", err)
|
t.Fatalf("unable to find route: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should come back with _exactly_ two routes.
|
// Now we'll examine the route returned for correctness.
|
||||||
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.
|
|
||||||
//
|
//
|
||||||
// It should be sending the exact payment amount as there are no
|
// It should be sending the exact payment amount as there are no
|
||||||
// additional hops.
|
// additional hops.
|
||||||
firstRoute := routes[0]
|
if route.TotalAmount != amt {
|
||||||
if firstRoute.TotalAmount != amt {
|
|
||||||
t.Fatalf("wrong total amount: got %v, expected %v",
|
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",
|
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 {
|
if fee != 0 {
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v", 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 CLTV expiry should be the current height plus 9 (the expiry for
|
||||||
// the B -> C channel.
|
// the B -> C channel.
|
||||||
if firstRoute.TotalTimeLock !=
|
if route.TotalTimeLock !=
|
||||||
startingHeight+zpay32.DefaultFinalCLTVDelta {
|
startingHeight+zpay32.DefaultFinalCLTVDelta {
|
||||||
|
|
||||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||||
firstRoute.TotalTimeLock,
|
route.TotalTimeLock,
|
||||||
startingHeight+zpay32.DefaultFinalCLTVDelta)
|
startingHeight+zpay32.DefaultFinalCLTVDelta)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1798,48 +1734,38 @@ 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.
|
||||||
routes, err = ctx.router.FindRoutes(
|
route, err = ctx.router.FindRoute(
|
||||||
source.PubKeyBytes, carol, amt, noRestrictions, 100,
|
source.PubKeyBytes, carol, amt, noRestrictions,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find routes: %v", err)
|
t.Fatalf("unable to find routes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should come back with _exactly_ two routes.
|
// The route should be two hops.
|
||||||
if len(routes) != 2 {
|
if len(route.Hops) != 2 {
|
||||||
t.Fatalf("expected %v routes, instead have: %v", 2,
|
|
||||||
len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both routes should be two hops.
|
|
||||||
if len(routes[0].Hops) != 2 {
|
|
||||||
t.Fatalf("route should be %v hops, is instead %v", 2,
|
t.Fatalf("route should be %v hops, is instead %v", 2,
|
||||||
len(routes[0].Hops))
|
len(route.Hops))
|
||||||
}
|
|
||||||
if len(routes[1].Hops) != 2 {
|
|
||||||
t.Fatalf("route should be %v hops, is instead %v", 2,
|
|
||||||
len(routes[1].Hops))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The total amount should factor in a fee of 10199 and also use a CLTV
|
// The total amount should factor in a fee of 10199 and also use a CLTV
|
||||||
// delta total of 29 (20 + 9),
|
// delta total of 29 (20 + 9),
|
||||||
expectedAmt := lnwire.MilliSatoshi(5010198)
|
expectedAmt := lnwire.MilliSatoshi(5010198)
|
||||||
if routes[0].TotalAmount != expectedAmt {
|
if route.TotalAmount != expectedAmt {
|
||||||
t.Fatalf("wrong amount: got %v, expected %v",
|
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",
|
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
|
// After taking the fee, Bob should be forwarding the remainder which
|
||||||
// is the exact payment to Bob.
|
// 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",
|
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
|
// 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
|
// * 200 + 4999999 * 2000 / 1000000 = 10199
|
||||||
|
|
||||||
fee = routes[0].HopFee(0)
|
fee = route.HopFee(0)
|
||||||
if fee != 10199 {
|
if fee != 10199 {
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v", 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
|
// While for the final hop, as there's no additional hop afterwards, we
|
||||||
// pay no fee.
|
// pay no fee.
|
||||||
fee = routes[0].HopFee(1)
|
fee = route.HopFee(1)
|
||||||
if fee != 0 {
|
if fee != 0 {
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v", 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
|
// The outgoing CLTV value itself should be the current height plus 30
|
||||||
// to meet Carol's requirements.
|
// to meet Carol's requirements.
|
||||||
if routes[0].Hops[0].OutgoingTimeLock !=
|
if route.Hops[0].OutgoingTimeLock !=
|
||||||
startingHeight+zpay32.DefaultFinalCLTVDelta {
|
startingHeight+zpay32.DefaultFinalCLTVDelta {
|
||||||
|
|
||||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||||
routes[0].Hops[0].OutgoingTimeLock,
|
route.Hops[0].OutgoingTimeLock,
|
||||||
startingHeight+zpay32.DefaultFinalCLTVDelta)
|
startingHeight+zpay32.DefaultFinalCLTVDelta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For B -> C, we assert that the final hop also has the proper
|
// For B -> C, we assert that the final hop also has the proper
|
||||||
// parameters.
|
// parameters.
|
||||||
lastHop := routes[0].Hops[1]
|
lastHop := route.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]
|
|
||||||
if lastHop.AmtToForward != amt {
|
if lastHop.AmtToForward != amt {
|
||||||
t.Fatalf("wrong forward amount: got %v, expected %v",
|
t.Fatalf("wrong forward amount: got %v, expected %v",
|
||||||
lastHop.AmtToForward, amt)
|
lastHop.AmtToForward, amt)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -1313,72 +1312,12 @@ type routingMsg struct {
|
|||||||
err chan error
|
err chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathsToFeeSortedRoutes takes a set of paths, and returns a corresponding set
|
// FindRoute attempts to query the ChannelRouter for the optimum path to a
|
||||||
// of routes. A route differs from a path in that it has full time-lock and
|
// particular target destination to which it is able to send `amt` after
|
||||||
// fee information attached. The set of routes returned may be less than the
|
// factoring in channel capacities and cumulative fees along the route.
|
||||||
// initial set of paths as it's possible we drop a route if it can't handle the
|
func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
||||||
// total payment flow after fees are calculated.
|
amt lnwire.MilliSatoshi, restrictions *RestrictParams,
|
||||||
func pathsToFeeSortedRoutes(source route.Vertex, paths [][]*channeldb.ChannelEdgePolicy,
|
finalExpiry ...uint16) (*route.Route, error) {
|
||||||
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) {
|
|
||||||
|
|
||||||
var finalCLTVDelta uint16
|
var finalCLTVDelta uint16
|
||||||
if len(finalExpiry) == 0 {
|
if len(finalExpiry) == 0 {
|
||||||
@@ -1389,11 +1328,6 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
|
|||||||
|
|
||||||
log.Debugf("Searching for path to %x, sending %v", target, amt)
|
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
|
// We can short circuit the routing by opportunistically checking to
|
||||||
// see if the target vertex event exists in the current graph.
|
// see if the target vertex event exists in the current graph.
|
||||||
if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
|
if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
|
||||||
@@ -1403,16 +1337,8 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
|
|||||||
return nil, newErrf(ErrTargetNotInNetwork, "target not found")
|
return nil, newErrf(ErrTargetNotInNetwork, "target not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll also fetch the current block height so we can properly
|
// We'll attempt to obtain a set of bandwidth hints that can help us
|
||||||
// calculate the required HTLC time locks within the route.
|
// eliminate certain routes early on in the path finding process.
|
||||||
_, 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.
|
|
||||||
bandwidthHints, err := generateBandwidthHints(
|
bandwidthHints, err := generateBandwidthHints(
|
||||||
r.selfNode, r.cfg.QueryBandwidth,
|
r.selfNode, r.cfg.QueryBandwidth,
|
||||||
)
|
)
|
||||||
@@ -1420,47 +1346,38 @@ func (r *ChannelRouter) FindRoutes(source, target route.Vertex,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := r.cfg.Graph.Database().Begin(false)
|
// Now that we know the destination is reachable within the graph, we'll
|
||||||
if err != nil {
|
// execute our path finding algorithm.
|
||||||
tx.Rollback()
|
path, err := findPath(
|
||||||
return nil, err
|
&graphParams{
|
||||||
}
|
graph: r.cfg.Graph,
|
||||||
|
bandwidthHints: bandwidthHints,
|
||||||
// Now that we know the destination is reachable within the graph,
|
},
|
||||||
// we'll execute our KSP algorithm to find the k-shortest paths from
|
restrictions, source, target, amt,
|
||||||
// our source to the destination.
|
|
||||||
shortestPaths, err := findPaths(
|
|
||||||
tx, r.cfg.Graph, source, target, amt, restrictions,
|
|
||||||
numPaths, bandwidthHints,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 {
|
if err != nil {
|
||||||
tx.Rollback()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Rollback()
|
// Create the route with absolute time lock values.
|
||||||
|
route, err := newRoute(
|
||||||
// Now that we have a set of paths, we'll need to turn them into
|
amt, source, path, uint32(currentHeight), finalCLTVDelta,
|
||||||
// *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),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
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
|
// generateSphinxPacket generates then encodes a sphinx packet which encodes
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/zpay32"
|
"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 {
|
type testCtx struct {
|
||||||
router *ChannelRouter
|
router *ChannelRouter
|
||||||
|
|
||||||
@@ -165,60 +161,6 @@ func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, f
|
|||||||
return createTestCtxFromGraphInstance(startingHeight, graphInstance)
|
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
|
// TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method
|
||||||
// within the channel router contain a total fee less than or equal to the fee
|
// within the channel router contain a total fee less than or equal to the fee
|
||||||
// limit.
|
// limit.
|
||||||
@@ -247,24 +189,20 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
|||||||
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := ctx.router.FindRoutes(
|
route, err := ctx.router.FindRoute(
|
||||||
ctx.router.selfNode.PubKeyBytes,
|
ctx.router.selfNode.PubKeyBytes,
|
||||||
target, paymentAmt, restrictions, defaultNumRoutes,
|
target, paymentAmt, restrictions,
|
||||||
zpay32.DefaultFinalCLTVDelta,
|
zpay32.DefaultFinalCLTVDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find any routes: %v", err)
|
t.Fatalf("unable to find any routes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routes) != 1 {
|
if route.TotalFees > restrictions.FeeLimit {
|
||||||
t.Fatalf("expected 1 route, got %d", len(routes))
|
t.Fatalf("route exceeded fee limit: %v", spew.Sdump(route))
|
||||||
}
|
}
|
||||||
|
|
||||||
if routes[0].TotalFees > restrictions.FeeLimit {
|
hops := route.Hops
|
||||||
t.Fatalf("route exceeded fee limit: %v", spew.Sdump(routes[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
hops := routes[0].Hops
|
|
||||||
if len(hops) != 2 {
|
if len(hops) != 2 {
|
||||||
t.Fatalf("expected 2 hops, got %d", len(hops))
|
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)
|
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)
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||||
targetNode := priv2.PubKey()
|
targetNode := priv2.PubKey()
|
||||||
var targetPubKeyBytes route.Vertex
|
var targetPubKeyBytes route.Vertex
|
||||||
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
|
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
|
||||||
routes, err := ctx.router.FindRoutes(
|
_, err = ctx.router.FindRoute(
|
||||||
ctx.router.selfNode.PubKeyBytes,
|
ctx.router.selfNode.PubKeyBytes,
|
||||||
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
|
targetPubKeyBytes, paymentAmt, noRestrictions,
|
||||||
zpay32.DefaultFinalCLTVDelta,
|
zpay32.DefaultFinalCLTVDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find any routes: %v", err)
|
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
|
// Now check that we can update the node info for the partial node
|
||||||
// without messing up the channel graph.
|
// without messing up the channel graph.
|
||||||
@@ -1388,19 +1323,16 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
|||||||
t.Fatalf("could not add node: %v", err)
|
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.
|
// updated.
|
||||||
routes, err = ctx.router.FindRoutes(
|
_, err = ctx.router.FindRoute(
|
||||||
ctx.router.selfNode.PubKeyBytes,
|
ctx.router.selfNode.PubKeyBytes,
|
||||||
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
|
targetPubKeyBytes, paymentAmt, noRestrictions,
|
||||||
zpay32.DefaultFinalCLTVDelta,
|
zpay32.DefaultFinalCLTVDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find any routes: %v", err)
|
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())
|
copy1, err := ctx.graph.FetchLightningNode(priv1.PubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
|||||||
}
|
}
|
||||||
return info.Capacity, nil
|
return info.Capacity, nil
|
||||||
},
|
},
|
||||||
FindRoutes: s.chanRouter.FindRoutes,
|
FindRoute: s.chanRouter.FindRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
Reference in New Issue
Block a user