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  }