|
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")
|
|
}
|
|
|
|
rollbackHeight := invalidState.LastBlockHeight
|
|
rollbackBlock := bs.LoadBlockMeta(rollbackHeight)
|
|
if rollbackBlock == nil {
|
|
return -1, nil, fmt.Errorf("block at height %d not found", rollbackHeight)
|
|
}
|
|
|
|
previousValidatorSet, err := ss.LoadValidators(rollbackHeight - 1)
|
|
if err != nil {
|
|
return -1, nil, err
|
|
}
|
|
|
|
previousParams, err := ss.LoadConsensusParams(rollbackHeight)
|
|
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
|
|
}
|
|
|
|
paramsChangeHeight := invalidState.LastHeightConsensusParamsChanged
|
|
// this can only happen if params changed from the last block
|
|
if paramsChangeHeight > rollbackHeight {
|
|
paramsChangeHeight = rollbackHeight
|
|
}
|
|
|
|
// 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: invalidState.LastBlockHeight - 1,
|
|
LastBlockID: rollbackBlock.Header.LastBlockID,
|
|
LastBlockTime: rollbackBlock.Header.Time,
|
|
|
|
NextValidators: invalidState.Validators,
|
|
Validators: invalidState.LastValidators,
|
|
LastValidators: previousValidatorSet,
|
|
LastHeightValidatorsChanged: valChangeHeight,
|
|
|
|
ConsensusParams: previousParams,
|
|
LastHeightConsensusParamsChanged: paramsChangeHeight,
|
|
|
|
LastResultsHash: rollbackBlock.Header.LastResultsHash,
|
|
AppHash: rollbackBlock.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
|
|
}
|