diff --git a/ROADMAP.md b/ROADMAP.md index 728cd26cb..b57fcc7c8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -134,4 +134,14 @@ need is to move specified settings under the `P2P` section and convert time-related settings to `Duration` format). Removal of deprecated P2P related application settings is scheduled for May-June -2023 (~0.103.0 release). \ No newline at end of file +2023 (~0.103.0 release). + +## Direct UnlockWallet consensus configuration + +Top-level UnlockWallet section in ApplicationConfiguration was used as an +implicit consensus service configuration, now this setting (with Enabled flag) +is moved into a section of its own (Consensus). Old configurations are still +supported, but this support will eventually be removed. + +Removal of this compatibility code is scheduled for May-June 2023 (~0.103.0 +release). diff --git a/cli/server/server.go b/cli/server/server.go index 6f132bb52..7c2df3ef3 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -376,8 +376,8 @@ func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *cor return orc, nil } -func mkConsensus(config config.Wallet, tpb time.Duration, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (consensus.Service, error) { - if len(config.Path) == 0 { +func mkConsensus(config config.Consensus, tpb time.Duration, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (consensus.Service, error) { + if !config.Enabled { return nil, nil } srv, err := consensus.NewService(consensus.Config{ @@ -387,7 +387,7 @@ func mkConsensus(config config.Wallet, tpb time.Duration, chain *core.Blockchain ProtocolConfiguration: chain.GetConfig(), RequestTx: serv.RequestTx, StopTxFlow: serv.StopTxFlow, - Wallet: &config, + Wallet: config.UnlockWallet, TimePerBlock: tpb, }) if err != nil { @@ -476,7 +476,7 @@ func startServer(ctx *cli.Context) error { if err != nil { return cli.NewExitError(err, 1) } - dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.UnlockWallet, serverConfig.TimePerBlock, chain, serv, log) + dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log) if err != nil { return cli.NewExitError(err, 1) } @@ -609,7 +609,7 @@ Main: serv.DelConsensusService(dbftSrv) dbftSrv.Shutdown() } - dbftSrv, err = mkConsensus(cfgnew.ApplicationConfiguration.UnlockWallet, serverConfig.TimePerBlock, chain, serv, log) + dbftSrv, err = mkConsensus(cfgnew.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log) if err != nil { log.Error("failed to create consensus service", zap.Error(err)) break // Whatever happens, I'll leave it all to chance. diff --git a/config/protocol.mainnet.neofs.yml b/config/protocol.mainnet.neofs.yml index ae86c908f..6e80fe479 100644 --- a/config/protocol.mainnet.neofs.yml +++ b/config/protocol.mainnet.neofs.yml @@ -58,6 +58,11 @@ ApplicationConfiguration: AttemptConnPeers: 20 MinPeers: 5 Relay: true + Consensus: + Enabled: false + UnlockWallet: + Path: "/cn_wallet.json" + Password: "pass" Oracle: Enabled: false AllowedContentTypes: diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index 58934510a..e540125f3 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -70,6 +70,11 @@ ApplicationConfiguration: AttemptConnPeers: 20 MinPeers: 10 Relay: true + Consensus: + Enabled: false + UnlockWallet: + Path: "/cn_wallet.json" + Password: "pass" Oracle: Enabled: false AllowedContentTypes: diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index d8d9818ee..7309b64a0 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -81,6 +81,8 @@ ApplicationConfiguration: Enabled: false Addresses: - ":20014" - UnlockWallet: - Path: "/wallet4.json" - Password: "four" + Consensus: + Enabled: true + UnlockWallet: + Path: "/wallet4.json" + Password: "four" diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index 36d32f5b1..81001029b 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -80,6 +80,8 @@ ApplicationConfiguration: Pprof: Enabled: false Port: 20011 - UnlockWallet: - Path: "/wallet1.json" - Password: "one" + Consensus: + Enabled: true + UnlockWallet: + Path: "/wallet1.json" + Password: "one" diff --git a/config/protocol.privnet.docker.single.yml b/config/protocol.privnet.docker.single.yml index 7b3ff8883..805f81009 100644 --- a/config/protocol.privnet.docker.single.yml +++ b/config/protocol.privnet.docker.single.yml @@ -72,6 +72,8 @@ ApplicationConfiguration: Enabled: false Addresses: - ":20011" - UnlockWallet: - Path: "/wallet1.json" - Password: "one" + Consensus: + Enabled: true + UnlockWallet: + Path: "/wallet1.json" + Password: "one" diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index a8020fcaa..5a768a84a 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -81,6 +81,8 @@ ApplicationConfiguration: Enabled: false Addresses: - ":20013" - UnlockWallet: - Path: "/wallet3.json" - Password: "three" + Consensus: + Enabled: true + UnlockWallet: + Path: "/wallet3.json" + Password: "three" diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index c49b475f2..21d6e6e3c 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -81,6 +81,8 @@ ApplicationConfiguration: Enabled: false Addresses: - ":20012" - UnlockWallet: - Path: "/wallet2.json" - Password: "two" + Consensus: + Enabled: true + UnlockWallet: + Path: "/wallet2.json" + Password: "two" diff --git a/config/protocol.privnet.yml b/config/protocol.privnet.yml index b9af738c5..d8a5a17e8 100644 --- a/config/protocol.privnet.yml +++ b/config/protocol.privnet.yml @@ -49,6 +49,11 @@ ApplicationConfiguration: AttemptConnPeers: 5 MinPeers: 3 Relay: true + Consensus: + Enabled: false + UnlockWallet: + Path: "/cn_wallet.json" + Password: "pass" P2PNotary: Enabled: false UnlockWallet: diff --git a/config/protocol.testnet.neofs.yml b/config/protocol.testnet.neofs.yml index e1573bf7b..53e0451d1 100644 --- a/config/protocol.testnet.neofs.yml +++ b/config/protocol.testnet.neofs.yml @@ -58,6 +58,11 @@ ApplicationConfiguration: AttemptConnPeers: 20 MinPeers: 5 Relay: true + Consensus: + Enabled: false + UnlockWallet: + Path: "/cn_wallet.json" + Password: "pass" Oracle: Enabled: false AllowedContentTypes: diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index 7134ffbfd..48f191afd 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -73,6 +73,11 @@ ApplicationConfiguration: AttemptConnPeers: 20 MinPeers: 10 Relay: true + Consensus: + Enabled: false + UnlockWallet: + Path: "/cn_wallet.json" + Password: "pass" Oracle: Enabled: false AllowedContentTypes: diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml index 298cce8e7..4463764eb 100644 --- a/config/protocol.unit_testnet.single.yml +++ b/config/protocol.unit_testnet.single.yml @@ -44,7 +44,9 @@ ApplicationConfiguration: MaxPeers: 10 AttemptConnPeers: 5 Relay: true - UnlockWallet: + Consensus: + Enabled: true + UnlockWallet: Path: "../testdata/wallet1_solo.json" Password: "one" P2PNotary: diff --git a/docs/consensus.md b/docs/consensus.md index 7b3cc20ee..3c9e6322f 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -48,9 +48,11 @@ neo-go binary). Add the following subsection to `ApplicationConfiguration` section of your configuration file (`protocol.mainnet.yml` or `protocol.testnet.yml`): ``` - UnlockWallet: - Path: "wallet.json" - Password: "welcometotherealworld" + Consensus: + Enabled: true + UnlockWallet: + Path: "wallet.json" + Password: "welcometotherealworld" ``` where `wallet.json` is a path to your NEP-6 wallet and `welcometotherealworld` is a password to your CN key. Run the node in a regular way after that: @@ -71,6 +73,15 @@ your expectations. Details on various configuration options are provided in the [node configuration documentation](node-configuration.md), CLI commands are provided in the [CLI documentation](cli.md). +Consensus service can also run in watch-only mode when the node will +receive/process/log dBFT messages generated by other nodes, but won't be able +to generate any. It's mostly useful for debugging/monitoring. To enable this +mode just drop the `UnlockWallet` section from the configuration like this: +``` + Consensus: + Enabled: true +``` + ### Registration To register as a candidate, use neo-go as CLI command with an external RPC @@ -174,8 +185,10 @@ place the corresponding config named `protocol.privnet.yml` there. 2. Edit configuration file for every node. Examples can be found at `config/protocol.privnet.docker.one.yml` (`two`, `three` etc.). - 1. Add `UnlockWallet` section with `Path` and `Password` strings for NEP-6 - wallet path and the password for the account to be used for the consensus node. + 1. Add `Consensus` section with `Enabled: true` field and an + `UnlockWallet` subsection with `Path` and `Password` strings for NEP-6 + wallet path and the password for the account to be used for the + consensus node. 2. Make sure that your `MinPeers` setting is equal to the number of nodes participating in consensus. This requirement is needed for nodes to correctly diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 5778548fc..8651e10d4 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -37,9 +37,10 @@ node-related settings described in the table below. | Prometheus | [Metrics Services Configuration](#Metrics-Services-Configuration) | | Configuration for Prometheus (monitoring system). See the [Metrics Services Configuration](#Metrics-Services-Configuration) section for details | | ProtoTickInterval | `int64` | `5` | Duration in seconds between protocol ticks with each connected peer. Warning: this field is deprecated and moved to `P2P` section. | | Relay | `bool` | `true` | Determines whether the server is forwarding its inventory. | +| Consensus | [Consensus Configuration](#Consensus-Configuration) | | Describes consensus (dBFT) configuration. See the [Consensus Configuration](#Consensus-Configuration) for details. | | RPC | [RPC Configuration](#RPC-Configuration) | | Describes [RPC subsystem](rpc.md) configuration. See the [RPC Configuration](#RPC-Configuration) for details. | | StateRoot | [State Root Configuration](#State-Root-Configuration) | | State root module configuration. See the [State Root Configuration](#State-Root-Configuration) section for details. | -| UnlockWallet | [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) | | Node wallet configuration used for consensus (dBFT) operation. See the [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) section for details. | +| UnlockWallet | [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) | | Node wallet configuration used for consensus (dBFT) operation. See the [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) section for details. This section is deprecated and replaced by Consensus, it only exists for compatibility with old configuration files, but will be removed in future node versions. | ### P2P Configuration @@ -293,6 +294,26 @@ where: [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) section for structure details. +### Consensus Configuration + +`Consensus` configuration section describes configuration for dBFT node +module and has the following structure: +``` +Consensus: + Enabled: false + UnlockWallet: + Path: "/consensus_node_wallet.json" + Password: "pass" +``` +where: +- `Enabled` denotes whether dBFT module is active. +- `UnlockWallet` is a consensus node wallet configuration, see the + [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) section for + structure details. + +Please, refer to the [consensus node documentation](./consensus.md) for more +details on consensus node setup. + ### Unlock Wallet Configuration `UnlockWallet` configuration section contains wallet settings and has the following diff --git a/internal/testcli/executor.go b/internal/testcli/executor.go index 568146306..440704f0b 100644 --- a/internal/testcli/executor.go +++ b/internal/testcli/executor.go @@ -158,7 +158,7 @@ func NewTestChain(t *testing.T, f func(*config.Config), run bool) (*core.Blockch ProtocolConfiguration: chain.GetConfig(), RequestTx: netSrv.RequestTx, StopTxFlow: netSrv.StopTxFlow, - Wallet: &cfg.ApplicationConfiguration.UnlockWallet, + Wallet: cfg.ApplicationConfiguration.Consensus.UnlockWallet, TimePerBlock: serverConfig.TimePerBlock, }) require.NoError(t, err) diff --git a/pkg/config/application_config.go b/pkg/config/application_config.go index 8cf5a2756..85c520cdc 100644 --- a/pkg/config/application_config.go +++ b/pkg/config/application_config.go @@ -43,6 +43,7 @@ type ApplicationConfiguration struct { // Deprecated: this option is moved to the P2P section. ProtoTickInterval int64 `yaml:"ProtoTickInterval"` Relay bool `yaml:"Relay"` + Consensus Consensus `yaml:"Consensus"` RPC RPC `yaml:"RPC"` UnlockWallet Wallet `yaml:"UnlockWallet"` Oracle OracleConfiguration `yaml:"Oracle"` diff --git a/pkg/config/config.go b/pkg/config/config.go index 345d71503..ab6a4c5f6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -42,7 +42,8 @@ func Load(path string, netMode netmode.Magic) (Config, error) { return LoadFile(configPath) } -// LoadFile loads config from the provided path. +// LoadFile loads config from the provided path. It also applies backwards compatibility +// fixups if necessary. func LoadFile(configPath string) (Config, error) { if _, err := os.Stat(configPath); os.IsNotExist(err) { return Config{}, fmt.Errorf("config '%s' doesn't exist", configPath) @@ -72,6 +73,11 @@ func LoadFile(configPath string) (Config, error) { return Config{}, fmt.Errorf("failed to unmarshal config YAML: %w", err) } + if len(config.ApplicationConfiguration.UnlockWallet.Path) > 0 && len(config.ApplicationConfiguration.Consensus.UnlockWallet.Path) == 0 { + config.ApplicationConfiguration.Consensus.UnlockWallet = config.ApplicationConfiguration.UnlockWallet + config.ApplicationConfiguration.Consensus.Enabled = true + } + err = config.ProtocolConfiguration.Validate() if err != nil { return Config{}, err diff --git a/pkg/config/consensus.go b/pkg/config/consensus.go new file mode 100644 index 000000000..cdf5f36c9 --- /dev/null +++ b/pkg/config/consensus.go @@ -0,0 +1,4 @@ +package config + +// Consensus contains consensus service configuration. +type Consensus InternalService diff --git a/pkg/config/internal_service.go b/pkg/config/internal_service.go new file mode 100644 index 000000000..c8c49f11b --- /dev/null +++ b/pkg/config/internal_service.go @@ -0,0 +1,8 @@ +package config + +// InternalService stores configuration for internal services that don't have +// any network configuration, but use a wallet and can be enabled/disabled. +type InternalService struct { + Enabled bool `yaml:"Enabled"` + UnlockWallet Wallet `yaml:"UnlockWallet"` +} diff --git a/pkg/config/notary_config.go b/pkg/config/notary_config.go index 791ed8f86..df4e51187 100644 --- a/pkg/config/notary_config.go +++ b/pkg/config/notary_config.go @@ -1,7 +1,4 @@ package config // P2PNotary stores configuration for Notary node service. -type P2PNotary struct { - Enabled bool `yaml:"Enabled"` - UnlockWallet Wallet `yaml:"UnlockWallet"` -} +type P2PNotary InternalService diff --git a/pkg/config/state_root.go b/pkg/config/state_root.go index 904c104fb..dd44f264d 100644 --- a/pkg/config/state_root.go +++ b/pkg/config/state_root.go @@ -1,7 +1,4 @@ package config // StateRoot contains state root service configuration. -type StateRoot struct { - Enabled bool `yaml:"Enabled"` - UnlockWallet Wallet `yaml:"UnlockWallet"` -} +type StateRoot InternalService diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 03978792a..f727fb7ae 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -124,8 +124,9 @@ type Config struct { StopTxFlow func() // TimePerBlock is minimal time that should pass before the next block is accepted. TimePerBlock time.Duration - // Wallet is a local-node wallet configuration. - Wallet *config.Wallet + // Wallet is a local-node wallet configuration. If the path is empty, then + // no wallet will be initialized and the service will be in watch-only mode. + Wallet config.Wallet } // NewService returns a new consensus.Service instance. @@ -154,21 +155,23 @@ func NewService(cfg Config) (Service, error) { var err error - if srv.wallet, err = wallet.NewWalletFromFile(cfg.Wallet.Path); err != nil { - return nil, err - } - - // Check that the wallet password is correct for at least one account. - var ok bool - for _, acc := range srv.wallet.Accounts { - err := acc.Decrypt(srv.Config.Wallet.Password, srv.wallet.Scrypt) - if err == nil { - ok = true - break + if len(cfg.Wallet.Path) > 0 { + if srv.wallet, err = wallet.NewWalletFromFile(cfg.Wallet.Path); err != nil { + return nil, err + } + + // Check that the wallet password is correct for at least one account. + var ok bool + for _, acc := range srv.wallet.Accounts { + err := acc.Decrypt(srv.Config.Wallet.Password, srv.wallet.Scrypt) + if err == nil { + ok = true + break + } + } + if !ok { + return nil, errors.New("no account with provided password was found") } - } - if !ok { - return nil, errors.New("no account with provided password was found") } srv.dbft = dbft.New( @@ -282,7 +285,9 @@ func (s *service) Shutdown() { s.log.Info("stopping consensus service") close(s.quit) <-s.finished - s.wallet.Close() + if s.wallet != nil { + s.wallet.Close() + } } } @@ -377,24 +382,25 @@ func (s *service) validatePayload(p *Payload) bool { } func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) { - for i := range pubs { - sh := pubs[i].(*publicKey).GetScriptHash() - acc := s.wallet.GetAccount(sh) - if acc == nil { - continue - } - - if !acc.CanSign() { - err := acc.Decrypt(s.Config.Wallet.Password, s.wallet.Scrypt) - if err != nil { - s.log.Fatal("can't unlock account", zap.String("address", address.Uint160ToString(sh))) - break + if s.wallet != nil { + for i := range pubs { + sh := pubs[i].(*publicKey).GetScriptHash() + acc := s.wallet.GetAccount(sh) + if acc == nil { + continue } + + if !acc.CanSign() { + err := acc.Decrypt(s.Config.Wallet.Password, s.wallet.Scrypt) + if err != nil { + s.log.Fatal("can't unlock account", zap.String("address", address.Uint160ToString(sh))) + break + } + } + + return i, &privateKey{PrivateKey: acc.PrivateKey()}, &publicKey{PublicKey: acc.PublicKey()} } - - return i, &privateKey{PrivateKey: acc.PrivateKey()}, &publicKey{PublicKey: acc.PublicKey()} } - return -1, nil, nil } diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index 665cbf5dc..996f147a4 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -44,6 +44,24 @@ func TestNewService(t *testing.T) { require.Equal(t, tx, txx[0]) } +func TestNewWatchingService(t *testing.T) { + bc := newTestChain(t, false) + srv, err := NewService(Config{ + Logger: zaptest.NewLogger(t), + Broadcast: func(*npayload.Extensible) {}, + Chain: bc, + ProtocolConfiguration: bc.GetConfig(), + RequestTx: func(...util.Uint256) {}, + StopTxFlow: func() {}, + TimePerBlock: bc.GetConfig().TimePerBlock, + // No wallet provided. + }) + require.NoError(t, err) + + require.NotPanics(t, srv.Start) + require.NotPanics(t, srv.Shutdown) +} + func initServiceNextConsensus(t *testing.T, newAcc *wallet.Account, offset uint32) (*service, *wallet.Account) { acc, err := wallet.NewAccountFromWIF(testchain.WIF(testchain.IDToOrder(0))) require.NoError(t, err) @@ -481,7 +499,7 @@ func newTestServiceWithChain(t *testing.T, bc *core.Blockchain) *service { RequestTx: func(...util.Uint256) {}, StopTxFlow: func() {}, TimePerBlock: bc.GetConfig().TimePerBlock, - Wallet: &config.Wallet{ + Wallet: config.Wallet{ Path: "./testdata/wallet1.json", Password: "one", },