You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

112 lines
3.9 KiB

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
}