From 6346773b7a623ad4ce820344d3f66a266337d7af Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Mon, 9 Dec 2024 21:38:39 +0100 Subject: [PATCH 01/39] lnd+lncfg: Refactor `RemoteSigner` Config Upcoming commits will introduce and enable a new alternative remote signer implementation. This new implementation establishes the connection between the remote signer and the watch-only node in the reverse direction compared to the existing implementation. In this setup, the remote signer initiates an outbound connection to the watch-only node, whereas the previous version allowed an inbound connection from the watch-only node. As a result, this type is referred to as an "outbound remote signer." This commit refactors the `RemoteSigner` configuration struct in preparation for the new implementation. The new implementation will require configuration changes on both the watch-only node side and the remote signer side. This refactor simplifies the process of integrating that functionality. --- config.go | 6 +-- lncfg/remotesigner.go | 87 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/config.go b/config.go index d5f4a90d897..b4d252381d7 100644 --- a/config.go +++ b/config.go @@ -757,10 +757,8 @@ func DefaultConfig() Config { ChannelCommitBatchSize: defaultChannelCommitBatchSize, CoinSelectionStrategy: defaultCoinSelectionStrategy, KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts, - RemoteSigner: &lncfg.RemoteSigner{ - Timeout: lncfg.DefaultRemoteSignerRPCTimeout, - }, - Sweeper: lncfg.DefaultSweeperConfig(), + RemoteSigner: lncfg.DefaultRemoteSignerCfg(), + Sweeper: lncfg.DefaultSweeperConfig(), Htlcswitch: &lncfg.Htlcswitch{ MailboxDeliveryTimeout: htlcswitch.DefaultMailboxDeliveryTimeout, QuiescenceTimeout: lncfg.DefaultQuiescenceTimeout, diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 02479a85e8e..8537653fe34 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -11,34 +11,91 @@ const ( DefaultRemoteSignerRPCTimeout = 5 * time.Second ) -// RemoteSigner holds the configuration options for a remote RPC signer. +// RemoteSigner holds the configuration options for how to connect to a remote +// signer. Only a watch-only node specifies this config. // //nolint:ll type RemoteSigner struct { - Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` - RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"` - MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"` - TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"` - Timeout time.Duration `long:"timeout" description:"The timeout for connecting to and signing requests with the remote signer. Valid time units are {s, m, h}."` - MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"` + // Enable signals if this node is a watch-only node in a remote signer + // setup. + Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` + + // MigrateWatchOnly migrates the wallet to a watch-only wallet by + // purging all private keys from the wallet after first unlock with this + // flag. + MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"` + + // ConnectionCfg holds the connection configuration options that the + // watch-only node will use when setting up the connection to the remote + // signer. + ConnectionCfg +} + +// DefaultRemoteSignerCfg returns the default RemoteSigner config. +func DefaultRemoteSignerCfg() *RemoteSigner { + return &RemoteSigner{ + Enable: false, + ConnectionCfg: defaultConnectionCfg(), + } } // Validate checks the values configured for our remote RPC signer. func (r *RemoteSigner) Validate() error { + if r.MigrateWatchOnly && !r.Enable { + return fmt.Errorf("remote signer: cannot turn on wallet " + + "migration to watch-only if remote signing is not " + + "enabled") + } + if !r.Enable { return nil } - if r.Timeout < time.Millisecond { - return fmt.Errorf("remote signer: timeout of %v is invalid, "+ - "cannot be smaller than %v", r.Timeout, - time.Millisecond) + // Else, we are in outbound mode, so we verify the connection config. + err := r.ConnectionCfg.Validate() + if err != nil { + return fmt.Errorf("remotesigner.%w", err) } - if r.MigrateWatchOnly && !r.Enable { - return fmt.Errorf("remote signer: cannot turn on wallet " + - "migration to watch-only if remote signing is not " + - "enabled") + return nil +} + +// ConnectionCfg holds the configuration options required when setting up a +// connection to either a remote signer or watch-only node, depending on which +// side makes the outbound connection. +// +//nolint:ll +type ConnectionCfg struct { + RPCHost string `long:"rpchost" description:"The RPC host:port of the remote signer. For watch-only nodes, this should be set to the remote signer's RPC host:port."` + MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer. For watch-only nodes, this should be set to the remote signer's macaroon."` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity. For watch-only nodes, this should be set to the remote signer's TLS certificate."` + Timeout time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer. Valid time units are {s, m, h}."` +} + +// defaultConnectionCfg returns the default ConnectionCfg config. +func defaultConnectionCfg() ConnectionCfg { + return ConnectionCfg{ + Timeout: DefaultRemoteSignerRPCTimeout, + } +} + +// Validate checks the values set in the ConnectionCfg config are valid. +func (c *ConnectionCfg) Validate() error { + if c.Timeout < time.Millisecond { + return fmt.Errorf("timeout of %v is invalid, cannot be "+ + "smaller than %v", c.Timeout, time.Millisecond) + } + + if c.RPCHost == "" { + return fmt.Errorf("rpchost must be set") + } + + if c.MacaroonPath == "" { + return fmt.Errorf("macaroonpath must be set") + } + + if c.TLSCertPath == "" { + return fmt.Errorf("tlscertpath must be set") } return nil From c20f7eca4e1257ba0b9003735775f16c41b2f2bd Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Mon, 20 May 2024 20:52:31 +0200 Subject: [PATCH 02/39] multi: correct `DefaultRemoteSignerRPCTimeout` docs The documentation for the DefaultRemoteSignerRPCTimeout constant incorrectly specified that the value was also used as the timeout for requests to and from the remote signer. However, the value is only used as the timeout when setting up the connection to the remote signer. This commit corrects the documentation to reflect the actual usage of the constant. --- lncfg/remotesigner.go | 5 +++-- sample-lnd.conf | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 8537653fe34..945dfd297ff 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -6,8 +6,9 @@ import ( ) const ( - // DefaultRemoteSignerRPCTimeout is the default timeout that is used - // when forwarding a request to the remote signer through RPC. + // DefaultRemoteSignerRPCTimeout is the default connection timeout + // that is used when connecting to the remote signer or watch-only node + // through RPC. DefaultRemoteSignerRPCTimeout = 5 * time.Second ) diff --git a/sample-lnd.conf b/sample-lnd.conf index a487565afcd..3b7ce3d47b8 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1769,9 +1769,12 @@ ; Example: ; remotesigner.tlscertpath=/path/to/remote/signer/tls.cert -; The timeout for connecting to and signing requests with the remote signer. +; The timeout for connecting to the remote signer. ; Valid time units are {s, m, h}. -; remotesigner.timeout=5s +; Default: +; remotesigner.timeout=5s +; Example: +; remotesigner.timeout=2m ; If a wallet with private key material already exists, migrate it into a ; watch-only wallet on first startup. From b1c241db63dd6521b1b9da45140d118c056467cd Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 23 Aug 2024 15:37:22 +0200 Subject: [PATCH 03/39] lnd: add new `remotesigner` macaroon entity This commit introduces a new macaroon entity that grants the caller access to connect a remote signer to LND, provided the LND instance is configured to allow an inbound connection from an outbound remote signer. --- rpcserver.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rpcserver.go b/rpcserver.go index fe0e6702ba5..700f320eae5 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -186,6 +186,10 @@ var ( Entity: "macaroon", Action: "write", }, + { + Entity: "remotesigner", + Action: "generate", + }, } // invoicePermissions is a slice of all the entities that allows a user @@ -220,7 +224,7 @@ var ( // implemented. validActions = []string{"read", "write", "generate"} validEntities = []string{ - "onchain", "offchain", "address", "message", + "onchain", "offchain", "address", "message", "remotesigner", "peers", "info", "invoices", "signer", "macaroon", macaroons.PermissionEntityCustomURI, } From 51097d828e0a69bb03233b65398755565f264852 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 10:39:36 +0200 Subject: [PATCH 04/39] walletrpc: add `SignCoordinatorStreams` RPC To enable an outbound remote signer to connect to the watch-only lnd node, we add a SignCoordinatorStreams bi-directional streaming RPC endpoint. The stream created when the remote signer connects to this endpoint can be used to pass any requests to the remote signer and to receive the corresponding responses. We clearly define the types of requests and responses that can be sent over the stream, including all the requests that can be sent to the remote signer with the previous implementation. Those are the ones sent to the `signrpc.SignerClient` and `walletrpc.WalletKitClient` in the `lnwallet/rpcwallet.go` file. We also include messages for the required handshake between the remote signer and the watch-only node, and a message that the remote signer can send if it encounters an error while processing a request. --- lnrpc/walletrpc/walletkit.pb.go | 3296 ++++++++++++++++-------- lnrpc/walletrpc/walletkit.pb.gw.go | 76 + lnrpc/walletrpc/walletkit.proto | 211 ++ lnrpc/walletrpc/walletkit.swagger.json | 487 ++++ lnrpc/walletrpc/walletkit.yaml | 3 + lnrpc/walletrpc/walletkit_grpc.pb.go | 77 +- lnrpc/walletrpc/walletkit_server.go | 12 + 7 files changed, 3019 insertions(+), 1143 deletions(-) diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index d89f3861e4b..36604e43d7f 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -383,6 +383,726 @@ func (ChangeAddressType) EnumDescriptor() ([]byte, []int) { return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{2} } +type SignCoordinatorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A unique request ID of a SignCoordinator gRPC request. Useful for mapping + // requests to responses. + RequestId uint64 `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Messages between the watch-only node and the remote signer can only be of + // certain types. + // + // Types that are assignable to SignRequestType: + // + // *SignCoordinatorRequest_RegistrationResponse + // *SignCoordinatorRequest_Ping + // *SignCoordinatorRequest_SharedKeyRequest + // *SignCoordinatorRequest_SignMessageReq + // *SignCoordinatorRequest_MuSig2SessionRequest + // *SignCoordinatorRequest_MuSig2RegisterNoncesRequest + // *SignCoordinatorRequest_MuSig2SignRequest + // *SignCoordinatorRequest_MuSig2CombineSigRequest + // *SignCoordinatorRequest_MuSig2CleanupRequest + // *SignCoordinatorRequest_SignPsbtRequest + SignRequestType isSignCoordinatorRequest_SignRequestType `protobuf_oneof:"sign_request_type"` +} + +func (x *SignCoordinatorRequest) Reset() { + *x = SignCoordinatorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignCoordinatorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignCoordinatorRequest) ProtoMessage() {} + +func (x *SignCoordinatorRequest) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignCoordinatorRequest.ProtoReflect.Descriptor instead. +func (*SignCoordinatorRequest) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{0} +} + +func (x *SignCoordinatorRequest) GetRequestId() uint64 { + if x != nil { + return x.RequestId + } + return 0 +} + +func (m *SignCoordinatorRequest) GetSignRequestType() isSignCoordinatorRequest_SignRequestType { + if m != nil { + return m.SignRequestType + } + return nil +} + +func (x *SignCoordinatorRequest) GetRegistrationResponse() *RegistrationResponse { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_RegistrationResponse); ok { + return x.RegistrationResponse + } + return nil +} + +func (x *SignCoordinatorRequest) GetPing() bool { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_Ping); ok { + return x.Ping + } + return false +} + +func (x *SignCoordinatorRequest) GetSharedKeyRequest() *signrpc.SharedKeyRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_SharedKeyRequest); ok { + return x.SharedKeyRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetSignMessageReq() *signrpc.SignMessageReq { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_SignMessageReq); ok { + return x.SignMessageReq + } + return nil +} + +func (x *SignCoordinatorRequest) GetMuSig2SessionRequest() *signrpc.MuSig2SessionRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_MuSig2SessionRequest); ok { + return x.MuSig2SessionRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetMuSig2RegisterNoncesRequest() *signrpc.MuSig2RegisterNoncesRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_MuSig2RegisterNoncesRequest); ok { + return x.MuSig2RegisterNoncesRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetMuSig2SignRequest() *signrpc.MuSig2SignRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_MuSig2SignRequest); ok { + return x.MuSig2SignRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetMuSig2CombineSigRequest() *signrpc.MuSig2CombineSigRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_MuSig2CombineSigRequest); ok { + return x.MuSig2CombineSigRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetMuSig2CleanupRequest() *signrpc.MuSig2CleanupRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_MuSig2CleanupRequest); ok { + return x.MuSig2CleanupRequest + } + return nil +} + +func (x *SignCoordinatorRequest) GetSignPsbtRequest() *SignPsbtRequest { + if x, ok := x.GetSignRequestType().(*SignCoordinatorRequest_SignPsbtRequest); ok { + return x.SignPsbtRequest + } + return nil +} + +type isSignCoordinatorRequest_SignRequestType interface { + isSignCoordinatorRequest_SignRequestType() +} + +type SignCoordinatorRequest_RegistrationResponse struct { + // The Registration Response message is returned by the watch-only lnd as + // a response to SignerRegistration message. + RegistrationResponse *RegistrationResponse `protobuf:"bytes,2,opt,name=registration_response,json=registrationResponse,proto3,oneof"` +} + +type SignCoordinatorRequest_Ping struct { + // To ensure that the remote signer is still active and alive, the + // watch-only lnd can send a Ping message to the remote signer, which + // should then respond with the respective Pong message. + Ping bool `protobuf:"varint,3,opt,name=ping,proto3,oneof"` +} + +type SignCoordinatorRequest_SharedKeyRequest struct { + // Requests a shared public key from the remote signer. + SharedKeyRequest *signrpc.SharedKeyRequest `protobuf:"bytes,4,opt,name=shared_key_request,json=sharedKeyRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_SignMessageReq struct { + // Requests that the remote signer signs the passed message. + SignMessageReq *signrpc.SignMessageReq `protobuf:"bytes,5,opt,name=sign_message_req,json=signMessageReq,proto3,oneof"` +} + +type SignCoordinatorRequest_MuSig2SessionRequest struct { + // Requests a MuSig2 Session of the remote signer. + MuSig2SessionRequest *signrpc.MuSig2SessionRequest `protobuf:"bytes,6,opt,name=mu_sig2_session_request,json=muSig2SessionRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_MuSig2RegisterNoncesRequest struct { + // Requests that the remote signer registers a nonce with the referenced + // MuSig2 Session. + MuSig2RegisterNoncesRequest *signrpc.MuSig2RegisterNoncesRequest `protobuf:"bytes,7,opt,name=mu_sig2_register_nonces_request,json=muSig2RegisterNoncesRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_MuSig2SignRequest struct { + // Requests that the remote signer signs the passed message digest with + // the referenced MuSig2 Session. + MuSig2SignRequest *signrpc.MuSig2SignRequest `protobuf:"bytes,8,opt,name=mu_sig2_sign_request,json=muSig2SignRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_MuSig2CombineSigRequest struct { + // Requests that the remote signer combines and adds the passed partial + // signatures for the referenced MuSig2 Session. + MuSig2CombineSigRequest *signrpc.MuSig2CombineSigRequest `protobuf:"bytes,9,opt,name=mu_sig2_combine_sig_request,json=muSig2CombineSigRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_MuSig2CleanupRequest struct { + // Requests that the remote signer removes/cleans up the referenced + // MuSig2 session. + MuSig2CleanupRequest *signrpc.MuSig2CleanupRequest `protobuf:"bytes,10,opt,name=mu_sig2_cleanup_request,json=muSig2CleanupRequest,proto3,oneof"` +} + +type SignCoordinatorRequest_SignPsbtRequest struct { + // Requests that the remote signer signs the passed PSBT. + SignPsbtRequest *SignPsbtRequest `protobuf:"bytes,11,opt,name=sign_psbt_request,json=signPsbtRequest,proto3,oneof"` +} + +func (*SignCoordinatorRequest_RegistrationResponse) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_Ping) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_SharedKeyRequest) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_SignMessageReq) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_MuSig2SessionRequest) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_MuSig2RegisterNoncesRequest) isSignCoordinatorRequest_SignRequestType() { +} + +func (*SignCoordinatorRequest_MuSig2SignRequest) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_MuSig2CombineSigRequest) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_MuSig2CleanupRequest) isSignCoordinatorRequest_SignRequestType() {} + +func (*SignCoordinatorRequest_SignPsbtRequest) isSignCoordinatorRequest_SignRequestType() {} + +type SignCoordinatorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request ID this response refers to. + RefRequestId uint64 `protobuf:"varint,1,opt,name=ref_request_id,json=refRequestId,proto3" json:"ref_request_id,omitempty"` + // The remote signer responses can only be of certain types. + // + // Types that are assignable to SignResponseType: + // + // *SignCoordinatorResponse_SignerRegistration + // *SignCoordinatorResponse_Pong + // *SignCoordinatorResponse_SharedKeyResponse + // *SignCoordinatorResponse_SignMessageResp + // *SignCoordinatorResponse_MuSig2SessionResponse + // *SignCoordinatorResponse_MuSig2RegisterNoncesResponse + // *SignCoordinatorResponse_MuSig2SignResponse + // *SignCoordinatorResponse_MuSig2CombineSigResponse + // *SignCoordinatorResponse_MuSig2CleanupResponse + // *SignCoordinatorResponse_SignPsbtResponse + // *SignCoordinatorResponse_SignerError + SignResponseType isSignCoordinatorResponse_SignResponseType `protobuf_oneof:"sign_response_type"` +} + +func (x *SignCoordinatorResponse) Reset() { + *x = SignCoordinatorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignCoordinatorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignCoordinatorResponse) ProtoMessage() {} + +func (x *SignCoordinatorResponse) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignCoordinatorResponse.ProtoReflect.Descriptor instead. +func (*SignCoordinatorResponse) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{1} +} + +func (x *SignCoordinatorResponse) GetRefRequestId() uint64 { + if x != nil { + return x.RefRequestId + } + return 0 +} + +func (m *SignCoordinatorResponse) GetSignResponseType() isSignCoordinatorResponse_SignResponseType { + if m != nil { + return m.SignResponseType + } + return nil +} + +func (x *SignCoordinatorResponse) GetSignerRegistration() *SignerRegistration { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_SignerRegistration); ok { + return x.SignerRegistration + } + return nil +} + +func (x *SignCoordinatorResponse) GetPong() bool { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_Pong); ok { + return x.Pong + } + return false +} + +func (x *SignCoordinatorResponse) GetSharedKeyResponse() *signrpc.SharedKeyResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_SharedKeyResponse); ok { + return x.SharedKeyResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetSignMessageResp() *signrpc.SignMessageResp { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_SignMessageResp); ok { + return x.SignMessageResp + } + return nil +} + +func (x *SignCoordinatorResponse) GetMuSig2SessionResponse() *signrpc.MuSig2SessionResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_MuSig2SessionResponse); ok { + return x.MuSig2SessionResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetMuSig2RegisterNoncesResponse() *signrpc.MuSig2RegisterNoncesResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_MuSig2RegisterNoncesResponse); ok { + return x.MuSig2RegisterNoncesResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetMuSig2SignResponse() *signrpc.MuSig2SignResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_MuSig2SignResponse); ok { + return x.MuSig2SignResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetMuSig2CombineSigResponse() *signrpc.MuSig2CombineSigResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_MuSig2CombineSigResponse); ok { + return x.MuSig2CombineSigResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetMuSig2CleanupResponse() *signrpc.MuSig2CleanupResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_MuSig2CleanupResponse); ok { + return x.MuSig2CleanupResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetSignPsbtResponse() *SignPsbtResponse { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_SignPsbtResponse); ok { + return x.SignPsbtResponse + } + return nil +} + +func (x *SignCoordinatorResponse) GetSignerError() *SignerError { + if x, ok := x.GetSignResponseType().(*SignCoordinatorResponse_SignerError); ok { + return x.SignerError + } + return nil +} + +type isSignCoordinatorResponse_SignResponseType interface { + isSignCoordinatorResponse_SignResponseType() +} + +type SignCoordinatorResponse_SignerRegistration struct { + // The Signer Registration message is sent by the remote signer when it + // connects to the watch-only lnd node, to initialize a handshake between + // the nodes. + SignerRegistration *SignerRegistration `protobuf:"bytes,2,opt,name=signer_registration,json=signerRegistration,proto3,oneof"` +} + +type SignCoordinatorResponse_Pong struct { + // To ensure that the remote signer is still active and alive, the + // watch-only node can send a Ping message to remote signer. This Pong + // message should then be sent by the remote signer to respond to the Ping + // message. + Pong bool `protobuf:"varint,3,opt,name=pong,proto3,oneof"` +} + +type SignCoordinatorResponse_SharedKeyResponse struct { + // The remote signer's corresponding response to a Shared Key request. + SharedKeyResponse *signrpc.SharedKeyResponse `protobuf:"bytes,4,opt,name=shared_key_response,json=sharedKeyResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_SignMessageResp struct { + // The remote signer's corresponding response to a Sign Message request. + SignMessageResp *signrpc.SignMessageResp `protobuf:"bytes,5,opt,name=sign_message_resp,json=signMessageResp,proto3,oneof"` +} + +type SignCoordinatorResponse_MuSig2SessionResponse struct { + // The remote signer's corresponding response to a Mu Sig2 Session + // request. + MuSig2SessionResponse *signrpc.MuSig2SessionResponse `protobuf:"bytes,6,opt,name=mu_sig2_session_response,json=muSig2SessionResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_MuSig2RegisterNoncesResponse struct { + // The remote signer's corresponding response to a Mu Sig2 Register Nonces + // request. + MuSig2RegisterNoncesResponse *signrpc.MuSig2RegisterNoncesResponse `protobuf:"bytes,7,opt,name=mu_sig2_register_nonces_response,json=muSig2RegisterNoncesResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_MuSig2SignResponse struct { + // The remote signer's corresponding response to a Mu Sig2 Sign request. + MuSig2SignResponse *signrpc.MuSig2SignResponse `protobuf:"bytes,8,opt,name=mu_sig2_sign_response,json=muSig2SignResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_MuSig2CombineSigResponse struct { + // The remote signer's corresponding response to a Mu Sig2 Combine Sig + // request. + MuSig2CombineSigResponse *signrpc.MuSig2CombineSigResponse `protobuf:"bytes,9,opt,name=mu_sig2_combine_sig_response,json=muSig2CombineSigResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_MuSig2CleanupResponse struct { + // The remote signer's corresponding response to a Mu Sig2 Cleanup + // request. + MuSig2CleanupResponse *signrpc.MuSig2CleanupResponse `protobuf:"bytes,10,opt,name=mu_sig2_cleanup_response,json=muSig2CleanupResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_SignPsbtResponse struct { + // The remote signer's corresponding response to a Sign Psbt request. + SignPsbtResponse *SignPsbtResponse `protobuf:"bytes,11,opt,name=sign_psbt_response,json=signPsbtResponse,proto3,oneof"` +} + +type SignCoordinatorResponse_SignerError struct { + // If the remote signer encounters an error while processing a request, it + // will respond with a SignerError message that details the error. + SignerError *SignerError `protobuf:"bytes,12,opt,name=signer_error,json=signerError,proto3,oneof"` +} + +func (*SignCoordinatorResponse_SignerRegistration) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_Pong) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_SharedKeyResponse) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_SignMessageResp) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_MuSig2SessionResponse) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_MuSig2RegisterNoncesResponse) isSignCoordinatorResponse_SignResponseType() { +} + +func (*SignCoordinatorResponse_MuSig2SignResponse) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_MuSig2CombineSigResponse) isSignCoordinatorResponse_SignResponseType() { +} + +func (*SignCoordinatorResponse_MuSig2CleanupResponse) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_SignPsbtResponse) isSignCoordinatorResponse_SignResponseType() {} + +func (*SignCoordinatorResponse_SignerError) isSignCoordinatorResponse_SignResponseType() {} + +type SignerError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Details an error which occurred on remote signer. + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *SignerError) Reset() { + *x = SignerError{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignerError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignerError) ProtoMessage() {} + +func (x *SignerError) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignerError.ProtoReflect.Descriptor instead. +func (*SignerError) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{2} +} + +func (x *SignerError) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type SignerRegistration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The registration challenge allows the remote signer to pass data that will + // be signed by the watch-only lnd. The resulting signature will be returned in + // the RegistrationResponse message. + RegistrationChallenge string `protobuf:"bytes,1,opt,name=registration_challenge,json=registrationChallenge,proto3" json:"registration_challenge,omitempty"` + // The registration info contains details about the remote signer that may be + // useful for the watch-only lnd. + RegistrationInfo string `protobuf:"bytes,2,opt,name=registration_info,json=registrationInfo,proto3" json:"registration_info,omitempty"` +} + +func (x *SignerRegistration) Reset() { + *x = SignerRegistration{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignerRegistration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignerRegistration) ProtoMessage() {} + +func (x *SignerRegistration) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignerRegistration.ProtoReflect.Descriptor instead. +func (*SignerRegistration) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{3} +} + +func (x *SignerRegistration) GetRegistrationChallenge() string { + if x != nil { + return x.RegistrationChallenge + } + return "" +} + +func (x *SignerRegistration) GetRegistrationInfo() string { + if x != nil { + return x.RegistrationInfo + } + return "" +} + +type RegistrationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The registration response indicates either a successful registration or an + // error. + // + // Types that are assignable to RegistrationResponseType: + // + // *RegistrationResponse_RegistrationComplete + // *RegistrationResponse_RegistrationError + RegistrationResponseType isRegistrationResponse_RegistrationResponseType `protobuf_oneof:"registration_response_type"` +} + +func (x *RegistrationResponse) Reset() { + *x = RegistrationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegistrationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegistrationResponse) ProtoMessage() {} + +func (x *RegistrationResponse) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegistrationResponse.ProtoReflect.Descriptor instead. +func (*RegistrationResponse) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{4} +} + +func (m *RegistrationResponse) GetRegistrationResponseType() isRegistrationResponse_RegistrationResponseType { + if m != nil { + return m.RegistrationResponseType + } + return nil +} + +func (x *RegistrationResponse) GetRegistrationComplete() *RegistrationComplete { + if x, ok := x.GetRegistrationResponseType().(*RegistrationResponse_RegistrationComplete); ok { + return x.RegistrationComplete + } + return nil +} + +func (x *RegistrationResponse) GetRegistrationError() string { + if x, ok := x.GetRegistrationResponseType().(*RegistrationResponse_RegistrationError); ok { + return x.RegistrationError + } + return "" +} + +type isRegistrationResponse_RegistrationResponseType interface { + isRegistrationResponse_RegistrationResponseType() +} + +type RegistrationResponse_RegistrationComplete struct { + // Sent by the watch-only lnd when the remote signer registration is + // successful. + RegistrationComplete *RegistrationComplete `protobuf:"bytes,1,opt,name=registration_complete,json=registrationComplete,proto3,oneof"` +} + +type RegistrationResponse_RegistrationError struct { + // Contains details about any errors that occurred during remote signer + // registration. + RegistrationError string `protobuf:"bytes,2,opt,name=registration_error,json=registrationError,proto3,oneof"` +} + +func (*RegistrationResponse_RegistrationComplete) isRegistrationResponse_RegistrationResponseType() {} + +func (*RegistrationResponse_RegistrationError) isRegistrationResponse_RegistrationResponseType() {} + +type RegistrationComplete struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Holds the signature generated by the watch-only node when signing the + // registration_challenge provided by the remote signer in SignerRegistration. + Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + // Contains information about the watch-only lnd that may be useful for the + // remote signer. + RegistrationInfo string `protobuf:"bytes,2,opt,name=registration_info,json=registrationInfo,proto3" json:"registration_info,omitempty"` +} + +func (x *RegistrationComplete) Reset() { + *x = RegistrationComplete{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegistrationComplete) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegistrationComplete) ProtoMessage() {} + +func (x *RegistrationComplete) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegistrationComplete.ProtoReflect.Descriptor instead. +func (*RegistrationComplete) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{5} +} + +func (x *RegistrationComplete) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +func (x *RegistrationComplete) GetRegistrationInfo() string { + if x != nil { + return x.RegistrationInfo + } + return "" +} + type ListUnspentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -404,7 +1124,7 @@ type ListUnspentRequest struct { func (x *ListUnspentRequest) Reset() { *x = ListUnspentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[0] + mi := &file_walletrpc_walletkit_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -417,7 +1137,7 @@ func (x *ListUnspentRequest) String() string { func (*ListUnspentRequest) ProtoMessage() {} func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[0] + mi := &file_walletrpc_walletkit_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -430,7 +1150,7 @@ func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentRequest.ProtoReflect.Descriptor instead. func (*ListUnspentRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{0} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{6} } func (x *ListUnspentRequest) GetMinConfs() int32 { @@ -473,7 +1193,7 @@ type ListUnspentResponse struct { func (x *ListUnspentResponse) Reset() { *x = ListUnspentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[1] + mi := &file_walletrpc_walletkit_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -486,7 +1206,7 @@ func (x *ListUnspentResponse) String() string { func (*ListUnspentResponse) ProtoMessage() {} func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[1] + mi := &file_walletrpc_walletkit_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -499,7 +1219,7 @@ func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentResponse.ProtoReflect.Descriptor instead. func (*ListUnspentResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{1} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{7} } func (x *ListUnspentResponse) GetUtxos() []*lnrpc.Utxo { @@ -527,7 +1247,7 @@ type LeaseOutputRequest struct { func (x *LeaseOutputRequest) Reset() { *x = LeaseOutputRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[2] + mi := &file_walletrpc_walletkit_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -540,7 +1260,7 @@ func (x *LeaseOutputRequest) String() string { func (*LeaseOutputRequest) ProtoMessage() {} func (x *LeaseOutputRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[2] + mi := &file_walletrpc_walletkit_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -553,7 +1273,7 @@ func (x *LeaseOutputRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaseOutputRequest.ProtoReflect.Descriptor instead. func (*LeaseOutputRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{2} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{8} } func (x *LeaseOutputRequest) GetId() []byte { @@ -589,7 +1309,7 @@ type LeaseOutputResponse struct { func (x *LeaseOutputResponse) Reset() { *x = LeaseOutputResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[3] + mi := &file_walletrpc_walletkit_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -602,7 +1322,7 @@ func (x *LeaseOutputResponse) String() string { func (*LeaseOutputResponse) ProtoMessage() {} func (x *LeaseOutputResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[3] + mi := &file_walletrpc_walletkit_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -615,7 +1335,7 @@ func (x *LeaseOutputResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaseOutputResponse.ProtoReflect.Descriptor instead. func (*LeaseOutputResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{3} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{9} } func (x *LeaseOutputResponse) GetExpiration() uint64 { @@ -639,7 +1359,7 @@ type ReleaseOutputRequest struct { func (x *ReleaseOutputRequest) Reset() { *x = ReleaseOutputRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[4] + mi := &file_walletrpc_walletkit_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -652,7 +1372,7 @@ func (x *ReleaseOutputRequest) String() string { func (*ReleaseOutputRequest) ProtoMessage() {} func (x *ReleaseOutputRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[4] + mi := &file_walletrpc_walletkit_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -665,7 +1385,7 @@ func (x *ReleaseOutputRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseOutputRequest.ProtoReflect.Descriptor instead. func (*ReleaseOutputRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{4} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{10} } func (x *ReleaseOutputRequest) GetId() []byte { @@ -694,7 +1414,7 @@ type ReleaseOutputResponse struct { func (x *ReleaseOutputResponse) Reset() { *x = ReleaseOutputResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[5] + mi := &file_walletrpc_walletkit_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -707,7 +1427,7 @@ func (x *ReleaseOutputResponse) String() string { func (*ReleaseOutputResponse) ProtoMessage() {} func (x *ReleaseOutputResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[5] + mi := &file_walletrpc_walletkit_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -720,7 +1440,7 @@ func (x *ReleaseOutputResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseOutputResponse.ProtoReflect.Descriptor instead. func (*ReleaseOutputResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{5} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{11} } func (x *ReleaseOutputResponse) GetStatus() string { @@ -747,7 +1467,7 @@ type KeyReq struct { func (x *KeyReq) Reset() { *x = KeyReq{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[6] + mi := &file_walletrpc_walletkit_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -760,7 +1480,7 @@ func (x *KeyReq) String() string { func (*KeyReq) ProtoMessage() {} func (x *KeyReq) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[6] + mi := &file_walletrpc_walletkit_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -773,7 +1493,7 @@ func (x *KeyReq) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyReq.ProtoReflect.Descriptor instead. func (*KeyReq) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{6} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{12} } func (x *KeyReq) GetKeyFingerPrint() int32 { @@ -807,7 +1527,7 @@ type AddrRequest struct { func (x *AddrRequest) Reset() { *x = AddrRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[7] + mi := &file_walletrpc_walletkit_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -820,7 +1540,7 @@ func (x *AddrRequest) String() string { func (*AddrRequest) ProtoMessage() {} func (x *AddrRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[7] + mi := &file_walletrpc_walletkit_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -833,7 +1553,7 @@ func (x *AddrRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddrRequest.ProtoReflect.Descriptor instead. func (*AddrRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{7} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{13} } func (x *AddrRequest) GetAccount() string { @@ -869,7 +1589,7 @@ type AddrResponse struct { func (x *AddrResponse) Reset() { *x = AddrResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[8] + mi := &file_walletrpc_walletkit_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -882,7 +1602,7 @@ func (x *AddrResponse) String() string { func (*AddrResponse) ProtoMessage() {} func (x *AddrResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[8] + mi := &file_walletrpc_walletkit_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -895,7 +1615,7 @@ func (x *AddrResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddrResponse.ProtoReflect.Descriptor instead. func (*AddrResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{8} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{14} } func (x *AddrResponse) GetAddr() string { @@ -942,7 +1662,7 @@ type Account struct { func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[9] + mi := &file_walletrpc_walletkit_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -955,7 +1675,7 @@ func (x *Account) String() string { func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[9] + mi := &file_walletrpc_walletkit_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -968,7 +1688,7 @@ func (x *Account) ProtoReflect() protoreflect.Message { // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{9} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{15} } func (x *Account) GetName() string { @@ -1055,7 +1775,7 @@ type AddressProperty struct { func (x *AddressProperty) Reset() { *x = AddressProperty{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[10] + mi := &file_walletrpc_walletkit_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1068,7 +1788,7 @@ func (x *AddressProperty) String() string { func (*AddressProperty) ProtoMessage() {} func (x *AddressProperty) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[10] + mi := &file_walletrpc_walletkit_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1081,7 +1801,7 @@ func (x *AddressProperty) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressProperty.ProtoReflect.Descriptor instead. func (*AddressProperty) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{10} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{16} } func (x *AddressProperty) GetAddress() string { @@ -1142,7 +1862,7 @@ type AccountWithAddresses struct { func (x *AccountWithAddresses) Reset() { *x = AccountWithAddresses{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[11] + mi := &file_walletrpc_walletkit_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1155,7 +1875,7 @@ func (x *AccountWithAddresses) String() string { func (*AccountWithAddresses) ProtoMessage() {} func (x *AccountWithAddresses) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[11] + mi := &file_walletrpc_walletkit_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1168,7 +1888,7 @@ func (x *AccountWithAddresses) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountWithAddresses.ProtoReflect.Descriptor instead. func (*AccountWithAddresses) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{11} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{17} } func (x *AccountWithAddresses) GetName() string { @@ -1213,7 +1933,7 @@ type ListAccountsRequest struct { func (x *ListAccountsRequest) Reset() { *x = ListAccountsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[12] + mi := &file_walletrpc_walletkit_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1226,7 +1946,7 @@ func (x *ListAccountsRequest) String() string { func (*ListAccountsRequest) ProtoMessage() {} func (x *ListAccountsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[12] + mi := &file_walletrpc_walletkit_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1239,7 +1959,7 @@ func (x *ListAccountsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAccountsRequest.ProtoReflect.Descriptor instead. func (*ListAccountsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{12} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{18} } func (x *ListAccountsRequest) GetName() string { @@ -1267,7 +1987,7 @@ type ListAccountsResponse struct { func (x *ListAccountsResponse) Reset() { *x = ListAccountsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[13] + mi := &file_walletrpc_walletkit_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1280,7 +2000,7 @@ func (x *ListAccountsResponse) String() string { func (*ListAccountsResponse) ProtoMessage() {} func (x *ListAccountsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[13] + mi := &file_walletrpc_walletkit_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1293,7 +2013,7 @@ func (x *ListAccountsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAccountsResponse.ProtoReflect.Descriptor instead. func (*ListAccountsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{13} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{19} } func (x *ListAccountsResponse) GetAccounts() []*Account { @@ -1315,7 +2035,7 @@ type RequiredReserveRequest struct { func (x *RequiredReserveRequest) Reset() { *x = RequiredReserveRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[14] + mi := &file_walletrpc_walletkit_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1328,7 +2048,7 @@ func (x *RequiredReserveRequest) String() string { func (*RequiredReserveRequest) ProtoMessage() {} func (x *RequiredReserveRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[14] + mi := &file_walletrpc_walletkit_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1341,7 +2061,7 @@ func (x *RequiredReserveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequiredReserveRequest.ProtoReflect.Descriptor instead. func (*RequiredReserveRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{14} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{20} } func (x *RequiredReserveRequest) GetAdditionalPublicChannels() uint32 { @@ -1363,7 +2083,7 @@ type RequiredReserveResponse struct { func (x *RequiredReserveResponse) Reset() { *x = RequiredReserveResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[15] + mi := &file_walletrpc_walletkit_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1376,7 +2096,7 @@ func (x *RequiredReserveResponse) String() string { func (*RequiredReserveResponse) ProtoMessage() {} func (x *RequiredReserveResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[15] + mi := &file_walletrpc_walletkit_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1389,7 +2109,7 @@ func (x *RequiredReserveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequiredReserveResponse.ProtoReflect.Descriptor instead. func (*RequiredReserveResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{15} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{21} } func (x *RequiredReserveResponse) GetRequiredReserve() int64 { @@ -1414,7 +2134,7 @@ type ListAddressesRequest struct { func (x *ListAddressesRequest) Reset() { *x = ListAddressesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[16] + mi := &file_walletrpc_walletkit_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1427,7 +2147,7 @@ func (x *ListAddressesRequest) String() string { func (*ListAddressesRequest) ProtoMessage() {} func (x *ListAddressesRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[16] + mi := &file_walletrpc_walletkit_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1440,7 +2160,7 @@ func (x *ListAddressesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAddressesRequest.ProtoReflect.Descriptor instead. func (*ListAddressesRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{16} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{22} } func (x *ListAddressesRequest) GetAccountName() string { @@ -1469,7 +2189,7 @@ type ListAddressesResponse struct { func (x *ListAddressesResponse) Reset() { *x = ListAddressesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[17] + mi := &file_walletrpc_walletkit_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1482,7 +2202,7 @@ func (x *ListAddressesResponse) String() string { func (*ListAddressesResponse) ProtoMessage() {} func (x *ListAddressesResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[17] + mi := &file_walletrpc_walletkit_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1495,7 +2215,7 @@ func (x *ListAddressesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAddressesResponse.ProtoReflect.Descriptor instead. func (*ListAddressesResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{17} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{23} } func (x *ListAddressesResponse) GetAccountWithAddresses() []*AccountWithAddresses { @@ -1517,7 +2237,7 @@ type GetTransactionRequest struct { func (x *GetTransactionRequest) Reset() { *x = GetTransactionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[18] + mi := &file_walletrpc_walletkit_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1530,7 +2250,7 @@ func (x *GetTransactionRequest) String() string { func (*GetTransactionRequest) ProtoMessage() {} func (x *GetTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[18] + mi := &file_walletrpc_walletkit_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1543,7 +2263,7 @@ func (x *GetTransactionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTransactionRequest.ProtoReflect.Descriptor instead. func (*GetTransactionRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{18} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{24} } func (x *GetTransactionRequest) GetTxid() string { @@ -1569,7 +2289,7 @@ type SignMessageWithAddrRequest struct { func (x *SignMessageWithAddrRequest) Reset() { *x = SignMessageWithAddrRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[19] + mi := &file_walletrpc_walletkit_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1582,7 +2302,7 @@ func (x *SignMessageWithAddrRequest) String() string { func (*SignMessageWithAddrRequest) ProtoMessage() {} func (x *SignMessageWithAddrRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[19] + mi := &file_walletrpc_walletkit_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1595,7 +2315,7 @@ func (x *SignMessageWithAddrRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageWithAddrRequest.ProtoReflect.Descriptor instead. func (*SignMessageWithAddrRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{19} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{25} } func (x *SignMessageWithAddrRequest) GetMsg() []byte { @@ -1624,7 +2344,7 @@ type SignMessageWithAddrResponse struct { func (x *SignMessageWithAddrResponse) Reset() { *x = SignMessageWithAddrResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[20] + mi := &file_walletrpc_walletkit_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1637,7 +2357,7 @@ func (x *SignMessageWithAddrResponse) String() string { func (*SignMessageWithAddrResponse) ProtoMessage() {} func (x *SignMessageWithAddrResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[20] + mi := &file_walletrpc_walletkit_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1650,7 +2370,7 @@ func (x *SignMessageWithAddrResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageWithAddrResponse.ProtoReflect.Descriptor instead. func (*SignMessageWithAddrResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{20} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{26} } func (x *SignMessageWithAddrResponse) GetSignature() string { @@ -1679,7 +2399,7 @@ type VerifyMessageWithAddrRequest struct { func (x *VerifyMessageWithAddrRequest) Reset() { *x = VerifyMessageWithAddrRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[21] + mi := &file_walletrpc_walletkit_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1692,7 +2412,7 @@ func (x *VerifyMessageWithAddrRequest) String() string { func (*VerifyMessageWithAddrRequest) ProtoMessage() {} func (x *VerifyMessageWithAddrRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[21] + mi := &file_walletrpc_walletkit_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1705,7 +2425,7 @@ func (x *VerifyMessageWithAddrRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageWithAddrRequest.ProtoReflect.Descriptor instead. func (*VerifyMessageWithAddrRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{21} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{27} } func (x *VerifyMessageWithAddrRequest) GetMsg() []byte { @@ -1743,7 +2463,7 @@ type VerifyMessageWithAddrResponse struct { func (x *VerifyMessageWithAddrResponse) Reset() { *x = VerifyMessageWithAddrResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[22] + mi := &file_walletrpc_walletkit_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1756,7 +2476,7 @@ func (x *VerifyMessageWithAddrResponse) String() string { func (*VerifyMessageWithAddrResponse) ProtoMessage() {} func (x *VerifyMessageWithAddrResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[22] + mi := &file_walletrpc_walletkit_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1769,7 +2489,7 @@ func (x *VerifyMessageWithAddrResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageWithAddrResponse.ProtoReflect.Descriptor instead. func (*VerifyMessageWithAddrResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{22} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{28} } func (x *VerifyMessageWithAddrResponse) GetValid() bool { @@ -1817,7 +2537,7 @@ type ImportAccountRequest struct { func (x *ImportAccountRequest) Reset() { *x = ImportAccountRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[23] + mi := &file_walletrpc_walletkit_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1830,7 +2550,7 @@ func (x *ImportAccountRequest) String() string { func (*ImportAccountRequest) ProtoMessage() {} func (x *ImportAccountRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[23] + mi := &file_walletrpc_walletkit_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1843,7 +2563,7 @@ func (x *ImportAccountRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportAccountRequest.ProtoReflect.Descriptor instead. func (*ImportAccountRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{23} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{29} } func (x *ImportAccountRequest) GetName() string { @@ -1901,7 +2621,7 @@ type ImportAccountResponse struct { func (x *ImportAccountResponse) Reset() { *x = ImportAccountResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[24] + mi := &file_walletrpc_walletkit_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1914,7 +2634,7 @@ func (x *ImportAccountResponse) String() string { func (*ImportAccountResponse) ProtoMessage() {} func (x *ImportAccountResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[24] + mi := &file_walletrpc_walletkit_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1927,7 +2647,7 @@ func (x *ImportAccountResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportAccountResponse.ProtoReflect.Descriptor instead. func (*ImportAccountResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{24} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{30} } func (x *ImportAccountResponse) GetAccount() *Account { @@ -1965,7 +2685,7 @@ type ImportPublicKeyRequest struct { func (x *ImportPublicKeyRequest) Reset() { *x = ImportPublicKeyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[25] + mi := &file_walletrpc_walletkit_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1978,7 +2698,7 @@ func (x *ImportPublicKeyRequest) String() string { func (*ImportPublicKeyRequest) ProtoMessage() {} func (x *ImportPublicKeyRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[25] + mi := &file_walletrpc_walletkit_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1991,7 +2711,7 @@ func (x *ImportPublicKeyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportPublicKeyRequest.ProtoReflect.Descriptor instead. func (*ImportPublicKeyRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{25} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{31} } func (x *ImportPublicKeyRequest) GetPublicKey() []byte { @@ -2020,7 +2740,7 @@ type ImportPublicKeyResponse struct { func (x *ImportPublicKeyResponse) Reset() { *x = ImportPublicKeyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[26] + mi := &file_walletrpc_walletkit_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2033,7 +2753,7 @@ func (x *ImportPublicKeyResponse) String() string { func (*ImportPublicKeyResponse) ProtoMessage() {} func (x *ImportPublicKeyResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[26] + mi := &file_walletrpc_walletkit_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2046,7 +2766,7 @@ func (x *ImportPublicKeyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportPublicKeyResponse.ProtoReflect.Descriptor instead. func (*ImportPublicKeyResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{26} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{32} } func (x *ImportPublicKeyResponse) GetStatus() string { @@ -2075,7 +2795,7 @@ type ImportTapscriptRequest struct { func (x *ImportTapscriptRequest) Reset() { *x = ImportTapscriptRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[27] + mi := &file_walletrpc_walletkit_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2088,7 +2808,7 @@ func (x *ImportTapscriptRequest) String() string { func (*ImportTapscriptRequest) ProtoMessage() {} func (x *ImportTapscriptRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[27] + mi := &file_walletrpc_walletkit_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2101,7 +2821,7 @@ func (x *ImportTapscriptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportTapscriptRequest.ProtoReflect.Descriptor instead. func (*ImportTapscriptRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{27} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{33} } func (x *ImportTapscriptRequest) GetInternalPublicKey() []byte { @@ -2198,7 +2918,7 @@ type TapscriptFullTree struct { func (x *TapscriptFullTree) Reset() { *x = TapscriptFullTree{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[28] + mi := &file_walletrpc_walletkit_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2211,7 +2931,7 @@ func (x *TapscriptFullTree) String() string { func (*TapscriptFullTree) ProtoMessage() {} func (x *TapscriptFullTree) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[28] + mi := &file_walletrpc_walletkit_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2224,7 +2944,7 @@ func (x *TapscriptFullTree) ProtoReflect() protoreflect.Message { // Deprecated: Use TapscriptFullTree.ProtoReflect.Descriptor instead. func (*TapscriptFullTree) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{28} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} } func (x *TapscriptFullTree) GetAllLeaves() []*TapLeaf { @@ -2248,7 +2968,7 @@ type TapLeaf struct { func (x *TapLeaf) Reset() { *x = TapLeaf{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[29] + mi := &file_walletrpc_walletkit_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2261,7 +2981,7 @@ func (x *TapLeaf) String() string { func (*TapLeaf) ProtoMessage() {} func (x *TapLeaf) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[29] + mi := &file_walletrpc_walletkit_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2274,7 +2994,7 @@ func (x *TapLeaf) ProtoReflect() protoreflect.Message { // Deprecated: Use TapLeaf.ProtoReflect.Descriptor instead. func (*TapLeaf) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{29} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} } func (x *TapLeaf) GetLeafVersion() uint32 { @@ -2308,7 +3028,7 @@ type TapscriptPartialReveal struct { func (x *TapscriptPartialReveal) Reset() { *x = TapscriptPartialReveal{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[30] + mi := &file_walletrpc_walletkit_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2321,7 +3041,7 @@ func (x *TapscriptPartialReveal) String() string { func (*TapscriptPartialReveal) ProtoMessage() {} func (x *TapscriptPartialReveal) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[30] + mi := &file_walletrpc_walletkit_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2334,7 +3054,7 @@ func (x *TapscriptPartialReveal) ProtoReflect() protoreflect.Message { // Deprecated: Use TapscriptPartialReveal.ProtoReflect.Descriptor instead. func (*TapscriptPartialReveal) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{30} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} } func (x *TapscriptPartialReveal) GetRevealedLeaf() *TapLeaf { @@ -2364,7 +3084,7 @@ type ImportTapscriptResponse struct { func (x *ImportTapscriptResponse) Reset() { *x = ImportTapscriptResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[31] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2377,7 +3097,7 @@ func (x *ImportTapscriptResponse) String() string { func (*ImportTapscriptResponse) ProtoMessage() {} func (x *ImportTapscriptResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[31] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2390,7 +3110,7 @@ func (x *ImportTapscriptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportTapscriptResponse.ProtoReflect.Descriptor instead. func (*ImportTapscriptResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{31} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} } func (x *ImportTapscriptResponse) GetP2TrAddress() string { @@ -2416,7 +3136,7 @@ type Transaction struct { func (x *Transaction) Reset() { *x = Transaction{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[32] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2429,7 +3149,7 @@ func (x *Transaction) String() string { func (*Transaction) ProtoMessage() {} func (x *Transaction) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[32] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2442,7 +3162,7 @@ func (x *Transaction) ProtoReflect() protoreflect.Message { // Deprecated: Use Transaction.ProtoReflect.Descriptor instead. func (*Transaction) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{32} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} } func (x *Transaction) GetTxHex() []byte { @@ -2475,7 +3195,7 @@ type PublishResponse struct { func (x *PublishResponse) Reset() { *x = PublishResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[33] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2488,7 +3208,7 @@ func (x *PublishResponse) String() string { func (*PublishResponse) ProtoMessage() {} func (x *PublishResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[33] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2501,7 +3221,7 @@ func (x *PublishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PublishResponse.ProtoReflect.Descriptor instead. func (*PublishResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{33} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} } func (x *PublishResponse) GetPublishError() string { @@ -2523,7 +3243,7 @@ type RemoveTransactionResponse struct { func (x *RemoveTransactionResponse) Reset() { *x = RemoveTransactionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[34] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2536,7 +3256,7 @@ func (x *RemoveTransactionResponse) String() string { func (*RemoveTransactionResponse) ProtoMessage() {} func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[34] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2549,7 +3269,7 @@ func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveTransactionResponse.ProtoReflect.Descriptor instead. func (*RemoveTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} } func (x *RemoveTransactionResponse) GetStatus() string { @@ -2583,7 +3303,7 @@ type SendOutputsRequest struct { func (x *SendOutputsRequest) Reset() { *x = SendOutputsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2596,7 +3316,7 @@ func (x *SendOutputsRequest) String() string { func (*SendOutputsRequest) ProtoMessage() {} func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2609,7 +3329,7 @@ func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsRequest.ProtoReflect.Descriptor instead. func (*SendOutputsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} } func (x *SendOutputsRequest) GetSatPerKw() int64 { @@ -2666,7 +3386,7 @@ type SendOutputsResponse struct { func (x *SendOutputsResponse) Reset() { *x = SendOutputsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2679,7 +3399,7 @@ func (x *SendOutputsResponse) String() string { func (*SendOutputsResponse) ProtoMessage() {} func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2692,7 +3412,7 @@ func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsResponse.ProtoReflect.Descriptor instead. func (*SendOutputsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} } func (x *SendOutputsResponse) GetRawTx() []byte { @@ -2714,7 +3434,7 @@ type EstimateFeeRequest struct { func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2727,7 +3447,7 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2740,7 +3460,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} } func (x *EstimateFeeRequest) GetConfTarget() int32 { @@ -2765,7 +3485,7 @@ type EstimateFeeResponse struct { func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2778,7 +3498,7 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2791,7 +3511,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} } func (x *EstimateFeeResponse) GetSatPerKw() int64 { @@ -2873,7 +3593,7 @@ type PendingSweep struct { func (x *PendingSweep) Reset() { *x = PendingSweep{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2886,7 +3606,7 @@ func (x *PendingSweep) String() string { func (*PendingSweep) ProtoMessage() {} func (x *PendingSweep) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2899,7 +3619,7 @@ func (x *PendingSweep) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweep.ProtoReflect.Descriptor instead. func (*PendingSweep) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} } func (x *PendingSweep) GetOutpoint() *lnrpc.OutPoint { @@ -3021,7 +3741,7 @@ type PendingSweepsRequest struct { func (x *PendingSweepsRequest) Reset() { *x = PendingSweepsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[40] + mi := &file_walletrpc_walletkit_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3034,7 +3754,7 @@ func (x *PendingSweepsRequest) String() string { func (*PendingSweepsRequest) ProtoMessage() {} func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[40] + mi := &file_walletrpc_walletkit_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3047,7 +3767,7 @@ func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsRequest.ProtoReflect.Descriptor instead. func (*PendingSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} } type PendingSweepsResponse struct { @@ -3062,7 +3782,7 @@ type PendingSweepsResponse struct { func (x *PendingSweepsResponse) Reset() { *x = PendingSweepsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[41] + mi := &file_walletrpc_walletkit_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3075,7 +3795,7 @@ func (x *PendingSweepsResponse) String() string { func (*PendingSweepsResponse) ProtoMessage() {} func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[41] + mi := &file_walletrpc_walletkit_proto_msgTypes[47] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3088,7 +3808,7 @@ func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsResponse.ProtoReflect.Descriptor instead. func (*PendingSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} } func (x *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { @@ -3145,7 +3865,7 @@ type BumpFeeRequest struct { func (x *BumpFeeRequest) Reset() { *x = BumpFeeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[42] + mi := &file_walletrpc_walletkit_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3158,7 +3878,7 @@ func (x *BumpFeeRequest) String() string { func (*BumpFeeRequest) ProtoMessage() {} func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[42] + mi := &file_walletrpc_walletkit_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3171,7 +3891,7 @@ func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeRequest.ProtoReflect.Descriptor instead. func (*BumpFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} } func (x *BumpFeeRequest) GetOutpoint() *lnrpc.OutPoint { @@ -3244,7 +3964,7 @@ type BumpFeeResponse struct { func (x *BumpFeeResponse) Reset() { *x = BumpFeeResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[43] + mi := &file_walletrpc_walletkit_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3257,7 +3977,7 @@ func (x *BumpFeeResponse) String() string { func (*BumpFeeResponse) ProtoMessage() {} func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[43] + mi := &file_walletrpc_walletkit_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3270,7 +3990,7 @@ func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeResponse.ProtoReflect.Descriptor instead. func (*BumpFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} } func (x *BumpFeeResponse) GetStatus() string { @@ -3316,7 +4036,7 @@ type BumpForceCloseFeeRequest struct { func (x *BumpForceCloseFeeRequest) Reset() { *x = BumpForceCloseFeeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[44] + mi := &file_walletrpc_walletkit_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3329,7 +4049,7 @@ func (x *BumpForceCloseFeeRequest) String() string { func (*BumpForceCloseFeeRequest) ProtoMessage() {} func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[44] + mi := &file_walletrpc_walletkit_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3342,7 +4062,7 @@ func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeRequest.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} } func (x *BumpForceCloseFeeRequest) GetChanPoint() *lnrpc.ChannelPoint { @@ -3399,7 +4119,7 @@ type BumpForceCloseFeeResponse struct { func (x *BumpForceCloseFeeResponse) Reset() { *x = BumpForceCloseFeeResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[45] + mi := &file_walletrpc_walletkit_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3412,7 +4132,7 @@ func (x *BumpForceCloseFeeResponse) String() string { func (*BumpForceCloseFeeResponse) ProtoMessage() {} func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[45] + mi := &file_walletrpc_walletkit_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3425,7 +4145,7 @@ func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeResponse.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} } func (x *BumpForceCloseFeeResponse) GetStatus() string { @@ -3453,7 +4173,7 @@ type ListSweepsRequest struct { func (x *ListSweepsRequest) Reset() { *x = ListSweepsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[46] + mi := &file_walletrpc_walletkit_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3466,7 +4186,7 @@ func (x *ListSweepsRequest) String() string { func (*ListSweepsRequest) ProtoMessage() {} func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[46] + mi := &file_walletrpc_walletkit_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3479,7 +4199,7 @@ func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsRequest.ProtoReflect.Descriptor instead. func (*ListSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} } func (x *ListSweepsRequest) GetVerbose() bool { @@ -3511,7 +4231,7 @@ type ListSweepsResponse struct { func (x *ListSweepsResponse) Reset() { *x = ListSweepsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[47] + mi := &file_walletrpc_walletkit_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3524,7 +4244,7 @@ func (x *ListSweepsResponse) String() string { func (*ListSweepsResponse) ProtoMessage() {} func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[47] + mi := &file_walletrpc_walletkit_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3537,7 +4257,7 @@ func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsResponse.ProtoReflect.Descriptor instead. func (*ListSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} } func (m *ListSweepsResponse) GetSweeps() isListSweepsResponse_Sweeps { @@ -3594,7 +4314,7 @@ type LabelTransactionRequest struct { func (x *LabelTransactionRequest) Reset() { *x = LabelTransactionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[48] + mi := &file_walletrpc_walletkit_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3607,7 +4327,7 @@ func (x *LabelTransactionRequest) String() string { func (*LabelTransactionRequest) ProtoMessage() {} func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[48] + mi := &file_walletrpc_walletkit_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3620,7 +4340,7 @@ func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionRequest.ProtoReflect.Descriptor instead. func (*LabelTransactionRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} } func (x *LabelTransactionRequest) GetTxid() []byte { @@ -3656,7 +4376,7 @@ type LabelTransactionResponse struct { func (x *LabelTransactionResponse) Reset() { *x = LabelTransactionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[49] + mi := &file_walletrpc_walletkit_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3669,7 +4389,7 @@ func (x *LabelTransactionResponse) String() string { func (*LabelTransactionResponse) ProtoMessage() {} func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[49] + mi := &file_walletrpc_walletkit_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3682,7 +4402,7 @@ func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionResponse.ProtoReflect.Descriptor instead. func (*LabelTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} } func (x *LabelTransactionResponse) GetStatus() string { @@ -3739,7 +4459,7 @@ type FundPsbtRequest struct { func (x *FundPsbtRequest) Reset() { *x = FundPsbtRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[50] + mi := &file_walletrpc_walletkit_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3752,7 +4472,7 @@ func (x *FundPsbtRequest) String() string { func (*FundPsbtRequest) ProtoMessage() {} func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[50] + mi := &file_walletrpc_walletkit_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3765,7 +4485,7 @@ func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtRequest.ProtoReflect.Descriptor instead. func (*FundPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} } func (m *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template { @@ -3972,7 +4692,7 @@ type FundPsbtResponse struct { func (x *FundPsbtResponse) Reset() { *x = FundPsbtResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[51] + mi := &file_walletrpc_walletkit_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3985,7 +4705,7 @@ func (x *FundPsbtResponse) String() string { func (*FundPsbtResponse) ProtoMessage() {} func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[51] + mi := &file_walletrpc_walletkit_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3998,7 +4718,7 @@ func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtResponse.ProtoReflect.Descriptor instead. func (*FundPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} } func (x *FundPsbtResponse) GetFundedPsbt() []byte { @@ -4042,7 +4762,7 @@ type TxTemplate struct { func (x *TxTemplate) Reset() { *x = TxTemplate{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[52] + mi := &file_walletrpc_walletkit_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4055,7 +4775,7 @@ func (x *TxTemplate) String() string { func (*TxTemplate) ProtoMessage() {} func (x *TxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[52] + mi := &file_walletrpc_walletkit_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4068,7 +4788,7 @@ func (x *TxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use TxTemplate.ProtoReflect.Descriptor instead. func (*TxTemplate) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} } func (x *TxTemplate) GetInputs() []*lnrpc.OutPoint { @@ -4110,7 +4830,7 @@ type PsbtCoinSelect struct { func (x *PsbtCoinSelect) Reset() { *x = PsbtCoinSelect{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[53] + mi := &file_walletrpc_walletkit_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4123,7 +4843,7 @@ func (x *PsbtCoinSelect) String() string { func (*PsbtCoinSelect) ProtoMessage() {} func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[53] + mi := &file_walletrpc_walletkit_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4136,7 +4856,7 @@ func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtCoinSelect.ProtoReflect.Descriptor instead. func (*PsbtCoinSelect) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} } func (x *PsbtCoinSelect) GetPsbt() []byte { @@ -4210,7 +4930,7 @@ type UtxoLease struct { func (x *UtxoLease) Reset() { *x = UtxoLease{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[54] + mi := &file_walletrpc_walletkit_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4223,7 +4943,7 @@ func (x *UtxoLease) String() string { func (*UtxoLease) ProtoMessage() {} func (x *UtxoLease) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[54] + mi := &file_walletrpc_walletkit_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4236,7 +4956,7 @@ func (x *UtxoLease) ProtoReflect() protoreflect.Message { // Deprecated: Use UtxoLease.ProtoReflect.Descriptor instead. func (*UtxoLease) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} } func (x *UtxoLease) GetId() []byte { @@ -4287,7 +5007,7 @@ type SignPsbtRequest struct { func (x *SignPsbtRequest) Reset() { *x = SignPsbtRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[55] + mi := &file_walletrpc_walletkit_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4300,7 +5020,7 @@ func (x *SignPsbtRequest) String() string { func (*SignPsbtRequest) ProtoMessage() {} func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[55] + mi := &file_walletrpc_walletkit_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4313,7 +5033,7 @@ func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead. func (*SignPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{61} } func (x *SignPsbtRequest) GetFundedPsbt() []byte { @@ -4337,7 +5057,7 @@ type SignPsbtResponse struct { func (x *SignPsbtResponse) Reset() { *x = SignPsbtResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[56] + mi := &file_walletrpc_walletkit_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4350,7 +5070,7 @@ func (x *SignPsbtResponse) String() string { func (*SignPsbtResponse) ProtoMessage() {} func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[56] + mi := &file_walletrpc_walletkit_proto_msgTypes[62] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4363,7 +5083,7 @@ func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead. func (*SignPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{62} } func (x *SignPsbtResponse) GetSignedPsbt() []byte { @@ -4397,7 +5117,7 @@ type FinalizePsbtRequest struct { func (x *FinalizePsbtRequest) Reset() { *x = FinalizePsbtRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[57] + mi := &file_walletrpc_walletkit_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4410,7 +5130,7 @@ func (x *FinalizePsbtRequest) String() string { func (*FinalizePsbtRequest) ProtoMessage() {} func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[57] + mi := &file_walletrpc_walletkit_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4423,7 +5143,7 @@ func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead. func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{63} } func (x *FinalizePsbtRequest) GetFundedPsbt() []byte { @@ -4454,7 +5174,7 @@ type FinalizePsbtResponse struct { func (x *FinalizePsbtResponse) Reset() { *x = FinalizePsbtResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[58] + mi := &file_walletrpc_walletkit_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4467,7 +5187,7 @@ func (x *FinalizePsbtResponse) String() string { func (*FinalizePsbtResponse) ProtoMessage() {} func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[58] + mi := &file_walletrpc_walletkit_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4480,7 +5200,7 @@ func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead. func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{64} } func (x *FinalizePsbtResponse) GetSignedPsbt() []byte { @@ -4506,7 +5226,7 @@ type ListLeasesRequest struct { func (x *ListLeasesRequest) Reset() { *x = ListLeasesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[59] + mi := &file_walletrpc_walletkit_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4519,7 +5239,7 @@ func (x *ListLeasesRequest) String() string { func (*ListLeasesRequest) ProtoMessage() {} func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[59] + mi := &file_walletrpc_walletkit_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4532,7 +5252,7 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. func (*ListLeasesRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{65} } type ListLeasesResponse struct { @@ -4547,7 +5267,7 @@ type ListLeasesResponse struct { func (x *ListLeasesResponse) Reset() { *x = ListLeasesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[60] + mi := &file_walletrpc_walletkit_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4560,7 +5280,7 @@ func (x *ListLeasesResponse) String() string { func (*ListLeasesResponse) ProtoMessage() {} func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[60] + mi := &file_walletrpc_walletkit_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4573,7 +5293,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. func (*ListLeasesResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{66} } func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { @@ -4597,7 +5317,7 @@ type ListSweepsResponse_TransactionIDs struct { func (x *ListSweepsResponse_TransactionIDs) Reset() { *x = ListSweepsResponse_TransactionIDs{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[61] + mi := &file_walletrpc_walletkit_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4610,7 +5330,7 @@ func (x *ListSweepsResponse_TransactionIDs) String() string { func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {} func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[61] + mi := &file_walletrpc_walletkit_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4623,7 +5343,7 @@ func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message // Deprecated: Use ListSweepsResponse_TransactionIDs.ProtoReflect.Descriptor instead. func (*ListSweepsResponse_TransactionIDs) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47, 0} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53, 0} } func (x *ListSweepsResponse_TransactionIDs) GetTransactionIds() []string { @@ -4640,738 +5360,887 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{ 0x65, 0x74, 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01, - 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f, - 0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74, - 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01, - 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, - 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x15, - 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x51, 0x0a, - 0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, - 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, - 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, - 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, - 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, - 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, - 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, - 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, - 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, - 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, - 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, - 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xae, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc8, 0x01, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, + 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe3, 0x06, + 0x0a, 0x16, 0x53, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x15, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x14, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, + 0x04, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x49, 0x0a, 0x12, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, + 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x10, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x43, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x69, 0x67, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x56, 0x0a, 0x17, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, + 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x14, 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x6c, 0x0a, + 0x1f, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, + 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x1b, + 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4d, 0x0a, 0x14, 0x6d, + 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x60, 0x0a, 0x1b, 0x6d, 0x75, + 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x69, + 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, + 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x17, 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, + 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x17, + 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, + 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x14, + 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x48, 0x0a, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x70, 0x73, 0x62, + 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x73, + 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x13, + 0x0a, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x22, 0xbb, 0x07, 0x0a, 0x17, 0x53, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x50, 0x0a, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x12, 0x4c, 0x0a, + 0x13, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x69, 0x67, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, + 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x11, 0x73, + 0x69, 0x67, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x48, 0x00, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x59, 0x0a, 0x18, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x15, 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, + 0x0a, 0x20, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, + 0x00, 0x52, 0x1c, 0x6d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x50, 0x0a, 0x15, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x5f, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x12, 0x6d, + 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x63, 0x0a, 0x1c, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x63, 0x6f, 0x6d, + 0x62, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, + 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x18, 0x6d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x18, 0x6d, 0x75, 0x5f, 0x73, 0x69, 0x67, + 0x32, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x15, 0x6d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4b, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, + 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x10, 0x73, 0x69, + 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, + 0x0a, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x0b, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x14, 0x0a, 0x12, 0x73, + 0x69, 0x67, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x23, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x78, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x16, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, + 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, + 0x6e, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, + 0x22, 0xbd, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x14, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x2f, 0x0a, 0x12, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x11, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x42, 0x1c, 0x0a, 0x1a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x61, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x22, 0x93, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, + 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, + 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, + 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, + 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, + 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, + 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, + 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, + 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x15, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0x51, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, + 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, + 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xae, 0x01, 0x0a, + 0x0f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, + 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc8, 0x01, + 0x0a, 0x14, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x38, + 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x56, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x44, + 0x0a, 0x17, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x22, 0x6b, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x30, 0x0a, 0x14, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, + 0x68, 0x6f, 0x77, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x22, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x16, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x14, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x42, + 0x0a, 0x1a, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, + 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, + 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, + 0x64, 0x72, 0x22, 0x3b, 0x0a, 0x1b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, + 0x62, 0x0a, 0x1c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, + 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, + 0x64, 0x64, 0x72, 0x22, 0x4d, 0x0a, 0x1d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x22, 0xe4, 0x01, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, + 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x38, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, - 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x65, 0x73, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, - 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x22, 0x56, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, - 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x44, 0x0a, 0x17, 0x52, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x6b, - 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x6f, - 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, 0x68, 0x6f, 0x77, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x15, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, - 0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x14, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, - 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x42, 0x0a, 0x1a, 0x53, 0x69, 0x67, 0x6e, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x3b, 0x0a, 0x1b, - 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, - 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x62, 0x0a, 0x1c, 0x56, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, - 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x4d, 0x0a, - 0x1d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, - 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xe4, 0x01, 0x0a, - 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, - 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, - 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, - 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, - 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, - 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, - 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, - 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, - 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, - 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, - 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x17, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa9, 0x02, 0x0a, - 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, - 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x48, 0x00, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, - 0x54, 0x72, 0x65, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, - 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x48, - 0x00, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, - 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6f, 0x6e, - 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x75, 0x6c, 0x6c, - 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, - 0x00, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4b, 0x65, 0x79, 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x08, - 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x46, 0x0a, 0x11, 0x54, 0x61, 0x70, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x12, 0x31, 0x0a, - 0x0a, 0x61, 0x6c, 0x6c, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, - 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, - 0x22, 0x44, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x6c, - 0x65, 0x61, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0b, 0x6c, 0x65, 0x61, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x16, 0x54, 0x61, 0x70, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, - 0x6c, 0x12, 0x37, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x65, - 0x61, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x0c, 0x72, 0x65, - 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x75, - 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, - 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e, - 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x3c, 0x0a, 0x17, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x32, 0x74, 0x72, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, - 0x32, 0x74, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, - 0x68, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x33, - 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x92, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, + 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, + 0x31, 0x0a, 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0xa9, 0x02, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, + 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x48, 0x00, + 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x70, 0x61, + 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, + 0x65, 0x76, 0x65, 0x61, 0x6c, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, + 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, + 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x24, + 0x0a, 0x0d, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4b, 0x65, 0x79, + 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x46, + 0x0a, 0x11, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x54, + 0x72, 0x65, 0x65, 0x12, 0x31, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x09, 0x61, 0x6c, 0x6c, + 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x22, 0x44, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, + 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6c, 0x65, 0x61, 0x66, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x83, 0x01, 0x0a, + 0x16, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, + 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x12, 0x37, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x61, + 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x65, + 0x61, 0x66, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x66, + 0x12, 0x30, 0x0a, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, + 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x22, 0x3c, 0x0a, 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x32, 0x74, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x32, 0x74, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x22, 0x33, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x92, 0x02, 0x0a, 0x12, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, + 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, + 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x2c, + 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, + 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x22, 0x6a, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, - 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, - 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, - 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, - 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x6a, 0x0a, - 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, - 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, - 0x4b, 0x77, 0x12, 0x35, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, - 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, - 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0x90, 0x05, 0x0a, 0x0c, 0x50, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, - 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, - 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, - 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, - 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, - 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, - 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, - 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, - 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, - 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, - 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, - 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, - 0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, - 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, - 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, - 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, - 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x16, 0x0a, 0x14, - 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, - 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x9f, 0x02, - 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x35, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, + 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x52, + 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, + 0x90, 0x05, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, - 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, - 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, - 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, - 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, - 0x29, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x18, 0x42, - 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, - 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, - 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, - 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, - 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, - 0x67, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x33, 0x0a, 0x19, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, - 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x11, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x80, 0x02, 0x0a, 0x12, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, + 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, + 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, + 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, + 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, + 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, + 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x15, + 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x36, + 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, + 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, + 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, + 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, + 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, + 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, + 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, + 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, + 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, + 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, + 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, + 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x25, + 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x29, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0xf7, 0x01, 0x0a, 0x18, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, + 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x33, 0x0a, 0x19, 0x42, 0x75, + 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x50, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, - 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x22, 0x32, 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x88, 0x05, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, - 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x73, 0x62, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, - 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, - 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, - 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, - 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, - 0x77, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, - 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, - 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, - 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x24, - 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x6f, - 0x63, 0x6b, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x78, 0x70, 0x69, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x0a, 0x0a, 0x08, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, - 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, - 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, - 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, - 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, - 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x7f, 0x0a, 0x0e, 0x50, 0x73, 0x62, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x73, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x13, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, - 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, - 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x03, 0x61, 0x64, - 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, - 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, - 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, - 0x50, 0x73, 0x62, 0x74, 0x22, 0x58, 0x0a, 0x10, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, - 0x52, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0x50, - 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, - 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, - 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, - 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, - 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, - 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, - 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, - 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, - 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, - 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, - 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, - 0x2a, 0xfb, 0x09, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, - 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, - 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, - 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, - 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, - 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, - 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, - 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, - 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, - 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, - 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, - 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, - 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, - 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, - 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, - 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, - 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, - 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, - 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, - 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, - 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, - 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, - 0x4c, 0x41, 0x59, 0x5f, 0x54, 0x57, 0x45, 0x41, 0x4b, 0x4c, 0x45, 0x53, 0x53, 0x10, 0x0e, 0x12, - 0x22, 0x0a, 0x1e, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, - 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, - 0x44, 0x10, 0x0f, 0x12, 0x35, 0x0a, 0x31, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, - 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, - 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, - 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x10, 0x12, 0x36, 0x0a, 0x32, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, - 0x10, 0x11, 0x12, 0x1e, 0x0a, 0x1a, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, - 0x10, 0x12, 0x12, 0x28, 0x0a, 0x24, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, - 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x13, 0x12, 0x2b, 0x0a, 0x27, - 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, - 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, - 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x14, 0x12, 0x2c, 0x0a, 0x28, 0x4c, 0x45, 0x41, - 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, + 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, + 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, + 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, + 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, + 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, + 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, + 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x32, 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x88, 0x05, 0x0a, 0x0f, + 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x14, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, + 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, + 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x21, + 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, + 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, + 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x08, 0x73, + 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, + 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, + 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x0b, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, + 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x52, + 0x61, 0x74, 0x69, 0x6f, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6c, 0x6f, 0x63, + 0x6b, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, + 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, + 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, + 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, + 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7f, 0x0a, 0x0e, 0x50, 0x73, 0x62, 0x74, 0x43, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x34, 0x0a, + 0x15, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x13, + 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x48, 0x00, 0x52, 0x03, 0x61, 0x64, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x55, 0x74, 0x78, + 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, + 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, + 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, + 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22, 0x58, 0x0a, 0x10, 0x53, 0x69, + 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, + 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, + 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, + 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, + 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, + 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, + 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x2a, 0xfb, 0x09, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, + 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, + 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, + 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, + 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, + 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, + 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, + 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, + 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, - 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x15, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x41, 0x50, 0x52, 0x4f, - 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, - 0x10, 0x16, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4c, 0x4f, - 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, - 0x10, 0x17, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x52, 0x45, - 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, - 0x44, 0x10, 0x18, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x41, - 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x53, 0x50, 0x45, 0x4e, - 0x44, 0x10, 0x19, 0x12, 0x2d, 0x0a, 0x29, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, - 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, - 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x10, 0x1a, 0x12, 0x2e, 0x0a, 0x2a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x10, 0x1b, 0x12, 0x24, 0x0a, 0x20, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, + 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, - 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1c, 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x41, 0x50, 0x52, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, + 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, + 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, + 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, + 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, + 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x5f, 0x54, 0x57, 0x45, 0x41, 0x4b, 0x4c, + 0x45, 0x53, 0x53, 0x10, 0x0e, 0x12, 0x22, 0x0a, 0x1e, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0f, 0x12, 0x35, 0x0a, 0x31, 0x48, 0x54, 0x4c, + 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, + 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, + 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x10, + 0x12, 0x36, 0x0a, 0x32, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, + 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, + 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f, 0x4e, + 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x11, 0x12, 0x1e, 0x0a, 0x1a, 0x4c, 0x45, 0x41, 0x53, + 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, + 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x12, 0x12, 0x28, 0x0a, 0x24, 0x4c, 0x45, 0x41, 0x53, + 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, + 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, + 0x10, 0x13, 0x12, 0x2b, 0x0a, 0x27, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x14, 0x12, + 0x2c, 0x0a, 0x28, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, + 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, + 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x15, 0x12, 0x19, 0x0a, + 0x15, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x5f, 0x4b, 0x45, 0x59, + 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x16, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, + 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x17, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, + 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x18, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, + 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x5f, 0x53, 0x57, 0x45, 0x45, + 0x50, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x19, 0x12, 0x2d, 0x0a, 0x29, 0x54, 0x41, 0x50, + 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, + 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, + 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x1a, 0x12, 0x2e, 0x0a, 0x2a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, - 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1d, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, - 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, - 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1e, 0x12, 0x27, 0x0a, 0x23, 0x54, - 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, - 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x10, 0x1f, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, - 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24, + 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, + 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x1b, 0x12, 0x24, 0x0a, 0x20, 0x54, 0x41, 0x50, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1c, 0x12, 0x20, + 0x0a, 0x1c, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, + 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1d, + 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, + 0x1e, 0x12, 0x27, 0x0a, 0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, + 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x1f, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x41, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, + 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, + 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x21, 0x12, 0x27, 0x0a, 0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, - 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, - 0x43, 0x45, 0x53, 0x53, 0x10, 0x21, 0x12, 0x27, 0x0a, 0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, - 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, - 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x22, 0x12, - 0x1d, 0x0a, 0x19, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, - 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x23, 0x2a, 0x56, - 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, - 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x48, 0x41, 0x4e, - 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x50, 0x32, 0x54, 0x52, 0x10, 0x01, 0x32, 0xd6, 0x11, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, - 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, - 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, - 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, - 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, - 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, - 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x52, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x21, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, - 0x12, 0x25, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, - 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6a, 0x0a, 0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12, 0x27, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x28, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, - 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, - 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, - 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, - 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, - 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, - 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, - 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x10, 0x22, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, + 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, + 0x4b, 0x45, 0x10, 0x23, 0x2a, 0x56, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x48, 0x41, + 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, + 0x0a, 0x18, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x32, 0x54, 0x52, 0x10, 0x01, 0x32, 0xbb, 0x12, 0x0a, + 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, - 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, - 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, - 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, - 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, - 0x65, 0x12, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, - 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, - 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, - 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, - 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, - 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, - 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, - 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, - 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, + 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, + 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, + 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, + 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x64, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12, 0x27, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, + 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, + 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, + 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, + 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, + 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, + 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, + 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x16, 0x53, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x12, + 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x1a, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5387,181 +6256,223 @@ func file_walletrpc_walletkit_proto_rawDescGZIP() []byte { } var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 63) +var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 69) var file_walletrpc_walletkit_proto_goTypes = []interface{}{ (AddressType)(0), // 0: walletrpc.AddressType (WitnessType)(0), // 1: walletrpc.WitnessType (ChangeAddressType)(0), // 2: walletrpc.ChangeAddressType - (*ListUnspentRequest)(nil), // 3: walletrpc.ListUnspentRequest - (*ListUnspentResponse)(nil), // 4: walletrpc.ListUnspentResponse - (*LeaseOutputRequest)(nil), // 5: walletrpc.LeaseOutputRequest - (*LeaseOutputResponse)(nil), // 6: walletrpc.LeaseOutputResponse - (*ReleaseOutputRequest)(nil), // 7: walletrpc.ReleaseOutputRequest - (*ReleaseOutputResponse)(nil), // 8: walletrpc.ReleaseOutputResponse - (*KeyReq)(nil), // 9: walletrpc.KeyReq - (*AddrRequest)(nil), // 10: walletrpc.AddrRequest - (*AddrResponse)(nil), // 11: walletrpc.AddrResponse - (*Account)(nil), // 12: walletrpc.Account - (*AddressProperty)(nil), // 13: walletrpc.AddressProperty - (*AccountWithAddresses)(nil), // 14: walletrpc.AccountWithAddresses - (*ListAccountsRequest)(nil), // 15: walletrpc.ListAccountsRequest - (*ListAccountsResponse)(nil), // 16: walletrpc.ListAccountsResponse - (*RequiredReserveRequest)(nil), // 17: walletrpc.RequiredReserveRequest - (*RequiredReserveResponse)(nil), // 18: walletrpc.RequiredReserveResponse - (*ListAddressesRequest)(nil), // 19: walletrpc.ListAddressesRequest - (*ListAddressesResponse)(nil), // 20: walletrpc.ListAddressesResponse - (*GetTransactionRequest)(nil), // 21: walletrpc.GetTransactionRequest - (*SignMessageWithAddrRequest)(nil), // 22: walletrpc.SignMessageWithAddrRequest - (*SignMessageWithAddrResponse)(nil), // 23: walletrpc.SignMessageWithAddrResponse - (*VerifyMessageWithAddrRequest)(nil), // 24: walletrpc.VerifyMessageWithAddrRequest - (*VerifyMessageWithAddrResponse)(nil), // 25: walletrpc.VerifyMessageWithAddrResponse - (*ImportAccountRequest)(nil), // 26: walletrpc.ImportAccountRequest - (*ImportAccountResponse)(nil), // 27: walletrpc.ImportAccountResponse - (*ImportPublicKeyRequest)(nil), // 28: walletrpc.ImportPublicKeyRequest - (*ImportPublicKeyResponse)(nil), // 29: walletrpc.ImportPublicKeyResponse - (*ImportTapscriptRequest)(nil), // 30: walletrpc.ImportTapscriptRequest - (*TapscriptFullTree)(nil), // 31: walletrpc.TapscriptFullTree - (*TapLeaf)(nil), // 32: walletrpc.TapLeaf - (*TapscriptPartialReveal)(nil), // 33: walletrpc.TapscriptPartialReveal - (*ImportTapscriptResponse)(nil), // 34: walletrpc.ImportTapscriptResponse - (*Transaction)(nil), // 35: walletrpc.Transaction - (*PublishResponse)(nil), // 36: walletrpc.PublishResponse - (*RemoveTransactionResponse)(nil), // 37: walletrpc.RemoveTransactionResponse - (*SendOutputsRequest)(nil), // 38: walletrpc.SendOutputsRequest - (*SendOutputsResponse)(nil), // 39: walletrpc.SendOutputsResponse - (*EstimateFeeRequest)(nil), // 40: walletrpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 41: walletrpc.EstimateFeeResponse - (*PendingSweep)(nil), // 42: walletrpc.PendingSweep - (*PendingSweepsRequest)(nil), // 43: walletrpc.PendingSweepsRequest - (*PendingSweepsResponse)(nil), // 44: walletrpc.PendingSweepsResponse - (*BumpFeeRequest)(nil), // 45: walletrpc.BumpFeeRequest - (*BumpFeeResponse)(nil), // 46: walletrpc.BumpFeeResponse - (*BumpForceCloseFeeRequest)(nil), // 47: walletrpc.BumpForceCloseFeeRequest - (*BumpForceCloseFeeResponse)(nil), // 48: walletrpc.BumpForceCloseFeeResponse - (*ListSweepsRequest)(nil), // 49: walletrpc.ListSweepsRequest - (*ListSweepsResponse)(nil), // 50: walletrpc.ListSweepsResponse - (*LabelTransactionRequest)(nil), // 51: walletrpc.LabelTransactionRequest - (*LabelTransactionResponse)(nil), // 52: walletrpc.LabelTransactionResponse - (*FundPsbtRequest)(nil), // 53: walletrpc.FundPsbtRequest - (*FundPsbtResponse)(nil), // 54: walletrpc.FundPsbtResponse - (*TxTemplate)(nil), // 55: walletrpc.TxTemplate - (*PsbtCoinSelect)(nil), // 56: walletrpc.PsbtCoinSelect - (*UtxoLease)(nil), // 57: walletrpc.UtxoLease - (*SignPsbtRequest)(nil), // 58: walletrpc.SignPsbtRequest - (*SignPsbtResponse)(nil), // 59: walletrpc.SignPsbtResponse - (*FinalizePsbtRequest)(nil), // 60: walletrpc.FinalizePsbtRequest - (*FinalizePsbtResponse)(nil), // 61: walletrpc.FinalizePsbtResponse - (*ListLeasesRequest)(nil), // 62: walletrpc.ListLeasesRequest - (*ListLeasesResponse)(nil), // 63: walletrpc.ListLeasesResponse - (*ListSweepsResponse_TransactionIDs)(nil), // 64: walletrpc.ListSweepsResponse.TransactionIDs - nil, // 65: walletrpc.TxTemplate.OutputsEntry - (*lnrpc.Utxo)(nil), // 66: lnrpc.Utxo - (*lnrpc.OutPoint)(nil), // 67: lnrpc.OutPoint - (*signrpc.TxOut)(nil), // 68: signrpc.TxOut - (lnrpc.CoinSelectionStrategy)(0), // 69: lnrpc.CoinSelectionStrategy - (*lnrpc.ChannelPoint)(nil), // 70: lnrpc.ChannelPoint - (*lnrpc.TransactionDetails)(nil), // 71: lnrpc.TransactionDetails - (*signrpc.KeyLocator)(nil), // 72: signrpc.KeyLocator - (*signrpc.KeyDescriptor)(nil), // 73: signrpc.KeyDescriptor - (*lnrpc.Transaction)(nil), // 74: lnrpc.Transaction + (*SignCoordinatorRequest)(nil), // 3: walletrpc.SignCoordinatorRequest + (*SignCoordinatorResponse)(nil), // 4: walletrpc.SignCoordinatorResponse + (*SignerError)(nil), // 5: walletrpc.SignerError + (*SignerRegistration)(nil), // 6: walletrpc.SignerRegistration + (*RegistrationResponse)(nil), // 7: walletrpc.RegistrationResponse + (*RegistrationComplete)(nil), // 8: walletrpc.RegistrationComplete + (*ListUnspentRequest)(nil), // 9: walletrpc.ListUnspentRequest + (*ListUnspentResponse)(nil), // 10: walletrpc.ListUnspentResponse + (*LeaseOutputRequest)(nil), // 11: walletrpc.LeaseOutputRequest + (*LeaseOutputResponse)(nil), // 12: walletrpc.LeaseOutputResponse + (*ReleaseOutputRequest)(nil), // 13: walletrpc.ReleaseOutputRequest + (*ReleaseOutputResponse)(nil), // 14: walletrpc.ReleaseOutputResponse + (*KeyReq)(nil), // 15: walletrpc.KeyReq + (*AddrRequest)(nil), // 16: walletrpc.AddrRequest + (*AddrResponse)(nil), // 17: walletrpc.AddrResponse + (*Account)(nil), // 18: walletrpc.Account + (*AddressProperty)(nil), // 19: walletrpc.AddressProperty + (*AccountWithAddresses)(nil), // 20: walletrpc.AccountWithAddresses + (*ListAccountsRequest)(nil), // 21: walletrpc.ListAccountsRequest + (*ListAccountsResponse)(nil), // 22: walletrpc.ListAccountsResponse + (*RequiredReserveRequest)(nil), // 23: walletrpc.RequiredReserveRequest + (*RequiredReserveResponse)(nil), // 24: walletrpc.RequiredReserveResponse + (*ListAddressesRequest)(nil), // 25: walletrpc.ListAddressesRequest + (*ListAddressesResponse)(nil), // 26: walletrpc.ListAddressesResponse + (*GetTransactionRequest)(nil), // 27: walletrpc.GetTransactionRequest + (*SignMessageWithAddrRequest)(nil), // 28: walletrpc.SignMessageWithAddrRequest + (*SignMessageWithAddrResponse)(nil), // 29: walletrpc.SignMessageWithAddrResponse + (*VerifyMessageWithAddrRequest)(nil), // 30: walletrpc.VerifyMessageWithAddrRequest + (*VerifyMessageWithAddrResponse)(nil), // 31: walletrpc.VerifyMessageWithAddrResponse + (*ImportAccountRequest)(nil), // 32: walletrpc.ImportAccountRequest + (*ImportAccountResponse)(nil), // 33: walletrpc.ImportAccountResponse + (*ImportPublicKeyRequest)(nil), // 34: walletrpc.ImportPublicKeyRequest + (*ImportPublicKeyResponse)(nil), // 35: walletrpc.ImportPublicKeyResponse + (*ImportTapscriptRequest)(nil), // 36: walletrpc.ImportTapscriptRequest + (*TapscriptFullTree)(nil), // 37: walletrpc.TapscriptFullTree + (*TapLeaf)(nil), // 38: walletrpc.TapLeaf + (*TapscriptPartialReveal)(nil), // 39: walletrpc.TapscriptPartialReveal + (*ImportTapscriptResponse)(nil), // 40: walletrpc.ImportTapscriptResponse + (*Transaction)(nil), // 41: walletrpc.Transaction + (*PublishResponse)(nil), // 42: walletrpc.PublishResponse + (*RemoveTransactionResponse)(nil), // 43: walletrpc.RemoveTransactionResponse + (*SendOutputsRequest)(nil), // 44: walletrpc.SendOutputsRequest + (*SendOutputsResponse)(nil), // 45: walletrpc.SendOutputsResponse + (*EstimateFeeRequest)(nil), // 46: walletrpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 47: walletrpc.EstimateFeeResponse + (*PendingSweep)(nil), // 48: walletrpc.PendingSweep + (*PendingSweepsRequest)(nil), // 49: walletrpc.PendingSweepsRequest + (*PendingSweepsResponse)(nil), // 50: walletrpc.PendingSweepsResponse + (*BumpFeeRequest)(nil), // 51: walletrpc.BumpFeeRequest + (*BumpFeeResponse)(nil), // 52: walletrpc.BumpFeeResponse + (*BumpForceCloseFeeRequest)(nil), // 53: walletrpc.BumpForceCloseFeeRequest + (*BumpForceCloseFeeResponse)(nil), // 54: walletrpc.BumpForceCloseFeeResponse + (*ListSweepsRequest)(nil), // 55: walletrpc.ListSweepsRequest + (*ListSweepsResponse)(nil), // 56: walletrpc.ListSweepsResponse + (*LabelTransactionRequest)(nil), // 57: walletrpc.LabelTransactionRequest + (*LabelTransactionResponse)(nil), // 58: walletrpc.LabelTransactionResponse + (*FundPsbtRequest)(nil), // 59: walletrpc.FundPsbtRequest + (*FundPsbtResponse)(nil), // 60: walletrpc.FundPsbtResponse + (*TxTemplate)(nil), // 61: walletrpc.TxTemplate + (*PsbtCoinSelect)(nil), // 62: walletrpc.PsbtCoinSelect + (*UtxoLease)(nil), // 63: walletrpc.UtxoLease + (*SignPsbtRequest)(nil), // 64: walletrpc.SignPsbtRequest + (*SignPsbtResponse)(nil), // 65: walletrpc.SignPsbtResponse + (*FinalizePsbtRequest)(nil), // 66: walletrpc.FinalizePsbtRequest + (*FinalizePsbtResponse)(nil), // 67: walletrpc.FinalizePsbtResponse + (*ListLeasesRequest)(nil), // 68: walletrpc.ListLeasesRequest + (*ListLeasesResponse)(nil), // 69: walletrpc.ListLeasesResponse + (*ListSweepsResponse_TransactionIDs)(nil), // 70: walletrpc.ListSweepsResponse.TransactionIDs + nil, // 71: walletrpc.TxTemplate.OutputsEntry + (*signrpc.SharedKeyRequest)(nil), // 72: signrpc.SharedKeyRequest + (*signrpc.SignMessageReq)(nil), // 73: signrpc.SignMessageReq + (*signrpc.MuSig2SessionRequest)(nil), // 74: signrpc.MuSig2SessionRequest + (*signrpc.MuSig2RegisterNoncesRequest)(nil), // 75: signrpc.MuSig2RegisterNoncesRequest + (*signrpc.MuSig2SignRequest)(nil), // 76: signrpc.MuSig2SignRequest + (*signrpc.MuSig2CombineSigRequest)(nil), // 77: signrpc.MuSig2CombineSigRequest + (*signrpc.MuSig2CleanupRequest)(nil), // 78: signrpc.MuSig2CleanupRequest + (*signrpc.SharedKeyResponse)(nil), // 79: signrpc.SharedKeyResponse + (*signrpc.SignMessageResp)(nil), // 80: signrpc.SignMessageResp + (*signrpc.MuSig2SessionResponse)(nil), // 81: signrpc.MuSig2SessionResponse + (*signrpc.MuSig2RegisterNoncesResponse)(nil), // 82: signrpc.MuSig2RegisterNoncesResponse + (*signrpc.MuSig2SignResponse)(nil), // 83: signrpc.MuSig2SignResponse + (*signrpc.MuSig2CombineSigResponse)(nil), // 84: signrpc.MuSig2CombineSigResponse + (*signrpc.MuSig2CleanupResponse)(nil), // 85: signrpc.MuSig2CleanupResponse + (*lnrpc.Utxo)(nil), // 86: lnrpc.Utxo + (*lnrpc.OutPoint)(nil), // 87: lnrpc.OutPoint + (*signrpc.TxOut)(nil), // 88: signrpc.TxOut + (lnrpc.CoinSelectionStrategy)(0), // 89: lnrpc.CoinSelectionStrategy + (*lnrpc.ChannelPoint)(nil), // 90: lnrpc.ChannelPoint + (*lnrpc.TransactionDetails)(nil), // 91: lnrpc.TransactionDetails + (*signrpc.KeyLocator)(nil), // 92: signrpc.KeyLocator + (*signrpc.KeyDescriptor)(nil), // 93: signrpc.KeyDescriptor + (*lnrpc.Transaction)(nil), // 94: lnrpc.Transaction } var file_walletrpc_walletkit_proto_depIdxs = []int32{ - 66, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 67, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 67, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType - 0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType - 0, // 5: walletrpc.AccountWithAddresses.address_type:type_name -> walletrpc.AddressType - 13, // 6: walletrpc.AccountWithAddresses.addresses:type_name -> walletrpc.AddressProperty - 0, // 7: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType - 12, // 8: walletrpc.ListAccountsResponse.accounts:type_name -> walletrpc.Account - 14, // 9: walletrpc.ListAddressesResponse.account_with_addresses:type_name -> walletrpc.AccountWithAddresses - 0, // 10: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType - 12, // 11: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account - 0, // 12: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType - 31, // 13: walletrpc.ImportTapscriptRequest.full_tree:type_name -> walletrpc.TapscriptFullTree - 33, // 14: walletrpc.ImportTapscriptRequest.partial_reveal:type_name -> walletrpc.TapscriptPartialReveal - 32, // 15: walletrpc.TapscriptFullTree.all_leaves:type_name -> walletrpc.TapLeaf - 32, // 16: walletrpc.TapscriptPartialReveal.revealed_leaf:type_name -> walletrpc.TapLeaf - 68, // 17: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut - 69, // 18: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 67, // 19: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint - 1, // 20: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType - 42, // 21: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep - 67, // 22: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint - 70, // 23: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint - 71, // 24: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails - 64, // 25: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs - 55, // 26: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate - 56, // 27: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect - 2, // 28: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType - 69, // 29: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 57, // 30: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 67, // 31: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint - 65, // 32: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry - 67, // 33: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint - 57, // 34: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 3, // 35: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest - 5, // 36: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest - 7, // 37: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest - 62, // 38: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest - 9, // 39: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq - 72, // 40: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator - 10, // 41: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest - 21, // 42: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest - 15, // 43: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest - 17, // 44: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest - 19, // 45: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest - 22, // 46: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest - 24, // 47: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest - 26, // 48: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest - 28, // 49: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest - 30, // 50: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest - 35, // 51: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction - 21, // 52: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest - 38, // 53: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest - 40, // 54: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest - 43, // 55: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest - 45, // 56: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest - 47, // 57: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest - 49, // 58: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest - 51, // 59: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest - 53, // 60: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest - 58, // 61: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest - 60, // 62: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest - 4, // 63: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse - 6, // 64: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse - 8, // 65: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse - 63, // 66: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse - 73, // 67: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor - 73, // 68: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor - 11, // 69: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse - 74, // 70: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction - 16, // 71: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse - 18, // 72: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse - 20, // 73: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse - 23, // 74: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse - 25, // 75: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse - 27, // 76: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse - 29, // 77: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse - 34, // 78: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse - 36, // 79: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse - 37, // 80: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse - 39, // 81: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse - 41, // 82: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse - 44, // 83: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse - 46, // 84: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse - 48, // 85: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse - 50, // 86: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse - 52, // 87: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse - 54, // 88: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse - 59, // 89: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse - 61, // 90: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse - 63, // [63:91] is the sub-list for method output_type - 35, // [35:63] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 7, // 0: walletrpc.SignCoordinatorRequest.registration_response:type_name -> walletrpc.RegistrationResponse + 72, // 1: walletrpc.SignCoordinatorRequest.shared_key_request:type_name -> signrpc.SharedKeyRequest + 73, // 2: walletrpc.SignCoordinatorRequest.sign_message_req:type_name -> signrpc.SignMessageReq + 74, // 3: walletrpc.SignCoordinatorRequest.mu_sig2_session_request:type_name -> signrpc.MuSig2SessionRequest + 75, // 4: walletrpc.SignCoordinatorRequest.mu_sig2_register_nonces_request:type_name -> signrpc.MuSig2RegisterNoncesRequest + 76, // 5: walletrpc.SignCoordinatorRequest.mu_sig2_sign_request:type_name -> signrpc.MuSig2SignRequest + 77, // 6: walletrpc.SignCoordinatorRequest.mu_sig2_combine_sig_request:type_name -> signrpc.MuSig2CombineSigRequest + 78, // 7: walletrpc.SignCoordinatorRequest.mu_sig2_cleanup_request:type_name -> signrpc.MuSig2CleanupRequest + 64, // 8: walletrpc.SignCoordinatorRequest.sign_psbt_request:type_name -> walletrpc.SignPsbtRequest + 6, // 9: walletrpc.SignCoordinatorResponse.signer_registration:type_name -> walletrpc.SignerRegistration + 79, // 10: walletrpc.SignCoordinatorResponse.shared_key_response:type_name -> signrpc.SharedKeyResponse + 80, // 11: walletrpc.SignCoordinatorResponse.sign_message_resp:type_name -> signrpc.SignMessageResp + 81, // 12: walletrpc.SignCoordinatorResponse.mu_sig2_session_response:type_name -> signrpc.MuSig2SessionResponse + 82, // 13: walletrpc.SignCoordinatorResponse.mu_sig2_register_nonces_response:type_name -> signrpc.MuSig2RegisterNoncesResponse + 83, // 14: walletrpc.SignCoordinatorResponse.mu_sig2_sign_response:type_name -> signrpc.MuSig2SignResponse + 84, // 15: walletrpc.SignCoordinatorResponse.mu_sig2_combine_sig_response:type_name -> signrpc.MuSig2CombineSigResponse + 85, // 16: walletrpc.SignCoordinatorResponse.mu_sig2_cleanup_response:type_name -> signrpc.MuSig2CleanupResponse + 65, // 17: walletrpc.SignCoordinatorResponse.sign_psbt_response:type_name -> walletrpc.SignPsbtResponse + 5, // 18: walletrpc.SignCoordinatorResponse.signer_error:type_name -> walletrpc.SignerError + 8, // 19: walletrpc.RegistrationResponse.registration_complete:type_name -> walletrpc.RegistrationComplete + 86, // 20: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 87, // 21: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 87, // 22: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 0, // 23: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType + 0, // 24: walletrpc.Account.address_type:type_name -> walletrpc.AddressType + 0, // 25: walletrpc.AccountWithAddresses.address_type:type_name -> walletrpc.AddressType + 19, // 26: walletrpc.AccountWithAddresses.addresses:type_name -> walletrpc.AddressProperty + 0, // 27: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType + 18, // 28: walletrpc.ListAccountsResponse.accounts:type_name -> walletrpc.Account + 20, // 29: walletrpc.ListAddressesResponse.account_with_addresses:type_name -> walletrpc.AccountWithAddresses + 0, // 30: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType + 18, // 31: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account + 0, // 32: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType + 37, // 33: walletrpc.ImportTapscriptRequest.full_tree:type_name -> walletrpc.TapscriptFullTree + 39, // 34: walletrpc.ImportTapscriptRequest.partial_reveal:type_name -> walletrpc.TapscriptPartialReveal + 38, // 35: walletrpc.TapscriptFullTree.all_leaves:type_name -> walletrpc.TapLeaf + 38, // 36: walletrpc.TapscriptPartialReveal.revealed_leaf:type_name -> walletrpc.TapLeaf + 88, // 37: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut + 89, // 38: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 87, // 39: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint + 1, // 40: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType + 48, // 41: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep + 87, // 42: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint + 90, // 43: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint + 91, // 44: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails + 70, // 45: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs + 61, // 46: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate + 62, // 47: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect + 2, // 48: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType + 89, // 49: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 63, // 50: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 87, // 51: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint + 71, // 52: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry + 87, // 53: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint + 63, // 54: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 9, // 55: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest + 11, // 56: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest + 13, // 57: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest + 68, // 58: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest + 15, // 59: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq + 92, // 60: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator + 16, // 61: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest + 27, // 62: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest + 21, // 63: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest + 23, // 64: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest + 25, // 65: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest + 28, // 66: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest + 30, // 67: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest + 32, // 68: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest + 34, // 69: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest + 36, // 70: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest + 41, // 71: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction + 27, // 72: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest + 44, // 73: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest + 46, // 74: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest + 49, // 75: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest + 51, // 76: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest + 53, // 77: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest + 55, // 78: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest + 57, // 79: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest + 59, // 80: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest + 64, // 81: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest + 66, // 82: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest + 4, // 83: walletrpc.WalletKit.SignCoordinatorStreams:input_type -> walletrpc.SignCoordinatorResponse + 10, // 84: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse + 12, // 85: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse + 14, // 86: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse + 69, // 87: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse + 93, // 88: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor + 93, // 89: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor + 17, // 90: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse + 94, // 91: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction + 22, // 92: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse + 24, // 93: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse + 26, // 94: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse + 29, // 95: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse + 31, // 96: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse + 33, // 97: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse + 35, // 98: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse + 40, // 99: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse + 42, // 100: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse + 43, // 101: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse + 45, // 102: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse + 47, // 103: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse + 50, // 104: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse + 52, // 105: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse + 54, // 106: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse + 56, // 107: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse + 58, // 108: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse + 60, // 109: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse + 65, // 110: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse + 67, // 111: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse + 3, // 112: walletrpc.WalletKit.SignCoordinatorStreams:output_type -> walletrpc.SignCoordinatorRequest + 84, // [84:113] is the sub-list for method output_type + 55, // [55:84] is the sub-list for method input_type + 55, // [55:55] is the sub-list for extension type_name + 55, // [55:55] is the sub-list for extension extendee + 0, // [0:55] is the sub-list for field type_name } func init() { file_walletrpc_walletkit_proto_init() } @@ -5571,7 +6482,7 @@ func file_walletrpc_walletkit_proto_init() { } if !protoimpl.UnsafeEnabled { file_walletrpc_walletkit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUnspentRequest); i { + switch v := v.(*SignCoordinatorRequest); i { case 0: return &v.state case 1: @@ -5583,7 +6494,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUnspentResponse); i { + switch v := v.(*SignCoordinatorResponse); i { case 0: return &v.state case 1: @@ -5595,7 +6506,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LeaseOutputRequest); i { + switch v := v.(*SignerError); i { case 0: return &v.state case 1: @@ -5607,7 +6518,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LeaseOutputResponse); i { + switch v := v.(*SignerRegistration); i { case 0: return &v.state case 1: @@ -5619,7 +6530,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReleaseOutputRequest); i { + switch v := v.(*RegistrationResponse); i { case 0: return &v.state case 1: @@ -5631,7 +6542,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReleaseOutputResponse); i { + switch v := v.(*RegistrationComplete); i { case 0: return &v.state case 1: @@ -5643,7 +6554,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyReq); i { + switch v := v.(*ListUnspentRequest); i { case 0: return &v.state case 1: @@ -5655,7 +6566,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddrRequest); i { + switch v := v.(*ListUnspentResponse); i { case 0: return &v.state case 1: @@ -5667,7 +6578,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddrResponse); i { + switch v := v.(*LeaseOutputRequest); i { case 0: return &v.state case 1: @@ -5679,7 +6590,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Account); i { + switch v := v.(*LeaseOutputResponse); i { case 0: return &v.state case 1: @@ -5691,7 +6602,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddressProperty); i { + switch v := v.(*ReleaseOutputRequest); i { case 0: return &v.state case 1: @@ -5703,7 +6614,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AccountWithAddresses); i { + switch v := v.(*ReleaseOutputResponse); i { case 0: return &v.state case 1: @@ -5715,7 +6626,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAccountsRequest); i { + switch v := v.(*KeyReq); i { case 0: return &v.state case 1: @@ -5727,7 +6638,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAccountsResponse); i { + switch v := v.(*AddrRequest); i { case 0: return &v.state case 1: @@ -5739,7 +6650,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RequiredReserveRequest); i { + switch v := v.(*AddrResponse); i { case 0: return &v.state case 1: @@ -5751,7 +6662,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RequiredReserveResponse); i { + switch v := v.(*Account); i { case 0: return &v.state case 1: @@ -5763,7 +6674,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAddressesRequest); i { + switch v := v.(*AddressProperty); i { case 0: return &v.state case 1: @@ -5775,7 +6686,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAddressesResponse); i { + switch v := v.(*AccountWithAddresses); i { case 0: return &v.state case 1: @@ -5787,7 +6698,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTransactionRequest); i { + switch v := v.(*ListAccountsRequest); i { case 0: return &v.state case 1: @@ -5799,7 +6710,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignMessageWithAddrRequest); i { + switch v := v.(*ListAccountsResponse); i { case 0: return &v.state case 1: @@ -5811,7 +6722,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignMessageWithAddrResponse); i { + switch v := v.(*RequiredReserveRequest); i { case 0: return &v.state case 1: @@ -5823,7 +6734,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyMessageWithAddrRequest); i { + switch v := v.(*RequiredReserveResponse); i { case 0: return &v.state case 1: @@ -5835,7 +6746,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyMessageWithAddrResponse); i { + switch v := v.(*ListAddressesRequest); i { case 0: return &v.state case 1: @@ -5847,7 +6758,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportAccountRequest); i { + switch v := v.(*ListAddressesResponse); i { case 0: return &v.state case 1: @@ -5859,7 +6770,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportAccountResponse); i { + switch v := v.(*GetTransactionRequest); i { case 0: return &v.state case 1: @@ -5871,7 +6782,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportPublicKeyRequest); i { + switch v := v.(*SignMessageWithAddrRequest); i { case 0: return &v.state case 1: @@ -5883,7 +6794,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportPublicKeyResponse); i { + switch v := v.(*SignMessageWithAddrResponse); i { case 0: return &v.state case 1: @@ -5895,7 +6806,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportTapscriptRequest); i { + switch v := v.(*VerifyMessageWithAddrRequest); i { case 0: return &v.state case 1: @@ -5907,7 +6818,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TapscriptFullTree); i { + switch v := v.(*VerifyMessageWithAddrResponse); i { case 0: return &v.state case 1: @@ -5919,7 +6830,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TapLeaf); i { + switch v := v.(*ImportAccountRequest); i { case 0: return &v.state case 1: @@ -5931,7 +6842,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TapscriptPartialReveal); i { + switch v := v.(*ImportAccountResponse); i { case 0: return &v.state case 1: @@ -5943,7 +6854,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImportTapscriptResponse); i { + switch v := v.(*ImportPublicKeyRequest); i { case 0: return &v.state case 1: @@ -5955,7 +6866,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Transaction); i { + switch v := v.(*ImportPublicKeyResponse); i { case 0: return &v.state case 1: @@ -5967,7 +6878,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PublishResponse); i { + switch v := v.(*ImportTapscriptRequest); i { case 0: return &v.state case 1: @@ -5979,7 +6890,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveTransactionResponse); i { + switch v := v.(*TapscriptFullTree); i { case 0: return &v.state case 1: @@ -5991,7 +6902,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendOutputsRequest); i { + switch v := v.(*TapLeaf); i { case 0: return &v.state case 1: @@ -6003,7 +6914,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendOutputsResponse); i { + switch v := v.(*TapscriptPartialReveal); i { case 0: return &v.state case 1: @@ -6015,7 +6926,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstimateFeeRequest); i { + switch v := v.(*ImportTapscriptResponse); i { case 0: return &v.state case 1: @@ -6027,7 +6938,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstimateFeeResponse); i { + switch v := v.(*Transaction); i { case 0: return &v.state case 1: @@ -6039,7 +6950,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingSweep); i { + switch v := v.(*PublishResponse); i { case 0: return &v.state case 1: @@ -6051,7 +6962,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingSweepsRequest); i { + switch v := v.(*RemoveTransactionResponse); i { case 0: return &v.state case 1: @@ -6063,7 +6974,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingSweepsResponse); i { + switch v := v.(*SendOutputsRequest); i { case 0: return &v.state case 1: @@ -6075,7 +6986,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BumpFeeRequest); i { + switch v := v.(*SendOutputsResponse); i { case 0: return &v.state case 1: @@ -6087,7 +6998,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BumpFeeResponse); i { + switch v := v.(*EstimateFeeRequest); i { case 0: return &v.state case 1: @@ -6099,7 +7010,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BumpForceCloseFeeRequest); i { + switch v := v.(*EstimateFeeResponse); i { case 0: return &v.state case 1: @@ -6111,7 +7022,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BumpForceCloseFeeResponse); i { + switch v := v.(*PendingSweep); i { case 0: return &v.state case 1: @@ -6123,7 +7034,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSweepsRequest); i { + switch v := v.(*PendingSweepsRequest); i { case 0: return &v.state case 1: @@ -6135,7 +7046,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSweepsResponse); i { + switch v := v.(*PendingSweepsResponse); i { case 0: return &v.state case 1: @@ -6147,7 +7058,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LabelTransactionRequest); i { + switch v := v.(*BumpFeeRequest); i { case 0: return &v.state case 1: @@ -6159,7 +7070,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LabelTransactionResponse); i { + switch v := v.(*BumpFeeResponse); i { case 0: return &v.state case 1: @@ -6171,7 +7082,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundPsbtRequest); i { + switch v := v.(*BumpForceCloseFeeRequest); i { case 0: return &v.state case 1: @@ -6183,7 +7094,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundPsbtResponse); i { + switch v := v.(*BumpForceCloseFeeResponse); i { case 0: return &v.state case 1: @@ -6195,7 +7106,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TxTemplate); i { + switch v := v.(*ListSweepsRequest); i { case 0: return &v.state case 1: @@ -6207,7 +7118,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PsbtCoinSelect); i { + switch v := v.(*ListSweepsResponse); i { case 0: return &v.state case 1: @@ -6219,7 +7130,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UtxoLease); i { + switch v := v.(*LabelTransactionRequest); i { case 0: return &v.state case 1: @@ -6231,7 +7142,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignPsbtRequest); i { + switch v := v.(*LabelTransactionResponse); i { case 0: return &v.state case 1: @@ -6243,7 +7154,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignPsbtResponse); i { + switch v := v.(*FundPsbtRequest); i { case 0: return &v.state case 1: @@ -6255,7 +7166,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePsbtRequest); i { + switch v := v.(*FundPsbtResponse); i { case 0: return &v.state case 1: @@ -6267,7 +7178,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePsbtResponse); i { + switch v := v.(*TxTemplate); i { case 0: return &v.state case 1: @@ -6279,7 +7190,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListLeasesRequest); i { + switch v := v.(*PsbtCoinSelect); i { case 0: return &v.state case 1: @@ -6291,7 +7202,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListLeasesResponse); i { + switch v := v.(*UtxoLease); i { case 0: return &v.state case 1: @@ -6303,6 +7214,78 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignPsbtRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignPsbtResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinalizePsbtRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinalizePsbtResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSweepsResponse_TransactionIDs); i { case 0: return &v.state @@ -6315,17 +7298,46 @@ func file_walletrpc_walletkit_proto_init() { } } } - file_walletrpc_walletkit_proto_msgTypes[27].OneofWrappers = []interface{}{ + file_walletrpc_walletkit_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*SignCoordinatorRequest_RegistrationResponse)(nil), + (*SignCoordinatorRequest_Ping)(nil), + (*SignCoordinatorRequest_SharedKeyRequest)(nil), + (*SignCoordinatorRequest_SignMessageReq)(nil), + (*SignCoordinatorRequest_MuSig2SessionRequest)(nil), + (*SignCoordinatorRequest_MuSig2RegisterNoncesRequest)(nil), + (*SignCoordinatorRequest_MuSig2SignRequest)(nil), + (*SignCoordinatorRequest_MuSig2CombineSigRequest)(nil), + (*SignCoordinatorRequest_MuSig2CleanupRequest)(nil), + (*SignCoordinatorRequest_SignPsbtRequest)(nil), + } + file_walletrpc_walletkit_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*SignCoordinatorResponse_SignerRegistration)(nil), + (*SignCoordinatorResponse_Pong)(nil), + (*SignCoordinatorResponse_SharedKeyResponse)(nil), + (*SignCoordinatorResponse_SignMessageResp)(nil), + (*SignCoordinatorResponse_MuSig2SessionResponse)(nil), + (*SignCoordinatorResponse_MuSig2RegisterNoncesResponse)(nil), + (*SignCoordinatorResponse_MuSig2SignResponse)(nil), + (*SignCoordinatorResponse_MuSig2CombineSigResponse)(nil), + (*SignCoordinatorResponse_MuSig2CleanupResponse)(nil), + (*SignCoordinatorResponse_SignPsbtResponse)(nil), + (*SignCoordinatorResponse_SignerError)(nil), + } + file_walletrpc_walletkit_proto_msgTypes[4].OneofWrappers = []interface{}{ + (*RegistrationResponse_RegistrationComplete)(nil), + (*RegistrationResponse_RegistrationError)(nil), + } + file_walletrpc_walletkit_proto_msgTypes[33].OneofWrappers = []interface{}{ (*ImportTapscriptRequest_FullTree)(nil), (*ImportTapscriptRequest_PartialReveal)(nil), (*ImportTapscriptRequest_RootHashOnly)(nil), (*ImportTapscriptRequest_FullKeyOnly)(nil), } - file_walletrpc_walletkit_proto_msgTypes[47].OneofWrappers = []interface{}{ + file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []interface{}{ (*ListSweepsResponse_TransactionDetails)(nil), (*ListSweepsResponse_TransactionIds)(nil), } - file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []interface{}{ + file_walletrpc_walletkit_proto_msgTypes[56].OneofWrappers = []interface{}{ (*FundPsbtRequest_Psbt)(nil), (*FundPsbtRequest_Raw)(nil), (*FundPsbtRequest_CoinSelect)(nil), @@ -6333,7 +7345,7 @@ func file_walletrpc_walletkit_proto_init() { (*FundPsbtRequest_SatPerVbyte)(nil), (*FundPsbtRequest_SatPerKw)(nil), } - file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []interface{}{ + file_walletrpc_walletkit_proto_msgTypes[59].OneofWrappers = []interface{}{ (*PsbtCoinSelect_ExistingOutputIndex)(nil), (*PsbtCoinSelect_Add)(nil), } @@ -6343,7 +7355,7 @@ func file_walletrpc_walletkit_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_walletrpc_walletkit_proto_rawDesc, NumEnums: 3, - NumMessages: 63, + NumMessages: 69, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/walletrpc/walletkit.pb.gw.go b/lnrpc/walletrpc/walletkit.pb.gw.go index c4c4db99f33..6575706dbdd 100644 --- a/lnrpc/walletrpc/walletkit.pb.gw.go +++ b/lnrpc/walletrpc/walletkit.pb.gw.go @@ -980,6 +980,49 @@ func local_request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runti } +func request_WalletKit_SignCoordinatorStreams_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (WalletKit_SignCoordinatorStreamsClient, runtime.ServerMetadata, error) { + var metadata runtime.ServerMetadata + stream, err := client.SignCoordinatorStreams(ctx) + if err != nil { + grpclog.Infof("Failed to start streaming: %v", err) + return nil, metadata, err + } + dec := marshaler.NewDecoder(req.Body) + handleSend := func() error { + var protoReq SignCoordinatorResponse + err := dec.Decode(&protoReq) + if err == io.EOF { + return err + } + if err != nil { + grpclog.Infof("Failed to decode request: %v", err) + return err + } + if err := stream.Send(&protoReq); err != nil { + grpclog.Infof("Failed to send request: %v", err) + return err + } + return nil + } + go func() { + for { + if err := handleSend(); err != nil { + break + } + } + if err := stream.CloseSend(); err != nil { + grpclog.Infof("Failed to terminate client stream: %v", err) + } + }() + header, err := stream.Header() + if err != nil { + grpclog.Infof("Failed to get header from client: %v", err) + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil +} + // RegisterWalletKitHandlerServer registers the http handlers for service WalletKit to "mux". // UnaryRPC :call WalletKitServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1686,6 +1729,13 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SignCoordinatorStreams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + return nil } @@ -2343,6 +2393,28 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SignCoordinatorStreams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SignCoordinatorStreams", runtime.WithHTTPPathPattern("/v2/wallet/remotesigner-stream")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_WalletKit_SignCoordinatorStreams_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_WalletKit_SignCoordinatorStreams_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -2402,6 +2474,8 @@ var ( pattern_WalletKit_SignPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "sign"}, "")) pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, "")) + + pattern_WalletKit_SignCoordinatorStreams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "remotesigner-stream"}, "")) ) var ( @@ -2460,4 +2534,6 @@ var ( forward_WalletKit_SignPsbt_0 = runtime.ForwardResponseMessage forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage + + forward_WalletKit_SignCoordinatorStreams_0 = runtime.ForwardResponseStream ) diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index b50d2d67825..aa431fe36fa 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -357,6 +357,217 @@ service WalletKit { unlock/release any locked UTXOs in case of an error in this method. */ rpc FinalizePsbt (FinalizePsbtRequest) returns (FinalizePsbtResponse); + + /* + SignCoordinatorStreams dispatches a bi-directional streaming RPC that + allows a remote signer to connect to lnd and remotely provide the signatures + required for any on-chain related transactions or messages. + */ + rpc SignCoordinatorStreams (stream SignCoordinatorResponse) + returns (stream SignCoordinatorRequest); +} + +message SignCoordinatorRequest { + /* + A unique request ID of a SignCoordinator gRPC request. Useful for mapping + requests to responses. + */ + uint64 request_id = 1; + + /* + Messages between the watch-only node and the remote signer can only be of + certain types. + */ + oneof sign_request_type { + /* + The Registration Response message is returned by the watch-only lnd as + a response to SignerRegistration message. + */ + RegistrationResponse registration_response = 2; + + /* + To ensure that the remote signer is still active and alive, the + watch-only lnd can send a Ping message to the remote signer, which + should then respond with the respective Pong message. + */ + bool ping = 3; + + /* + Requests a shared public key from the remote signer. + */ + signrpc.SharedKeyRequest shared_key_request = 4; + + /* + Requests that the remote signer signs the passed message. + */ + signrpc.SignMessageReq sign_message_req = 5; + + /* + Requests a MuSig2 Session of the remote signer. + */ + signrpc.MuSig2SessionRequest mu_sig2_session_request = 6; + + /* + Requests that the remote signer registers a nonce with the referenced + MuSig2 Session. + */ + signrpc.MuSig2RegisterNoncesRequest mu_sig2_register_nonces_request = 7; + + /* + Requests that the remote signer signs the passed message digest with + the referenced MuSig2 Session. + */ + signrpc.MuSig2SignRequest mu_sig2_sign_request = 8; + + /* + Requests that the remote signer combines and adds the passed partial + signatures for the referenced MuSig2 Session. + */ + signrpc.MuSig2CombineSigRequest mu_sig2_combine_sig_request = 9; + + /* + Requests that the remote signer removes/cleans up the referenced + MuSig2 session. + */ + signrpc.MuSig2CleanupRequest mu_sig2_cleanup_request = 10; + + /* + Requests that the remote signer signs the passed PSBT. + */ + SignPsbtRequest sign_psbt_request = 11; + } +} + +message SignCoordinatorResponse { + /* + The request ID this response refers to. + */ + uint64 ref_request_id = 1; + + /* + The remote signer responses can only be of certain types. + */ + oneof sign_response_type { + /* + The Signer Registration message is sent by the remote signer when it + connects to the watch-only lnd node, to initialize a handshake between + the nodes. + */ + SignerRegistration signer_registration = 2; + + /* + To ensure that the remote signer is still active and alive, the + watch-only node can send a Ping message to remote signer. This Pong + message should then be sent by the remote signer to respond to the Ping + message. + */ + bool pong = 3; + + /* + The remote signer's corresponding response to a Shared Key request. + */ + signrpc.SharedKeyResponse shared_key_response = 4; + + /* + The remote signer's corresponding response to a Sign Message request. + */ + signrpc.SignMessageResp sign_message_resp = 5; + + /* + The remote signer's corresponding response to a Mu Sig2 Session + request. + */ + signrpc.MuSig2SessionResponse mu_sig2_session_response = 6; + + /* + The remote signer's corresponding response to a Mu Sig2 Register Nonces + request. + */ + signrpc.MuSig2RegisterNoncesResponse mu_sig2_register_nonces_response = + 7; + + /* + The remote signer's corresponding response to a Mu Sig2 Sign request. + */ + signrpc.MuSig2SignResponse mu_sig2_sign_response = 8; + + /* + The remote signer's corresponding response to a Mu Sig2 Combine Sig + request. + */ + signrpc.MuSig2CombineSigResponse mu_sig2_combine_sig_response = 9; + + /* + The remote signer's corresponding response to a Mu Sig2 Cleanup + request. + */ + signrpc.MuSig2CleanupResponse mu_sig2_cleanup_response = 10; + + /* + The remote signer's corresponding response to a Sign Psbt request. + */ + SignPsbtResponse sign_psbt_response = 11; + + /* + If the remote signer encounters an error while processing a request, it + will respond with a SignerError message that details the error. + */ + SignerError signer_error = 12; + } +} + +message SignerError { + // Details an error which occurred on remote signer. + string error = 1; +} + +message SignerRegistration { + /* + The registration challenge allows the remote signer to pass data that will + be signed by the watch-only lnd. The resulting signature will be returned in + the RegistrationResponse message. + */ + string registration_challenge = 1; + + /* + The registration info contains details about the remote signer that may be + useful for the watch-only lnd. + */ + string registration_info = 2; +} + +message RegistrationResponse { + /* + The registration response indicates either a successful registration or an + error. + */ + oneof registration_response_type { + /* + Sent by the watch-only lnd when the remote signer registration is + successful. + */ + RegistrationComplete registration_complete = 1; + + /* + Contains details about any errors that occurred during remote signer + registration. + */ + string registration_error = 2; + } +} + +message RegistrationComplete { + /* + Holds the signature generated by the watch-only node when signing the + registration_challenge provided by the remote signer in SignerRegistration. + */ + string signature = 1; + + /* + Contains information about the watch-only lnd that may be useful for the + remote signer. + */ + string registration_info = 2; } message ListUnspentRequest { diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index 1e8e0741fd8..295e74d804a 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -539,6 +539,49 @@ ] } }, + "/v2/wallet/remotesigner-stream": { + "post": { + "summary": "SignCoordinatorStreams dispatches a bi-directional streaming RPC that\nallows a remote signer to connect to lnd and remotely provide the signatures\nrequired for any on-chain related transactions or messages.", + "operationId": "WalletKit_SignCoordinatorStreams", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/walletrpcSignCoordinatorRequest" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of walletrpcSignCoordinatorRequest" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": " (streaming inputs)", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/walletrpcSignCoordinatorResponse" + } + } + ], + "tags": [ + "WalletKit" + ] + } + }, "/v2/wallet/removetx": { "post": { "summary": "lncli: `wallet removetx`\nRemoveTransaction attempts to remove the provided transaction from the\ninternal transaction store of the wallet.", @@ -1270,6 +1313,298 @@ } } }, + "signrpcMuSig2CleanupRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session that should be removed/cleaned up." + } + } + }, + "signrpcMuSig2CleanupResponse": { + "type": "object" + }, + "signrpcMuSig2CombineSigRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session to combine the signatures for." + }, + "other_partial_signatures": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "The list of all other participants' partial signatures to add to the current\nsession." + } + } + }, + "signrpcMuSig2CombineSigResponse": { + "type": "object", + "properties": { + "have_all_signatures": { + "type": "boolean", + "description": "Indicates whether all partial signatures required to create a final, full\nsignature are known yet. If this is true, then the final_signature field is\nset, otherwise it is empty." + }, + "final_signature": { + "type": "string", + "format": "byte", + "description": "The final, full signature that is valid for the combined public key." + } + } + }, + "signrpcMuSig2RegisterNoncesRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session those nonces should be registered with." + }, + "other_signer_public_nonces": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "A list of all public nonces of other signing participants that should be\nregistered." + } + } + }, + "signrpcMuSig2RegisterNoncesResponse": { + "type": "object", + "properties": { + "have_all_nonces": { + "type": "boolean", + "description": "Indicates whether all nonces required to start the signing process are known\nnow." + } + } + }, + "signrpcMuSig2SessionRequest": { + "type": "object", + "properties": { + "key_loc": { + "$ref": "#/definitions/signrpcKeyLocator", + "description": "The key locator that identifies which key to use for signing." + }, + "all_signer_pubkeys": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "A list of all public keys (serialized in 32-byte x-only format for v0.4.0\nand 33-byte compressed format for v1.0.0rc2!) participating in the signing\nsession. The list will always be sorted lexicographically internally. This\nmust include the local key which is described by the above key_loc." + }, + "other_signer_public_nonces": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "An optional list of all public nonces of other signing participants that\nmight already be known." + }, + "tweaks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/signrpcTweakDesc" + }, + "description": "A series of optional generic tweaks to be applied to the aggregated\npublic key." + }, + "taproot_tweak": { + "$ref": "#/definitions/signrpcTaprootTweakDesc", + "description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain." + }, + "version": { + "$ref": "#/definitions/signrpcMuSig2Version", + "description": "The mandatory version of the MuSig2 BIP draft to use. This is necessary to\ndifferentiate between the changes that were made to the BIP while this\nexperimental RPC was already released. Some of those changes affect how the\ncombined key and nonces are created." + }, + "pregenerated_local_nonce": { + "type": "string", + "format": "byte", + "description": "A set of pre generated secret local nonces to use in the musig2 session.\nThis field is optional. This can be useful for protocols that need to send\nnonces ahead of time before the set of signer keys are known. This value\nMUST be 97 bytes and be the concatenation of two CSPRNG generated 32 byte\nvalues and local public key used for signing as specified in the key_loc\nfield." + } + } + }, + "signrpcMuSig2SessionResponse": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID that represents this signing session. A session can be used\nfor producing a signature a single time. If the signing fails for any\nreason, a new session with the same participants needs to be created." + }, + "combined_key": { + "type": "string", + "format": "byte", + "description": "The combined public key (in the 32-byte x-only format) with all tweaks\napplied to it. If a taproot tweak is specified, this corresponds to the\ntaproot key that can be put into the on-chain output." + }, + "taproot_internal_key": { + "type": "string", + "format": "byte", + "description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used." + }, + "local_public_nonces": { + "type": "string", + "format": "byte", + "description": "The two public nonces the local signer uses, combined into a single value\nof 66 bytes. Can be split into the two 33-byte points to get the individual\nnonces." + }, + "have_all_nonces": { + "type": "boolean", + "description": "Indicates whether all nonces required to start the signing process are known\nnow." + }, + "version": { + "$ref": "#/definitions/signrpcMuSig2Version", + "description": "The version of the MuSig2 BIP that was used to create the session." + } + } + }, + "signrpcMuSig2SignRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session to use for signing." + }, + "message_digest": { + "type": "string", + "format": "byte", + "description": "The 32-byte SHA256 digest of the message to sign." + }, + "cleanup": { + "type": "boolean", + "description": "Cleanup indicates that after signing, the session state can be cleaned up,\nsince another participant is going to be responsible for combining the\npartial signatures." + } + } + }, + "signrpcMuSig2SignResponse": { + "type": "object", + "properties": { + "local_partial_signature": { + "type": "string", + "format": "byte", + "description": "The partial signature created by the local signer." + } + } + }, + "signrpcMuSig2Version": { + "type": "string", + "enum": [ + "MUSIG2_VERSION_UNDEFINED", + "MUSIG2_VERSION_V040", + "MUSIG2_VERSION_V100RC2" + ], + "default": "MUSIG2_VERSION_UNDEFINED", + "description": " - MUSIG2_VERSION_UNDEFINED: The default value on the RPC is zero for enums so we need to represent an\ninvalid/undefined version by default to make sure clients upgrade their\nsoftware to set the version explicitly.\n - MUSIG2_VERSION_V040: The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the\nversion v0.4.0 of the MuSig2 BIP draft.\n - MUSIG2_VERSION_V100RC2: The current version of MuSig2 which corresponds to the version v1.0.0rc2 of\nthe MuSig2 BIP draft." + }, + "signrpcSharedKeyRequest": { + "type": "object", + "properties": { + "ephemeral_pubkey": { + "type": "string", + "format": "byte", + "description": "The ephemeral public key to use for the DH key derivation." + }, + "key_loc": { + "$ref": "#/definitions/signrpcKeyLocator", + "description": "Deprecated. The optional key locator of the local key that should be used.\nIf this parameter is not set then the node's identity private key will be\nused." + }, + "key_desc": { + "$ref": "#/definitions/signrpcKeyDescriptor", + "description": "A key descriptor describes the key used for performing ECDH. Either a key\nlocator or a raw public key is expected, if neither is supplied, defaults to\nthe node's identity private key." + } + } + }, + "signrpcSharedKeyResponse": { + "type": "object", + "properties": { + "shared_key": { + "type": "string", + "format": "byte", + "description": "The shared public key, hashed with sha256." + } + } + }, + "signrpcSignMessageReq": { + "type": "object", + "properties": { + "msg": { + "type": "string", + "format": "byte", + "description": "The message to be signed. When using REST, this field must be encoded as\nbase64." + }, + "key_loc": { + "$ref": "#/definitions/signrpcKeyLocator", + "description": "The key locator that identifies which key to use for signing." + }, + "double_hash": { + "type": "boolean", + "description": "Double-SHA256 hash instead of just the default single round." + }, + "compact_sig": { + "type": "boolean", + "description": "Use the compact (pubkey recoverable) format instead of the raw lnwire\nformat. This option cannot be used with Schnorr signatures." + }, + "schnorr_sig": { + "type": "boolean", + "description": "Use Schnorr signature. This option cannot be used with compact format." + }, + "schnorr_sig_tap_tweak": { + "type": "string", + "format": "byte", + "title": "The optional Taproot tweak bytes to apply to the private key before creating\na Schnorr signature. The private key is tweaked as described in BIP-341:\nprivKey + h_tapTweak(internalKey || tapTweak)" + }, + "tag": { + "type": "string", + "format": "byte", + "description": "An optional tag that can be provided when taking a tagged hash of a\nmessage. This option can only be used when schnorr_sig is true." + } + } + }, + "signrpcSignMessageResp": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "format": "byte", + "description": "The signature for the given message in the fixed-size LN wire format." + } + } + }, + "signrpcTaprootTweakDesc": { + "type": "object", + "properties": { + "script_root": { + "type": "string", + "format": "byte", + "description": "The root hash of the tapscript tree if a script path is committed to. If\nthe MuSig2 key put on chain doesn't also commit to a script path (BIP-0086\nkey spend only), then this needs to be empty and the key_spend_only field\nbelow must be set to true. This is required because gRPC cannot\ndifferentiate between a zero-size byte slice and a nil byte slice (both\nwould be serialized the same way). So the extra boolean is required." + }, + "key_spend_only": { + "type": "boolean", + "description": "Indicates that the above script_root is expected to be empty because this\nis a BIP-0086 key spend only commitment where only the internal key is\ncommitted to instead of also including a script root hash." + } + } + }, + "signrpcTweakDesc": { + "type": "object", + "properties": { + "tweak": { + "type": "string", + "format": "byte", + "description": "Tweak is the 32-byte value that will modify the public key." + }, + "is_x_only": { + "type": "boolean", + "description": "Specifies if the target key should be converted to an x-only public key\nbefore tweaking. If true, then the public key will be mapped to an x-only\nkey before the tweaking operation is applied." + } + } + }, "signrpcTxOut": { "type": "object", "properties": { @@ -2042,6 +2377,32 @@ } } }, + "walletrpcRegistrationComplete": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "Holds the signature generated by the watch-only node when signing the\nregistration_challenge provided by the remote signer in SignerRegistration." + }, + "registration_info": { + "type": "string", + "description": "Contains information about the watch-only lnd that may be useful for the\nremote signer." + } + } + }, + "walletrpcRegistrationResponse": { + "type": "object", + "properties": { + "registration_complete": { + "$ref": "#/definitions/walletrpcRegistrationComplete", + "description": "Sent by the watch-only lnd when the remote signer registration is\nsuccessful." + }, + "registration_error": { + "type": "string", + "description": "Contains details about any errors that occurred during remote signer\nregistration." + } + } + }, "walletrpcReleaseOutputRequest": { "type": "object", "properties": { @@ -2129,6 +2490,110 @@ } } }, + "walletrpcSignCoordinatorRequest": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uint64", + "description": "A unique request ID of a SignCoordinator gRPC request. Useful for mapping\nrequests to responses." + }, + "registration_response": { + "$ref": "#/definitions/walletrpcRegistrationResponse", + "description": "The Registration Response message is returned by the watch-only lnd as\na response to SignerRegistration message." + }, + "ping": { + "type": "boolean", + "description": "To ensure that the remote signer is still active and alive, the\nwatch-only lnd can send a Ping message to the remote signer, which\nshould then respond with the respective Pong message." + }, + "shared_key_request": { + "$ref": "#/definitions/signrpcSharedKeyRequest", + "description": "Requests a shared public key from the remote signer." + }, + "sign_message_req": { + "$ref": "#/definitions/signrpcSignMessageReq", + "description": "Requests that the remote signer signs the passed message." + }, + "mu_sig2_session_request": { + "$ref": "#/definitions/signrpcMuSig2SessionRequest", + "description": "Requests a MuSig2 Session of the remote signer." + }, + "mu_sig2_register_nonces_request": { + "$ref": "#/definitions/signrpcMuSig2RegisterNoncesRequest", + "description": "Requests that the remote signer registers a nonce with the referenced\nMuSig2 Session." + }, + "mu_sig2_sign_request": { + "$ref": "#/definitions/signrpcMuSig2SignRequest", + "description": "Requests that the remote signer signs the passed message digest with\nthe referenced MuSig2 Session." + }, + "mu_sig2_combine_sig_request": { + "$ref": "#/definitions/signrpcMuSig2CombineSigRequest", + "description": "Requests that the remote signer combines and adds the passed partial\nsignatures for the referenced MuSig2 Session." + }, + "mu_sig2_cleanup_request": { + "$ref": "#/definitions/signrpcMuSig2CleanupRequest", + "description": "Requests that the remote signer removes/cleans up the referenced\nMuSig2 session." + }, + "sign_psbt_request": { + "$ref": "#/definitions/walletrpcSignPsbtRequest", + "description": "Requests that the remote signer signs the passed PSBT." + } + } + }, + "walletrpcSignCoordinatorResponse": { + "type": "object", + "properties": { + "ref_request_id": { + "type": "string", + "format": "uint64", + "description": "The request ID this response refers to." + }, + "signer_registration": { + "$ref": "#/definitions/walletrpcSignerRegistration", + "description": "The Signer Registration message is sent by the remote signer when it\nconnects to the watch-only lnd node, to initialize a handshake between\nthe nodes." + }, + "pong": { + "type": "boolean", + "description": "To ensure that the remote signer is still active and alive, the\nwatch-only node can send a Ping message to remote signer. This Pong\nmessage should then be sent by the remote signer to respond to the Ping\nmessage." + }, + "shared_key_response": { + "$ref": "#/definitions/signrpcSharedKeyResponse", + "description": "The remote signer's corresponding response to a Shared Key request." + }, + "sign_message_resp": { + "$ref": "#/definitions/signrpcSignMessageResp", + "description": "The remote signer's corresponding response to a Sign Message request." + }, + "mu_sig2_session_response": { + "$ref": "#/definitions/signrpcMuSig2SessionResponse", + "description": "The remote signer's corresponding response to a Mu Sig2 Session\nrequest." + }, + "mu_sig2_register_nonces_response": { + "$ref": "#/definitions/signrpcMuSig2RegisterNoncesResponse", + "description": "The remote signer's corresponding response to a Mu Sig2 Register Nonces\nrequest." + }, + "mu_sig2_sign_response": { + "$ref": "#/definitions/signrpcMuSig2SignResponse", + "description": "The remote signer's corresponding response to a Mu Sig2 Sign request." + }, + "mu_sig2_combine_sig_response": { + "$ref": "#/definitions/signrpcMuSig2CombineSigResponse", + "description": "The remote signer's corresponding response to a Mu Sig2 Combine Sig\nrequest." + }, + "mu_sig2_cleanup_response": { + "$ref": "#/definitions/signrpcMuSig2CleanupResponse", + "description": "The remote signer's corresponding response to a Mu Sig2 Cleanup\nrequest." + }, + "sign_psbt_response": { + "$ref": "#/definitions/walletrpcSignPsbtResponse", + "description": "The remote signer's corresponding response to a Sign Psbt request." + }, + "signer_error": { + "$ref": "#/definitions/walletrpcSignerError", + "description": "If the remote signer encounters an error while processing a request, it\nwill respond with a SignerError message that details the error." + } + } + }, "walletrpcSignMessageWithAddrRequest": { "type": "object", "properties": { @@ -2180,6 +2645,28 @@ } } }, + "walletrpcSignerError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Details an error which occurred on remote signer." + } + } + }, + "walletrpcSignerRegistration": { + "type": "object", + "properties": { + "registration_challenge": { + "type": "string", + "description": "The registration challenge allows the remote signer to pass data that will\nbe signed by the watch-only lnd. The resulting signature will be returned in\nthe RegistrationResponse message." + }, + "registration_info": { + "type": "string", + "description": "The registration info contains details about the remote signer that may be\nuseful for the watch-only lnd." + } + } + }, "walletrpcTapLeaf": { "type": "object", "properties": { diff --git a/lnrpc/walletrpc/walletkit.yaml b/lnrpc/walletrpc/walletkit.yaml index b912fea23b9..021c531547a 100644 --- a/lnrpc/walletrpc/walletkit.yaml +++ b/lnrpc/walletrpc/walletkit.yaml @@ -79,3 +79,6 @@ http: - selector: walletrpc.WalletKit.BumpForceCloseFee post: "/v2/wallet/BumpForceCloseFee" body: "*" + - selector: walletrpc.WalletKit.SignCoordinatorStreams + post: "/v2/wallet/remotesigner-stream" + body: "*" diff --git a/lnrpc/walletrpc/walletkit_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go index 579aa47bb3b..035987265f5 100644 --- a/lnrpc/walletrpc/walletkit_grpc.pb.go +++ b/lnrpc/walletrpc/walletkit_grpc.pb.go @@ -279,6 +279,10 @@ type WalletKitClient interface { // caller's responsibility to either publish the transaction on success or // unlock/release any locked UTXOs in case of an error in this method. FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) + // SignCoordinatorStreams dispatches a bi-directional streaming RPC that + // allows a remote signer to connect to lnd and remotely provide the signatures + // required for any on-chain related transactions or messages. + SignCoordinatorStreams(ctx context.Context, opts ...grpc.CallOption) (WalletKit_SignCoordinatorStreamsClient, error) } type walletKitClient struct { @@ -541,6 +545,37 @@ func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequ return out, nil } +func (c *walletKitClient) SignCoordinatorStreams(ctx context.Context, opts ...grpc.CallOption) (WalletKit_SignCoordinatorStreamsClient, error) { + stream, err := c.cc.NewStream(ctx, &WalletKit_ServiceDesc.Streams[0], "/walletrpc.WalletKit/SignCoordinatorStreams", opts...) + if err != nil { + return nil, err + } + x := &walletKitSignCoordinatorStreamsClient{stream} + return x, nil +} + +type WalletKit_SignCoordinatorStreamsClient interface { + Send(*SignCoordinatorResponse) error + Recv() (*SignCoordinatorRequest, error) + grpc.ClientStream +} + +type walletKitSignCoordinatorStreamsClient struct { + grpc.ClientStream +} + +func (x *walletKitSignCoordinatorStreamsClient) Send(m *SignCoordinatorResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *walletKitSignCoordinatorStreamsClient) Recv() (*SignCoordinatorRequest, error) { + m := new(SignCoordinatorRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // WalletKitServer is the server API for WalletKit service. // All implementations must embed UnimplementedWalletKitServer // for forward compatibility @@ -804,6 +839,10 @@ type WalletKitServer interface { // caller's responsibility to either publish the transaction on success or // unlock/release any locked UTXOs in case of an error in this method. FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) + // SignCoordinatorStreams dispatches a bi-directional streaming RPC that + // allows a remote signer to connect to lnd and remotely provide the signatures + // required for any on-chain related transactions or messages. + SignCoordinatorStreams(WalletKit_SignCoordinatorStreamsServer) error mustEmbedUnimplementedWalletKitServer() } @@ -895,6 +934,9 @@ func (UnimplementedWalletKitServer) SignPsbt(context.Context, *SignPsbtRequest) func (UnimplementedWalletKitServer) FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented") } +func (UnimplementedWalletKitServer) SignCoordinatorStreams(WalletKit_SignCoordinatorStreamsServer) error { + return status.Errorf(codes.Unimplemented, "method SignCoordinatorStreams not implemented") +} func (UnimplementedWalletKitServer) mustEmbedUnimplementedWalletKitServer() {} // UnsafeWalletKitServer may be embedded to opt out of forward compatibility for this service. @@ -1412,6 +1454,32 @@ func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _WalletKit_SignCoordinatorStreams_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(WalletKitServer).SignCoordinatorStreams(&walletKitSignCoordinatorStreamsServer{stream}) +} + +type WalletKit_SignCoordinatorStreamsServer interface { + Send(*SignCoordinatorRequest) error + Recv() (*SignCoordinatorResponse, error) + grpc.ServerStream +} + +type walletKitSignCoordinatorStreamsServer struct { + grpc.ServerStream +} + +func (x *walletKitSignCoordinatorStreamsServer) Send(m *SignCoordinatorRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *walletKitSignCoordinatorStreamsServer) Recv() (*SignCoordinatorResponse, error) { + m := new(SignCoordinatorResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // WalletKit_ServiceDesc is the grpc.ServiceDesc for WalletKit service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1532,6 +1600,13 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{ Handler: _WalletKit_FinalizePsbt_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "SignCoordinatorStreams", + Handler: _WalletKit_SignCoordinatorStreams_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, Metadata: "walletrpc/walletkit.proto", } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 9efc730a4a8..ae6a094ea81 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -185,6 +185,10 @@ var ( Entity: "onchain", Action: "write", }}, + "/walletrpc.WalletKit/SignCoordinatorStreams": {{ + Entity: "remotesigner", + Action: "generate", + }}, } // DefaultWalletKitMacFilename is the default name of the wallet kit @@ -453,6 +457,14 @@ func (w *WalletKit) ListUnspent(ctx context.Context, }, nil } +// SignCoordinatorStreams opens a bi-directional streaming RPC, which is used +// to allow a remote signer to process sign requests on behalf of the wallet. +func (w *WalletKit) SignCoordinatorStreams( + stream WalletKit_SignCoordinatorStreamsServer) error { + + return fmt.Errorf("Unimplemented") +} + // LeaseOutput locks an output to the given ID, preventing it from being // available for any future coin selection attempts. The absolute time of the // lock's expiration is returned. The expiration of the lock can be extended by From ec09ade68bc132782b76df942af752eb0cdde856 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:42:09 +0200 Subject: [PATCH 05/39] rpcwallet: add `RemoteSignerConnection` interface RemoteSignerConnection is an interface that abstracts the communication with a remote signer. It extends the RemoteSignerRequests interface, and adds som additional methods to manage the connection and verify the health of the remote signer. As we'll add an outbound remote signer implementation in upcoming commits, we need this interface to abstract the commonalities of both the inbound (the current remote signer implementation) and outbound remote signer implementations, so that the RPCKeyRing doesn't need to know which type it's using. --- .../rpcwallet/remote_signer_connection.go | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 lnwallet/rpcwallet/remote_signer_connection.go diff --git a/lnwallet/rpcwallet/remote_signer_connection.go b/lnwallet/rpcwallet/remote_signer_connection.go new file mode 100644 index 00000000000..e934f78349f --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_connection.go @@ -0,0 +1,92 @@ +package rpcwallet + +import ( + "context" + "time" + + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "google.golang.org/grpc" +) + +type ( + StreamClient = walletrpc.WalletKit_SignCoordinatorStreamsClient + StreamServer = walletrpc.WalletKit_SignCoordinatorStreamsServer +) + +// RemoteSignerConnection is an interface that abstracts the communication with +// a remote signer. It extends the RemoteSignerRequests interface, and adds some +// additional methods to manage the connection and verify the health of the +// remote signer. +type RemoteSignerConnection interface { + // RemoteSignerRequests is an interface that defines the requests that + // can be sent to a remote signer. + RemoteSignerRequests + + // Timeout returns the set connection timeout for the remote signer. + Timeout() time.Duration + + // Ready returns a channel that nil gets sent over once the remote + // signer is ready to accept requests. + Ready(ctx context.Context) chan error + + // Stop gracefully disconnects from the remote signer. + Stop() + + // Ping verifies that the remote signer is still responsive. + Ping(ctx context.Context, timeout time.Duration) error +} + +// RemoteSignerRequests is an interface that defines the requests that can be +// sent to a remote signer. It's a subset of the signrpc.SignerClient and +// walletrpc.WalletKitClient interfaces. +type RemoteSignerRequests interface { + // DeriveSharedKey sends a SharedKeyRequest to the remote signer and + // waits for the corresponding response. + DeriveSharedKey(ctx context.Context, + in *signrpc.SharedKeyRequest, + opts ...grpc.CallOption) (*signrpc.SharedKeyResponse, error) + + // MuSig2Cleanup sends a MuSig2CleanupRequest to the remote signer and + // waits for the corresponding response. + MuSig2Cleanup(ctx context.Context, + in *signrpc.MuSig2CleanupRequest, + opts ...grpc.CallOption) (*signrpc.MuSig2CleanupResponse, error) + + // MuSig2CombineSig sends a MuSig2CombineSigRequest to the remote signer + // and waits for the corresponding response. + MuSig2CombineSig(ctx context.Context, + in *signrpc.MuSig2CombineSigRequest, + opts ...grpc.CallOption) (*signrpc.MuSig2CombineSigResponse, + error) + + // MuSig2CreateSession sends a MuSig2SessionRequest to the remote signer + // and waits for the corresponding response. + MuSig2CreateSession(ctx context.Context, + in *signrpc.MuSig2SessionRequest, + opts ...grpc.CallOption) (*signrpc.MuSig2SessionResponse, error) + + // MuSig2RegisterNonces sends a MuSig2RegisterNoncesRequest to the + // remote signer and waits for the corresponding response. + MuSig2RegisterNonces(ctx context.Context, + in *signrpc.MuSig2RegisterNoncesRequest, + opts ...grpc.CallOption) (*signrpc.MuSig2RegisterNoncesResponse, + error) + + // MuSig2Sign sends a MuSig2SignRequest to the remote signer and waits + // for the corresponding response. + MuSig2Sign(ctx context.Context, + in *signrpc.MuSig2SignRequest, + opts ...grpc.CallOption) (*signrpc.MuSig2SignResponse, error) + + // SignMessage sends a SignMessageReq to the remote signer and waits for + // the corresponding response. + SignMessage(ctx context.Context, + in *signrpc.SignMessageReq, + opts ...grpc.CallOption) (*signrpc.SignMessageResp, error) + + // SignPsbt sends a SignPsbtRequest to the remote signer and waits for + // the corresponding response. + SignPsbt(ctx context.Context, in *walletrpc.SignPsbtRequest, + opts ...grpc.CallOption) (*walletrpc.SignPsbtResponse, error) +} From ad8b0541de5b664363be454de4b4bf8dc2c1a992 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:43:19 +0200 Subject: [PATCH 06/39] rpcwallet: add `OutboundConnection` implementation This commit wraps the connection to the current remote signer implementation in the new `RemoteSignerConnection` interface within an `OutboundConnection` struct. The name `OutboundConnection` is chosen to reflect the fact that the watch-only node makes an outbound connection to the inbound remote signer in the current implementation, and this connection instance is used on the watch-only node's side. --- .../rpcwallet/remote_signer_connection.go | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/lnwallet/rpcwallet/remote_signer_connection.go b/lnwallet/rpcwallet/remote_signer_connection.go index e934f78349f..6f96c52cc9c 100644 --- a/lnwallet/rpcwallet/remote_signer_connection.go +++ b/lnwallet/rpcwallet/remote_signer_connection.go @@ -2,11 +2,21 @@ package rpcwallet import ( "context" + "crypto/x509" + "errors" + "fmt" + "os" "time" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon.v2" ) type ( @@ -90,3 +100,165 @@ type RemoteSignerRequests interface { SignPsbt(ctx context.Context, in *walletrpc.SignPsbtRequest, opts ...grpc.CallOption) (*walletrpc.SignPsbtResponse, error) } + +// OutboundConnection is an abstraction of the outbound connection made to an +// inbound remote signer. An inbound remote signer is a remote signer that +// allows the watch-only node to connect to it via an inbound GRPC connection. +type OutboundConnection struct { + // Embedded signrpc.SignerClient and walletrpc.WalletKitClient to + // implement the RemoteSigner interface. + signrpc.SignerClient + walletrpc.WalletKitClient + + // The ConnectionCfg containing connection details of the remote signer. + cfg lncfg.ConnectionCfg + + // conn represents the connection to the remote signer. + conn *grpc.ClientConn +} + +// NewOutboundConnection creates a new OutboundConnection instance. +// The function sets up a connection to the remote signer node. +func NewOutboundConnection(ctx context.Context, + cfg lncfg.ConnectionCfg) (*OutboundConnection, error) { + + remoteSigner := &OutboundConnection{ + cfg: cfg, + } + + err := remoteSigner.connect(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("error connecting to the remote "+ + "signing node through RPC: %v", err) + } + + return remoteSigner, nil +} + +// Ready returns a channel that nil gets sent over once the connection to the +// remote signer is set up and the remote signer is ready to accept requests. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *OutboundConnection) Ready(_ context.Context) chan error { + // The inbound remote signer is ready as soon we have connected to the + // remote signer node in the constructor. Therefore, we always send + // nil here to signal that we are ready. + readyChan := make(chan error, 1) + readyChan <- nil + + return readyChan +} + +// Ping verifies that the remote signer is still responsive. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *OutboundConnection) Ping(ctx context.Context, _ time.Duration) error { + if r.conn == nil || r.conn.GetState() != connectivity.Ready { + return errors.New("remote signer is not connected") + } + + pingMsg := []byte("ping test") + + // To simulate a ping, we send a sign message request to the remote + // signer to check that it is responding. Note that we do not care what + // sign response we actually get from the remote signer, as long as it's + // actually responding. Therefore, we request that the signer signs + // with a "random" key, in this case the family node key & index 1 + // to not use any channel keys for the ping request. + keyLoc := &signrpc.KeyLocator{ + KeyFamily: int32(keychain.KeyFamilyNodeKey), + KeyIndex: 1, + } + + // Sign a message with the default ECDSA. + signMsgReq := &signrpc.SignMessageReq{ + Msg: pingMsg, + KeyLoc: keyLoc, + SchnorrSig: false, + } + + _, err := r.SignMessage(ctx, signMsgReq) + + return err +} + +// Timeout returns the set connection timeout for the remote signer. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *OutboundConnection) Timeout() time.Duration { + return r.cfg.Timeout +} + +// Stop closes the connection to the remote signer. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *OutboundConnection) Stop() { + if r.conn != nil { + r.conn.Close() + } +} + +// connect tries to establish an RPC connection to the configured host:port with +// the supplied certificate and macaroon. +func (r *OutboundConnection) connect(ctx context.Context, + cfg lncfg.ConnectionCfg) error { + + certBytes, err := os.ReadFile(cfg.TLSCertPath) + if err != nil { + return fmt.Errorf("error reading TLS cert file %v: %w", + cfg.TLSCertPath, err) + } + + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(certBytes) { + return fmt.Errorf("credentials: failed to append certificate") + } + + macBytes, err := os.ReadFile(cfg.MacaroonPath) + if err != nil { + return fmt.Errorf("error reading macaroon file %v: %w", + cfg.MacaroonPath, err) + } + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return fmt.Errorf("error decoding macaroon: %w", err) + } + + macCred, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return fmt.Errorf("error creating creds: %w", err) + } + + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert( + cp, "", + )), + grpc.WithPerRPCCredentials(macCred), + grpc.WithBlock(), + } + + ctxt, cancel := context.WithTimeout(ctx, cfg.Timeout) + + // In the blocking case, ctx can be used to cancel or expire the pending + // connection. Once this function returns, the cancellation and + // expiration of ctx will be noop. Users should call ClientConn.Close to + // terminate all the pending operations after this function returns. + defer cancel() + + conn, err := grpc.DialContext(ctxt, cfg.RPCHost, opts...) + if err != nil { + return fmt.Errorf("unable to connect to RPC server: %w", err) + } + + // If we were able to connect to the remote signer, we store the + // connection in the OutboundConnection struct. + r.conn = conn + r.SignerClient = signrpc.NewSignerClient(conn) + r.WalletKitClient = walletrpc.NewWalletKitClient(conn) + + return nil +} + +// A compile time assertion to ensure OutboundConnection meets the +// RemoteSignerConnection interface. +var _ RemoteSignerConnection = (*OutboundConnection)(nil) From 8c1f6f23b96303431e11f2912e7134ee4b5f5815 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 16:15:00 +0200 Subject: [PATCH 07/39] rpcwallet: add `RemoteSignerConnectionBuilder` `RemoteSignerConnectionBuilder` is a helper that creates instances of the RemoteSigner connection interface based on the `lncfg.RemoteSigner` config. --- .../remote_signer_connection_builder.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lnwallet/rpcwallet/remote_signer_connection_builder.go diff --git a/lnwallet/rpcwallet/remote_signer_connection_builder.go b/lnwallet/rpcwallet/remote_signer_connection_builder.go new file mode 100644 index 00000000000..4f5e66e992d --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_connection_builder.go @@ -0,0 +1,41 @@ +package rpcwallet + +import ( + "context" + "errors" + + "github.com/lightningnetwork/lnd/lncfg" +) + +// RemoteSignerConnectionBuilder creates instances of the RemoteSignerConnection +// interface, based on the provided configuration. +type RemoteSignerConnectionBuilder struct { + cfg *lncfg.RemoteSigner +} + +// NewRemoteSignerConnectionBuilder creates a new instance of the +// RemoteSignerBuilder. +func NewRemoteSignerConnectionBuilder( + cfg *lncfg.RemoteSigner) *RemoteSignerConnectionBuilder { + + return &RemoteSignerConnectionBuilder{cfg} +} + +// Build creates a new RemoteSignerConnection instance. If the configuration +// specifies that an inbound remote signer should be used, a new +// OutboundConnection is created. If the configuration specifies that an +// outbound remote signer should be used, a new InboundConnection is created. +// The function returns the created RemoteSignerConnection instance, and a +// cleanup function that should be called when the RemoteSignerConnection is no +// longer needed. +func (b *RemoteSignerConnectionBuilder) Build( + ctx context.Context) (RemoteSignerConnection, error) { + + if !b.cfg.Enable { + // This should be unreachable, but this is an extra sanity check + return nil, errors.New("remote signer not enabled in " + + "config") + } + + return NewOutboundConnection(ctx, b.cfg.ConnectionCfg) +} From 4a3b89d3483c50963683f6edf1463baaa743bc12 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 16:23:32 +0200 Subject: [PATCH 08/39] rpcwallet: use `RemoteSignerConnection` in RPCKeyRing As the `RemoteSignerConnectionBuilder` can now create an `OutboundConnection` that matches the functionality of the previous remote signer communication implementation, we refactor the `rpcwallet` package to use a `RemoteSignerConnection` instance created by the `RemoteSignerConnectionBuilder`. --- config_builder.go | 42 +++++++++++-- lnwallet/rpcwallet/rpcwallet.go | 102 ++++++++++++++++---------------- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/config_builder.go b/config_builder.go index 7ce63041ee2..1222e239db9 100644 --- a/config_builder.go +++ b/config_builder.go @@ -867,28 +867,57 @@ func (d *RPCSignerWalletImpl) BuildChainControl( partialChainControl *chainreg.PartialChainControl, walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) { + // Keeps track of both the remote signer and the chain control clean up + // functions. + var ( + cleanUpTasks []func() + cleanUp = func() { + for i := len(cleanUpTasks) - 1; i >= 0; i-- { + cleanUpTasks[i]() + } + } + ) + walletController, err := btcwallet.New( *walletConfig, partialChainControl.Cfg.BlockCache, ) if err != nil { err := fmt.Errorf("unable to create wallet controller: %w", err) d.logger.Error(err) - return nil, nil, err + return nil, cleanUp, err } + remoteSignerConnBuilder := rpcwallet.NewRemoteSignerConnectionBuilder( + d.DefaultWalletImpl.cfg.RemoteSigner, + ) + + // Create the remote signer connection instance. + remoteSignerConn, err := remoteSignerConnBuilder.Build( + context.Background(), + ) + if err != nil { + err := fmt.Errorf("unable to set up remote signer: %w", err) + d.logger.Error(err) + + return nil, cleanUp, err + } + + cleanUpTasks = append(cleanUpTasks, remoteSignerConn.Stop) + baseKeyRing := keychain.NewBtcWalletKeyRing( walletController.InternalWallet(), walletConfig.CoinType, ) rpcKeyRing, err := rpcwallet.NewRPCKeyRing( baseKeyRing, walletController, - d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams, + remoteSignerConn, walletConfig.NetParams, ) if err != nil { err := fmt.Errorf("unable to create RPC remote signing wallet "+ "%v", err) d.logger.Error(err) - return nil, nil, err + + return nil, cleanUp, err } // Create, and start the lnwallet, which handles the core payment @@ -907,15 +936,18 @@ func (d *RPCSignerWalletImpl) BuildChainControl( // We've created the wallet configuration now, so we can finish // initializing the main chain control. - activeChainControl, cleanUp, err := chainreg.NewChainControl( + activeChainControl, ccCleanUp, err := chainreg.NewChainControl( lnWalletConfig, rpcKeyRing, partialChainControl, ) if err != nil { err := fmt.Errorf("unable to create chain control: %w", err) d.logger.Error(err) - return nil, nil, err + + return nil, cleanUp, err } + cleanUpTasks = append(cleanUpTasks, ccCleanUp) + return activeChainControl, cleanUp, nil } diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index f7062c1d36e..46eb696e3e0 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -25,7 +25,6 @@ import ( "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -63,8 +62,7 @@ type RPCKeyRing struct { rpcTimeout time.Duration - signerClient signrpc.SignerClient - walletClient walletrpc.WalletKitClient + remoteSignerConn RemoteSignerConnection } var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil) @@ -77,25 +75,15 @@ var _ lnwallet.WalletController = (*RPCKeyRing)(nil) // delegates any signing or ECDH operations to the remove signer through RPC. func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, watchOnlyWalletController lnwallet.WalletController, - remoteSigner *lncfg.RemoteSigner, + remoteSignerConn RemoteSignerConnection, netParams *chaincfg.Params) (*RPCKeyRing, error) { - rpcConn, err := connectRPC( - remoteSigner.RPCHost, remoteSigner.TLSCertPath, - remoteSigner.MacaroonPath, remoteSigner.Timeout, - ) - if err != nil { - return nil, fmt.Errorf("error connecting to the remote "+ - "signing node through RPC: %v", err) - } - return &RPCKeyRing{ WalletController: watchOnlyWalletController, watchOnlyKeyRing: watchOnlyKeyRing, netParams: netParams, - rpcTimeout: remoteSigner.Timeout, - signerClient: signrpc.NewSignerClient(rpcConn), - walletClient: walletrpc.NewWalletKitClient(rpcConn), + rpcTimeout: remoteSignerConn.Timeout(), + remoteSignerConn: remoteSignerConn, }, nil } @@ -206,9 +194,9 @@ func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) ([]uint32, error) { return nil, fmt.Errorf("error serializing PSBT: %w", err) } - resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{ - FundedPsbt: buf.Bytes(), - }) + resp, err := r.remoteSignerConn.SignPsbt(ctxt, + &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}, + ) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing PSBT in remote signer "+ @@ -419,7 +407,7 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed() } - resp, err := r.signerClient.DeriveSharedKey(ctxt, req) + resp, err := r.remoteSignerConn.DeriveSharedKey(ctxt, req) if err != nil { considerShutdown(err) return key, fmt.Errorf("error deriving shared key in remote "+ @@ -442,14 +430,16 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{ - Msg: msg, - KeyLoc: &signrpc.KeyLocator{ - KeyFamily: int32(keyLoc.Family), - KeyIndex: int32(keyLoc.Index), + resp, err := r.remoteSignerConn.SignMessage(ctxt, + &signrpc.SignMessageReq{ + Msg: msg, + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + DoubleHash: doubleHash, }, - DoubleHash: doubleHash, - }) + ) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing message in remote "+ @@ -488,15 +478,17 @@ func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{ - Msg: msg, - KeyLoc: &signrpc.KeyLocator{ - KeyFamily: int32(keyLoc.Family), - KeyIndex: int32(keyLoc.Index), + resp, err := r.remoteSignerConn.SignMessage(ctxt, + &signrpc.SignMessageReq{ + Msg: msg, + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + DoubleHash: doubleHash, + CompactSig: true, }, - DoubleHash: doubleHash, - CompactSig: true, - }) + ) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing message in remote "+ @@ -521,17 +513,19 @@ func (r *RPCKeyRing) SignMessageSchnorr(keyLoc keychain.KeyLocator, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{ - Msg: msg, - KeyLoc: &signrpc.KeyLocator{ - KeyFamily: int32(keyLoc.Family), - KeyIndex: int32(keyLoc.Index), + resp, err := r.remoteSignerConn.SignMessage(ctxt, + &signrpc.SignMessageReq{ + Msg: msg, + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + DoubleHash: doubleHash, + SchnorrSig: true, + SchnorrSigTapTweak: taprootTweak, + Tag: tag, }, - DoubleHash: doubleHash, - SchnorrSig: true, - SchnorrSigTapTweak: taprootTweak, - Tag: tag, - }) + ) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing message in remote "+ @@ -716,7 +710,7 @@ func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.MuSig2CreateSession(ctxt, req) + resp, err := r.remoteSignerConn.MuSig2CreateSession(ctxt, req) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error creating MuSig2 session in "+ @@ -770,7 +764,7 @@ func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req) + resp, err := r.remoteSignerConn.MuSig2RegisterNonces(ctxt, req) if err != nil { considerShutdown(err) return false, fmt.Errorf("error registering MuSig2 nonces in "+ @@ -854,7 +848,7 @@ func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.MuSig2Sign(ctxt, req) + resp, err := r.remoteSignerConn.MuSig2Sign(ctxt, req) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing MuSig2 session in "+ @@ -898,7 +892,7 @@ func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID, ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - resp, err := r.signerClient.MuSig2CombineSig(ctxt, req) + resp, err := r.remoteSignerConn.MuSig2CombineSig(ctxt, req) if err != nil { considerShutdown(err) return nil, false, fmt.Errorf("error combining MuSig2 "+ @@ -920,6 +914,12 @@ func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID, return finalSig, resp.HaveAllSignatures, nil } +// RemoteSignerConnection returns the remote signer connection instance that is +// used by the RPC key ring to sign transactions. +func (r *RPCKeyRing) RemoteSignerConnection() RemoteSignerConnection { + return r.remoteSignerConn +} + // MuSig2Cleanup removes a session from memory to free up resources. func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { req := &signrpc.MuSig2CleanupRequest{ @@ -929,7 +929,7 @@ func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() - _, err := r.signerClient.MuSig2Cleanup(ctxt, req) + _, err := r.remoteSignerConn.MuSig2Cleanup(ctxt, req) if err != nil { considerShutdown(err) return fmt.Errorf("error cleaning up MuSig2 session in remote "+ @@ -1228,7 +1228,7 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor, return nil, fmt.Errorf("error serializing PSBT: %w", err) } - resp, err := r.walletClient.SignPsbt( + resp, err := r.remoteSignerConn.SignPsbt( ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}, ) if err != nil { From d85a40fd7cd04bc3569886f0f3ace4abddabef98 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 17 Dec 2024 01:06:56 +0100 Subject: [PATCH 09/39] lnd: refactor `createLivenessMonitor` signature In preparation for using the `RemoteSignerConnection` interface for health checks, we refactor the `createLivenessMonitor` function to take a passed context param and to return an error. --- server.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 4e8ad037c5f..291fa3b2e86 100644 --- a/server.go +++ b/server.go @@ -1789,7 +1789,10 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, } // Create liveness monitor. - s.createLivenessMonitor(cfg, cc, leaderElector) + err = s.createLivenessMonitor(ctx, cfg, cc, leaderElector) + if err != nil { + return nil, err + } listeners := make([]net.Listener, len(listenAddrs)) for i, listenAddr := range listenAddrs { @@ -1906,8 +1909,8 @@ func (s *server) signAliasUpdate(u *lnwire.ChannelUpdate1) (*ecdsa.Signature, // // If a health check has been disabled by setting attempts to 0, our monitor // will not run it. -func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl, - leaderElector cluster.LeaderElector) { +func (s *server) createLivenessMonitor(ctx context.Context, cfg *Config, + cc *chainreg.ChainControl, leaderElector cluster.LeaderElector) error { chainBackendAttempts := cfg.HealthChecks.ChainCheck.Attempts if cfg.Bitcoin.Node == "nochainbackend" { @@ -2041,7 +2044,7 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl, // as the healthcheck observer will handle the // timeout case for us. timeoutCtx, cancel := context.WithTimeout( - context.Background(), + ctx, cfg.HealthChecks.LeaderCheck.Timeout, ) defer cancel() @@ -2079,6 +2082,8 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl, Shutdown: srvrLog.Criticalf, }, ) + + return nil } // Started returns true if the server has been started, and false otherwise. From 714921baea022701684561f7a841a173320d7605 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:30:04 +0200 Subject: [PATCH 10/39] multi: use `RemoteSignerConnection` for health check With the `RPCKeyRing` now having the `RemoteSignerConnection` reference, we can use that reference to call the `Ping` implementation of the `RemoteSignerConnection` interface for the health check of the remote signer. This allows different types of remote signers to specify their own implementation to verify if the remote signer is active. --- lnwallet/rpcwallet/healthcheck.go | 29 ++++++---------- lnwallet/rpcwallet/rpcwallet.go | 56 ------------------------------- server.go | 26 +++++++++----- 3 files changed, 28 insertions(+), 83 deletions(-) diff --git a/lnwallet/rpcwallet/healthcheck.go b/lnwallet/rpcwallet/healthcheck.go index 7a412959e03..00a9a729e17 100644 --- a/lnwallet/rpcwallet/healthcheck.go +++ b/lnwallet/rpcwallet/healthcheck.go @@ -1,32 +1,25 @@ package rpcwallet import ( - "fmt" + "context" "time" - - "github.com/lightningnetwork/lnd/lncfg" ) // HealthCheck returns a health check function for the given remote signing // configuration. -func HealthCheck(cfg *lncfg.RemoteSigner, timeout time.Duration) func() error { +func HealthCheck(ctx context.Context, rs RemoteSignerConnection, + timeout time.Duration) func() error { + return func() error { - conn, err := connectRPC( - cfg.RPCHost, cfg.TLSCertPath, cfg.MacaroonPath, timeout, - ) + ctxt, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + err := rs.Ping(ctxt, timeout) if err != nil { - return fmt.Errorf("error connecting to the remote "+ - "signing node through RPC: %v", err) - } + log.Errorf("Remote signer health check failed: %v", err) - defer func() { - err = conn.Close() - if err != nil { - log.Warnf("Failed to close health check "+ - "connection to remote signing node: %v", - err) - } - }() + return err + } return nil } diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 46eb696e3e0..937ad09a446 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -4,10 +4,8 @@ import ( "bytes" "context" "crypto/sha256" - "crypto/x509" "errors" "fmt" - "os" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -31,12 +29,8 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/macaroons" - "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" - "gopkg.in/macaroon.v2" ) var ( @@ -1322,56 +1316,6 @@ func extractSignature(in *psbt.PInput, } } -// connectRPC tries to establish an RPC connection to the given host:port with -// the supplied certificate and macaroon. -func connectRPC(hostPort, tlsCertPath, macaroonPath string, - timeout time.Duration) (*grpc.ClientConn, error) { - - certBytes, err := os.ReadFile(tlsCertPath) - if err != nil { - return nil, fmt.Errorf("error reading TLS cert file %v: %w", - tlsCertPath, err) - } - - cp := x509.NewCertPool() - if !cp.AppendCertsFromPEM(certBytes) { - return nil, fmt.Errorf("credentials: failed to append " + - "certificate") - } - - macBytes, err := os.ReadFile(macaroonPath) - if err != nil { - return nil, fmt.Errorf("error reading macaroon file %v: %w", - macaroonPath, err) - } - mac := &macaroon.Macaroon{} - if err := mac.UnmarshalBinary(macBytes); err != nil { - return nil, fmt.Errorf("error decoding macaroon: %w", err) - } - - macCred, err := macaroons.NewMacaroonCredential(mac) - if err != nil { - return nil, fmt.Errorf("error creating creds: %w", err) - } - - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(credentials.NewClientTLSFromCert( - cp, "", - )), - grpc.WithPerRPCCredentials(macCred), - grpc.WithBlock(), - } - ctxt, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - conn, err := grpc.DialContext(ctxt, hostPort, opts...) - if err != nil { - return nil, fmt.Errorf("unable to connect to RPC server: %w", - err) - } - - return conn, nil -} - // packetFromTx creates a PSBT from a tx that potentially already contains // signed inputs. func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) { diff --git a/server.go b/server.go index 291fa3b2e86..7e57b7763af 100644 --- a/server.go +++ b/server.go @@ -2007,28 +2007,36 @@ func (s *server) createLivenessMonitor(ctx context.Context, cfg *Config, // If remote signing is enabled, add the healthcheck for the remote // signing RPC interface. - if s.cfg.RemoteSigner != nil && s.cfg.RemoteSigner.Enable { + if s.cfg.RemoteSigner.Enable { + rpckKeyRing, ok := cc.Wc.(*rpcwallet.RPCKeyRing) + if !ok { + return errors.New("incorrect WalletController type, " + + "expected *rpcwallet.RPCKeyRing") + } + + innerTimeout := cfg.HealthChecks.RemoteSigner.Timeout + // Because we have two cascading timeouts here, we need to add // some slack to the "outer" one of them in case the "inner" // returns exactly on time. - overhead := time.Millisecond * 10 + outerTimeout := innerTimeout + time.Millisecond*10 - remoteSignerConnectionCheck := healthcheck.NewObservation( + rsConnectionCheck := healthcheck.NewObservation( "remote signer connection", rpcwallet.HealthCheck( - s.cfg.RemoteSigner, - + ctx, + rpckKeyRing.RemoteSignerConnection(), // For the health check we might to be even // stricter than the initial/normal connect, so - // we use the health check timeout here. - cfg.HealthChecks.RemoteSigner.Timeout, + // we use the health check timeout. + innerTimeout, ), cfg.HealthChecks.RemoteSigner.Interval, - cfg.HealthChecks.RemoteSigner.Timeout+overhead, + outerTimeout, cfg.HealthChecks.RemoteSigner.Backoff, cfg.HealthChecks.RemoteSigner.Attempts, ) - checks = append(checks, remoteSignerConnectionCheck) + checks = append(checks, rsConnectionCheck) } // If we have a leader elector, we add a health check to ensure we are From 066041bb0bda7f3ccb8c3f833fe9110873b9a695 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Dec 2024 22:15:05 +0100 Subject: [PATCH 11/39] multi: Add `watchonlynode` Namespace to `Config` This commit introduces a new `watchonlynode` namespace to the `Config` struct. It is designed to be configured on nodes acting as remote signers in a remote signer setup and defines how the connection with the watch-only node is established. Note that enabling the watch-only node configuration is not yet supported in this commit and will be enabled in a future update. --- config.go | 17 ++++++++++++++ lncfg/remotesigner.go | 53 +++++++++++++++++++++++++++++++++++++++---- sample-lnd.conf | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/config.go b/config.go index b4d252381d7..db7bc84deb1 100644 --- a/config.go +++ b/config.go @@ -497,8 +497,15 @@ type Config struct { RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"` + // RemoteSigner defines how to connect to a remote signer node. If this + // is enabled, the node acts as a watch-only node in a remote signer + // setup. RemoteSigner *lncfg.RemoteSigner `group:"remotesigner" namespace:"remotesigner"` + // WatchOnlyNode defines how to connect to a watch-only node. If this is + // enabled, the node acts as a remote signer in a remote signer setup. + WatchOnlyNode *lncfg.WatchOnlyNode `group:"watchonlynode" namespace:"watchonlynode"` + Sweeper *lncfg.Sweeper `group:"sweeper" namespace:"sweeper"` Htlcswitch *lncfg.Htlcswitch `group:"htlcswitch" namespace:"htlcswitch"` @@ -758,6 +765,7 @@ func DefaultConfig() Config { CoinSelectionStrategy: defaultCoinSelectionStrategy, KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts, RemoteSigner: lncfg.DefaultRemoteSignerCfg(), + WatchOnlyNode: lncfg.DefaultWatchOnlyNodeCfg(), Sweeper: lncfg.DefaultSweeperConfig(), Htlcswitch: &lncfg.Htlcswitch{ MailboxDeliveryTimeout: htlcswitch.DefaultMailboxDeliveryTimeout, @@ -1788,6 +1796,14 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, ) } + // Validate that the node isn't configured as both a remote signer and a + // watch-only node. + if cfg.RemoteSigner.Enable && cfg.WatchOnlyNode.Enable { + return nil, fmt.Errorf("cannot be configured as both a " + + "watchonly node and a remote signer node " + + "simultaneously") + } + // Validate the subconfigs for workers, caches, and the tower client. err = lncfg.Validate( cfg.Workers, @@ -1798,6 +1814,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, cfg.HealthChecks, cfg.RPCMiddleware, cfg.RemoteSigner, + cfg.WatchOnlyNode, cfg.Sweeper, cfg.Htlcswitch, cfg.Invoices, diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 945dfd297ff..ddf28520edb 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -10,6 +10,10 @@ const ( // that is used when connecting to the remote signer or watch-only node // through RPC. DefaultRemoteSignerRPCTimeout = 5 * time.Second + + // DefaultRequestTimeout is the default timeout used for requests to and + // from the remote signer. + DefaultRequestTimeout = 5 * time.Second ) // RemoteSigner holds the configuration options for how to connect to a remote @@ -61,22 +65,56 @@ func (r *RemoteSigner) Validate() error { return nil } +// WatchOnlyNode holds the configuration options for how to connect to a watch +// only node. Only a signer node specifies this config. +// +//nolint:ll +type WatchOnlyNode struct { + // Enable signals if this node a signer node and is expected to connect + // to a watch-only node. + Enable bool `long:"enable" description:"Signals that this node functions as a remote signer that will to connect with a watch-only node."` + + // ConnectionCfg holds the connection configuration options that the + // remote signer node will use when setting up the connection to the + // watch-only node. + ConnectionCfg +} + +// DefaultWatchOnlyNodeCfg returns the default WatchOnlyNode config. +func DefaultWatchOnlyNodeCfg() *WatchOnlyNode { + return &WatchOnlyNode{ + Enable: false, + ConnectionCfg: defaultConnectionCfg(), + } +} + +// Validate checks the values set in the WatchOnlyNode config are valid. +func (w *WatchOnlyNode) Validate() error { + if !w.Enable { + return nil + } else { + return fmt.Errorf("watchonlynode: enable not supported to yet") + } +} + // ConnectionCfg holds the configuration options required when setting up a // connection to either a remote signer or watch-only node, depending on which // side makes the outbound connection. // //nolint:ll type ConnectionCfg struct { - RPCHost string `long:"rpchost" description:"The RPC host:port of the remote signer. For watch-only nodes, this should be set to the remote signer's RPC host:port."` - MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer. For watch-only nodes, this should be set to the remote signer's macaroon."` - TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity. For watch-only nodes, this should be set to the remote signer's TLS certificate."` - Timeout time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer. Valid time units are {s, m, h}."` + RPCHost string `long:"rpchost" description:"The RPC host:port of the remote signer or watch-only node. For watch-only nodes, this should be set to the remote signer's RPC host:port. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's RPC host:port."` + MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer or the watch-only node. For watch-only nodes, this should be set to the remote signer's macaroon. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's macaroon."` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's or watch-only node's identity. For watch-only nodes, this should be set to the remote signer's TLS certificate. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's TLS certificate."` + Timeout time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."` + RequestTimeout time.Duration `long:"requesttimeout" description:"The time we will wait when making requests to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."` } // defaultConnectionCfg returns the default ConnectionCfg config. func defaultConnectionCfg() ConnectionCfg { return ConnectionCfg{ - Timeout: DefaultRemoteSignerRPCTimeout, + Timeout: DefaultRemoteSignerRPCTimeout, + RequestTimeout: DefaultRequestTimeout, } } @@ -87,6 +125,11 @@ func (c *ConnectionCfg) Validate() error { "smaller than %v", c.Timeout, time.Millisecond) } + if c.RequestTimeout < time.Second { + return fmt.Errorf("requesttimeout of %v is invalid, cannot "+ + "be smaller than %v", c.RequestTimeout, time.Second) + } + if c.RPCHost == "" { return fmt.Errorf("rpchost must be set") } diff --git a/sample-lnd.conf b/sample-lnd.conf index 3b7ce3d47b8..27e258d83ef 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1776,6 +1776,13 @@ ; Example: ; remotesigner.timeout=2m +; The time we will wait when making requests to the remote signer. +; Valid time units are {s, m, h}. +; Default: +; remotesigner.requesttimeout=5s +; Example: +; remotesigner.requesttimeout=30s + ; If a wallet with private key material already exists, migrate it into a ; watch-only wallet on first startup. ; WARNING: This cannot be undone! Make sure you have backed up your seed before @@ -1784,6 +1791,51 @@ ; remotesigner.migrate-wallet-to-watch-only=false +[watchonlynode] + +; Configures how to connect to a watch-only node from a node that acts as a +; remote signer. + +; Signals that this node functions as a remote signer that will to connect with +; a watch-only node. +; Default: +; watchonlynode.enable=false +; Example: +; watchonlynode.enable=true + +; The watch-only node's RPC host:port. +; Default: +; watchonlynode.rpchost= +; Example: +; watchonlynode.rpchost=watch.only.lnd.host:10009 + +; The macaroon to use for authenticating with the watch-only node. +; Default: +; watchonlynode.macaroonpath= +; Example: +; watchonlynode.macaroonpath=/path/to/watch-only/watch-only.custom.macaroon + +; The TLS certificate to use for establishing the watch-only node's identity. +; Default: +; watchonlynode.tlscertpath= +; Example: +; watchonlynode.tlscertpath=/path/to/watch-only/tls.cert + +; The timeout during a connection attempt to the watch-only node. +; Valid time units are {s, m, h}. +; Default: +; watchonlynode.timeout=5s +; Example: +; watchonlynode.timeout=2m + +; The time we will wait when when sending responses to the watch-only node. +; Valid time units are {s, m, h}. +; Default: +; watchonlynode.requesttimeout=5s +; Example: +; watchonlynode.requesttimeout=30s + + [gossip] ; Specify a set of pinned gossip syncers, which will always be actively syncing From a8cb836eadecc9d1db861ee59ffd232eec5bf5cd Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 11:42:29 +0200 Subject: [PATCH 12/39] rpcwallet: add `RemoteSignerClient` struct The previous commits added the foundation for creating different types of remote signer connections and defined the RPC that an outbound remote signer would use to communicate with the watch-only node. We will now define the implementation that the outbound signer node will use to set up the stream to the watch-only node and process any sign request messages that the watch-only node sends to the signer node. This implementation is encapsulated in the `OutboundClient`. The `OutboundClient` establishes an outbound gRPC connection to the watch-only node to set up a stream between them. It then processes all requests sent by the watch-only node to the remote signer by forwarding them to the appropriate `walletrpc.WalletKitServer` and `signrpc.SignerServer`. An alternative implementation of `RemoteSignerClient`, called `NoOpClient`, is also provided. As the name implies, this client does not perform any operations. Note once again that this is the implementation for the signer node side, not the watch-only node. --- lnwallet/rpcwallet/remote_signer_client.go | 895 ++++++++++++++++++ .../rpcwallet/remote_signer_client_test.go | 730 ++++++++++++++ 2 files changed, 1625 insertions(+) create mode 100644 lnwallet/rpcwallet/remote_signer_client.go create mode 100644 lnwallet/rpcwallet/remote_signer_client_test.go diff --git a/lnwallet/rpcwallet/remote_signer_client.go b/lnwallet/rpcwallet/remote_signer_client.go new file mode 100644 index 00000000000..16d98b961e6 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client.go @@ -0,0 +1,895 @@ +package rpcwallet + +import ( + "context" + "errors" + "fmt" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/reflect/protoreflect" + "gopkg.in/macaroon.v2" +) + +type ( + // signerResponse is a type alias for the SignCoordinator response type, + // created to keep line length within 80 characters. + signerResponse = walletrpc.SignCoordinatorResponse + + // registrationResp is a type alias for the registration response type, + // created to keep line length within 80 characters. + registrationResp = walletrpc.SignCoordinatorRequest_RegistrationResponse + + // completeType is a type alias for the registration complete type, + // created to keep line length within 80 characters. + completeType = walletrpc.RegistrationResponse_RegistrationComplete + + // signerRegType is a type alias for the signer registration type, + // created to keep line length within 80 characters. + signerRegType = walletrpc.SignCoordinatorResponse_SignerRegistration +) + +var ( + // ErrShuttingDown indicates that the server is in the process of + // gracefully exiting. + ErrShuttingDown = errors.New("lnd is shutting down") + + // ErrRequestType is returned when the request type by the watch-only + // node has not been implemented by remote signer. + ErrRequestType = errors.New("unimplemented request by watch-only node") +) + +const ( + // defaultRetryTimeout is the default timeout used when retrying to + // connect to the watch-only node. + defaultRetryTimeout = time.Second * 1 + + // retryMultiplier is the multiplier used to increase the retry timeout + // for every retry. + retryMultiplier = 1.5 + + // defaultMaxRetryTimeout is the default max value for the + // maxRetryTimeout, which defines the maximum backoff period before + // attempting to reconnect to the watch-only node. + defaultMaxRetryTimeout = time.Minute * 1 + + // handshakeRequestID is the request ID that is reversed for the + // handshake with the watch-only node. + handshakeRequestID = uint64(1) +) + +// Stream represents the stream to the watch-only node with a Close function +// that closes the connection. +type Stream struct { + StreamClient + + // Close closes the connection to the watch-only node. + Close func() error +} + +// NewStream creates a new Stream instance. +func NewStream(client StreamClient, closeConn func() error) *Stream { + return &Stream{ + StreamClient: client, + Close: closeConn, + } +} + +// SignCoordinatorStreamFeeder is an interface that returns a newly created +// stream to the watch-only node. The stream is used to send and receive +// messages between the remote signer client and the watch-only node. +type SignCoordinatorStreamFeeder interface { + // GetStream returns a new stream to the watch-only node. The function + // also returns a cleanup function that should be called when the stream + // is no longer needed. + GetStream(ctx context.Context) (*Stream, error) + + // Stop stops the stream feeder. + Stop() +} + +// RemoteSignerClient is an interface that defines the methods that a remote +// signer client should implement. +type RemoteSignerClient interface { + // Start starts the remote signer client. + Start(ctx context.Context) error + + // Stop stops the remote signer client. + Stop() error + + // MustImplementRemoteSignerClient is a no-op method that makes it + // easier to filter structs that implement the RemoteSignerClient + // interface. + MustImplementRemoteSignerClient() +} + +// StreamFeeder is an implementation of the SignCoordinatorStreamFeeder +// interface that creates a new stream to the watch-only node, by making an +// outbound gRPC connection to the watch-only node. +type StreamFeeder struct { + wg sync.WaitGroup + + cfg lncfg.ConnectionCfg + + cg *fn.ContextGuard +} + +// NewStreamFeeder creates a new StreamFeeder instance. +func NewStreamFeeder(cfg lncfg.ConnectionCfg) *StreamFeeder { + return &StreamFeeder{ + cfg: cfg, + cg: fn.NewContextGuard(), + } +} + +// Stop stops the StreamFeeder and disables the StreamFeeder from creating any +// new connections. +// +// NOTE: This is part of the SignCoordinatorStreamFeeder interface. +func (s *StreamFeeder) Stop() { + s.cg.Quit() + + s.wg.Wait() +} + +// GetStream returns a new stream to the watch-only node, by making an +// outbound gRPC connection to the watch-only node. The function also returns a +// cleanup function that closes the connection, which should be called when the +// stream is no longer needed. +// +// NOTE: This is part of the SignCoordinatorStreamFeeder interface. +func (s *StreamFeeder) GetStream(ctx context.Context) (*Stream, error) { + select { + // Don't run if the StreamFeeder has already been shutdown. + case <-s.cg.Done(): + return nil, ErrShuttingDown + default: + } + + // Create a new outbound gRPC connection to the watch-only node. + conn, err := s.getClientConn(ctx) + if err != nil { + return nil, err + } + + // Wrap the connection in a WalletKitClient. + walletKitClient := walletrpc.NewWalletKitClient(conn) + + // Create a new stream to the watch-only node. + streamClient, err := walletKitClient.SignCoordinatorStreams(ctx) + if err != nil { + connErr := conn.Close() + if connErr != nil { + log.ErrorS(ctx, "Unable to close watch-only node "+ + "connection: %v", connErr) + } + + return nil, err + } + + return NewStream(streamClient, conn.Close), nil +} + +// getClientConn creates a new outbound gRPC connection to the watch-only node. +func (s *StreamFeeder) getClientConn( + ctx context.Context) (*grpc.ClientConn, error) { + + // Ensure that our top level ctx is derived from the context guard. + // That way we know that we only need to select on the context guard's + // Done channel if the remote signer client is shutting down. + // If we fail to connect to the watch-only node within the + // configured timeout we should return an error. + ctx, cancel := s.cg.Create(ctx, fn.WithCustomTimeoutCG(s.cfg.Timeout)) + defer cancel() + + // Load the specified macaroon file for the watch-only node. + macBytes, err := os.ReadFile(s.cfg.MacaroonPath) + if err != nil { + return nil, fmt.Errorf("could not read macaroon file: %w", err) + } + + mac := &macaroon.Macaroon{} + + err = mac.UnmarshalBinary(macBytes) + if err != nil { + return nil, fmt.Errorf("could not unmarshal macaroon: %w", err) + } + + macCred, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return nil, fmt.Errorf( + "could not create macaroon credential: %w", err) + } + + // Load the specified TLS cert for the watch-only node. + tlsCreds, err := credentials.NewClientTLSFromFile(s.cfg.TLSCertPath, "") + if err != nil { + return nil, fmt.Errorf("could not load TLS cert: %w", err) + } + + opts := []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithTransportCredentials(tlsCreds), + grpc.WithPerRPCCredentials(macCred), + } + + log.InfoS(ctx, "Attempting to connect to the watch-only node on: %s", + s.cfg.RPCHost) + + // Connect to the watch-only node using the new context. + return grpc.DialContext(ctx, s.cfg.RPCHost, opts...) +} + +// A compile time assertion to ensure StreamFeeder meets the +// SignCoordinatorStreamFeeder interface. +var _ SignCoordinatorStreamFeeder = (*StreamFeeder)(nil) + +// NoOpClient is a remote signer client that is a no op, and is used when the +// configuration doesn't enable the use of a remote signer client. +type NoOpClient struct{} + +// Start is a no-op. +// +// NOTE: Part of the RemoteSignerClient interface. +func (n *NoOpClient) Start(ctx context.Context) error { + return nil +} + +// Stop is a no-op. +// +// NOTE: Part of the RemoteSignerClient interface. +func (n *NoOpClient) Stop() error { + return nil +} + +// MustImplementRemoteSignerClient is a no-op. +// +// NOTE: Part of the RemoteSignerClient interface. +func (n *NoOpClient) MustImplementRemoteSignerClient() {} + +// A compile time assertion to ensure NoOpClient meets the +// RemoteSignerClient interface. +var _ RemoteSignerClient = (*NoOpClient)(nil) + +// OutboundClient is a remote signer client which will process and respond to +// sign requests from the watch-only node, which are sent over a stream between +// the node and a watch-only node. +type OutboundClient struct { + stopped atomic.Bool + + log btclog.Logger + + // walletServer is the WalletKitServer that the remote signer client + // will use to process walletrpc requests. + walletServer walletrpc.WalletKitServer + + // signerServer is the SignerServer that the remote signer client will + // use to process signrpc requests. + signerServer signrpc.SignerServer + + // streamFeeder is the stream feeder that will set up a stream to the + // watch-only node when requested to do so by the remote signer client. + streamFeeder SignCoordinatorStreamFeeder + + // requestTimeout is the timeout used when sending responses to the + // watch-only node. + requestTimeout time.Duration + + // retryTimeout is the backoff timeout used when retrying to set up a + // connection to the watch-only node, if the previous connection/attempt + // failed. + retryTimeout time.Duration + + // maxRetryTimeout is the max value for the retryTimeout, defining + // the maximum backoff period before attempting to reconnect to the + // watch-only node. + maxRetryTimeout time.Duration + + wg sync.WaitGroup + + // wgMu ensures that we can't spawn a new Run goroutine after we've + // stopped the remote signer client. + wgMu sync.Mutex + + cg *fn.ContextGuard + gManager *fn.GoroutineManager +} + +// NewOutboundClient creates a new instance of the remote signer client. +// The passed subServers need to include a walletrpc.WalletKitServer and a +// signrpc.SignerServer, or the OutboundClient will be disabled. +// Note that the client will only fully start if the configuration +// enables an outbound remote signer. +func NewOutboundClient(walletServer walletrpc.WalletKitServer, + signerServer signrpc.SignerServer, + streamFeeder SignCoordinatorStreamFeeder, + requestTimeout time.Duration) (*OutboundClient, error) { + + if walletServer == nil || signerServer == nil { + return nil, errors.New("sub-servers cannot be nil when using " + + "an outbound remote signer") + } + + if streamFeeder == nil { + return nil, errors.New("streamFeeder cannot be nil") + } + + return &OutboundClient{ + log: log.WithPrefix("Remote signer client: "), + walletServer: walletServer, + signerServer: signerServer, + streamFeeder: streamFeeder, + requestTimeout: requestTimeout, + retryTimeout: defaultRetryTimeout, + maxRetryTimeout: defaultMaxRetryTimeout, + cg: fn.NewContextGuard(), + }, nil +} + +// Start starts the remote signer client. The function will continuously try to +// set up a connection to the configured watch-only node, and retry to connect +// if the connection fails until we Stop the remote signer client. +// +// NOTE: Part of the RemoteSignerClient interface. +func (r *OutboundClient) Start(ctx context.Context) error { + // Ensure that our top level ctx is derived from the context guard. + // That way we know that we only need to select on the context guard's + // Done channel if the remote signer client is shutting down. + ctx, _ = r.cg.Create(ctx) + + r.wg.Add(1) + go r.runForever(ctx) + + return nil +} + +// runForever continuously tries to set up a connection to the watch-only node, +// and retry to connect if the connection fails until we Stop the remote +// signer client. +func (r *OutboundClient) runForever(ctx context.Context) { + defer r.wg.Done() + + for { + // Check if we are shutting down. + select { + case <-ctx.Done(): + return + default: + } + + err := r.runOnce(ctx) + if err != nil { + r.log.ErrorS(ctx, "runOnce error", err) + } + + r.log.InfoS( + ctx, + "Connection retry to watch-only node scheduled", + "retry_after", r.retryTimeout, + ) + + // Backoff before retrying to connect to the watch-only node. + select { + case <-ctx.Done(): + return + case <-time.After(r.retryTimeout): + } + + r.log.InfoS(ctx, "Retrying to connect to watch-only node") + + // Increase the retry timeout by 50% for every retry. + r.retryTimeout = time.Duration( + float64(r.retryTimeout) * retryMultiplier, + ) + + // But cap the retryTimeout at r.maxRetryTimeout + if r.retryTimeout > r.maxRetryTimeout { + r.retryTimeout = r.maxRetryTimeout + } + } +} + +// Stop stops the remote signer client. +// +// NOTE: Part of the RemoteSignerClient interface. +func (r *OutboundClient) Stop() error { + if r.stopped.Swap(true) { + return errors.New("remote signer client is already shut down") + } + + r.log.Info("Shutting down") + + // Ensure that no new Run goroutines can start when we've initiated + // the stopping of the remote signer client. + r.wgMu.Lock() + + r.cg.Quit() + + r.streamFeeder.Stop() + + r.wgMu.Unlock() + + r.wg.Wait() + + r.log.Debugf("Shutdown complete") + + return nil +} + +// MustImplementRemoteSignerClient is a no-op. +// +// NOTE: Part of the RemoteSignerClient interface. +func (r *OutboundClient) MustImplementRemoteSignerClient() {} + +// runOnce creates a new stream to the watch-only node, and starts processing +// and responding to the sign requests that are sent over the stream. The +// function will continuously run until the remote signer client is either +// stopped or the stream errors. +func (r *OutboundClient) runOnce(ctx context.Context) error { + r.wgMu.Lock() + select { + case <-r.cg.Done(): + return ErrShuttingDown + default: + } + + // Derive a context for the lifetime of the stream. + ctx, cancel := r.cg.Create(ctx) + r.wgMu.Unlock() + + // Cancel the stream context whenever we return from this function. + defer cancel() + + log.InfoS(ctx, "Attempting to setup the watch-only node connection") + + // Try to get a new stream to the watch-only node. + stream, err := r.streamFeeder.GetStream(ctx) + if err != nil { + return err + } + defer func() { + err := stream.Close() + if err != nil { + log.ErrorS(ctx, "Unable to close watch-only node "+ + "connection", err) + } + }() + + // Once the stream has been created, we'll need to perform the handshake + // process with the watch-only node, before it will start sending us + // requests. + err = r.handshake(ctx, stream) + if err != nil { + return err + } + + log.InfoS(ctx, "Completed setup connection to watch-only node") + + // Reset the retry timeout after a successful connection. + r.retryTimeout = defaultRetryTimeout + + return r.processSignRequestsForever(ctx, stream) +} + +// handshake performs the handshake process with the watch-only node. As we are +// the initiator of the stream, we need to send the first message over the +// stream. The watch-only node will only proceed to sending us requests after +// the handshake has been completed. +func (r *OutboundClient) handshake(ctx context.Context, stream *Stream) error { + // Derive a context that times out the handshake process, if it takes + // longer than the request timeout. + ctxt, cancel := context.WithTimeout(ctx, r.requestTimeout) + defer cancel() + + var ( + msg *walletrpc.SignCoordinatorRequest + errChan = make(chan error, 1) + ) + + // registrationMsg is the message that we send to the watch-only node to + // initiate the handshake process. + // TODO(viktor): This could be extended to include info about the + // version of the remote signer in the future. + // The RegistrationChallenge should also be set to a randomized string. + var registrationMsg = &walletrpc.SignCoordinatorResponse{ + RefRequestId: handshakeRequestID, + SignResponseType: &signerRegType{ + SignerRegistration: &walletrpc.SignerRegistration{ + RegistrationChallenge: "registrationChallenge", + RegistrationInfo: "outboundSigner", + }, + }, + } + + r.wg.Add(1) + go func() { + defer r.wg.Done() + // Send the registration message to the watch-only node. + err := stream.Send(registrationMsg) + if err != nil { + errChan <- err + return + } + + // After the registration message has been sent, the signer node + // will respond with a message indicating that it has accepted + // the signer registration request if the registration was + // successful. + msg, err = stream.Recv() + errChan <- err + }() + + // Wait for the response. + select { + case <-ctxt.Done(): + return ctxt.Err() + case err := <-errChan: + if err != nil { + return fmt.Errorf("handshake error: %w", err) + } + } + + // Verify that the request ID of the response is the same as the + // request ID of the registration message. + if msg.GetRequestId() != handshakeRequestID { + return fmt.Errorf("initial response request id must "+ + "be %d, but is: %d", handshakeRequestID, + msg.GetRequestId()) + } + + // Check the type of the response message. + resp, ok := msg.GetSignRequestType().(*registrationResp) + if !ok { + return fmt.Errorf("expected registration response, but got: %T", + msg.GetSignRequestType()) + } + + switch rType := resp.RegistrationResponse. + GetRegistrationResponseType().(type) { + // The registration was successful. + case *completeType: + // TODO(viktor): This should verify that the signature in the + // complete message is valid. + return nil + + // An error occurred during the registration process. + case *walletrpc.RegistrationResponse_RegistrationError: + return fmt.Errorf("registration error: %s", + rType.RegistrationError) + + default: + return fmt.Errorf("unknown registration response type: %T", + resp.RegistrationResponse.GetRegistrationResponseType()) + } +} + +// processSignRequestsForever processes and responds to the sign requests tha +// are sent over the stream. The function will continuously run until the +// remote signer client is either stopped or the stream errors. +func (r *OutboundClient) processSignRequestsForever(ctx context.Context, + stream *Stream) error { + + for { + err := r.processSingleSignReq(ctx, stream) + if err != nil { + return err + } + + select { + case <-ctx.Done(): + return ErrShuttingDown + default: + } + } +} + +// processSingleSignReq waits for and processes a single request from the +// watch-only node, and sends the corresponding response back. +func (r *OutboundClient) processSingleSignReq(ctx context.Context, + stream *Stream) error { + + // Wait for a request from the watch-only node. + req, err := r.waitForRequest(ctx, stream) + if err != nil { + return err + } + + // Process the received request. + resp := r.formResponse(ctx, req) + + // Send the response back to the watch-only node. + return r.sendResponse(ctx, resp, stream) +} + +// waitForRequest waits for a request from the watch-only node. +func (r *OutboundClient) waitForRequest(ctx context.Context, stream *Stream) ( + *walletrpc.SignCoordinatorRequest, error) { + + var ( + req *walletrpc.SignCoordinatorRequest + err error + errChan = make(chan error, 1) + ) + + // We run the stream.Recv() in a goroutine to ensure we can stop if the + // remote signer client is shutting down (i.e. the quit channel is + // closed). Shutting down the remote signer client will cancel the ctx, + // which will cancel the stream context, which in turn will stop the + // goroutine. + r.wg.Add(1) + go func() { + defer r.wg.Done() + req, err = stream.Recv() + errChan <- err + }() + + // Wait for the response and then handle it. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + if err != nil { + return nil, fmt.Errorf("error receiving request: %w", + err) + } + } + + return req, nil +} + +// formResponse processes the received request from the watch-only node, and +// sends the corresponding response back. +func (r *OutboundClient) formResponse(ctx context.Context, + req *walletrpc.SignCoordinatorRequest) *signerResponse { + + resp, err := r.process(ctx, req) + if err != nil { + r.log.ErrorS(ctx, "could not process request", err) + + // If we fail to process the request, we will send a SignerError + // back to the watch-only node, indicating the nature of the + // error. + eType := &walletrpc.SignCoordinatorResponse_SignerError{ + SignerError: &walletrpc.SignerError{ + Error: "error processing the request in the " + + "remote signer: " + err.Error(), + }, + } + + resp = &signerResponse{ + RefRequestId: req.GetRequestId(), + SignResponseType: eType, + } + } + + return resp +} + +// process sends the passed request on to the appropriate server for processing +// it, and returns the response. +func (r *OutboundClient) process(ctx context.Context, + req *walletrpc.SignCoordinatorRequest) (*signerResponse, error) { + + r.log.DebugS(ctx, "Processing a request from watch-only", + btclog.Fmt("request_type", "%T", req.GetSignRequestType())) + + r.log.TraceS(ctx, "Request content", + "content", formatSignCoordinatorMsg(req)) + + var ( + requestID = req.GetRequestId() + signResp = &signerResponse{ + RefRequestId: requestID, + } + ) + + //nolint:ll + switch reqType := req.GetSignRequestType().(type) { + case *walletrpc.SignCoordinatorRequest_SharedKeyRequest: + resp, err := r.signerServer.DeriveSharedKey( + ctx, reqType.SharedKeyRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_SharedKeyResponse{ + SharedKeyResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_SignMessageReq: + resp, err := r.signerServer.SignMessage( + ctx, reqType.SignMessageReq, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_SignMessageResp{ + SignMessageResp: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_MuSig2SessionRequest: + resp, err := r.signerServer.MuSig2CreateSession( + ctx, reqType.MuSig2SessionRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_MuSig2SessionResponse{ + MuSig2SessionResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_MuSig2RegisterNoncesRequest: + resp, err := r.signerServer.MuSig2RegisterNonces( + ctx, reqType.MuSig2RegisterNoncesRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_MuSig2RegisterNoncesResponse{ + MuSig2RegisterNoncesResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_MuSig2SignRequest: + resp, err := r.signerServer.MuSig2Sign( + ctx, reqType.MuSig2SignRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_MuSig2SignResponse{ + MuSig2SignResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_MuSig2CombineSigRequest: + resp, err := r.signerServer.MuSig2CombineSig( + ctx, reqType.MuSig2CombineSigRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_MuSig2CombineSigResponse{ + MuSig2CombineSigResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_MuSig2CleanupRequest: + resp, err := r.signerServer.MuSig2Cleanup( + ctx, reqType.MuSig2CleanupRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_MuSig2CleanupResponse{ + MuSig2CleanupResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_SignPsbtRequest: + resp, err := r.walletServer.SignPsbt( + ctx, reqType.SignPsbtRequest, + ) + if err != nil { + return nil, err + } + + rType := &walletrpc.SignCoordinatorResponse_SignPsbtResponse{ + SignPsbtResponse: resp, + } + + signResp.SignResponseType = rType + + return signResp, nil + + case *walletrpc.SignCoordinatorRequest_Ping: + // If the received request is a ping, we don't need to pass the + // request on to a server, but can respond with a pong directly. + rType := &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + } + + signResp.SignResponseType = rType + + return signResp, nil + + default: + return nil, ErrRequestType + } +} + +// sendResponse sends the passed response back to the watch-only node over the +// stream. +func (r *OutboundClient) sendResponse(ctx context.Context, resp *signerResponse, + stream *Stream) error { + + // Timeout sending the response if it takes too long. + ctxt, cancel := r.cg.Create( + ctx, fn.WithCustomTimeoutCG(r.requestTimeout), + ) + defer cancel() + + var errChan = make(chan error, 1) + + // We send the response in a goroutine to ensure we can return an error + // if the send times out or we shut down. This is done to ensure that + // this function won't block indefinitely. + r.wg.Add(1) + go func() { + defer r.wg.Done() + errChan <- stream.Send(resp) + }() + + select { + case <-ctxt.Done(): + return ctxt.Err() + + case err := <-errChan: + if err != nil { + return fmt.Errorf("error sending response: %w", err) + } + } + + r.log.TraceS(ctxt, "Sent response to watch-only node", + btclog.ClosureAttr("response", formatSignCoordinatorMsg(resp))) + + return nil +} + +// A compile time assertion to ensure OutboundClient meets the +// RemoteSignerClient interface. +var _ RemoteSignerClient = (*OutboundClient)(nil) + +// formatSignCoordinatorMsg formats the passed proto message into a JSON string. +func formatSignCoordinatorMsg(msg protoreflect.ProtoMessage) btclog.Closure { + return func() string { + jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(msg) + if err != nil { + return fmt.Sprintf("", err.Error()) + } + + return string(jsonBytes) + } +} diff --git a/lnwallet/rpcwallet/remote_signer_client_test.go b/lnwallet/rpcwallet/remote_signer_client_test.go new file mode 100644 index 00000000000..efb90362614 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client_test.go @@ -0,0 +1,730 @@ +package rpcwallet + +import ( + "context" + "errors" + "math" + "sync" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +var ( + // ErrStreamCanceled is returned when the mock stream is canceled by the + // remote signer client. + ErrStreamCanceled = errors.New("stream canceled") + + // ErrMockResponseErr is a mock error that is returned by the mock + // signer server. + ErrMockResponseErr = errors.New("mock response error") + + // ErrStreamError is returned when the mock stream creation fails. + ErrStreamError = errors.New("stream creation error") +) + +// mockStreamFeeder is a mock implementation of SignCoordinatorStreamFeeder. +type mockStreamFeeder struct { + // stream is the current mock stream instance that gets set when the + // GetStream execution is successful. + stream *mockStream + + // streamShouldFail is a boolean that indicates if the stream should + // fail when GetStream is called. + streamShouldFail bool + + // streamCreated is a channel that is used to signal when the stream has + // been created. If the stream creation fails, an error is sent over the + // channel instead. + streamCreated chan error + + quit chan struct{} + + mu sync.Mutex +} + +// newMockStreamFeeder creates a new mock stream feeder instance. If +// getStreamShouldFail is set to true, the GetStream method will fail and return +// an error when executed, until the SetStreamFailure method is called to change +// the behavior. +func newMockStreamFeeder(getStreamShouldFail bool) *mockStreamFeeder { + return &mockStreamFeeder{ + streamCreated: make(chan error), + quit: make(chan struct{}), + streamShouldFail: getStreamShouldFail, + } +} + +// GetStream returns a mock stream instance. If the stream creation fails, an +// error is returned instead. +func (msf *mockStreamFeeder) GetStream(ctx context.Context) ( + *Stream, error) { + + msf.mu.Lock() + + select { + case <-msf.quit: + msf.mu.Unlock() + return nil, ErrShuttingDown + default: + } + + // If we've configured the stream feeder to fail, we'll fail the stream + // creation and return an error. + if msf.streamShouldFail { + msf.mu.Unlock() + + // Signal that the stream creation has failed. + select { + case msf.streamCreated <- ErrStreamError: + case <-ctx.Done(): + case <-msf.quit: + } + + return nil, ErrStreamError + } + + // Wrap the context in a cancelable context, so that the stream will be + // canceled when either party cancels to the context. + // If cancel function is executed, that simulates that the stream was + // cancelled by the other party (i.e. the watch-only node). + // If the parent context is cancelled, the remote signer client has + // cancelled the stream. + ctxc, cancel := context.WithCancel(ctx) + + // Else create a new mock stream instance. + mStream := newMockStream(ctxc) + + msf.stream = mStream + + // cancel the context on the closure of the stream. + closeFunc := func() error { + cancel() + + return nil + } + + returnStream := NewStream(msf.stream, closeFunc) + + msf.mu.Unlock() + + // Signal that the stream creation has succeeded. + select { + case msf.streamCreated <- nil: + case <-ctxc.Done(): + case <-msf.quit: + } + + return returnStream, nil +} + +// SetStreamFailure sets the streamShouldFail boolean to the provided value. +// If set to true, the GetStream method will fail and return an error when +// executed. If set to false, the GetStream method will succeed and return a +// mock stream instance. +func (msf *mockStreamFeeder) SetStreamFailure(shouldFail bool) { + msf.mu.Lock() + defer msf.mu.Unlock() + + msf.streamShouldFail = shouldFail +} + +// GetStreamShouldFail returns the current value of the streamShouldFail +// boolean. +func (msf *mockStreamFeeder) GetStreamShouldFail() bool { + msf.mu.Lock() + defer msf.mu.Unlock() + + return msf.streamShouldFail +} + +// Stop signals the mock stream feeder to stop. +func (msf *mockStreamFeeder) Stop() { + close(msf.quit) +} + +// A compile time assertion to ensure mockStreamFeeder meets the +// SignCoordinatorStreamFeeder interface. +var _ SignCoordinatorStreamFeeder = (*mockStreamFeeder)(nil) + +// Mock implementation of a stream. +type mockStream struct { + sendChan chan *walletrpc.SignCoordinatorResponse + recvChan chan *walletrpc.SignCoordinatorRequest + + // recvErrChan can be used to simulate that the stream errors. + recvErrChan chan error + + // ctx is the context that the stream was created with. + ctx context.Context //nolint:containedctx +} + +// newMockStream creates a new mock stream instance. +// The second return value is a cancel function that can be used to cancel the +// stream. +func newMockStream(ctx context.Context) *mockStream { + return &mockStream{ + sendChan: make(chan *walletrpc.SignCoordinatorResponse), + recvChan: make(chan *walletrpc.SignCoordinatorRequest), + recvErrChan: make(chan error), + ctx: ctx, + } +} + +// Send sends a response over the mock stream. This is called by the remote +// signer client when it responds to a request. +func (ms *mockStream) Send(resp *walletrpc.SignCoordinatorResponse) error { + select { + case <-ms.ctx.Done(): + // If the context is canceled, we return an error to indicate + // that the stream has been canceled. + return ErrStreamCanceled + case ms.sendChan <- resp: + } + + return nil +} + +// Recv simulates that a request over is sent over the mock stream to the +// remote signer client. If a request is sent over the recvChan, the remote +// signer client will handle the request. If an error is sent over the +// recvErrChan channel, the error will be received by the remote signer client. +func (ms *mockStream) Recv() (*walletrpc.SignCoordinatorRequest, error) { + select { + case resp := <-ms.recvChan: + return resp, nil + case err := <-ms.recvErrChan: + return nil, err + case <-ms.ctx.Done(): + // If the context is canceled, we return an error to indicate + // that the stream has been canceled. + return nil, ErrStreamCanceled + } +} + +// Helper function to simulate requests sent over the mock stream. +// The function will return an error if the stream is canceled before the +// request is received. +func (ms *mockStream) recvRequest(req *walletrpc.SignCoordinatorRequest) error { + select { + case ms.recvChan <- req: + return nil + case <-ms.ctx.Done(): + return ErrStreamCanceled + } +} + +// Helper function to simulate that the stream errors. +// The function will return an error if the stream is canceled before the error +// is received. +func (ms *mockStream) recvErr(err error) error { + select { + case ms.recvErrChan <- err: + return nil + case <-ms.ctx.Done(): + return ErrStreamCanceled + } +} + +// handleHandshake simulates the handshake procedure between the remote signer +// client and the watch-only node. +func (ms *mockStream) handleHandshake(t *testing.T) error { + var resp *walletrpc.SignCoordinatorResponse + + // Wait for the handshake init from the remote signer client. + select { + case <-ms.ctx.Done(): + // If the context is canceled, we return an error to indicate + // that the stream has been canceled. + return ErrStreamCanceled + case resp = <-ms.sendChan: + } + + require.Equal(t, handshakeRequestID, resp.GetRefRequestId()) + require.NotEmpty(t, resp.GetSignerRegistration()) + + complete := &walletrpc.RegistrationResponse_RegistrationComplete{ + RegistrationComplete: &walletrpc.RegistrationComplete{ + Signature: "", + RegistrationInfo: "watch-only registration info", + }, + } + + rType := &walletrpc.SignCoordinatorRequest_RegistrationResponse{ + RegistrationResponse: &walletrpc.RegistrationResponse{ + RegistrationResponseType: complete, + }, + } + + // Send a message to the client to simulate that the watch-only node has + // accepted the registration and that it's completed. + regCompleteMsg := &walletrpc.SignCoordinatorRequest{ + RequestId: handshakeRequestID, + SignRequestType: rType, + } + + return ms.recvRequest(regCompleteMsg) +} + +// Mock implementations of various WalletKit_SignCoordinatorStreamsClient +// methods. +func (ms *mockStream) Header() (metadata.MD, error) { return nil, nil } +func (ms *mockStream) SendMsg(m any) error { return nil } +func (ms *mockStream) Trailer() metadata.MD { return nil } +func (ms *mockStream) CloseSend() error { return nil } +func (ms *mockStream) RecvMsg(m any) error { return nil } +func (ms *mockStream) Context() context.Context { return ms.ctx } + +// newTestRemoteSignerClient creates a new outbound remote signer client +// instance for testing purposes, and inserts the passed streamFeeder together +// with a mock sub-servers into the created instance. +func newTestRemoteSignerClient(t *testing.T, + streamFeeder *mockStreamFeeder) *OutboundClient { + + client, err := NewOutboundClient( + &mockWalletKitServer{}, &mockSignerServer{}, streamFeeder, + 1*time.Second, + ) + require.NoError(t, err) + require.NoError(t, client.Start(context.Background())) + + // We expect the remote signer client attempt to create a stream during + // the start up. So if the stream feeder is configured to succeed, we + // need to handle the handshake procedure to finalize the stream set up. + if !streamFeeder.GetStreamShouldFail() { + // Wait for the stream to be created. + err := <-streamFeeder.streamCreated + require.NoError(t, err) + + err = streamFeeder.stream.handleHandshake(t) + require.NoError(t, err) + } + + return client +} + +// TestPingResponse tests that we can send a ping request to the remote signer +// client, and that it will respond with a pong. +func TestPingResponse(t *testing.T) { + t.Parallel() + + mockFeeder := newMockStreamFeeder(false) + + client := newTestRemoteSignerClient(t, mockFeeder) + defer func() { + // Ensure that the remote signer client is stopped successfully + // after the test. + require.NoError(t, client.Stop()) + }() + + // create the ping request. + pingReq := &walletrpc.SignCoordinatorRequest_Ping{ + Ping: true, + } + + requestID := uint64(2) + + req := &walletrpc.SignCoordinatorRequest{ + RequestId: requestID, + SignRequestType: pingReq, + } + + // Send the request to the remote signer client. + err := mockFeeder.stream.recvRequest(req) + require.NoError(t, err) + + // Wait for the response from the remote signer client. + resp := <-mockFeeder.stream.sendChan + + // Ensure that the response contains the correct request ID and that + // it's a pong response. + require.Equal(t, requestID, resp.GetRefRequestId()) + require.True(t, resp.GetPong()) +} + +// TestMultiplePingResponses tests that we can send multiple ping requests to +// the remote signer client, and that it will respond with a pong for each +// request. +func TestMultiplePingResponses(t *testing.T) { + t.Parallel() + + mockFeeder := newMockStreamFeeder(false) + + client := newTestRemoteSignerClient(t, mockFeeder) + defer func() { + // Ensure that the remote signer client is stopped successfully + // after the test. + require.NoError(t, client.Stop()) + }() + + // Create the first ping request. + pingReq := &walletrpc.SignCoordinatorRequest_Ping{ + Ping: true, + } + + requestID1 := uint64(2) + + req1 := &walletrpc.SignCoordinatorRequest{ + RequestId: requestID1, + SignRequestType: pingReq, + } + + // Send the first request to the remote signer client. + err := mockFeeder.stream.recvRequest(req1) + require.NoError(t, err) + + // Wait for the first response from the remote signer client. + resp1 := <-mockFeeder.stream.sendChan + + // Ensure that the response contains the correct request ID and that + // it's a pong response. + require.Equal(t, requestID1, resp1.GetRefRequestId()) + require.True(t, resp1.GetPong()) + + // Create the second ping request. + requestID2 := uint64(3) + + req2 := &walletrpc.SignCoordinatorRequest{ + RequestId: requestID2, + SignRequestType: pingReq, + } + + // Send the second request to the remote signer client. + err = mockFeeder.stream.recvRequest(req2) + require.NoError(t, err) + + // Wait for the second response from the remote signer client. + resp2 := <-mockFeeder.stream.sendChan + + // Ensure that the response contains the correct request ID, which + // differs from the first request, and that it's a pong response. + require.Equal(t, requestID2, resp2.GetRefRequestId()) + require.True(t, resp2.GetPong()) +} + +// TestStreamRecvErrorHandling tests that the remote signer client will cancel +// the stream if an error is received over the stream.Recv() method. +// The remote signer client should then proceed to retry to create a new stream. +func TestStreamRecvErrorHandling(t *testing.T) { + t.Parallel() + + msf := newMockStreamFeeder(false) + + client := newTestRemoteSignerClient(t, msf) + defer func() { + // Ensure that the remote signer client is stopped successfully + // after the test. + require.NoError(t, client.Stop()) + }() + + // Fetch the stream context before the stream is canceled. + streamCtx := msf.stream.Context() + + // Simulate that the stream errors, which should cause the remote signer + // client to cancel the stream. + err := msf.stream.recvErr(ErrStreamCanceled) + require.NoError(t, err) + + // Ensure that the stream has been canceled, as that should cause the + // remote signer client to cancel the stream context. + <-streamCtx.Done() + + // Now we expect the remote signer client to retry to create a new + // stream. We therefore ensure that the stream creation has been + // attempted successfully. + err = <-msf.streamCreated + require.NoError(t, err) +} + +// TestResponseError tests that the remote signer client will return a +// SignerError if it cannot process a received request. +func TestResponseError(t *testing.T) { + t.Parallel() + + msf := newMockStreamFeeder(false) + + client := newTestRemoteSignerClient(t, msf) + defer func() { + // Ensure that the remote signer client is stopped successfully + // after the test. + require.NoError(t, client.Stop()) + }() + + // Create a SignMessage request. As the remote signer client has an + // mockSignerServer instance as the signrpc server, this request will + // thrown an error when the signer server processes it. + signMessageReq := &walletrpc.SignCoordinatorRequest_SignMessageReq{ + SignMessageReq: &signrpc.SignMessageReq{}, + } + + requestID := uint64(2) + + req := &walletrpc.SignCoordinatorRequest{ + RequestId: requestID, + SignRequestType: signMessageReq, + } + + // Send the request to the remote signer client. + err := msf.stream.recvRequest(req) + require.NoError(t, err) + + // Wait for the response from the remote signer client. + resp := <-msf.stream.sendChan + + // Ensure that the response contains the correct request ID. + require.Equal(t, requestID, resp.GetRefRequestId()) + + // The response should be a SignerError, as the request could not be + // processed. + signErr := resp.GetSignerError() + require.NotNil(t, signErr) + + // The error should contain the error message that was returned by the + // mock signer server. + require.Contains(t, signErr.GetError(), ErrMockResponseErr.Error()) +} + +// TestStreamCreationBackoff tests that the client will retry to create a stream +// if the stream creation fails, and that the backoff duration before retrying +// to set up the stream again increases with each failed attempt. +func TestStreamCreationBackoff(t *testing.T) { + t.Parallel() + + msf := newMockStreamFeeder(true) + + client := newTestRemoteSignerClient(t, msf) + defer func() { + // Ensure that the remote signer client is stopped successfully + // after the test. + require.NoError(t, client.Stop()) + }() + + // For testing purposes, we set the max retry timeout to a value that + // ensures that the retry timeout will be capped on the fourth backoff. + client.maxRetryTimeout = defaultRetryTimeout * 3 + + // As we passed false to the newMockStreamFeeder constructor, the stream + // creation should fail. + err := <-msf.streamCreated + require.Equal(t, ErrStreamError, err) + + lastStreamCreationAttempt := time.Now() + + // The first time the client fails to set up a stream, we expect that + // the client will retry to create the stream after the default retry + // timeout, without any multiplied backoff. Once that happens, the + // streamCreated channel should receive the ErrStreamError. + err = <-msf.streamCreated + require.Equal(t, ErrStreamError, err) + + // Now let's verify that the client waited the default retry timeout + // before retrying to recreate the stream. + retryBackoff := time.Since(lastStreamCreationAttempt) + expectedBackoff := time.Duration(float64(defaultRetryTimeout) * + math.Pow(float64(retryMultiplier), 0)) // 0 for no multiplier + + // Verify that the retry backoff is within the expected range. We allow + // a small margin of error (100ms) on the range bound to account for the + // time it takes to execute the test code. We also allow a small margin + // of error (10ms) on the lower bound to account for the time the code + // execution takes between the creation of the stream, and when + // retryBackoff is set. + require.GreaterOrEqual( + t, retryBackoff, expectedBackoff-10*time.Millisecond, + ) + require.LessOrEqual( + t, retryBackoff, expectedBackoff+100*time.Millisecond, + ) + + // Reset the last attempt time, so we can check the next retry. + lastStreamCreationAttempt = time.Now() + + // Now let's wait until the client retries to create the stream again. + // This time we expect that a multiplier of retryMultiplier^1 has been + // applied to the backoff duration. + err = <-msf.streamCreated + require.Equal(t, ErrStreamError, err) + + // Verify that the retry backoff is within the expected range, with the + // multiplier applied. + retryBackoff = time.Since(lastStreamCreationAttempt) + + // The second backoff should have the multiplier applied once, therefore + // the multiplier raised to the power of 1. + expectedBackoff = time.Duration(float64(defaultRetryTimeout) * + math.Pow(float64(retryMultiplier), 1)) + + // Verify that the retry backoff is within the expected range. + require.GreaterOrEqual( + t, retryBackoff, expectedBackoff-10*time.Millisecond, + ) + require.LessOrEqual( + t, retryBackoff, expectedBackoff+100*time.Millisecond, + ) + + // Reset the last attempt time, so that we can check that the retry + // timeout correctly gets multiplied again. + lastStreamCreationAttempt = time.Now() + + // Now let's wait until the client retries to create the stream again. + // This time we expect that a multiplier of retryMultiplier^2 has been + // applied to the backoff duration. + err = <-msf.streamCreated + require.Equal(t, ErrStreamError, err) + + // Verify that the retry backoff is within the expected range, with the + // multiplier applied. + retryBackoff = time.Since(lastStreamCreationAttempt) + + // The third backoff should have the multiplier applied twice, therefore + // the multiplier raised to the power of 2. + expectedBackoff = time.Duration(float64(defaultRetryTimeout) * + math.Pow(float64(retryMultiplier), 2)) + + // Verify that the retry backoff is within the expected range. + require.GreaterOrEqual( + t, retryBackoff, expectedBackoff-10*time.Millisecond, + ) + require.LessOrEqual( + t, retryBackoff, expectedBackoff+100*time.Millisecond, + ) + + // For the next retry, we want the stream creation to succeed. This will + // reset the retry backoff to the default value, once the stream is + // successfully created. + msf.SetStreamFailure(false) + + // Reset the last attempt time, so we can check the next retry. + lastStreamCreationAttempt = time.Now() + + // Now let's wait until the client retries to create the stream again. + // Even though the creation will succeed, it'll still take the expected + // backoff time before the client attempts to make the successful stream + // creation. However, since we capped the maximum retry timeout, and + // this was the third time the retry timeout was multiplied, the maximum + // retry timeout should have been reached. Therefore, we expect the + // retry timeout to be set to client.maxRetryTimeout. + err = <-msf.streamCreated + + // We expect the stream creation to succeed this time. + require.NoError(t, err) + + // Verify that the retry backoff is within the expected range, with the + // multiplier applied. + retryBackoff = time.Since(lastStreamCreationAttempt) + + // The fourth backoff should have the multiplier applied three times, + // which would result in a backoff larger than the client’s + // maxRetryTimeout. Therefore, the backoff should have been capped at + // the client’s maxRetryTimeout. + expectedBackoff = client.maxRetryTimeout + + // Verify that the retry backoff was capped. + require.GreaterOrEqual( + t, retryBackoff, expectedBackoff-10*time.Millisecond, + ) + require.LessOrEqual( + t, retryBackoff, expectedBackoff+100*time.Millisecond, + ) + + // As the steam creation was successful, the client will proceed with + // the handshake procedure before the stream creation is considered + // successful. We therefore need to simulate the handshake procedure. + err = msf.stream.handleHandshake(t) + require.NoError(t, err) + + // Now let's cause the stream to fail again, to verify that the client + // reset the backoff to the default value, as the last stream creation + // attempt was successful. + err = msf.stream.recvErr(ErrStreamCanceled) + require.NoError(t, err) + + // Reset the last attempt time, so we can check the next retry. + lastStreamCreationAttempt = time.Now() + + // Now let's wait till the client retries to create the stream again. + err = <-msf.streamCreated + // We expect the stream creation to also succeed this time. + require.NoError(t, err) + + // As the backoff is reset to the default value, we expect that no + // multiplier has been applied to the backoff duration. + retryBackoff = time.Since(lastStreamCreationAttempt) + expectedBackoff = time.Duration(float64(defaultRetryTimeout) * + math.Pow(float64(retryMultiplier), 0)) + + // Verify that the retry backoff is within the expected range. + require.GreaterOrEqual( + t, retryBackoff, expectedBackoff-10*time.Millisecond, + ) + require.LessOrEqual( + t, retryBackoff, expectedBackoff+100*time.Millisecond, + ) +} + +// mockWalletKitServer is a mock walletrpc.WalletKitServer implementation that +// panics for all request methods. +type mockWalletKitServer struct { + walletrpc.UnimplementedWalletKitServer +} + +var _ walletrpc.WalletKitServer = (*mockWalletKitServer)(nil) + +// Name returns a unique string representation of the sub-server. This +// can be used to identify the sub-server and also de-duplicate them. +func (m *mockWalletKitServer) Name() string { return walletrpc.SubServerName } + +// Start starts the sub-server and all goroutines it needs to operate. +func (m *mockWalletKitServer) Start() error { return nil } + +// Stop signals that the sub-server should wrap up any lingering +// requests, and being a graceful shutdown. +func (m *mockWalletKitServer) Stop() error { return nil } + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +func (m *mockWalletKitServer) InjectDependencies( + _ lnrpc.SubServerConfigDispatcher, _ bool) error { + + return nil +} + +// mockSignerServer is a mock signrpc.SignerServer implementation that panics +// for all request methods except SignMessage. +type mockSignerServer struct { + signrpc.UnimplementedSignerServer +} + +var _ signrpc.SignerServer = (*mockSignerServer)(nil) + +func (m *mockSignerServer) SignMessage(_ context.Context, + _ *signrpc.SignMessageReq) (*signrpc.SignMessageResp, error) { + + return nil, ErrMockResponseErr +} + +// Name returns a unique string representation of the sub-server. This +// can be used to identify the sub-server and also de-duplicate them. +func (m *mockSignerServer) Name() string { return "SignRPC" } + +// Start starts the sub-server and all goroutines it needs to operate. +func (m *mockSignerServer) Start() error { return nil } + +// Stop signals that the sub-server should wrap up any lingering +// requests, and being a graceful shutdown. +func (m *mockSignerServer) Stop() error { return nil } + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +func (m *mockSignerServer) InjectDependencies( + _ lnrpc.SubServerConfigDispatcher, _ bool) error { + + return nil +} From 49331475cbf84358098cd936a27e64b8edc7b8d8 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Thu, 31 Oct 2024 10:03:52 +0100 Subject: [PATCH 13/39] f - rpcwallet: use GoroutineManager in remote signer signer client --- lnwallet/rpcwallet/remote_signer_client.go | 58 ++++++++-------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/lnwallet/rpcwallet/remote_signer_client.go b/lnwallet/rpcwallet/remote_signer_client.go index 16d98b961e6..1a6a2dac13f 100644 --- a/lnwallet/rpcwallet/remote_signer_client.go +++ b/lnwallet/rpcwallet/remote_signer_client.go @@ -296,12 +296,6 @@ type OutboundClient struct { // watch-only node. maxRetryTimeout time.Duration - wg sync.WaitGroup - - // wgMu ensures that we can't spawn a new Run goroutine after we've - // stopped the remote signer client. - wgMu sync.Mutex - cg *fn.ContextGuard gManager *fn.GoroutineManager } @@ -334,6 +328,7 @@ func NewOutboundClient(walletServer walletrpc.WalletKitServer, retryTimeout: defaultRetryTimeout, maxRetryTimeout: defaultMaxRetryTimeout, cg: fn.NewContextGuard(), + gManager: fn.NewGoroutineManager(), }, nil } @@ -348,8 +343,10 @@ func (r *OutboundClient) Start(ctx context.Context) error { // Done channel if the remote signer client is shutting down. ctx, _ = r.cg.Create(ctx) - r.wg.Add(1) - go r.runForever(ctx) + success := r.gManager.Go(ctx, r.runForever) + if !success { + return errors.New("failed to start remote signer client") + } return nil } @@ -358,8 +355,6 @@ func (r *OutboundClient) Start(ctx context.Context) error { // and retry to connect if the connection fails until we Stop the remote // signer client. func (r *OutboundClient) runForever(ctx context.Context) { - defer r.wg.Done() - for { // Check if we are shutting down. select { @@ -410,17 +405,11 @@ func (r *OutboundClient) Stop() error { r.log.Info("Shutting down") - // Ensure that no new Run goroutines can start when we've initiated - // the stopping of the remote signer client. - r.wgMu.Lock() - r.cg.Quit() r.streamFeeder.Stop() - r.wgMu.Unlock() - - r.wg.Wait() + r.gManager.Stop() r.log.Debugf("Shutdown complete") @@ -437,16 +426,8 @@ func (r *OutboundClient) MustImplementRemoteSignerClient() {} // function will continuously run until the remote signer client is either // stopped or the stream errors. func (r *OutboundClient) runOnce(ctx context.Context) error { - r.wgMu.Lock() - select { - case <-r.cg.Done(): - return ErrShuttingDown - default: - } - // Derive a context for the lifetime of the stream. ctx, cancel := r.cg.Create(ctx) - r.wgMu.Unlock() // Cancel the stream context whenever we return from this function. defer cancel() @@ -512,9 +493,7 @@ func (r *OutboundClient) handshake(ctx context.Context, stream *Stream) error { }, } - r.wg.Add(1) - go func() { - defer r.wg.Done() + ok := r.gManager.Go(ctxt, func(_ context.Context) { // Send the registration message to the watch-only node. err := stream.Send(registrationMsg) if err != nil { @@ -528,7 +507,10 @@ func (r *OutboundClient) handshake(ctx context.Context, stream *Stream) error { // successful. msg, err = stream.Recv() errChan <- err - }() + }) + if !ok { + return fmt.Errorf("error sending registration message") + } // Wait for the response. select { @@ -627,12 +609,13 @@ func (r *OutboundClient) waitForRequest(ctx context.Context, stream *Stream) ( // closed). Shutting down the remote signer client will cancel the ctx, // which will cancel the stream context, which in turn will stop the // goroutine. - r.wg.Add(1) - go func() { - defer r.wg.Done() + ok := r.gManager.Go(ctx, func(_ context.Context) { req, err = stream.Recv() errChan <- err - }() + }) + if !ok { + return nil, fmt.Errorf("error receiving request") + } // Wait for the response and then handle it. select { @@ -856,11 +839,12 @@ func (r *OutboundClient) sendResponse(ctx context.Context, resp *signerResponse, // We send the response in a goroutine to ensure we can return an error // if the send times out or we shut down. This is done to ensure that // this function won't block indefinitely. - r.wg.Add(1) - go func() { - defer r.wg.Done() + ok := r.gManager.Go(ctxt, func(ctxt context.Context) { errChan <- stream.Send(resp) - }() + }) + if !ok { + return fmt.Errorf("error sending response") + } select { case <-ctxt.Done(): From f74deca615dfb51a392b08ace77ee61c4d1f3eca Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Sun, 1 Sep 2024 20:49:59 +0200 Subject: [PATCH 14/39] rpcwallet: Add `RemoteSignerClientBuilder` Add a remote signer client builder that constructs either an outbound remote signer client or a No Op client, depending on the current configuration. --- .../rpcwallet/remote_signer_client_builder.go | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 lnwallet/rpcwallet/remote_signer_client_builder.go diff --git a/lnwallet/rpcwallet/remote_signer_client_builder.go b/lnwallet/rpcwallet/remote_signer_client_builder.go new file mode 100644 index 00000000000..6e67a16e881 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client_builder.go @@ -0,0 +1,69 @@ +package rpcwallet + +import ( + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" +) + +type rscBuilder = RemoteSignerClientBuilder + +// RemoteSignerClientBuilder creates instances of the RemoteSignerClient +// interface, based on the provided configuration. +type RemoteSignerClientBuilder struct { + cfg *lncfg.WatchOnlyNode +} + +// NewRemoteSignerClientBuilder creates a new instance of the +// RemoteSignerClientBuilder. +func NewRemoteSignerClientBuilder(cfg *lncfg.WatchOnlyNode) *rscBuilder { + return &rscBuilder{cfg} +} + +// Build creates a new RemoteSignerClient instance. If the configuration enables +// an outbound remote signer, a new OutboundRemoteSignerClient will be returned. +// Else, a NoOpClient will be returned. +func (b *rscBuilder) Build(subServers []lnrpc.SubServer) ( + RemoteSignerClient, error) { + + var ( + walletServer walletrpc.WalletKitServer + signerServer signrpc.SignerServer + ) + + for _, subServer := range subServers { + if server, ok := subServer.(walletrpc.WalletKitServer); ok { + walletServer = server + } + + if server, ok := subServer.(signrpc.SignerServer); ok { + signerServer = server + } + } + + // Check if we have all servers and if the configuration enables an + // outbound remote signer. If not, return a NoOpClient. + if walletServer == nil || signerServer == nil { + log.Debugf("Using a No Op remote signer client due to " + + "current sub-server support") + + return &NoOpClient{}, nil + } + + if !b.cfg.Enable { + log.Debugf("Using a No Op remote signer client due to the " + + "current watchonly config") + + return &NoOpClient{}, nil + } + + // An outbound remote signer client is enabled, therefore we create one. + log.Debugf("Using an outbound remote signer client") + + streamFeeder := NewStreamFeeder(b.cfg.ConnectionCfg) + + return NewOutboundClient( + walletServer, signerServer, streamFeeder, b.cfg.RequestTimeout, + ) +} From 6bacf1663e7dfb915deaa4295317870b95e98f14 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 16:39:21 +0200 Subject: [PATCH 15/39] lnd: add `RemoteSignerClient` instance on startup This commit adds a `RemoteSignerClient` instance to the main `lnd` server. The specific type of `RemoteSignerClient` used will depend on the configuration. If `watchonlynode.enable` is set to `true`, an `OutboundClient` instance will be used. Otherwise, a `NoOpClient` instance will be utilized. --- lnd.go | 13 ++++++++++++- server.go | 20 ++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lnd.go b/lnd.go index 76b08a114a1..fa0a0d7dccc 100644 --- a/lnd.go +++ b/lnd.go @@ -30,6 +30,7 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/rpcperms" @@ -623,13 +624,23 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, multiAcceptor = chanacceptor.NewChainedAcceptor() } + // Set up the remote signer client. If cfg.WatchOnlyNode.Enable isn't + // set to true, this remote signer client won't run when the server + // starts. + rscBuilder := rpcwallet.NewRemoteSignerClientBuilder(cfg.WatchOnlyNode) + + rsClient, err := rscBuilder.Build(rpcServer.subServers) + if err != nil { + return mkErr("unable to create remote signer client", err) + } + // Set up the core server which will listen for incoming peer // connections. server, err := newServer( ctx, cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, activeChainControl.Cfg.WalletUnlockParams.ChansToRestore, multiAcceptor, torController, tlsManager, leaderElector, - implCfg, + implCfg, rsClient, ) if err != nil { return mkErr("unable to create server", err) diff --git a/server.go b/server.go index 7e57b7763af..b855898b396 100644 --- a/server.go +++ b/server.go @@ -389,6 +389,8 @@ type server struct { tlsManager *TLSManager + remoteSignerClient rpcwallet.RemoteSignerClient + // featureMgr dispatches feature vectors for various contexts within the // daemon. featureMgr *feature.Manager @@ -583,8 +585,8 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, chansToRestore walletunlocker.ChannelsToRecover, chanPredicate chanacceptor.ChannelAcceptor, torController *tor.Controller, tlsManager *TLSManager, - leaderElector cluster.LeaderElector, - implCfg *ImplementationCfg) (*server, error) { + leaderElector cluster.LeaderElector, implCfg *ImplementationCfg, + remoteSignerClient rpcwallet.RemoteSignerClient) (*server, error) { var ( err error @@ -729,6 +731,8 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, tlsManager: tlsManager, + remoteSignerClient: remoteSignerClient, + featureMgr: featureMgr, quit: make(chan struct{}), } @@ -2178,6 +2182,14 @@ func (s *server) Start(ctx context.Context) error { } } + ctx := context.TODO() + + cleanup = cleanup.add(s.remoteSignerClient.Stop) + if err := s.remoteSignerClient.Start(ctx); err != nil { + startErr = err + return + } + // Start the notification server. This is used so channel // management goroutines can be notified when a funding // transaction reaches a sufficient number of confirmations, or @@ -2696,6 +2708,10 @@ func (s *server) Stop() error { srvrLog.Warnf("Unable to stop BestBlockTracker: %v", err) } + if err := s.remoteSignerClient.Stop(); err != nil { + srvrLog.Warnf("Unable to stop remote signer "+ + "client: %v", err) + } if err := s.chanEventStore.Stop(); err != nil { srvrLog.Warnf("Unable to stop ChannelEventStore: %v", err) From 24de630b9a2761c16eb9eb2f09d8659beaf93027 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Dec 2024 02:10:20 +0100 Subject: [PATCH 16/39] lncfg: allow `watchonlynode.enable` As all the necessary pieces on the signer node side to let the remote signer make an outbound connection to the watch-only node are in place, the `lncfg` package can now permit the `watchonlynode.enable` setting to be configured. Note that we still haven't created the implementation on the watch-only node side to accept the connection and send the requests over the stream. This will be added in the upcoming commits. --- lncfg/remotesigner.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index ddf28520edb..f570f6014d3 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -92,9 +92,14 @@ func DefaultWatchOnlyNodeCfg() *WatchOnlyNode { func (w *WatchOnlyNode) Validate() error { if !w.Enable { return nil - } else { - return fmt.Errorf("watchonlynode: enable not supported to yet") } + + err := w.ConnectionCfg.Validate() + if err != nil { + return fmt.Errorf("watchonlynode.%w", err) + } + + return nil } // ConnectionCfg holds the configuration options required when setting up a From fb2915aa3e86f51b711236a8a3ab0b4802f97336 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Dec 2024 22:10:42 +0100 Subject: [PATCH 17/39] conf: add `remotesigner.allowinboundconnection` This commit introduces configuration functionality on the watch-only node's side to support an inbound connection from an outbound remote signer. Note that enabling this option is not supported in this commit, but the ability to toggle it will be added in an upcoming commit. --- lncfg/remotesigner.go | 53 ++++++++++++++++++++++++++++++++++++++----- sample-lnd.conf | 22 ++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index f570f6014d3..5d22698c4fa 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -14,6 +14,11 @@ const ( // DefaultRequestTimeout is the default timeout used for requests to and // from the remote signer. DefaultRequestTimeout = 5 * time.Second + + // DefaultStartupTimeout is the default startup timeout used when a + // watch-only node with 'remotesigner.allowinboundconnection' set to + // true waits for the remote signer to connect. + DefaultStartupTimeout = 5 * time.Minute ) // RemoteSigner holds the configuration options for how to connect to a remote @@ -25,6 +30,10 @@ type RemoteSigner struct { // setup. Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` + // AllowInboundConnection is true if the remote signer node will connect + // to this node. + AllowInboundConnection bool `long:"allowinboundconnection" description:"Signals that we allow an inbound connection from a remote signer to this node."` + // MigrateWatchOnly migrates the wallet to a watch-only wallet by // purging all private keys from the wallet after first unlock with this // flag. @@ -34,13 +43,22 @@ type RemoteSigner struct { // watch-only node will use when setting up the connection to the remote // signer. ConnectionCfg + + // inboundWatchOnlyCfg holds the configuration options specifically + // used when the watch-only node expects an inbound connection from + // the remote signer. + inboundWatchOnlyCfg } // DefaultRemoteSignerCfg returns the default RemoteSigner config. func DefaultRemoteSignerCfg() *RemoteSigner { return &RemoteSigner{ - Enable: false, - ConnectionCfg: defaultConnectionCfg(), + Enable: false, + AllowInboundConnection: false, + ConnectionCfg: defaultConnectionCfg(), + inboundWatchOnlyCfg: inboundWatchOnlyCfg{ + StartupTimeout: DefaultStartupTimeout, + }, } } @@ -56,6 +74,21 @@ func (r *RemoteSigner) Validate() error { return nil } + if r.AllowInboundConnection { + return fmt.Errorf("remote signer: allowinboundconnection " + + "is not supported yet") + } + + if r.AllowInboundConnection { + if r.StartupTimeout < time.Second { + return fmt.Errorf("remotesigner.startuptimeout of "+ + "%v is invalid, cannot be smaller than %v", + r.StartupTimeout, time.Second) + } + + return nil + } + // Else, we are in outbound mode, so we verify the connection config. err := r.ConnectionCfg.Validate() if err != nil { @@ -65,6 +98,14 @@ func (r *RemoteSigner) Validate() error { return nil } +// inboundWatchOnlyCfg holds the configuration options specific for watch-only +// nodes with the allowinboundconnection` option set. +// +//nolint:ll +type inboundWatchOnlyCfg struct { + StartupTimeout time.Duration `long:"startuptimeout" description:"The time the watch-only node will wait for the remote signer to connect during startup. If the timeout expires before the remote signer connects, the watch-only node will shut down. Valid time units are {s, m, h}."` +} + // WatchOnlyNode holds the configuration options for how to connect to a watch // only node. Only a signer node specifies this config. // @@ -108,10 +149,10 @@ func (w *WatchOnlyNode) Validate() error { // //nolint:ll type ConnectionCfg struct { - RPCHost string `long:"rpchost" description:"The RPC host:port of the remote signer or watch-only node. For watch-only nodes, this should be set to the remote signer's RPC host:port. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's RPC host:port."` - MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer or the watch-only node. For watch-only nodes, this should be set to the remote signer's macaroon. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's macaroon."` - TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's or watch-only node's identity. For watch-only nodes, this should be set to the remote signer's TLS certificate. For remote signer nodes connecting to a watch-only node, this should be set to the watch-only node's TLS certificate."` - Timeout time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."` + RPCHost string `long:"rpchost" description:"The RPC host:port of the remote signer or watch-only node. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's RPC host:port. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's RPC host:port."` + MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer or the watch-only node. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's macaroon. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's macaroon."` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's or watch-only node's identity. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's TLS certificate. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's TLS certificate."` + Timeout time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. For watch-only nodes with 'remotesigner.allowinboundconnection' set to true, this timeout value has no effect. Valid time units are {s, m, h}."` RequestTimeout time.Duration `long:"requesttimeout" description:"The time we will wait when making requests to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."` } diff --git a/sample-lnd.conf b/sample-lnd.conf index 27e258d83ef..fb79115288b 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1751,19 +1751,29 @@ ; private keys. ; remotesigner.enable=false -; The remote signer's RPC host:port. +; Signals that we allow an inbound connection from a remote signer to this node +; when the local node acts as a watch-only node. +; Default: +; remotesigner.allowinboundconnection=false +; Example: +; remotesigner.allowinboundconnection=true + +; The remote signer's RPC host:port. Should not be set if +; `remotesigner.allowinboundconnection` is set to true. ; Default: ; remotesigner.rpchost= ; Example: ; remotesigner.rpchost=remote.signer.lnd.host:10009 -; The macaroon to use for authenticating with the remote signer. +; The macaroon to use for authenticating with the remote signer. Should not be +; set if `remotesigner.allowinboundconnection` is set to true. ; Default: ; remotesigner.macaroonpath= ; Example: ; remotesigner.macaroonpath=/path/to/remote/signer/admin.macaroon ; The TLS certificate to use for establishing the remote signer's identity. +; Should not be set if `remotesigner.allowinboundconnection` is set to true. ; Default: ; remotesigner.tlscertpath= ; Example: @@ -1783,6 +1793,14 @@ ; Example: ; remotesigner.requesttimeout=30s +; The time a node with `remotesigner.allowinboundconnection` set to true will +; wait for the remote signer to connect. +; Valid time units are {s, m, h}. +; Default: +; remotesigner.startuptimeout=5m +; Example: +; remotesigner.startuptimeout=1m + ; If a wallet with private key material already exists, migrate it into a ; watch-only wallet on first startup. ; WARNING: This cannot be undone! Make sure you have backed up your seed before From 945d17ff24e6d418bac9219ae44ac074d3cdf868 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 11:45:02 +0200 Subject: [PATCH 18/39] rpcwallet: add `SignCoordinator` struct This commit introduces an implementation for the watch-only node to send and receive messages over the `SignCoordinatorStreams` stream, which serves as the connection stream with an outbound remote signer. Previous commits added the `OutboundClient` implementation, defining the signer node's side of this functionality. The new implementation, called `SignCoordinator`, converts requests sent to the remote signer connection on the watch-only node side into the corresponding `SignCoordinatorStreams` request messages and transmits them over the stream. The requests we send to a remote signer are defined in the `RemoteSignerRequests` interface. When a response is received from the outbound remote signer, it is then converted back into the appropriate `walletrpc` or `signrpc` response. Additionally, the `SignCoordinator` includes functions to block and signal once the outbound remote signer has connected. Since requests cannot be processed before the connection to the outbound remote signer is established, any requests sent to the `SignCoordinator` will wait for the remote signer to connect before being processed. --- lnwallet/rpcwallet/sign_coordinator.go | 873 ++++++++++++++++++++ lnwallet/rpcwallet/sign_coordinator_test.go | 772 +++++++++++++++++ 2 files changed, 1645 insertions(+) create mode 100644 lnwallet/rpcwallet/sign_coordinator.go create mode 100644 lnwallet/rpcwallet/sign_coordinator_test.go diff --git a/lnwallet/rpcwallet/sign_coordinator.go b/lnwallet/rpcwallet/sign_coordinator.go new file mode 100644 index 00000000000..48a585dc2b0 --- /dev/null +++ b/lnwallet/rpcwallet/sign_coordinator.go @@ -0,0 +1,873 @@ +package rpcwallet + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnutils" + "google.golang.org/grpc" +) + +var ( + // ErrRequestTimeout is the error that's returned if we time out while + // waiting for a response from the remote signer. + ErrRequestTimeout = errors.New("remote signer response timeout reached") + + // ErrConnectTimeout is the error that's returned if we time out while + // waiting for the remote signer to connect. + ErrConnectTimeout = errors.New("timed out when waiting for remote " + + "signer to connect") + + // ErrMultipleConnections is the error that's returned if another + // remote signer attempts to connect while we already have one + // connected. + ErrMultipleConnections = errors.New("only one remote signer can be " + + "connected") + + // ErrNotConnected is the error that's returned if the remote signer + // closes the stream or we encounter an error when receiving over the + // stream. + ErrNotConnected = errors.New("the remote signer is no longer connected") + + // ErrUnexpectedResponse is the error that's returned if the response + // with the expected request ID from the remote signer is of an + // unexpected type. + ErrUnexpectedResponse = errors.New("unexpected response type") +) + +// SignCoordinator is an implementation of the signrpc.SignerClient and the +// walletrpc.WalletKitClient interfaces that passes on all requests to a remote +// signer. It is used by the watch-only wallet to delegate any signing or ECDH +// operations to a remote node over a +// walletrpc.WalletKit_SignCoordinatorStreamsServer stream. The stream is set up +// by the remote signer when it connects to the watch-only wallet, which should +// execute the Run method. +type SignCoordinator struct { + // nextRequestID keeps track of the next request ID that should + // be used when sending a request to the remote signer. + nextRequestID atomic.Uint64 + + // stream is a bi-directional stream between us and the remote signer. + stream StreamServer + + // responses is a map of request IDs to response channels. This map + // should be populated with a response channel for each request that has + // been sent to the remote signer. The response channel should be + // inserted into the map before the request is sent. + // Any response received over the stream that does not have an + // associated response channel in this map is ignored. + // The response channel should be removed from the map when the response + // has been received and processed. + responses *lnutils.SyncMap[uint64, chan *signerResponse] + + // receiveErrChan is used to signal that the stream with the remote + // signer has errored, and we can no longer process responses. + receiveErrChan chan error + + // doneReceiving is closed when either party terminates and signals to + // any pending requests that we'll no longer process the response for + // that request. + doneReceiving chan struct{} + + // quit is closed when lnd is shutting down. + quit chan struct{} + + // clientConnected is sent over when the remote signer connects. + clientConnected chan struct{} + + // requestTimeout is the maximum time we will wait for a response from + // the remote signer. + requestTimeout time.Duration + + // connectionTimeout is the maximum time we will wait for the remote + // signer to connect. + connectionTimeout time.Duration + + mu sync.Mutex + + wg sync.WaitGroup +} + +// A compile time assertion to ensure SignCoordinator meets the +// RemoteSignerRequests interface. +var _ RemoteSignerRequests = (*SignCoordinator)(nil) + +// NewSignCoordinator creates a new instance of the SignCoordinator. +func NewSignCoordinator(requestTimeout time.Duration, + connectionTimeout time.Duration) *SignCoordinator { + + respsMap := &lnutils.SyncMap[uint64, chan *signerResponse]{} + + s := &SignCoordinator{ + responses: respsMap, + receiveErrChan: make(chan error, 1), + doneReceiving: make(chan struct{}), + clientConnected: make(chan struct{}), + quit: make(chan struct{}), + requestTimeout: requestTimeout, + connectionTimeout: connectionTimeout, + } + + // We initialize the atomic nextRequestID to the handshakeRequestID, as + // requestID 1 is reserved for the initial handshake by the remote + // signer. + s.nextRequestID.Store(handshakeRequestID) + + return s +} + +// Run starts the SignCoordinator and blocks until the remote signer either +// disconnects, the SignCoordinator is shut down, or an error occurs. +func (s *SignCoordinator) Run(stream StreamServer) error { + s.mu.Lock() + + select { + case <-s.quit: + s.mu.Unlock() + return ErrShuttingDown + + case <-s.doneReceiving: + s.mu.Unlock() + return ErrNotConnected + + default: + } + + s.wg.Add(1) + defer s.wg.Done() + + // If we already have a stream, we error out as we can only have one + // connection throughout the lifetime of the SignCoordinator. + if s.stream != nil { + s.mu.Unlock() + return ErrMultipleConnections + } + + s.stream = stream + + s.mu.Unlock() + + // The handshake must be completed before we can start sending requests + // to the remote signer. + err := s.handshake(stream) + if err != nil { + return err + } + + log.Infof("Remote signer connected") + close(s.clientConnected) + + // Now let's start the main receiving loop, which will receive all + // responses to our requests from the remote signer! + // We start the receiving loop in a goroutine to ensure that this + // function exits if the SignCoordinator is shut down (i.e. the s.quit + // channel is closed). Returning from this function will cause the + // stream to be closed, which in turn will cause the receiving loop to + // exit. + s.wg.Add(1) + go s.StartReceiving() + + select { + case err := <-s.receiveErrChan: + return err + + case <-s.quit: + return ErrShuttingDown + + case <-s.doneReceiving: + return ErrNotConnected + } +} + +// Stop shuts down the SignCoordinator and waits until the main receiving loop +// has exited and all pending requests have been terminated. +func (s *SignCoordinator) Stop() { + log.Infof("Stopping Sign Coordinator") + defer log.Debugf("Sign coordinator stopped") + + // We lock the mutex before closing the quit channel to ensure that we + // can't get a concurrent request into the SignCoordinator while we're + // stopping it. That will ensure that the s.wg.Wait() call below will + // always wait for any ongoing requests to finish before we return. + s.mu.Lock() + + close(s.quit) + + s.mu.Unlock() + + s.wg.Wait() +} + +// handshake performs the initial handshake with the remote signer. This must +// be done before any other requests are sent to the remote signer. +func (s *SignCoordinator) handshake(stream StreamServer) error { + var ( + registerChan = make(chan *walletrpc.SignerRegistration) + registerDoneChan = make(chan struct{}) + errChan = make(chan error) + ) + + // Create a context with a timeout using the context from the stream as + // the parent context. This ensures that we'll exit if either the stream + // is closed by the remote signer or if we time out. + ctxt, cancel := context.WithTimeout( + stream.Context(), s.requestTimeout, + ) + defer cancel() + + // Read the first message in a goroutine because the Recv method blocks + // until the message arrives. + s.wg.Add(1) + go func() { + defer s.wg.Done() + + msg, err := stream.Recv() + if err != nil { + select { + case errChan <- err: + case <-ctxt.Done(): + } + + return + } + + if msg.GetRefRequestId() != handshakeRequestID { + err = fmt.Errorf("initial request ID must be %d, "+ + "but is: %d", handshakeRequestID, + msg.GetRefRequestId()) + + select { + case errChan <- err: + case <-ctxt.Done(): + } + + return + } + + switch req := msg.GetSignResponseType().(type) { + case *walletrpc.SignCoordinatorResponse_SignerRegistration: + select { + case registerChan <- req.SignerRegistration: + case <-ctxt.Done(): + } + + return + + default: + err := fmt.Errorf("expected registration message, "+ + "but got: %T", req) + + select { + case errChan <- err: + case <-ctxt.Done(): + } + + return + } + }() + + // Wait for the initial message to arrive or time out if it takes too + // long. The initial message must be a registration message from the + // remote signer. + select { + case signerRegistration := <-registerChan: + // TODO(viktor): This could be extended to validate the version + // of the remote signer in the future. + if signerRegistration.GetRegistrationInfo() == "" { + return errors.New("invalid remote signer " + + "registration info") + } + + // Todo(viktor): The RegistrationChallenge in the + // signerRegistration should likely also be signed here. + + case err := <-errChan: + return fmt.Errorf("error receiving initial remote signer "+ + "registration message: %v", err) + + case <-s.quit: + return ErrShuttingDown + + case <-ctxt.Done(): + return ctxt.Err() + } + + complete := &walletrpc.RegistrationResponse_RegistrationComplete{ + // TODO(viktor): The signature should be generated by signing + // the RegistrationChallenge contained in the SignerRegistration + // message in the future. + // The RegistrationInfo could also be extended to include info + // about the watch-only node in the future. + RegistrationComplete: &walletrpc.RegistrationComplete{ + Signature: "", + RegistrationInfo: "watch-only registration info", + }, + } + // Send a message to the client to indicate that the registration has + // successfully completed. + req := &walletrpc.SignCoordinatorRequest_RegistrationResponse{ + RegistrationResponse: &walletrpc.RegistrationResponse{ + RegistrationResponseType: complete, + }, + } + + regCompleteMsg := &walletrpc.SignCoordinatorRequest{ + RequestId: handshakeRequestID, + SignRequestType: req, + } + + // Send the message in a goroutine because the Send method blocks until + // the message is read by the client. + s.wg.Add(1) + go func() { + defer s.wg.Done() + + err := stream.Send(regCompleteMsg) + if err != nil { + select { + case errChan <- err: + case <-ctxt.Done(): + } + + return + } + + close(registerDoneChan) + }() + + select { + case err := <-errChan: + return fmt.Errorf("error sending registration complete "+ + " message to remote signer: %v", err) + + case <-ctxt.Done(): + return ctxt.Err() + + case <-s.quit: + return ErrShuttingDown + + case <-registerDoneChan: + } + + return nil +} + +// StartReceiving is the main receive loop that receives responses from the +// remote signer. Responses must have a RequestID that corresponds to requests +// which are waiting for a response; otherwise, the response is ignored. +func (s *SignCoordinator) StartReceiving() { + defer s.wg.Done() + + // Signals to any ongoing requests that the remote signer is no longer + // connected. + defer close(s.doneReceiving) + + for { + resp, err := s.stream.Recv() + if err != nil { + select { + // If we've already shut down, the main Run method will + // not be able to receive any error sent over the error + // channel. So we just return. + case <-s.quit: + + // Send the error over the error channel, so that the + // main Run method can return the error. + case s.receiveErrChan <- err: + } + + return + } + + respChan, ok := s.responses.Load(resp.GetRefRequestId()) + + if ok { + select { + // We should always be able to send over the response + // channel, as the channel allows for a buffer of 1, and + // we shouldn't have multiple requests and responses for + // the same request ID. + case respChan <- resp: + + case <-s.quit: + return + + // The timeout case be unreachable, as we should always + // be able to send 1 response over the response channel. + // We keep this case just to avoid a scenario where the + // receive loop would be blocked if we receive multiple + // responses for the same request ID. + case <-time.After(s.requestTimeout): + } + } + + // If there's no response channel, the thread waiting for the + // response has most likely timed out. We therefore ignore the + // response. The other scenario where we don't have a response + // channel would be if we received a response for a request that + // we didn't send. This should never happen, but if it does, we + // ignore the response. + + select { + case <-s.quit: + return + default: + } + } +} + +// WaitUntilConnected waits until the remote signer has connected. If the remote +// signer does not connect within the configured connection timeout, or if the +// passed context is canceled, an error is returned. +func (s *SignCoordinator) WaitUntilConnected(ctx context.Context) error { + select { + case <-s.clientConnected: + return nil + + case <-s.quit: + return ErrShuttingDown + + case <-ctx.Done(): + return ctx.Err() + + case <-time.After(s.connectionTimeout): + return ErrConnectTimeout + + case <-s.doneReceiving: + return ErrNotConnected + } +} + +// createResponseChannel creates a response channel for the given request ID and +// inserts it into the responses map. The function returns a cleanup function +// which removes the channel from the responses map, and the caller must ensure +// that this cleanup function is executed once the thread that's waiting for +// the response is done. +func (s *SignCoordinator) createResponseChannel(requestID uint64) func() { + // Create a new response channel. + respChan := make(chan *signerResponse, 1) + + // Insert the response channel into the map. + s.responses.Store(requestID, respChan) + + // Create a cleanup function that will delete the response channel. + return func() { + select { + // If we have timed out, there could be a very unlikely + // scenario where we did receive a response before we managed to + // grab the lock in the cleanup func. In that case, we'll just + // ignore the response. We should still clean up the response + // channel though. + case <-respChan: + default: + } + + s.responses.Delete(requestID) + } +} + +// getResponse waits for a response with the given request ID and returns the +// response if it is received. If the corresponding response from the remote +// signer is a SignerError, the error message is returned. If the response is +// not received within the given timeout, an error is returned. +// +// Note: Before calling this function, the caller must have created a response +// channel for the request ID. +func (s *SignCoordinator) getResponse(ctx context.Context, + requestID uint64) (*signerResponse, error) { + + respChan, ok := s.responses.Load(requestID) + + // Verify that we have a response channel for the request ID. + if !ok { + // It should be impossible to reach this case, as we create the + // response channel before sending the request. + return nil, fmt.Errorf("no response channel found for "+ + "request ID %d", requestID) + } + + // Wait for the response to arrive. + select { + case resp, ok := <-respChan: + if !ok { + // If the response channel was closed, we return an + // error as the receiving thread must have timed out + // before we managed to grab the response. + return nil, ErrRequestTimeout + } + + // a temp type alias to limit the length of the line below. + type sErr = walletrpc.SignCoordinatorResponse_SignerError + + // If the response is an error, we return the error message. + if errorResp, ok := resp.GetSignResponseType().(*sErr); ok { + errStr := errorResp.SignerError.GetError() + + log.Debugf("Received an error response from remote "+ + "signer for request ID %d. Error: %v", + requestID, errStr) + + return nil, errors.New(errStr) + } + + log.Debugf("Received remote signer %T response for request "+ + "ID %d", resp.GetSignResponseType(), requestID) + + log.Tracef("Remote signer response content: %v", + formatSignCoordinatorMsg(resp)) + + return resp, nil + + case <-s.doneReceiving: + log.Debugf("Stopped waiting for remote signer response for "+ + "request ID %d as the stream has been closed", + requestID) + + return nil, ErrNotConnected + + case <-s.quit: + log.Debugf("Stopped waiting for remote signer response for "+ + "request ID %d as we're shutting down", requestID) + + return nil, ErrShuttingDown + + case <-ctx.Done(): + log.Debugf("Context cancelled while waiting for remote signer "+ + "response for request ID %d", requestID) + + return nil, ctx.Err() + + case <-time.After(s.requestTimeout): + log.Debugf("Remote signer response timed out for request ID %d", + requestID) + + return nil, ErrRequestTimeout + } +} + +// registerRequest registers a new request with the SignCoordinator, ensuring it +// awaits the handling of the request before shutting down. The function returns +// a Done function that must be executed once the request has been handled. +func (s *SignCoordinator) registerRequest() (func(), error) { + // We lock the mutex to ensure that we can't have a race where we'd + // register a request while shutting down. + s.mu.Lock() + defer s.mu.Unlock() + + select { + case <-s.quit: + return nil, ErrShuttingDown + default: + } + + s.wg.Add(1) + + return func() { + s.wg.Done() + }, nil +} + +// Ping sends a ping request to the remote signer and waits for a pong response. +func (s *SignCoordinator) Ping(ctx context.Context, + timeout time.Duration) (bool, error) { + + req := &walletrpc.SignCoordinatorRequest_Ping{ + Ping: true, + } + + // As we're pinging, we will time out the request if we don't receive a + // response within the timeout. + ctxt, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + return processRequest( + ctxt, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) bool { + return resp.GetPong() + }, + ) +} + +// DeriveSharedKey sends a SharedKeyRequest to the remote signer and waits for +// the corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) DeriveSharedKey(ctx context.Context, + in *signrpc.SharedKeyRequest, + _ ...grpc.CallOption) (*signrpc.SharedKeyResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_SharedKeyRequest{ + SharedKeyRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.SharedKeyResponse { + return resp.GetSharedKeyResponse() + }, + ) +} + +// MuSig2Cleanup sends a MuSig2CleanupRequest to the remote signer and waits for +// the corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) MuSig2Cleanup(ctx context.Context, + in *signrpc.MuSig2CleanupRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2CleanupResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2CleanupRequest{ + MuSig2CleanupRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.MuSig2CleanupResponse { + return resp.GetMuSig2CleanupResponse() + }, + ) +} + +// MuSig2CombineSig sends a MuSig2CombineSigRequest to the remote signer and +// waits for the corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) MuSig2CombineSig(ctx context.Context, + in *signrpc.MuSig2CombineSigRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2CombineSigResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2CombineSigRequest{ + MuSig2CombineSigRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.MuSig2CombineSigResponse { + return resp.GetMuSig2CombineSigResponse() + }, + ) +} + +// MuSig2CreateSession sends a MuSig2SessionRequest to the remote signer and +// waits for the corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) MuSig2CreateSession(ctx context.Context, + in *signrpc.MuSig2SessionRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2SessionResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2SessionRequest{ + MuSig2SessionRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.MuSig2SessionResponse { + return resp.GetMuSig2SessionResponse() + }, + ) +} + +// MuSig2RegisterNonces sends a MuSig2RegisterNoncesRequest to the remote signer +// and waits for the corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) MuSig2RegisterNonces(ctx context.Context, + in *signrpc.MuSig2RegisterNoncesRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2RegisterNoncesResponse, + error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2RegisterNoncesRequest{ + MuSig2RegisterNoncesRequest: in, + } + + type muSig2RegisterNoncesResp = *signrpc.MuSig2RegisterNoncesResponse + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) muSig2RegisterNoncesResp { + return resp.GetMuSig2RegisterNoncesResponse() + }, + ) +} + +// MuSig2Sign sends a MuSig2SignRequest to the remote signer and waits for the +// corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) MuSig2Sign(ctx context.Context, + in *signrpc.MuSig2SignRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2SignResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2SignRequest{ + MuSig2SignRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.MuSig2SignResponse { + return resp.GetMuSig2SignResponse() + }, + ) +} + +// SignMessage sends a SignMessageReq to the remote signer and waits for the +// corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) SignMessage(ctx context.Context, + in *signrpc.SignMessageReq, + _ ...grpc.CallOption) (*signrpc.SignMessageResp, error) { + + req := &walletrpc.SignCoordinatorRequest_SignMessageReq{ + SignMessageReq: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *signrpc.SignMessageResp { + return resp.GetSignMessageResp() + }, + ) +} + +// SignPsbt sends a SignPsbtRequest to the remote signer and waits for the +// corresponding response. +// +// NOTE: This is part of the RemoteSignerRequests interface. +func (s *SignCoordinator) SignPsbt(ctx context.Context, + in *walletrpc.SignPsbtRequest, + _ ...grpc.CallOption) (*walletrpc.SignPsbtResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_SignPsbtRequest{ + SignPsbtRequest: in, + } + + return processRequest( + ctx, s, + func(reqId uint64) walletrpc.SignCoordinatorRequest { + return walletrpc.SignCoordinatorRequest{ + RequestId: reqId, + SignRequestType: req, + } + }, + func(resp *signerResponse) *walletrpc.SignPsbtResponse { + return resp.GetSignPsbtResponse() + }, + ) +} + +// processRequest is a generic function that sends a request to the remote +// signer and waits for the corresponding response. If a timeout is set, the +// function will limit the execution time of the entire function to the +// specified timeout. If it is not set, configured timeouts will be used for +// the individual operations within the function. +func processRequest[R comparable](ctx context.Context, s *SignCoordinator, + generateRequest func(uint64) walletrpc.SignCoordinatorRequest, + extractResponse func(*signerResponse) R) (R, error) { + + var zero R + + done, err := s.registerRequest() + if err != nil { + return zero, err + } + defer done() + + // Wait for the remote signer to connect. If the remote signer doesn't + // connect within the configured connection timeout, or before the ctx + // times out, we will return an error. + err = s.WaitUntilConnected(ctx) + + if err != nil { + return zero, err + } + + reqID := s.nextRequestID.Add(1) + req := generateRequest(reqID) + + cleanUpChannel := s.createResponseChannel(reqID) + defer cleanUpChannel() + + log.Debugf("Sending a %T to the remote signer with request ID %d", + req.SignRequestType, reqID) + + log.Tracef("Request content: %v", formatSignCoordinatorMsg(&req)) + + err = s.stream.Send(&req) + if err != nil { + return zero, err + } + + var resp *walletrpc.SignCoordinatorResponse + + // Wait for the remote signer response for the given request. We will + // wait for the configured request timeout, or until the context is + // cancelled/timed out. + resp, err = s.getResponse(ctx, reqID) + + if err != nil { + return zero, err + } + + rpcResp := extractResponse(resp) + if rpcResp == zero { + return zero, ErrUnexpectedResponse + } + + return rpcResp, nil +} diff --git a/lnwallet/rpcwallet/sign_coordinator_test.go b/lnwallet/rpcwallet/sign_coordinator_test.go new file mode 100644 index 00000000000..fcbc79d32f0 --- /dev/null +++ b/lnwallet/rpcwallet/sign_coordinator_test.go @@ -0,0 +1,772 @@ +package rpcwallet + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +// mockSCStream is a mock implementation of the +// walletrpc.WalletKit_SignCoordinatorStreamsServer stream interface. +type mockSCStream struct { + // sendChan is used to simulate requests sent over the stream from the + // sign coordinator to the remote signer. + sendChan chan *walletrpc.SignCoordinatorRequest + + // recvChan is used to simulate responses sent over the stream from the + // remote signer to the sign coordinator. + recvChan chan *walletrpc.SignCoordinatorResponse + + // cancelChan is used to simulate a canceled stream. + cancelChan chan struct{} + + ctx context.Context //nolint:containedctx +} + +// newMockSCStream creates a new mock stream. +func newMockSCStream() *mockSCStream { + return &mockSCStream{ + sendChan: make(chan *walletrpc.SignCoordinatorRequest, 1), + recvChan: make(chan *walletrpc.SignCoordinatorResponse, 1), + cancelChan: make(chan struct{}), + ctx: context.Background(), + } +} + +// Send simulates a sent request from the sign coordinator to the remote signer +// over the mock stream. +func (ms *mockSCStream) Send(req *walletrpc.SignCoordinatorRequest) error { + select { + case ms.sendChan <- req: + return nil + + case <-ms.cancelChan: + return ErrStreamCanceled + } +} + +// Recv simulates a received response from the remote signer to the sign +// coordinator over the mock stream. +func (ms *mockSCStream) Recv() (*walletrpc.SignCoordinatorResponse, error) { + select { + case resp := <-ms.recvChan: + return resp, nil + + case <-ms.cancelChan: + // To simulate a canceled stream, we return an error when the + // cancelChan is closed. + return nil, ErrStreamCanceled + } +} + +// Mock implementations of various WalletKit_SignCoordinatorStreamsServer +// methods. +func (ms *mockSCStream) RecvMsg(msg any) error { return nil } +func (ms *mockSCStream) SendHeader(metadata.MD) error { return nil } +func (ms *mockSCStream) SendMsg(m any) error { return nil } +func (ms *mockSCStream) SetHeader(metadata.MD) error { return nil } +func (ms *mockSCStream) SetTrailer(metadata.MD) {} + +// Context returns the context of the mock stream. +func (ms *mockSCStream) Context() context.Context { + return ms.ctx +} + +// Cancel closes the cancelChan to simulate a canceled stream. +func (ms *mockSCStream) Cancel() { + close(ms.cancelChan) +} + +// Helper function to simulate responses sent over the mock stream. +func (ms *mockSCStream) sendResponse(resp *walletrpc.SignCoordinatorResponse) { + ms.recvChan <- resp +} + +// setupSignCoordinator sets up a new SignCoordinator instance with a mock +// stream to simulate communication with a remote signer. It also simulates the +// handshake between the sign coordinator and the remote signer. +func setupSignCoordinator(t *testing.T) (*SignCoordinator, *mockSCStream, + chan error) { + + stream := newMockSCStream() + coordinator := NewSignCoordinator(2*time.Second, 3*time.Second) + + errChan := make(chan error) + go func() { + err := coordinator.Run(stream) + if err != nil { + errChan <- err + } + }() + + signReg := &walletrpc.SignerRegistration{ + RegistrationChallenge: "registrationChallenge", + RegistrationInfo: "outboundSigner", + } + + regType := &walletrpc.SignCoordinatorResponse_SignerRegistration{ + SignerRegistration: signReg, + } + + registrationMsg := &walletrpc.SignCoordinatorResponse{ + RefRequestId: 1, // Request ID is always 1 for registration. + SignResponseType: regType, + } + + stream.sendResponse(registrationMsg) + + // Ensure that the sign coordinator responds with a registration + // complete message. + select { + case req := <-stream.sendChan: + require.Equal(t, uint64(1), req.GetRequestId()) + + comp := req.GetRegistrationResponse().GetRegistrationComplete() + require.NotNil(t, comp) + + case <-time.After(time.Second): + require.Fail( + t, "registration complete message not received", + ) + } + + return coordinator, stream, errChan +} + +// getRequest is a helper function to get a request that has been sent from +// the sign coordinator over the mock stream. +func getRequest(s *mockSCStream) (*walletrpc.SignCoordinatorRequest, error) { + select { + case req := <-s.sendChan: + return req, nil + + case <-time.After(time.Second): + return nil, ErrRequestTimeout + } +} + +// TestPingRequests tests that the sign coordinator correctly sends a Ping +// request to the remote signer and handles the received Pong response +// correctly. +func TestPingRequests(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + var wg sync.WaitGroup + + // Send a Ping request in a goroutine so that we can pick up the request + // sent over the mock stream, and respond accordingly. + wg.Add(1) + + go func() { + defer wg.Done() + + // The Ping method will return true if the response is a Pong + // response. + success, err := coordinator.Ping(ctx, 2*time.Second) + require.NoError(t, err) + require.True(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now we simulate the response from the remote signer by sending a Pong + // response over the mock stream. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 2, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Wait for the goroutines to finish, which should only happen after the + // requests have had their expected responses processed. + wg.Wait() + + // Verify the responses map is empty after all responses are received + // to ensure that no memory leaks occur. + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestConcurrentPingRequests tests that the sign coordinator correctly handles +// concurrent Ping requests and responses, and that the order in which responses +// are sent back over the stream doesn't matter. +func TestConcurrentPingRequests(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + var wg sync.WaitGroup + + // Let's first start by sending two concurrent Ping requests and sending + // the respective responses back in order. + wg.Add(1) + + go func() { + defer wg.Done() + success, err := coordinator.Ping(ctx, 2*time.Second) + + require.NoError(t, err) + require.True(t, success) + }() + + // Get the first request sent over the mock stream. + req1, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req1.GetRequestId()) + require.True(t, req1.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now we send the second Ping request. + wg.Add(1) + + go func() { + defer wg.Done() + success, err := coordinator.Ping(ctx, 2*time.Second) + + require.NoError(t, err) + require.True(t, success) + }() + + // Get the second request sent over the mock stream. + req2, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(3), req2.GetRequestId()) + require.True(t, req2.GetPing()) + + // Verify that the coordinator has correctly set up two response + // channels for the Ping requests with their specific request IDs. + require.Equal(t, coordinator.responses.Len(), 2) + _, ok = coordinator.responses.Load(uint64(3)) + require.True(t, ok) + + // Send responses for both Ping requests in order. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 2, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 3, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Wait for the goroutines to finish, which should only happen after the + // requests have had their expected responses processed. + wg.Wait() + + // Verify the responses map is empty after all responses are received. + require.Equal(t, coordinator.responses.Len(), 0) + + // Now let's verify that the sign coordinator can correctly process + // responses that are sent back in a different order than the requests + // were sent. + + // Send a new set of concurrent Ping requests. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, 2*time.Second) + require.NoError(t, err) + require.True(t, success) + }() + + req3, err := getRequest(stream) + require.NoError(t, err) + + require.Equal(t, uint64(4), req3.GetRequestId()) + require.True(t, req3.GetPing()) + + // Verify that the coordinator has removed the response channels for the + // previous Ping requests and set up a new one for the new request. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok = coordinator.responses.Load(uint64(4)) + require.True(t, ok) + + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, 2*time.Second) + require.NoError(t, err) + require.True(t, success) + }() + + req4, err := getRequest(stream) + require.NoError(t, err) + + require.Equal(t, uint64(5), req4.GetRequestId()) + require.True(t, req4.GetPing()) + + require.Equal(t, coordinator.responses.Len(), 2) + _, ok = coordinator.responses.Load(uint64(5)) + require.True(t, ok) + + // Send the responses back in reverse order. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 5, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 4, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Wait for the goroutines to finish, which should only happen after the + // requests have had their expected responses processed. + wg.Wait() + + // Verify the responses map is empty after all responses are received + // to ensure that no memory leaks occur. + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestPingTimeout tests that the sign coordinator correctly handles a Ping +// request that times out. +func TestPingTimeout(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + // Simulate a Ping request that times out. + _, err := coordinator.Ping(ctx, 1*time.Second) + require.Equal(t, context.DeadlineExceeded, err) + + // Verify that the responses map is empty after the timeout. + require.Equal(t, coordinator.responses.Len(), 0) + + // Now let's simulate that the response is sent back after the request + // has timed out. + req, err := getRequest(stream) + require.NoError(t, err) + + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 2, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Verify that the responses map still remains empty, as responses for + // timed out requests are ignored. + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestConcurrentPingTimeout tests that the sign coordinator correctly handles a +// Ping request that times out, while another Ping request is still pending +// and then receives a response. +func TestConcurrentPingTimeout(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + var wg sync.WaitGroup + + timeoutChan := make(chan struct{}) + + // Send a Ping request that is expected to time out. + wg.Add(1) + + go func() { + defer wg.Done() + + // Note that the timeout is set to 1 second. + success, err := coordinator.Ping(ctx, 1*time.Second) + require.Equal(t, context.DeadlineExceeded, err) + require.False(t, success) + + // Signal that the request has timed out. + close(timeoutChan) + }() + + // Get the request sent over the mock stream. + req1, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req1.GetRequestId()) + require.True(t, req1.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now let's send another Ping request that will receive a response. + wg.Add(1) + + go func() { + defer wg.Done() + + // Note that the timeout is set to 2 seconds and will therefore + // time out later than the first request. + success, err := coordinator.Ping(ctx, 2*time.Second) + require.NoError(t, err) + require.True(t, success) + }() + + // Get the second request sent over the mock stream. + req2, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(3), req2.GetRequestId()) + require.True(t, req2.GetPing()) + + // Verify that the coordinator has correctly set up two response + // channels for the Ping requests with their specific request IDs. + require.Equal(t, coordinator.responses.Len(), 2) + _, ok = coordinator.responses.Load(uint64(3)) + require.True(t, ok) + + // Now let's wait for the first request to time out. + <-timeoutChan + + // Ensure that this leads to the sign coordinator removing the response + // channel for the timed-out request. + require.Equal(t, coordinator.responses.Len(), 1) + + // The second request should still be pending, so the responses map + // should contain the response channel for the second request. + _, ok = coordinator.responses.Load(uint64(3)) + require.True(t, ok) + + // Send responses for the second Ping request. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 3, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Wait for the goroutines to finish, which should only happen after the + // second request has had its expected response processed. + wg.Wait() + + // Verify the responses map is empty after all responses have been + // handled, to ensure that no memory leaks occur. + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestIncorrectResponseRequestId tests that the sign coordinator correctly +// ignores responses with an unknown request ID. +func TestIncorrectResponseRequestId(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + var wg sync.WaitGroup + + // Save the start time of the test. + startTime := time.Now() + pingTimeout := 2 * time.Second + + wg.Add(1) + + // Send a Ping request that times out in 2 seconds. + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, pingTimeout) + require.Equal(t, ErrRequestTimeout, err) + require.False(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now let's send a response with another request ID than the Ping + // request. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 3, // Incorrect request ID + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Ensure that the response is ignored and that the responses map still + // contains the response channel for the Ping request until it times + // out. We allow a small margin of error to account for the time it + // takes to execute the Invariant function. + err = wait.Invariant(func() bool { + correctLen := coordinator.responses.Len() == 1 + _, ok = coordinator.responses.Load(uint64(2)) + + return correctLen && ok + }, pingTimeout-time.Since(startTime)-100*time.Millisecond) + require.NoError(t, err) + + // Wait for the goroutines to finish, which should only happen after the + // request has timed out and verified the error. + wg.Wait() + + // Verify the responses map is empty after all responses are received + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestSignerErrorResponse tests that the sign coordinator correctly handles a +// SignerError response from the remote signer. +func TestSignerErrorResponse(t *testing.T) { + t.Parallel() + + coordinator, stream, _ := setupSignCoordinator(t) + ctx := context.Background() + + var wg sync.WaitGroup + + // Send a Ping request that will receive a SignerError response. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, 1*time.Second) + // Ensure that the result from the Ping method is an error, + // which is the expected result when a SignerError response is + // received. + require.Equal(t, "mock error", err.Error()) + require.False(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now let's send a SignerError response instead of a Pong back over the + // mock stream. + rType := &walletrpc.SignCoordinatorResponse_SignerError{ + SignerError: &walletrpc.SignerError{ + Error: "mock error", + }, + } + + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 2, + SignResponseType: rType, + }) + + // Wait for the goroutines to finish, which should only happen after the + // request has had its expected response processed. + wg.Wait() + + // Verify the responses map is empty after all responses have been + // processed, to ensure that no memory leaks occur. + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestStopCoordinator tests that the sign coordinator correctly stops +// processing responses for any pending requests when the sign coordinator is +// stopped. +func TestStopCoordinator(t *testing.T) { + t.Parallel() + + coordinator, stream, runErrChan := setupSignCoordinator(t) + ctx := context.Background() + + pingTimeout := 3 * time.Second + startTime := time.Now() + + var wg sync.WaitGroup + + // Send a Ping request with a long timeout to ensure that the request + // will not time out before the coordinator is stopped. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, pingTimeout) + require.Equal(t, ErrShuttingDown, err) + require.False(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now let's stop the sign coordinator. + wg.Add(1) + + go func() { + defer wg.Done() + + coordinator.Stop() + }() + + // When the coordinator is stopped, the Run function will return an + // error that gets sent over the runErrChan. + err = <-runErrChan + + // Ensure that the Run function returned the expected error that lnd is + // shutting down. + require.Equal(t, ErrShuttingDown, err) + + // As the coordinator Run function returned the ErrShuttingDown error, + // lnd would normally cancel the stream. We simulate this by calling the + // Cancel method on the mock stream. + stream.Cancel() + + // Ensure that both the Ping request goroutine and the sign coordinator + // Stop goroutine have finished. + wg.Wait() + + // Ensure that the Ping request goroutine returned before the timeout + // was reached, which indicates that the request was canceled because + // the sign coordinator was stopped. + require.Less(t, time.Since(startTime), pingTimeout) + + // Verify the responses map is empty after all responses are received + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestRemoteSignerDisconnects tests that the sign coordinator correctly handles +// the remote signer disconnecting, which closes the stream. +func TestRemoteSignerDisconnects(t *testing.T) { + t.Parallel() + + coordinator, stream, runErrChan := setupSignCoordinator(t) + ctx := context.Background() + + pingTimeout := 3 * time.Second + startTime := time.Now() + + var wg sync.WaitGroup + + // Send a Ping request with a long timeout to ensure that the request + // will not time out before the remote signer disconnects. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, pingTimeout) + require.Equal(t, ErrNotConnected, err) + require.False(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // We simulate the remote signer disconnecting by canceling the + // stream. + stream.Cancel() + + // This should cause the Run function to return the error that the + // stream was canceled with. + err = <-runErrChan + require.Equal(t, ErrStreamCanceled, err) + + // Ensure that the Ping request goroutine has finished. + wg.Wait() + + // Verify that the coordinator signals that it's done receiving + // responses after the stream is canceled, i.e. the StartReceiving + // function is no longer running. + <-coordinator.doneReceiving + + // Ensure that the Ping request goroutine returned before the timeout + // was reached, which indicates that the request was canceled because + // the remote signer disconnected. + require.Less(t, time.Since(startTime), pingTimeout) + + // Verify the responses map is empty after all responses are received + require.Equal(t, coordinator.responses.Len(), 0) +} From e0c4df7bcd6d4e54cf3993251cda3c96eb10f4b3 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Dec 2024 01:31:55 +0100 Subject: [PATCH 19/39] rpcwallet: add `InboundConnection` implementation As the previous commit implemented the foundation for the watch-only node to send and receive messages with an outbound remote signer (the `SignCoordinator` implementation), we can now wrap the communication in the `RemoteSignerConnection` interface, making it usable through the `RPCKeyRing`. This commit introduces the `InboundConnection` implementation to achieve that. --- .../rpcwallet/remote_signer_connection.go | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/lnwallet/rpcwallet/remote_signer_connection.go b/lnwallet/rpcwallet/remote_signer_connection.go index 6f96c52cc9c..3cf5a057421 100644 --- a/lnwallet/rpcwallet/remote_signer_connection.go +++ b/lnwallet/rpcwallet/remote_signer_connection.go @@ -262,3 +262,98 @@ func (r *OutboundConnection) connect(ctx context.Context, // A compile time assertion to ensure OutboundConnection meets the // RemoteSignerConnection interface. var _ RemoteSignerConnection = (*OutboundConnection)(nil) + +// InboundRemoteSignerConnection is an interface that abstracts the +// communication with an outbound remote signer. It extends the +// RemoteSignerConnection insterface. +type InboundRemoteSignerConnection interface { + RemoteSignerConnection + + // AddConnection feeds the inbound connection handler with the incoming + // stream set up by an outbound remote signer and then blocks until the + // stream is closed. Lnd can then send any requests to the remote signer + // through the stream. + AddConnection(stream StreamServer) error +} + +// InboundConnection is an abstraction that manages the inbound connection that +// is set up by an outbound remote signer that connects to the watch-only node. +type InboundConnection struct { + *SignCoordinator + + connectionTimeout time.Duration +} + +// NewInboundConnection creates a new InboundConnection instance. +func NewInboundConnection(requestTimeout time.Duration, + connectionTimeout time.Duration) *InboundConnection { + + remoteSigner := &InboundConnection{ + connectionTimeout: connectionTimeout, + } + + remoteSigner.SignCoordinator = NewSignCoordinator( + requestTimeout, connectionTimeout, + ) + + return remoteSigner +} + +// Timeout returns the set connection timeout for the remote signer. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *InboundConnection) Timeout() time.Duration { + return r.connectionTimeout +} + +// Ready returns a channel that nil gets sent over once the remote signer +// connected and is ready to accept requests. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *InboundConnection) Ready(ctx context.Context) chan error { + readyChan := make(chan error, 1) + + // We wait for the remote signer to connect in a go func and signal + // over the channel once it's ready. + go func() { + log.Infof("Waiting for the remote signer to connect") + + readyChan <- r.SignCoordinator.WaitUntilConnected(ctx) + close(readyChan) + }() + + return readyChan +} + +// Ping verifies that the remote signer is still responsive. +// +// NOTE: This is part of the RemoteSignerConnection interface. +func (r *InboundConnection) Ping(ctx context.Context, + timeout time.Duration) error { + + pong, err := r.SignCoordinator.Ping(ctx, timeout) + if err != nil { + return fmt.Errorf("ping request to remote signer "+ + "errored: %w", err) + } + + if !pong { + return errors.New("incorrect Pong response from remote signer") + } + + return nil +} + +// AddConnection feeds the inbound connection handler with the incoming stream +// set up by an outbound remote signer and then blocks until the stream is +// closed. Lnd can then send any requests to the remote signer through the +// stream. +// +// NOTE: This is part of the InboundRemoteSignerConnection interface. +func (r *InboundConnection) AddConnection(stream StreamServer) error { + return r.SignCoordinator.Run(stream) +} + +// A compile time assertion to ensure InboundConnection meets the +// RemoteSigner interface. +var _ InboundRemoteSignerConnection = (*InboundConnection)(nil) From 5d040b93983579a4a4e48fd638ead5d4d107c49b Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 10:43:20 +0200 Subject: [PATCH 20/39] lnrpc: add `AllowRemoteSigner` `WalletState` proto To accept incoming connections from the remote signer and use the remote signer stream for any required signatures on the watch-only node, we must allow the connection from the remote signer before any signatures are needed. Currently, we only allow requests through the `InterceptorChain` into the rpc-servers after the `WalletState` has been set to `RpcActive`. This status is only set once the main `RpcServer`, along with all sub-servers, have been fully started and populated with their dependencies. The problem is that we need signatures from the remote signer to create some of the dependencies for the sub-servers. Because of this, we need to let the remote signer connect before all dependencies are created. To enable this, we add a new `WalletState`, `AllowRemoteSigner`, which allows connection requests from a remote signer to pass through the `InterceptorChain` when the `AllowRemoteSigner` state is set. This state is set before the `RpcActive` state. --- lnrpc/stateservice.pb.go | 61 ++++++++++++++++++--------------- lnrpc/stateservice.proto | 4 +++ lnrpc/stateservice.swagger.json | 3 +- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/lnrpc/stateservice.pb.go b/lnrpc/stateservice.pb.go index d6f6a23b228..e788181c882 100644 --- a/lnrpc/stateservice.pb.go +++ b/lnrpc/stateservice.pb.go @@ -30,6 +30,9 @@ const ( // UNLOCKED means that the wallet was unlocked successfully, but RPC server // isn't ready. WalletState_UNLOCKED WalletState = 2 + // ALLOW_REMOTE_SIGNER means that the lnd server in a state where it's only + // ready to accept incoming connections from a remote signer. + WalletState_ALLOW_REMOTE_SIGNER WalletState = 5 // RPC_ACTIVE means that the lnd server is active but not fully ready for // calls. WalletState_RPC_ACTIVE WalletState = 3 @@ -46,17 +49,19 @@ var ( 0: "NON_EXISTING", 1: "LOCKED", 2: "UNLOCKED", + 5: "ALLOW_REMOTE_SIGNER", 3: "RPC_ACTIVE", 4: "SERVER_ACTIVE", 255: "WAITING_TO_START", } WalletState_value = map[string]int32{ - "NON_EXISTING": 0, - "LOCKED": 1, - "UNLOCKED": 2, - "RPC_ACTIVE": 3, - "SERVER_ACTIVE": 4, - "WAITING_TO_START": 255, + "NON_EXISTING": 0, + "LOCKED": 1, + "UNLOCKED": 2, + "ALLOW_REMOTE_SIGNER": 5, + "RPC_ACTIVE": 3, + "SERVER_ACTIVE": 4, + "WAITING_TO_START": 255, } ) @@ -272,27 +277,29 @@ var file_stateservice_proto_rawDesc = []byte{ 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x73, 0x0a, 0x0b, 0x57, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x4e, 0x5f, - 0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, - 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b, - 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, - 0x56, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41, - 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x10, 0x57, 0x41, 0x49, 0x54, 0x49, - 0x4e, 0x47, 0x5f, 0x54, 0x4f, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xff, 0x01, 0x32, 0x95, - 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x8c, 0x01, 0x0a, 0x0b, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x4e, + 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, + 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x4c, 0x4f, 0x43, + 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x52, + 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x52, 0x10, 0x05, 0x12, 0x0e, + 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x11, + 0x0a, 0x0d, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, + 0x04, 0x12, 0x15, 0x0a, 0x10, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x4f, 0x5f, + 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xff, 0x01, 0x32, 0x95, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/lnrpc/stateservice.proto b/lnrpc/stateservice.proto index 97a78d31ad6..ee29273885b 100644 --- a/lnrpc/stateservice.proto +++ b/lnrpc/stateservice.proto @@ -46,6 +46,10 @@ enum WalletState { // isn't ready. UNLOCKED = 2; + // ALLOW_REMOTE_SIGNER means that the lnd server in a state where it's only + // ready to accept incoming connections from a remote signer. + ALLOW_REMOTE_SIGNER = 5; + // RPC_ACTIVE means that the lnd server is active but not fully ready for // calls. RPC_ACTIVE = 3; diff --git a/lnrpc/stateservice.swagger.json b/lnrpc/stateservice.swagger.json index 7b8fe1db09d..7ca0b983412 100644 --- a/lnrpc/stateservice.swagger.json +++ b/lnrpc/stateservice.swagger.json @@ -95,12 +95,13 @@ "NON_EXISTING", "LOCKED", "UNLOCKED", + "ALLOW_REMOTE_SIGNER", "RPC_ACTIVE", "SERVER_ACTIVE", "WAITING_TO_START" ], "default": "NON_EXISTING", - "description": " - NON_EXISTING: NON_EXISTING means that the wallet has not yet been initialized.\n - LOCKED: LOCKED means that the wallet is locked and requires a password to unlock.\n - UNLOCKED: UNLOCKED means that the wallet was unlocked successfully, but RPC server\nisn't ready.\n - RPC_ACTIVE: RPC_ACTIVE means that the lnd server is active but not fully ready for\ncalls.\n - SERVER_ACTIVE: SERVER_ACTIVE means that the lnd server is ready to accept calls.\n - WAITING_TO_START: WAITING_TO_START means that node is waiting to become the leader in a\ncluster and is not started yet." + "description": " - NON_EXISTING: NON_EXISTING means that the wallet has not yet been initialized.\n - LOCKED: LOCKED means that the wallet is locked and requires a password to unlock.\n - UNLOCKED: UNLOCKED means that the wallet was unlocked successfully, but RPC server\nisn't ready.\n - ALLOW_REMOTE_SIGNER: ALLOW_REMOTE_SIGNER means that the lnd server in a state where it's only\nready to accept incoming connections from a remote signer.\n - RPC_ACTIVE: RPC_ACTIVE means that the lnd server is active but not fully ready for\ncalls.\n - SERVER_ACTIVE: SERVER_ACTIVE means that the lnd server is ready to accept calls.\n - WAITING_TO_START: WAITING_TO_START means that node is waiting to become the leader in a\ncluster and is not started yet." }, "protobufAny": { "type": "object", From 2c3a95af64558357a0d1a983eb396e5fbbfda5e6 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 10:49:53 +0200 Subject: [PATCH 21/39] rpcperms: allow some RPCs before `rpcActive` state Change the `InterceptorChain` behavior to allow a remote signer to call the `walletrpc.SignCoordinatorStreams` while the `rpcState` is set to `allowRemoteSigner`. This state precedes the `rpcActive` state, which allows all RPCs. This change is necessary because `lnd` needs the remote signer to be connected before some of the internal dependencies for RPC sub-servers can be created. These dependencies must be inserted into the sub-servers before moving the `rpcState` to `rpcActive`. --- rpcperms/interceptor.go | 62 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index 9bbef0414da..9f42cbccb83 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -43,6 +43,11 @@ const ( // RPC server is not yet ready. walletUnlocked + // allowRemoteSigner means that the wallet is unlocked, and that we're + // waiting for the remote signer to connect before proceeding. Only + // rpc calls to connect the remote signer are allowed during this state. + allowRemoteSigner + // rpcActive means that the RPC server is ready to accept calls. rpcActive @@ -70,6 +75,12 @@ var ( ErrWalletUnlocked = fmt.Errorf("wallet already unlocked, " + "WalletUnlocker service is no longer available") + // ErrAwaitingRemoteSigner is returned if an RPC call is made, other + // than an RPC call to connect a remote signer, while LND is waiting for + // a remote signer to connect. + ErrAwaitingRemoteSigner = fmt.Errorf("waiting for remote signer to " + + "connect before other RPC calls can be accepted") + // ErrRPCStarting is returned if the wallet has been unlocked but the // RPC server is not yet ready to accept calls. ErrRPCStarting = fmt.Errorf("the RPC server is in the process of " + @@ -94,6 +105,15 @@ var ( "/lnrpc.State/SubscribeState": {}, "/lnrpc.State/GetState": {}, } + + // allowRemoteSignerWhitelist defines methods that we allow to be called + // when we are waiting for the remote signer to connect, i.e. in the + // allowRemoteSigner state. + allowRemoteSignerWhitelist = map[string]struct{}{ + "/walletrpc.WalletKit/SignCoordinatorStreams": {}, + + "/lnrpc.Lightning/StopDaemon": {}, + } ) // InterceptorChain is a struct that can be added to the running GRPC server, @@ -265,7 +285,18 @@ func (r *InterceptorChain) SetWalletUnlocked() { _ = r.ntfnServer.SendUpdate(r.state) } -// SetRPCActive moves the RPC state from walletUnlocked to rpcActive. +// SetAllowRemoteSigner moves the RPC state from walletUnlocked to +// waitRemoteSigner. +func (r *InterceptorChain) SetAllowRemoteSigner() { + r.Lock() + defer r.Unlock() + + r.state = allowRemoteSigner + _ = r.ntfnServer.SendUpdate(r.state) +} + +// SetRPCActive moves the RPC state from either walletUnlocked or +// waitRemoteSigner to rpcActive. func (r *InterceptorChain) SetRPCActive() { r.Lock() defer r.Unlock() @@ -298,6 +329,8 @@ func rpcStateToWalletState(state rpcState) (lnrpc.WalletState, error) { walletState = lnrpc.WalletState_LOCKED case walletUnlocked: walletState = lnrpc.WalletState_UNLOCKED + case allowRemoteSigner: + walletState = lnrpc.WalletState_ALLOW_REMOTE_SIGNER case rpcActive: walletState = lnrpc.WalletState_RPC_ACTIVE case serverActive: @@ -708,7 +741,9 @@ func (r *InterceptorChain) MacaroonStreamServerInterceptor() grpc.StreamServerIn // checkRPCState checks whether a call to the given server is allowed in the // current RPC state. -func (r *InterceptorChain) checkRPCState(srv interface{}) error { +func (r *InterceptorChain) checkRPCState(srv interface{}, + fullMethod string) error { + // The StateService is being accessed, we allow the call regardless of // the current state. _, ok := srv.(lnrpc.StateServer) @@ -751,6 +786,22 @@ func (r *InterceptorChain) checkRPCState(srv interface{}) error { return ErrRPCStarting + // If lnd is waiting for the remote signer to connect, we only allow + // calls to the remote signer. + case allowRemoteSigner: + _, ok := srv.(lnrpc.WalletUnlockerServer) + if ok { + return ErrWalletUnlocked + } + + // As we only allow calls to connect the remote signer until the + // full rpc server is active, we check whether the method is + // whitelisted or not. + _, ok = allowRemoteSignerWhitelist[fullMethod] + if !ok { + return ErrAwaitingRemoteSigner + } + // If the RPC server or lnd server is active, we allow calls to any // service except the WalletUnlocker. case rpcActive, serverActive: @@ -772,9 +823,10 @@ func (r *InterceptorChain) rpcStateUnaryServerInterceptor() grpc.UnaryServerInte return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - r.rpcsLog.Debugf("[%v] requested", info.FullMethod) + method := info.FullMethod + r.rpcsLog.Debugf("[%v] requested", method) - if err := r.checkRPCState(info.Server); err != nil { + if err := r.checkRPCState(info.Server, method); err != nil { return nil, err } @@ -790,7 +842,7 @@ func (r *InterceptorChain) rpcStateStreamServerInterceptor() grpc.StreamServerIn r.rpcsLog.Debugf("[%v] requested", info.FullMethod) - if err := r.checkRPCState(srv); err != nil { + if err := r.checkRPCState(srv, info.FullMethod); err != nil { return err } From f53ca65391798404e36cf4552d087c8900abd109 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 11:08:00 +0200 Subject: [PATCH 22/39] rpcperms: fix `SetServerActive` function docs typo The `SetServerActive` moves the `rpcState` from `rpcActive` to `serverActive`. Update the docs to correctly reflect that. --- rpcperms/interceptor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index 9f42cbccb83..bf83e4d8b30 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -305,7 +305,7 @@ func (r *InterceptorChain) SetRPCActive() { _ = r.ntfnServer.SendUpdate(r.state) } -// SetServerActive moves the RPC state from walletUnlocked to rpcActive. +// SetServerActive moves the RPC state from rpcActive to serverActive. func (r *InterceptorChain) SetServerActive() { r.Lock() defer r.Unlock() From 97d35f7a5530e18e80b2ae1f669a8203cf064cf0 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:06:19 +0200 Subject: [PATCH 23/39] multi: enable `RpcServer` before dependencies exist To enable an outbound remote signer to connect to lnd before all dependencies for the RPC sub-servers are created, we need to separate the process of adding dependencies to the sub-servers from created the sub-servers. Prior to this commit, the RPC sub-servers were created and enabled only after all dependencies were in place. Such a limitation prevents accepting an incoming connection request from an outbound remote signer (e.g., a `walletrpc.SignCoordinatorStreams` RPC call) to the `WalletKitServer` until all dependencies for the RPC sub-servers are created. However, this limitation would not work, as we need the remote signer in order to create some of the dependencies for the other RPC sub-servers. Therefore, we need to enable calls to at least the `WalletKitServer` and the main RPC server before creating the remaining dependencies. This commit refactors the logic for the main RPC server and sub-servers, allowing them to be enabled before dependencies are inserted into the sub-servers. The `WalletState` for the `InterceptorChain` is only set to `RpcActive` after all dependencies have been created and inserted, ensuring that RPC requests won't be allowed into the sub-servers before the dependencies exist. An upcoming commit will set the state to `AllowRemoteSigner` before all dependencies are created, enabling an outbound remote signer to connect when needed. --- lnd.go | 29 +++- lnrpc/autopilotrpc/autopilot_server.go | 76 ++++++---- lnrpc/autopilotrpc/driver.go | 40 ++++-- lnrpc/chainrpc/chain_server.go | 104 ++++++++------ lnrpc/chainrpc/driver.go | 51 ++++--- lnrpc/devrpc/dev_server.go | 46 +++--- lnrpc/devrpc/driver.go | 42 ++++-- lnrpc/invoicesrpc/driver.go | 35 +++-- lnrpc/invoicesrpc/invoices_server.go | 88 +++++++----- lnrpc/neutrinorpc/driver.go | 35 +++-- lnrpc/neutrinorpc/neutrino_server.go | 47 ++++--- lnrpc/peersrpc/driver.go | 35 +++-- lnrpc/peersrpc/peers_server.go | 48 ++++--- lnrpc/routerrpc/driver.go | 46 +++--- lnrpc/routerrpc/router_server.go | 95 +++++++------ lnrpc/rpc_utils.go | 5 + lnrpc/signrpc/driver.go | 56 +++++--- lnrpc/signrpc/signer_server.go | 83 ++++++----- lnrpc/sub_server.go | 21 +-- lnrpc/verrpc/server.go | 22 +-- lnrpc/walletrpc/driver.go | 68 +++++---- lnrpc/walletrpc/walletkit_server.go | 75 ++++++---- lnrpc/walletrpc/walletkit_server_test.go | 8 +- lnrpc/watchtowerrpc/driver.go | 36 +++-- lnrpc/watchtowerrpc/handler.go | 43 ++++-- lnrpc/wtclientrpc/driver.go | 37 +++-- lnrpc/wtclientrpc/wtclient.go | 46 ++++-- rpcserver.go | 170 +++++++++++------------ subrpcserver_config.go | 4 +- 29 files changed, 919 insertions(+), 572 deletions(-) diff --git a/lnd.go b/lnd.go index fa0a0d7dccc..5636d9febbb 100644 --- a/lnd.go +++ b/lnd.go @@ -503,6 +503,24 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer cleanUp() + // Prepare the sub-servers, and insert the permissions required to + // access them into the interceptor chain. Note that we do not yet have + // all dependencies required to use all sub-servers. + err = rpcServer.prepareSubServers( + interceptorChain.MacaroonService(), cfg.SubRPCServers, + activeChainControl, + ) + if err != nil { + return mkErr("error adding sub server permissions", err) + } + + defer func() { + err := rpcServer.Stop() + if err != nil { + ltndLog.Errorf("Error stopping the RPC server", err) + } + }() + // TODO(roasbeef): add rotation idKeyDesc, err := activeChainControl.KeyRing.DeriveKey( keychain.KeyLocator{ @@ -671,8 +689,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, return mkErr("unable to load permanent TLS certificate", err) } - // Now we have created all dependencies necessary to populate and - // start the RPC server. + // Now we have created all dependencies necessary to be able to use all + // sub-servers, so we add the dependencies to the sub-servers. err = rpcServer.addDeps( ctx, server, interceptorChain.MacaroonService(), cfg.SubRPCServers, atplManager, server.invoices, tower, @@ -681,12 +699,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, if err != nil { return mkErr("unable to add deps to RPC server", err) } - if err := rpcServer.Start(); err != nil { - return mkErr("unable to start RPC server", err) - } - defer rpcServer.Stop() - // We transition the RPC state to Active, as the RPC server is up. + // We transition the RPC state to Active, as the sub-servers are now + // ready to be used. interceptorChain.SetRPCActive() if err := interceptor.Notifier.NotifyReady(true); err != nil { diff --git a/lnrpc/autopilotrpc/autopilot_server.go b/lnrpc/autopilotrpc/autopilot_server.go index e5952949ece..3bd80c3510d 100644 --- a/lnrpc/autopilotrpc/autopilot_server.go +++ b/lnrpc/autopilotrpc/autopilot_server.go @@ -6,6 +6,7 @@ package autopilotrpc import ( "context" "encoding/hex" + "errors" "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" @@ -63,7 +64,7 @@ type ServerShell struct { // RPC server allows external callers to access the status of the autopilot // currently active within lnd, as well as configuring it at runtime. type Server struct { - started int32 // To be used atomically. + injected int32 // To be used atomically. shutdown int32 // To be used atomically. // Required by the grpc-gateway/v2 library for forward compatibility. @@ -85,26 +86,10 @@ var _ AutopilotServer = (*Server)(nil) // this method. If the macaroons we need aren't found in the filepath, then // we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { // We don't create any new macaroons for this subserver, instead reuse // existing onchain/offchain permissions. - server := &Server{ - cfg: cfg, - manager: cfg.Manager, - } - - return server, macPermissions, nil -} - -// Start launches any helper goroutines required for the Server to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - if atomic.AddInt32(&s.started, 1) != 1 { - return nil - } - - return s.manager.Start() + return &Server{cfg: &Config{}}, macPermissions, nil } // Stop signals any active goroutines for a graceful closure. @@ -115,6 +100,10 @@ func (s *Server) Stop() error { return nil } + if s.manager == nil { + return nil + } + return s.manager.Stop() } @@ -126,6 +115,43 @@ func (s *Server) Name() string { return subServerName } +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + if s.shutdown != 0 { + return errors.New("server shutting down") + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + s.cfg = cfg + + // If we're not finalizing the dependencies, and this InjectDependencies + // call doesn't have the s.manager yet, we don't set it and return + // early. It needs to be set by a new InjectDependencies call though, as + // the server can't be used before the s.manager has been started. + if !finalizeDependencies && cfg.Manager == nil { + return nil + } + + s.manager = cfg.Manager + + return s.manager.Start() +} + // RegisterWithRootServer will be called by the root gRPC server to direct a // sub RPC server to register itself with the main gRPC root server. Until this // is called, each sub-server won't be able to have @@ -165,17 +191,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/autopilotrpc/driver.go b/lnrpc/autopilotrpc/driver.go index 850540df4ca..b8c2c4ce20e 100644 --- a/lnrpc/autopilotrpc/driver.go +++ b/lnrpc/autopilotrpc/driver.go @@ -9,12 +9,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -22,28 +23,41 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +// +//nolint:stylecheck +func verifyDependencies(config *Config) error { // Before we try to make the new service instance, we'll perform // some sanity checks on the arguments to ensure that they're usable. switch { case config.Manager == nil: - return nil, nil, fmt.Errorf("Manager must be set to create " + - "Autopilotrpc") + return fmt.Errorf("Manager must be set to create Autopilotrpc") } - return New(config) + return nil } func init() { diff --git a/lnrpc/chainrpc/chain_server.go b/lnrpc/chainrpc/chain_server.go index e42a6843c01..df8a80b2272 100644 --- a/lnrpc/chainrpc/chain_server.go +++ b/lnrpc/chainrpc/chain_server.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "sync" + "sync/atomic" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -104,11 +105,12 @@ type ServerShell struct { // to create custom protocols, external to lnd, even backed by multiple distinct // lnd across independent failure domains. type Server struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedChainNotifierServer UnimplementedChainKitServer - started sync.Once stopped sync.Once cfg Config @@ -121,7 +123,55 @@ type Server struct { // this method. If the macaroons we need aren't found in the filepath, then // we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { + return &Server{ + cfg: Config{}, + quit: make(chan struct{}), + }, macPermissions, nil +} + +// Compile-time checks to ensure that Server fully implements the +// ChainNotifierServer gRPC service, ChainKitServer gRPC service, and +// lnrpc.SubServer interface. +var _ ChainNotifierServer = (*Server)(nil) +var _ ChainKitServer = (*Server)(nil) +var _ lnrpc.SubServer = (*Server)(nil) + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + s.stopped.Do(func() { + close(s.quit) + }) + + return nil +} + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + if finalizeDependencies { + s.cfg = *cfg + + return nil + } + // If the path of the chain notifier macaroon wasn't generated, then // we'll assume that it's found at the default network directory. if cfg.ChainNotifierMacPath == "" { @@ -137,8 +187,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { if cfg.MacService != nil && !cfg.MacService.StatelessInit && !lnrpc.FileExists(macFilePath) { - log.Infof("Baking macaroons for ChainNotifier RPC Server at: %v", - macFilePath) + log.Infof("Baking macaroons for ChainNotifier RPC Server "+ + "at: %v", macFilePath) // At this point, we know that the chain notifier macaroon // doesn't yet, exist, so we need to create it with the help of @@ -148,47 +198,21 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { macaroonOps..., ) if err != nil { - return nil, nil, err + return err } chainNotifierMacBytes, err := chainNotifierMac.M().MarshalBinary() if err != nil { - return nil, nil, err + return err } err = os.WriteFile(macFilePath, chainNotifierMacBytes, 0644) if err != nil { _ = os.Remove(macFilePath) - return nil, nil, err + return err } } - return &Server{ - cfg: *cfg, - quit: make(chan struct{}), - }, macPermissions, nil -} + s.cfg = *cfg -// Compile-time checks to ensure that Server fully implements the -// ChainNotifierServer gRPC service, ChainKitServer gRPC service, and -// lnrpc.SubServer interface. -var _ ChainNotifierServer = (*Server)(nil) -var _ ChainKitServer = (*Server)(nil) -var _ lnrpc.SubServer = (*Server)(nil) - -// Start launches any helper goroutines required for the server to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - s.started.Do(func() {}) - return nil -} - -// Stop signals any active goroutines for a graceful closure. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { - s.stopped.Do(func() { - close(s.quit) - }) return nil } @@ -253,17 +277,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/chainrpc/driver.go b/lnrpc/chainrpc/driver.go index 89a6a116a4d..976f7c3086a 100644 --- a/lnrpc/chainrpc/driver.go +++ b/lnrpc/chainrpc/driver.go @@ -9,32 +9,45 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new chain notifier -// sub server given the main config dispatcher method. If we're unable to find -// the config that is meant for us in the config dispatcher, then we'll exit -// with an error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this // config. - chainNotifierServerConf, ok := configRegistry.FetchConfig(subServerName) + subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. - config, ok := chainNotifierServerConf.(*Config) + config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, chainNotifierServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(config *Config) error { // Before we try to make the new chain notifier service instance, we'll // perform some sanity checks on the arguments to ensure that they're // usable. @@ -43,19 +56,17 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // ensure that we know where to look for them, or create them if not // found. case config.MacService != nil && config.NetworkDir == "": - return nil, nil, fmt.Errorf("NetworkDir must be set to create " + - "chainrpc") + return fmt.Errorf("NetworkDir must be set to create chainrpc") case config.ChainNotifier == nil: - return nil, nil, fmt.Errorf("ChainNotifier must be set to " + - "create chainrpc") + return fmt.Errorf("ChainNotifier must be set to create " + + "chainrpc") case config.Chain == nil: - return nil, nil, fmt.Errorf("field Chain must be set to " + - "create chainrpc") + return fmt.Errorf("field Chain must be set to create chainrpc") } - return New(config) + return nil } func init() { diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index 39c089dffa0..6e2b87810fa 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -59,7 +59,7 @@ type ServerShell struct { // RPC server allows developers to set and query LND state that is not possible // during normal operation. type Server struct { - started int32 // To be used atomically. + injected int32 // To be used atomically. shutdown int32 // To be used atomically. quit chan struct{} @@ -80,37 +80,49 @@ var _ DevServer = (*Server)(nil) // If the macaroons we need aren't found in the filepath, then we'll create them // on start up. If we're unable to locate, or create the macaroons we need, then // we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { // We don't create any new macaroons for this subserver, instead reuse // existing onchain/offchain permissions. server := &Server{ quit: make(chan struct{}), - cfg: cfg, + cfg: &Config{}, } return server, macPermissions, nil } -// Start launches any helper goroutines required for the Server to function. +// Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - if atomic.AddInt32(&s.started, 1) != 1 { +func (s *Server) Stop() error { + if atomic.AddInt32(&s.shutdown, 1) != 1 { return nil } + close(s.quit) + return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { - if atomic.AddInt32(&s.shutdown, 1) != 1 { - return nil +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized } - close(s.quit) + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + s.cfg = cfg return nil } @@ -162,17 +174,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/devrpc/driver.go b/lnrpc/devrpc/driver.go index a0d4d17c8cb..96c44938ac5 100644 --- a/lnrpc/devrpc/driver.go +++ b/lnrpc/devrpc/driver.go @@ -9,12 +9,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -22,32 +23,43 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(config *Config) error { // Before we try to make the new service instance, we'll perform // some sanity checks on the arguments to ensure that they're useable. if config.ActiveNetParams == nil { - return nil, nil, fmt.Errorf("ActiveNetParams must be set to " + - "create DevRPC") + return fmt.Errorf("ActiveNetParams must be set to create " + + "DevRPC") } if config.GraphDB == nil { - return nil, nil, fmt.Errorf("GraphDB must be set to create " + - "DevRPC") + return fmt.Errorf("GraphDB must be set to create DevRPC") } - return New(config) + return nil } func init() { diff --git a/lnrpc/invoicesrpc/driver.go b/lnrpc/invoicesrpc/driver.go index 8014528ea0d..58d14918d7a 100644 --- a/lnrpc/invoicesrpc/driver.go +++ b/lnrpc/invoicesrpc/driver.go @@ -9,12 +9,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -22,20 +23,32 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } - return New(config) + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(_ *Config) error { + return nil } func init() { diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index 7b00b9b6438..cd883be0df1 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "sync/atomic" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/invoices" @@ -91,6 +92,8 @@ type ServerShell struct { // RPC server allows external callers to access the status of the invoices // currently active within lnd, as well as configuring it at runtime. type Server struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedInvoicesServer @@ -108,7 +111,48 @@ var _ InvoicesServer = (*Server)(nil) // this method. If the macaroons we need aren't found in the filepath, then // we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { + server := &Server{ + cfg: &Config{}, + quit: make(chan struct{}, 1), + } + + return server, macPermissions, nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + close(s.quit) + + return nil +} + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + if finalizeDependencies { + s.cfg = cfg + + return nil + } + // If the path of the invoices macaroon wasn't specified, then we'll // assume that it's found at the default network directory. macFilePath := filepath.Join( @@ -132,39 +176,20 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { macaroonOps..., ) if err != nil { - return nil, nil, err + return err } invoicesMacBytes, err := invoicesMac.M().MarshalBinary() if err != nil { - return nil, nil, err + return err } err = os.WriteFile(macFilePath, invoicesMacBytes, 0644) if err != nil { _ = os.Remove(macFilePath) - return nil, nil, err + return err } } - server := &Server{ - cfg: cfg, - quit: make(chan struct{}, 1), - } - - return server, macPermissions, nil -} - -// Start launches any helper goroutines required for the Server to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - return nil -} - -// Stop signals any active goroutines for a graceful closure. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { - close(s.quit) + s.cfg = cfg return nil } @@ -215,18 +240,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer( - configRegistry lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, - lnrpc.MacaroonPerms, error) { +func (r *ServerShell) CreateSubServer() ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/neutrinorpc/driver.go b/lnrpc/neutrinorpc/driver.go index 9bfe7ccd13f..1e6c30065aa 100644 --- a/lnrpc/neutrinorpc/driver.go +++ b/lnrpc/neutrinorpc/driver.go @@ -9,12 +9,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -22,20 +23,32 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } - return New(config) + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(_ *Config) error { + return nil } func init() { diff --git a/lnrpc/neutrinorpc/neutrino_server.go b/lnrpc/neutrinorpc/neutrino_server.go index 7154be7dbb3..8b4038c1021 100644 --- a/lnrpc/neutrinorpc/neutrino_server.go +++ b/lnrpc/neutrinorpc/neutrino_server.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "sync/atomic" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -78,6 +79,8 @@ type ServerShell struct { // RPC server allows external callers to access the status of the neutrino // currently active within lnd, as well as configuring it at runtime. type Server struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. // Must be after the atomically used variables to not break struct // alignment. @@ -95,27 +98,39 @@ var _ NeutrinoKitServer = (*Server)(nil) // this method. If the macaroons we need aren't found in the filepath, then // we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { // We don't create any new macaroons for this subserver, instead reuse // existing onchain/offchain permissions. - server := &Server{ - cfg: cfg, - } - - return server, macPermissions, nil + return &Server{cfg: &Config{}}, macPermissions, nil } -// Start launches any helper goroutines required for the Server to function. +// Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { +func (s *Server) Stop() error { return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + config, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + s.cfg = config + return nil } @@ -166,17 +181,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/peersrpc/driver.go b/lnrpc/peersrpc/driver.go index 8abf3ed9248..b669f051254 100644 --- a/lnrpc/peersrpc/driver.go +++ b/lnrpc/peersrpc/driver.go @@ -9,12 +9,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -22,20 +23,32 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } - return New(config) + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(_ *Config) error { + return nil } func init() { diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go index 7bc68ca2b4f..ad1f06b9b9f 100644 --- a/lnrpc/peersrpc/peers_server.go +++ b/lnrpc/peersrpc/peers_server.go @@ -47,7 +47,7 @@ type ServerShell struct { // Server is a sub-server of the main RPC server: the peers RPC. This sub // RPC server allows to intereact with our Peers in the Lightning Network. type Server struct { - started int32 // To be used atomically. + injected int32 // To be used atomically. shutdown int32 // To be used atomically. // Required by the grpc-gateway/v2 library for forward compatibility. @@ -67,33 +67,41 @@ var _ PeersServer = (*Server)(nil) // this method. If the macaroons we need aren't found in the filepath, then // we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { - server := &Server{ - cfg: cfg, - } - - return server, macPermissions, nil +func New() (*Server, lnrpc.MacaroonPerms, error) { + return &Server{cfg: &Config{}}, macPermissions, nil } -// Start launches any helper goroutines required for the Server to function. +// Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - if atomic.AddInt32(&s.started, 1) != 1 { +func (s *Server) Stop() error { + if atomic.AddInt32(&s.shutdown, 1) != 1 { return nil } return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { - if atomic.AddInt32(&s.shutdown, 1) != 1 { - return nil +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized } + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + s.cfg = cfg + return nil } @@ -144,17 +152,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/routerrpc/driver.go b/lnrpc/routerrpc/driver.go index 4e9cea65772..6856f2c5cea 100644 --- a/lnrpc/routerrpc/driver.go +++ b/lnrpc/routerrpc/driver.go @@ -6,41 +6,55 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new router sub -// server given the main config dispatcher method. If we're unable to find the -// config that is meant for us in the config dispatcher, then we'll exit with -// an error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this // config. - routeServerConf, ok := configRegistry.FetchConfig(subServerName) + subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. - config, ok := routeServerConf.(*Config) + config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, routeServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +// +//nolint:stylecheck +func verifyDependencies(config *Config) error { // Before we try to make the new router service instance, we'll perform // some sanity checks on the arguments to ensure that they're usable. switch { case config.Router == nil: - return nil, nil, fmt.Errorf("Router must be set to create " + - "Routerpc") + return fmt.Errorf("Router must be set to create Routerpc") } - return New(config) + return nil } func init() { diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index a4031b154eb..530588178aa 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -190,7 +190,7 @@ type ServerShell struct { // Server is a stand-alone sub RPC server which exposes functionality that // allows clients to route arbitrary payment through the Lightning Network. type Server struct { - started int32 // To be used atomically. + injected int32 // To be used atomically. shutdown int32 // To be used atomically. forwardInterceptorActive int32 // To be used atomically. @@ -213,7 +213,52 @@ var _ RouterServer = (*Server)(nil) // we're unable to create it, then an error will be returned. We also return // the set of permissions that we require as a server. At the time of writing // of this documentation, this is the same macaroon as the admin macaroon. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { + routerServer := &Server{ + cfg: &Config{}, + quit: make(chan struct{}), + } + + return routerServer, macPermissions, nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + if atomic.AddInt32(&s.shutdown, 1) != 1 { + return nil + } + + close(s.quit) + + return nil +} + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + if finalizeDependencies { + s.cfg = cfg + + return nil + } + // If the path of the router macaroon wasn't generated, then we'll // assume that it's found at the default network directory. if cfg.RouterMacPath == "" { @@ -240,47 +285,21 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { macaroonOps..., ) if err != nil { - return nil, nil, err + return err } routerMacBytes, err := routerMac.M().MarshalBinary() if err != nil { - return nil, nil, err + return err } err = os.WriteFile(macFilePath, routerMacBytes, 0644) if err != nil { _ = os.Remove(macFilePath) - return nil, nil, err + return err } } - routerServer := &Server{ - cfg: cfg, - quit: make(chan struct{}), - } - - return routerServer, macPermissions, nil -} - -// Start launches any helper goroutines required for the rpcServer to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - if atomic.AddInt32(&s.started, 1) != 1 { - return nil - } - - return nil -} - -// Stop signals any active goroutines for a graceful closure. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { - if atomic.AddInt32(&s.shutdown, 1) != 1 { - return nil - } + s.cfg = cfg - close(s.quit) return nil } @@ -330,17 +349,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/rpc_utils.go b/lnrpc/rpc_utils.go index 15e3f2f7622..677a0468600 100644 --- a/lnrpc/rpc_utils.go +++ b/lnrpc/rpc_utils.go @@ -55,6 +55,11 @@ var ( RESTJsonUnmarshalOpts = &protojson.UnmarshalOptions{ AllowPartial: false, } + + // ErrDependenciesFinalized is an error that is returned when the final + // dependencies have already been injected into a sub-server. + ErrDependenciesFinalized = errors.New("final dependencies have " + + "already been injected") ) // RPCTransaction returns a rpc transaction. diff --git a/lnrpc/signrpc/driver.go b/lnrpc/signrpc/driver.go index 01b07d60cf1..a2de73823b8 100644 --- a/lnrpc/signrpc/driver.go +++ b/lnrpc/signrpc/driver.go @@ -9,53 +9,63 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new signer sub -// server given the main config dispatcher method. If we're unable to find the -// config that is meant for us in the config dispatcher, then we'll exit with -// an error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Server, lnrpc.MacaroonPerms, error) { +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our - // subServerName name. If we can't find this, then we'll exit with an + // SubServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this // config. - signServerConf, ok := configRegistry.FetchConfig(subServerName) + subServerConf, ok := configRegistry.FetchConfig(SubServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", SubServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. - config, ok := signServerConf.(*Config) + config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, signServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", SubServerName, &Config{}, + subServerConf) } - // Before we try to make the new signer service instance, we'll perform - // some sanity checks on the arguments to ensure that they're usable. + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} +// verifyDependencies ensures that the dependencies in the config are properly +// set. +// +//nolint:stylecheck +func verifyDependencies(config *Config) error { switch { // If the macaroon service is set (we should use macaroons), then // ensure that we know where to look for them, or create them if not // found. case config.MacService != nil && config.NetworkDir == "": - return nil, nil, fmt.Errorf("NetworkDir must be set to create " + - "Signrpc") + return fmt.Errorf("NetworkDir must be set to create Signrpc") case config.Signer == nil: - return nil, nil, fmt.Errorf("Signer must be set to create " + - "Signrpc") + return fmt.Errorf("Signer must be set to create Signrpc") } - return New(config) + return nil } func init() { subServer := &lnrpc.SubServerDriver{ - SubServerName: subServerName, + SubServerName: SubServerName, NewGrpcHandler: func() lnrpc.GrpcHandler { return &ServerShell{} }, @@ -65,6 +75,6 @@ func init() { // sub-RPC server within the global lnrpc package namespace. if err := lnrpc.RegisterSubServer(subServer); err != nil { panic(fmt.Sprintf("failed to register sub server driver '%s': %v", - subServerName, err)) + SubServerName, err)) } } diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index b0b161f13ef..45f39cd8f93 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -10,6 +10,7 @@ import ( "fmt" "os" "path/filepath" + "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -28,11 +29,11 @@ import ( ) const ( - // subServerName is the name of the sub rpc server. We'll use this name + // SubServerName is the name of the sub rpc server. We'll use this name // to register ourselves, and we also require that the main // SubServerConfigDispatcher instance recognize this as the name of the // config file that we need. - subServerName = "SignRPC" + SubServerName = "SignRPC" // BIP0340 is the prefix for BIP0340-related tagged hashes. BIP0340 = "BIP0340" @@ -126,6 +127,8 @@ type ServerShell struct { // lnd. This allows callers to create custom protocols, external to lnd, even // backed by multiple distinct lnd across independent failure domains. type Server struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedSignerServer @@ -141,7 +144,41 @@ var _ SignerServer = (*Server)(nil) // method. If the macaroons we need aren't found in the filepath, then we'll // create them on start up. If we're unable to locate, or create the macaroons // we need, then we'll return with an error. -func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { +func New() (*Server, lnrpc.MacaroonPerms, error) { + return &Server{cfg: &Config{}}, macPermissions, nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + return nil +} + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + if finalizeDependencies { + s.cfg = cfg + + return nil + } + // If the path of the signer macaroon wasn't generated, then we'll // assume that it's found at the default network directory. if cfg.SignerMacPath == "" { @@ -168,37 +205,21 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { macaroonOps..., ) if err != nil { - return nil, nil, err + return err } signerMacBytes, err := signerMac.M().MarshalBinary() if err != nil { - return nil, nil, err + return err } err = os.WriteFile(macFilePath, signerMacBytes, 0644) if err != nil { _ = os.Remove(macFilePath) - return nil, nil, err + return err } } - signerServer := &Server{ - cfg: cfg, - } - - return signerServer, macPermissions, nil -} - -// Start launches any helper goroutines required for the rpcServer to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { - return nil -} + s.cfg = cfg -// Stop signals any active goroutines for a graceful closure. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { return nil } @@ -207,7 +228,7 @@ func (s *Server) Stop() error { // // NOTE: This is part of the lnrpc.SubServer interface. func (s *Server) Name() string { - return subServerName + return SubServerName } // RegisterWithRootServer will be called by the root gRPC server to direct a @@ -249,17 +270,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } @@ -481,6 +500,7 @@ func (s *Server) SignOutputRaw(_ context.Context, in *SignReq) (*SignResp, resp := &SignResp{ RawSigs: make([][]byte, numSigs), } + for i, signDesc := range signDescs { sig, err := s.cfg.Signer.SignOutputRaw(&txToSign, signDesc) if err != nil { @@ -583,6 +603,7 @@ func (s *Server) ComputeInputScript(ctx context.Context, resp := &InputScriptResp{ InputScripts: make([]*InputScript, numWitnesses), } + for i, signDesc := range signDescs { inputScript, err := s.cfg.Signer.ComputeInputScript( &txToSign, signDesc, diff --git a/lnrpc/sub_server.go b/lnrpc/sub_server.go index b09828213ce..443100ce88e 100644 --- a/lnrpc/sub_server.go +++ b/lnrpc/sub_server.go @@ -22,9 +22,6 @@ type MacaroonPerms map[string][]bakery.Op // main RPC server. The main rpcserver will create, start, stop, and manage // each sub-server in a generalized manner. type SubServer interface { - // Start starts the sub-server and all goroutines it needs to operate. - Start() error - // Stop signals that the sub-server should wrap up any lingering // requests, and being a graceful shutdown. Stop() error @@ -32,6 +29,13 @@ type SubServer interface { // Name returns a unique string representation of the sub-server. This // can be used to identify the sub-server and also de-duplicate them. Name() string + + // InjectDependencies populates the sub-server's dependencies using the + // passed SubServerConfigDispatcher. If the finalizeDependencies boolean + // is true, then the sub-server should finalize its dependencies and + // return an error if any required dependencies are missing. + InjectDependencies(subCfgs SubServerConfigDispatcher, + finalizeDependencies bool) error } // GrpcHandler is the interface that should be registered with the root gRPC @@ -53,13 +57,10 @@ type GrpcHandler interface { RegisterWithRestServer(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error - // CreateSubServer populates the subserver's dependencies using the - // passed SubServerConfigDispatcher. This method should fully - // initialize the sub-server instance, making it ready for action. It - // returns the macaroon permissions that the sub-server wishes to pass - // on to the root server for all methods routed towards it. - CreateSubServer(subCfgs SubServerConfigDispatcher) (SubServer, - MacaroonPerms, error) + // CreateSubServer creates an instance of the sub-server, and returns + // the macaroon permissions that the sub-server wishes to pass on to the + // root server for all methods routed towards it. + CreateSubServer() (SubServer, MacaroonPerms, error) } // SubServerConfigDispatcher is an interface that all sub-servers will use to diff --git a/lnrpc/verrpc/server.go b/lnrpc/verrpc/server.go index a68e87c5322..b7a90360e1a 100644 --- a/lnrpc/verrpc/server.go +++ b/lnrpc/verrpc/server.go @@ -33,17 +33,21 @@ type Server struct { UnimplementedVersionerServer } -// Start launches any helper goroutines required for the rpcServer to function. +// Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Start() error { +func (s *Server) Stop() error { return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (s *Server) Stop() error { +func (s *Server) InjectDependencies(_ lnrpc.SubServerConfigDispatcher, + _ bool) error { + // There are no specific dependencies to populate for this subServer. return nil } @@ -91,14 +95,12 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(_ lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { subServer := &Server{} diff --git a/lnrpc/walletrpc/driver.go b/lnrpc/walletrpc/driver.go index 7446470f1fb..b194d3193ac 100644 --- a/lnrpc/walletrpc/driver.go +++ b/lnrpc/walletrpc/driver.go @@ -9,62 +9,76 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new WalletKit RPC -// sub server given the main config dispatcher method. If we're unable to find -// the config that is meant for us in the config dispatcher, then we'll exit -// with an error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *WalletKit, lnrpc.MacaroonPerms, error) { - +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // SubServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this // config. - walletKitServerConf, ok := configRegistry.FetchConfig(SubServerName) + subServerConf, ok := configRegistry.FetchConfig(SubServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", SubServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", SubServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. - config, ok := walletKitServerConf.(*Config) + config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", SubServerName, - &Config{}, walletKitServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", SubServerName, &Config{}, + subServerConf) + } + + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } } + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +// +//nolint:stylecheck +func verifyDependencies(config *Config) error { // Before we try to make the new WalletKit service instance, we'll // perform some sanity checks on the arguments to ensure that they're // usable. switch { case config.MacService != nil && config.NetworkDir == "": - return nil, nil, fmt.Errorf("NetworkDir must be set to " + - "create WalletKit RPC server") + return fmt.Errorf("NetworkDir must be set to create " + + "WalletKit RPC server") case config.FeeEstimator == nil: - return nil, nil, fmt.Errorf("FeeEstimator must be set to " + - "create WalletKit RPC server") + return fmt.Errorf("FeeEstimator must be set to create " + + "WalletKit RPC server") case config.Wallet == nil: - return nil, nil, fmt.Errorf("Wallet must be set to create " + - "WalletKit RPC server") + return fmt.Errorf("Wallet must be set to create WalletKit " + + "RPC server") case config.KeyRing == nil: - return nil, nil, fmt.Errorf("KeyRing must be set to create " + - "WalletKit RPC server") + return fmt.Errorf("KeyRing must be set to create WalletKit " + + "RPC server") case config.Sweeper == nil: - return nil, nil, fmt.Errorf("Sweeper must be set to create " + - "WalletKit RPC server") + return fmt.Errorf("Sweeper must be set to create WalletKit " + + "RPC server") case config.Chain == nil: - return nil, nil, fmt.Errorf("Chain must be set to create " + - "WalletKit RPC server") + return fmt.Errorf("Chain must be set to create WalletKit RPC " + + "server") } - return New(config) + return nil } func init() { diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index ae6a094ea81..4298f1c182d 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -16,6 +16,7 @@ import ( "path/filepath" "slices" "sort" + "sync/atomic" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -255,6 +256,8 @@ type ServerShell struct { // to execute common wallet operations. This includes requesting new addresses, // keys (for contracts!), and publishing transactions. type WalletKit struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedWalletKitServer @@ -266,7 +269,41 @@ type WalletKit struct { var _ WalletKitServer = (*WalletKit)(nil) // New creates a new instance of the WalletKit sub-RPC server. -func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) { +func New() (*WalletKit, lnrpc.MacaroonPerms, error) { + return &WalletKit{cfg: &Config{}}, macPermissions, nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (w *WalletKit) Stop() error { + return nil +} + +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (w *WalletKit) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&w.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + if finalizeDependencies { + w.cfg = cfg + + return nil + } + // If the path of the wallet kit macaroon wasn't specified, then we'll // assume that it's found at the default network directory. if cfg.WalletKitMacPath == "" { @@ -293,37 +330,21 @@ func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) { macaroonOps..., ) if err != nil { - return nil, nil, err + return err } walletKitMacBytes, err := walletKitMac.M().MarshalBinary() if err != nil { - return nil, nil, err + return err } err = os.WriteFile(macFilePath, walletKitMacBytes, 0644) if err != nil { _ = os.Remove(macFilePath) - return nil, nil, err + return err } } - walletKit := &WalletKit{ - cfg: cfg, - } - - return walletKit, macPermissions, nil -} + w.cfg = cfg -// Start launches any helper goroutines required for the sub-server to function. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (w *WalletKit) Start() error { - return nil -} - -// Stop signals any active goroutines for a graceful closure. -// -// NOTE: This is part of the lnrpc.SubServer interface. -func (w *WalletKit) Stop() error { return nil } @@ -373,17 +394,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/walletrpc/walletkit_server_test.go b/lnrpc/walletrpc/walletkit_server_test.go index 40fb62ef2ad..2b83c58be93 100644 --- a/lnrpc/walletrpc/walletkit_server_test.go +++ b/lnrpc/walletrpc/walletkit_server_test.go @@ -638,12 +638,14 @@ func TestFundPsbtCoinSelect(t *testing.T) { RootKey: privKey, Utxos: tc.utxos, } - rpcServer, _, err := New(&Config{ + rpcServer, _, err := New() + require.NoError(t, err) + + rpcServer.cfg = &Config{ Wallet: walletMock, CoinSelectionLocker: &mockCoinSelectionLocker{}, CoinSelectionStrategy: wallet.CoinSelectionLargest, - }) - require.NoError(t, err) + } t.Run(tc.name, func(tt *testing.T) { // To avoid our packet being mutated, we'll make a deep diff --git a/lnrpc/watchtowerrpc/driver.go b/lnrpc/watchtowerrpc/driver.go index 6352de7ccc5..e8e198b8ac2 100644 --- a/lnrpc/watchtowerrpc/driver.go +++ b/lnrpc/watchtowerrpc/driver.go @@ -9,33 +9,45 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *Handler, lnrpc.MacaroonPerms, error) { - +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an // error, as we're unable to properly initialize ourselves without this // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) + } + + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } } - return New(config) + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(_ *Config) error { + return nil } func init() { diff --git a/lnrpc/watchtowerrpc/handler.go b/lnrpc/watchtowerrpc/handler.go index 5c99bc121b5..f684c884b4b 100644 --- a/lnrpc/watchtowerrpc/handler.go +++ b/lnrpc/watchtowerrpc/handler.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "sync/atomic" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/lnrpc" @@ -46,6 +47,8 @@ type ServerShell struct { // Handler is the RPC server we'll use to interact with the backing active // watchtower. type Handler struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedWatchtowerServer @@ -61,21 +64,37 @@ var _ WatchtowerServer = (*Handler)(nil) // If the macaroons we need aren't found in the filepath, then we'll create them // on start up. If we're unable to locate, or create the macaroons we need, then // we'll return with an error. -func New(cfg *Config) (*Handler, lnrpc.MacaroonPerms, error) { - return &Handler{cfg: *cfg}, macPermissions, nil +func New() (*Handler, lnrpc.MacaroonPerms, error) { + return &Handler{cfg: Config{}}, macPermissions, nil } -// Start launches any helper goroutines required for the Handler to function. +// Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. -func (c *Handler) Start() error { +func (c *Handler) Stop() error { return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (c *Handler) Stop() error { +func (c *Handler) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&c.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + c.cfg = *cfg + return nil } @@ -125,17 +144,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/lnrpc/wtclientrpc/driver.go b/lnrpc/wtclientrpc/driver.go index 62a18cffcef..7b06cf04017 100644 --- a/lnrpc/wtclientrpc/driver.go +++ b/lnrpc/wtclientrpc/driver.go @@ -7,12 +7,13 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -// createNewSubServer is a helper method that will create the new sub server -// given the main config dispatcher method. If we're unable to find the config +// getConfig is a helper method that will fetch the config for sub-server given +// the main config dispatcher method. If we're unable to find the config // that is meant for us in the config dispatcher, then we'll exit with an -// error. -func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - *WatchtowerClient, lnrpc.MacaroonPerms, error) { +// error. If enforceDependencies is set to true, the function also verifies that +// the dependencies in the config are properly set. +func getConfig(configRegistry lnrpc.SubServerConfigDispatcher, + enforceDependencies bool) (*Config, error) { // We'll attempt to look up the config that we expect, according to our // subServerName name. If we can't find this, then we'll exit with an @@ -20,27 +21,39 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // config. subServerConf, ok := configRegistry.FetchConfig(subServerName) if !ok { - return nil, nil, fmt.Errorf("unable to find config for "+ - "subserver type %s", subServerName) + return nil, fmt.Errorf("unable to find config for subserver "+ + "type %s", subServerName) } // Now that we've found an object mapping to our service name, we'll // ensure that it's the type we need. config, ok := subServerConf.(*Config) if !ok { - return nil, nil, fmt.Errorf("wrong type of config for "+ - "subserver %s, expected %T got %T", subServerName, - &Config{}, subServerConf) + return nil, fmt.Errorf("wrong type of config for subserver "+ + "%s, expected %T got %T", subServerName, &Config{}, + subServerConf) } + if enforceDependencies { + if err := verifyDependencies(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// verifyDependencies ensures that the dependencies in the config are properly +// set. +func verifyDependencies(config *Config) error { // Before we try to make the new service instance, we'll perform // some sanity checks on the arguments to ensure that they're usable. switch { case config.Resolver == nil: - return nil, nil, errors.New("a lncfg.TCPResolver is required") + return errors.New("a lncfg.TCPResolver is required") } - return New(config) + return nil } func init() { diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 5ddb99d370e..9ce78d58229 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -9,6 +9,7 @@ import ( "net" "sort" "strconv" + "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -87,6 +88,8 @@ type ServerShell struct { // // TODO(wilmer): better name? type WatchtowerClient struct { + injected int32 // To be used atomically. + // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedWatchtowerClientServer @@ -102,22 +105,37 @@ var _ WatchtowerClientServer = (*WatchtowerClient)(nil) // within this method. If the macaroons we need aren't found in the filepath, // then we'll create them on start up. If we're unable to locate, or create the // macaroons we need, then we'll return with an error. -func New(cfg *Config) (*WatchtowerClient, lnrpc.MacaroonPerms, error) { - return &WatchtowerClient{cfg: *cfg}, macPermissions, nil +func New() (*WatchtowerClient, lnrpc.MacaroonPerms, error) { + return &WatchtowerClient{cfg: Config{}}, macPermissions, nil } -// Start launches any helper goroutines required for the WatchtowerClient to -// function. +// Stop signals any active goroutines for a graceful closure. // -// NOTE: This is part of the lnrpc.SubWatchtowerClient interface. -func (c *WatchtowerClient) Start() error { +// NOTE: This is part of the lnrpc.SubServer interface. +func (c *WatchtowerClient) Stop() error { return nil } -// Stop signals any active goroutines for a graceful closure. +// InjectDependencies populates the sub-server's dependencies. If the +// finalizeDependencies boolean is true, then the sub-server will finalize its +// dependencies and return an error if any required dependencies are missing. // // NOTE: This is part of the lnrpc.SubServer interface. -func (c *WatchtowerClient) Stop() error { +func (c *WatchtowerClient) InjectDependencies( + configRegistry lnrpc.SubServerConfigDispatcher, + finalizeDependencies bool) error { + + if finalizeDependencies && atomic.AddInt32(&c.injected, 1) != 1 { + return lnrpc.ErrDependenciesFinalized + } + + cfg, err := getConfig(configRegistry, finalizeDependencies) + if err != nil { + return err + } + + c.cfg = *cfg + return nil } @@ -160,17 +178,15 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, return nil } -// CreateSubServer populates the subserver's dependencies using the passed -// SubServerConfigDispatcher. This method should fully initialize the -// sub-server instance, making it ready for action. It returns the macaroon -// permissions that the sub-server wishes to pass on to the root server for all -// methods routed towards it. +// CreateSubServer creates an instance of the sub-server, and returns the +// macaroon permissions that the sub-server wishes to pass on to the root server +// for all methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( +func (r *ServerShell) CreateSubServer() ( lnrpc.SubServer, lnrpc.MacaroonPerms, error) { - subServer, macPermissions, err := createNewSubServer(configRegistry) + subServer, macPermissions, err := New() if err != nil { return nil, nil, err } diff --git a/rpcserver.go b/rpcserver.go index 700f320eae5..7b13dbd5a53 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -613,7 +613,6 @@ type AuxDataParser interface { // rpcServer is a gRPC, RPC front end to the lnd daemon. // TODO(roasbeef): pagination support for the list-style calls type rpcServer struct { - started int32 // To be used atomically. shutdown int32 // To be used atomically. // Required by the grpc-gateway/v2 library for forward compatibility. @@ -697,9 +696,81 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, } } -// addDeps populates all dependencies needed by the RPC server, and any -// of the sub-servers that it maintains. When this is done, the RPC server can -// be started, and start accepting RPC calls. +// prepareSubServers prepares the sub-servers, and insert the permissions +// required to access them into the interceptor chain. +func (r *rpcServer) prepareSubServers(macService *macaroons.Service, + subServerCgs *subRPCServerConfigs, cc *chainreg.ChainControl) error { + + var ( + subServers []lnrpc.SubServer + subServerPerms []lnrpc.MacaroonPerms + ) + + // Create all of the sub-servers. Note that we do not yet have all + // dependencies required to use all sub-servers, as they are injected + // in the addDeps function, after all sub-servers have been started. + for _, subServerInstance := range r.subGrpcHandlers { + subServer, macPerms, err := subServerInstance.CreateSubServer() + if err != nil { + return err + } + + // We'll collect the sub-server, and also the set of + // permissions it needs for macaroons so we can apply the + // interceptors below. + subServers = append(subServers, subServer) + subServerPerms = append(subServerPerms, macPerms) + } + + // Next, we need to merge the set of sub server macaroon permissions + // with the main RPC server permissions so we can unite them under a + // single set of interceptors. + for m, ops := range MainRPCServerPermissions() { + err := r.interceptorChain.AddPermission(m, ops) + if err != nil { + return err + } + } + + for _, subServerPerm := range subServerPerms { + for method, ops := range subServerPerm { + err := r.interceptorChain.AddPermission(method, ops) + if err != nil { + return err + } + } + } + + // External subserver possibly need to register their own permissions + // and macaroon validator. + for method, ops := range r.implCfg.ExternalValidator.Permissions() { + err := r.interceptorChain.AddPermission(method, ops) + if err != nil { + return err + } + + // Give the external subservers the possibility to also use + // their own validator to check any macaroons attached to calls + // to this method. This allows them to have their own root key + // ID database and permission entities. + err = macService.RegisterExternalValidator( + method, r.implCfg.ExternalValidator, + ) + if err != nil { + return fmt.Errorf("could not register external "+ + "macaroon validator: %v", err) + } + } + + r.subServers = subServers + r.macService = macService + + return nil +} + +// addDeps populates and injects all dependencies needed by the RPC server, and +// any of the sub-servers that it maintains. When this is done, the RPC server +// can start accepting all RPC calls. func (r *rpcServer) addDeps(ctx context.Context, s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager, @@ -800,14 +871,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, return parseAddr(addr, r.cfg.net) } - var ( - subServers []lnrpc.SubServer - subServerPerms []lnrpc.MacaroonPerms - ) - - // Before we create any of the sub-servers, we need to ensure that all - // the dependencies they need are properly populated within each sub - // server configuration struct. + // Now populate the dependencies for the sub-servers. // // TODO(roasbeef): extend sub-sever config to have both (local vs remote) DB err = subServerCgs.PopulateDependencies( @@ -824,70 +888,24 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, return err } - // Now that the sub-servers have all their dependencies in place, we - // can create each sub-server! - for _, subServerInstance := range r.subGrpcHandlers { - subServer, macPerms, err := subServerInstance.CreateSubServer( - subServerCgs, - ) - if err != nil { - return err - } - - // We'll collect the sub-server, and also the set of - // permissions it needs for macaroons so we can apply the - // interceptors below. - subServers = append(subServers, subServer) - subServerPerms = append(subServerPerms, macPerms) - } - - // Next, we need to merge the set of sub server macaroon permissions - // with the main RPC server permissions so we can unite them under a - // single set of interceptors. - for m, ops := range MainRPCServerPermissions() { - err := r.interceptorChain.AddPermission(m, ops) - if err != nil { - return err - } - } - - for _, subServerPerm := range subServerPerms { - for method, ops := range subServerPerm { - err := r.interceptorChain.AddPermission(method, ops) - if err != nil { - return err - } - } - } - - // External subserver possibly need to register their own permissions - // and macaroon validator. - for method, ops := range r.implCfg.ExternalValidator.Permissions() { - err := r.interceptorChain.AddPermission(method, ops) + // Inject the dependencies into the respective sub-servers. This also + // ensures that all dependencies are properly set within each sub-server + // configuration struct. + for _, subServer := range r.subServers { + err = subServer.InjectDependencies(subServerCgs, true) if err != nil { return err } - // Give the external subservers the possibility to also use - // their own validator to check any macaroons attached to calls - // to this method. This allows them to have their own root key - // ID database and permission entities. - err = macService.RegisterExternalValidator( - method, r.implCfg.ExternalValidator, - ) - if err != nil { - return fmt.Errorf("could not register external "+ - "macaroon validator: %v", err) - } + rpcsLog.Debugf("Finalized the startup procedure of the sub "+ + "RPC server: %v", subServer.Name()) } // Finally, with all the set up complete, add the last dependencies to // the rpc server. r.server = s - r.subServers = subServers r.routerBackend = routerBackend r.chanPredicate = chanPredicate - r.macService = macService r.selfNode = selfNode.PubKeyBytes graphCacheDuration := r.cfg.Caches.RPCGraphCacheDuration @@ -962,28 +980,6 @@ func (r *rpcServer) RegisterWithGrpcServer(grpcServer *grpc.Server) error { return nil } -// Start launches any helper goroutines required for the rpcServer to function. -func (r *rpcServer) Start() error { - if atomic.AddInt32(&r.started, 1) != 1 { - return nil - } - - // First, we'll start all the sub-servers to ensure that they're ready - // to take new requests in. - // - // TODO(roasbeef): some may require that the entire daemon be started - // at that point - for _, subServer := range r.subServers { - rpcsLog.Debugf("Starting sub RPC server: %v", subServer.Name()) - - if err := subServer.Start(); err != nil { - return err - } - } - - return nil -} - // RegisterWithRestProxy registers the RPC server and any subservers with the // given REST proxy. func (r *rpcServer) RegisterWithRestProxy(restCtx context.Context, diff --git a/subrpcserver_config.go b/subrpcserver_config.go index d55d5a49238..bf26cda8a84 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -102,8 +102,8 @@ type subRPCServerConfigs struct { // within this struct, and populate the items it requires based on the main // configuration file, and the chain control. // -// NOTE: This MUST be called before any callers are permitted to execute the -// FetchConfig method. +// NOTE: When preparing all sub-servers to be ready to accept RPC calls, this +// MUST be called before the FetchConfig method is executed. func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, cc *chainreg.ChainControl, networkDir string, macService *macaroons.Service, From de1153c9415e69a209724bc0c2a4d5b36699762a Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:12:32 +0200 Subject: [PATCH 24/39] multi: add `RemoteSignerConnection` to walletrpc conf This commit adds the `RemoteSignerConnection` reference to the `WalletKit` `Config`, enabling it to be accessed from the `WalletKit` sub-server. When a remote signer connects by calling the `SignCoordinatorStreams` RPC endpoint, we need to pass the stream from the outbound remote signer to the `InboundRemoteSignerConnection` `AddConnection` function. This change ensures that the `InboundRemoteSignerConnection` `AddConnection` function is reachable from the `SignCoordinatorStreams` RPC endpoint implementation. Note that this field should only to be populated when the `RPCKeyRing` `RemoteSignerConnection` is an `InboundConnection` instance, as the only that connection type implements the `InboundRemoteSignerConnection` interface. --- lnrpc/walletrpc/config_active.go | 4 ++++ lnrpc/walletrpc/walletkit_server.go | 11 +++++++++++ subrpcserver_config.go | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 4636473f0b5..444af0d08f1 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -80,4 +80,8 @@ type Config struct { // ChanStateDB is the reference to the channel db. ChanStateDB *channeldb.ChannelStateDB + + // RemoteSignerConnection is an inbound connection to the remote signer + // that the WalletKit server will use to sign transactions, if enabled. + RemoteSignerConnection InboundRemoteSignerConnection } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 4298f1c182d..73fb8ddd2db 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -245,6 +245,17 @@ var ( } ) +// InboundRemoteSignerConnection is an interface that mimics a subset of the +// rpcwallet InboundRemoteSignerConnection interface to avoid circular +// dependencies. +type InboundRemoteSignerConnection interface { + // AddConnection feeds the inbound connection handler with the incoming + // stream set up by an outbound remote signer and then blocks until the + // stream is closed. Lnd can then send any requests to the remote signer + // through the stream. + AddConnection(stream WalletKit_SignCoordinatorStreamsServer) error +} + // ServerShell is a shell struct holding a reference to the actual sub-server. // It is used to register the gRPC sub-server with the root server before we // have the necessary dependencies to populate the actual sub-server. diff --git a/subrpcserver_config.go b/subrpcserver_config.go index bf26cda8a84..5c0b9ba582d 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/netann" @@ -213,6 +214,14 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(chanStateDB), ) + kRing, ok := cc.Wallet.WalletController.(*rpcwallet.RPCKeyRing) + if !ok || !cfg.RemoteSigner.AllowInboundConnection { + break + } + subCfgValue.FieldByName("RemoteSignerConnection").Set( + reflect.ValueOf(kRing.RemoteSignerConnection()), + ) + case *autopilotrpc.Config: subCfgValue := extractReflectValue(subCfg) From 19f3572d8a68c9044d39f10a015c6a5719d9c9bc Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:33:35 +0200 Subject: [PATCH 25/39] walletrpc: implement `SignCoordinatorStreams` RPC With the ability to reach the `InboundRemoteSignerConnection` `AddConnection` function in the `WalletKit` sub-server, we now implement the `SignCoordinatorStreams` RPC endpoint. --- lnrpc/walletrpc/walletkit_server.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 73fb8ddd2db..f0afd4d6a60 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -492,7 +492,16 @@ func (w *WalletKit) ListUnspent(ctx context.Context, func (w *WalletKit) SignCoordinatorStreams( stream WalletKit_SignCoordinatorStreamsServer) error { - return fmt.Errorf("Unimplemented") + // Check that the user actually has configured that the reverse remote + // signer functionality should be enabled. + if w.cfg.RemoteSignerConnection == nil { + return fmt.Errorf("inbound connections from remote signers " + + "not enabled in config") + } + + connectionCoordinator := w.cfg.RemoteSignerConnection + + return connectionCoordinator.AddConnection(stream) } // LeaseOutput locks an output to the given ID, preventing it from being From 02b897d4cfdfd815be01743e7ed58d602069f9f9 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 28 May 2024 02:33:04 +0200 Subject: [PATCH 26/39] multi: populate `RemoteSignerConnection` ref early This commit populates the `RemoteSignerConnection` reference in the `WalletKit` config before other dependencies are added. To ensure that an outbound remote signer can connect before other dependencies are created, and since we use this reference in the walletrpc `SignCoordinatorStreams` RPC, we must populate this dependency prior to other dependencies during the lnd startup process. --- lnrpc/walletrpc/walletkit_server.go | 126 ++++++++++++++++++++++++++++ rpcserver.go | 29 ++++++- subrpcserver_config.go | 35 ++++++-- 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index f0afd4d6a60..04f2a7c207a 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -16,6 +16,7 @@ import ( "path/filepath" "slices" "sort" + "sync" "sync/atomic" "time" @@ -273,6 +274,12 @@ type WalletKit struct { UnimplementedWalletKitServer cfg *Config + + // As we allow rpc requests into the server before InjectDependencies + // has been executed, the read lock should be held when accessing values + // from the cfg. + // The write lock should be held when setting the cfg. + sync.RWMutex } // A compile time check to ensure that WalletKit fully implements the @@ -304,6 +311,9 @@ func (w *WalletKit) InjectDependencies( return lnrpc.ErrDependenciesFinalized } + w.Lock() + defer w.Unlock() + cfg, err := getConfig(configRegistry, finalizeDependencies) if err != nil { return err @@ -424,6 +434,9 @@ func (r *ServerShell) CreateSubServer() ( // internalScope returns the internal key scope. func (w *WalletKit) internalScope() waddrmgr.KeyScope { + w.RLock() + defer w.RUnlock() + return waddrmgr.KeyScope{ Purpose: keychain.BIP0043Purpose, Coin: w.cfg.ChainParams.HDCoinType, @@ -466,6 +479,10 @@ func (w *WalletKit) ListUnspent(ctx context.Context, // any other concurrent processes attempting to lock any UTXOs which may // be shown available to us. var utxos []*lnwallet.Utxo + + w.RLock() + defer w.RUnlock() + err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { utxos, err = w.cfg.Wallet.ListUnspentWitness( minConfs, maxConfs, req.Account, @@ -492,15 +509,23 @@ func (w *WalletKit) ListUnspent(ctx context.Context, func (w *WalletKit) SignCoordinatorStreams( stream WalletKit_SignCoordinatorStreamsServer) error { + w.RLock() + // Check that the user actually has configured that the reverse remote // signer functionality should be enabled. if w.cfg.RemoteSignerConnection == nil { + w.RUnlock() + return fmt.Errorf("inbound connections from remote signers " + "not enabled in config") } connectionCoordinator := w.cfg.RemoteSignerConnection + // Release the read lock as we will acquire the write in the + // InjectDependencies function while the stream is still open. + w.RUnlock() + return connectionCoordinator.AddConnection(stream) } @@ -544,6 +569,9 @@ func (w *WalletKit) LeaseOutput(ctx context.Context, duration = time.Duration(req.ExpirationSeconds) * time.Second } + w.RLock() + defer w.RUnlock() + // Acquire the global coin selection lock to ensure there aren't any // other concurrent processes attempting to lease the same UTXO. var expiration time.Time @@ -579,6 +607,9 @@ func (w *WalletKit) ReleaseOutput(ctx context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + // Acquire the global coin selection lock to maintain consistency as // it's acquired when we initially leased the output. err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { @@ -597,6 +628,9 @@ func (w *WalletKit) ReleaseOutput(ctx context.Context, func (w *WalletKit) ListLeases(ctx context.Context, req *ListLeasesRequest) (*ListLeasesResponse, error) { + w.RLock() + defer w.RUnlock() + leases, err := w.cfg.Wallet.ListLeasedOutputs() if err != nil { return nil, err @@ -613,6 +647,9 @@ func (w *WalletKit) ListLeases(ctx context.Context, func (w *WalletKit) DeriveNextKey(ctx context.Context, req *KeyReq) (*signrpc.KeyDescriptor, error) { + w.RLock() + defer w.RUnlock() + nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey( keychain.KeyFamily(req.KeyFamily), ) @@ -634,6 +671,9 @@ func (w *WalletKit) DeriveNextKey(ctx context.Context, func (w *WalletKit) DeriveKey(ctx context.Context, req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) { + w.RLock() + defer w.RUnlock() + keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{ Family: keychain.KeyFamily(req.KeyFamily), Index: uint32(req.KeyIndex), @@ -673,6 +713,9 @@ func (w *WalletKit) NextAddr(ctx context.Context, addrType = lnwallet.TaprootPubkey } + w.RLock() + defer w.RUnlock() + addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account) if err != nil { return nil, err @@ -698,6 +741,9 @@ func (w *WalletKit) GetTransaction(_ context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + res, err := w.cfg.Wallet.GetTransactionDetails(txHash) if err != nil { return nil, err @@ -731,6 +777,9 @@ func (w *WalletKit) PublishTransaction(ctx context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + err = w.cfg.Wallet.PublishTransaction(tx, label) if err != nil { return nil, err @@ -763,6 +812,9 @@ func (w *WalletKit) RemoveTransaction(_ context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + // Query the tx store of our internal wallet for the specified // transaction. res, err := w.cfg.Wallet.GetTransactionDetails(txHash) @@ -833,6 +885,9 @@ func (w *WalletKit) SendOutputs(ctx context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + // Before sending out funds we need to ensure that the remainder of our // wallet funds would cover for the anchor reserve requirement. We'll // also take unconfirmed funds into account. @@ -900,6 +955,9 @@ func (w *WalletKit) EstimateFee(ctx context.Context, "than 0") } + w.RLock() + defer w.RUnlock() + satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW( uint32(req.ConfTarget), ) @@ -923,6 +981,9 @@ func (w *WalletKit) EstimateFee(ctx context.Context, func (w *WalletKit) PendingSweeps(ctx context.Context, in *PendingSweepsRequest) (*PendingSweepsResponse, error) { + w.RLock() + defer w.RUnlock() + // Retrieve all of the outputs the UtxoSweeper is currently trying to // sweep. inputsMap, err := w.cfg.Sweeper.PendingInputs() @@ -1091,6 +1152,9 @@ func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest, return sweep.Params{}, false, err } + w.RLock() + defer w.RUnlock() + // Get the current pending inputs. inputMap, err := w.cfg.Sweeper.PendingInputs() if err != nil { @@ -1184,6 +1248,9 @@ func (w *WalletKit) BumpFee(ctx context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + // Get the current height so we can calculate the deadline height. _, currentHeight, err := w.cfg.Chain.GetBestBlock() if err != nil { @@ -1396,6 +1463,9 @@ func (w *WalletKit) BumpForceCloseFee(_ context.Context, func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32, params sweep.Params) error { + w.RLock() + defer w.RUnlock() + log.Debugf("Attempting to sweep outpoint %s", op) // Since the sweeper is not aware of the input, we'll assume the user @@ -1470,6 +1540,9 @@ func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32, func (w *WalletKit) ListSweeps(ctx context.Context, in *ListSweepsRequest) (*ListSweepsResponse, error) { + w.RLock() + defer w.RUnlock() + sweeps, err := w.cfg.Sweeper.ListSweeps() if err != nil { return nil, err @@ -1553,6 +1626,9 @@ func (w *WalletKit) LabelTransaction(ctx context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite) return &LabelTransactionResponse{ @@ -1593,6 +1669,9 @@ func (w *WalletKit) LabelTransaction(ctx context.Context, func (w *WalletKit) FundPsbt(_ context.Context, req *FundPsbtRequest) (*FundPsbtResponse, error) { + w.RLock() + defer w.RUnlock() + coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy( req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy, ) @@ -1845,6 +1924,9 @@ func (w *WalletKit) fundPsbtInternalWallet(account string, customLockID *wtxmgr.LockID, customLockDuration time.Duration) ( *FundPsbtResponse, error) { + w.RLock() + defer w.RUnlock() + // The RPC parsing part is now over. Several of the following operations // require us to hold the global coin selection lock, so we do the rest // of the tasks while holding the lock. The result is a list of locked @@ -1973,6 +2055,8 @@ func (w *WalletKit) fundPsbtInternalWallet(account string, // fundPsbtCoinSelect uses the "new" PSBT funding method using the channel // funding coin selection algorithm that allows specifying custom inputs while // selecting coins. +// +//nolint:funlen func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, packet *psbt.Packet, minConfs int32, changeType chanfunding.ChangeAddressType, @@ -1990,6 +2074,9 @@ func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, return nil, err } + w.RLock() + defer w.RUnlock() + // In case the user just specified the input outpoints of UTXOs we own, // the fee estimation below will error out because the UTXO information // is missing. We need to fetch the UTXO information from the wallet @@ -2186,6 +2273,9 @@ func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32, account string) error { + w.RLock() + defer w.RUnlock() + return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { // Get a list of all unspent witness outputs. utxos, err := w.cfg.Wallet.ListUnspentWitness( @@ -2218,6 +2308,9 @@ func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet, customLockID *wtxmgr.LockID, customLockDuration time.Duration) ( *FundPsbtResponse, error) { + w.RLock() + defer w.RUnlock() + // Make sure we can properly serialize the packet. If this goes wrong // then something isn't right with the inputs, and we probably shouldn't // try to lock any of them. @@ -2259,6 +2352,9 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32, return changeIndex, nil } + w.RLock() + defer w.RUnlock() + // The user requested a new change output. addrType := addrTypeFromChangeAddressType(changeType) changeAddr, err := w.cfg.Wallet.NewAddress( @@ -2396,6 +2492,9 @@ func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) ( } } + w.RLock() + defer w.RUnlock() + // Let the wallet do the heavy lifting. This will sign all inputs that // we have the UTXO for. If some inputs can't be signed and don't have // witness data attached, they will just be skipped. @@ -2452,6 +2551,9 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, return nil, fmt.Errorf("PSBT is already fully signed") } + w.RLock() + defer w.RUnlock() + // Let the wallet do the heavy lifting. This will sign all inputs that // we have the UTXO for. If some inputs can't be signed and don't have // witness data attached, this will fail. @@ -2623,6 +2725,9 @@ func (w *WalletKit) ListAccounts(ctx context.Context, req.AddressType) } + w.RLock() + defer w.RUnlock() + accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter) if err != nil { return nil, err @@ -2657,6 +2762,9 @@ func (w *WalletKit) ListAccounts(ctx context.Context, func (w *WalletKit) RequiredReserve(ctx context.Context, req *RequiredReserveRequest) (*RequiredReserveResponse, error) { + w.RLock() + defer w.RUnlock() + numAnchorChans, err := w.cfg.CurrentNumAnchorChans() if err != nil { return nil, err @@ -2677,6 +2785,9 @@ func (w *WalletKit) RequiredReserve(ctx context.Context, func (w *WalletKit) ListAddresses(ctx context.Context, req *ListAddressesRequest) (*ListAddressesResponse, error) { + w.RLock() + defer w.RUnlock() + addressLists, err := w.cfg.Wallet.ListAddresses( req.AccountName, req.ShowCustomAccounts, @@ -2770,6 +2881,9 @@ const msgSignaturePrefix = "Bitcoin Signed Message:\n" func (w *WalletKit) SignMessageWithAddr(_ context.Context, req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) { + w.RLock() + defer w.RUnlock() + addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) @@ -2857,6 +2971,9 @@ func (w *WalletKit) VerifyMessageWithAddr(_ context.Context, serializedPubkey = pk.SerializeUncompressed() } + w.RLock() + defer w.RUnlock() + addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) @@ -2978,6 +3095,9 @@ func (w *WalletKit) ImportAccount(_ context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount( req.Name, accountPubKey, mkfp, addrType, req.DryRun, ) @@ -3037,6 +3157,9 @@ func (w *WalletKit) ImportPublicKey(_ context.Context, return nil, err } + w.RLock() + defer w.RUnlock() + if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil { return nil, err } @@ -3116,6 +3239,9 @@ func (w *WalletKit) ImportTapscript(_ context.Context, return nil, fmt.Errorf("invalid script") } + w.RLock() + defer w.RUnlock() + taprootScope := waddrmgr.KeyScopeBIP0086 addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript) if err != nil { diff --git a/rpcserver.go b/rpcserver.go index 7b13dbd5a53..aaf809a09fb 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -696,8 +696,9 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, } } -// prepareSubServers prepares the sub-servers, and insert the permissions -// required to access them into the interceptor chain. +// prepareSubServers prepares the sub-servers. The function populates the wallet +// sub-server configuration with the remote signer connection, and insert the +// permissions required to access the sub-servers into the interceptor chain. func (r *rpcServer) prepareSubServers(macService *macaroons.Service, subServerCgs *subRPCServerConfigs, cc *chainreg.ChainControl) error { @@ -720,6 +721,30 @@ func (r *rpcServer) prepareSubServers(macService *macaroons.Service, // interceptors below. subServers = append(subServers, subServer) subServerPerms = append(subServerPerms, macPerms) + + // We need to populate the wallet sub-server configuration with + // the remote signer values, and then inject the values into the + // wallet sub-server. This needs to be done prior to the other + // sub-servers, as we need the wallet sub-server to be able to + // accept connections from a remote signer before the other + // sub-servers will be ready to handle requests. + if subServer.Name() == walletrpc.SubServerName { + // Populate the wallet sub-server configuration with the + // remote signer connection. + err := subServerCgs.PopulateRemoteSignerConnectionCfg( + r.cfg, cc, + ) + if err != nil { + return err + } + + // Inject the remote signer values into the wallet + // sub-server. + err = subServer.InjectDependencies(subServerCgs, false) + if err != nil { + return err + } + } } // Next, we need to merge the set of sub server macaroon permissions diff --git a/subrpcserver_config.go b/subrpcserver_config.go index 5c0b9ba582d..efe1c720207 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -214,13 +214,10 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(chanStateDB), ) - kRing, ok := cc.Wallet.WalletController.(*rpcwallet.RPCKeyRing) - if !ok || !cfg.RemoteSigner.AllowInboundConnection { - break - } - subCfgValue.FieldByName("RemoteSignerConnection").Set( - reflect.ValueOf(kRing.RemoteSignerConnection()), - ) + // The "RemoteSignerConnection" field have already been + // added through the PopulateRemoteSignerCfgValues + // function, and we therefore don't need to overwrite + // them here. case *autopilotrpc.Config: subCfgValue := extractReflectValue(subCfg) @@ -392,6 +389,30 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, return nil } +// PopulateRemoteSignerConnectionCfg populates the WalletKit sub-server config +// with the remote signer connection instance, given that it's an inbound +// connection. +func (s *subRPCServerConfigs) PopulateRemoteSignerConnectionCfg(cfg *Config, + cc *chainreg.ChainControl) error { + + // Only populate the WalletKit sub-server with the connection if it's + // we allow inbound connections. + if !cfg.RemoteSigner.AllowInboundConnection { + return nil + } + + // Extract the WalletKit sub-server config, and populate the config with + // the remote signer connection. + subCfgValue := extractReflectValue(s.WalletKitRPC) + + if rpckKeyRing, ok := cc.Wc.(*rpcwallet.RPCKeyRing); ok { + conn := reflect.ValueOf(rpckKeyRing.RemoteSignerConnection()) + subCfgValue.FieldByName("RemoteSignerConnection").Set(conn) + } + + return nil +} + // FetchConfig attempts to locate an existing configuration file mapped to the // target sub-server. If we're unable to find a config file matching the // subServerName name, then false will be returned for the second parameter. From 602c640d069d3529d92e348487ee4859b4b3ff3a Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Thu, 5 Dec 2024 12:25:56 +0100 Subject: [PATCH 27/39] f - multi: populate `RemoteSignerConnection` ref early This commit populates the `RemoteSignerConnection` reference in the `WalletKit` config before other dependencies are added. To ensure that an outbound remote signer can connect before other dependencies are created, and since we use this reference in the walletrpc `SignCoordinatorStreams` RPC, we must populate this dependency prior to other dependencies during the lnd startup process. --- lnrpc/walletrpc/walletkit_server.go | 113 +--------------------------- 1 file changed, 3 insertions(+), 110 deletions(-) diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 04f2a7c207a..5ea2bba2842 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -275,9 +275,9 @@ type WalletKit struct { cfg *Config - // As we allow rpc requests into the server before InjectDependencies - // has been executed, the read lock should be held when accessing values - // from the cfg. + // As we allow rpc requests into the server before dependencies have + // been finalized, the read lock should be held in functions that + // accesses values from the cfg before dependencies are finalized. // The write lock should be held when setting the cfg. sync.RWMutex } @@ -434,9 +434,6 @@ func (r *ServerShell) CreateSubServer() ( // internalScope returns the internal key scope. func (w *WalletKit) internalScope() waddrmgr.KeyScope { - w.RLock() - defer w.RUnlock() - return waddrmgr.KeyScope{ Purpose: keychain.BIP0043Purpose, Coin: w.cfg.ChainParams.HDCoinType, @@ -480,9 +477,6 @@ func (w *WalletKit) ListUnspent(ctx context.Context, // be shown available to us. var utxos []*lnwallet.Utxo - w.RLock() - defer w.RUnlock() - err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { utxos, err = w.cfg.Wallet.ListUnspentWitness( minConfs, maxConfs, req.Account, @@ -569,9 +563,6 @@ func (w *WalletKit) LeaseOutput(ctx context.Context, duration = time.Duration(req.ExpirationSeconds) * time.Second } - w.RLock() - defer w.RUnlock() - // Acquire the global coin selection lock to ensure there aren't any // other concurrent processes attempting to lease the same UTXO. var expiration time.Time @@ -607,9 +598,6 @@ func (w *WalletKit) ReleaseOutput(ctx context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - // Acquire the global coin selection lock to maintain consistency as // it's acquired when we initially leased the output. err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { @@ -628,9 +616,6 @@ func (w *WalletKit) ReleaseOutput(ctx context.Context, func (w *WalletKit) ListLeases(ctx context.Context, req *ListLeasesRequest) (*ListLeasesResponse, error) { - w.RLock() - defer w.RUnlock() - leases, err := w.cfg.Wallet.ListLeasedOutputs() if err != nil { return nil, err @@ -647,9 +632,6 @@ func (w *WalletKit) ListLeases(ctx context.Context, func (w *WalletKit) DeriveNextKey(ctx context.Context, req *KeyReq) (*signrpc.KeyDescriptor, error) { - w.RLock() - defer w.RUnlock() - nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey( keychain.KeyFamily(req.KeyFamily), ) @@ -671,9 +653,6 @@ func (w *WalletKit) DeriveNextKey(ctx context.Context, func (w *WalletKit) DeriveKey(ctx context.Context, req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) { - w.RLock() - defer w.RUnlock() - keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{ Family: keychain.KeyFamily(req.KeyFamily), Index: uint32(req.KeyIndex), @@ -713,9 +692,6 @@ func (w *WalletKit) NextAddr(ctx context.Context, addrType = lnwallet.TaprootPubkey } - w.RLock() - defer w.RUnlock() - addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account) if err != nil { return nil, err @@ -741,9 +717,6 @@ func (w *WalletKit) GetTransaction(_ context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - res, err := w.cfg.Wallet.GetTransactionDetails(txHash) if err != nil { return nil, err @@ -777,9 +750,6 @@ func (w *WalletKit) PublishTransaction(ctx context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - err = w.cfg.Wallet.PublishTransaction(tx, label) if err != nil { return nil, err @@ -812,9 +782,6 @@ func (w *WalletKit) RemoveTransaction(_ context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - // Query the tx store of our internal wallet for the specified // transaction. res, err := w.cfg.Wallet.GetTransactionDetails(txHash) @@ -885,9 +852,6 @@ func (w *WalletKit) SendOutputs(ctx context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - // Before sending out funds we need to ensure that the remainder of our // wallet funds would cover for the anchor reserve requirement. We'll // also take unconfirmed funds into account. @@ -955,9 +919,6 @@ func (w *WalletKit) EstimateFee(ctx context.Context, "than 0") } - w.RLock() - defer w.RUnlock() - satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW( uint32(req.ConfTarget), ) @@ -981,9 +942,6 @@ func (w *WalletKit) EstimateFee(ctx context.Context, func (w *WalletKit) PendingSweeps(ctx context.Context, in *PendingSweepsRequest) (*PendingSweepsResponse, error) { - w.RLock() - defer w.RUnlock() - // Retrieve all of the outputs the UtxoSweeper is currently trying to // sweep. inputsMap, err := w.cfg.Sweeper.PendingInputs() @@ -1152,9 +1110,6 @@ func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest, return sweep.Params{}, false, err } - w.RLock() - defer w.RUnlock() - // Get the current pending inputs. inputMap, err := w.cfg.Sweeper.PendingInputs() if err != nil { @@ -1248,9 +1203,6 @@ func (w *WalletKit) BumpFee(ctx context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - // Get the current height so we can calculate the deadline height. _, currentHeight, err := w.cfg.Chain.GetBestBlock() if err != nil { @@ -1463,9 +1415,6 @@ func (w *WalletKit) BumpForceCloseFee(_ context.Context, func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32, params sweep.Params) error { - w.RLock() - defer w.RUnlock() - log.Debugf("Attempting to sweep outpoint %s", op) // Since the sweeper is not aware of the input, we'll assume the user @@ -1540,9 +1489,6 @@ func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32, func (w *WalletKit) ListSweeps(ctx context.Context, in *ListSweepsRequest) (*ListSweepsResponse, error) { - w.RLock() - defer w.RUnlock() - sweeps, err := w.cfg.Sweeper.ListSweeps() if err != nil { return nil, err @@ -1626,9 +1572,6 @@ func (w *WalletKit) LabelTransaction(ctx context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite) return &LabelTransactionResponse{ @@ -1669,9 +1612,6 @@ func (w *WalletKit) LabelTransaction(ctx context.Context, func (w *WalletKit) FundPsbt(_ context.Context, req *FundPsbtRequest) (*FundPsbtResponse, error) { - w.RLock() - defer w.RUnlock() - coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy( req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy, ) @@ -1924,9 +1864,6 @@ func (w *WalletKit) fundPsbtInternalWallet(account string, customLockID *wtxmgr.LockID, customLockDuration time.Duration) ( *FundPsbtResponse, error) { - w.RLock() - defer w.RUnlock() - // The RPC parsing part is now over. Several of the following operations // require us to hold the global coin selection lock, so we do the rest // of the tasks while holding the lock. The result is a list of locked @@ -2055,8 +1992,6 @@ func (w *WalletKit) fundPsbtInternalWallet(account string, // fundPsbtCoinSelect uses the "new" PSBT funding method using the channel // funding coin selection algorithm that allows specifying custom inputs while // selecting coins. -// -//nolint:funlen func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, packet *psbt.Packet, minConfs int32, changeType chanfunding.ChangeAddressType, @@ -2074,9 +2009,6 @@ func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, return nil, err } - w.RLock() - defer w.RUnlock() - // In case the user just specified the input outpoints of UTXOs we own, // the fee estimation below will error out because the UTXO information // is missing. We need to fetch the UTXO information from the wallet @@ -2273,9 +2205,6 @@ func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32, func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32, account string) error { - w.RLock() - defer w.RUnlock() - return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error { // Get a list of all unspent witness outputs. utxos, err := w.cfg.Wallet.ListUnspentWitness( @@ -2308,9 +2237,6 @@ func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet, customLockID *wtxmgr.LockID, customLockDuration time.Duration) ( *FundPsbtResponse, error) { - w.RLock() - defer w.RUnlock() - // Make sure we can properly serialize the packet. If this goes wrong // then something isn't right with the inputs, and we probably shouldn't // try to lock any of them. @@ -2352,9 +2278,6 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32, return changeIndex, nil } - w.RLock() - defer w.RUnlock() - // The user requested a new change output. addrType := addrTypeFromChangeAddressType(changeType) changeAddr, err := w.cfg.Wallet.NewAddress( @@ -2492,9 +2415,6 @@ func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) ( } } - w.RLock() - defer w.RUnlock() - // Let the wallet do the heavy lifting. This will sign all inputs that // we have the UTXO for. If some inputs can't be signed and don't have // witness data attached, they will just be skipped. @@ -2551,9 +2471,6 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, return nil, fmt.Errorf("PSBT is already fully signed") } - w.RLock() - defer w.RUnlock() - // Let the wallet do the heavy lifting. This will sign all inputs that // we have the UTXO for. If some inputs can't be signed and don't have // witness data attached, this will fail. @@ -2725,9 +2642,6 @@ func (w *WalletKit) ListAccounts(ctx context.Context, req.AddressType) } - w.RLock() - defer w.RUnlock() - accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter) if err != nil { return nil, err @@ -2762,9 +2676,6 @@ func (w *WalletKit) ListAccounts(ctx context.Context, func (w *WalletKit) RequiredReserve(ctx context.Context, req *RequiredReserveRequest) (*RequiredReserveResponse, error) { - w.RLock() - defer w.RUnlock() - numAnchorChans, err := w.cfg.CurrentNumAnchorChans() if err != nil { return nil, err @@ -2785,9 +2696,6 @@ func (w *WalletKit) RequiredReserve(ctx context.Context, func (w *WalletKit) ListAddresses(ctx context.Context, req *ListAddressesRequest) (*ListAddressesResponse, error) { - w.RLock() - defer w.RUnlock() - addressLists, err := w.cfg.Wallet.ListAddresses( req.AccountName, req.ShowCustomAccounts, @@ -2881,9 +2789,6 @@ const msgSignaturePrefix = "Bitcoin Signed Message:\n" func (w *WalletKit) SignMessageWithAddr(_ context.Context, req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) { - w.RLock() - defer w.RUnlock() - addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) @@ -2971,9 +2876,6 @@ func (w *WalletKit) VerifyMessageWithAddr(_ context.Context, serializedPubkey = pk.SerializeUncompressed() } - w.RLock() - defer w.RUnlock() - addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) @@ -3095,9 +2997,6 @@ func (w *WalletKit) ImportAccount(_ context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount( req.Name, accountPubKey, mkfp, addrType, req.DryRun, ) @@ -3157,9 +3056,6 @@ func (w *WalletKit) ImportPublicKey(_ context.Context, return nil, err } - w.RLock() - defer w.RUnlock() - if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil { return nil, err } @@ -3239,9 +3135,6 @@ func (w *WalletKit) ImportTapscript(_ context.Context, return nil, fmt.Errorf("invalid script") } - w.RLock() - defer w.RUnlock() - taprootScope := waddrmgr.KeyScopeBIP0086 addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript) if err != nil { From 9682594c1e723abe9a5a3f3c745ea7c04edfa72a Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 17:20:54 +0200 Subject: [PATCH 28/39] multi: add `ReadySignal` to `WalletController` Previous commits added functionality to handle the incoming connection from an outbound remote signer and ensured that the outbound remote signer could connect before any signatures from the remote signer are needed. However, one issue still remains: we need to ensure that we wait for the outbound remote signer to connect when starting lnd before executing any code that requires the remote signer to be connected. This commit adds a `ReadySignal` function to the `WalletController` that returns a channel, which will signal once the wallet is ready to be used. For an `InboundConnection`, this channel will only signal once the outbound remote signer has connected. This can then be used to ensure that lnd waits for the outbound remote signer to connect during the startup process. --- lntest/mock/walletcontroller.go | 9 +++++++++ lnwallet/btcwallet/btcwallet.go | 9 +++++++++ lnwallet/interface.go | 5 +++++ lnwallet/mock.go | 9 +++++++++ lnwallet/rpcwallet/rpcwallet.go | 10 ++++++++++ 5 files changed, 42 insertions(+) diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index fa623bf84dd..9151c82cc4c 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -1,6 +1,7 @@ package mock import ( + "context" "encoding/hex" "sync/atomic" "time" @@ -281,6 +282,14 @@ func (w *WalletController) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (w *WalletController) ReadySignal(_ context.Context) chan error { + readyChan := make(chan error, 1) + readyChan <- nil + + return readyChan +} + func (w *WalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) { return nil, nil } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index a29139dbab9..8e0437ec187 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -2,6 +2,7 @@ package btcwallet import ( "bytes" + "context" "encoding/hex" "errors" "fmt" @@ -410,6 +411,14 @@ func (b *BtcWallet) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (b *BtcWallet) ReadySignal(_ context.Context) chan error { + readyChan := make(chan error, 1) + readyChan <- nil + + return readyChan +} + // ConfirmedBalance returns the sum of all the wallet's unspent outputs that // have at least confs confirmations. If confs is set to zero, then all unspent // outputs, including those currently in the mempool will be included in the diff --git a/lnwallet/interface.go b/lnwallet/interface.go index f5a717d3f55..24e74d5d0b8 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -1,6 +1,7 @@ package lnwallet import ( + "context" "errors" "fmt" "sync" @@ -539,6 +540,10 @@ type WalletController interface { // starting up required goroutines etc. Start() error + // RequireSignal returns a channel which is sent over with no error, + // once the wallet is ready to be used. + ReadySignal(ctx context.Context) chan error + // Stop signals the wallet for shutdown. Shutdown may entail closing // any active sockets, database handles, stopping goroutines, etc. Stop() error diff --git a/lnwallet/mock.go b/lnwallet/mock.go index 39e520d2760..b39ed60871c 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -1,6 +1,7 @@ package lnwallet import ( + "context" "encoding/hex" "sync/atomic" "time" @@ -296,6 +297,14 @@ func (w *mockWalletController) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (w *mockWalletController) ReadySignal(_ context.Context) chan error { + readyChan := make(chan error, 1) + readyChan <- nil + + return readyChan +} + func (w *mockWalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) { return nil, nil } diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 937ad09a446..70f3efc4666 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -914,6 +914,16 @@ func (r *RPCKeyRing) RemoteSignerConnection() RemoteSignerConnection { return r.remoteSignerConn } +// ReadySignal returns a channel that signals once the wallet is ready to be +// used, i.e. once the remote signer is connected. If we time out while waiting, +// an error gets sent over the channel. This method overrides/shadows the +// default implementation of the WalletController interface. +// +// NOTE: This method is part of the WalletController interface. +func (r *RPCKeyRing) ReadySignal(ctx context.Context) chan error { + return r.remoteSignerConn.Ready(ctx) +} + // MuSig2Cleanup removes a session from memory to free up resources. func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { req := &signrpc.MuSig2CleanupRequest{ From 9040572d3f1912200fe561f95c742c51f5a7ee26 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 28 May 2024 02:33:49 +0200 Subject: [PATCH 29/39] lnd: await remote signer connection on startup With the functionality in place to allow an outbound remote signer to connect before any signatures are needed and the ability to wait for this connection, this commit enables the functionality to wait for the remote signer to connect before proceeding with the startup process. This includes setting the `WalletState` in the `InterceptorChain` to `AllowRemoteSigner` before waiting for the outbound remote signer to connect. --- lnd.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lnd.go b/lnd.go index 5636d9febbb..a1d2dc5330b 100644 --- a/lnd.go +++ b/lnd.go @@ -505,7 +505,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // Prepare the sub-servers, and insert the permissions required to // access them into the interceptor chain. Note that we do not yet have - // all dependencies required to use all sub-servers. + // all dependencies required to use all sub-servers, but we need be able + // to allow a remote signer to connect to lnd before we can derive the + // keys create the required dependencies. err = rpcServer.prepareSubServers( interceptorChain.MacaroonService(), cfg.SubRPCServers, activeChainControl, @@ -521,6 +523,36 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } }() + // To ensure that a potential remote signer can connect to lnd before we + // can handle other requests, we set the interceptor chain to be ready + // accept remote signer connections, if enabled by the cfg. + if cfg.RemoteSigner.AllowInboundConnection { + interceptorChain.SetAllowRemoteSigner() + } + + // We'll wait until the wallet is fully ready to be used before we + // proceed to derive keys from it. + select { + case err = <-activeChainControl.Wallet.WalletController.ReadySignal( + ctx, + ): + + if err != nil { + return mkErr("error when waiting for wallet to be "+ + "ready", err) + } + + case <-interceptor.ShutdownChannel(): + // If we receive a shutdown signal while waiting for the wallet + // to be ready, we must stop blocking so that all the deferred + // clean up functions can be executed. That will also shut down + // the wallet. + // We can't continue to execute the code below as we can't + // do any operations which requires private keys. + return mkErr("Shutting down", errors.New("shutdown signal "+ + "received while waiting for wallet to be ready")) + } + // TODO(roasbeef): add rotation idKeyDesc, err := activeChainControl.KeyRing.DeriveKey( keychain.KeyLocator{ From 02d53ed83067f473a515659380356b8ab012a5f9 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Dec 2024 01:38:48 +0100 Subject: [PATCH 30/39] multi: allow `remotesigner.allowinboundconnection` With all the necessary components on the watch-only node side in place to support usage of an outbound remote signer, the `lncfg` package can now permit the `remotesigner.allowinboundconnection` setting to be configured. This commit also adds support for the building `InboundConnection` instances in the `RemoteSignerConnectionBuilder`. --- lncfg/remotesigner.go | 23 ++++++++++--------- .../remote_signer_connection_builder.go | 11 ++++++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 5d22698c4fa..2566c1501f6 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -64,19 +64,20 @@ func DefaultRemoteSignerCfg() *RemoteSigner { // Validate checks the values configured for our remote RPC signer. func (r *RemoteSigner) Validate() error { - if r.MigrateWatchOnly && !r.Enable { - return fmt.Errorf("remote signer: cannot turn on wallet " + - "migration to watch-only if remote signing is not " + - "enabled") - } - if !r.Enable { - return nil - } + if r.MigrateWatchOnly { + return fmt.Errorf("remote signer: cannot turn on " + + "wallet migration to watch-only if remote " + + "signing is not enabled") + } - if r.AllowInboundConnection { - return fmt.Errorf("remote signer: allowinboundconnection " + - "is not supported yet") + if r.AllowInboundConnection { + return fmt.Errorf("remote signer: cannot enable " + + "'allowinboundconnection' if remote signing " + + "is not enabled") + } + + return nil } if r.AllowInboundConnection { diff --git a/lnwallet/rpcwallet/remote_signer_connection_builder.go b/lnwallet/rpcwallet/remote_signer_connection_builder.go index 4f5e66e992d..0e9e52d0229 100644 --- a/lnwallet/rpcwallet/remote_signer_connection_builder.go +++ b/lnwallet/rpcwallet/remote_signer_connection_builder.go @@ -37,5 +37,14 @@ func (b *RemoteSignerConnectionBuilder) Build( "config") } - return NewOutboundConnection(ctx, b.cfg.ConnectionCfg) + // Create the remote signer based on the configuration. + if !b.cfg.AllowInboundConnection { + return NewOutboundConnection(ctx, b.cfg.ConnectionCfg) + } + + inboundConnection := NewInboundConnection( + b.cfg.RequestTimeout, b.cfg.StartupTimeout, + ) + + return inboundConnection, nil } From 42300675371a2f406cfc1676d75998d1aff87493 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Mon, 13 May 2024 15:17:23 +0200 Subject: [PATCH 31/39] docs: add outbound signer to remote signing docs With support for the outbound remote signer now added, we update the documentation to detail how to enable the use of this new remote signer type. --- docs/remote-signing.md | 203 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 191 insertions(+), 12 deletions(-) diff --git a/docs/remote-signing.md b/docs/remote-signing.md index 0f06463b3f2..8074b3be37a 100644 --- a/docs/remote-signing.md +++ b/docs/remote-signing.md @@ -8,11 +8,11 @@ keys in its wallet. The second instance (in this document referred to as the **private** keys. The advantage of such a setup is that the `lnd` instance containing the private -keys (the "signer") can be completely offline except for a single inbound gRPC -connection. +keys (the "signer") can be completely offline except for a single inbound or +outbound gRPC connection. The signer instance can run on a different machine with more tightly locked down -network security, optimally only allowing the single gRPC connection from the -outside. +network security, optimally only allowing the single gRPC connection to or from +the outside. An example setup could look like: @@ -39,12 +39,24 @@ xxx xx ``` -## Example setup +When using a remote signer, the "signer" node can be configured to operate in +one of two modes. +It can either be configured as an "inbound" remote signer (the default setting) +or as an "outbound" remote signer. As an "inbound" remote signer, the signer +node permits a single inbound gRPC connection **from** the watch-only lnd node. +Conversely, when configured as an "outbound" remote signer, it allows a single +outbound gRPC connection **to** the watch-only lnd node. -In this example we are going to set up two nodes, the "signer" that has the full -seed and private keys and the "watch-only" node that only has public keys. +## Example setups -### The "signer" node +In the examples below, we demonstrate how to configure the "signer" node and the +"watch-only" node, either using an "inbound" or an "outbound" remote signer. The +"signer" node possesses the full seed and private keys, while the "watch-only" +node holds only the public keys. + +### Inbound remote signer example (default option) + +#### The inbound "signer" node The node "signer" is the hardened node that contains the private key material and is not connected to the internet or LN P2P network at all. Ideally only a @@ -104,7 +116,7 @@ signer> $ lncli bakemacaroon --save_to signer.custom.macaroon \ Copy this file (`signer.custom.macaroon`) along with the `tls.cert` of the signer node to the machine where the watch-only node will be running. -### The "watch-only" node +#### The "watch-only" node with an inbound remote signer The node "watch-only" is the public, internet facing node that does not contain any private keys in its wallet but delegates all signing operations to the node @@ -118,6 +130,10 @@ remotesigner.enable=true remotesigner.rpchost=zane.example.internal:10019 remotesigner.tlscertpath=/home/watch-only/example/signer.tls.cert remotesigner.macaroonpath=/home/watch-only/example/signer.custom.macaroon +# Optionally, specify that the watch-only doesn't allow any inbound connections +# from the remote signer. +# However, since this is the default behaviour, this isn't required. +remotesigner.allowinboundconnection=false ``` After starting "watch-only", the wallet can be created in watch-only mode by @@ -136,7 +152,170 @@ Input an optional address look-ahead used to scan for used keys (default 2500): ``` Alternatively a script can be used for initializing the watch-only wallet -through the RPC interface as is described in the next section. +through the RPC interface as is described in the +[section below](#Example-initialization-script). + +### Outbound remote signer example + +The setup of an outbound remote signer, can be done in 3 steps: + +1. Start the signer node and export the `xpub`s of the wallet. +2. Bake a custom macaroon for the watch-only node with a specified root key, +which allows the signer node to establish an outbound connection to it. +3. Start watch-only node and initialize its watch-only wallet using the same +root key as in step 2. + +Note: These steps are only required during the initial setup of the signer +wallet with a connected watch-only wallet. After this setup, the signer and +watch-only node can be started as usual, provided the configuration from these +steps remains in place. + +#### Step 1: export the `xpub`s of the outbound signer node's wallet + +When starting the signer node to export the `xpub`s of the wallet, these entries +in `lnd.conf` are recommended: + +```text +# We apply some basic "hardening" parameters to make sure no connections to the +# internet are opened. + +[Application Options] +# Don't listen on the p2p port. +nolisten=true + +# Don't reach out to the bootstrap nodes, we don't need a synced graph. +nobootstrap=true + +# The signer node will not look at the chain at all, it only needs to sign +# things with the keys contained in its wallet. So we don't need to hook it up +# to any chain backend. +[bitcoin] +# We still need to signal that we're using the Bitcoin chain. +bitcoin.active=true + +# And we're making sure mainnet parameters are used. +bitcoin.mainnet=true + +# But we aren't using a "real" chain backed but a mocked one. +bitcoin.node=nochainbackend + +# Specify that signer will make an outbound connection to the watch-only node. +watchonlynode.enable=true + +# The watch-only node's RPC host. +watchonlynode.rpchost=zane.example.internal:10019 + +# A macaroon and TLS certificate for the watch-only node. +watchonlynode.macaroonpath=/home/signer/example/watch-only.custom.macaroon +watchonlynode.tlscertpath=/home/signer/example/watch-only.tls.cert +``` + +**Note:** The watch-only node’s `rpchost`, `macaroonpath`, and `tlscertpath` +specified in the configuration will not resolve successfully until steps 2 and 3 +are completed, as these files do not yet exist, and no node is currently running +at the specified `rpchost`. +The signer node will continuously attempt to establish a connection to the +watch-only node using these values until the connection is successful. +Consequently, the configuration values will resolve properly once steps 2 and 3 +have been executed. + +After successfully starting up the "signer", and either unlocking an existing or +creating a new wallet, the following command can be run to export the `xpub`s of +the wallet: + +```shell +signer> $ lncli wallet accounts list > accounts-signer.json +``` + +That `accounts-signer.json` file has to be copied to the machine on which +"watch-only" will be running. It contains the extended public keys for all of +`lnd`'s accounts (see [required accounts](#required-accounts) ). + +#### Step 2: Bake the watch-only node's custom macaroon with a specified root key + +To bake the custom macaroon for the watch-only node before its wallet exists, +first generate a root key, which will be used both to bake the macaroon and to +create the watch-only node's wallet. + +Generation of a root key is exemplified below: + +```shell +watch-only> $ ROOT_KEY=$(cat /dev/urandom | head -c32 | xxd -p -c32) +``` + +Once the root key is ready, bake the custom macaroon with: + +```shell +watch-only> $ lncli bakemacaroon --root_key $ROOT_KEY \ +--save_to /home/signer/example/watch-only.custom.macaroon remotesigner:generate +``` + +**Note:** The `save_to` path should match the `remotesigner.macaroonpath` +specified in step 1. If the signer and watch-only nodes are on separate +environments, move the macaroon to the `remotesigner.macaroonpath` after baking +it instead. + +Also note that the watch-only node does not need to be running to execute this +command. + + +#### Step 3: Start the Watch-Only Node and Initialize Its Watch-Only Wallet + +When starting the watch-only node, ensure the following entries are set in +`lnd.conf`: + +```text +# Enable the use of a remote signer. +remotesigner.enable=true + +# Specify that the watch-only node will accept an incoming connection from the +# remote signer. +remotesigner.allowinboundconnection=true +``` + +It is also recommended to set the following parameter, which defines the +interval at which the watch-only node will check if the signer node is still +connected. If the signer node is disconnected during a check, the watch-only +node will shut down: + +```text +# Set the interval for how often the watch-only node will check that the signer +# node is still connected. +healthcheck.remotesigner.interval=5s +``` + +Since the signer node set up in Step 1 increases the delay between connection +attempts slightly with each failed attempt, it may take some time before it +reconnects to the watch-only node after it has been started. Setting a high +value for this configuration field will help ensure that the watch-only node +does not time out when starting up. + +After starting the watch-only node, you can create a new watch-only wallet by +following the example below: + +```shell +watch-only> $ lncli createwatchonly --mac_root_key $ROOT_KEY \ + accounts-signer.json + +Input wallet password: +Confirm password: + +Input an optional wallet birthday unix timestamp of first block to start scanning from (default 0): + + +Input an optional address look-ahead used to scan for used keys (default 2500): +``` + +**Note:** This command should be executed in an environment where the +`$ROOT_KEY` environment variable, created in Step 2, is defined. When selecting +a wallet birthday UNIX timestamp, choose one that is as close as possible to the +wallet’s actual creation time to expedite the initial setup of the watch-only +wallet. + +Finally, if the watch-only node and signer node are set up in different +environments, you will also need to copy the watch-only node's TLS certificate +and place it in the path specified for the `watchonlynode.tlscertpath` +configuration field in Step 1. ## Migrating an existing setup to remote signing @@ -146,9 +325,9 @@ a watch-only and a remote signer node). To migrate an existing node, follow these steps: 1. Create a new "signer" node using the same seed as the existing node, - following the steps [mentioned above](#the-signer-node). + following the steps the "signer" node examples above. 2. In the configuration of the existing node, add the configuration entries as - [shown above](#the-watch-only-node). But instead of creating a new wallet + "watch-only" node examples above. But instead of creating a new wallet (since one already exists), instruct `lnd` to migrate the existing wallet to a watch-only one (by purging all private key material from it) by adding the `remotesigner.migrate-wallet-to-watch-only=true` configuration entry. From a9a9273f19acc73be4439188b2be56d7989a42ec Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Thu, 31 Oct 2024 16:51:28 +0100 Subject: [PATCH 32/39] docs: update release notes Update release notes to include information about the support for the new outbound remote signer type. --- docs/release-notes/release-notes-0.21.0.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index ee39431a603..23be10c7502 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -93,6 +93,10 @@ specify a list of inputs to use as transaction inputs via the new `inputs` field in `EstimateFeeRequest`. +* [SignCoordinatorStreams](https://github.com/lightningnetwork/lnd/pull/8754) + allows a remote signer to connect to the lnd node, if the + `remotesigner.allowinboundconnection` cfg value has been set to `true`. + ## lncli Additions * The `estimatefee` command now supports the `--utxos` flag to specify explicit @@ -107,9 +111,16 @@ This applies to both funders and fundees, with the ability to override the value during channel opening or acceptance. +<<<<<<< HEAD * Rename [experimental endorsement signal](https://github.com/lightning/blips/blob/a833e7b49f224e1240b5d669e78fa950160f5a06/blip-0004.md) to [accountable](https://github.com/lightningnetwork/lnd/pull/10367) to match the latest [proposal](https://github.com/lightning/blips/pull/67). +======= +* [Added](https://github.com/lightningnetwork/lnd/pull/8754) support for a new + remote signer type `outbound`, which makes an outbound connection to the + watch-only node, instead of requiring on an inbound connection from the + watch-only node. +>>>>>>> 39f496b44 (docs: update release notes) ## RPC Updates From 000bb318530ede7c46b5825310ca7045cde0e17c Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 11:50:35 +0200 Subject: [PATCH 33/39] lntest: separate creation/start of watch-only node Update the harness to allow creating a watch-only node without starting it. This is useful for tests that need to create a watch-only node prior to starting it, such as tests that use an outbound remote signer. --- itest/lnd_remote_signer_test.go | 2 +- lntest/harness.go | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index eac22828b5e..81fc4535366 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -162,7 +162,7 @@ func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) ( // WatchOnly is the node that has a watch-only wallet and uses the // Signer node for any operation that requires access to private keys. - watchOnly := ht.NewNodeRemoteSigner( + watchOnly := ht.NewNodeWatchOnly( "WatchOnly", append([]string{ "--remotesigner.enable", fmt.Sprintf( diff --git a/lntest/harness.go b/lntest/harness.go index f3ffc9222c0..24eaf0e69c9 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -238,7 +238,7 @@ func (h *HarnessTest) setupWatchOnlyNode(name string, name) // Create a new watch-only node with remote signer configuration. - return h.NewNodeRemoteSigner( + return h.NewNodeWatchOnly( name, remoteSignerArgs, password, &lnrpc.WatchOnly{ MasterKeyBirthdayTimestamp: 0, @@ -764,15 +764,35 @@ func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, return h.newNodeWithSeed(name, extraArgs, req, statelessInit) } -// NewNodeRemoteSigner creates a new remote signer node and asserts its +// NewNodeWatchOnly creates a new watch-only node and asserts its // creation. -func (h *HarnessTest) NewNodeRemoteSigner(name string, extraArgs []string, +func (h *HarnessTest) NewNodeWatchOnly(name string, extraArgs []string, password []byte, watchOnly *lnrpc.WatchOnly) *node.HarnessNode { + hn := h.CreateNewNode(name, extraArgs, password) + + h.StartWatchOnly(hn, name, password, watchOnly) + + return hn +} + +// CreateNodeWatchOnly creates a new node and asserts its creation. The function +// will only create the node and will not start it. +func (h *HarnessTest) CreateNewNode(name string, extraArgs []string, + password []byte) *node.HarnessNode { + hn, err := h.manager.newNode(h.T, name, extraArgs, password, true) require.NoErrorf(h, err, "unable to create new node for %s", name) - err = hn.StartWithNoAuth(h.runCtx) + return hn +} + +// StartWatchOnly starts the passed node in watch-only mode. The function will +// assert that the node is started and that the initialization is successful. +func (h *HarnessTest) StartWatchOnly(hn *node.HarnessNode, name string, + password []byte, watchOnly *lnrpc.WatchOnly) { + + err := hn.StartWithNoAuth(h.runCtx) require.NoError(h, err, "failed to start node %s", name) // With the seed created, construct the init request to the node, @@ -786,8 +806,6 @@ func (h *HarnessTest) NewNodeRemoteSigner(name string, extraArgs []string, // will also initialize the macaroon-authenticated LightningClient. _, err = h.manager.initWalletAndNode(hn, initReq) require.NoErrorf(h, err, "failed to init node %s", name) - - return hn } // KillNode kills the node and waits for the node process to stop. From 10d3c2efc1202febf6130457bde8780c2ab7487e Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 21 Mar 2025 18:38:54 +0100 Subject: [PATCH 34/39] itest: fix testRemoteSignerRandomSeedOutbound typo rename testRemoteSignerRadomSeedOutbound to testRemoteSignerRandomSeedOutbound, as random was misspelled. --- itest/lnd_remote_signer_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index 81fc4535366..b096786f47d 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -21,7 +21,7 @@ import ( var remoteSignerTestCases = []*lntest.TestCase{ { Name: "random seed", - TestFunc: testRemoteSignerRadomSeed, + TestFunc: testRemoteSignerRandomSeed, }, { Name: "account import", @@ -202,9 +202,9 @@ func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) ( return signer, watchOnly, carol } -// testRemoteSignerRadomSeed tests that a watch-only wallet can use a remote +// testRemoteSignerRandomSeed tests that a watch-only wallet can use a remote // signing wallet to perform any signing or ECDH operations. -func testRemoteSignerRadomSeed(ht *lntest.HarnessTest) { +func testRemoteSignerRandomSeed(ht *lntest.HarnessTest) { tc := remoteSignerTestCase{ name: "random seed", randomSeed: true, From 4ea791decaffcf4a2ae9df216e2f62ecdecfb7c7 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Tue, 14 May 2024 16:07:40 +0200 Subject: [PATCH 35/39] itest: add outbound remote signer itests --- itest/lnd_remote_signer_test.go | 406 +++++++++++++++++++++++++------- 1 file changed, 326 insertions(+), 80 deletions(-) diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index b096786f47d..09490d7f4b5 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -23,54 +23,106 @@ var remoteSignerTestCases = []*lntest.TestCase{ Name: "random seed", TestFunc: testRemoteSignerRandomSeed, }, + { + Name: "random seed outbound", + TestFunc: testRemoteSignerRandomSeedOutbound, + }, { Name: "account import", TestFunc: testRemoteSignerAccountImport, }, + { + Name: "account import outbound", + TestFunc: testRemoteSignerAccountImportOutbound, + }, { Name: "tapscript import", TestFunc: testRemoteSignerTapscriptImport, }, + { + Name: "tapscript import outbound", + TestFunc: testRemoteSignerTapscriptImportOutbound, + }, { Name: "channel open", TestFunc: testRemoteSignerChannelOpen, }, + { + Name: "channel open outbound", + TestFunc: testRemoteSignerChannelOpenOutbound, + }, { Name: "funding input types", TestFunc: testRemoteSignerChannelFundingInputTypes, }, + { + Name: "funding input types outbound", + TestFunc: testRemoteSignerChannelFundingInputTypesOutbound, + }, { Name: "funding async payments", TestFunc: testRemoteSignerAsyncPayments, }, + { + Name: "funding async payments outbound", + TestFunc: testRemoteSignerAsyncPaymentsOutbound, + }, { Name: "funding async payments taproot", TestFunc: testRemoteSignerAsyncPaymentsTaproot, }, + { + Name: "funding async payments taproot outbound", + TestFunc: testRemoteSignerAsyncPaymentsTaprootOutbound, + }, { Name: "shared key", TestFunc: testRemoteSignerSharedKey, }, + { + Name: "shared key outbound", + TestFunc: testRemoteSignerSharedKeyOutbound, + }, { Name: "bump fee", TestFunc: testRemoteSignerBumpFee, }, + { + Name: "bump fee outbound", + TestFunc: testRemoteSignerBumpFeeOutbound, + }, { Name: "psbt", TestFunc: testRemoteSignerPSBT, }, + { + Name: "psbt outbound", + TestFunc: testRemoteSignerPSBTOutbound, + }, { Name: "sign output raw", TestFunc: testRemoteSignerSignOutputRaw, }, + { + Name: "sign output raw outbound", + TestFunc: testRemoteSignerSignOutputRawOutbound, + }, { Name: "verify msg", TestFunc: testRemoteSignerSignVerifyMsg, }, + { + Name: "verify msg outbound", + TestFunc: testRemoteSignerSignVerifyMsgOutbound, + }, { Name: "taproot", TestFunc: testRemoteSignerTaproot, }, + { + Name: "taproot outbound", + TestFunc: testRemoteSignerTaprootOutbound, + }, } var ( @@ -112,17 +164,18 @@ var ( // remoteSignerTestCase defines a test case for the remote signer test suite. type remoteSignerTestCase struct { - name string randomSeed bool sendCoins bool commitType lnrpc.CommitmentType + isOutbound bool fn func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) } -// prepareRemoteSignerTest prepares a test case for the remote signer test -// suite by creating three nodes. -func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) ( - *node.HarnessNode, *node.HarnessNode, *node.HarnessNode) { +// prepareInboundRemoteSignerTest prepares a test case using an inbound remote +// signer for the test suite by creating three nodes. +func prepareInboundRemoteSignerTest(ht *lntest.HarnessTest, + tc remoteSignerTestCase) (*node.HarnessNode, *node.HarnessNode, + *node.HarnessNode) { // Signer is our signing node and has the wallet with the full master // private key. We test that we can create the watch-only wallet from @@ -202,24 +255,154 @@ func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) ( return signer, watchOnly, carol } -// testRemoteSignerRandomSeed tests that a watch-only wallet can use a remote -// signing wallet to perform any signing or ECDH operations. -func testRemoteSignerRandomSeed(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "random seed", +// prepareInboundRemoteSignerTest prepares a test case using an outbound remote +// signer for the test suite by creating three nodes. +func prepareOutboundRemoteSignerTest(ht *lntest.HarnessTest, + tc remoteSignerTestCase) (*node.HarnessNode, *node.HarnessNode, + *node.HarnessNode) { + + // Signer is our signing node and has the wallet with the full + // master private key. We test that we can create the watch-only + // wallet from the exported accounts but also from a static key + // to make sure the derivation of the account public keys is + // correct in both cases. + password := []byte("itestpassword") + var ( + signerNodePubKey = nodePubKey + watchOnlyAccounts = deriveCustomScopeAccounts(ht.T) + signer *node.HarnessNode + err error + ) + + var commitArgs []string + if tc.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { + commitArgs = lntest.NodeArgsForCommitType( + tc.commitType, + ) + } + + // WatchOnly is the node that has a watch-only wallet and uses + // the Signer node for any operation that requires access to + // private keys. We use the outbound signer type here, meaning + // that the watch-only node expects the signer to make an + // outbound connection to it. + watchOnly := ht.CreateNewNode( + "WatchOnly", append([]string{ + "--remotesigner.enable", + "--remotesigner.allowinboundconnection", + "--remotesigner.timeout=30s", + "--remotesigner.requesttimeout=30s", + }, commitArgs...), + password, + ) + + // As the signer node will make an outbound connection to the + // watch-only node, we must specify the watch-only node's RPC + // connection details in the signer's configuration. + signerArgs := []string{ + "--watchonlynode.enable", + "--watchonlynode.timeout=30s", + "--watchonlynode.requesttimeout=10s", + fmt.Sprintf( + "--watchonlynode.rpchost=localhost:%d", + watchOnly.Cfg.RPCPort, + ), + fmt.Sprintf( + "--watchonlynode.tlscertpath=%s", + watchOnly.Cfg.TLSCertPath, + ), + fmt.Sprintf( + "--watchonlynode.macaroonpath=%s", + watchOnly.Cfg.AdminMacPath, + ), + } + + if !tc.randomSeed { + signer = ht.RestoreNodeWithSeed( + "Signer", signerArgs, password, nil, rootKey, 0, + nil, + ) + } else { + signer = ht.NewNode("Signer", signerArgs) + signerNodePubKey = signer.PubKeyStr + + rpcAccts := signer.RPC.ListAccounts( + &walletrpc.ListAccountsRequest{}, + ) + + watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly( + rpcAccts.Accounts, + ) + require.NoError(ht, err) + } + + // As the watch-only node will not fully start until the signer + // node connects to it, we need to start the watch-only node + // after having started the signer node. + ht.StartWatchOnly(watchOnly, "WatchOnly", password, + &lnrpc.WatchOnly{ + MasterKeyBirthdayTimestamp: 0, + MasterKeyFingerprint: nil, + Accounts: watchOnlyAccounts, + }, + ) + + resp := watchOnly.RPC.GetInfo() + require.Equal(ht, signerNodePubKey, resp.IdentityPubkey) + + if tc.sendCoins { + ht.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly) + ht.AssertWalletAccountBalance( + watchOnly, "default", + btcutil.SatoshiPerBitcoin, 0, + ) + } + + carol := ht.NewNode("carol", commitArgs) + ht.EnsureConnected(watchOnly, carol) + + return signer, watchOnly, carol +} + +func executeRemoteSignerTestCase(ht *lntest.HarnessTest, + tc remoteSignerTestCase) { + + var watchOnly, carol *node.HarnessNode + + if tc.isOutbound { + _, watchOnly, carol = prepareOutboundRemoteSignerTest(ht, tc) + } else { + _, watchOnly, carol = prepareInboundRemoteSignerTest(ht, tc) + } + + tc.fn(ht, watchOnly, carol) +} + +func randomSeedTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ randomSeed: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { // Nothing more to test here. }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +// testRemoteSignerRandomSeed tests that a watch-only wallet can use a remote +// signing wallet to perform any signing or ECDH operations. +func testRemoteSignerRandomSeed(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, randomSeedTestCase(false)) } -func testRemoteSignerAccountImport(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "account import", +// testRemoteSignerRandomSeed tests that a watch-only wallet can use an outbound +// remote signing wallet to perform any signing or ECDH operations. +func testRemoteSignerRandomSeedOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, randomSeedTestCase(true)) +} + +func accountImportTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runWalletImportAccountScenario( tt, walletrpc.AddressType_WITNESS_PUBKEY_HASH, @@ -227,15 +410,22 @@ func testRemoteSignerAccountImport(ht *lntest.HarnessTest) { ) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerAccountImport(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, accountImportTestCase(false)) } -func testRemoteSignerTapscriptImport(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "tapscript import", - sendCoins: true, +func testRemoteSignerAccountImportOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, accountImportTestCase(true)) +} + +func tapscriptImportTestCase(ht *lntest.HarnessTest, + isOutbound bool) remoteSignerTestCase { + + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { testTaprootImportTapscriptFullTree(ht, wo) testTaprootImportTapscriptPartialReveal(ht, wo) @@ -245,54 +435,74 @@ func testRemoteSignerTapscriptImport(ht *lntest.HarnessTest) { testTaprootImportTapscriptFullKeyFundPsbt(ht, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerTapscriptImport(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, tapscriptImportTestCase(ht, false)) } -func testRemoteSignerChannelOpen(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "basic channel open close", - sendCoins: true, +func testRemoteSignerTapscriptImportOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, tapscriptImportTestCase(ht, true)) +} + +func channelOpenTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runBasicChannelCreationAndUpdates(tt, wo, carol) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerChannelOpen(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, channelOpenTestCase(false)) } -func testRemoteSignerChannelFundingInputTypes(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "channel funding input types", - sendCoins: false, +func testRemoteSignerChannelOpenOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, channelOpenTestCase(true)) +} + +func channelFundingInputTypesTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: false, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runChannelFundingInputTypes(tt, carol, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerChannelFundingInputTypes(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, channelFundingInputTypesTestCase(false)) } -func testRemoteSignerAsyncPayments(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "async payments", - sendCoins: true, +func testRemoteSignerChannelFundingInputTypesOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, channelFundingInputTypesTestCase(true)) +} + +func asyncPaymentsTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runAsyncPayments(tt, wo, carol, nil) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerAsyncPayments(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, asyncPaymentsTestCase(false)) } -func testRemoteSignerAsyncPaymentsTaproot(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "async payments taproot", - sendCoins: true, +func testRemoteSignerAsyncPaymentsOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, asyncPaymentsTestCase(true)) +} + +func asyncPaymentsTaprootTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { commitType := lnrpc.CommitmentType_SIMPLE_TAPROOT @@ -302,40 +512,56 @@ func testRemoteSignerAsyncPaymentsTaproot(ht *lntest.HarnessTest) { }, commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerAsyncPaymentsTaproot(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, asyncPaymentsTaprootTestCase(false)) } -func testRemoteSignerSharedKey(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "shared key", +func testRemoteSignerAsyncPaymentsTaprootOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, asyncPaymentsTaprootTestCase(true)) +} + +func sharedKeyTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runDeriveSharedKey(tt, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerSharedKey(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, sharedKeyTestCase(false)) } -func testRemoteSignerBumpFee(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "bumpfee", - sendCoins: true, +func testRemoteSignerSharedKeyOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, sharedKeyTestCase(true)) +} + +func bumpFeeTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runBumpFee(tt, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerBumpFee(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, bumpFeeTestCase(false)) } -func testRemoteSignerPSBT(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "psbt", +func testRemoteSignerBumpFeeOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, bumpFeeTestCase(true)) +} + +func psbtTestCase(ht *lntest.HarnessTest, + isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ randomSeed: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runPsbtChanFundingWithNodes( tt, carol, wo, false, @@ -357,42 +583,57 @@ func testRemoteSignerPSBT(ht *lntest.HarnessTest) { runFundPsbt(ht, wo, carol) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerPSBT(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, psbtTestCase(ht, false)) } -func testRemoteSignerSignOutputRaw(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "sign output raw", - sendCoins: true, +func testRemoteSignerPSBTOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, psbtTestCase(ht, true)) +} + +func signOutputRawTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runSignOutputRaw(tt, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerSignOutputRaw(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, signOutputRawTestCase(false)) } -func testRemoteSignerSignVerifyMsg(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "sign verify msg", - sendCoins: true, +func testRemoteSignerSignOutputRawOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, signOutputRawTestCase(true)) +} + +func signVerifyMsgTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ + sendCoins: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { runSignVerifyMessage(tt, wo) }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerSignVerifyMsg(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, signVerifyMsgTestCase(false)) } -func testRemoteSignerTaproot(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "taproot", +func testRemoteSignerSignVerifyMsgOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, signVerifyMsgTestCase(true)) +} + +func taprootTestCase(isOutbound bool) remoteSignerTestCase { + return remoteSignerTestCase{ sendCoins: true, randomSeed: true, + isOutbound: isOutbound, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { testTaprootSendCoinsKeySpendBip86(tt, wo) testTaprootComputeInputScriptKeySpendBip86(tt, wo) @@ -416,9 +657,14 @@ func testRemoteSignerTaproot(ht *lntest.HarnessTest) { } }, } +} - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) +func testRemoteSignerTaproot(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, taprootTestCase(false)) +} + +func testRemoteSignerTaprootOutbound(ht *lntest.HarnessTest) { + executeRemoteSignerTestCase(ht, taprootTestCase(true)) } // deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd From f25f2e207aebb5a7b6320541754b1458dbd311ab Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Wed, 28 Aug 2024 12:02:58 +0200 Subject: [PATCH 36/39] itest: add testOutboundRSMacaroonEnforcement itest testOutboundRSMacaroonEnforcement tests that a valid macaroon including the `remotesigner` entity is required to connect to a watch-only node that uses an outbound remote signer, while the watch-only node is in the state (WalletState_ALLOW_REMOTE_SIGNER) where it waits for the signer to connect. --- itest/list_on_test.go | 4 ++ itest/lnd_remote_signer_test.go | 112 +++++++++++++++++++++++++++++++- lntest/harness.go | 6 +- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 42471e9c3cb..aff10e2761e 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -519,6 +519,10 @@ var allTestCases = []*lntest.TestCase{ Name: "async payments benchmark", TestFunc: testAsyncPayments, }, + { + Name: "outbound remote signer macaroon enforcement", + TestFunc: testOutboundRSMacaroonEnforcement, + }, { Name: "taproot coop close", TestFunc: testTaprootCoopClose, diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index 09490d7f4b5..97ee9d40f14 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -3,16 +3,20 @@ package itest import ( "fmt" "testing" + "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/stretchr/testify/require" ) @@ -293,7 +297,7 @@ func prepareOutboundRemoteSignerTest(ht *lntest.HarnessTest, "--remotesigner.timeout=30s", "--remotesigner.requesttimeout=30s", }, commitArgs...), - password, + password, true, ) // As the signer node will make an outbound connection to the @@ -667,6 +671,112 @@ func testRemoteSignerTaprootOutbound(ht *lntest.HarnessTest) { executeRemoteSignerTestCase(ht, taprootTestCase(true)) } +// testOutboundRSMacaroonEnforcement tests that a valid macaroon including +// the `remotesigner` entity is required to connect to a watch-only node that +// uses an outbound remote signer, while the watch-only node is in the state +// where it waits for the signer to connect. +func testOutboundRSMacaroonEnforcement(ht *lntest.HarnessTest) { + // Ensure that the watch-only node uses a configuration that requires an + // outbound remote signer during startup. + watchOnlyArgs := []string{ + "--remotesigner.enable", + "--remotesigner.allowinboundconnection", + "--remotesigner.timeout=15s", + "--remotesigner.requesttimeout=15s", + } + + // Create the watch-only node. Note that we require authentication for + // the watch-only node, as we want to test that the macaroon enforcement + // works as expected. + watchOnly := ht.CreateNewNode("WatchOnly", watchOnlyArgs, nil, false) + + startChan := make(chan error) + + // Start the watch-only node in a goroutine as it requires a remote + // signer to connect before it can fully start. + go func() { + startChan <- watchOnly.Start(ht.Context()) + }() + + // Wait and ensure that the watch-only node reaches the state where + // it waits for the remote signer to connect, as this is the state where + // we want to test the macaroon enforcement. + err := wait.NoError(func() error { + if watchOnly.RPC == nil { + return fmt.Errorf("watchOnly RPC is nil") + } + + state, err := watchOnly.RPC.State.GetState( + ht.Context(), &lnrpc.GetStateRequest{}, + ) + if err != nil { + return err + } + + if state.State != lnrpc.WalletState_ALLOW_REMOTE_SIGNER { + return fmt.Errorf("not yet in allow remote signer + " + + "state") + } + + return nil + }, 15*time.Second) + require.NoError(ht, err) + + // Set up a connection to the watch-only node. However, instead of using + // the watch-only node's admin macaroon, we'll use the invoice macaroon. + // The connection should not be allowed using this macaroon because it + // lacks the `remotesigner` entity required when the signer node + // connects to the watch-only node. + connectionCfg := lncfg.ConnectionCfg{ + RPCHost: watchOnly.Cfg.RPCAddr(), + MacaroonPath: watchOnly.Cfg.InvoiceMacPath, + TLSCertPath: watchOnly.Cfg.TLSCertPath, + Timeout: 10 * time.Second, + RequestTimeout: 10 * time.Second, + } + + streamFeeder := rpcwallet.NewStreamFeeder(connectionCfg) + + stream, err := streamFeeder.GetStream(ht.Context()) + require.NoError(ht, err) + + defer func() { + require.NoError(ht, stream.Close()) + }() + + // Since we're using an unauthorized macaroon, we should expect to be + // denied access to the watch-only node. + _, err = stream.Recv() + require.ErrorContains(ht, err, "permission denied") + + // Finally, connect a real signer to the watch-only node so that + // it can start up properly. + signerArgs := []string{ + "--watchonlynode.enable", + "--watchonlynode.timeout=30s", + "--watchonlynode.requesttimeout=10s", + fmt.Sprintf( + "--watchonlynode.rpchost=localhost:%d", + watchOnly.Cfg.RPCPort, + ), + fmt.Sprintf( + "--watchonlynode.tlscertpath=%s", + watchOnly.Cfg.TLSCertPath, + ), + fmt.Sprintf( + "--watchonlynode.macaroonpath=%s", + watchOnly.Cfg.AdminMacPath, // An authorized macaroon. + ), + } + + _ = ht.NewNode("Signer", signerArgs) + + // Finally, wait and ensure that the watch-only node is able to start + // up properly. + err = <-startChan + require.NoError(ht, err, "Shouldn't error on watch-only node startup") +} + // deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd // internal key scope. func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount { diff --git a/lntest/harness.go b/lntest/harness.go index 24eaf0e69c9..9267d17be42 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -769,7 +769,7 @@ func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, func (h *HarnessTest) NewNodeWatchOnly(name string, extraArgs []string, password []byte, watchOnly *lnrpc.WatchOnly) *node.HarnessNode { - hn := h.CreateNewNode(name, extraArgs, password) + hn := h.CreateNewNode(name, extraArgs, password, true) h.StartWatchOnly(hn, name, password, watchOnly) @@ -779,9 +779,9 @@ func (h *HarnessTest) NewNodeWatchOnly(name string, extraArgs []string, // CreateNodeWatchOnly creates a new node and asserts its creation. The function // will only create the node and will not start it. func (h *HarnessTest) CreateNewNode(name string, extraArgs []string, - password []byte) *node.HarnessNode { + password []byte, noAuth bool) *node.HarnessNode { - hn, err := h.manager.newNode(h.T, name, extraArgs, password, true) + hn, err := h.manager.newNode(h.T, name, extraArgs, password, noAuth) require.NoErrorf(h, err, "unable to create new node for %s", name) return hn From f19cd5015c36bb7615cc30a46683b3c14ea68f37 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 17 May 2024 16:39:35 +0200 Subject: [PATCH 37/39] itest: wrap deriveCustomScopeAccounts at 80 chars This commit fixes that word wrapping for the deriveCustomScopeAccounts function docs, and ensures that it wraps at 80 characters or less. --- itest/lnd_remote_signer_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index 97ee9d40f14..8a67350ea53 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -777,8 +777,8 @@ func testOutboundRSMacaroonEnforcement(ht *lntest.HarnessTest) { require.NoError(ht, err, "Shouldn't error on watch-only node startup") } -// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd -// internal key scope. +// deriveCustomScopeAccounts derives the first 255 default accounts of the +// custom lnd internal key scope. func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount { allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts)) allAccounts = append(allAccounts, accounts...) From 8098afb4ce3ff31d12ad85e3edc52ebdffe66b05 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 21 Mar 2025 19:14:33 +0100 Subject: [PATCH 38/39] rpcwallet: allow remote signer to reconnect Allow the remote signer to reconnect to the wallet after disconnecting, as long as the remote signer reconnects within the timeout limit. This is not a complete solution to the problem to allow the watch-only node to stay online when the remote signer is disconnected, but is more fault-tolerant than the current implementation as it allows the remote to be temporarily disconnected. --- lnwallet/rpcwallet/sign_coordinator.go | 114 +++++--- lnwallet/rpcwallet/sign_coordinator_test.go | 282 ++++++++++++++++++-- 2 files changed, 345 insertions(+), 51 deletions(-) diff --git a/lnwallet/rpcwallet/sign_coordinator.go b/lnwallet/rpcwallet/sign_coordinator.go index 48a585dc2b0..c261884176f 100644 --- a/lnwallet/rpcwallet/sign_coordinator.go +++ b/lnwallet/rpcwallet/sign_coordinator.go @@ -12,6 +12,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnutils" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var ( @@ -70,16 +72,20 @@ type SignCoordinator struct { // signer has errored, and we can no longer process responses. receiveErrChan chan error - // doneReceiving is closed when either party terminates and signals to + // disconnected is closed when either party terminates and signals to // any pending requests that we'll no longer process the response for // that request. - doneReceiving chan struct{} + disconnected chan struct{} // quit is closed when lnd is shutting down. quit chan struct{} - // clientConnected is sent over when the remote signer connects. - clientConnected chan struct{} + // clientReady is closed and sent over when the remote signer is + // connected and ready to accept requests (after the initial handshake). + clientReady chan struct{} + + // clientConnected is true if a remote signer is currently connected. + clientConnected bool // requestTimeout is the maximum time we will wait for a response from // the remote signer. @@ -107,11 +113,14 @@ func NewSignCoordinator(requestTimeout time.Duration, s := &SignCoordinator{ responses: respsMap, receiveErrChan: make(chan error, 1), - doneReceiving: make(chan struct{}), - clientConnected: make(chan struct{}), + clientReady: make(chan struct{}), + clientConnected: false, quit: make(chan struct{}), requestTimeout: requestTimeout, connectionTimeout: connectionTimeout, + // Note that the disconnected channel is not initialized here, + // as no code listens to it until the Run method has been called + // and set the field. } // We initialize the atomic nextRequestID to the handshakeRequestID, as @@ -132,25 +141,33 @@ func (s *SignCoordinator) Run(stream StreamServer) error { s.mu.Unlock() return ErrShuttingDown - case <-s.doneReceiving: - s.mu.Unlock() - return ErrNotConnected - default: } + if s.clientConnected { + // If we already have a stream, we error out as we can only have + // one connection at a time. + return ErrMultipleConnections + } + s.wg.Add(1) defer s.wg.Done() - // If we already have a stream, we error out as we can only have one - // connection throughout the lifetime of the SignCoordinator. - if s.stream != nil { - s.mu.Unlock() - return ErrMultipleConnections - } + s.clientConnected = true + defer func() { + s.mu.Lock() + defer s.mu.Unlock() + + // When `Run` returns, we set the clientConnected field to false + // to allow a new remote signer connection to be set up. + s.clientConnected = false + }() s.stream = stream + s.disconnected = make(chan struct{}) + defer close(s.disconnected) + s.mu.Unlock() // The handshake must be completed before we can start sending requests @@ -160,8 +177,18 @@ func (s *SignCoordinator) Run(stream StreamServer) error { return err } - log.Infof("Remote signer connected") - close(s.clientConnected) + log.Infof("Remote signer connected and ready") + + close(s.clientReady) + defer func() { + s.mu.Lock() + defer s.mu.Unlock() + + // We create a new clientReady channel, once this function + // has exited, to ensure that a new remote signer connection can + // be set up. + s.clientReady = make(chan struct{}) + }() // Now let's start the main receiving loop, which will receive all // responses to our requests from the remote signer! @@ -179,9 +206,6 @@ func (s *SignCoordinator) Run(stream StreamServer) error { case <-s.quit: return ErrShuttingDown - - case <-s.doneReceiving: - return ErrNotConnected } } @@ -364,10 +388,6 @@ func (s *SignCoordinator) handshake(stream StreamServer) error { func (s *SignCoordinator) StartReceiving() { defer s.wg.Done() - // Signals to any ongoing requests that the remote signer is no longer - // connected. - defer close(s.doneReceiving) - for { resp, err := s.stream.Recv() if err != nil { @@ -426,8 +446,16 @@ func (s *SignCoordinator) StartReceiving() { // signer does not connect within the configured connection timeout, or if the // passed context is canceled, an error is returned. func (s *SignCoordinator) WaitUntilConnected(ctx context.Context) error { + // As the Run method will redefine the clientReady channel once it + // returns, we need copy the pointer to the current clientReady channel + // to ensure that we're waiting for the correct channel, and to avoid + // a data race. + s.mu.Lock() + currentClientReady := s.clientReady + s.mu.Unlock() + select { - case <-s.clientConnected: + case <-currentClientReady: return nil case <-s.quit: @@ -438,9 +466,6 @@ func (s *SignCoordinator) WaitUntilConnected(ctx context.Context) error { case <-time.After(s.connectionTimeout): return ErrConnectTimeout - - case <-s.doneReceiving: - return ErrNotConnected } } @@ -524,7 +549,7 @@ func (s *SignCoordinator) getResponse(ctx context.Context, return resp, nil - case <-s.doneReceiving: + case <-s.disconnected: log.Debugf("Stopped waiting for remote signer response for "+ "request ID %d as the stream has been closed", requestID) @@ -848,8 +873,30 @@ func processRequest[R comparable](ctx context.Context, s *SignCoordinator, log.Tracef("Request content: %v", formatSignCoordinatorMsg(&req)) + // reprocessOnDisconnect is a helper function that will be used to + // resend the request if the remote signer disconnects, through which + // we will wait for it to reconnect within the configured timeout, and + // then resend the request. + reprocessOnDisconnect := func() (R, error) { + log.Debugf("Remote signer disconnected while waiting for "+ + "response for request ID %d. Retrying request...", + reqID) + + return processRequest[R]( + ctx, s, generateRequest, extractResponse, + ) + } + err = s.stream.Send(&req) if err != nil { + st, isStatusError := status.FromError(err) + if isStatusError && st.Code() == codes.Unavailable { + // If the stream was closed due to the remote signer + // disconnecting, we will retry to process the request + // if the remote signer reconnects. + return reprocessOnDisconnect() + } + return zero, err } @@ -860,7 +907,12 @@ func processRequest[R comparable](ctx context.Context, s *SignCoordinator, // cancelled/timed out. resp, err = s.getResponse(ctx, reqID) - if err != nil { + if errors.Is(err, ErrNotConnected) { + // If the remote signer disconnected while we were waiting for + // the response, we will retry to process the request if the + // remote signer reconnects. + return reprocessOnDisconnect() + } else if err != nil { return zero, err } diff --git a/lnwallet/rpcwallet/sign_coordinator_test.go b/lnwallet/rpcwallet/sign_coordinator_test.go index fcbc79d32f0..6cc19407b1b 100644 --- a/lnwallet/rpcwallet/sign_coordinator_test.go +++ b/lnwallet/rpcwallet/sign_coordinator_test.go @@ -9,7 +9,9 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) // mockSCStream is a mock implementation of the @@ -19,6 +21,10 @@ type mockSCStream struct { // sign coordinator to the remote signer. sendChan chan *walletrpc.SignCoordinatorRequest + // sendErrorChan is used to simulate requests sent over the stream from + // the sign coordinator to the remote signer. + sendErrorChan chan error + // recvChan is used to simulate responses sent over the stream from the // remote signer to the sign coordinator. recvChan chan *walletrpc.SignCoordinatorResponse @@ -32,10 +38,11 @@ type mockSCStream struct { // newMockSCStream creates a new mock stream. func newMockSCStream() *mockSCStream { return &mockSCStream{ - sendChan: make(chan *walletrpc.SignCoordinatorRequest, 1), - recvChan: make(chan *walletrpc.SignCoordinatorResponse, 1), - cancelChan: make(chan struct{}), - ctx: context.Background(), + sendChan: make(chan *walletrpc.SignCoordinatorRequest), + sendErrorChan: make(chan error), + recvChan: make(chan *walletrpc.SignCoordinatorResponse, 1), + cancelChan: make(chan struct{}), + ctx: context.Background(), } } @@ -46,8 +53,8 @@ func (ms *mockSCStream) Send(req *walletrpc.SignCoordinatorRequest) error { case ms.sendChan <- req: return nil - case <-ms.cancelChan: - return ErrStreamCanceled + case err := <-ms.sendErrorChan: + return err } } @@ -94,8 +101,19 @@ func (ms *mockSCStream) sendResponse(resp *walletrpc.SignCoordinatorResponse) { func setupSignCoordinator(t *testing.T) (*SignCoordinator, *mockSCStream, chan error) { - stream := newMockSCStream() coordinator := NewSignCoordinator(2*time.Second, 3*time.Second) + stream, errChan := setupNewStream(t, coordinator) + + return coordinator, stream, errChan +} + +// setupNewStream sets up a new mock stream to simulate a communication with a +// remote signer. It also simulates the handshake between the passed sign +// coordinator and the remote signer. +func setupNewStream(t *testing.T, + coordinator *SignCoordinator) (*mockSCStream, chan error) { + + stream := newMockSCStream() errChan := make(chan error) go func() { @@ -136,7 +154,7 @@ func setupSignCoordinator(t *testing.T) (*SignCoordinator, *mockSCStream, ) } - return coordinator, stream, errChan + return stream, errChan } // getRequest is a helper function to get a request that has been sent from @@ -372,21 +390,43 @@ func TestPingTimeout(t *testing.T) { coordinator, stream, _ := setupSignCoordinator(t) ctx := context.Background() - // Simulate a Ping request that times out. - _, err := coordinator.Ping(ctx, 1*time.Second) - require.Equal(t, context.DeadlineExceeded, err) + var wg sync.WaitGroup + + // Simulate a Ping request that is expected to time out. + wg.Add(1) + + go func() { + defer wg.Done() + + // Note that the timeout is set to 1 second. + success, err := coordinator.Ping(ctx, 1*time.Second) + require.Equal(t, context.DeadlineExceeded, err) + require.False(t, success) + }() + + // Get the request sent over the mock stream. + req1, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req1.GetRequestId()) + require.True(t, req1.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now wait for the request to time out. + wg.Wait() // Verify that the responses map is empty after the timeout. require.Equal(t, coordinator.responses.Len(), 0) // Now let's simulate that the response is sent back after the request // has timed out. - req, err := getRequest(stream) - require.NoError(t, err) - - require.Equal(t, uint64(2), req.GetRequestId()) - require.True(t, req.GetPing()) - stream.sendResponse(&walletrpc.SignCoordinatorResponse{ RefRequestId: 2, SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ @@ -726,7 +766,7 @@ func TestRemoteSignerDisconnects(t *testing.T) { defer wg.Done() success, err := coordinator.Ping(ctx, pingTimeout) - require.Equal(t, ErrNotConnected, err) + require.Equal(t, ErrConnectTimeout, err) require.False(t, success) }() @@ -750,7 +790,7 @@ func TestRemoteSignerDisconnects(t *testing.T) { stream.Cancel() // This should cause the Run function to return the error that the - // stream was canceled with. + // stream was canceled. err = <-runErrChan require.Equal(t, ErrStreamCanceled, err) @@ -760,11 +800,213 @@ func TestRemoteSignerDisconnects(t *testing.T) { // Verify that the coordinator signals that it's done receiving // responses after the stream is canceled, i.e. the StartReceiving // function is no longer running. - <-coordinator.doneReceiving + <-coordinator.disconnected // Ensure that the Ping request goroutine returned before the timeout // was reached, which indicates that the request was canceled because // the remote signer disconnected. + require.Greater(t, time.Since(startTime), pingTimeout) + require.Less(t, time.Since(startTime), pingTimeout+100*time.Millisecond) + + // Verify the responses map is empty after all responses are received + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestRemoteSignerReconnectsDuringResponseWait verifies that the sign +// coordinator correctly handles the scenario where the remote signer +// disconnects while a request is being processed and then reconnects. In this +// case, the sign coordinator should establish a new stream, reprocess the +// request, and ultimately receive a response. +func TestRemoteSignerReconnectsDuringResponseWait(t *testing.T) { + t.Parallel() + + coordinator, stream, runErrChan := setupSignCoordinator(t) + ctx := context.Background() + + pingTimeout := 3 * time.Second + startTime := time.Now() + + var wg sync.WaitGroup + + // Send a Ping request with a long timeout to ensure that the request + // will not time out before the remote signer disconnects. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, pingTimeout) + require.NoError(t, err) + require.True(t, success) + }() + + // Get the request sent over the mock stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Verify that the request has the expected request ID and that it's a + // Ping request. + require.Equal(t, uint64(2), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 1) + _, ok := coordinator.responses.Load(uint64(2)) + require.True(t, ok) + + // Now, lets simulate that the remote signer disconnects by canceling + // the stream, while the sign coordinator is still waiting for the Pong + // response for the request it sent. + stream.Cancel() + + // This should cause the Run function to return the error that the + // stream was canceled. + err = <-runErrChan + require.Equal(t, ErrStreamCanceled, err) + + // Verify that the coordinator signals that it's done receiving + // responses after the stream is canceled, i.e. the StartReceiving + // function is no longer running. + <-coordinator.disconnected + + // Now let's simulate that the remote signer reconnects with a new + // stream. + stream, _ = setupNewStream(t, coordinator) + + // This should lead to that the sign coordinator resends the Ping + // request it's needs a response for over the new stream. + req, err = getRequest(stream) + require.NoError(t, err) + + // Note that the request ID will be 3 for the resent request, as the + // coordinator will no longer wait for the response for the request with + // request ID 2. + require.Equal(t, uint64(3), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 2) + _, ok = coordinator.responses.Load(uint64(3)) + require.True(t, ok) + + // Now let's send the Pong response for the resent Ping request. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 3, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Ensure that the Ping request goroutine has finished. + wg.Wait() + + // Ensure that the Ping request goroutine returned before the timeout + // was reached, which indicates that the request didn't time out as + // the remote signer reconnected in time and sent a response. + require.Less(t, time.Since(startTime), pingTimeout) + + // Verify the responses map is empty after all responses are received + require.Equal(t, coordinator.responses.Len(), 0) +} + +// TestRemoteSignerDisconnectsMidSend verifies that the sign coordinator +// correctly handles the scenario in which the remote signer disconnects while +// the sign coordinator is sending data over the stream (i.e., during the +// execution of the `Send` function) and then reconnects. In such a case, the +// sign coordinator should establish a new stream, reprocess the request, and +// eventually receive a response. +func TestRemoteSignerDisconnectsMidSend(t *testing.T) { + t.Parallel() + + coordinator, stream, runErrChan := setupSignCoordinator(t) + ctx := context.Background() + + pingTimeout := 3 * time.Second + startTime := time.Now() + + var wg sync.WaitGroup + + // Send a Ping request with a long timeout to ensure that the request + // will not time out before the remote signer disconnects. + wg.Add(1) + + go func() { + defer wg.Done() + + success, err := coordinator.Ping(ctx, pingTimeout) + require.NoError(t, err) + require.True(t, success) + }() + + // Just wait slightly, to ensure that the Ping requests starts getting + // processed before we simulate the remote signer disconnecting. + <-time.After(10 * time.Millisecond) + + // We simulate the remote signer disconnecting by canceling the + // stream. + stream.Cancel() + + // This should cause the Run function to return the error that the + // stream was canceled with. + err := <-runErrChan + require.Equal(t, ErrStreamCanceled, err) + + // Verify that the coordinator signals that it's done receiving + // responses after the stream is canceled, i.e. the StartReceiving + // function is no longer running. + <-coordinator.disconnected + + // Now since the sign coordinator is still processing the requests, and + // we never extracted the request sent over the stream, the sign + // coordinator is stuck at the steam.Send function. We simulate this + // function now errors with the codes.Unavailable error, which is what + // the function would error with if the signer was disconnected during + // the send operation in a real scenario. + stream.sendErrorChan <- status.Errorf( + codes.Unavailable, "simulated unavailable error", + ) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, 1, coordinator.responses.Len()) + + // Now let's simulate that the remote signer reconnects with a new + // stream. + stream, _ = setupNewStream(t, coordinator) + + // This should lead to that the sign coordinator resends the Ping + // request it's needs a response for over the new stream. + req, err := getRequest(stream) + require.NoError(t, err) + + // Note that the request ID will be 3 for the resent request, as the + // coordinator will no longer wait for the response for the request with + // request ID 2. + require.Equal(t, uint64(3), req.GetRequestId()) + require.True(t, req.GetPing()) + + // Verify that the coordinator has correctly set up a single response + // channel for the Ping request with the specific request ID. + require.Equal(t, coordinator.responses.Len(), 2) + _, ok := coordinator.responses.Load(uint64(3)) + require.True(t, ok) + + // Now let's send the Pong response for the resent Ping request. + stream.sendResponse(&walletrpc.SignCoordinatorResponse{ + RefRequestId: 3, + SignResponseType: &walletrpc.SignCoordinatorResponse_Pong{ + Pong: true, + }, + }) + + // Ensure that the Ping request goroutine has finished. + wg.Wait() + + // Ensure that the Ping request goroutine returned before the timeout + // was reached, which indicates that the request didn't time out as + // the remote signer reconnected in time and sent a response. require.Less(t, time.Since(startTime), pingTimeout) // Verify the responses map is empty after all responses are received From 2d1981d09e6feebd111ea4dbaf32c9753ce2eaf4 Mon Sep 17 00:00:00 2001 From: Viktor Torstensson Date: Fri, 6 Feb 2026 01:01:19 +0100 Subject: [PATCH 39/39] multi: allow 0 `startuptimeout` value --- lncfg/remotesigner.go | 2 +- lnwallet/rpcwallet/sign_coordinator.go | 9 ++- lnwallet/rpcwallet/sign_coordinator_test.go | 76 +++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 2566c1501f6..0a85da83968 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -104,7 +104,7 @@ func (r *RemoteSigner) Validate() error { // //nolint:ll type inboundWatchOnlyCfg struct { - StartupTimeout time.Duration `long:"startuptimeout" description:"The time the watch-only node will wait for the remote signer to connect during startup. If the timeout expires before the remote signer connects, the watch-only node will shut down. Valid time units are {s, m, h}."` + StartupTimeout time.Duration `long:"startuptimeout" description:"The time the watch-only node will wait for the remote signer to connect during startup. If the timeout expires before the remote signer connects, the watch-only node will shut down. If set to 0, no timeout will not expire. Valid time units are {s, m, h}."` } // WatchOnlyNode holds the configuration options for how to connect to a watch diff --git a/lnwallet/rpcwallet/sign_coordinator.go b/lnwallet/rpcwallet/sign_coordinator.go index c261884176f..4ce50202c50 100644 --- a/lnwallet/rpcwallet/sign_coordinator.go +++ b/lnwallet/rpcwallet/sign_coordinator.go @@ -454,6 +454,13 @@ func (s *SignCoordinator) WaitUntilConnected(ctx context.Context) error { currentClientReady := s.clientReady s.mu.Unlock() + var timeout <-chan time.Time + if s.connectionTimeout > 0 { + timer := time.NewTimer(s.connectionTimeout) + defer timer.Stop() + timeout = timer.C + } + select { case <-currentClientReady: return nil @@ -464,7 +471,7 @@ func (s *SignCoordinator) WaitUntilConnected(ctx context.Context) error { case <-ctx.Done(): return ctx.Err() - case <-time.After(s.connectionTimeout): + case <-timeout: return ErrConnectTimeout } } diff --git a/lnwallet/rpcwallet/sign_coordinator_test.go b/lnwallet/rpcwallet/sign_coordinator_test.go index 6cc19407b1b..71e4eb851ff 100644 --- a/lnwallet/rpcwallet/sign_coordinator_test.go +++ b/lnwallet/rpcwallet/sign_coordinator_test.go @@ -812,6 +812,82 @@ func TestRemoteSignerDisconnects(t *testing.T) { require.Equal(t, coordinator.responses.Len(), 0) } +// TestWaitUntilConnectedNoTimeout verifies that a zero connection timeout +// disables the internal connect timeout entirely (i.e. no timer is armed), +// so WaitUntilConnected can only return when the signer connects, the +// coordinator is shutting down, or the caller's context is canceled. This +// ensures we're not swapping in some other timeout value when set to 0. +func TestWaitUntilConnectedNoTimeout(t *testing.T) { + t.Parallel() + + coordinator := NewSignCoordinator(2*time.Second, 0) + stream := newMockSCStream() + + runErrChan := make(chan error, 1) + go func() { + err := coordinator.Run(stream) + if err != nil { + runErrChan <- err + } + }() + + ctx, cancel := context.WithTimeout( + context.Background(), 500*time.Millisecond, + ) + defer cancel() + + waitErrChan := make(chan error, 1) + go func() { + waitErrChan <- coordinator.WaitUntilConnected(ctx) + }() + + // With connectionTimeout == 0, there is no internal timeout path, so + // WaitUntilConnected must stay blocked until the handshake completes or + // the ctx is canceled. + select { + case err := <-waitErrChan: + t.Fatalf("WaitUntilConnected returned early: %v", err) + case <-time.After(50 * time.Millisecond): + } + + signReg := &walletrpc.SignerRegistration{ + RegistrationChallenge: "registrationChallenge", + RegistrationInfo: "outboundSigner", + } + + regType := &walletrpc.SignCoordinatorResponse_SignerRegistration{ + SignerRegistration: signReg, + } + + registrationMsg := &walletrpc.SignCoordinatorResponse{ + RefRequestId: 1, + SignResponseType: regType, + } + + stream.sendResponse(registrationMsg) + + // Drain the registration complete message to avoid blocking Send in + // the handshake. + select { + case req := <-stream.sendChan: + require.Equal(t, handshakeRequestID, req.GetRequestId()) + case <-time.After(2 * time.Second): + t.Fatalf("registration complete was not sent") + } + + require.NoError(t, <-waitErrChan) + + stream.Cancel() + coordinator.Stop() + + select { + case err := <-runErrChan: + require.Equal(t, ErrShuttingDown, err) + case <-time.After(2 * time.Second): + t.Fatalf("Run did not exit after shutdown") + } +} + // TestRemoteSignerReconnectsDuringResponseWait verifies that the sign // coordinator correctly handles the scenario where the remote signer // disconnects while a request is being processed and then reconnects. In this