Merge pull request #10057 from Abdulkbk/outchanids-to-queryroutes

routerrpc: accept mutliple outgoing chan ids in queryroutes
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-07-15 15:02:23 -06:00
commit d7100ebe52
7 files changed
+1874 -1757

No files matched your search

+17 -3
View File
@@ -1158,10 +1158,11 @@ var queryRoutesCommand = cli.Command{
Name: "use_mc",
Usage: "use mission control probabilities",
},
cli.Uint64Flag{
cli.StringSliceFlag{
Name: "outgoing_chan_id",
Usage: "(optional) the channel id of the channel " +
"that must be taken to the first hop",
"to use as the first hop. This flag can be " +
"specified multiple times in the same command.",
},
cli.StringSliceFlag{
Name: "ignore_pair",
@@ -1275,12 +1276,25 @@ func queryRoutes(ctx *cli.Context) error {
FinalCltvDelta: int32(ctx.Int("final_cltv_delta")),
UseMissionControl: ctx.Bool("use_mc"),
CltvLimit: uint32(ctx.Uint64(cltvLimitFlag.Name)),
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
TimePref: ctx.Float64(timePrefFlag.Name),
IgnoredPairs: ignoredPairs,
BlindedPaymentPaths: blindedRoutes,
}
outgoingChanIds := ctx.StringSlice("outgoing_chan_id")
if len(outgoingChanIds) != 0 {
req.OutgoingChanIds = make([]uint64, len(outgoingChanIds))
for i, chanID := range outgoingChanIds {
id, err := strconv.ParseUint(chanID, 10, 64)
if err != nil {
return fmt.Errorf("invalid outgoing_chan_id "+
"argument: %w", err)
}
req.OutgoingChanIds[i] = id
}
}
if ctx.IsSet("route_hints") {
if len(blindedRoutes) > 0 {
return fmt.Errorf("--route_hints should not be used " +
@@ -122,6 +122,10 @@ circuit. The indices are only available for forwarding events saved after v0.20.
to the database.
## lncli Updates
* Previously, users could only specify one `outgoing_chan_id` when calling the
`lncli queryroutes` or the QueryRoutes RPC. With this change, multiple
`outgoing_chan_id` can be passed during the call.
## Code Health
@@ -155,6 +159,10 @@ reader of a payment request.
| [`routerrpc.SendPaymentV2`](https://lightning.engineering/api-docs/api/lnd/router/send-payment-v2/index.html) | ✅ | `POST` | `/v2/router/send` |
| [`routerrpc.TrackPaymentV2`](https://lightning.engineering/api-docs/api/lnd/router/track-payment-v2/) | ✅ | `GET` | `/v2/router/track/{payment_hash}` |
* We are deprecating `OutgoingChanId` in favour of `OutgoingChanIds` in the
`QueryRoutes` RPC. This [transition](https://github.com/lightningnetwork/lnd/pull/10057) allows us to specify more than one outgoing channel
the pathfinder should use when finding a route.
# Technical and Architectural Updates
## BOLT Spec Updates
+1753 -1737
View File
File diff suppressed because it is too large. Load diff
+9 -3
View File
@@ -3158,10 +3158,10 @@ message QueryRoutesRequest {
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.
Deprecated, use outgoing_chan_ids. 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];
uint64 outgoing_chan_id = 14 [jstype = JS_STRING, deprecated = true];
/*
The pubkey of the last hop of the route. If empty, any hop may be used.
@@ -3196,6 +3196,12 @@ message QueryRoutesRequest {
only, to 1 to optimize for reliability only or a value inbetween for a mix.
*/
double time_pref = 18;
/*
The channel ids of the channels allowed for the first hop. If empty, any
channel may be used.
*/
repeated uint64 outgoing_chan_ids = 20;
}
message NodePair {
+22 -2
View File
@@ -1459,7 +1459,7 @@
},
{
"name": "outgoing_chan_id",
"description": "The channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used.",
"description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used.",
"in": "query",
"required": false,
"type": "string",
@@ -1520,6 +1520,18 @@
"required": false,
"type": "number",
"format": "double"
},
{
"name": "outgoing_chan_ids",
"description": "The channel ids of the channels allowed for the first hop. If empty, any\nchannel may be used.",
"in": "query",
"required": false,
"type": "array",
"items": {
"type": "string",
"format": "uint64"
},
"collectionFormat": "multi"
}
],
"tags": [
@@ -1629,7 +1641,7 @@
"outgoing_chan_id": {
"type": "string",
"format": "uint64",
"description": "The channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used."
"description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used."
},
"last_hop_pubkey": {
"type": "string",
@@ -1663,6 +1675,14 @@
"type": "number",
"format": "double",
"description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix."
},
"outgoing_chan_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uint64"
},
"description": "The channel ids of the channels allowed for the first hop. If empty, any\nchannel may be used."
}
}
}
+12 -2
View File
@@ -413,8 +413,18 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
BlindedPaymentPathSet: blindedPathSet,
}
// Pass along an outgoing channel restriction if specified.
if in.OutgoingChanId != 0 {
// We set the outgoing channel restrictions if the user provides a
// list of channel ids. We also handle the case where the user
// provides the deprecated `OutgoingChanId` field.
switch {
case len(in.OutgoingChanIds) > 0 && in.OutgoingChanId != 0:
return nil, errors.New("outgoing_chan_id and " +
"outgoing_chan_ids cannot both be set")
case len(in.OutgoingChanIds) > 0:
restrictions.OutgoingChannelIDs = in.OutgoingChanIds
case in.OutgoingChanId != 0:
restrictions.OutgoingChannelIDs = []uint64{in.OutgoingChanId}
}
+53 -10
View File
@@ -34,25 +34,39 @@ var (
node2 = route.Vertex{11}
)
var (
singleChanID = "singleChanID"
multiChanID = "multiChanID"
bothChanIds = "bothChanIds"
)
// TestQueryRoutes asserts that query routes rpc parameters are properly parsed
// and passed onto path finding.
func TestQueryRoutes(t *testing.T) {
t.Run("no mission control", func(t *testing.T) {
testQueryRoutes(t, false, false, true)
testQueryRoutes(t, false, false, true, singleChanID)
})
t.Run("no mission control and msat", func(t *testing.T) {
testQueryRoutes(t, false, true, true)
testQueryRoutes(t, false, true, true, singleChanID)
})
t.Run("with mission control", func(t *testing.T) {
testQueryRoutes(t, true, false, true)
testQueryRoutes(t, true, false, true, singleChanID)
})
t.Run("no mission control bad cltv limit", func(t *testing.T) {
testQueryRoutes(t, false, false, false)
testQueryRoutes(t, false, false, false, singleChanID)
})
t.Run("both outgoing chan id and chan ids", func(t *testing.T) {
testQueryRoutes(t, true, false, true, bothChanIds)
})
t.Run("multiple outgoing chan ids", func(t *testing.T) {
testQueryRoutes(t, false, true, true, multiChanID)
})
}
func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
setTimelock bool) {
setTimelock bool, outgoingChanConfig string) {
ignoreNodeBytes, err := hex.DecodeString(ignoreNodeKey)
if err != nil {
@@ -68,8 +82,9 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
}
var (
lastHop = route.Vertex{64}
outgoingChan = uint64(383322)
lastHop = route.Vertex{64}
outgoingChan = uint64(383322)
outgoingChanIds = []uint64{383322, 383323}
)
hintNode, err := route.NewVertexFromStr(hintNodeKey)
@@ -102,7 +117,6 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
}},
UseMissionControl: useMissionControl,
LastHopPubkey: lastHop[:],
OutgoingChanId: outgoingChan,
DestFeatures: []lnrpc.FeatureBit{lnrpc.FeatureBit_MPP_OPT},
RouteHints: rpcRouteHints,
}
@@ -124,6 +138,18 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
}
}
switch outgoingChanConfig {
case singleChanID:
request.OutgoingChanId = outgoingChan
case multiChanID:
request.OutgoingChanIds = outgoingChanIds
case bothChanIds:
request.OutgoingChanId = outgoingChan
request.OutgoingChanIds = outgoingChanIds
}
findRoute := func(req *routing.RouteRequest) (*route.Route, float64,
error) {
@@ -165,8 +191,18 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
t.Fatal("unexpected last hop")
}
if restrictions.OutgoingChannelIDs[0] != outgoingChan {
t.Fatal("unexpected outgoing channel id")
switch outgoingChanConfig {
case singleChanID:
require.Equal(
t, restrictions.OutgoingChannelIDs,
[]uint64{outgoingChan},
)
case multiChanID:
require.Equal(
t, restrictions.OutgoingChannelIDs,
outgoingChanIds,
)
}
if !restrictions.DestFeatures.HasFeature(lnwire.MPPOptional) {
@@ -230,6 +266,13 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
resp, err := backend.QueryRoutes(context.Background(), request)
// If we're using both OutgoingChanId and OutgoingChanIds, we should get
// an error.
if outgoingChanConfig == bothChanIds {
require.Error(t, err)
return
}
// If no MaxTotalTimelock was set for the QueryRoutes request, make
// sure an error was returned.
if !setTimelock {