github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/cmd/cometbft/commands/rollback.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "path/filepath" 6 7 "github.com/spf13/cobra" 8 9 dbm "github.com/badrootd/nibiru-db" 10 11 cfg "github.com/badrootd/nibiru-cometbft/config" 12 "github.com/badrootd/nibiru-cometbft/libs/os" 13 "github.com/badrootd/nibiru-cometbft/state" 14 "github.com/badrootd/nibiru-cometbft/store" 15 ) 16 17 var removeBlock = false 18 19 func init() { 20 RollbackStateCmd.Flags().BoolVar(&removeBlock, "hard", false, "remove last block as well as state") 21 } 22 23 var RollbackStateCmd = &cobra.Command{ 24 Use: "rollback", 25 Short: "rollback CometBFT state by one height", 26 Long: ` 27 A state rollback is performed to recover from an incorrect application state transition, 28 when CometBFT has persisted an incorrect app hash and is thus unable to make 29 progress. Rollback overwrites a state at height n with the state at height n - 1. 30 The application should also roll back to height n - 1. If the --hard flag is not used, 31 no blocks will be removed so upon restarting CometBFT the transactions in block n will be 32 re-executed against the application. Using --hard will also remove block n. This can 33 be done multiple times. 34 `, 35 RunE: func(cmd *cobra.Command, args []string) error { 36 height, hash, err := RollbackState(config, removeBlock) 37 if err != nil { 38 return fmt.Errorf("failed to rollback state: %w", err) 39 } 40 41 if removeBlock { 42 fmt.Printf("Rolled back both state and block to height %d and hash %X\n", height, hash) 43 } else { 44 fmt.Printf("Rolled back state to height %d and hash %X\n", height, hash) 45 } 46 47 return nil 48 }, 49 } 50 51 // RollbackState takes the state at the current height n and overwrites it with the state 52 // at height n - 1. Note state here refers to CometBFT state not application state. 53 // Returns the latest state height and app hash alongside an error if there was one. 54 func RollbackState(config *cfg.Config, removeBlock bool) (int64, []byte, error) { 55 // use the parsed config to load the block and state store 56 blockStore, stateStore, err := loadStateAndBlockStore(config) 57 if err != nil { 58 return -1, nil, err 59 } 60 defer func() { 61 _ = blockStore.Close() 62 _ = stateStore.Close() 63 }() 64 65 // rollback the last state 66 return state.Rollback(blockStore, stateStore, removeBlock) 67 } 68 69 func loadStateAndBlockStore(config *cfg.Config) (*store.BlockStore, state.Store, error) { 70 dbType := dbm.BackendType(config.DBBackend) 71 72 if !os.FileExists(filepath.Join(config.DBDir(), "blockstore.db")) { 73 return nil, nil, fmt.Errorf("no blockstore found in %v", config.DBDir()) 74 } 75 76 // Get BlockStore 77 blockStoreDB, err := dbm.NewDB("blockstore", dbType, config.DBDir()) 78 if err != nil { 79 return nil, nil, err 80 } 81 blockStore := store.NewBlockStore(blockStoreDB) 82 83 if !os.FileExists(filepath.Join(config.DBDir(), "state.db")) { 84 return nil, nil, fmt.Errorf("no statestore found in %v", config.DBDir()) 85 } 86 87 // Get StateStore 88 stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) 89 if err != nil { 90 return nil, nil, err 91 } 92 stateStore := state.NewStore(stateDB, state.StoreOptions{ 93 DiscardABCIResponses: config.Storage.DiscardABCIResponses, 94 }) 95 96 return blockStore, stateStore, nil 97 }