|
package state
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"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 need to rollback the block store too.
|
|
if height == invalidState.LastBlockHeight+1 {
|
|
if err := bs.Rollback(); err != nil {
|
|
return -1, nil, err
|
|
}
|
|
height--
|
|
}
|
|
|
|
// 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: Version{
|
|
Consensus: version.Consensus{
|
|
Block: version.BlockProtocol,
|
|
App: previousParams.Version.AppVersion,
|
|
},
|
|
Software: version.TMVersion,
|
|
},
|
|
// 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
|
|
}
|