- package state
-
- import (
- "errors"
- "fmt"
-
- tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
- tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
- "github.com/tendermint/tendermint/version"
- )
-
- // Rollback overwrites the current Tendermint state (height n) with the most
- // recent previous state (height n - 1).
- // Note that this function does not affect application state.
- func Rollback(bs BlockStore, ss Store) (int64, []byte, error) {
- invalidState, err := ss.Load()
- if err != nil {
- return -1, nil, err
- }
- if invalidState.IsEmpty() {
- return -1, nil, errors.New("no state found")
- }
-
- height := bs.Height()
-
- // NOTE: persistence of state and blocks don't happen atomically. Therefore it is possible that
- // when the user stopped the node the state wasn't updated but the blockstore was. In this situation
- // we don't need to rollback any state and can just return early
- if height == invalidState.LastBlockHeight+1 {
- return invalidState.LastBlockHeight, invalidState.AppHash, nil
- }
-
- // If the state store isn't one below nor equal to the blockstore height than this violates the
- // invariant
- if height != invalidState.LastBlockHeight {
- return -1, nil, fmt.Errorf("statestore height (%d) is not one below or equal to blockstore height (%d)",
- invalidState.LastBlockHeight, height)
- }
-
- // state store height is equal to blockstore height. We're good to proceed with rolling back state
- rollbackHeight := invalidState.LastBlockHeight - 1
- rollbackBlock := bs.LoadBlockMeta(rollbackHeight)
- if rollbackBlock == nil {
- return -1, nil, fmt.Errorf("block at height %d not found", rollbackHeight)
- }
- // We also need to retrieve the latest block because the app hash and last
- // results hash is only agreed upon in the following block.
- latestBlock := bs.LoadBlockMeta(invalidState.LastBlockHeight)
- if latestBlock == nil {
- return -1, nil, fmt.Errorf("block at height %d not found", invalidState.LastBlockHeight)
- }
-
- previousLastValidatorSet, err := ss.LoadValidators(rollbackHeight)
- if err != nil {
- return -1, nil, err
- }
-
- previousParams, err := ss.LoadConsensusParams(rollbackHeight + 1)
- if err != nil {
- return -1, nil, err
- }
-
- valChangeHeight := invalidState.LastHeightValidatorsChanged
- // this can only happen if the validator set changed since the last block
- if valChangeHeight > rollbackHeight {
- valChangeHeight = rollbackHeight + 1
- }
-
- paramsChangeHeight := invalidState.LastHeightConsensusParamsChanged
- // this can only happen if params changed from the last block
- if paramsChangeHeight > rollbackHeight {
- paramsChangeHeight = rollbackHeight + 1
- }
-
- // build the new state from the old state and the prior block
- rolledBackState := State{
- Version: tmstate.Version{
- Consensus: tmversion.Consensus{
- Block: version.BlockProtocol,
- App: previousParams.Version.AppVersion,
- },
- Software: version.TMCoreSemVer,
- },
- // immutable fields
- ChainID: invalidState.ChainID,
- InitialHeight: invalidState.InitialHeight,
-
- LastBlockHeight: rollbackBlock.Header.Height,
- LastBlockID: rollbackBlock.BlockID,
- LastBlockTime: rollbackBlock.Header.Time,
-
- NextValidators: invalidState.Validators,
- Validators: invalidState.LastValidators,
- LastValidators: previousLastValidatorSet,
- LastHeightValidatorsChanged: valChangeHeight,
-
- ConsensusParams: previousParams,
- LastHeightConsensusParamsChanged: paramsChangeHeight,
-
- LastResultsHash: latestBlock.Header.LastResultsHash,
- AppHash: latestBlock.Header.AppHash,
- }
-
- // persist the new state. This overrides the invalid one. NOTE: this will also
- // persist the validator set and consensus params over the existing structures,
- // but both should be the same
- if err := ss.Save(rolledBackState); err != nil {
- return -1, nil, fmt.Errorf("failed to save rolled back state: %w", err)
- }
-
- return rolledBackState.LastBlockHeight, rolledBackState.AppHash, nil
- }
|