Skip to content

Commit b7b7c2a

Browse files
routerrpc: allow BuildRoute RPC to use arbitrary source
The routerrpc.Server and ChannelRouter assume that the source public key for a route is always the node’s public key when calling BuildRoute. We update to allow overriding of this field to any arbitrary public key. The list of hop public keys must be connected to the source via channels in our graph view.
1 parent c4004b9 commit b7b7c2a

File tree

6 files changed

+457
-402
lines changed

6 files changed

+457
-402
lines changed

lnrpc/routerrpc/router.pb.go

Lines changed: 393 additions & 381 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/routerrpc/router.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,12 @@ message BuildRouteRequest {
753753
base64.
754754
*/
755755
map<uint64, bytes> first_hop_custom_records = 6;
756+
757+
/*
758+
An optional source node public key from whose perspective the route is to be
759+
built. If empty, the router's identity key is assumed.
760+
*/
761+
bytes source_pubkey = 7;
756762
}
757763

758764
message BuildRouteResponse {

lnrpc/routerrpc/router.swagger.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,11 @@
13131313
"format": "byte"
13141314
},
13151315
"description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64."
1316+
},
1317+
"source_pubkey": {
1318+
"type": "string",
1319+
"format": "byte",
1320+
"description": "An optional source node public key from whose perspective the route is to be\nbuilt. If empty, the router's identity key is assumed."
13161321
}
13171322
}
13181323
},

lnrpc/routerrpc/router_server.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,10 +1488,23 @@ func (s *Server) BuildRoute(_ context.Context,
14881488
firstHopBlob = fn.Some(firstHopData)
14891489
}
14901490

1491+
// Set the source node based on the request, using the router's
1492+
// self-node as the default if no source_pubkey is provided.
1493+
var sourceNode route.Vertex
1494+
var err error
1495+
if len(req.SourcePubkey) != 0 {
1496+
sourceNode, err = route.NewVertexFromBytes(req.SourcePubkey)
1497+
if err != nil {
1498+
return nil, err
1499+
}
1500+
} else {
1501+
sourceNode = s.cfg.Router.GetSelfNode()
1502+
}
1503+
14911504
// Build the route and return it to the caller.
14921505
route, err := s.cfg.Router.BuildRoute(
1493-
amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
1494-
firstHopBlob,
1506+
sourceNode, amt, hops, outgoingChan, req.FinalCltvDelta,
1507+
payAddr, firstHopBlob,
14951508
)
14961509
if err != nil {
14971510
return nil, err

routing/router.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,15 +1376,18 @@ func (e ErrNoChannel) Error() string {
13761376
"node index %v", e.position)
13771377
}
13781378

1379-
// BuildRoute returns a fully specified route based on a list of pubkeys. If
1380-
// amount is nil, the minimum routable amount is used. To force a specific
1381-
// outgoing channel, use the outgoingChan parameter.
1382-
func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
1383-
hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32,
1379+
// BuildRoute builds a fully specified route based on a list of pubkeys from
1380+
// the perspective of the provided source node. If amount is nil, the minimum
1381+
// routable amount is used. To force a specific outgoing channel, use the
1382+
// outgoingChan parameter.
1383+
func (r *ChannelRouter) BuildRoute(sourceNode route.Vertex,
1384+
amt fn.Option[lnwire.MilliSatoshi], hops []route.Vertex,
1385+
outgoingChan *uint64, finalCltvDelta int32,
13841386
payAddr fn.Option[[32]byte], firstHopBlob fn.Option[[]byte]) (
13851387
*route.Route, error) {
13861388

1387-
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt)
1389+
log.Tracef("BuildRoute called: sourceNode=%v, hopsCount=%v, amt=%v",
1390+
sourceNode, len(hops), amt)
13881391

13891392
var outgoingChans map[uint64]struct{}
13901393
if outgoingChan != nil {
@@ -1403,12 +1406,10 @@ func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
14031406
return nil, err
14041407
}
14051408

