diff --git a/server/rollback.go b/server/rollback.go new file mode 100644 index 000000000000..f10bcaead505 --- /dev/null +++ b/server/rollback.go @@ -0,0 +1,49 @@ +package server + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/spf13/cobra" + tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" +) + +// NewRollbackCmd creates a command to rollback tendermint and multistore state by one height. +func NewRollbackCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "rollback", + Short: "rollback cosmos-sdk and tendermint state by one height", + Long: ` +A state rollback is performed to recover from an incorrect application state transition, +when Tendermint has persisted an incorrect app hash and is thus unable to make +progress. Rollback overwrites a state at height n with the state at height n - 1. +The application also roll back to height n - 1. No blocks are removed, so upon +restarting Tendermint the transactions in block n will be re-executed against the +application. +`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := GetServerContextFromCmd(cmd) + cfg := ctx.Config + home := cfg.RootDir + db, err := openDB(home) + if err != nil { + return err + } + // rollback tendermint state + height, hash, err := tmcmd.RollbackState(ctx.Config) + if err != nil { + return fmt.Errorf("failed to rollback tendermint state: %w", err) + } + // rollback the multistore + cms := rootmulti.NewStore(db) + cms.RollbackToVersion(height) + + fmt.Printf("Rolled back state to height %d and hash %X", height, hash) + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + return cmd +} diff --git a/server/util.go b/server/util.go index e1d0e03eb181..14c1daed1581 100644 --- a/server/util.go +++ b/server/util.go @@ -283,6 +283,7 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type tendermintCmd, ExportCmd(appExport, defaultNodeHome), version.NewVersionCommand(), + NewRollbackCmd(defaultNodeHome), ) } diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 05d8631e8c05..74ef0cdb881d 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -960,6 +960,30 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo { } } +// RollbackToVersion delete the versions after `target` and update the latest version. +func (rs *Store) RollbackToVersion(target int64) int64 { + if target < 0 { + panic("Negative rollback target") + } + current := getLatestVersion(rs.db) + if target >= current { + return current + } + for ; current > target; current-- { + rs.pruneHeights = append(rs.pruneHeights, current) + } + rs.pruneStores() + + // update latest height + bz, err := gogotypes.StdInt64Marshal(current) + if err != nil { + panic(err) + } + + rs.db.Set([]byte(latestVersionKey), bz) + return current +} + type storeParams struct { key types.StoreKey db dbm.DB