diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 4f3ddc142..42a2f5194 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,7 +19,7 @@ Special thanks to external contributors on this release: - [rpc] \#7713 Remove unused options for websocket clients. (@creachadair) - [config] \#7930 Add new event subscription options and defaults. (@creachadair) - [rpc] \#7982 Add new Events interface and deprecate Subscribe. (@creachadair) - - [cli] \#8081 make the reset command safe to use. (@marbar3778) + - [cli] \#8081 make the reset command safe to use by intoducing `reset-state` command. Fixed by \#8259. (@marbar3778, @cmwaters) - [config] \#8222 default indexer configuration to null. (@creachadair) - Apps diff --git a/UPGRADING.md b/UPGRADING.md index 199a5f83c..a67d9dfb2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -126,6 +126,25 @@ lays out the reasoning for the changes as well as [RFC 009](https://tinyurl.com/rfc009) for a discussion of the complexities of upgrading consensus parameters. +### CLI Changes + +The functionality around resetting a node has been extended to make it safer. The +`unsafe-reset-all` command has been replaced by a `reset` command with four +subcommands: `blockchain`, `peers`, `unsafe-signer` and `unsafe-all`. + +- `tendermint reset blockchain`: Clears a node of all blocks, consensus state, evidence, + and indexed transactions. NOTE: This command does not reset application state. + If you need to rollback the last application state (to recover from application + nondeterminism), see instead the `tendermint rollback` command. +- `tendermint reset peers`: Clears the peer store, which persists information on peers used + by the networking layer. This can be used to get rid of stale addresses or to switch + to a predefined set of static peers. +- `tendermint reset unsafe-signer`: Resets the watermark level of the PrivVal File signer + allowing it to sign votes from the genesis height. This should only be used in testing as + it can lead to the node double signing. +- `tendermint reset unsafe-all`: A summation of the other three commands. This will delete + the entire `data` directory which may include application data as well. + ## v0.35 ### ABCI Changes diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset.go similarity index 53% rename from cmd/tendermint/commands/reset_priv_validator.go rename to cmd/tendermint/commands/reset.go index 8236b2bd4..332ed2a4d 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset.go @@ -13,81 +13,92 @@ import ( "github.com/tendermint/tendermint/types" ) -// MakeResetAllCommand constructs a command that removes the database of +// MakeResetCommand constructs a command that removes the database of // the specified Tendermint core instance. -func MakeResetAllCommand(conf *config.Config, logger log.Logger) *cobra.Command { +func MakeResetCommand(conf *config.Config, logger log.Logger) *cobra.Command { var keyType string - cmd := &cobra.Command{ - Use: "unsafe-reset-all", - Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", + resetCmd := &cobra.Command{ + Use: "reset", + Short: "Set of commands to conveniently reset tendermint related data", + } + + resetBlocksCmd := &cobra.Command{ + Use: "blockchain", + Short: "Removes all blocks, state, transactions and evidence stored by the tendermint node", RunE: func(cmd *cobra.Command, args []string) error { - return resetAll(conf.DBDir(), conf.PrivValidator.KeyFile(), + return ResetState(conf.DBDir(), logger) + }, + } + + resetPeersCmd := &cobra.Command{ + Use: "peers", + Short: "Removes all peer addresses", + RunE: func(cmd *cobra.Command, args []string) error { + return ResetPeerStore(conf.DBDir()) + }, + } + + resetSignerCmd := &cobra.Command{ + Use: "unsafe-signer", + Short: "esets private validator signer state", + Long: `Resets private validator signer state. +Only use in testing. This can cause the node to double sign`, + RunE: func(cmd *cobra.Command, args []string) error { + return ResetFilePV(conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType) + }, + } + + resetAllCmd := &cobra.Command{ + Use: "unsafe-all", + Short: "Removes all tendermint data including signing state", + Long: `Removes all tendermint data including signing state. +Only use in testing. This can cause the node to double sign`, + RunE: func(cmd *cobra.Command, args []string) error { + return ResetAll(conf.DBDir(), conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType) }, } - cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, - "Key type to generate privval file with. Options: ed25519, secp256k1") - return cmd + resetSignerCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, + "Signer key type. Options: ed25519, secp256k1") + + resetAllCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, + "Signer key type. Options: ed25519, secp256k1") + + resetCmd.AddCommand(resetBlocksCmd) + resetCmd.AddCommand(resetPeersCmd) + resetCmd.AddCommand(resetSignerCmd) + resetCmd.AddCommand(resetAllCmd) + + return resetCmd } -// MakeResetStateCommand constructs a command that removes the database of -// the specified Tendermint core instance. -func MakeResetStateCommand(conf *config.Config, logger log.Logger) *cobra.Command { - var keyType string - - return &cobra.Command{ - Use: "reset-state", - Short: "Remove all the data and WAL", - RunE: func(cmd *cobra.Command, args []string) error { - return resetState(conf.DBDir(), logger, keyType) - }, - } -} - -func MakeResetPrivateValidatorCommand(conf *config.Config, logger log.Logger) *cobra.Command { - var keyType string - - cmd := &cobra.Command{ - Use: "unsafe-reset-priv-validator", - Short: "(unsafe) Reset this node's validator to genesis state", - RunE: func(cmd *cobra.Command, args []string) error { - return resetFilePV(conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType) - }, - } - - cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, - "Key type to generate privval file with. Options: ed25519, secp256k1") - return cmd - -} - -// XXX: this is totally unsafe. -// it's only suitable for testnets. - -// XXX: this is totally unsafe. -// it's only suitable for testnets. - -// resetAll removes address book files plus all data, and resets the privValdiator data. -func resetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { +// ResetAll removes address book files plus all data, and resets the privValdiator data. +// Exported for extenal CLI usage +// XXX: this is unsafe and should only suitable for testnets. +func ResetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { if err := os.RemoveAll(dbDir); err == nil { logger.Info("Removed all blockchain history", "dir", dbDir) } else { logger.Error("error removing all blockchain history", "dir", dbDir, "err", err) } - return resetFilePV(privValKeyFile, privValStateFile, logger, keyType) + if err := tmos.EnsureDir(dbDir, 0700); err != nil { + logger.Error("unable to recreate dbDir", "err", err) + } + + // recreate the dbDir since the privVal state needs to live there + return ResetFilePV(privValKeyFile, privValStateFile, logger, keyType) } -// resetState removes address book files plus all databases. -func resetState(dbDir string, logger log.Logger, keyType string) error { +// ResetState removes all blocks, tendermint state, indexed transactions and evidence. +func ResetState(dbDir string, logger log.Logger) error { blockdb := filepath.Join(dbDir, "blockstore.db") state := filepath.Join(dbDir, "state.db") wal := filepath.Join(dbDir, "cs.wal") evidence := filepath.Join(dbDir, "evidence.db") txIndex := filepath.Join(dbDir, "tx_index.db") - peerstore := filepath.Join(dbDir, "peerstore.db") if tmos.FileExists(blockdb) { if err := os.RemoveAll(blockdb); err == nil { @@ -129,20 +140,13 @@ func resetState(dbDir string, logger log.Logger, keyType string) error { } } - if tmos.FileExists(peerstore) { - if err := os.RemoveAll(peerstore); err == nil { - logger.Info("Removed peerstore.db", "dir", peerstore) - } else { - logger.Error("error removing peerstore.db", "dir", peerstore, "err", err) - } - } - if err := tmos.EnsureDir(dbDir, 0700); err != nil { - logger.Error("unable to recreate dbDir", "err", err) - } - return nil + return tmos.EnsureDir(dbDir, 0700) } -func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { +// ResetFilePV loads the file private validator and resets the watermark to 0. If used on an existing network, +// this can cause the node to double sign. +// XXX: this is unsafe and should only suitable for testnets. +func ResetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { if _, err := os.Stat(privValKeyFile); err == nil { pv, err := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile) if err != nil { @@ -166,3 +170,13 @@ func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, key } return nil } + +// ResetPeerStore removes the peer store containing all information used by the tendermint networking layer +// In the case of a reset, new peers will need to be set either via the config or through the discovery mechanism +func ResetPeerStore(dbDir string) error { + peerstore := filepath.Join(dbDir, "peerstore.db") + if tmos.FileExists(peerstore) { + return os.RemoveAll(peerstore) + } + return nil +} diff --git a/cmd/tendermint/commands/reset_test.go b/cmd/tendermint/commands/reset_test.go new file mode 100644 index 000000000..7abdfa688 --- /dev/null +++ b/cmd/tendermint/commands/reset_test.go @@ -0,0 +1,62 @@ +package commands + +import ( + "context" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +func Test_ResetAll(t *testing.T) { + config := cfg.TestConfig() + dir := t.TempDir() + config.SetRoot(dir) + logger := log.NewNopLogger() + cfg.EnsureRoot(dir) + require.NoError(t, initFilesWithConfig(context.Background(), config, logger, types.ABCIPubKeyTypeEd25519)) + pv, err := privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + pv.LastSignState.Height = 10 + require.NoError(t, pv.Save()) + require.NoError(t, ResetAll(config.DBDir(), config.PrivValidator.KeyFile(), + config.PrivValidator.StateFile(), logger, types.ABCIPubKeyTypeEd25519)) + require.DirExists(t, config.DBDir()) + require.NoFileExists(t, filepath.Join(config.DBDir(), "block.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "state.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "evidence.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "tx_index.db")) + require.FileExists(t, config.PrivValidator.StateFile()) + pv, err = privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + require.Equal(t, int64(0), pv.LastSignState.Height) +} + +func Test_ResetState(t *testing.T) { + config := cfg.TestConfig() + dir := t.TempDir() + config.SetRoot(dir) + logger := log.NewNopLogger() + cfg.EnsureRoot(dir) + require.NoError(t, initFilesWithConfig(context.Background(), config, logger, types.ABCIPubKeyTypeEd25519)) + pv, err := privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + pv.LastSignState.Height = 10 + require.NoError(t, pv.Save()) + require.NoError(t, ResetState(config.DBDir(), logger)) + require.DirExists(t, config.DBDir()) + require.NoFileExists(t, filepath.Join(config.DBDir(), "block.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "state.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "evidence.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "tx_index.db")) + require.FileExists(t, config.PrivValidator.StateFile()) + pv, err = privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + // private validator state should still be in tact. + require.Equal(t, int64(10), pv.LastSignState.Height) +} diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index 84f7b386e..715e8e1d4 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -33,9 +33,7 @@ func main() { commands.MakeLightCommand(conf, logger), commands.MakeReplayCommand(conf, logger), commands.MakeReplayConsoleCommand(conf, logger), - commands.MakeResetAllCommand(conf, logger), - commands.MakeResetStateCommand(conf, logger), - commands.MakeResetPrivateValidatorCommand(conf, logger), + commands.MakeResetCommand(conf, logger), commands.MakeShowValidatorCommand(conf, logger), commands.MakeTestnetFilesCommand(conf, logger), commands.MakeShowNodeIDCommand(conf), diff --git a/docs/app-dev/getting-started.md b/docs/app-dev/getting-started.md index cffaec501..a480137ca 100644 --- a/docs/app-dev/getting-started.md +++ b/docs/app-dev/getting-started.md @@ -182,7 +182,7 @@ node example/counter.js In another window, reset and start `tendermint`: ```sh -tendermint unsafe-reset-all +tendermint reset unsafe-all tendermint start ```