From 001c96723e56c10d8ed3a820f3c89f3ee15aa0f1 Mon Sep 17 00:00:00 2001
From: marbar3778 <marbar3778@yahoo.com>
Date: Fri, 10 Jan 2025 13:40:20 +0100
Subject: [PATCH 1/4] all rollback function to replacable

---
 server/rollback.go          | 12 +++++++++---
 server/util.go              | 23 +++++++++++++++++++----
 simapp/simd/cmd/commands.go |  2 +-
 3 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/server/rollback.go b/server/rollback.go
index 7dd58bcedf64..87eb369e33a3 100644
--- a/server/rollback.go
+++ b/server/rollback.go
@@ -3,14 +3,20 @@ package server
 import (
 	"fmt"
 
-	cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
 	"github.com/spf13/cobra"
 
 	"github.com/cosmos/cosmos-sdk/server/types"
 )
 
+// 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 Rollbackable interface {
+	RollbackToVersion(ctx *Context, removeBlock bool) (int64, []byte, error)
+}
+
 // NewRollbackCmd creates a command to rollback CometBFT and multistore state by one height.
-func NewRollbackCmd[T types.Application](appCreator types.AppCreator[T]) *cobra.Command {
+func NewRollbackCmd[T types.Application, R Rollbackable](appCreator types.AppCreator[T], rollbackable R) *cobra.Command {
 	var removeBlock bool
 
 	cmd := &cobra.Command{
@@ -33,7 +39,7 @@ application.
 			}
 			app := appCreator(ctx.Logger, db, nil, ctx.Viper)
 			// rollback CometBFT state
-			height, hash, err := cmtcmd.RollbackState(ctx.Config, removeBlock)
+			height, hash, err := rollbackable.RollbackToVersion(ctx, removeBlock)
 			if err != nil {
 				return fmt.Errorf("failed to rollback CometBFT state: %w", err)
 			}
diff --git a/server/util.go b/server/util.go
index 58afe6cbab97..aed192675607 100644
--- a/server/util.go
+++ b/server/util.go
@@ -332,7 +332,7 @@ func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customCo
 }
 
 // AddCommands add server commands
-func AddCommands[T types.Application](rootCmd *cobra.Command, appCreator types.AppCreator[T], opts StartCmdOptions[T]) {
+func AddCommands[T types.Application, R Rollbackable](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
 	cometCmd := &cobra.Command{
 		Use:     "comet",
 		Aliases: []string{"cometbft", "tendermint"},
@@ -354,15 +354,30 @@ func AddCommands[T types.Application](rootCmd *cobra.Command, appCreator types.A
 		startCmd,
 		cometCmd,
 		version.NewVersionCommand(),
-		NewRollbackCmd(appCreator),
+		NewRollbackCmd(appCreator, rollbackable),
 		ModuleHashByHeightQuery(appCreator),
 	)
 }
 
+// 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)
+}
+
 // AddCommandsWithStartCmdOptions adds server commands with the provided StartCmdOptions.
 // Deprecated: Use AddCommands directly instead.
-func AddCommandsWithStartCmdOptions[T types.Application](rootCmd *cobra.Command, appCreator types.AppCreator[T], opts StartCmdOptions[T]) {
-	AddCommands(rootCmd, appCreator, opts)
+func AddCommandsWithStartCmdOptions[T types.Application, R Rollbackable](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
+	AddCommands(rootCmd, appCreator, rollbackable, opts)
 }
 
 // AddTestnetCreatorCommand allows chains to create a testnet from the state existing in their node's data directory.
diff --git a/simapp/simd/cmd/commands.go b/simapp/simd/cmd/commands.go
index 2b6e1a9032af..11792e544c7e 100644
--- a/simapp/simd/cmd/commands.go
+++ b/simapp/simd/cmd/commands.go
@@ -45,7 +45,7 @@ func initRootCmd(
 		snapshot.Cmd(newApp),
 	)
 
-	server.AddCommands(rootCmd, newApp, server.StartCmdOptions[servertypes.Application]{})
+	server.AddCommands(rootCmd, newApp, server.NewDefaultRollbackable(newApp), server.StartCmdOptions[servertypes.Application]{})
 
 	// add keybase, auxiliary RPC, query, genesis, and tx child commands
 	rootCmd.AddCommand(

From 437f6ba9457fd276e8c19a0dee2f2f5ba24b9c0f Mon Sep 17 00:00:00 2001
From: marbar3778 <marbar3778@yahoo.com>
Date: Fri, 10 Jan 2025 14:33:40 +0100
Subject: [PATCH 2/4] minor cleanup

---
 server/rollback.go |  9 +--------
 server/util.go     | 41 ++++++++++++++++++++++++-----------------
 2 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/server/rollback.go b/server/rollback.go
index 87eb369e33a3..ba486b4571af 100644
--- a/server/rollback.go
+++ b/server/rollback.go
@@ -8,15 +8,8 @@ import (
 	"github.com/cosmos/cosmos-sdk/server/types"
 )
 
-// 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 Rollbackable interface {
-	RollbackToVersion(ctx *Context, removeBlock bool) (int64, []byte, error)
-}
-
 // NewRollbackCmd creates a command to rollback CometBFT and multistore state by one height.
-func NewRollbackCmd[T types.Application, R Rollbackable](appCreator types.AppCreator[T], rollbackable R) *cobra.Command {
+func NewRollbackCmd[T types.Application, R Rollback](appCreator types.AppCreator[T], rollbackable R) *cobra.Command {
 	var removeBlock bool
 
 	cmd := &cobra.Command{
diff --git a/server/util.go b/server/util.go
index aed192675607..fbbb8b40b704 100644
--- a/server/util.go
+++ b/server/util.go
@@ -332,7 +332,7 @@ func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customCo
 }
 
 // AddCommands add server commands
-func AddCommands[T types.Application, R Rollbackable](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
+func AddCommands[T types.Application, R Rollback](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
 	cometCmd := &cobra.Command{
 		Use:     "comet",
 		Aliases: []string{"cometbft", "tendermint"},
@@ -359,24 +359,9 @@ func AddCommands[T types.Application, R Rollbackable](rootCmd *cobra.Command, ap
 	)
 }
 
-// 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)
-}
-
 // AddCommandsWithStartCmdOptions adds server commands with the provided StartCmdOptions.
 // Deprecated: Use AddCommands directly instead.
-func AddCommandsWithStartCmdOptions[T types.Application, R Rollbackable](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
+func AddCommandsWithStartCmdOptions[T types.Application, R Rollback](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
 	AddCommands(rootCmd, appCreator, rollbackable, opts)
 }
 
@@ -595,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)
+}

From 104cbcccb5cb2da4339ef65168b6b4fc6031a3ad Mon Sep 17 00:00:00 2001
From: marbar3778 <marbar3778@yahoo.com>
Date: Fri, 10 Jan 2025 16:11:33 +0100
Subject: [PATCH 3/4] different design

---
 CHANGELOG.md       |  1 +
 server/rollback.go | 46 +++++++++++++++++++++++++++++++++++++++++++++-
 server/util.go     |  2 +-
 3 files changed, 47 insertions(+), 2 deletions(-)

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 ba486b4571af..cc6ed2ea8406 100644
--- a/server/rollback.go
+++ b/server/rollback.go
@@ -3,13 +3,57 @@ package server
 import (
 	"fmt"
 
+	cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
 	"github.com/spf13/cobra"
 
 	"github.com/cosmos/cosmos-sdk/server/types"
 )
 
 // NewRollbackCmd creates a command to rollback CometBFT and multistore state by one height.
-func NewRollbackCmd[T types.Application, R Rollback](appCreator types.AppCreator[T], rollbackable R) *cobra.Command {
+func NewRollbackCmd[T types.Application](appCreator types.AppCreator[T]) *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 := cmtcmd.RollbackState(ctx.Config, 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
+}
+
+// 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{
diff --git a/server/util.go b/server/util.go
index fbbb8b40b704..0dea937c4abc 100644
--- a/server/util.go
+++ b/server/util.go
@@ -354,7 +354,7 @@ func AddCommands[T types.Application, R Rollback](rootCmd *cobra.Command, appCre
 		startCmd,
 		cometCmd,
 		version.NewVersionCommand(),
-		NewRollbackCmd(appCreator, rollbackable),
+		NewRollbackCmd(appCreator),
 		ModuleHashByHeightQuery(appCreator),
 	)
 }

From 063bae10345be4b0f9a54255a5cf0bde2420e294 Mon Sep 17 00:00:00 2001
From: Julien Robert <julien@rbrt.fr>
Date: Tue, 14 Jan 2025 09:18:32 +0100
Subject: [PATCH 4/4] revert api breaks

---
 server/util.go              | 6 +++---
 simapp/simd/cmd/commands.go | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/server/util.go b/server/util.go
index 0dea937c4abc..052614142e80 100644
--- a/server/util.go
+++ b/server/util.go
@@ -332,7 +332,7 @@ func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customCo
 }
 
 // AddCommands add server commands
-func AddCommands[T types.Application, R Rollback](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
+func AddCommands[T types.Application](rootCmd *cobra.Command, appCreator types.AppCreator[T], opts StartCmdOptions[T]) {
 	cometCmd := &cobra.Command{
 		Use:     "comet",
 		Aliases: []string{"cometbft", "tendermint"},
@@ -361,8 +361,8 @@ func AddCommands[T types.Application, R Rollback](rootCmd *cobra.Command, appCre
 
 // AddCommandsWithStartCmdOptions adds server commands with the provided StartCmdOptions.
 // Deprecated: Use AddCommands directly instead.
-func AddCommandsWithStartCmdOptions[T types.Application, R Rollback](rootCmd *cobra.Command, appCreator types.AppCreator[T], rollbackable R, opts StartCmdOptions[T]) {
-	AddCommands(rootCmd, appCreator, rollbackable, opts)
+func AddCommandsWithStartCmdOptions[T types.Application](rootCmd *cobra.Command, appCreator types.AppCreator[T], opts StartCmdOptions[T]) {
+	AddCommands(rootCmd, appCreator, opts)
 }
 
 // AddTestnetCreatorCommand allows chains to create a testnet from the state existing in their node's data directory.
diff --git a/simapp/simd/cmd/commands.go b/simapp/simd/cmd/commands.go
index 11792e544c7e..2b6e1a9032af 100644
--- a/simapp/simd/cmd/commands.go
+++ b/simapp/simd/cmd/commands.go
@@ -45,7 +45,7 @@ func initRootCmd(
 		snapshot.Cmd(newApp),
 	)
 
-	server.AddCommands(rootCmd, newApp, server.NewDefaultRollbackable(newApp), server.StartCmdOptions[servertypes.Application]{})
+	server.AddCommands(rootCmd, newApp, server.StartCmdOptions[servertypes.Application]{})
 
 	// add keybase, auxiliary RPC, query, genesis, and tx child commands
 	rootCmd.AddCommand(