Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/release-notes/release-notes-0.21.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@
map is returned. When `include_log` is set to `true`, the log file content is
also included in the response.

* [Add `source_pub_key` to `Route` proto message](https://github.com/lightningnetwork/lnd/pull/9153)
so that routes can be constructed and unmarshalled from the perspective of
different nodes. Defaults to the node's own public key.

## lncli Updates

* The `getdebuginfo` command now supports an `--include_log` flag. By default,
Expand Down
19 changes: 15 additions & 4 deletions lnrpc/lightning.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions lnrpc/lightning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3554,6 +3554,12 @@ message Route {
Custom channel data that might be populated in custom channels.
*/
bytes custom_channel_data = 8;

/*
The source node from whose perspective the route was built. Defaults to
the node's own public key.
*/
string source_pub_key = 9;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use optional

}

message NodeInfoRequest {
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/lightning.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -7529,6 +7529,10 @@
"type": "string",
"format": "byte",
"description": "Custom channel data that might be populated in custom channels."
},
"source_pub_key": {
"type": "string",
"description": "The source node from whose perspective the route was built. Defaults to\nthe node's own public key."
}
},
"description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for."
Expand Down
19 changes: 15 additions & 4 deletions lnrpc/routerrpc/router.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions lnrpc/routerrpc/router.proto
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,12 @@ message BuildRouteRequest {
base64.
*/
map<uint64, bytes> first_hop_custom_records = 6;

/*
An optional source node public key from whose perspective the route is to be
built. If empty, the router's identity key is assumed.
*/
bytes source_pub_key = 7;
}

message BuildRouteResponse {
Expand Down
9 changes: 9 additions & 0 deletions lnrpc/routerrpc/router.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,10 @@
"type": "string",
"format": "byte",
"description": "Custom channel data that might be populated in custom channels."
},
"source_pub_key": {
"type": "string",
"description": "The source node from whose perspective the route was built. Defaults to\nthe node's own public key."
}
},
"description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for."
Expand Down Expand Up @@ -1344,6 +1348,11 @@
"format": "byte"
},
"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."
},
"source_pub_key": {
"type": "string",
"format": "byte",
"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."
}
}
},
Expand Down
24 changes: 21 additions & 3 deletions lnrpc/routerrpc/router_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error)
TotalAmtMsat: int64(route.TotalAmount),
Hops: make([]*lnrpc.Hop, len(route.Hops)),
FirstHopAmountMsat: int64(route.FirstHopAmount.Val.Int()),
SourcePubKey: route.SourcePubKey.String(),
}

