From 201950e8523a4ca1ae81002a81638c4881b46a75 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 24 Jul 2024 16:45:03 +0200 Subject: [PATCH 1/2] state_client: add default timeout --- lnd_services.go | 2 +- state_client.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lnd_services.go b/lnd_services.go index 40a6c66..b417399 100644 --- a/lnd_services.go +++ b/lnd_services.go @@ -307,7 +307,7 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) { } basicClient := lnrpc.NewLightningClient(conn) - stateClient := newStateClient(conn, readonlyMac) + stateClient := newStateClient(conn, readonlyMac, timeout) versionerClient := newVersionerClient(conn, readonlyMac, timeout) cleanupConn := func() { diff --git a/state_client.go b/state_client.go index 498ecb7..18abe28 100644 --- a/state_client.go +++ b/state_client.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync" + "time" "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/grpc" @@ -90,17 +91,19 @@ func (s WalletState) ReadyForGetInfo() bool { type stateClient struct { client lnrpc.StateClient readonlyMac serializedMacaroon + timeout time.Duration wg sync.WaitGroup } // newStateClient returns a new stateClient. func newStateClient(conn grpc.ClientConnInterface, - readonlyMac serializedMacaroon) *stateClient { + readonlyMac serializedMacaroon, timeout time.Duration) *stateClient { return &stateClient{ client: lnrpc.NewStateClient(conn), readonlyMac: readonlyMac, + timeout: timeout, } } From cf9a271170076be22160c732753fc9a8ab19dae9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 24 Jul 2024 16:46:14 +0200 Subject: [PATCH 2/2] multi: add RawClientWithMacAuth to every client --- chainkit_client.go | 15 +++++++++++++++ chainnotifier_client.go | 15 +++++++++++++++ invoices_client.go | 15 +++++++++++++++ lightning_client.go | 16 +++++++++++++++- lnd_services.go | 8 ++++++++ lnd_services_test.go | 10 ++++++++++ macaroon_recipes.go | 11 +++++++++++ router_client.go | 15 +++++++++++++++ signer_client.go | 15 +++++++++++++++ state_client.go | 15 +++++++++++++++ versioner_client.go | 19 +++++++++++++++++-- walletkit_client.go | 13 ++++++++++++- 12 files changed, 163 insertions(+), 4 deletions(-) diff --git a/chainkit_client.go b/chainkit_client.go index 3351ddb..48a0fcd 100644 --- a/chainkit_client.go +++ b/chainkit_client.go @@ -14,6 +14,8 @@ import ( // ChainKitClient exposes chain functionality. type ChainKitClient interface { + ServiceClient[chainrpc.ChainKitClient] + // GetBlock returns a block given the corresponding block hash. GetBlock(ctx context.Context, hash chainhash.Hash) (*wire.MsgBlock, error) @@ -41,6 +43,10 @@ type chainKitClient struct { wg sync.WaitGroup } +// A compile time check to ensure that chainKitClient implements the +// ChainKitClient interface. +var _ ChainKitClient = (*chainKitClient)(nil) + func newChainKitClient(conn grpc.ClientConnInterface, chainMac serializedMacaroon, timeout time.Duration) *chainKitClient { @@ -55,6 +61,15 @@ func (s *chainKitClient) WaitForFinished() { s.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *chainKitClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + chainrpc.ChainKitClient) { + + return s.chainMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + // GetBlock returns a block given the corresponding block hash. func (s *chainKitClient) GetBlock(ctxParent context.Context, hash chainhash.Hash) (*wire.MsgBlock, error) { diff --git a/chainnotifier_client.go b/chainnotifier_client.go index 744303d..0c7b088 100644 --- a/chainnotifier_client.go +++ b/chainnotifier_client.go @@ -60,6 +60,8 @@ func WithReOrgChan(reOrgChan chan struct{}) NotifierOption { // ChainNotifierClient exposes base lightning functionality. type ChainNotifierClient interface { + ServiceClient[chainrpc.ChainNotifierClient] + RegisterBlockEpochNtfn(ctx context.Context) ( chan int32, chan error, error) @@ -81,6 +83,10 @@ type chainNotifierClient struct { wg sync.WaitGroup } +// A compile time check to ensure that chainNotifierClient implements the +// ChainNotifierClient interface. +var _ ChainNotifierClient = (*chainNotifierClient)(nil) + func newChainNotifierClient(conn grpc.ClientConnInterface, chainMac serializedMacaroon, timeout time.Duration) *chainNotifierClient { @@ -95,6 +101,15 @@ func (s *chainNotifierClient) WaitForFinished() { s.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *chainNotifierClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + chainrpc.ChainNotifierClient) { + + return s.chainMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, outpoint *wire.OutPoint, pkScript []byte, heightHint int32) ( chan *chainntnfs.SpendDetail, chan error, error) { diff --git a/invoices_client.go b/invoices_client.go index 0b93cc8..90c0b3a 100644 --- a/invoices_client.go +++ b/invoices_client.go @@ -41,6 +41,8 @@ type InvoiceHtlcModifyHandler func(context.Context, // InvoicesClient exposes invoice functionality. type InvoicesClient interface { + ServiceClient[invoicesrpc.InvoicesClient] + SubscribeSingleInvoice(ctx context.Context, hash lntypes.Hash) ( <-chan InvoiceUpdate, <-chan error, error) @@ -75,6 +77,10 @@ type invoicesClient struct { wg sync.WaitGroup } +// A compile time check to ensure that invoicesClient implements the +// InvoicesClient interface. +var _ InvoicesClient = (*invoicesClient)(nil) + func newInvoicesClient(conn grpc.ClientConnInterface, invoiceMac serializedMacaroon, timeout time.Duration) *invoicesClient { @@ -94,6 +100,15 @@ func (s *invoicesClient) WaitForFinished() { s.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *invoicesClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + invoicesrpc.InvoicesClient) { + + return s.invoiceMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + func (s *invoicesClient) SettleInvoice(ctx context.Context, preimage lntypes.Preimage) error { diff --git a/lightning_client.go b/lightning_client.go index f5a7c14..ba7839c 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -70,11 +70,12 @@ func WithRemoteReserve(reserve uint64) OpenChannelOption { return func(r *lnrpc.OpenChannelRequest) { r.RemoteChanReserveSat = reserve } - } // LightningClient exposes base lightning functionality. type LightningClient interface { + ServiceClient[lnrpc.LightningClient] + PayInvoice(ctx context.Context, invoice string, maxFee btcutil.Amount, outgoingChannel *uint64) chan PaymentResult @@ -1321,6 +1322,10 @@ type lightningClient struct { adminMac serializedMacaroon } +// A compile time check to ensure that lightningClient implements the +// LightningClient interface. +var _ LightningClient = (*lightningClient)(nil) + func newLightningClient(conn grpc.ClientConnInterface, timeout time.Duration, params *chaincfg.Params, adminMac serializedMacaroon) *lightningClient { @@ -1344,6 +1349,15 @@ func (s *lightningClient) WaitForFinished() { s.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *lightningClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + lnrpc.LightningClient) { + + return s.adminMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + // WalletBalance returns a summary of the node's wallet balance. func (s *lightningClient) WalletBalance(ctx context.Context) ( *WalletBalance, error) { diff --git a/lnd_services.go b/lnd_services.go index b417399..e6c7cfb 100644 --- a/lnd_services.go +++ b/lnd_services.go @@ -78,6 +78,14 @@ var ( } ) +// ServiceClient is an interface that all lnd service clients need to implement. +type ServiceClient[T any] interface { + // RawClientWithMacAuth returns a context with the proper macaroon + // authentication, the default RPC timeout, and the raw client. + RawClientWithMacAuth(parentCtx context.Context) (context.Context, + time.Duration, T) +} + // LndServicesConfig holds all configuration settings that are needed to connect // to an lnd node. type LndServicesConfig struct { diff --git a/lnd_services_test.go b/lnd_services_test.go index b1f46f8..21be763 100644 --- a/lnd_services_test.go +++ b/lnd_services_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc" @@ -18,6 +19,15 @@ type mockVersioner struct { err error } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (m *mockVersioner) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + verrpc.VersionerClient) { + + return parentCtx, 0, nil +} + func (m *mockVersioner) GetVersion(_ context.Context) (*verrpc.Version, error) { return m.version, m.err } diff --git a/macaroon_recipes.go b/macaroon_recipes.go index c90a660..4deb801 100644 --- a/macaroon_recipes.go +++ b/macaroon_recipes.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "slices" "strings" ) @@ -43,6 +44,12 @@ var ( "OpenChannelStream": "OpenChannel", "ListSweepsVerbose": "ListSweeps", } + + // ignores is a list of method names on the client implementations that + // we don't need to check macaroon permissions for. + ignores = []string{ + "RawClientWithMacAuth", + } ) // MacaroonRecipe returns a list of macaroon permissions that is required to use @@ -79,6 +86,10 @@ func MacaroonRecipe(c LightningClient, packages []string) ([]MacaroonPermission, methodName = rename } + if slices.Contains(ignores, methodName) { + continue + } + // The full RPC URI is /package.Service/MethodName. rpcURI := fmt.Sprintf( "/%s.%s/%s", pkg, serverName, methodName, diff --git a/router_client.go b/router_client.go index 74fed92..0064ac1 100644 --- a/router_client.go +++ b/router_client.go @@ -31,6 +31,8 @@ var ErrRouterShuttingDown = errors.New("router shutting down") // RouterClient exposes payment functionality. type RouterClient interface { + ServiceClient[routerrpc.RouterClient] + // SendPayment attempts to route a payment to the final destination. The // call returns a payment update stream and an error stream. SendPayment(ctx context.Context, request SendPaymentRequest) ( @@ -401,6 +403,10 @@ type routerClient struct { wg sync.WaitGroup } +// A compile time check to ensure that routerClient implements the RouterClient +// interface. +var _ RouterClient = (*routerClient)(nil) + func newRouterClient(conn grpc.ClientConnInterface, routerKitMac serializedMacaroon, timeout time.Duration) *routerClient { @@ -422,6 +428,15 @@ func (r *routerClient) WaitForFinished() { r.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (r *routerClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + routerrpc.RouterClient) { + + return r.routerKitMac.WithMacaroonAuth(parentCtx), r.timeout, r.client +} + // SendPayment attempts to route a payment to the final destination. The call // returns a payment update stream and an error stream. func (r *routerClient) SendPayment(ctx context.Context, diff --git a/signer_client.go b/signer_client.go index 23ccb6b..7fa0b84 100644 --- a/signer_client.go +++ b/signer_client.go @@ -18,6 +18,8 @@ import ( // SignerClient exposes sign functionality. type SignerClient interface { + ServiceClient[signrpc.SignerClient] + // SignOutputRaw is a method that can be used to generate a signature // for a set of inputs/outputs to a transaction. Each request specifies // details concerning how the outputs should be signed, which keys they @@ -190,6 +192,10 @@ type signerClient struct { timeout time.Duration } +// A compile time check to ensure that signerClient implements the SignerClient +// interface. +var _ SignerClient = (*signerClient)(nil) + func newSignerClient(conn grpc.ClientConnInterface, signerMac serializedMacaroon, timeout time.Duration) *signerClient { @@ -200,6 +206,15 @@ func newSignerClient(conn grpc.ClientConnInterface, } } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *signerClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + signrpc.SignerClient) { + + return s.signerMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + func marshallSignDescriptors( signDescriptors []*SignDescriptor) []*signrpc.SignDescriptor { diff --git a/state_client.go b/state_client.go index 18abe28..ca9d803 100644 --- a/state_client.go +++ b/state_client.go @@ -12,6 +12,8 @@ import ( // StateClient exposes base lightning functionality. type StateClient interface { + ServiceClient[lnrpc.StateClient] + // SubscribeState subscribes to the current state of the wallet. SubscribeState(ctx context.Context) (chan WalletState, chan error, error) @@ -96,6 +98,10 @@ type stateClient struct { wg sync.WaitGroup } +// A compile time check to ensure that stateClient implements the StateClient +// interface. +var _ StateClient = (*stateClient)(nil) + // newStateClient returns a new stateClient. func newStateClient(conn grpc.ClientConnInterface, readonlyMac serializedMacaroon, timeout time.Duration) *stateClient { @@ -112,6 +118,15 @@ func (s *stateClient) WaitForFinished() { s.wg.Wait() } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (s *stateClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + lnrpc.StateClient) { + + return s.readonlyMac.WithMacaroonAuth(parentCtx), s.timeout, s.client +} + // SubscribeState subscribes to the current state of the wallet. func (s *stateClient) SubscribeState(ctx context.Context) (chan WalletState, chan error, error) { diff --git a/versioner_client.go b/versioner_client.go index 08320dd..1de9de0 100644 --- a/versioner_client.go +++ b/versioner_client.go @@ -12,6 +12,8 @@ import ( // VersionerClient exposes the version of lnd. type VersionerClient interface { + ServiceClient[verrpc.VersionerClient] + // GetVersion returns the version and build information of the lnd // daemon. GetVersion(ctx context.Context) (*verrpc.Version, error) @@ -23,6 +25,10 @@ type versionerClient struct { timeout time.Duration } +// A compile time check to ensure that versionerClient implements the +// VersionerClient interface. +var _ VersionerClient = (*versionerClient)(nil) + func newVersionerClient(conn grpc.ClientConnInterface, readonlyMac serializedMacaroon, timeout time.Duration) *versionerClient { @@ -33,6 +39,15 @@ func newVersionerClient(conn grpc.ClientConnInterface, } } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (v *versionerClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + verrpc.VersionerClient) { + + return v.readonlyMac.WithMacaroonAuth(parentCtx), v.timeout, v.client +} + // GetVersion returns the version and build information of the lnd // daemon. // @@ -47,7 +62,7 @@ func (v *versionerClient) GetVersion(ctx context.Context) (*verrpc.Version, return v.client.GetVersion(rpcCtx, &verrpc.VersionRequest{}) } -// VersionString returns a nice, human readable string of a version returned by +// VersionString returns a nice, human-readable string of a version returned by // the VersionerClient, including all build tags. func VersionString(version *verrpc.Version) string { short := VersionStringShort(version) @@ -55,7 +70,7 @@ func VersionString(version *verrpc.Version) string { return fmt.Sprintf("%s, build tags '%s'", short, enabledTags) } -// VersionStringShort returns a nice, human readable string of a version +// VersionStringShort returns a nice, human-readable string of a version // returned by the VersionerClient. func VersionStringShort(version *verrpc.Version) string { versionStr := fmt.Sprintf( diff --git a/walletkit_client.go b/walletkit_client.go index 9fdb2cc..99174f8 100644 --- a/walletkit_client.go +++ b/walletkit_client.go @@ -66,6 +66,8 @@ func WithUnspentUnconfirmedOnly() ListUnspentOption { // WalletKitClient exposes wallet functionality. type WalletKitClient interface { + ServiceClient[walletrpc.WalletKitClient] + // ListUnspent returns a list of all utxos spendable by the wallet with // a number of confirmations between the specified minimum and maximum. ListUnspent(ctx context.Context, minConfs, maxConfs int32, @@ -214,7 +216,7 @@ type walletKitClient struct { params *chaincfg.Params } -// A compile-time constraint to ensure walletKitclient satisfies the +// A compile time check to ensure that walletKitClient implements the // WalletKitClient interface. var _ WalletKitClient = (*walletKitClient)(nil) @@ -230,6 +232,15 @@ func newWalletKitClient(conn grpc.ClientConnInterface, } } +// RawClientWithMacAuth returns a context with the proper macaroon +// authentication, the default RPC timeout, and the raw client. +func (m *walletKitClient) RawClientWithMacAuth( + parentCtx context.Context) (context.Context, time.Duration, + walletrpc.WalletKitClient) { + + return m.walletKitMac.WithMacaroonAuth(parentCtx), m.timeout, m.client +} + // ListUnspent returns a list of all utxos spendable by the wallet with a number // of confirmations between the specified minimum and maximum. func (m *walletKitClient) ListUnspent(ctx context.Context, minConfs,