github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/rollback.go (about)

     1  package state
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	cmtstate "github.com/badrootd/celestia-core/proto/tendermint/state"
     8  	cmtversion "github.com/badrootd/celestia-core/proto/tendermint/version"
     9  	"github.com/badrootd/celestia-core/version"
    10  )
    11  
    12  // Rollback overwrites the current CometBFT state (height n) with the most
    13  // recent previous state (height n - 1).
    14  // Note that this function does not affect application state.
    15  func Rollback(bs BlockStore, ss Store, removeBlock bool) (int64, []byte, error) {
    16  	invalidState, err := ss.Load()
    17  	if err != nil {
    18  		return -1, nil, err
    19  	}
    20  	if invalidState.IsEmpty() {
    21  		return -1, nil, errors.New("no state found")
    22  	}
    23  
    24  	height := bs.Height()
    25  
    26  	// NOTE: persistence of state and blocks don't happen atomically. Therefore it is possible that
    27  	// when the user stopped the node the state wasn't updated but the blockstore was. Discard the
    28  	// pending block before continuing.
    29  	if height == invalidState.LastBlockHeight+1 {
    30  		if removeBlock {
    31  			if err := bs.DeleteLatestBlock(); err != nil {
    32  				return -1, nil, fmt.Errorf("failed to remove final block from blockstore: %w", err)
    33  			}
    34  		}
    35  		return invalidState.LastBlockHeight, invalidState.AppHash, nil
    36  	}
    37  
    38  	// If the state store isn't one below nor equal to the blockstore height than this violates the
    39  	// invariant
    40  	if height != invalidState.LastBlockHeight {
    41  		return -1, nil, fmt.Errorf("statestore height (%d) is not one below or equal to blockstore height (%d)",
    42  			invalidState.LastBlockHeight, height)
    43  	}
    44  
    45  	// state store height is equal to blockstore height. We're good to proceed with rolling back state
    46  	rollbackHeight := invalidState.LastBlockHeight - 1
    47  	rollbackBlock := bs.LoadBlockMeta(rollbackHeight)
    48  	if rollbackBlock == nil {
    49  		return -1, nil, fmt.Errorf("block at height %d not found", rollbackHeight)
    50  	}
    51  	// We also need to retrieve the latest block because the app hash and last
    52  	// results hash is only agreed upon in the following block.
    53  	latestBlock := bs.LoadBlockMeta(invalidState.LastBlockHeight)
    54  	if latestBlock == nil {
    55  		return -1, nil, fmt.Errorf("block at height %d not found", invalidState.LastBlockHeight)
    56  	}
    57  
    58  	previousLastValidatorSet, err := ss.LoadValidators(rollbackHeight)
    59  	if err != nil {
    60  		return -1, nil, err
    61  	}
    62  
    63  	previousParams, err := ss.LoadConsensusParams(rollbackHeight + 1)
    64  	if err != nil {
    65  		return -1, nil, err
    66  	}
    67  
    68  	valChangeHeight := invalidState.LastHeightValidatorsChanged
    69  	// this can only happen if the validator set changed since the last block
    70  	if valChangeHeight > rollbackHeight {
    71  		valChangeHeight = rollbackHeight + 1
    72  	}
    73  
    74  	paramsChangeHeight := invalidState.LastHeightConsensusParamsChanged
    75  	// this can only happen if params changed from the last block
    76  	if paramsChangeHeight > rollbackHeight {
    77  		paramsChangeHeight = rollbackHeight + 1
    78  	}
    79  
    80  	// build the new state from the old state and the prior block
    81  	rolledBackState := State{
    82  		Version: cmtstate.Version{
    83  			Consensus: cmtversion.Consensus{
    84  				Block: version.BlockProtocol,
    85  				App:   previousParams.Version.App,
    86  			},
    87  			Software: version.TMCoreSemVer,
    88  		},
    89  		// immutable fields
    90  		ChainID:       invalidState.ChainID,
    91  		InitialHeight: invalidState.InitialHeight,
    92  
    93  		LastBlockHeight: rollbackBlock.Header.Height,
    94  		LastBlockID:     rollbackBlock.BlockID,
    95  		LastBlockTime:   rollbackBlock.Header.Time,
    96  
    97  		NextValidators:              invalidState.Validators,
    98  		Validators:                  invalidState.LastValidators,
    99  		LastValidators:              previousLastValidatorSet,
   100  		LastHeightValidatorsChanged: valChangeHeight,
   101  
   102  		ConsensusParams:                  previousParams,
   103  		LastHeightConsensusParamsChanged: paramsChangeHeight,
   104  
   105  		LastResultsHash: latestBlock.Header.LastResultsHash,
   106  		AppHash:         latestBlock.Header.AppHash,
   107  	}
   108  
   109  	// persist the new state. This overrides the invalid one. NOTE: this will also
   110  	// persist the validator set and consensus params over the existing structures,
   111  	// but both should be the same
   112  	if err := ss.Save(rolledBackState); err != nil {
   113  		return -1, nil, fmt.Errorf("failed to save rolled back state: %w", err)
   114  	}
   115  
   116  	// If removeBlock is true then also remove the block associated with the previous state.
   117  	// This will mean both the last state and last block height is equal to n - 1
   118  	if removeBlock {
   119  		if err := bs.DeleteLatestBlock(); err != nil {
   120  			return -1, nil, fmt.Errorf("failed to remove final block from blockstore: %w", err)
   121  		}
   122  	}
   123  
   124  	return rolledBackState.LastBlockHeight, rolledBackState.AppHash, nil
   125  }