// Encode the route's custom channel data (if available).
Expand Down Expand Up @@ -808,7 +809,25 @@ func (r *RouterBackend) UnmarshallHop(rpcHop *lnrpc.Hop,
func (r *RouterBackend) UnmarshallRoute(rpcroute *lnrpc.Route) (
*route.Route, error) {

prevNodePubKey := r.SelfNode
var err error

// Most routes we construct are from our node's perspective.
sourcePubKey := r.SelfNode

// Optionally override with supplied public key.
if rpcroute.SourcePubKey != "" {
sourcePubKey, err = route.NewVertexFromStr(
rpcroute.SourcePubKey,
)
if err != nil {
return nil, fmt.Errorf("invalid source pubkey: %w", err)
}
}

// The previous node starts as the source of the route. This is
// used for implicit hop pubkey resolution when hops only specify
// a channel ID.
prevNodePubKey := sourcePubKey

hops := make([]*route.Hop, len(rpcroute.Hops))
for i, hop := range rpcroute.Hops {
Expand All @@ -818,14 +837,13 @@ func (r *RouterBackend) UnmarshallRoute(rpcroute *lnrpc.Route) (
}

hops[i] = routeHop

prevNodePubKey = routeHop.PubKeyBytes
}

route, err := route.NewRouteFromHops(
lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
rpcroute.TotalTimeLock,
r.SelfNode,
sourcePubKey,
hops,
)
if err != nil {
Expand Down
131 changes: 131 additions & 0 deletions lnrpc/routerrpc/router_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package routerrpc
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -989,3 +990,133 @@ func TestMarshallRouteChanCapacity(t *testing.T) {
t, hop1Forward.ToSatoshis(), rpcRoute.Hops[1].ChanCapacity,
)
}

// TestUnmarshallRouteSourcePubKey tests that UnmarshallRoute correctly handles
// the source_pub_key field on the Route proto message.
func TestUnmarshallRouteSourcePubKey(t *testing.T) {
t.Parallel()

// Define test vertices. The self node is the default source used
// when source_pub_key is empty.
selfNode := route.Vertex{1, 2, 3}
overrideSource := route.Vertex{4, 5, 6}

// A hop target that will appear in the route.
hopTarget := route.Vertex{7, 8, 9}

backend := &RouterBackend{
SelfNode: selfNode,
FetchChannelEndpoints: func(chanID uint64) (route.Vertex,
route.Vertex, error) {

// For channel 12345, return overrideSource and
// hopTarget as the two endpoints.
if chanID == 12345 {
return overrideSource, hopTarget, nil
}

return route.Vertex{}, route.Vertex{},
fmt.Errorf("unknown channel: %d", chanID)
},
}

t.Run("default source when empty", func(t *testing.T) {
t.Parallel()

rpcRoute := &lnrpc.Route{
TotalAmtMsat: 1000,
TotalTimeLock: 144,
Hops: []*lnrpc.Hop{
{
PubKey: hex.EncodeToString(
hopTarget[:],
),
ChanId: 12345,
AmtToForward: 1000,
},
},
}

r, err := backend.UnmarshallRoute(rpcRoute)
require.NoError(t, err)

// With no SourcePubKey set, the route source should
// default to the backend's SelfNode.
require.Equal(t, selfNode, r.SourcePubKey)
})

t.Run("override source with valid pubkey", func(t *testing.T) {
t.Parallel()

rpcRoute := &lnrpc.Route{
TotalAmtMsat: 1000,
TotalTimeLock: 144,
SourcePubKey: hex.EncodeToString(overrideSource[:]),
Hops: []*lnrpc.Hop{
{
PubKey: hex.EncodeToString(
hopTarget[:],
),
ChanId: 12345,
AmtToForward: 1000,
},
},
}

r, err := backend.UnmarshallRoute(rpcRoute)
require.NoError(t, err)

// The route source should match the override.
require.Equal(t, overrideSource, r.SourcePubKey)
})

t.Run("invalid source pubkey", func(t *testing.T) {
t.Parallel()

rpcRoute := &lnrpc.Route{
TotalAmtMsat: 1000,
TotalTimeLock: 144,
SourcePubKey: "not-a-valid-hex-pubkey",
Hops: []*lnrpc.Hop{
{
PubKey: hex.EncodeToString(
hopTarget[:],
),
ChanId: 12345,
AmtToForward: 1000,
},
},
}

_, err := backend.UnmarshallRoute(rpcRoute)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid source pubkey")
})

t.Run("implicit hop resolution with src override ", func(t *testing.T) {
t.Parallel()

// Hop omits PubKey, forcing implicit resolution via
// FetchChannelEndpoints using the override source.
rpcRoute := &lnrpc.Route{
TotalAmtMsat: 1000,
TotalTimeLock: 144,
SourcePubKey: hex.EncodeToString(overrideSource[:]),
Hops: []*lnrpc.Hop{
{
ChanId: 12345,
AmtToForward: 1000,
},
},
}

r, err := backend.UnmarshallRoute(rpcRoute)
require.NoError(t, err)

// The hop should resolve to hopTarget (the other
// endpoint of channel 12345, since the source is
// overrideSource).
require.Equal(t, hopTarget, r.Hops[0].PubKeyBytes)
require.Equal(t, overrideSource, r.SourcePubKey)
})
}
14 changes: 12 additions & 2 deletions lnrpc/routerrpc/router_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,10 +1701,20 @@ func (s *Server) BuildRoute(_ context.Context,
firstHopBlob = fn.Some(firstHopData)
}

// Default to the router's own identity as the route source.
sourceNode := s.cfg.RouterBackend.SelfNode
if len(req.SourcePubKey) != 0 {
src, err := route.NewVertexFromBytes(req.SourcePubKey)
if err != nil {
return nil, err
}
sourceNode = src
}

// Build the route and return it to the caller.
route, err := s.cfg.Router.BuildRoute(
amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
firstHopBlob,
sourceNode, amt, hops, outgoingChan, req.FinalCltvDelta,
payAddr, firstHopBlob,
)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/switchrpc/switch.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@
"type": "string",
"format": "byte",
"description": "Custom channel data that might be populated in custom channels."
},
"source_pub_key": {
"type": "string",
"description": "The source node from whose perspective the route was built. Defaults to\nthe node's own public key."
}
},
"description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for."
Expand Down
Loading
Loading