Merge pull request #3911 from joostjager/extend-qr

routing+lnrpc: add missing query routes parameters
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2020-01-14 16:07:45 -08:00
commit 269182c9b9
11 files changed
+894 -682

No files matched your search

+44 -4
View File
@@ -45,7 +45,8 @@ type RouterBackend struct {
FindRoute func(source, target route.Vertex, FindRoute func(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
destCustomRecords record.CustomSet, destCustomRecords record.CustomSet,
finalExpiry ...uint16) (*route.Route, error) routeHints map[route.Vertex][]*channeldb.ChannelEdgePolicy,
finalExpiry uint16) (*route.Route, error)
MissionControl MissionControl MissionControl MissionControl
@@ -62,6 +63,10 @@ type RouterBackend struct {
// MaxTotalTimelock is the maximum total time lock a route is allowed to // MaxTotalTimelock is the maximum total time lock a route is allowed to
// have. // have.
MaxTotalTimelock uint32 MaxTotalTimelock uint32
// DefaultFinalCltvDelta is the default value used as final cltv delta
// when an RPC caller doesn't specify a value.
DefaultFinalCltvDelta uint16
} }
// MissionControl defines the mission control dependencies of routerrpc. // MissionControl defines the mission control dependencies of routerrpc.
@@ -193,12 +198,18 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
// We need to subtract the final delta before passing it into path // We need to subtract the final delta before passing it into path
// finding. The optimal path is independent of the final cltv delta and // finding. The optimal path is independent of the final cltv delta and
// the path finding algorithm is unaware of this value. // the path finding algorithm is unaware of this value.
finalCLTVDelta := uint16(zpay32.DefaultFinalCLTVDelta) finalCLTVDelta := r.DefaultFinalCltvDelta
if in.FinalCltvDelta != 0 { if in.FinalCltvDelta != 0 {
finalCLTVDelta = uint16(in.FinalCltvDelta) finalCLTVDelta = uint16(in.FinalCltvDelta)
} }
cltvLimit -= uint32(finalCLTVDelta) cltvLimit -= uint32(finalCLTVDelta)
// Parse destination feature bits.
features, err := UnmarshalFeatures(in.DestFeatures)
if err != nil {
return nil, err
}
restrictions := &routing.RestrictParams{ restrictions := &routing.RestrictParams{
FeeLimit: feeLimit, FeeLimit: feeLimit,
ProbabilitySource: func(fromNode, toNode route.Vertex, ProbabilitySource: func(fromNode, toNode route.Vertex,
@@ -226,6 +237,23 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
}, },
DestCustomRecords: record.CustomSet(in.DestCustomRecords), DestCustomRecords: record.CustomSet(in.DestCustomRecords),
CltvLimit: cltvLimit, CltvLimit: cltvLimit,
DestFeatures: features,
}
// Pass along an outgoing channel restriction if specified.
if in.OutgoingChanId != 0 {
restrictions.OutgoingChannelID = &in.OutgoingChanId
}
// Pass along a last hop restriction if specified.
if len(in.LastHopPubkey) > 0 {
lastHop, err := route.NewVertexFromBytes(
in.LastHopPubkey,
)
if err != nil {
return nil, err
}
restrictions.LastHop = &lastHop
} }
// If we have any TLV records destined for the final hop, then we'll // If we have any TLV records destined for the final hop, then we'll
@@ -236,12 +264,24 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
return nil, err return nil, err
} }
// Convert route hints to an edge map.
routeHints, err := unmarshallRouteHints(in.RouteHints)
if err != nil {
return nil, err
}
routeHintEdges, err := routing.RouteHintsToEdges(
routeHints, targetPubKey,
)
if err != nil {
return nil, err
}
// 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.
route, err := r.FindRoute( route, err := r.FindRoute(
sourcePubKey, targetPubKey, amt, restrictions, sourcePubKey, targetPubKey, amt, restrictions,
customRecords, finalCLTVDelta, customRecords, routeHintEdges, finalCLTVDelta,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -621,7 +661,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.FinalCLTVDelta = payIntent.FinalCLTVDelta =
uint16(rpcPayReq.FinalCltvDelta) uint16(rpcPayReq.FinalCltvDelta)
} else { } else {
payIntent.FinalCLTVDelta = zpay32.DefaultFinalCLTVDelta payIntent.FinalCLTVDelta = r.DefaultFinalCltvDelta
} }
// Amount. // Amount.
+45 -1
View File
@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
@@ -18,6 +19,7 @@ import (
const ( const (
destKey = "0286098b97bc843372b4426d4b276cea9aa2f48f0428d6f5b66ae101befc14f8b4" destKey = "0286098b97bc843372b4426d4b276cea9aa2f48f0428d6f5b66ae101befc14f8b4"
ignoreNodeKey = "02f274f48f3c0d590449a6776e3ce8825076ac376e470e992246eebc565ef8bb2a" ignoreNodeKey = "02f274f48f3c0d590449a6776e3ce8825076ac376e470e992246eebc565ef8bb2a"
hintNodeKey = "0274e7fb33eafd74fe1acb6db7680bb4aa78e9c839a6e954e38abfad680f645ef7"
testMissionControlProb = 0.5 testMissionControlProb = 0.5
) )
@@ -58,6 +60,27 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) {
t.Fatal(err) t.Fatal(err)
} }
var (
lastHop = route.Vertex{64}
outgoingChan = uint64(383322)
)
hintNode, err := route.NewVertexFromStr(hintNodeKey)
if err != nil {
t.Fatal(err)
}
rpcRouteHints := []*lnrpc.RouteHint{
{
HopHints: []*lnrpc.HopHint{
{
ChanId: 38484,
NodeId: hintNodeKey,
},
},
},
}
request := &lnrpc.QueryRoutesRequest{ request := &lnrpc.QueryRoutesRequest{
PubKey: destKey, PubKey: destKey,
FinalCltvDelta: 100, FinalCltvDelta: 100,
@@ -71,6 +94,10 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) {
To: node2[:], To: node2[:],
}}, }},
UseMissionControl: useMissionControl, UseMissionControl: useMissionControl,
LastHopPubkey: lastHop[:],
OutgoingChanId: outgoingChan,
DestFeatures: []lnrpc.FeatureBit{lnrpc.FeatureBit_MPP_OPT},
RouteHints: rpcRouteHints,
} }
amtSat := int64(100000) amtSat := int64(100000)
@@ -93,7 +120,8 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) {
findRoute := func(source, target route.Vertex, findRoute := func(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
_ record.CustomSet, _ record.CustomSet,
finalExpiry ...uint16) (*route.Route, error) { routeHints map[route.Vertex][]*channeldb.ChannelEdgePolicy,
finalExpiry uint16) (*route.Route, error) {
if int64(amt) != amtSat*1000 { if int64(amt) != amtSat*1000 {
t.Fatal("unexpected amount") t.Fatal("unexpected amount")
@@ -127,6 +155,22 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) {
t.Fatal("expecting 0% probability for ignored pair") t.Fatal("expecting 0% probability for ignored pair")
} }
if *restrictions.LastHop != lastHop {
t.Fatal("unexpected last hop")
}
if *restrictions.OutgoingChannelID != outgoingChan {
t.Fatal("unexpected outgoing channel id")
}
if !restrictions.DestFeatures.HasFeature(lnwire.MPPOptional) {
t.Fatal("unexpected dest features")
}
if _, ok := routeHints[hintNode]; !ok {
t.Fatal("expected route hint")
}
expectedProb := 1.0 expectedProb := 1.0
if useMissionControl { if useMissionControl {
expectedProb = testMissionControlProb expectedProb = testMissionControlProb
+1 -1
View File
@@ -260,7 +260,7 @@ func (s *Server) EstimateRouteFee(ctx context.Context,
&routing.RestrictParams{ &routing.RestrictParams{
FeeLimit: feeLimit, FeeLimit: feeLimit,
CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock, CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock,
}, nil, }, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta,
) )
if err != nil { if err != nil {
return nil, err return nil, err
+663 -617
View File
File diff suppressed because it is too large. Load diff
+25
View File
@@ -2046,6 +2046,31 @@ message QueryRoutesRequest {
REST, the values must be encoded as base64. REST, the values must be encoded as base64.
*/ */
map<uint64, bytes> dest_custom_records = 13; map<uint64, bytes> dest_custom_records = 13;
/**
The channel id of the channel that must be taken to the first hop. If zero,
any channel may be used.
*/
uint64 outgoing_chan_id = 14 [jstype = JS_STRING];
/**
The pubkey of the last hop of the route. If empty, any hop may be used.
*/
bytes last_hop_pubkey = 15;
/**
Optional route hints to reach the destination through private channels.
*/
repeated lnrpc.RouteHint route_hints = 16;
/**
Features assumed to be supported by the final node. All transitive feature
depdencies must also be set properly. For a given feature bit pair, either
optional or remote may be set, but not both. If this field is nil or empty,
the router will try to load destination features from the graph as a
fallback.
*/
repeated lnrpc.FeatureBit dest_features = 17;
} }
message NodePair { message NodePair {
+46
View File
@@ -812,6 +812,52 @@
"required": false, "required": false,
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
},
{
"name": "outgoing_chan_id",
"description": "*\nThe channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used.",
"in": "query",
"required": false,
"type": "string",
"format": "uint64"
},
{
"name": "last_hop_pubkey",
"description": "*\nThe pubkey of the last hop of the route. If empty, any hop may be used.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
},
{
"name": "dest_features",
"description": "*\nFeatures assumed to be supported by the final node. All transitive feature\ndepdencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both. If this field is nil or empty,\nthe router will try to load destination features from the graph as a\nfallback.",
"in": "query",
"required": false,
"type": "array",
"items": {
"type": "string",
"enum": [
"DATALOSS_PROTECT_REQ",
"DATALOSS_PROTECT_OPT",
"INITIAL_ROUING_SYNC",
"UPFRONT_SHUTDOWN_SCRIPT_REQ",
"UPFRONT_SHUTDOWN_SCRIPT_OPT",
"GOSSIP_QUERIES_REQ",
"GOSSIP_QUERIES_OPT",
"TLV_ONION_REQ",
"TLV_ONION_OPT",
"EXT_GOSSIP_QUERIES_REQ",
"EXT_GOSSIP_QUERIES_OPT",
"STATIC_REMOTE_KEY_REQ",
"STATIC_REMOTE_KEY_OPT",
"PAYMENT_ADDR_REQ",
"PAYMENT_ADDR_OPT",
"MPP_REQ",
"MPP_OPT"
]
},
"collectionFormat": "multi"
} }
], ],
"tags": [ "tags": [
+4 -2
View File
@@ -2109,7 +2109,8 @@ func TestPathFindSpecExample(t *testing.T) {
carol := ctx.aliases["C"] carol := ctx.aliases["C"]
const amt lnwire.MilliSatoshi = 4999999 const amt lnwire.MilliSatoshi = 4999999
route, err := ctx.router.FindRoute( route, err := ctx.router.FindRoute(
bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, nil,
zpay32.DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find route: %v", err) t.Fatalf("unable to find route: %v", err)
@@ -2164,7 +2165,8 @@ 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.
route, err = ctx.router.FindRoute( route, err = ctx.router.FindRoute(
source.PubKeyBytes, carol, amt, noRestrictions, nil, source.PubKeyBytes, carol, amt, noRestrictions, nil, nil,
zpay32.DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find routes: %v", err) t.Fatalf("unable to find routes: %v", err)
+50 -37
View File
@@ -50,6 +50,55 @@ type SessionSource struct {
func (m *SessionSource) NewPaymentSession(routeHints [][]zpay32.HopHint, func (m *SessionSource) NewPaymentSession(routeHints [][]zpay32.HopHint,
target route.Vertex) (PaymentSession, error) { target route.Vertex) (PaymentSession, error) {
edges, err := RouteHintsToEdges(routeHints, target)
if err != nil {
return nil, err
}
sourceNode, err := m.Graph.SourceNode()
if err != nil {
return nil, err
}
getBandwidthHints := func() (map[uint64]lnwire.MilliSatoshi,
error) {
return generateBandwidthHints(sourceNode, m.QueryBandwidth)
}
return &paymentSession{
additionalEdges: edges,
getBandwidthHints: getBandwidthHints,
sessionSource: m,
pathFinder: findPath,
}, nil
}
// NewPaymentSessionForRoute creates a new paymentSession instance that is just
// used for failure reporting to missioncontrol.
func (m *SessionSource) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
return &paymentSession{
sessionSource: m,
preBuiltRoute: preBuiltRoute,
}
}
// NewPaymentSessionEmpty creates a new paymentSession instance that is empty,
// and will be exhausted immediately. Used for failure reporting to
// missioncontrol for resumed payment we don't want to make more attempts for.
func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
return &paymentSession{
sessionSource: m,
preBuiltRoute: &route.Route{},
preBuiltRouteTried: true,
}
}
// RouteHintsToEdges converts a list of invoice route hints to an edge map that
// can be passed into pathfinding.
func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) (
map[route.Vertex][]*channeldb.ChannelEdgePolicy, error) {
edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy) edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy)
// Traverse through all of the available hop hints and include them in // Traverse through all of the available hop hints and include them in
@@ -97,41 +146,5 @@ func (m *SessionSource) NewPaymentSession(routeHints [][]zpay32.HopHint,
} }
} }
sourceNode, err := m.Graph.SourceNode() return edges, nil
if err != nil {
return nil, err
}
getBandwidthHints := func() (map[uint64]lnwire.MilliSatoshi,
error) {
return generateBandwidthHints(sourceNode, m.QueryBandwidth)
}
return &paymentSession{
additionalEdges: edges,
getBandwidthHints: getBandwidthHints,
sessionSource: m,
pathFinder: findPath,
}, nil
}
// NewPaymentSessionForRoute creates a new paymentSession instance that is just
// used for failure reporting to missioncontrol.
func (m *SessionSource) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
return &paymentSession{
sessionSource: m,
preBuiltRoute: preBuiltRoute,
}
}
// NewPaymentSessionEmpty creates a new paymentSession instance that is empty,
// and will be exhausted immediately. Used for failure reporting to
// missioncontrol for resumed payment we don't want to make more attempts for.
func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
return &paymentSession{
sessionSource: m,
preBuiltRoute: &route.Route{},
preBuiltRouteTried: true,
}
} }
+7 -12
View File
@@ -1402,14 +1402,8 @@ type routingMsg struct {
func (r *ChannelRouter) FindRoute(source, target route.Vertex, func (r *ChannelRouter) FindRoute(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *RestrictParams, amt lnwire.MilliSatoshi, restrictions *RestrictParams,
destCustomRecords record.CustomSet, destCustomRecords record.CustomSet,
finalExpiry ...uint16) (*route.Route, error) { routeHints map[route.Vertex][]*channeldb.ChannelEdgePolicy,
finalExpiry uint16) (*route.Route, error) {
var finalCLTVDelta uint16
if len(finalExpiry) == 0 {
finalCLTVDelta = zpay32.DefaultFinalCLTVDelta
} else {
finalCLTVDelta = finalExpiry[0]
}
log.Debugf("Searching for path to %v, sending %v", target, amt) log.Debugf("Searching for path to %v, sending %v", target, amt)
@@ -1440,12 +1434,13 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
// Now that we know the destination is reachable within the graph, we'll // Now that we know the destination is reachable within the graph, we'll
// execute our path finding algorithm. // execute our path finding algorithm.
finalHtlcExpiry := currentHeight + int32(finalCLTVDelta) finalHtlcExpiry := currentHeight + int32(finalExpiry)
path, err := findPath( path, err := findPath(
&graphParams{ &graphParams{
graph: r.cfg.Graph, graph: r.cfg.Graph,
bandwidthHints: bandwidthHints, bandwidthHints: bandwidthHints,
additionalEdges: routeHints,
}, },
restrictions, &r.cfg.PathFindingConfig, restrictions, &r.cfg.PathFindingConfig,
source, target, amt, finalHtlcExpiry, source, target, amt, finalHtlcExpiry,
@@ -1459,7 +1454,7 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
source, path, uint32(currentHeight), source, path, uint32(currentHeight),
finalHopParams{ finalHopParams{
amt: amt, amt: amt,
cltvDelta: finalCLTVDelta, cltvDelta: finalExpiry,
records: destCustomRecords, records: destCustomRecords,
}, },
) )
+3 -3
View File
@@ -227,7 +227,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
route, err := ctx.router.FindRoute( route, err := ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes, ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, restrictions, nil, target, paymentAmt, restrictions, nil, nil,
zpay32.DefaultFinalCLTVDelta, zpay32.DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -1269,7 +1269,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed()) copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
_, err = ctx.router.FindRoute( _, err = ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes, ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, nil, targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil,
zpay32.DefaultFinalCLTVDelta, zpay32.DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -1312,7 +1312,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// updated. // updated.
_, err = ctx.router.FindRoute( _, err = ctx.router.FindRoute(
ctx.router.selfNode.PubKeyBytes, ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, nil, targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil,
zpay32.DefaultFinalCLTVDelta, zpay32.DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
+6 -5
View File
@@ -562,11 +562,12 @@ func newRPCServer(s *server, macService *macaroons.Service,
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
}, },
FindRoute: s.chanRouter.FindRoute, FindRoute: s.chanRouter.FindRoute,
MissionControl: s.missionControl, MissionControl: s.missionControl,
ActiveNetParams: activeNetParams.Params, ActiveNetParams: activeNetParams.Params,
Tower: s.controlTower, Tower: s.controlTower,
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry, MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
DefaultFinalCltvDelta: uint16(cfg.Bitcoin.TimeLockDelta),
} }
genInvoiceFeatures := func() *lnwire.FeatureVector { genInvoiceFeatures := func() *lnwire.FeatureVector {