diff --git a/.gitignore b/.gitignore index 6be439ed7e..0bb1288196 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ _testmain.go /lnd /lnd-debug +/lndsigner +/lndsigner-debug /lncli /lncli-debug /lnd-itest diff --git a/Makefile b/Makefile index ccf95cb902..8bb10a9692 100644 --- a/Makefile +++ b/Makefile @@ -107,11 +107,12 @@ $(GOIMPORTS_BIN): # INSTALLATION # ============ -#? build: Build lnd and lncli binaries, place them in project directory +#? build: Build lnd, lncli and lndsigner binaries, place them in project directory build: - @$(call print, "Building debug lnd and lncli.") + @$(call print, "Building debug lnd/lndsigner and lncli.") $(GOBUILD) -tags="$(DEV_TAGS)" -o lnd-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lnd $(GOBUILD) -tags="$(DEV_TAGS)" -o lncli-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lncli + $(GOBUILD) -tags="$(LND_SIGNER_TAGS)" -o lndsigner-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lndsigner #? build-itest: Build integration test binaries, place them in itest directory build-itest: @@ -131,18 +132,19 @@ build-itest-race: @$(call print, "Building itest binary for ${backend} backend.") CGO_ENABLED=0 $(GOTEST) -v ./itest -tags="$(DEV_TAGS) $(RPC_TAGS) integration $(backend)" -c -o itest/itest.test$(EXEC_SUFFIX) -#? install-binaries: Build and install lnd and lncli binaries, place them in $GOPATH/bin +#? install-binaries: Build and install lnd, lncli and lndsigner binaries, place them in $GOPATH/bin install-binaries: - @$(call print, "Installing lnd and lncli.") + @$(call print, "Installing lnd/lndsigner and lncli.") $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lnd $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lncli + $(GOINSTALL) -tags="${LND_SIGNER_TAGS}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lndsigner #? manpages: generate and install man pages manpages: @$(call print, "Generating man pages lncli.1 and lnd.1.") ./scripts/gen_man_pages.sh $(DESTDIR) $(PREFIX) -#? install: Build and install lnd and lncli binaries and place them in $GOPATH/bin. +#? install: Build and install lnd, lncli and lndsigner binaries and place them in $GOPATH/bin. install: install-binaries #? install-all: Performs all the same tasks as the install command along with generating and @@ -150,19 +152,20 @@ install: install-binaries # environment where a user has root access and so has write access to the man page directory. install-all: install manpages -#? release-install: Build and install lnd and lncli release binaries, place them in $GOPATH/bin +#? release-install: Build and install lnd, lncli and lndsigner binaries release binaries, place them in $GOPATH/bin release-install: - @$(call print, "Installing release lnd and lncli.") + @$(call print, "Installing release lnd/lndsigner and lncli.") env CGO_ENABLED=0 $(GOINSTALL) -v -trimpath -ldflags="$(RELEASE_LDFLAGS)" -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lnd env CGO_ENABLED=0 $(GOINSTALL) -v -trimpath -ldflags="$(RELEASE_LDFLAGS)" -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lncli + env CGO_ENABLED=0 $(GOINSTALL) -v -trimpath -ldflags="$(RELEASE_LDFLAGS)" -tags="$(LND_SIGNER_TAGS)" $(PKG)/cmd/lndsigner #? release: Build the full set of reproducible release binaries for all supported platforms # Make sure the generated mobile RPC stubs don't influence our vendor package # by removing them first in the clean-mobile target. release: clean-mobile - @$(call print, "Releasing lnd and lncli binaries.") + @$(call print, "Releasing lnd/lndsigner and lncli binaries.") $(VERSION_CHECK) - ./scripts/release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_TAGS)" "$(RELEASE_LDFLAGS)" "$(GO_VERSION)" + ./scripts/release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_TAGS)" "$(LND_SIGNER_TAGS)" "$(RELEASE_LDFLAGS)" "$(GO_VERSION)" #? docker-release: Same as release but within a docker container to support reproducible builds on BSD/MacOS platforms docker-release: @@ -427,7 +430,7 @@ mobile: ios android #? clean: Remove all generated files clean: @$(call print, "Cleaning source.$(NC)") - $(RM) ./lnd-debug ./lncli-debug + $(RM) ./lnd-debug ./lndsigner-debug ./lncli-debug $(RM) ./lnd-itest ./lncli-itest $(RM) -r ./vendor .vendor-new diff --git a/cmd/lndsigner/main.go b/cmd/lndsigner/main.go new file mode 100644 index 0000000000..76e7f46bc9 --- /dev/null +++ b/cmd/lndsigner/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/jessevdk/go-flags" + "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/signal" +) + +func main() { + // Hook interceptor for os signals. + shutdownInterceptor, err := signal.Intercept() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + // Load the signer configuration, and parse any command line options. + // This function will also set up logging properly. + loadedConfig, err := lnd.LoadSignerConfig(shutdownInterceptor) + if err != nil { + var flagsErr *flags.Error + if errors.As(err, &flagsErr) && flagsErr.Type == flags.ErrHelp { + // Help was requested, exit normally. + os.Exit(0) + } + + // Print error if not due to help request. + err = fmt.Errorf("failed to load config: %w", err) + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + implCfg := loadedConfig.ImplementationConfig(shutdownInterceptor) + + // Call the "real" main in a nested manner so the defers will properly + // be executed in the case of a graceful shutdown. + if err = lnd.Main( + loadedConfig, lnd.ListenerCfg{}, implCfg, shutdownInterceptor, + ); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/config.go b/config.go index 04a4917658..b46d13303d 100644 --- a/config.go +++ b/config.go @@ -244,6 +244,7 @@ const ( bitcoindBackendName = "bitcoind" btcdBackendName = "btcd" neutrinoBackendName = "neutrino" + noChainBackendName = "nochainbackend" ) var ( @@ -259,6 +260,12 @@ var ( // file. DefaultConfigFile = filepath.Join(DefaultLndDir, lncfg.DefaultConfigFilename) + // DefaultSignerConfigFile is the default full path of lndsigner's + // configuration file. + DefaultSignerConfigFile = filepath.Join( + DefaultLndDir, lncfg.DefaultSignerConfigFilename, + ) + defaultDataDir = filepath.Join(DefaultLndDir, defaultDataDirname) defaultLogDir = filepath.Join(DefaultLndDir, defaultLogDirname) @@ -285,6 +292,19 @@ var ( defaultPrunedNodeMaxPeers = 4 ) +// GeneralizedConfig is an interface that defines the functions a config struct +// must support to be passable in the generalizedConfigLoader function. +type GeneralizedConfig interface { + // GetShowVersion returns the current value for the ShowVersion field + GetShowVersion() bool + + // GetLndDir returns the current value for the LndDir field + GetLndDir() string + + // GetConfigFile returns the current value for the ConfigFile field + GetConfigFile() string +} + // Config defines the configuration options for lnd. // // See LoadConfig for further details regarding the configuration @@ -518,6 +538,21 @@ type Config struct { HTTPHeaderTimeout time.Duration `long:"http-header-timeout" description:"The maximum duration that the server will wait before timing out reading the headers of an HTTP request."` } +// GetShowVersion returns the current value for the ShowVersion field. +func (c Config) GetShowVersion() bool { + return c.ShowVersion +} + +// GetLndDir returns the current value for the LndDir field. +func (c Config) GetLndDir() string { + return c.LndDir +} + +// GetConfigFile returns the current value for the ConfigFile field. +func (c Config) GetConfigFile() string { + return c.ConfigFile +} + // GRPCConfig holds the configuration options for the gRPC server. // See https://github.com/grpc/grpc-go/blob/v1.41.0/keepalive/keepalive.go#L50 // for more details. Any value of 0 means we use the gRPC internal default @@ -721,7 +756,10 @@ func DefaultConfig() Config { CoinSelectionStrategy: defaultCoinSelectionStrategy, KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts, RemoteSigner: &lncfg.RemoteSigner{ - Timeout: lncfg.DefaultRemoteSignerRPCTimeout, + SignerRole: lncfg.DefaultInboundWatchOnlyRole, + Timeout: lncfg.DefaultRemoteSignerRPCTimeout, + RequestTimeout: lncfg.DefaultRequestTimeout, + StartupTimeout: lncfg.DefaultStartupTimeout, }, Sweeper: lncfg.DefaultSweeperConfig(), Htlcswitch: &lncfg.Htlcswitch{ @@ -738,6 +776,93 @@ func DefaultConfig() Config { } } +// A compile time assertion to ensure Config meets the GeneralizedConfig +// interface. +var _ GeneralizedConfig = (*Config)(nil) + +// SignerConfig defines the configuration options for lndsigner. +// +// Note! Any new fields added to this struct MUST also be applied to the merged +// config in the `mergeConf` function in LoadSignerConfig. Else the added fields +// will have no effect. +// +// See LoadSignerConfig for further details regarding the configuration +// loading+parsing process. +// +//nolint:lll +type SignerConfig struct { + ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` + + LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc. This option overwrites all other directory options."` + ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` + DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"` + + LogDir string `long:"logdir" description:"Directory to log output."` + LogConfig *build.LogConfig `group:"logging" namespace:"logging"` + + TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"` + TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"` + + RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"` + RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"` + RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"` + + DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify ,=,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` + + Network string `long:"network" description:"The network the UI and all its components run on" choice:"regtest" choice:"testnet" choice:"testnet3" choice:"mainnet" choice:"simnet" choice:"signet"` + + Pprof *lncfg.Pprof `group:"Pprof" namespace:"pprof"` + + RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"` + + SignerRole string `long:"signerrole" description:"Sets the type of remote signer the node will act as a remote signer. Can be set to either 'signer-outbound' or 'signer-inbound' (default). 'signer-outbound' means that the lndsigner instance will make an outbound connection to a watch-only node with the 'watchonly-outbound' signerrole set. 'signer-inbound' means that the lndsigner instance will allow a watch-only node to connect which has the 'watchonly-inbound' signer role set" choice:"signer-outbound" choice:"signer-inbound"` + WatchOnlyRPCHost string `long:"watchonlyrpchost" description:"Sets the watch-only node's RPC host:port. This option should only be set if the signerrole is set to 'signer-outbound'"` + WatchOnlyMacaroonPath string `long:"watchonlymacaroonpath" description:"This param should be set to the watch-only node's macaroon path. This option should only be set if the signerrole is set to 'signer-outbound'"` + WatchOnlyTLSCertPath string `long:"watchonlytlscertpath" description:"This param should be set to the watch-only node's TLS certificate path. This option should only be set if the signerrole is set to 'signer-outbound'"` + Timeout time.Duration `long:"timeout" description:"The timeout when setting up a connect with the watch-only node. Valid time units are {s, m, h}"` + RequestTimeout time.Duration `long:"requesttimeout" description:"The time we will wait when making requests to the watch-only node. Valid time units are {s, m, h}."` +} + +// GetShowVersion returns the current value for the ShowVersion field. +func (s SignerConfig) GetShowVersion() bool { + return s.ShowVersion +} + +// GetLndDir returns the current value for the LndDir field. +func (s SignerConfig) GetLndDir() string { + return s.LndDir +} + +// GetConfigFile returns the current value for the ConfigFile field. +func (s SignerConfig) GetConfigFile() string { + return s.ConfigFile +} + +// DefaultSignerConfig returns all default values for the SignerConfig struct. +func DefaultSignerConfig() SignerConfig { + return SignerConfig{ + LndDir: DefaultLndDir, + ConfigFile: DefaultSignerConfigFile, + DataDir: defaultDataDir, + DebugLevel: defaultLogLevel, + TLSCertPath: defaultTLSCertPath, + TLSKeyPath: defaultTLSKeyPath, + LogDir: defaultLogDir, + LogConfig: build.DefaultLogConfig(), + RPCMiddleware: lncfg.DefaultRPCMiddleware(), + + Network: chainreg.BitcoinMainNetParams.Params.Name, + + SignerRole: lncfg.OutboundSignerRole, + Timeout: lncfg.DefaultRemoteSignerRPCTimeout, + RequestTimeout: lncfg.DefaultRequestTimeout, + } +} + +// A compile time assertion to ensure SignerConfig meets the GeneralizedConfig +// interface. +var _ GeneralizedConfig = (*SignerConfig)(nil) + // LoadConfig initializes and parses the config using a config file and command // line options. // @@ -747,9 +872,127 @@ func DefaultConfig() Config { // 3. Load configuration file overwriting defaults with any specified options // 4. Parse CLI options and overwrite/add any specified options func LoadConfig(interceptor signal.Interceptor) (*Config, error) { + // As we're passing the default Config type to generalizedConfigLoader, + // no further modification is required to the loaded config during + // merging. + mergeConf := func(cfg *Config) (*Config, error) { + return cfg, nil + } + + preCfg := DefaultConfig() + + // Load and validate the config. + cfg, err := generalizedConfigLoader( + interceptor, preCfg, mergeConf, DefaultConfigFile, + lncfg.DefaultConfigFilename, + ) + + return cfg, err +} + +// LoadSignerConfig initializes and parses the remote signer config using a +// config file and command line options. +// +// The configuration proceeds as follows:s +// 1. Start with a default signer config with sane settings +// 2. Overwrite with signer specific values +// 3. Pre-parse the command line to check for an alternative config file +// 4. Parse CLI options and overwrite/add any specified options +// 5. Load a main lnd config type, and merge the signer configuration file with +// the main config file. +func LoadSignerConfig(interceptor signal.Interceptor) (*Config, error) { + // We'll merge the configs by copying the values from the SignerConfig + // to the corresponding field in the main Config type. + mergeConf := func(signerCfg *SignerConfig) (*Config, error) { + cfg := DefaultConfig() + + // First we'll overwrite the default config with values that + // should be set when a node acts as a remote signer. + cfg.NoNetBootstrap = true + cfg.DisableListen = true + cfg.Bitcoin.Node = noChainBackendName + cfg.ConfigFile = DefaultSignerConfigFile + + // Next, we'll copy over the values that are set in the signer + // config, to merge them with the main config. + cfg.LndDir = signerCfg.LndDir + cfg.ConfigFile = signerCfg.ConfigFile + cfg.DataDir = signerCfg.DataDir + + cfg.DebugLevel = signerCfg.DebugLevel + cfg.TLSCertPath = signerCfg.TLSCertPath + cfg.TLSKeyPath = signerCfg.TLSKeyPath + cfg.LogDir = signerCfg.LogDir + cfg.LogConfig = signerCfg.LogConfig + cfg.RPCMiddleware = signerCfg.RPCMiddleware + + cfg.RawRPCListeners = signerCfg.RawRPCListeners + cfg.RawRESTListeners = signerCfg.RawRESTListeners + cfg.RawListeners = signerCfg.RawListeners + + cfg.RemoteSigner.SignerRole = signerCfg.SignerRole + cfg.RemoteSigner.Timeout = signerCfg.Timeout + cfg.RemoteSigner.RequestTimeout = signerCfg.RequestTimeout + + cfg.RemoteSigner.RPCHost = signerCfg.WatchOnlyRPCHost + cfg.RemoteSigner.MacaroonPath = signerCfg.WatchOnlyMacaroonPath + cfg.RemoteSigner.TLSCertPath = signerCfg.WatchOnlyTLSCertPath + + cfg.Pprof = signerCfg.Pprof + + switch signerCfg.Network { + case (chainreg.BitcoinMainNetParams.Params.Name): + cfg.Bitcoin.MainNet = true + case (chainreg.BitcoinTestNetParams.Params.Name), "testnet": + cfg.Bitcoin.TestNet3 = true + case (chainreg.BitcoinRegTestNetParams.Params.Name): + cfg.Bitcoin.RegTest = true + case (chainreg.BitcoinSimNetParams.Params.Name): + cfg.Bitcoin.SimNet = true + case (chainreg.BitcoinSigNetParams.Params.Name): + cfg.Bitcoin.SigNet = true + default: + return nil, fmt.Errorf( + "unknown network %s", signerCfg.Network, + ) + } + + return &cfg, nil + } + + // We use the SignerConfig, as the lndsigner should have more limited + // config options for an easier UX. + preCfg := DefaultSignerConfig() + + // Load and validate the config. + cfg, err := generalizedConfigLoader( + interceptor, preCfg, mergeConf, DefaultSignerConfigFile, + lncfg.DefaultSignerConfigFilename, + ) + if err != nil { + return nil, err + } + + // TODO(viktor): Remove this once RPCMiddleware interception is + // supported for outbound remote signers. + if cfg.RemoteSigner.SignerRole == lncfg.OutboundSignerRole && + cfg.RPCMiddleware.Enable { + + return nil, errors.New("RPCMiddleware interception is " + + "currently not supported when using an outbound " + + "remote signer") + } + + return cfg, err +} + +func generalizedConfigLoader[R GeneralizedConfig]( + interceptor signal.Interceptor, preCfg R, + mergeConfig func(cfg *R) (*Config, error), defaultConfigPath, + defaultConfigFileName string) (*Config, error) { + // Pre-parse the command line options to pick up an alternative config // file. - preCfg := DefaultConfig() if _, err := flags.Parse(&preCfg); err != nil { return nil, err } @@ -758,7 +1001,7 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { appName := filepath.Base(os.Args[0]) appName = strings.TrimSuffix(appName, filepath.Ext(appName)) usageMessage := fmt.Sprintf("Use %s -h to show usage", appName) - if preCfg.ShowVersion { + if preCfg.GetShowVersion() { fmt.Println(appName, "version", build.Version(), "commit="+build.Commit) os.Exit(0) @@ -768,21 +1011,21 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { // use the default config file path. However, if the user has modified // their lnddir, then we should assume they intend to use the config // file within it. - configFileDir := CleanAndExpandPath(preCfg.LndDir) - configFilePath := CleanAndExpandPath(preCfg.ConfigFile) + configFileDir := CleanAndExpandPath(preCfg.GetLndDir()) + configFilePath := CleanAndExpandPath(preCfg.GetConfigFile()) switch { // User specified --lnddir but no --configfile. Update the config file // path to the lnd config directory, but don't require it to exist. case configFileDir != DefaultLndDir && - configFilePath == DefaultConfigFile: + configFilePath == defaultConfigPath: configFilePath = filepath.Join( - configFileDir, lncfg.DefaultConfigFilename, + configFileDir, defaultConfigFileName, ) // User did specify an explicit --configfile, so we check that it does // exist under that path to avoid surprises. - case configFilePath != DefaultConfigFile: + case configFilePath != defaultConfigPath: if !lnrpc.FileExists(configFilePath) { return nil, fmt.Errorf("specified config file does "+ "not exist in %s", configFilePath) @@ -791,8 +1034,7 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { // Next, load any additional configuration options from the file. var configFileError error - cfg := preCfg - fileParser := flags.NewParser(&cfg, flags.Default) + fileParser := flags.NewParser(&preCfg, flags.Default) err := flags.NewIniParser(fileParser).ParseFile(configFilePath) if err != nil { // If it's a parsing related error, then we'll return @@ -809,14 +1051,33 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { // Finally, parse the remaining command line options again to ensure // they take precedence. - flagParser := flags.NewParser(&cfg, flags.Default) + flagParser := flags.NewParser(&preCfg, flags.Default) if _, err := flagParser.Parse(); err != nil { return nil, err } + // Merge the loaded config into the main Config type. + cfg, err := mergeConfig(&preCfg) + if err != nil { + return nil, err + } + + // The flag parser above is only aware of the flags in the preCfg + // definition, and not necessarily those in the Config struct. To handle + // this, we create a new flag parser for the Config struct, as it is + // passed to the ValidateConfig function. + // Since some flags in preCfg may not exist in Config, we use + // flags.IgnoreUnknown here to avoid errors for those flags. However, + // unknown flags are not allowed for the preCfg itself, as the earlier + // flag parser will error if such flags are present. + mergedFlagParser := flags.NewParser(cfg, flags.IgnoreUnknown) + if _, err := mergedFlagParser.Parse(); err != nil { + return nil, err + } + // Make sure everything we just loaded makes sense. cleanCfg, err := ValidateConfig( - cfg, interceptor, fileParser, flagParser, + *cfg, interceptor, fileParser, mergedFlagParser, ) var usageErr *lncfg.UsageError if errors.As(err, &usageErr) { @@ -1298,7 +1559,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, case neutrinoBackendName: // No need to get RPC parameters. - case "nochainbackend": + case noChainBackendName: // Nothing to configure, we're running without any chain // backend whatsoever (pure signing mode). diff --git a/config_builder.go b/config_builder.go index 7cc1a112d2..4ce7be8ecf 100644 --- a/config_builder.go +++ b/config_builder.go @@ -843,28 +843,60 @@ 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 _, fn := range cleanUpTasks { + if fn == nil { + continue + } + + fn() + } + } + ) + 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 } + remoteSignerBuilder := rpcwallet.NewRemoteSignerBuilder( + d.DefaultWalletImpl.cfg.RemoteSigner, + ) + + // Create the remote signer instance. The remote signer instance type + // will depend on the configuration passed to the builders contructor. + remoteSigner, rsCleanUp, err := remoteSignerBuilder.Build() + 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, rsCleanUp) + baseKeyRing := keychain.NewBtcWalletKeyRing( walletController.InternalWallet(), walletConfig.CoinType, ) rpcKeyRing, err := rpcwallet.NewRPCKeyRing( baseKeyRing, walletController, - d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams, + remoteSigner, 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 @@ -883,15 +915,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/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md index 12049b40c2..f821377397 100644 --- a/docs/release-notes/release-notes-0.19.0.md +++ b/docs/release-notes/release-notes-0.19.0.md @@ -75,6 +75,10 @@ * [The `walletrpc.FundPsbt` method now has a new option to specify the maximum fee to output amounts ratio.](https://github.com/lightningnetwork/lnd/pull/8600) +* [SignCoordinatorStreams](https://github.com/lightningnetwork/lnd/pull/8754) + allows a remote signer to connect to the lnd node, if the + `remotesigner.signerrole` cfg value has been set to `watchonly-outbound`. + ## lncli Additions * [A pre-generated macaroon root key can now be specified in `lncli create` and @@ -101,6 +105,11 @@ * LND updates channel.backup file at shutdown time. +* [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. + ## RPC Updates * Some RPCs that previously just returned an empty response message now at least diff --git a/docs/remote-signing.md b/docs/remote-signing.md index 0f06463b3f..d808c5d4ef 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,21 +39,45 @@ 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. +## Lndsigner -### The "signer" node +Starting with `lnd` `v0.19.0-beta`, every `lnd` release includes an additional +binary called `lndsigner`. This is a stripped-down version of `lnd` designed +specifically for use as a "remote signer" in remote signing setups. It exposes +only the features needed for remote signing and includes a streamlined +configuration file to make setup easier for users. + +## Example setups + +In the examples below, we demonstrate how to configure the "signer" node and the +"watch-only" node, when 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 single RPC based connection (that can be firewalled off specifically) can be opened to this node from the host on which the node "watch-only" is running. -Recommended entries in `lnd.conf`: +Recommended entries in `lnd.conf` if using an `lnd` instance as the remote +signer: ```text +# Indicates that the node will function as an inbound remote signer +remotesigner.signerrole=signer-inbound + # We apply some basic "hardening" parameters to make sure no connections to the # internet are opened. @@ -82,6 +106,17 @@ bitcoin.mainnet=true bitcoin.node=nochainbackend ``` +If you instead use the `lndsigner` binary as the remote signer, it is +recommended to include the following entries in `lndsigner.conf`: + +```text +# Indicates that lndsigner will function as an inbound remote signer +signerrole=signer-inbound + +# Specifies the mainnet network (mainnet is the default value if not set). +network=mainnet +``` + After successfully starting up "signer", the following command can be run to export the `xpub`s of the wallet: @@ -104,7 +139,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 +153,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 node uses an inbound remote signer. +# However, since the default signerrole is "watchonly-inbound," this isn't +# required. +remotesigner.signerrole=watchonly-inbound ``` After starting "watch-only", the wallet can be created in watch-only mode by @@ -136,7 +175,188 @@ 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 if using an `lnd` instance as the remote signer: + +```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. +remotesigner.signerrole=signer-outbound + +# The watch-only node's RPC host. +remotesigner.rpchost=zane.example.internal:10019 + +# A macaroon and TLS certificate for the watch-only node. +remotesigner.macaroonpath=/home/signer/example/watch-only.custom.macaroon +remotesigner.tlscertpath=/home/signer/example/watch-only.tls.cert +``` + +If you instead use the `lndsigner` binary as the remote signer, it is +recommended to include the following entries in `lndsigner.conf`: + +```text +# Indicates that lndsigner will function as an outbound remote signer. If this +# config option isn't set, this value defaults to "signer-outbound" as well. +signerrole=signer-outbound + +# Specifies the mainnet network (mainnet is the default value if not set). +network=mainnet + +# The watch-only node's RPC host. +watchonlyrpchost=zane.example.internal:10019 + +# A macaroon and TLS certificate for the watch-only node. +watchonlymacaroonpath=/home/signer/example/watch-only.custom.macaroon +watchonlytlscertpath=/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 an outbound remote signer is being used. +remotesigner.signerrole=watchonly-outbound +``` + +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 `remotesigner.tlscertpath` +configuration field in Step 1. ## Migrating an existing setup to remote signing @@ -146,9 +366,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. diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 68c923c3bb..c5f9e1da56 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -536,7 +536,15 @@ var allTestCases = []*lntest.TestCase{ }, { Name: "remote signer", - TestFunc: testRemoteSigner, + TestFunc: testInboundRemoteSigner, + }, + { + Name: "outbound remote signer", + TestFunc: testOutboundRemoteSigner, + }, + { + Name: "outbound remote signer macaroon enforcement", + TestFunc: testOutboundRSMacaroonEnforcement, }, { Name: "taproot coop close", diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index e18e5cb039..762ff807fb 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -3,6 +3,7 @@ package itest import ( "fmt" "testing" + "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" @@ -13,6 +14,8 @@ import ( "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" ) @@ -53,19 +56,17 @@ var ( }} ) -// testRemoteSigner tests that a watch-only wallet can use a remote signing -// wallet to perform any signing or ECDH operations. -func testRemoteSigner(ht *lntest.HarnessTest) { - type testCase struct { - name string - randomSeed bool - sendCoins bool - commitType lnrpc.CommitmentType - fn func(tt *lntest.HarnessTest, - wo, carol *node.HarnessNode) - } +type remoteSignerTestCase struct { + name string + randomSeed bool + sendCoins bool + commitType lnrpc.CommitmentType + fn func(tt *lntest.HarnessTest, + wo, carol *node.HarnessNode) +} - subTests := []testCase{{ +func getRemoteSignerTestCases(ht *lntest.HarnessTest) []remoteSignerTestCase { + return []remoteSignerTestCase{{ name: "random seed", randomSeed: true, fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { @@ -176,9 +177,15 @@ func testRemoteSigner(ht *lntest.HarnessTest) { } }, }} +} +// testInboundRemoteSigner tests that a watch-only wallet can use a remote +// signing wallet to perform any signing or ECDH operations. The test +// specifically uses an inbound remote signer, meaning that the watch-only node +// will make an outbound connection to the remote signer. +func testInboundRemoteSigner(ht *lntest.HarnessTest) { prepareTest := func(st *lntest.HarnessTest, - subTest testCase) (*node.HarnessNode, + subTest remoteSignerTestCase) (*node.HarnessNode, *node.HarnessNode, *node.HarnessNode) { // Signer is our signing node and has the wallet with the full @@ -221,9 +228,10 @@ func testRemoteSigner(ht *lntest.HarnessTest) { // 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 := st.NewNodeRemoteSigner( + watchOnly := st.NewNodeWatchOnly( "WatchOnly", append([]string{ "--remotesigner.enable", + "--remotesigner.signerrole=watchonly-inbound", fmt.Sprintf( "--remotesigner.rpchost=localhost:%d", signer.Cfg.RPCPort, @@ -261,7 +269,7 @@ func testRemoteSigner(ht *lntest.HarnessTest) { return signer, watchOnly, carol } - for _, testCase := range subTests { + for _, testCase := range getRemoteSignerTestCases(ht) { subTest := testCase success := ht.Run(subTest.name, func(tt *testing.T) { @@ -278,8 +286,231 @@ func testRemoteSigner(ht *lntest.HarnessTest) { } } -// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd -// internal key scope. +// testOutboundRemoteSigner tests that a watch-only wallet can use a remote +// signing wallet to perform any signing or ECDH operations. The test +// specifically uses an outbound remote signer, meaning that the remote signer +// node will make an outbound connection to the watch-only node. +func testOutboundRemoteSigner(ht *lntest.HarnessTest) { + prepareTest := func(st *lntest.HarnessTest, + subTest 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 subTest.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { + commitArgs = lntest.NodeArgsForCommitType( + subTest.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 := st.CreateNewNode( + "WatchOnly", append([]string{ + "--remotesigner.enable", + "--remotesigner.signerrole=watchonly-outbound", + "--remotesigner.timeout=30s", + "--remotesigner.requesttimeout=30s", + }, commitArgs...), + password, true, + ) + + // 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{ + "--remotesigner.signerrole=signer-outbound", + "--remotesigner.timeout=30s", + "--remotesigner.requesttimeout=10s", + fmt.Sprintf( + "--remotesigner.rpchost=localhost:%d", + watchOnly.Cfg.RPCPort, + ), + fmt.Sprintf( + "--remotesigner.tlscertpath=%s", + watchOnly.Cfg.TLSCertPath, + ), + fmt.Sprintf( + "--remotesigner.macaroonpath=%s", + watchOnly.Cfg.AdminMacPath, + ), + } + + if !subTest.randomSeed { + signer = st.RestoreNodeWithSeed( + "Signer", signerArgs, password, nil, rootKey, 0, + nil, + ) + } else { + signer = st.NewNode("Signer", signerArgs) + signerNodePubKey = signer.PubKeyStr + + rpcAccts := signer.RPC.ListAccounts( + &walletrpc.ListAccountsRequest{}, + ) + + watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly( + rpcAccts.Accounts, + ) + require.NoError(st, 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. + st.StartWatchOnly(watchOnly, "WatchOnly", password, + &lnrpc.WatchOnly{ + MasterKeyBirthdayTimestamp: 0, + MasterKeyFingerprint: nil, + Accounts: watchOnlyAccounts, + }, + ) + + resp := watchOnly.RPC.GetInfo() + require.Equal(st, signerNodePubKey, resp.IdentityPubkey) + + if subTest.sendCoins { + st.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly) + ht.AssertWalletAccountBalance( + watchOnly, "default", + btcutil.SatoshiPerBitcoin, 0, + ) + } + + carol := st.NewNode("carol", commitArgs) + st.EnsureConnected(watchOnly, carol) + + return signer, watchOnly, carol + } + + for _, testCase := range getRemoteSignerTestCases(ht) { + subTest := testCase + + success := ht.Run(subTest.name, func(tt *testing.T) { + // Skip the cleanup here as no standby node is used. + st := ht.Subtest(tt) + + _, watchOnly, carol := prepareTest(st, subTest) + subTest.fn(st, watchOnly, carol) + }) + + if !success { + return + } + } +} + +// 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.signerrole=watchonly-outbound", + "--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.Predicate(func() bool { + if watchOnly.RPC == nil { + return false + } + + state, err := watchOnly.RPC.State.GetState( + ht.Context(), &lnrpc.GetStateRequest{}, + ) + if err != nil { + return false + } + + return state.State == lnrpc.WalletState_ALLOW_REMOTE_SIGNER + }, 5*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. + streamFeeder := rpcwallet.NewStreamFeeder( + watchOnly.Cfg.RPCAddr(), watchOnly.Cfg.InvoiceMacPath, + watchOnly.Cfg.TLSCertPath, 10*time.Second, + ) + + stream, cleanup, err := streamFeeder.GetStream(ht.Context()) + require.NoError(ht, err) + + defer cleanup() + + // 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{ + "--remotesigner.signerrole=signer-outbound", + "--remotesigner.timeout=30s", + "--remotesigner.requesttimeout=10s", + fmt.Sprintf( + "--remotesigner.rpchost=localhost:%d", + watchOnly.Cfg.RPCPort, + ), + fmt.Sprintf( + "--remotesigner.tlscertpath=%s", + watchOnly.Cfg.TLSCertPath, + ), + fmt.Sprintf( + "--remotesigner.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 { allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts)) allAccounts = append(allAccounts, accounts...) diff --git a/lncfg/config.go b/lncfg/config.go index 889b327f6b..7329be570e 100644 --- a/lncfg/config.go +++ b/lncfg/config.go @@ -13,6 +13,10 @@ const ( // tries to load. DefaultConfigFilename = "lnd.conf" + // DefaultSignerConfigFilename is the default configuration file name + // lndsigner tries to load. + DefaultSignerConfigFilename = "lndsigner.conf" + // DefaultMaxPendingChannels is the default maximum number of incoming // pending channels permitted per peer. DefaultMaxPendingChannels = 1 diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 24ca61cfbd..7e43b1f786 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -6,40 +6,175 @@ 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 + + // 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 the + // watch-only node with signerrole 'watchonly-outbound' waits for the + // remote signer to connect. + DefaultStartupTimeout = 5 * time.Minute + + // DefaultInboundWatchOnlyRole is the default signer role used when + // enabling a remote signer on the watch-only node. It indicates that + // the remote signer node allows inbound connections from the watch-only + // node. + DefaultInboundWatchOnlyRole = "watchonly-inbound" + + // OutboundWatchOnlyRole is a type of signer role used when enabling a + // remote signer on the watch-only node. It indicates that the remote + // signer node will make an outbound connection to the watch-only node + // to connect the nodes. + OutboundWatchOnlyRole = "watchonly-outbound" + + // OutboundSignerRole indicates that the lnd instance will act as an + // outbound remote signer, connecting to a watch-only node that has the + // 'watchonly-outbound' signer role set. + OutboundSignerRole = "signer-outbound" + + // InboundSignerRole indicates that the lnd instance will act as an + // inbound remote signer, which allows a watch-only node to connect + // which has the 'watchonly-inbound' signer role set. + InboundSignerRole = "signer-inbound" ) // RemoteSigner holds the configuration options for a remote RPC signer. // //nolint:lll 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}."` + 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. This param should not be set to true when signerrole is set to either 'signer-outbound' or 'signer-inbound'"` + SignerRole string `long:"signerrole" description:"Sets the type of remote signer to use, or signals that the node will act as a remote signer. Can be set to either 'watchonly-inbound' (default), 'watchonly-outbound', 'signer-outbound' or 'signer-inbound'. 'watchonly-inbound' means that a remote signer that allows inbound connections from the watch-only node is used. 'watchonly-outbound' means that a remote signer node that makes an outbound connection to the watch-only node is used. 'signer-outbound' means the lnd instance will act as a remote signer, making an outbound connection to a watch-only node with the 'watchonly-outbound' signerrole set. 'signer-inbound' means that the lnd instance will act as an inbound remote signer, which allows a watch-only node to connect which has the 'watchonly-inbound' signer role set." choice:"watchonly-inbound" choice:"watchonly-outbound" choice:"signer-outbound" choice:"signer-inbound"` + RPCHost string `long:"rpchost" description:"The remote signer's or watch-only node's RPC host:port. For nodes which have the signerrole set to 'watchonly-inbound', this should be set to the remote signer node's RPC host:port. For nodes which have the signerrole set to 'signer-outbound', this should be set to the watch-only node's RPC host:port. This param should not be set when signerrole is set to either 'watchonly-outbound' or 'signer-inbound'."` + MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer or the watch-only node. For nodes which have the signerrole set to 'watchonly-inbound', this should be set to the remote signer node's macaroon. For nodes which have the signerrole set to 'signer-outbound', this should be set to the watch-only node's macaroon. This param should not be set when signerrole is set to either 'watchonly-outbound' or 'signer-inbound'."` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's or watch-only node's identity. For nodes which have the signerrole set to 'watchonly-inbound', this should be set to the remote signer node's TLS certificate. For nodes which have the signerrole set to 'signer-outbound', this should be set to the watch-only node's TLS certificate. This param should not be set when signerrole is set to either 'watchonly-outbound' or 'signer-inbound'."` + 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. This parameter will have no effect if signerrole is set to 'watchonly-inbound'. Valid time units are {s, m, h}."` + StartupTimeout time.Duration `long:"startuptimeout" description:"The time a watch-only node (with signerrole set to 'watchonly-outbound') 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. This parameter has no effect if 'signerrole' is not set to 'watchonly-outbound'. 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!"` } // Validate checks the values configured for our remote RPC signer. func (r *RemoteSigner) Validate() error { - 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) } + if r.RequestTimeout < time.Second { + return fmt.Errorf("remote signer: requesttimeout of %v is "+ + "invalid, cannot be smaller than %v", + r.Timeout, time.Second) + } + + if r.StartupTimeout < time.Second { + return fmt.Errorf("remote signer: startuptimeout of %v is "+ + "invalid, cannot be smaller than %v", + r.Timeout, time.Second) + } + 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.SignerRole == OutboundSignerRole && r.Enable { + return fmt.Errorf("remote signer: do not set " + + "remotesigner.enable when signerrole is set to " + + "'signer-outbound'") + } + + if r.SignerRole == OutboundSignerRole && r.RPCHost == "" { + return fmt.Errorf("remote signer: the rpchost for the " + + "watch-only node must be set when the node acts as " + + "an outbound remote signer") + } + + if r.SignerRole == OutboundSignerRole && r.MacaroonPath == "" { + return fmt.Errorf("remote signer: the macaroonpath for the " + + "watch-only node must be set when the node acts as " + + "an outbound remote signer") + } + + if r.SignerRole == OutboundSignerRole && r.TLSCertPath == "" { + return fmt.Errorf("remote signer: the tlscertpath for the " + + "watch-only node must be set when the node acts as " + + "an outbound remote signer") + } + + if r.SignerRole == InboundSignerRole && r.Enable { + return fmt.Errorf("remote signer: do not set " + + "remotesigner.enable when signerrole is set to " + + "'signer-inbound'") + } + + if r.SignerRole == InboundSignerRole && r.RPCHost != "" { + return fmt.Errorf("remote signer: the rpchost for the " + + "watch-only node should not be set when the node " + + "acts as an inbound remote signer") + } + + if r.SignerRole == InboundSignerRole && r.MacaroonPath != "" { + return fmt.Errorf("remote signer: the macaroonpath for the " + + "watch-only node should not be set when the node " + + "acts as an inbound remote signer") + } + + if r.SignerRole == InboundSignerRole && r.TLSCertPath != "" { + return fmt.Errorf("remote signer: the tlscertpath for the " + + "watch-only node should not be set when the node " + + "acts as an inbound remote signer") + } + + if !r.Enable { + return nil + } + + if r.SignerRole == DefaultInboundWatchOnlyRole && r.RPCHost == "" { + return fmt.Errorf("remote signer: the rpchost for the remote " + + "signer should be set when using an inbound remote " + + "signer") + } + + if r.SignerRole == DefaultInboundWatchOnlyRole && + r.MacaroonPath == "" { + + return fmt.Errorf("remote signer: the macaroonpath for the " + + "remote signer should be set when using an inbound " + + "remote signer") + } + + if r.SignerRole == DefaultInboundWatchOnlyRole && + r.TLSCertPath == "" { + + return fmt.Errorf("remote signer: the tlscertpath for the " + + "remote signer should be set when using an inbound " + + "remote signer") + } + + if r.SignerRole == OutboundWatchOnlyRole && r.RPCHost != "" { + return fmt.Errorf("remote signer: the rpchost for the remote " + + "signer should not be set if the signerrole is set " + + "to 'watchonly-outbound'") + } + + if r.SignerRole == OutboundWatchOnlyRole && r.MacaroonPath != "" { + return fmt.Errorf("remote signer: the macaroonpath for the " + + "remote signer should not be set if the signerrole " + + "is set to 'watchonly-outbound'") + } + + if r.SignerRole == OutboundWatchOnlyRole && r.TLSCertPath != "" { + return fmt.Errorf("remote signer: the tlscertpath for the " + + "remote signer not be set if the signerrole " + + "is set to 'watchonly-outbound'") + } + return nil } diff --git a/lnd.go b/lnd.go index f511811950..f1b19f949e 100644 --- a/lnd.go +++ b/lnd.go @@ -29,6 +29,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" @@ -317,10 +318,14 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } } + signerMode := cfg.RemoteSigner.SignerRole == lncfg.InboundSignerRole || + cfg.RemoteSigner.SignerRole == lncfg.OutboundSignerRole + // Create a new RPC interceptor that we'll add to the GRPC server. This // will be used to log the API calls invoked on the GRPC server. interceptorChain := rpcperms.NewInterceptorChain( - rpcsLog, cfg.NoMacaroons, cfg.RPCMiddleware.Mandatory, + rpcsLog, cfg.NoMacaroons, signerMode, + cfg.RPCMiddleware.Mandatory, ) if err := interceptorChain.Start(); err != nil { return mkErr("error starting interceptor chain: %v", err) @@ -474,6 +479,55 @@ 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, 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, + ) + if err != nil { + return mkErr("error adding sub server permissions: %v", err) + } + + defer func() { + err := rpcServer.Stop() + if err != nil { + ltndLog.Errorf("Error stopping the RPC server: %v", err) + } + }() + + // 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.Enable && + cfg.RemoteSigner.SignerRole == lncfg.OutboundWatchOnlyRole { + + 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(): + if err != nil { + return mkErr("error when waiting for wallet to be "+ + "ready: %v", 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 shutdown + // the wallet. + // We can't continue execute the code below as we can't generate + // any keys which. + return mkErr("Shutdown signal received while waiting for " + + "wallet to be ready.") + } + // TODO(roasbeef): add rotation idKeyDesc, err := activeChainControl.KeyRing.DeriveKey( keychain.KeyLocator{ @@ -595,13 +649,23 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, multiAcceptor = chanacceptor.NewChainedAcceptor() } + // Set up the remote signer client. If the + // cfg.RemoteSigner.SignerRole != lncfg.OutboundSignerRole, this remote + // signer client won't run when the server starts. + rscBuilder := rpcwallet.NewRemoteSignerClientBuilder(cfg.RemoteSigner) + + rsClient, err := rscBuilder.Build(rpcServer.subServers) + if err != nil { + return mkErr("unable to create remote signer client: %v", err) + } + // Set up the core server which will listen for incoming peer // connections. server, err := newServer( 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: %v", err) @@ -633,8 +697,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, 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( server, interceptorChain.MacaroonService(), cfg.SubRPCServers, atplManager, server.invoices, tower, multiAcceptor, @@ -643,12 +707,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, if err != nil { return mkErr("unable to add deps to RPC server: %v", err) } - if err := rpcServer.Start(); err != nil { - return mkErr("unable to start RPC server: %v", 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 761d5f0926..f543e8e505 100644 --- a/lnrpc/autopilotrpc/autopilot_server.go +++ b/lnrpc/autopilotrpc/autopilot_server.go @@ -6,6 +6,8 @@ package autopilotrpc import ( "context" "encoding/hex" + "errors" + "sync" "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" @@ -63,7 +65,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. @@ -74,6 +76,10 @@ type Server struct { cfg *Config manager *autopilot.Manager + + // This mutex should be held when accessing any fields of this struct, + // that can be accessed before the dependencies have been injected. + mu sync.Mutex } // A compile time check to ensure that Server fully implements the @@ -85,36 +91,30 @@ 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. // // NOTE: This is part of the lnrpc.SubServer interface. func (s *Server) Stop() error { + // As the Stop function could in theory in the future be called before + // the InjectDependencies function has been executed, we need to hold + // the lock here. + s.mu.Lock() + defer s.mu.Unlock() + if atomic.AddInt32(&s.shutdown, 1) != 1 { return nil } + if s.manager == nil { + return nil + } + return s.manager.Stop() } @@ -126,6 +126,44 @@ 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 + } + + s.mu.Lock() + defer s.mu.Unlock() + + 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, we the manager doesn't + // need to be set, and would then error if we tried to start it. + 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 +203,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 850540df4c..b8c2c4ce20 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 1defadafc0..6b1acf2c43 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" @@ -102,11 +103,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 @@ -119,7 +121,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 == "" { @@ -135,8 +185,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 @@ -146,47 +196,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 } @@ -251,17 +275,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 89a6a116a4..976f7c3086 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 662c0d08d9..f909c9ec9f 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -54,7 +54,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. // Required by the grpc-gateway/v2 library for forward compatibility. @@ -74,35 +74,43 @@ 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{ - 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 { - 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 } @@ -153,17 +161,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 a0d4d17c8c..96c44938ac 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 8014528ea0..58d14918d7 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 8ca02b260d..87f753cb41 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 9bfe7ccd13..1e6c30065a 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 65b0c8a6ef..91cc68ed19 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 8abf3ed924..b669f05125 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 14e039f615..2763a7536b 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 4e9cea6577..6856f2c5ce 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 a4112ba646..e58fec8d61 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -177,7 +177,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. @@ -200,7 +200,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 == "" { @@ -227,47 +272,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 } @@ -317,17 +336,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 eff0a5a53e..17a7303c3c 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 01b07d60cf..a2de73823b 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 4b2ac2c6a8..987269a9b7 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" @@ -118,6 +119,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 @@ -133,7 +136,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 == "" { @@ -160,37 +197,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 } @@ -199,7 +220,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 @@ -241,17 +262,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 } @@ -473,6 +492,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 { @@ -575,6 +595,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/stateservice.pb.go b/lnrpc/stateservice.pb.go index d6f6a23b22..e788181c88 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 97a78d31ad..ee29273885 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 bdf3bbdc85..881ffa38b6 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", diff --git a/lnrpc/sub_server.go b/lnrpc/sub_server.go index b09828213c..443100ce88 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 a68e87c532..b7a90360e1 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/config_active.go b/lnrpc/walletrpc/config_active.go index 4636473f0b..82e72e8d61 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 + + // RemoteSigner is the remote signer that the WalletKit will use to + // sign transactions, if enabled. + RemoteSigner RemoteSigner } diff --git a/lnrpc/walletrpc/driver.go b/lnrpc/walletrpc/driver.go index 7446470f1f..b194d3193a 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.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 9104e26be8..e9493464f7 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 { @@ -2870,7 +3590,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) } @@ -2883,7 +3603,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 { @@ -2896,7 +3616,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 { @@ -3011,7 +3731,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) } @@ -3024,7 +3744,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 { @@ -3037,7 +3757,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 { @@ -3052,7 +3772,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) } @@ -3065,7 +3785,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 { @@ -3078,7 +3798,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 { @@ -3131,7 +3851,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) } @@ -3144,7 +3864,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 { @@ -3157,7 +3877,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 { @@ -3223,7 +3943,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) } @@ -3236,7 +3956,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 { @@ -3249,7 +3969,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 { @@ -3291,7 +4011,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) } @@ -3304,7 +4024,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 { @@ -3317,7 +4037,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 { @@ -3367,7 +4087,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) } @@ -3380,7 +4100,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 { @@ -3393,7 +4113,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 { @@ -3421,7 +4141,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) } @@ -3434,7 +4154,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 { @@ -3447,7 +4167,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 { @@ -3479,7 +4199,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) } @@ -3492,7 +4212,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 { @@ -3505,7 +4225,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 { @@ -3562,7 +4282,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) } @@ -3575,7 +4295,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 { @@ -3588,7 +4308,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 { @@ -3624,7 +4344,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) } @@ -3637,7 +4357,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 { @@ -3650,7 +4370,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 { @@ -3699,7 +4419,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) } @@ -3712,7 +4432,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 { @@ -3725,7 +4445,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 { @@ -3918,7 +4638,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) } @@ -3931,7 +4651,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 { @@ -3944,7 +4664,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 { @@ -3988,7 +4708,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) } @@ -4001,7 +4721,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 { @@ -4014,7 +4734,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 { @@ -4056,7 +4776,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) } @@ -4069,7 +4789,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 { @@ -4082,7 +4802,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 { @@ -4156,7 +4876,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) } @@ -4169,7 +4889,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 { @@ -4182,7 +4902,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 { @@ -4233,7 +4953,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) } @@ -4246,7 +4966,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 { @@ -4259,7 +4979,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 { @@ -4283,7 +5003,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) } @@ -4296,7 +5016,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 { @@ -4309,7 +5029,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 { @@ -4343,7 +5063,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) } @@ -4356,7 +5076,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 { @@ -4369,7 +5089,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 { @@ -4400,7 +5120,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) } @@ -4413,7 +5133,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 { @@ -4426,7 +5146,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 { @@ -4452,7 +5172,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) } @@ -4465,7 +5185,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 { @@ -4478,7 +5198,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 { @@ -4493,7 +5213,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) } @@ -4506,7 +5226,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 { @@ -4519,7 +5239,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 { @@ -4543,7 +5263,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) } @@ -4556,7 +5276,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 { @@ -4569,7 +5289,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 { @@ -4586,725 +5306,874 @@ 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, 0xe7, 0x04, 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, 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, 0xf8, 0x01, 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, 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, 0xd6, 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, 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, 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, 0xaa, 0x04, 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, 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, + 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, + 0xe7, 0x04, 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, 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, 0xf8, 0x01, 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, 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, + 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, 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, 0xd6, 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, 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, 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, 0xaa, 0x04, 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, 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, + 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, 0x1c, 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x41, 0x50, + 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, 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, 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, + 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, 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, 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, + 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, 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 ( @@ -5320,181 +6189,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() } @@ -5504,7 +6415,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: @@ -5516,7 +6427,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: @@ -5528,7 +6439,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: @@ -5540,7 +6451,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: @@ -5552,7 +6463,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: @@ -5564,7 +6475,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: @@ -5576,7 +6487,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: @@ -5588,7 +6499,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: @@ -5600,7 +6511,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: @@ -5612,7 +6523,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: @@ -5624,7 +6535,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: @@ -5636,7 +6547,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: @@ -5648,7 +6559,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: @@ -5660,7 +6571,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: @@ -5672,7 +6583,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: @@ -5684,7 +6595,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: @@ -5696,7 +6607,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: @@ -5708,7 +6619,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: @@ -5720,7 +6631,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: @@ -5732,7 +6643,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: @@ -5744,7 +6655,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: @@ -5756,7 +6667,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: @@ -5768,7 +6679,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: @@ -5780,7 +6691,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: @@ -5792,7 +6703,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: @@ -5804,7 +6715,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: @@ -5816,7 +6727,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: @@ -5828,7 +6739,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: @@ -5840,7 +6751,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: @@ -5852,7 +6763,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: @@ -5864,7 +6775,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: @@ -5876,7 +6787,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: @@ -5888,7 +6799,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: @@ -5900,7 +6811,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: @@ -5912,7 +6823,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: @@ -5924,7 +6835,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: @@ -5936,7 +6847,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: @@ -5948,7 +6859,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: @@ -5960,7 +6871,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: @@ -5972,7 +6883,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: @@ -5984,7 +6895,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: @@ -5996,7 +6907,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: @@ -6008,7 +6919,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: @@ -6020,7 +6931,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: @@ -6032,7 +6943,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: @@ -6044,7 +6955,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: @@ -6056,7 +6967,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: @@ -6068,7 +6979,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: @@ -6080,7 +6991,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: @@ -6092,7 +7003,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: @@ -6104,7 +7015,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: @@ -6116,7 +7027,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: @@ -6128,7 +7039,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: @@ -6140,7 +7051,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: @@ -6152,7 +7063,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: @@ -6164,7 +7075,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: @@ -6176,7 +7087,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: @@ -6188,7 +7099,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: @@ -6200,7 +7111,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: @@ -6212,7 +7123,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: @@ -6224,7 +7135,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: @@ -6236,6 +7147,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 @@ -6248,17 +7231,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), @@ -6266,7 +7278,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), } @@ -6276,7 +7288,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.proto b/lnrpc/walletrpc/walletkit.proto index f9b7cd8e1c..50da53ba52 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 5acafa67df..04a45f708c 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -1259,6 +1259,297 @@ } } }, + "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": { + "$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": { @@ -1999,6 +2290,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": { @@ -2085,6 +2402,56 @@ } } }, + "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." + } + } + }, "walletrpcSignMessageWithAddrRequest": { "type": "object", "properties": { @@ -2136,6 +2503,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_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go index 579aa47bb3..035987265f 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 b15012f0c0..c54309c8a3 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -14,6 +14,8 @@ import ( "os" "path/filepath" "sort" + "sync" + "sync/atomic" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -184,6 +186,10 @@ var ( Entity: "onchain", Action: "write", }}, + "/walletrpc.WalletKit/SignCoordinatorStreams": {{ + Entity: "remotesigner", + Action: "generate", + }}, } // DefaultWalletKitMacFilename is the default name of the wallet kit @@ -239,6 +245,15 @@ var ( } ) +// RemoteSigner is an interface that mimics a subset of the rpcwallet +// RemoteSigner interface to avoid circular dependencies. +type RemoteSigner interface { + // Run feeds lnd with the incoming stream that an outbound remote signer + // has set up, and then blocks until the stream is closed. Lnd can then + // proceed to send any requests to the remote signer through the stream. + Run(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. @@ -250,10 +265,18 @@ 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 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 @@ -261,7 +284,44 @@ 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 + } + + w.Lock() + defer w.Unlock() + + 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 == "" { @@ -288,37 +348,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 } @@ -368,17 +412,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 } @@ -389,6 +431,9 @@ func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispat // 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, @@ -431,6 +476,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, @@ -452,6 +501,35 @@ 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 { + + w.RLock() + + // Check that the user actually has configured that the reverse remote + // signer functionality should be enabled. + if w.cfg.RemoteSigner == nil { + w.RUnlock() + + return fmt.Errorf("remote signer not set in config") + } + + signer := w.cfg.RemoteSigner + + // Release the read lock as we will acquire the write in the + // InjectDependencies function while the stream is still open. + w.RUnlock() + + err := signer.Run(stream) + if err != nil { + log.Errorf("Remote signer stream error: %v", err) + } + + return err +} + // 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 @@ -492,6 +570,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 @@ -527,6 +608,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 { @@ -545,6 +629,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 @@ -561,6 +648,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), ) @@ -582,6 +672,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), @@ -621,6 +714,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 @@ -646,6 +742,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 @@ -679,6 +778,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 @@ -711,6 +813,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) @@ -781,6 +886,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. @@ -850,6 +958,9 @@ func (w *WalletKit) EstimateFee(ctx context.Context, "than 1") } + w.RLock() + defer w.RUnlock() + satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW( uint32(req.ConfTarget), ) @@ -873,6 +984,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() @@ -1008,6 +1122,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 { @@ -1092,6 +1209,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 { @@ -1298,6 +1418,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 @@ -1361,6 +1484,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 @@ -1444,6 +1570,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{ @@ -1484,6 +1613,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, ) @@ -1714,6 +1846,9 @@ func (w *WalletKit) fundPsbtInternalWallet(account string, feeSatPerKW chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy) (*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 @@ -1841,6 +1976,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, @@ -1857,6 +1994,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 @@ -2049,6 +2189,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( @@ -2080,6 +2223,9 @@ func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet, newOutpoints []wire.OutPoint, changeIndex int32) (*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. @@ -2119,6 +2265,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( @@ -2256,6 +2405,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. @@ -2312,6 +2464,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. @@ -2483,6 +2638,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 @@ -2517,6 +2675,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 @@ -2537,6 +2698,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, @@ -2630,6 +2794,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) @@ -2717,6 +2884,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) @@ -2838,6 +3008,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, ) @@ -2897,6 +3070,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 } @@ -2976,6 +3152,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/lnrpc/walletrpc/walletkit_server_test.go b/lnrpc/walletrpc/walletkit_server_test.go index d567eb028a..77249ca964 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 6352de7ccc..e8e198b8ac 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 f61dabfd00..53125f4eb4 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 62a18cffce..7b06cf0401 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 5ddb99d370..9ce78d5822 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/lntest/harness.go b/lntest/harness.go index f96a3aadd7..6145ad1f57 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -236,7 +236,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, @@ -818,15 +818,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, err := h.manager.newNode(h.T, name, extraArgs, password, true) + hn := h.CreateNewNode(name, extraArgs, password, true) + + 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, noAuth bool) *node.HarnessNode { + + hn, err := h.manager.newNode(h.T, name, extraArgs, password, noAuth) 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, @@ -840,8 +860,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. diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index fd4a433486..4445f487ee 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -280,6 +280,14 @@ func (w *WalletController) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (w *WalletController) ReadySignal() 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 d0444edd58..17fbb0bccf 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -466,6 +466,14 @@ func (b *BtcWallet) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (b *BtcWallet) ReadySignal() 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 e2e491c735..a864dec9ea 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -537,6 +537,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() 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 6623d8014f..b0612dd560 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -296,6 +296,14 @@ func (w *mockWalletController) Stop() error { return nil } +// ReadySignal currently signals that the wallet is ready instantly. +func (w *mockWalletController) ReadySignal() 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/healthcheck.go b/lnwallet/rpcwallet/healthcheck.go index 7a412959e0..e04cbc2e15 100644 --- a/lnwallet/rpcwallet/healthcheck.go +++ b/lnwallet/rpcwallet/healthcheck.go @@ -1,32 +1,19 @@ package rpcwallet import ( - "fmt" "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(rs RemoteSigner, timeout time.Duration) func() error { return func() error { - conn, err := connectRPC( - cfg.RPCHost, cfg.TLSCertPath, cfg.MacaroonPath, timeout, - ) + err := rs.Ping(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/remote_signer.go b/lnwallet/rpcwallet/remote_signer.go new file mode 100644 index 0000000000..8ddfa89e33 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer.go @@ -0,0 +1,329 @@ +package rpcwallet + +import ( + "context" + "crypto/x509" + "errors" + "fmt" + "os" + "time" + + "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" + "gopkg.in/macaroon.v2" +) + +type ( + StreamClient = walletrpc.WalletKit_SignCoordinatorStreamsClient + StreamServer = walletrpc.WalletKit_SignCoordinatorStreamsServer +) + +// RemoteSigner is an interface that abstracts the communication with a remote +// signer. It extends the RemoteSignerRequests, and adds some additional methods +// to manage the connection and verify the health of the remote signer. +type RemoteSigner 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 blocks and returns nil when the remote signer is ready to + // accept requests. + Ready() error + + // Ping verifies that the remote signer is still responsive. + Ping(timeout time.Duration) error + + // Run feeds lnd 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. + Run(stream StreamServer) 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) +} + +// InboundRemoteSigner is an abstraction of the connection 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 InboundRemoteSigner struct { + // Embedded signrpc.SignerClient and walletrpc.WalletKitClient to + // implement the RemoteSigner interface. + signrpc.SignerClient + + walletrpc.WalletKitClient + + // The host:port of the remote signer node. + rpcHost string + + // The path to the TLS certificate of the remote signer node. + tlsCertPath string + + // The path to the macaroon of the remote signer node. + macaroonPath string + + // The timeout for the connection to the remote signer node. + timeout time.Duration +} + +// NewInboundRemoteSigner creates a new InboundRemoteSigner instance. +// The function sets up a connection to the remote signer node. +// The returned function is a cleanup function that should be called to close +// the connection when the remote signer is no longer needed. +func NewInboundRemoteSigner(rpcHost string, tlsCertPath string, + macaroonPath string, + timeout time.Duration) (*InboundRemoteSigner, func(), error) { + + rpcConn, err := connectRPC(rpcHost, tlsCertPath, macaroonPath, timeout) + if err != nil { + return nil, nil, fmt.Errorf("error connecting to the remote "+ + "signing node through RPC: %v", err) + } + + cleanUp := func() { + rpcConn.Close() + } + + remoteSigner := &InboundRemoteSigner{ + SignerClient: signrpc.NewSignerClient(rpcConn), + WalletKitClient: walletrpc.NewWalletKitClient(rpcConn), + rpcHost: rpcHost, + tlsCertPath: tlsCertPath, + macaroonPath: macaroonPath, + timeout: timeout, + } + + return remoteSigner, cleanUp, nil +} + +// Run feeds lnd 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 RemoteSigner interface. +func (*InboundRemoteSigner) Run(_ StreamServer) error { + // If lnd has been configured to use an inbound remote signer, it should + // not allow an outbound remote signer to connect. + return errors.New("not supported when remotesigner.signerrole is set " + + "to \"watchonly-inbound\"") +} + +// Ready blocks and returns nil when the remote signer is ready to accept +// requests. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *InboundRemoteSigner) Ready() error { + // The inbound remote signer is ready as soon we have connected to the + // remote signer node in the constructor. Therefore, we always return + // nil here to signal that we are ready. + return nil +} + +// Ping verifies that the remote signer is still responsive. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *InboundRemoteSigner) Ping(timeout time.Duration) error { + conn, err := connectRPC( + r.rpcHost, r.tlsCertPath, r.macaroonPath, timeout, + ) + if err != nil { + return fmt.Errorf("error connecting to the remote "+ + "signing node through RPC: %v", err) + } + + return conn.Close() +} + +// Timeout returns the set connection timeout for the remote signer. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *InboundRemoteSigner) Timeout() time.Duration { + return r.timeout +} + +// A compile time assertion to ensure InboundRemoteSigner meets the +// RemoteSigner interface. +var _ RemoteSigner = (*InboundRemoteSigner)(nil) + +// 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) + + // 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, hostPort, opts...) + if err != nil { + return nil, fmt.Errorf("unable to connect to RPC server: %w", + err) + } + + return conn, nil +} + +// OutboundRemoteSigner references a remote signer that makes an outbound +// connection to a watch-only node. +type OutboundRemoteSigner struct { + *SignCoordinator + + connectionTimeout time.Duration +} + +// NewOutboundRemoteSigner creates a new OutboundRemoteSigner instance. +func NewOutboundRemoteSigner(requestTimeout time.Duration, + connectionTimeout time.Duration) (*OutboundRemoteSigner, func()) { + + remoteSigner := &OutboundRemoteSigner{ + connectionTimeout: connectionTimeout, + } + + remoteSigner.SignCoordinator = NewSignCoordinator( + requestTimeout, connectionTimeout, + ) + + return remoteSigner, remoteSigner.Stop +} + +// Timeout returns the set connection timeout for the remote signer. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *OutboundRemoteSigner) Timeout() time.Duration { + return r.connectionTimeout +} + +// Ready blocks and returns nil when the remote signer is ready to accept +// requests. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *OutboundRemoteSigner) Ready() error { + log.Infof("Waiting for the remote signer to connect") + + return r.SignCoordinator.WaitUntilConnected() +} + +// Ping verifies that the remote signer is still responsive. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *OutboundRemoteSigner) Ping(timeout time.Duration) error { + pong, err := r.SignCoordinator.Ping(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 +} + +// Run feeds lnd with the incoming stream that an outbound remote signer has set +// up, and blocks until the stream is closed. Lnd can then proceed to send any +// requests to the remote signer through the stream. +// +// NOTE: This is part of the RemoteSigner interface. +func (r *OutboundRemoteSigner) Run(stream StreamServer) error { + return r.SignCoordinator.Run(stream) +} + +// A compile time assertion to ensure OutboundRemoteSigner meets the +// RemoteSigner interface. +var _ RemoteSigner = (*OutboundRemoteSigner)(nil) diff --git a/lnwallet/rpcwallet/remote_signer_builder.go b/lnwallet/rpcwallet/remote_signer_builder.go new file mode 100644 index 0000000000..ff4535d5d9 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_builder.go @@ -0,0 +1,81 @@ +package rpcwallet + +import ( + "errors" + + "github.com/lightningnetwork/lnd/lncfg" +) + +// RemoteSignerBuilder is creates instances of the RemoteSigner interface, based +// on the provided configuration. +type RemoteSignerBuilder struct { + cfg *lncfg.RemoteSigner +} + +// NewRemoteSignerBuilder creates a new instance of the RemoteSignerBuilder. +func NewRemoteSignerBuilder(cfg *lncfg.RemoteSigner) *RemoteSignerBuilder { + return &RemoteSignerBuilder{cfg} +} + +// Build creates a new RemoteSigner instance. If the configuration specifies +// that an inbound remote signer should be used, a new InboundRemoteSigner is +// created. If the configuration specifies that an outbound remote signer should +// be used, a new OutboundRemoteSigner is created. +// The function returns the created RemoteSigner instance, and a cleanup +// function that should be called when the RemoteSigner is no longer needed. +func (b *RemoteSignerBuilder) Build() (RemoteSigner, func(), error) { + if b.cfg == nil { + return nil, nil, errors.New("remote signer config is nil") + } + + // Validate that the configuration has valid values set. + err := b.cfg.Validate() + if err != nil { + return nil, nil, err + } + + if !b.cfg.Enable { + // This should be unreachable, but this is an extra sanity check + return nil, nil, errors.New("remote signer not enabled in " + + "config") + } + + // Create the remote signer based on the configuration. + switch b.cfg.SignerRole { + case lncfg.DefaultInboundWatchOnlyRole: + return b.createInboundRemoteSigner() + + case lncfg.OutboundWatchOnlyRole: + return b.createOutboundRemoteSigner() + + default: + return nil, nil, errors.New("unknown remote signer type") + } +} + +// createInboundRemoteSigner creates a new InboundRemoteSigner instance. +// The function returns the created InboundRemoteSigner instance, and a cleanup +// function that should be called when the InboundRemoteSigner is no longer +// needed. +func (b *RemoteSignerBuilder) createInboundRemoteSigner() ( + *InboundRemoteSigner, func(), error) { + + return NewInboundRemoteSigner( + b.cfg.RPCHost, b.cfg.TLSCertPath, b.cfg.MacaroonPath, + b.cfg.Timeout, + ) +} + +// createOutboundRemoteSigner creates a new OutboundRemoteSigner instance. +// The function returns the created OutboundRemoteSigner instance, and a cleanup +// function that should be called when the OutboundRemoteSigner is no longer +// needed. +func (b *RemoteSignerBuilder) createOutboundRemoteSigner() ( + *OutboundRemoteSigner, func(), error) { + + outboundRemoteSigner, cleanUp := NewOutboundRemoteSigner( + b.cfg.RequestTimeout, b.cfg.Timeout, + ) + + return outboundRemoteSigner, cleanUp, nil +} diff --git a/lnwallet/rpcwallet/remote_signer_client.go b/lnwallet/rpcwallet/remote_signer_client.go new file mode 100644 index 0000000000..dc44553557 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client.go @@ -0,0 +1,968 @@ +package rpcwallet + +import ( + "context" + "errors" + "fmt" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/lightningnetwork/lnd/fn" + "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 = walletrpc.SignCoordinatorResponse + +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) +) + +// 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(streamCtx context.Context) (StreamClient, func(), 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() error + + // Stop stops the remote signer client. + Stop() error +} + +// 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 + + rpcHost, macaroonPath, tlsCertPath string + + timeout time.Duration + + quit chan struct{} +} + +// NewStreamFeeder creates a new StreamFeeder instance. +func NewStreamFeeder(rpcHost, macaroonPath, tlsCertPath string, + timeout time.Duration) *StreamFeeder { + + return &StreamFeeder{ + quit: make(chan struct{}), + rpcHost: rpcHost, + macaroonPath: macaroonPath, + tlsCertPath: tlsCertPath, + timeout: timeout, + } +} + +// 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() { + close(s.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(streamCtx context.Context) ( + StreamClient, func(), error) { + + select { + // Don't run if the StreamFeeder has already been shutdown. + case <-s.quit: + return nil, nil, ErrShuttingDown + default: + } + + // Create a new outbound gRPC connection to the watch-only node. + conn, err := s.getClientConn() + if err != nil { + return nil, nil, err + } + + cleanUp := func() { + conn.Close() + } + + // Wrap the connection in a WalletKitClient. + walletKitClient := walletrpc.NewWalletKitClient(conn) + + // Create a new stream to the watch-only node. + stream, err := walletKitClient.SignCoordinatorStreams(streamCtx) + if err != nil { + cleanUp() + + return nil, nil, err + } + + return stream, cleanUp, nil +} + +// getClientConn creates a new outbound gRPC connection to the watch-only node. +func (s *StreamFeeder) getClientConn() (*grpc.ClientConn, error) { + // If we fail to connect to the watch-only node within the + // configured timeout we should return an error. + ctxt, cancel := context.WithTimeout( + context.Background(), s.timeout, + ) + defer cancel() + + // Load the specified macaroon file for the watch-only node. + macBytes, err := os.ReadFile(s.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.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), + } + + var ( + // A channel to signal when has successfully been created. + connDoneChan = make(chan *grpc.ClientConn) + errChan = make(chan error) + ) + + // Now let's try to connect to the watch-only node. We'll do this in a + // goroutine to ensure we can exit if the quit channel is closed. If the + // quit channel is closed, the context will also be canceled, hence + // stopping the goroutine. + s.wg.Add(1) + go func() { + defer s.wg.Done() + + log.Infof("Attempting to connect to the watch-only node on: %s", + s.rpcHost) + + conn, err := grpc.DialContext(ctxt, s.rpcHost, opts...) + if err != nil { + select { + case errChan <- fmt.Errorf("could not connect to "+ + "watch-only node: %v", err): + + case <-ctxt.Done(): + } + + return + } + + // Only send the connection if the getClientConn function hasn't + // returned yet. + select { + case <-ctxt.Done(): + return + + case connDoneChan <- conn: + } + }() + + select { + case conn := <-connDoneChan: + return conn, nil + + case err := <-errChan: + return nil, err + + case <-s.quit: + return nil, ErrShuttingDown + + case <-ctxt.Done(): + return nil, ctxt.Err() + } +} + +// 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 implements RemoteSignerClient, and is a no op. +func (n *NoOpClient) Start() error { + return nil +} + +// Stop implements RemoteSignerClient, and is a no op. +func (n *NoOpClient) Stop() error { + return nil +} + +// 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 + + // 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 + + // stream is the stream between the node and the watch-only node. + stream StreamClient + + // 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 + + quit chan struct{} + + gManager *fn.GoroutineManager + gmCtxCancel context.CancelFunc +} + +// 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") + } + + ctxc, cancel := context.WithCancel(context.Background()) + + return &OutboundClient{ + walletServer: walletServer, + signerServer: signerServer, + streamFeeder: streamFeeder, + requestTimeout: requestTimeout, + quit: make(chan struct{}), + retryTimeout: defaultRetryTimeout, + maxRetryTimeout: defaultMaxRetryTimeout, + gManager: fn.NewGoroutineManager(ctxc), + gmCtxCancel: cancel, + }, nil +} + +// Start starts the remote signer client. The function will continuously try to +// setup a connection to the configured watch-only node, and retry to connect if +// the connection fails until we Stop the remote signer client. +func (r *OutboundClient) Start() error { + // We'll continuously try setup a connection to the watch-only node, and + // retry to connect if the connection fails until we Stop the remote + // signer client. + err := r.gManager.Go(func(_ context.Context) { + for { + err := r.run() + if err != nil { + log.Errorf("Remote signer client error: %v", + err) + } + + select { + case <-r.quit: + return + default: + log.Infof("Will retry to connect to "+ + "watch-only node in: %v", + r.retryTimeout) + + // Backoff before retrying to connect to the + // watch-only node. + select { + case <-r.quit: + return + case <-time.After(r.retryTimeout): + } + } + + log.Infof("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 + } + } + }) + + return err +} + +// Stop stops the remote signer client. +func (r *OutboundClient) Stop() error { + if r.stopped.Swap(true) { + return errors.New("remote signer client is already shut down") + } + + log.Info("Remote signer client shutting down") + + close(r.quit) + + if r.streamFeeder != nil { + r.streamFeeder.Stop() + } + + r.gManager.Stop() + + r.gmCtxCancel() + + log.Debugf("Remote signer client shut down") + + return nil +} + +// run 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) run() error { + select { + case <-r.quit: + return ErrShuttingDown + default: + } + + streamCtx, cancel := context.WithCancel(context.Background()) + + // Cancel the stream context whenever we return from this function. + defer cancel() + + log.Infof("Attempting to setup the watch-only node connection") + + // Try to get a new stream to the watch-only node. + stream, streamCleanUp, err := r.streamFeeder.GetStream(streamCtx) + if err != nil { + return err + } + + r.stream = stream + defer streamCleanUp() + + // 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(streamCtx) + if err != nil { + return err + } + + log.Infof("Completed setup connection to watch-only node") + + // Reset the retry timeout after a successful connection. + r.retryTimeout = defaultRetryTimeout + + return r.processSignRequests(streamCtx) +} + +// 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(streamCtx context.Context) error { + var ( + regSentChan = make(chan struct{}) + completeChan = make(chan *walletrpc.RegistrationComplete) + errChan = make(chan error) + + // The returnedChan is used to signal that this function has + // already returned, so that the goroutines don't remain blocked + // indefinitely when trying to send to the above channels. + returnedChan = make(chan struct{}) + ) + defer close(returnedChan) + + // completeType is a type alias for the registration complete type, + // created to keep line length within 80 characters. + type completeType = walletrpc.RegistrationResponse_RegistrationComplete + + // 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. + signReg := &walletrpc.SignerRegistration{ + RegistrationChallenge: "registrationChallenge", + RegistrationInfo: "outboundSigner", + } + + regType := &walletrpc.SignCoordinatorResponse_SignerRegistration{ + SignerRegistration: signReg, + } + + registrationMsg := &walletrpc.SignCoordinatorResponse{ + RefRequestId: handshakeRequestID, + SignResponseType: regType, + } + + // Send the registration message to the watch-only node. + err := r.gManager.Go(func(_ context.Context) { + err := r.stream.Send(registrationMsg) + if err != nil { + select { + case errChan <- err: + case <-returnedChan: + } + + return + } + + close(regSentChan) + }) + if err != nil { + return fmt.Errorf("error starting registration message "+ + "sending function : %w", err) + } + + select { + case err := <-errChan: + return fmt.Errorf("error sending registration message to "+ + "watch-only node: %w", err) + + case <-streamCtx.Done(): + return streamCtx.Err() + + case <-r.quit: + return ErrShuttingDown + + case <-time.After(r.requestTimeout): + return errors.New("watch-only node handshake timeout") + + case <-regSentChan: + } + + // 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. + err = r.gManager.Go(func(_ context.Context) { + msg, err := r.stream.Recv() + if err != nil { + select { + case errChan <- err: + case <-returnedChan: + } + + return + } + + // Verify that the request ID of the response is the same as the + // request ID of the registration message. + if msg.GetRequestId() != handshakeRequestID { + err = fmt.Errorf("initial response request id must "+ + "be %d, but is: %d", handshakeRequestID, + msg.GetRequestId()) + + select { + case errChan <- err: + case <-returnedChan: + } + + return + } + + // Check the type of the response message. + switch reqType := msg.GetSignRequestType().(type) { + case *walletrpc.SignCoordinatorRequest_RegistrationResponse: + + switch rType := reqType.RegistrationResponse. + GetRegistrationResponseType().(type) { + // The registration was successful. + case *completeType: + select { + case completeChan <- rType.RegistrationComplete: + case <-returnedChan: + } + + // An error occurred during the registration process. + case *walletrpc.RegistrationResponse_RegistrationError: + err := fmt.Errorf("registration error: %s", + rType.RegistrationError) + + select { + case errChan <- err: + case <-returnedChan: + } + } + + return + + default: + err := fmt.Errorf("expected registration response, "+ + "but got: %T", reqType) + + select { + case errChan <- err: + case <-returnedChan: + } + + return + } + }) + if err != nil { + return fmt.Errorf("error starting registration completion "+ + "checking function : %w", err) + } + + // Wait for the watch-only node to respond that it has accepted the + // signer has registered. + select { + case <-completeChan: + // TODO(viktor): This should verify that the signature in the + // complete message is valid. + + case err := <-errChan: + return fmt.Errorf("watch-only node handshake error: %w", err) + + case <-r.quit: + return ErrShuttingDown + + case <-streamCtx.Done(): + return streamCtx.Err() + + case <-time.After(r.requestTimeout): + return errors.New("watch-only node handshake timeout") + } + + return nil +} + +// processSignRequests processes and responds 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) processSignRequests(streamCtx context.Context) error { + var ( + reqChan = make(chan *walletrpc.SignCoordinatorRequest) + errChan = make(chan error) + ) + + // We run the receive loop in a goroutine to ensure we can stop if the + // remote signer client is shutting down (i.e. the quit channel is + // closed). Closing the quit channel will make the processSignRequests + // function return, which will cancel the stream context, which in turn + // will stop the receive goroutine. + err := r.gManager.Go(func(_ context.Context) { + for { + req, err := r.stream.Recv() + if err != nil { + wrappedErr := fmt.Errorf("error receiving "+ + "request from watch-only node: %w", err) + + // Send the error to the error channel, given + // that we're still listening on the channel. + select { + case errChan <- wrappedErr: + case <-streamCtx.Done(): + case <-r.quit: + } + + return + } + + select { + case <-streamCtx.Done(): + return + + case <-r.quit: + return + + case reqChan <- req: + } + } + }) + if err != nil { + return fmt.Errorf("error starting receiving loop: %w", err) + } + + for { + log.Tracef("Waiting for a request from the watch-only node") + + select { + case req := <-reqChan: + // Process the received request. + err := r.handleRequest(streamCtx, req) + if err != nil { + return err + } + + case <-r.quit: + return ErrShuttingDown + + case <-streamCtx.Done(): + return streamCtx.Err() + + case err := <-errChan: + return err + } + } +} + +// handleRequest processes the received request from the watch-only node, and +// sends the corresponding response back. +func (r *OutboundClient) handleRequest(streamCtx context.Context, + req *walletrpc.SignCoordinatorRequest) error { + + log.Debugf("Processing a request from watch-only node of type: %T", + req.GetSignRequestType()) + + log.Tracef("Request content: %v", formatSignCoordinatorMsg(req)) + + // Process the request. + resp, err := r.process(streamCtx, req) + if err != nil { + errStr := "error processing the request in the remote " + + "signer: " + err.Error() + + log.Errorf(errStr) + + // 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: errStr, + }, + } + + resp = &signerResponse{ + RefRequestId: req.GetRequestId(), + SignResponseType: eType, + } + } + + // Send the response back to the watch-only node. + err = r.sendResponse(streamCtx, resp) + if err != nil { + return fmt.Errorf("error sending response to watch-only "+ + "node: %w", err) + } + + log.Tracef("Sent the following response to watch-only node: %v", + formatSignCoordinatorMsg(resp)) + + return nil +} + +// 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) { + + var ( + requestID = req.GetRequestId() + signResp = &signerResponse{ + RefRequestId: requestID, + } + ) + + //nolint:lll + 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) error { + + // We send the response in a goroutine to ensure we can return an error + // if the send times out or the context is canceled. This is done to + // ensure that this function won't block indefinitely. + var ( + sendDone = make(chan struct{}) + errChan = make(chan error) + + // The returnedChan is used to signal that this function has + // already returned, so that the goroutines don't remain blocked + // indefinitely when trying to send to the above channels. + returnedChan = make(chan struct{}) + ) + defer close(returnedChan) + + err := r.gManager.Go(func(_ context.Context) { + err := r.stream.Send(resp) + if err != nil { + select { + case errChan <- err: + case <-returnedChan: + } + + return + } + + close(sendDone) + }) + if err != nil { + return fmt.Errorf("error starting send function: %w", err) + } + + select { + case err := <-errChan: + return fmt.Errorf("send response to watch-only node error: %w", + err) + + case <-time.After(r.requestTimeout): + return errors.New("send response to watch-only node timeout") + + case <-r.quit: + return ErrShuttingDown + + case <-ctx.Done(): + return ctx.Err() + + case <-sendDone: + 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) 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_builder.go b/lnwallet/rpcwallet/remote_signer_client_builder.go new file mode 100644 index 0000000000..aac0804693 --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client_builder.go @@ -0,0 +1,81 @@ +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 is creates instances of the RemoteSignerClient +// interface, based on the provided configuration. +type RemoteSignerClientBuilder struct { + cfg *lncfg.RemoteSigner +} + +// NewRemoteSignerClientBuilder creates a new instance of the +// RemoteSignerClientBuilder. +func NewRemoteSignerClientBuilder(cfg *lncfg.RemoteSigner) *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 == nil || b.cfg.SignerRole != lncfg.OutboundSignerRole || + b.cfg.RPCHost == "" || b.cfg.MacaroonPath == "" || + b.cfg.TLSCertPath == "" || b.cfg.RequestTimeout <= 0 || + b.cfg.Timeout <= 0 { + + log.Debugf("Using a No Op remote signer client due to " + + "current remote signer 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.RPCHost, b.cfg.MacaroonPath, b.cfg.TLSCertPath, + b.cfg.Timeout, + ) + + rsClient, err := NewOutboundClient( + walletServer, signerServer, streamFeeder, b.cfg.RequestTimeout, + ) + if err != nil { + return nil, err + } + + return rsClient, nil +} diff --git a/lnwallet/rpcwallet/remote_signer_client_test.go b/lnwallet/rpcwallet/remote_signer_client_test.go new file mode 100644 index 0000000000..43c8b3ac7a --- /dev/null +++ b/lnwallet/rpcwallet/remote_signer_client_test.go @@ -0,0 +1,721 @@ +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(ctxc context.Context) ( + StreamClient, func(), error) { + + msf.mu.Lock() + + select { + case <-msf.quit: + msf.mu.Unlock() + return nil, 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 <-ctxc.Done(): + case <-msf.quit: + } + + return nil, nil, ErrStreamError + } + + // Else create a new mock stream instance. + stream, cancel := newMockStream(ctxc) + + msf.stream = stream + + msf.mu.Unlock() + + // Signal that the stream creation has succeeded. + select { + case msf.streamCreated <- nil: + case <-ctxc.Done(): + case <-msf.quit: + } + + return msf.stream, cancel, 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(ctxc context.Context) (*mockStream, func()) { + // Wrap the context in a cancel function, 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 wrapped context is cancelled, the remote signer client has + // cancelled the stream. + ctxc, cancel := context.WithCancel(ctxc) + + return &mockStream{ + sendChan: make(chan *walletrpc.SignCoordinatorResponse), + recvChan: make(chan *walletrpc.SignCoordinatorRequest), + recvErrChan: make(chan error), + ctx: ctxc, + }, cancel +} + +// 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: "outboundWatchOnly", + }, + } + + 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()) + + // 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 +} diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index bf6aa61df3..495eaaa684 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" @@ -25,19 +23,14 @@ import ( "github.com/lightningnetwork/lnd/fn" "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" "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 ( @@ -63,8 +56,7 @@ type RPCKeyRing struct { rpcTimeout time.Duration - signerClient signrpc.SignerClient - walletClient walletrpc.WalletKitClient + remoteSigner RemoteSigner } var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil) @@ -77,25 +69,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, + remoteSigner RemoteSigner, 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: remoteSigner.Timeout(), + remoteSigner: remoteSigner, }, nil } @@ -206,7 +188,7 @@ 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{ + resp, err := r.remoteSigner.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{ FundedPsbt: buf.Bytes(), }) if err != nil { @@ -419,7 +401,7 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed() } - resp, err := r.signerClient.DeriveSharedKey(ctxt, req) + resp, err := r.remoteSigner.DeriveSharedKey(ctxt, req) if err != nil { considerShutdown(err) return key, fmt.Errorf("error deriving shared key in remote "+ @@ -442,7 +424,7 @@ 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{ + resp, err := r.remoteSigner.SignMessage(ctxt, &signrpc.SignMessageReq{ Msg: msg, KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(keyLoc.Family), @@ -488,7 +470,7 @@ 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{ + resp, err := r.remoteSigner.SignMessage(ctxt, &signrpc.SignMessageReq{ Msg: msg, KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(keyLoc.Family), @@ -521,7 +503,7 @@ 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{ + resp, err := r.remoteSigner.SignMessage(ctxt, &signrpc.SignMessageReq{ Msg: msg, KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(keyLoc.Family), @@ -716,7 +698,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.remoteSigner.MuSig2CreateSession(ctxt, req) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error creating MuSig2 session in "+ @@ -770,7 +752,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.remoteSigner.MuSig2RegisterNonces(ctxt, req) if err != nil { considerShutdown(err) return false, fmt.Errorf("error registering MuSig2 nonces in "+ @@ -801,7 +783,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.remoteSigner.MuSig2Sign(ctxt, req) if err != nil { considerShutdown(err) return nil, fmt.Errorf("error signing MuSig2 session in "+ @@ -845,7 +827,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.remoteSigner.MuSig2CombineSig(ctxt, req) if err != nil { considerShutdown(err) return nil, false, fmt.Errorf("error combining MuSig2 "+ @@ -867,6 +849,27 @@ func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID, return finalSig, resp.HaveAllSignatures, nil } +// RemoteSigner returns the remote signer instance that is used by the RPC key +// ring to sign transactions. +func (r *RPCKeyRing) RemoteSigner() RemoteSigner { + return r.remoteSigner +} + +// RequireReady waits until the remote signer is ready to sign transactions, and +// returns an error if we time out while waiting. This method overrides/shadows +// the default implementation of the WalletController interface. +// +// NOTE: This method is part of the WalletController interface. +func (r *RPCKeyRing) ReadySignal() chan error { + readyChan := make(chan error, 1) + + go func() { + readyChan <- r.remoteSigner.Ready() + }() + + return readyChan +} + // MuSig2Cleanup removes a session from memory to free up resources. func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { req := &signrpc.MuSig2CleanupRequest{ @@ -876,7 +879,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.remoteSigner.MuSig2Cleanup(ctxt, req) if err != nil { considerShutdown(err) return fmt.Errorf("error cleaning up MuSig2 session in remote "+ @@ -1175,7 +1178,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.remoteSigner.SignPsbt( ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}, ) if err != nil { @@ -1269,56 +1272,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/lnwallet/rpcwallet/sign_coordinator.go b/lnwallet/rpcwallet/sign_coordinator.go new file mode 100644 index 0000000000..ae6a3ffb3c --- /dev/null +++ b/lnwallet/rpcwallet/sign_coordinator.go @@ -0,0 +1,891 @@ +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") +) + +const ( + // noTimeout is a constant that can be used to indicate that no timeout + // should be enforced when waiting for a response from the remote + // signer. + noTimeout = 0 +) + +// 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 +// 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: "outboundWatchOnly", + }, + } + // 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, an error is +// returned. +func (s *SignCoordinator) WaitUntilConnected() error { + return s.waitUntilConnectedWithTimeout(s.connectionTimeout) +} + +// waitUntilConnectedWithTimeout waits until the remote signer has connected. If +// the remote signer does not connect within the given timeout, an error is +// returned. +func (s *SignCoordinator) waitUntilConnectedWithTimeout( + timeout time.Duration) error { + + select { + case <-s.clientConnected: + return nil + + case <-s.quit: + return ErrShuttingDown + + case <-time.After(timeout): + 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(requestID uint64, + timeout time.Duration) (*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 <-time.After(timeout): + 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(timeout time.Duration) (bool, error) { + req := &walletrpc.SignCoordinatorRequest_Ping{ + Ping: true, + } + + return processRequest( + s, timeout, // As we're pinging, we specify a time limit. + 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(_ context.Context, + in *signrpc.SharedKeyRequest, + _ ...grpc.CallOption) (*signrpc.SharedKeyResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_SharedKeyRequest{ + SharedKeyRequest: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.MuSig2CleanupRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2CleanupResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2CleanupRequest{ + MuSig2CleanupRequest: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.MuSig2CombineSigRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2CombineSigResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2CombineSigRequest{ + MuSig2CombineSigRequest: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.MuSig2SessionRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2SessionResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2SessionRequest{ + MuSig2SessionRequest: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.MuSig2RegisterNoncesRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2RegisterNoncesResponse, + error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2RegisterNoncesRequest{ + MuSig2RegisterNoncesRequest: in, + } + + type muSig2RegisterNoncesResp = *signrpc.MuSig2RegisterNoncesResponse + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.MuSig2SignRequest, + _ ...grpc.CallOption) (*signrpc.MuSig2SignResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_MuSig2SignRequest{ + MuSig2SignRequest: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *signrpc.SignMessageReq, + _ ...grpc.CallOption) (*signrpc.SignMessageResp, error) { + + req := &walletrpc.SignCoordinatorRequest_SignMessageReq{ + SignMessageReq: in, + } + + return processRequest( + s, noTimeout, + 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(_ context.Context, + in *walletrpc.SignPsbtRequest, + _ ...grpc.CallOption) (*walletrpc.SignPsbtResponse, error) { + + req := &walletrpc.SignCoordinatorRequest_SignPsbtRequest{ + SignPsbtRequest: in, + } + + return processRequest( + s, noTimeout, + 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](s *SignCoordinator, timeout time.Duration, + 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() + + startTime := time.Now() + + // If a timeout is enforced, we will wait for the connection using the + // specified timeout. Otherwise, we will wait for the connection using + // the configured connection timeout. + if timeout != 0 { + err = s.waitUntilConnectedWithTimeout(timeout) + } else { + err = s.WaitUntilConnected() + } + + 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 + + // If a timeout is enforced, we need to limit the entire execution time + // of this function to the timeout. Therefore, we need to calculate the + // remaining allowed execution time. + // If no timeout is enforced, we will wait for the response using the + // configured request timeout. + if timeout != 0 { + newTimeout := timeout - time.Since(startTime) + + if time.Since(startTime) > timeout { + return zero, ErrRequestTimeout + } + + resp, err = s.getResponse(reqID, newTimeout) + } else { + resp, err = s.getResponse(reqID, s.requestTimeout) + } + + 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 0000000000..e394e08c76 --- /dev/null +++ b/lnwallet/rpcwallet/sign_coordinator_test.go @@ -0,0 +1,764 @@ +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) + + 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(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) + + 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(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(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(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(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) + + // Simulate a Ping request that times out. + _, err := coordinator.Ping(1 * time.Second) + require.Equal(t, ErrRequestTimeout, 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) + + 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(1 * time.Second) + require.Equal(t, ErrRequestTimeout, 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(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) + + 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(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) + + 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(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) + + 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(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) + + 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(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) +} diff --git a/make/release_flags.mk b/make/release_flags.mk index 7bf12119d7..bb7b933f6a 100644 --- a/make/release_flags.mk +++ b/make/release_flags.mk @@ -35,6 +35,8 @@ windows-arm RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc monitoring peersrpc kvdb_postgres kvdb_etcd kvdb_sqlite +LND_SIGNER_TAGS = signrpc walletrpc monitoring kvdb_postgres kvdb_etcd kvdb_sqlite + WASM_RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc monitoring peersrpc # One can either specify a git tag as the version suffix or one is generated diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index 9bbef0414d..95aeb7771b 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,11 +75,23 @@ 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 " + "starting up, but not yet ready to accept calls") + // ErrRemoteSignerMode is returned if an RPC method is called that isn't + // whitelisted in the remoteSignerWhitelist, i.e. not allowed while the + // node acts as a remote signer. + ErrRemoteSignerMode = fmt.Errorf("the RPC method cannot be called " + + "when the node acts a remote signer") + // macaroonWhitelist defines methods that we don't require macaroons to // access. We also allow these methods to be called even if not all // mandatory middlewares are registered yet. If the wallet is locked @@ -94,6 +111,55 @@ var ( "/lnrpc.State/SubscribeState": {}, "/lnrpc.State/GetState": {}, } + + // remoteSignerWhitelist specifies the methods allowed when the node + // functions as a remote signer. + remoteSignerWhitelist = map[string]struct{}{ + // Required setup method to export the wallet's accounts to a + // watch-only node if not migrating an existing wallet to a + // watch-only version. + "/walletrpc.WalletKit/ListAccounts": {}, + + // Required methods called by watch-only node for an inbound + // remote signer. + "/walletrpc.WalletKit/SignPsbt": {}, + "/signrpc.Signer/DeriveSharedKey": {}, + "/signrpc.Signer/MuSig2Cleanup": {}, + "/signrpc.Signer/MuSig2CombineSig": {}, + "/signrpc.Signer/MuSig2CreateSession": {}, + "/signrpc.Signer/MuSig2RegisterNonces": {}, + "/signrpc.Signer/MuSig2Sign": {}, + "/signrpc.Signer/SignMessage": {}, + + // Macaroon methods. An inbound remote signer needs to create a + // macaroon for the watch-only node. + "/lnrpc.Lightning/BakeMacaroon": {}, + "/lnrpc.Lightning/ListMacaroonIDs": {}, + "/lnrpc.Lightning/DeleteMacaroonID": {}, + "/lnrpc.Lightning/ListPermissions": {}, + "/lnrpc.Lightning/CheckMacaroonPermissions": {}, + + // Standard daemon methods + "/lnrpc.Lightning/StopDaemon": {}, + "/lnrpc.Lightning/DebugLevel": {}, + "/verrpc.Versioner/GetVersion": {}, + "/lnrpc.Lightning/GetInfo": {}, + "/lnrpc.Lightning/GetDebugInfo": {}, + "/lnrpc.Lightning/GetNetworkInfo": {}, + + "/lnrpc.Lightning/VerifyMessage": {}, + + // Add the ability to add RPCMiddleware interception for the + // remote signer. + lnrpc.RegisterRPCMiddlewareURI: {}, + } + + // 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": {}, + } ) // InterceptorChain is a struct that can be added to the running GRPC server, @@ -157,6 +223,9 @@ type InterceptorChain struct { // noMacaroons should be set true if we don't want to check macaroons. noMacaroons bool + // remoteSignerMode should be set true if lnd acts as a remote signer. + remoteSignerMode bool + // svc is the macaroon service used to enforce permissions in case // macaroons are used. svc *macaroons.Service @@ -198,13 +267,14 @@ type InterceptorChain struct { var _ lnrpc.StateServer = (*InterceptorChain)(nil) // NewInterceptorChain creates a new InterceptorChain. -func NewInterceptorChain(log btclog.Logger, noMacaroons bool, +func NewInterceptorChain(log btclog.Logger, noMacaroons bool, remoteSigner bool, mandatoryMiddleware []string) *InterceptorChain { return &InterceptorChain{ state: waitingToStart, ntfnServer: subscribe.NewServer(), noMacaroons: noMacaroons, + remoteSignerMode: remoteSigner, permissionMap: make(map[string][]bakery.Op), rpcsLog: log, registeredMiddlewareNames: make(map[string]int), @@ -265,7 +335,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() @@ -274,7 +355,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() @@ -298,6 +379,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 +791,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 +836,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: @@ -759,6 +860,15 @@ func (r *InterceptorChain) checkRPCState(srv interface{}) error { return ErrWalletUnlocked } + if r.remoteSignerMode { + // As we only allow certain calls to the remote signer, + // as only limited functionality is required. + _, ok = remoteSignerWhitelist[fullMethod] + if !ok { + return ErrRemoteSignerMode + } + } + default: return fmt.Errorf("unknown RPC state: %v", state) } @@ -772,9 +882,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 +901,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 } diff --git a/rpcserver.go b/rpcserver.go index efb6cf54ff..861ee827ae 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -183,6 +183,10 @@ var ( Entity: "macaroon", Action: "write", }, + { + Entity: "remotesigner", + Action: "generate", + }, } // invoicePermissions is a slice of all the entities that allows a user @@ -217,7 +221,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, } @@ -594,7 +598,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. @@ -678,9 +681,106 @@ 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. The function populates the wallet +// sub-server configuration with the remote signer values, 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 { + + 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) + + // 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 values. + err := subServerCgs.PopulateRemoteSignerCfgValues( + 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 + // 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(s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone, @@ -771,14 +871,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, 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( @@ -795,70 +888,24 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, 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 @@ -912,28 +959,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/sample-lnd.conf b/sample-lnd.conf index 6af5e4b578..70063d7e54 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1660,27 +1660,54 @@ ; private keys. ; remotesigner.enable=false -; The remote signer's RPC host:port. +; The role of the lnd node in a remote signer setup. +; Default: +; remotesigner.signerrole=watchonly-inbound +; Example: +; remotesigner.signerrole=watchonly-outbound + +; The remote signer's or watch-only node's RPC host:port. ; 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 or the +; watch-only node. ; Default: ; remotesigner.macaroonpath= ; Example: ; remotesigner.macaroonpath=/path/to/remote/signer/admin.macaroon -; The TLS certificate to use for establishing the remote signer's identity. +; The TLS certificate to use for establishing the remote signer's or the +; watch-only node's identity. ; Default: ; remotesigner.tlscertpath= ; 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 or watch-only node. +; Valid time units are {s, m, h}. +; Default: +; remotesigner.timeout=5s +; Example: +; remotesigner.timeout=2m + +; The time we will wait when making requests to the remote signer or watch-only +; node. ; Valid time units are {s, m, h}. -; remotesigner.timeout=5s +; Default: +; remotesigner.requesttimeout=5s +; Example: +; remotesigner.requesttimeout=30s + +; The time the watch-only node with signerrole 'watchonly-outbound' 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. diff --git a/scripts/release.sh b/scripts/release.sh index 42a5f7d4e3..7f9121f02c 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -11,7 +11,8 @@ set -e LND_VERSION_REGEX="lnd version (.+) commit" PKG="github.com/lightningnetwork/lnd" -PACKAGE=lnd +LND_PACKAGE=lnd +SIGNER_PACKAGE=lndsigner # Needed for setting file timestamps to get reproducible archives. BUILD_DATE="2020-01-01 00:00:00" @@ -127,15 +128,50 @@ function check_tag_correct() { fi } +# build_package builds release binaries for the passed package and lncli, for +# the passed environment. +# arguments: +# +function build_package() { + local package_name=$1 + local index=$2 + local tag=$3 + local os=$4 + local arch=$5 + local arm=$6 + local build_tags=$7 + local ldflags=$8 + + dir="${package_name}-${index}-${tag}" + mkdir "${dir}" + pushd "${dir}" + + green " - Building ${package_name}: ${os} ${arch} ${arm} with build tags '${build_tags}'" + env GOEXPERIMENT=loopvar CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${build_tags}" ${PKG}/cmd/${package_name} + env GOEXPERIMENT=loopvar CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${build_tags}" ${PKG}/cmd/lncli + popd + + # Add the hashes for the individual binaries as well for easy verification + # of a single installed binary. + shasum -a 256 "${dir}/"* >> "manifest-$tag.txt" + + if [[ $os == "windows" ]]; then + reproducible_zip "${dir}" + else + reproducible_tar_gzip "${dir}" + fi +} + # build_release builds the actual release binaries. -# arguments: -# +# arguments: +# function build_release() { local tag=$1 local sys=$2 - local buildtags=$3 - local ldflags=$4 - local goversion=$5 + local lndbuildtags=$3 + local signerbuildtags=$4 + local ldflags=$5 + local goversion=$6 # Check if the active Go version matches the specified Go version. active_go_version=$(go version | awk '{print $3}' | sed 's/go//') @@ -151,13 +187,13 @@ required Go version ($goversion)." go mod vendor reproducible_tar_gzip vendor - maindir=$PACKAGE-$tag + maindir=$LND_PACKAGE-$tag mkdir -p $maindir mv vendor.tar.gz "${maindir}/" # Don't use tag in source directory, otherwise our file names get too long and # tar starts to package them non-deterministically. - package_source="${PACKAGE}-source" + package_source="${LND_PACKAGE}-source" # The git archive command doesn't support setting timestamps and file # permissions. That's why we unpack the tar again, then use our reproducible @@ -184,28 +220,15 @@ required Go version ($goversion)." arm=7 fi - dir="${PACKAGE}-${i}-${tag}" - mkdir "${dir}" - pushd "${dir}" + # Build lnd package + build_package "${LND_PACKAGE}" "${i}" "${tag}" "${os}" "${arch}" "${arm}" "${lndbuildtags}" "${ldflags}" - green " - Building: ${os} ${arch} ${arm} with build tags '${buildtags}'" - env GOEXPERIMENT=loopvar CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${buildtags}" ${PKG}/cmd/lnd - env GOEXPERIMENT=loopvar CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${buildtags}" ${PKG}/cmd/lncli - popd - - # Add the hashes for the individual binaries as well for easy verification - # of a single installed binary. - shasum -a 256 "${dir}/"* >> "manifest-$tag.txt" - - if [[ $os == "windows" ]]; then - reproducible_zip "${dir}" - else - reproducible_tar_gzip "${dir}" - fi + # Build lndsigner package + build_package "${SIGNER_PACKAGE}" "${i}" "${tag}" "${os}" "${arch}" "${arm}" "${signerbuildtags}" "${ldflags}" done # Add the hash of the packages too, then sort by the second column (name). - shasum -a 256 lnd-* vendor* >> "manifest-$tag.txt" + shasum -a 256 "${LND_PACKAGE}"-* "${SIGNER_PACKAGE}"-* vendor* >> "manifest-$tag.txt" LC_ALL=C sort -k2 -o "manifest-$tag.txt" "manifest-$tag.txt" cat "manifest-$tag.txt" } diff --git a/server.go b/server.go index c131759124..2669172aba 100644 --- a/server.go +++ b/server.go @@ -317,6 +317,8 @@ type server struct { tlsManager *TLSManager + remoteSignerClient rpcwallet.RemoteSignerClient + // featureMgr dispatches feature vectors for various contexts within the // daemon. featureMgr *feature.Manager @@ -500,8 +502,8 @@ func newServer(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 @@ -650,6 +652,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, tlsManager: tlsManager, + remoteSignerClient: remoteSignerClient, + featureMgr: featureMgr, quit: make(chan struct{}), } @@ -1938,27 +1942,31 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl, // If remote signing is enabled, add the healthcheck for the remote // signing RPC interface. if s.cfg.RemoteSigner != nil && s.cfg.RemoteSigner.Enable { - // 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 - - remoteSignerConnectionCheck := healthcheck.NewObservation( - "remote signer connection", - rpcwallet.HealthCheck( - s.cfg.RemoteSigner, - - // 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, - ), - cfg.HealthChecks.RemoteSigner.Interval, - cfg.HealthChecks.RemoteSigner.Timeout+overhead, - cfg.HealthChecks.RemoteSigner.Backoff, - cfg.HealthChecks.RemoteSigner.Attempts, - ) - checks = append(checks, remoteSignerConnectionCheck) + if rpckKeyRing, ok := cc.Wc.(*rpcwallet.RPCKeyRing); ok { + timeout := 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. + outerTimeout := timeout + time.Millisecond*10 + + rsConnectionCheck := healthcheck.NewObservation( + "remote signer connection", + rpcwallet.HealthCheck( + rpckKeyRing.RemoteSigner(), + // For the health check we might to be + // even stricter than the initial/normal + // connect, so we use the health check + // timeout. + timeout, + ), + cfg.HealthChecks.RemoteSigner.Interval, + outerTimeout, + cfg.HealthChecks.RemoteSigner.Backoff, + cfg.HealthChecks.RemoteSigner.Attempts, + ) + checks = append(checks, rsConnectionCheck) + } } // If we have a leader elector, we add a health check to ensure we are @@ -2077,6 +2085,12 @@ func (s *server) Start() error { } } + cleanup = cleanup.add(s.remoteSignerClient.Stop) + if err := s.remoteSignerClient.Start(); 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 @@ -2542,6 +2556,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) diff --git a/subrpcserver_config.go b/subrpcserver_config.go index a4ee6d1a16..d172b7450f 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -26,6 +26,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" @@ -100,8 +101,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, @@ -210,6 +211,10 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(chanStateDB), ) + // The "RemoteSigner" 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) @@ -376,6 +381,30 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, return nil } +// PopulateRemoteSignerCfgValues populates the WalletKit sub-server config with +// the remote signer items, given that the remote signer is enabled. +func (s *subRPCServerConfigs) PopulateRemoteSignerCfgValues(cfg *Config, + cc *chainreg.ChainControl) error { + + // Only populate the config values with the remote signer if it's + // enabled. + if cfg.RemoteSigner == nil || !cfg.RemoteSigner.Enable { + return nil + } + + // Extract the WalletKit sub-server config, and populate the config with + // the remote signer values. + subCfgValue := extractReflectValue(s.WalletKitRPC) + + if rpckKeyRing, ok := cc.Wc.(*rpcwallet.RPCKeyRing); ok { + subCfgValue.FieldByName("RemoteSigner").Set( + reflect.ValueOf(rpckKeyRing.RemoteSigner()), + ) + } + + 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.