Skip to content

Commit 46eaf15

Browse files
committed
lnd: add aux data parser
This commit adds an optional data parser that can inspect and in-place format custom data of certain RPC messages. We don't add an implementation of the interface itself, as that will be provided by external components when packaging up lnd as a bundle with other software.
1 parent a1cf178 commit 46eaf15

File tree

4 files changed

+143
-5
lines changed

4 files changed

+143
-5
lines changed

config_builder.go

+4
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ type AuxComponents struct {
178178
// AuxSigner is an optional signer that can be used to sign auxiliary
179179
// leaves for certain custom channel types.
180180
AuxSigner fn.Option[lnwallet.AuxSigner]
181+
182+
// AuxDataParser is an optional data parser that can be used to parse
183+
// auxiliary data for certain custom channel types.
184+
AuxDataParser fn.Option[AuxDataParser]
181185
}
182186

183187
// DefaultWalletImpl is the default implementation of our normal, btcwallet

lnrpc/routerrpc/router_backend.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/lightningnetwork/lnd/routing/route"
2626
"github.com/lightningnetwork/lnd/subscribe"
2727
"github.com/lightningnetwork/lnd/zpay32"
28+
"google.golang.org/protobuf/proto"
2829
)
2930

3031
const (
@@ -104,6 +105,10 @@ type RouterBackend struct {
104105
// TODO(yy): remove this config after the new status code is fully
105106
// deployed to the network(v0.20.0).
106107
UseStatusInitiated bool
108+
109+
// ParseCustomChannelData is a function that can be used to parse custom
110+
// channel data from the first hop of a route.
111+
ParseCustomChannelData func(message proto.Message) error
107112
}
108113

109114
// MissionControl defines the mission control dependencies of routerrpc.
@@ -596,8 +601,14 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error)
596601

597602
resp.CustomChannelData = customData
598603

599-
// TODO(guggero): Feed the route into the custom data parser
600-
// (part 3 of the mega PR series).
604+
// Allow the aux data parser to parse the custom records into
605+
// a human-readable JSON (if available).
606+
if r.ParseCustomChannelData != nil {
607+
err := r.ParseCustomChannelData(resp)
608+
if err != nil {
609+
return nil, err
610+
}
611+
}
601612
}
602613

603614
incomingAmt := route.TotalAmount

rpcserver.go

+60-2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import (
8787
"google.golang.org/grpc"
8888
"google.golang.org/grpc/codes"
8989
"google.golang.org/grpc/status"
90+
"google.golang.org/protobuf/proto"
9091
"gopkg.in/macaroon-bakery.v2/bakery"
9192
)
9293

@@ -579,6 +580,17 @@ func MainRPCServerPermissions() map[string][]bakery.Op {
579580
}
580581
}
581582

583+
// AuxDataParser is an interface that is used to parse auxiliary custom data
584+
// within RPC messages. This is used to transform binary blobs to human-readable
585+
// JSON representations.
586+
type AuxDataParser interface {
587+
// InlineParseCustomData replaces any custom data binary blob in the
588+
// given RPC message with its corresponding JSON formatted data. This
589+
// transforms the binary (likely TLV encoded) data to a human-readable
590+
// JSON representation (still as byte slice).
591+
InlineParseCustomData(msg proto.Message) error
592+
}
593+
582594
// rpcServer is a gRPC, RPC front end to the lnd daemon.
583595
// TODO(roasbeef): pagination support for the list-style calls
584596
type rpcServer struct {
@@ -731,6 +743,20 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
731743
},
732744
SetChannelAuto: s.chanStatusMgr.RequestAuto,
733745
UseStatusInitiated: subServerCgs.RouterRPC.UseStatusInitiated,
746+
ParseCustomChannelData: func(msg proto.Message) error {
747+
err = fn.MapOptionZ(
748+
r.server.implCfg.AuxDataParser,
749+
func(parser AuxDataParser) error {
750+
return parser.InlineParseCustomData(msg)
751+
},
752+
)
753+
if err != nil {
754+
return fmt.Errorf("error parsing custom data: "+
755+
"%w", err)
756+
}
757+
758+
return nil
759+
},
734760
}
735761