1406-
sourceNode := r.cfg.SelfNode
1407-
14081409
// We check that each node in the route has a connection to others that
14091410
// can forward in principle.
14101411
unifiers, err := getEdgeUnifiers(
1411-
r.cfg.SelfNode, hops, outgoingChans, r.cfg.RoutingGraph,
1412+
sourceNode, hops, outgoingChans, r.cfg.RoutingGraph,
14121413
)
14131414
if err != nil {
14141415
return nil, err

routing/router_test.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,17 +1661,21 @@ func TestBuildRoute(t *testing.T) {
16611661

16621662
noAmt := fn.None[lnwire.MilliSatoshi]()
16631663

1664+
selfNode := ctx.router.GetSelfNode()
1665+
16641666
// Test that we can't build a route when no hops are given.
16651667
hops = []route.Vertex{}
16661668
_, err = ctx.router.BuildRoute(
1667-
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
1669+
selfNode, noAmt, hops, nil, 40,
1670+
fn.None[[32]byte](), fn.None[[]byte](),
16681671
)
16691672
require.Error(t, err)
16701673

16711674
// Create hop list for an unknown destination.
16721675
hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]}
16731676
_, err = ctx.router.BuildRoute(
1674-
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
1677+
selfNode, noAmt, hops, nil, 40,
1678+
fn.Some(payAddr), fn.None[[]byte](),
16751679
)
16761680
noChanErr := ErrNoChannel{}
16771681
require.ErrorAs(t, err, &noChanErr)
@@ -1683,8 +1687,8 @@ func TestBuildRoute(t *testing.T) {
16831687

16841688
// Build the route for the given amount.
16851689
rt, err := ctx.router.BuildRoute(
1686-
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
1687-
fn.None[[]byte](),
1690+
selfNode, fn.Some(amt), hops, nil, 40,
1691+
fn.Some(payAddr), fn.None[[]byte](),
16881692
)
16891693
require.NoError(t, err)
16901694

@@ -1696,7 +1700,8 @@ func TestBuildRoute(t *testing.T) {
16961700

16971701
// Build the route for the minimum amount.
16981702
rt, err = ctx.router.BuildRoute(
1699-
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
1703+
selfNode, noAmt, hops, nil, 40,
1704+
fn.Some(payAddr), fn.None[[]byte](),
17001705
)
17011706
require.NoError(t, err)
17021707

@@ -1714,7 +1719,8 @@ func TestBuildRoute(t *testing.T) {
17141719
// There is no amount that can pass through both channel 5 and 4.
17151720
hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]}
17161721
_, err = ctx.router.BuildRoute(
1717-
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
1722+
selfNode, noAmt, hops, nil, 40,
1723+
fn.None[[32]byte](), fn.None[[]byte](),
17181724
)
17191725
require.Error(t, err)
17201726
noChanErr = ErrNoChannel{}
@@ -1734,7 +1740,8 @@ func TestBuildRoute(t *testing.T) {
17341740
// policy of channel 3.
17351741
hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]}
17361742
rt, err = ctx.router.BuildRoute(
1737-
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
1743+
selfNode, noAmt, hops, nil, 40,
1744+
fn.Some(payAddr), fn.None[[]byte](),
17381745
)
17391746
require.NoError(t, err)
17401747
checkHops(rt, []uint64{1, 8}, payAddr)
@@ -1748,8 +1755,8 @@ func TestBuildRoute(t *testing.T) {
17481755
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
17491756
amt = lnwire.NewMSatFromSatoshis(100)
17501757
rt, err = ctx.router.BuildRoute(
1751-
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
1752-
fn.None[[]byte](),
1758+
selfNode, fn.Some(amt), hops, nil, 40,
1759+
fn.Some(payAddr), fn.None[[]byte](),
17531760
)
17541761
require.NoError(t, err)
17551762
checkHops(rt, []uint64{9, 10}, payAddr)
@@ -1765,11 +1772,22 @@ func TestBuildRoute(t *testing.T) {
17651772
// is a third pass through newRoute in which this gets corrected to end
17661773
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
17671774
rt, err = ctx.router.BuildRoute(
1768-
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
1775+
selfNode, noAmt, hops, nil, 40,
1776+
fn.Some(payAddr), fn.None[[]byte](),
17691777
)
17701778
require.NoError(t, err)
17711779
checkHops(rt, []uint64{9, 10}, payAddr)
17721780
require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount)
1781+
1782+
// Test a route built from an alternate source node (eg: b --> c).
1783+
hops = []route.Vertex{ctx.aliases["c"]}
1784+
rt, err = ctx.router.BuildRoute(
1785+
ctx.aliases["b"], fn.Some(amt), hops, nil, 40,
1786+
fn.Some(payAddr), fn.None[[]byte](),
1787+
)
1788+
require.NoError(t, err)
1789+
checkHops(rt, []uint64{7}, payAddr)
1790+
17731791
}
17741792

17751793
// TestReceiverAmtForwardPass tests that the forward pass returns the expected

0 commit comments

Comments
 (0)