diff --git a/client/app/start.go b/client/app/start.go index ed9c8f36..5cd677ef 100644 --- a/client/app/start.go +++ b/client/app/start.go @@ -184,6 +184,50 @@ func Start(ctx context.Context, cfg Config) (func(context.Context) error, error) }, nil } +// TODO: Refactor CreateApp() to be used within the Start function, as most of the code originates from there. +func CreateApp(ctx context.Context, cfg Config) *App { + privVal, err := loadPrivVal(cfg) + if err != nil { + panic(errors.Wrap(err, "load validator key")) + } + + db, err := dbm.NewDB("application", cfg.BackendType(), cfg.DataDir()) + if err != nil { + panic(errors.Wrap(err, "create db")) + } + + baseAppOpts, err := makeBaseAppOpts(cfg) + if err != nil { + panic(errors.Wrap(err, "make base app opts")) + } + + engineCl, err := newEngineClient(ctx, cfg) + if err != nil { + panic(err) + } + + //nolint:contextcheck // False positive + app, err := newApp( + newSDKLogger(ctx), + db, + engineCl, + baseAppOpts..., + ) + if err != nil { + panic(errors.Wrap(err, "create app")) + } + app.Keepers.EVMEngKeeper.SetBuildDelay(cfg.EVMBuildDelay) + app.Keepers.EVMEngKeeper.SetBuildOptimistic(cfg.EVMBuildOptimistic) + + addr, err := k1util.PubKeyToAddress(privVal.Key.PrivKey.PubKey()) + if err != nil { + panic(errors.Wrap(err, "convert validator pubkey to address")) + } + app.Keepers.EVMEngKeeper.SetValidatorAddress(addr) + + return app +} + func newCometNode(ctx context.Context, cfg *cmtcfg.Config, app *App, privVal cmttypes.PrivValidator, ) (*node.Node, error) { nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) diff --git a/client/cmd/cmd.go b/client/cmd/cmd.go index 9d742e7e..295ac820 100644 --- a/client/cmd/cmd.go +++ b/client/cmd/cmd.go @@ -3,13 +3,16 @@ package cmd import ( "context" + "fmt" + cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" "github.com/spf13/cobra" "github.com/piplabs/story/client/app" storycfg "github.com/piplabs/story/client/config" "github.com/piplabs/story/lib/buildinfo" libcmd "github.com/piplabs/story/lib/cmd" + "github.com/piplabs/story/lib/errors" "github.com/piplabs/story/lib/log" ) @@ -23,6 +26,7 @@ func New() *cobra.Command { buildinfo.NewVersionCmd(), newValidatorCmds(), newStatusCmd(), + newRollbackCmd(app.CreateApp), ) } @@ -60,3 +64,59 @@ func newRunCmd(name string, runFunc func(context.Context, app.Config) error) *co return cmd } + +// newRollbackCmd returns a new cobra command that rolls back one block of the story consensus client. +func newRollbackCmd(appCreateFunc func(context.Context, app.Config) *app.App) *cobra.Command { + storyCfg := storycfg.DefaultConfig() + logCfg := log.DefaultConfig() + + 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, _ []string) error { + ctx, err := log.Init(cmd.Context(), logCfg) + if err != nil { + return err + } + if err := libcmd.LogFlags(ctx, cmd.Flags()); err != nil { + return err + } + + cometCfg, err := parseCometConfig(ctx, storyCfg.HomeDir) + if err != nil { + return err + } + + app := appCreateFunc(ctx, app.Config{ + Config: storyCfg, + Comet: cometCfg, + }) + height, hash, err := cmtcmd.RollbackState(&cometCfg, storyCfg.RemoveBlock) + if err != nil { + return errors.Wrap(err, "failed to rollback CometBFT state") + } + + if err = app.CommitMultiStore().RollbackToVersion(height); err != nil { + return errors.Wrap(err, "failed to rollback to version") + } + + fmt.Printf("Rolled back state to height %d and hash %X", height, hash) + + return nil + }, + } + + bindRunFlags(cmd, &storyCfg) + bindRollbackFlags(cmd, &storyCfg) + log.BindFlags(cmd.Flags(), &logCfg) + + return cmd +} diff --git a/client/cmd/flags.go b/client/cmd/flags.go index b787b9dc..b0de2471 100644 --- a/client/cmd/flags.go +++ b/client/cmd/flags.go @@ -120,6 +120,10 @@ func bindStatusFlags(flags *pflag.FlagSet, cfg *StatusConfig) { libcmd.BindHomeFlag(flags, &cfg.HomeDir) } +func bindRollbackFlags(cmd *cobra.Command, cfg *config.Config) { + cmd.Flags().BoolVar(&cfg.RemoveBlock, "hard", false, "remove last block as well as state") +} + // Flag Validation func validateFlags(flags map[string]string) error { diff --git a/client/config/config.go b/client/config/config.go index c9bba36b..0a6dda76 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -160,6 +160,7 @@ type Config struct { ExternalAddress string Seeds string SeedMode bool + RemoveBlock bool // See cosmos-sdk/server/rollback.go } // ConfigFile returns the default path to the toml story config file.