diff --git a/CHANGELOG.md b/CHANGELOG.md index 37cb138cd5e3..8f84834882d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (sims) [#23013](https://github.com/cosmos/cosmos-sdk/pull/23013) Integration with app v2 * (x/auth/ante) [#23128](https://github.com/cosmos/cosmos-sdk/pull/23128) Allow custom verifyIsOnCurve when validate tx for public key like ethsecp256k1. +* (server) [#23128](https://github.com/cosmos/cosmos-sdk/pull/23128) Add custom rollback command option. In order to use it, you need to implement the Rollback interface and remove the default rollback command with `cmd.RemoveCommand(cmd.RollbackCmd)` and then add it back with `cmd.AddCommand(cmd.NewCustomRollbackCmd(appCreator, rollbackable))`. ### Improvements diff --git a/server/rollback.go b/server/rollback.go index 7dd58bcedf64..cc6ed2ea8406 100644 --- a/server/rollback.go +++ b/server/rollback.go @@ -51,3 +51,46 @@ application. cmd.Flags().BoolVar(&removeBlock, "hard", false, "remove last block as well as state") return cmd } + +// NewRollbackCmdRollback creates a command to set custom rollback functionality and multistore state by one height. +func NewRollbackCmdRollback[T types.Application, R Rollback](appCreator types.AppCreator[T], rollbackable R) *cobra.Command { + var removeBlock bool + + cmd := &cobra.Command{ + Use: "rollback", + Short: "rollback Cosmos SDK and CometBFT state by one height", + Long: ` +A state rollback is performed to recover from an incorrect application state transition, +when CometBFT 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 rolls back to height n - 1. No blocks are removed, so upon +restarting CometBFT the transactions in block n will be re-executed against the +application. +`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := GetServerContextFromCmd(cmd) + + db, err := OpenDB(ctx.Config.RootDir, GetAppDBBackend(ctx.Viper)) + if err != nil { + return err + } + app := appCreator(ctx.Logger, db, nil, ctx.Viper) + // rollback CometBFT state + height, hash, err := rollbackable.RollbackToVersion(ctx, removeBlock) + if err != nil { + return fmt.Errorf("failed to rollback CometBFT state: %w", err) + } + // rollback the multistore + + if err := app.CommitMultiStore().RollbackToVersion(height); err != nil { + return fmt.Errorf("failed to rollback to version: %w", err) + } + + fmt.Printf("Rolled back state to height %d and hash %X\n", height, hash) + return nil + }, + } + + cmd.Flags().BoolVar(&removeBlock, "hard", false, "remove last block as well as state") + return cmd +} diff --git a/server/util.go b/server/util.go index 58afe6cbab97..052614142e80 100644 --- a/server/util.go +++ b/server/util.go @@ -580,3 +580,25 @@ func GetSnapshotStore(appOpts types.AppOptions) (*snapshots.Store, error) { return snapshotStore, nil } + +// Rollbackable is an interface that allows for rollback operations. +// It is used to allow for custom rollback operations, such as those provided by the +// DefaultRollbackable implementation. +type Rollback interface { + RollbackToVersion(ctx *Context, removeBlock bool) (int64, []byte, error) +} + +// DefaultRollbackable is a default implementation of the Rollbackable interface. +type DefaultRollbackable[T types.Application] struct { + appCreator types.AppCreator[T] +} + +// NewDefaultRollbackable creates a new DefaultRollbackable instance. +func NewDefaultRollbackable[T types.Application](appCreator types.AppCreator[T]) *DefaultRollbackable[T] { + return &DefaultRollbackable[T]{appCreator} +} + +// RollbackToVersion implements the Rollbackable interface. +func (d DefaultRollbackable[T]) RollbackToVersion(ctx *Context, removeBlock bool) (int64, []byte, error) { + return cmtcmd.RollbackState(ctx.Config, removeBlock) +}