736762
genInvoiceFeatures := func() *lnwire.FeatureVector {
@@ -3596,7 +3622,7 @@ func (r *rpcServer) ChannelBalance(ctx context.Context,
35963622
unsettledRemoteBalance, pendingOpenLocalBalance,
35973623
pendingOpenRemoteBalance)
35983624

3599-
return &lnrpc.ChannelBalanceResponse{
3625+
resp := &lnrpc.ChannelBalanceResponse{
36003626
LocalBalance: &lnrpc.Amount{
36013627
Sat: uint64(localBalance.ToSatoshis()),
36023628
Msat: uint64(localBalance),
@@ -3626,7 +3652,19 @@ func (r *rpcServer) ChannelBalance(ctx context.Context,
36263652
// Deprecated fields.
36273653
Balance: int64(localBalance.ToSatoshis()),
36283654
PendingOpenBalance: int64(pendingOpenLocalBalance.ToSatoshis()),
3629-
}, nil
3655+
}
3656+
3657+
err = fn.MapOptionZ(
3658+
r.server.implCfg.AuxDataParser,
3659+
func(parser AuxDataParser) error {
3660+
return parser.InlineParseCustomData(resp)
3661+
},
3662+
)
3663+
if err != nil {
3664+
return nil, fmt.Errorf("error parsing custom data: %w", err)
3665+
}
3666+
3667+
return resp, nil
36303668
}
36313669

36323670
type (
@@ -4068,6 +4106,16 @@ func (r *rpcServer) PendingChannels(ctx context.Context,
40684106
resp.WaitingCloseChannels = waitingCloseChannels
40694107
resp.TotalLimboBalance += limbo
40704108

4109+
err = fn.MapOptionZ(
4110+
r.server.implCfg.AuxDataParser,
4111+
func(parser AuxDataParser) error {
4112+
return parser.InlineParseCustomData(resp)
4113+
},
4114+
)
4115+
if err != nil {
4116+
return nil, fmt.Errorf("error parsing custom data: %w", err)
4117+
}
4118+
40714119
return resp, nil
40724120
}
40734121

@@ -4382,6 +4430,16 @@ func (r *rpcServer) ListChannels(ctx context.Context,
43824430
resp.Channels = append(resp.Channels, channel)
43834431
}
43844432

4433+
err = fn.MapOptionZ(
4434+
r.server.implCfg.AuxDataParser,
4435+
func(parser AuxDataParser) error {
4436+
return parser.InlineParseCustomData(resp)
4437+
},
4438+
)
4439+
if err != nil {
4440+
return nil, fmt.Errorf("error parsing custom data: %w", err)
4441+
}
4442+
43854443
return resp, nil
43864444
}
43874445

rpcserver_test.go

+66-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,79 @@
11
package lnd
22

33
import (
4+
"fmt"
45
"testing"
56

7+
"github.com/lightningnetwork/lnd/channeldb"
8+
"github.com/lightningnetwork/lnd/fn"
9+
"github.com/lightningnetwork/lnd/lnrpc"
610
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"google.golang.org/protobuf/proto"
713
)
814

915
func TestGetAllPermissions(t *testing.T) {
1016
perms := GetAllPermissions()
1117

12-
// Currently there are there are 16 entity:action pairs in use.
18+
// Currently there are 16 entity:action pairs in use.
1319
assert.Equal(t, len(perms), 16)
1420
}
21+
22+
// mockDataParser is a mock implementation of the AuxDataParser interface.
23+
type mockDataParser struct {
24+
}
25+
26+
// InlineParseCustomData replaces any custom data binary blob in the given RPC
27+
// message with its corresponding JSON formatted data. This transforms the
28+
// binary (likely TLV encoded) data to a human-readable JSON representation
29+
// (still as byte slice).
30+
func (m *mockDataParser) InlineParseCustomData(msg proto.Message) error {
31+
switch m := msg.(type) {
32+
case *lnrpc.ChannelBalanceResponse:
33+
m.CustomChannelData = []byte(`{"foo": "bar"}`)
34+
35+
return nil
36+
37+
default:
38+
return fmt.Errorf("mock only supports ChannelBalanceResponse")
39+
}
40+
}
41+
42+
func TestAuxDataParser(t *testing.T) {
43+
// We create an empty channeldb, so we can fetch some channels.
44+
cdb, err := channeldb.Open(t.TempDir())
45+
require.NoError(t, err)
46+
t.Cleanup(func() {
47+
require.NoError(t, cdb.Close())
48+
})
49+
50+
r := &rpcServer{
51+
server: &server{
52+
chanStateDB: cdb.ChannelStateDB(),
53+
implCfg: &ImplementationCfg{
54+
AuxComponents: AuxComponents{
55+
AuxDataParser: fn.Some[AuxDataParser](
56+
&mockDataParser{},
57+
),
58+
},
59+
},
60+
},
61+
}
62+
63+
// With the aux data parser in place, we should get a formatted JSON
64+
// in the custom channel data field.
65+
resp, err := r.ChannelBalance(nil, &lnrpc.ChannelBalanceRequest{})
66+
require.NoError(t, err)
67+
require.NotNil(t, resp)
68+
require.Equal(t, []byte(`{"foo": "bar"}`), resp.CustomChannelData)
69+
70+
// If we don't supply the aux data parser, we should get the raw binary
71+
// data. Which in this case is just two VarInt fields (1 byte each) that
72+
// represent the value of 0 (zero active and zero pending channels).
73+
r.server.implCfg.AuxComponents.AuxDataParser = fn.None[AuxDataParser]()
74+
75+
resp, err = r.ChannelBalance(nil, &lnrpc.ChannelBalanceRequest{})
76+
require.NoError(t, err)
77+
require.NotNil(t, resp)
78+
require.Equal(t, []byte{0x00, 0x00}, resp.CustomChannelData)
79+
}

0 commit comments

Comments
 (0)