diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 45e0b3a27..58b7a8294 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -45,6 +45,11 @@ type RouterBackend struct { // capacity of a channel to populate in responses. FetchChannelCapacity func(chanID uint64) (btcutil.Amount, error) + // FetchAmountPairCapacity determines the maximal channel capacity + // between two nodes given a certain amount. + FetchAmountPairCapacity func(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) + // FetchChannelEndpoints returns the pubkeys of both endpoints of the // given channel id. FetchChannelEndpoints func(chanID uint64) (route.Vertex, @@ -325,7 +330,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, // 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. - route, _, err := r.FindRoute( + route, successProb, err := r.FindRoute( sourcePubKey, targetPubKey, amt, in.TimePref, restrictions, customRecords, routeHintEdges, finalCLTVDelta, ) @@ -340,11 +345,6 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, return nil, err } - // Calculate route success probability. Do not rely on a probability - // that could have been returned from path finding, because mission - // control may have been disabled in the provided ProbabilitySource. - successProb := r.getSuccessProbability(route) - routeResp := &lnrpc.QueryRoutesResponse{ Routes: []*lnrpc.Route{rpcRoute}, SuccessProb: successProb, @@ -353,28 +353,6 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, return routeResp, nil } -// getSuccessProbability returns the success probability for the given route -// based on the current state of mission control. -func (r *RouterBackend) getSuccessProbability(rt *route.Route) float64 { - fromNode := rt.SourcePubKey - amtToFwd := rt.TotalAmount - successProb := 1.0 - for _, hop := range rt.Hops { - toNode := hop.PubKeyBytes - - probability := r.MissionControl.GetProbability( - fromNode, toNode, amtToFwd, 0, - ) - - successProb *= probability - - amtToFwd = hop.AmtToForward - fromNode = toNode - } - - return successProb -} - // rpcEdgeToPair looks up the provided channel and returns the channel endpoints // as a directed pair. func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) ( diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index bdbcfb8b8..4a5d73135 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -200,6 +200,11 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, return 1, nil }, + FetchAmountPairCapacity: func(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) { + + return 1, nil + }, MissionControl: &mockMissionControl{}, FetchChannelEndpoints: func(chanID uint64) (route.Vertex, route.Vertex, error) { diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 502132db3..3966ca406 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -709,8 +709,22 @@ func (s *Server) QueryProbability(ctx context.Context, amt := lnwire.MilliSatoshi(req.AmtMsat) + // Compute the probability. + var prob float64 mc := s.cfg.RouterBackend.MissionControl - prob := mc.GetProbability(fromNode, toNode, amt, 0) + capacity, err := s.cfg.RouterBackend.FetchAmountPairCapacity( + fromNode, toNode, amt, + ) + + // If we cannot query the capacity this means that either we don't have + // information available or that the channel fails min/maxHtlc + // constraints, so we return a zero probability. + if err != nil { + log.Errorf("Cannot fetch capacity: %v", err) + } else { + prob = mc.GetProbability(fromNode, toNode, amt, capacity) + } + history := mc.GetPairHistorySnapshot(fromNode, toNode) return &QueryProbabilityResponse{ diff --git a/lntest/itest/lnd_rest_api_test.go b/lntest/itest/lnd_rest_api_test.go index c02e7da4c..16f7c6c17 100644 --- a/lntest/itest/lnd_rest_api_test.go +++ b/lntest/itest/lnd_rest_api_test.go @@ -111,7 +111,7 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { resp := &routerrpc.QueryProbabilityResponse{} err := invokeGET(a, url, resp) require.Nil(t, err, "query probability") - assert.Greater(t, resp.Probability, 0.5, "probability") + require.Zero(t, resp.Probability) }, }, { name: "GET with map type query param", diff --git a/routing/graph.go b/routing/graph.go index 54ddd46b1..23bdd41e2 100644 --- a/routing/graph.go +++ b/routing/graph.go @@ -1,6 +1,9 @@ package routing import ( + "fmt" + + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -20,6 +23,11 @@ type routingGraph interface { // fetchNodeFeatures returns the features of the given node. fetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error) + + // FetchAmountPairCapacity determines the maximal capacity between two + // pairs of nodes. + FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) } // CachedGraph is a routingGraph implementation that retrieves from the @@ -51,9 +59,9 @@ func NewCachedGraph(sourceNode *channeldb.LightningNode, }, nil } -// close attempts to close the underlying db transaction. This is a no-op in +// Close attempts to close the underlying db transaction. This is a no-op in // case the underlying graph uses an in-memory cache. -func (g *CachedGraph) close() error { +func (g *CachedGraph) Close() error { if g.tx == nil { return nil } @@ -86,3 +94,33 @@ func (g *CachedGraph) fetchNodeFeatures(nodePub route.Vertex) ( return g.graph.FetchNodeFeatures(nodePub) } + +// FetchAmountPairCapacity determines the maximal public capacity between two +// nodes depending on the amount we try to send. +// +// NOTE: Part of the routingGraph interface. +func (g *CachedGraph) FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) { + + // Create unified edges for all incoming connections. + u := newNodeEdgeUnifier(g.sourceNode(), nodeTo, nil) + + err := u.addGraphPolicies(g) + if err != nil { + return 0, err + } + + edgeUnifier, ok := u.edgeUnifiers[nodeFrom] + if !ok { + return 0, fmt.Errorf("no edge info for node pair %v -> %v", + nodeFrom, nodeTo) + } + + edge := edgeUnifier.getEdgeNetwork(amount) + if edge == nil { + return 0, fmt.Errorf("no edge for node pair %v -> %v "+ + "(amount %v)", nodeFrom, nodeTo, amount) + } + + return edge.capacity, nil +} diff --git a/routing/mock_graph_test.go b/routing/mock_graph_test.go index 79d46d012..dfbfd6aba 100644 --- a/routing/mock_graph_test.go +++ b/routing/mock_graph_test.go @@ -226,6 +226,31 @@ func (m *mockGraph) fetchNodeFeatures(nodePub route.Vertex) ( return lnwire.EmptyFeatureVector(), nil } +// FetchAmountPairCapacity returns the maximal capacity between nodes in the +// graph. +// +// NOTE: Part of the routingGraph interface. +func (m *mockGraph) FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) { + + var capacity btcutil.Amount + + cb := func(channel *channeldb.DirectedChannel) error { + if channel.OtherNode == nodeTo { + capacity = channel.Capacity + } + + return nil + } + + err := m.forEachNodeChannel(nodeFrom, cb) + if err != nil { + return 0, err + } + + return capacity, nil +} + // htlcResult describes the resolution of an htlc. If failure is nil, the htlc // was settled. type htlcResult struct { diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 998d3a6cd..3fb0dc9ce 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -3112,7 +3112,7 @@ func dbFindPath(graph *channeldb.ChannelGraph, } defer func() { - if err := routingGraph.close(); err != nil { + if err := routingGraph.Close(); err != nil { log.Errorf("Error closing db tx: %v", err) } }() diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go index 1a589fd4e..9d42f6a4a 100644 --- a/routing/payment_session_source.go +++ b/routing/payment_session_source.go @@ -51,7 +51,7 @@ func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) { return nil, nil, err } return routingTx, func() { - err := routingTx.close() + err := routingTx.Close() if err != nil { log.Errorf("Error closing db tx: %v", err) } diff --git a/rpcserver.go b/rpcserver.go index 4c3062c64..95e001022 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -689,6 +689,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, return err } graph := s.graphDB + routerBackend := &routerrpc.RouterBackend{ SelfNode: selfNode.PubKeyBytes, FetchChannelCapacity: func(chanID uint64) (btcutil.Amount, @@ -700,6 +701,28 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, } return info.Capacity, nil }, + FetchAmountPairCapacity: func(nodeFrom, nodeTo route.Vertex, + amount lnwire.MilliSatoshi) (btcutil.Amount, error) { + + routingGraph, err := routing.NewCachedGraph( + selfNode, graph, + ) + if err != nil { + return 0, err + } + defer func() { + closeErr := routingGraph.Close() + if closeErr != nil { + rpcsLog.Errorf("not able to close "+ + "routing graph tx: %v", + closeErr) + } + }() + + return routingGraph.FetchAmountPairCapacity( + nodeFrom, nodeTo, amount, + ) + }, FetchChannelEndpoints: func(chanID uint64) (route.Vertex, route.Vertex, error) {