diff --git a/cli/server/server.go b/cli/server/server.go index bd76e49fc..5aad55f77 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -73,6 +73,13 @@ func NewCommands() []cli.Command { Usage: "use if dump is incremental", }, ) + var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1) + copy(cfgHeightFlags, cfgFlags) + cfgHeightFlags[len(cfgHeightFlags)-1] = cli.UintFlag{ + Name: "height", + Usage: "Height of the state to reset DB to", + Required: true, + } return []cli.Command{ { Name: "node", @@ -99,6 +106,13 @@ func NewCommands() []cli.Command { Action: restoreDB, Flags: cfgCountInFlags, }, + { + Name: "reset", + Usage: "reset database to the previous state", + UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t]", + Action: resetDB, + Flags: cfgHeightFlags, + }, }, }, } @@ -302,6 +316,35 @@ func restoreDB(ctx *cli.Context) error { return nil } +func resetDB(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.NewExitError(err, 1) + } + h := uint32(ctx.Uint("height")) + + log, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) + if err != nil { + return cli.NewExitError(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + chain, err := initBlockChain(cfg, log) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to create Blockchain instance: %w", err), 1) + } + + err = chain.Reset(h) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1) + } + return nil +} + func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*oracle.Oracle, error) { if !config.Enabled { return nil, nil diff --git a/cli/server/server_test.go b/cli/server/server_test.go index d311db08e..ca5d3540a 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -351,3 +351,18 @@ func TestInitBlockChain(t *testing.T) { require.Error(t, err) }) } + +func TestResetDB(t *testing.T) { + d := t.TempDir() + err := os.Chdir(d) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + set.Bool("privnet", true, "") + set.Bool("debug", true, "") + set.Int("height", 0, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + err = resetDB(ctx) + require.NoError(t, err) +} diff --git a/docs/cli.md b/docs/cli.md index 53e8a4034..2f480771e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -89,13 +89,23 @@ Typical scenarios when this can be useful (without full node restart): * updating TLS certificates for the RPC server * resolving operational issues -### DB import/exports +### DB import/exports/reset Node operates using some database as a backend to store blockchain data. NeoGo allows to dump chain into a file from the database (when node is stopped) or to import blocks from a file into the database (also when node is stopped). Use `db` command for that. +NeoGo allows to reset the node state to a particular point. It is possible for +those nodes that do store complete chain state or for nodes with `RemoveUntraceableBlocks` +setting on that are not yet reached `MaxTraceableBlocks` number of blocks. Use +`db reset` command with the target block specified to roll back all the changes +made since the target block (not including changes made by the specified block +acceptance). The set of changes to be removed includes blocks, transactions, +execution results, contract storage changes, MPT-related auxiliary data and NEP +transfers data. Some stale MPT nodes may be left in storage after reset. +Once DB reset is finished, the node can be started in a regular manner. + ## Smart contracts Use `contract` command to create/compile/deploy/invoke/debug smart contracts,