Files
tendermint/state/rollback_test.go
mergify[bot] bd9ed68a04 cli: add --hard flag to rollback command to remove block as well (backport #9261) (#9465)
* cli: add --hard flag to rollback command to remove block as well  (#9261)

Co-authored-by: Levi Aul <levi@leviaul.com>
(cherry picked from commit e84d43ec93)

* Fix lint

Signed-off-by: Thane Thomson <connect@thanethomson.com>

Signed-off-by: Thane Thomson <connect@thanethomson.com>
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
Co-authored-by: Thane Thomson <connect@thanethomson.com>
2022-12-21 17:56:06 -05:00

288 lines
10 KiB
Go

package state_test
import (
"crypto/rand"
"testing"
"time"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash"
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/state/mocks"
"github.com/tendermint/tendermint/store"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/version"
)
func TestRollback(t *testing.T) {
var (
height int64 = 100
nextHeight int64 = 101
)
blockStore := &mocks.BlockStore{}
stateStore := setupStateStore(t, height)
initialState, err := stateStore.Load()
require.NoError(t, err)
// perform the rollback over a version bump
newParams := types.DefaultConsensusParams()
newParams.Version.App = 11
newParams.Block.MaxBytes = 1000
nextState := initialState.Copy()
nextState.LastBlockHeight = nextHeight
nextState.Version.Consensus.App = 11
nextState.LastBlockID = makeBlockIDRandom()
nextState.AppHash = tmhash.Sum([]byte("app_hash"))
nextState.LastValidators = initialState.Validators
nextState.Validators = initialState.NextValidators
nextState.NextValidators = initialState.NextValidators.CopyIncrementProposerPriority(1)
nextState.ConsensusParams = *newParams
nextState.LastHeightConsensusParamsChanged = nextHeight + 1
nextState.LastHeightValidatorsChanged = nextHeight + 1
// update the state
require.NoError(t, stateStore.Save(nextState))
block := &types.BlockMeta{
BlockID: initialState.LastBlockID,
Header: types.Header{
Height: initialState.LastBlockHeight,
Time: initialState.LastBlockTime,
AppHash: crypto.CRandBytes(tmhash.Size),
LastBlockID: makeBlockIDRandom(),
LastResultsHash: initialState.LastResultsHash,
},
}
nextBlock := &types.BlockMeta{
BlockID: initialState.LastBlockID,
Header: types.Header{
Height: nextState.LastBlockHeight,
AppHash: initialState.AppHash,
LastBlockID: block.BlockID,
Time: nextState.LastBlockTime,
LastResultsHash: nextState.LastResultsHash,
},
}
blockStore.On("LoadBlockMeta", height).Return(block)
blockStore.On("LoadBlockMeta", nextHeight).Return(nextBlock)
blockStore.On("Height").Return(nextHeight)
// rollback the state
rollbackHeight, rollbackHash, err := state.Rollback(blockStore, stateStore, false)
require.NoError(t, err)
require.EqualValues(t, height, rollbackHeight)
require.EqualValues(t, initialState.AppHash, rollbackHash)
blockStore.AssertExpectations(t)
// assert that we've recovered the prior state
loadedState, err := stateStore.Load()
require.NoError(t, err)
require.EqualValues(t, initialState, loadedState)
}
func TestRollbackHard(t *testing.T) {
const height int64 = 100
blockStore := store.NewBlockStore(dbm.NewMemDB())
stateStore := state.NewStore(dbm.NewMemDB(), state.StoreOptions{DiscardABCIResponses: false})
valSet, _ := types.RandValidatorSet(5, 10)
params := types.DefaultConsensusParams()
params.Version.App = 10
now := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
block := &types.Block{
Header: types.Header{
Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1},
ChainID: "test-chain",
Time: now,
Height: height,
AppHash: crypto.CRandBytes(tmhash.Size),
LastBlockID: makeBlockIDRandom(),
LastCommitHash: crypto.CRandBytes(tmhash.Size),
DataHash: crypto.CRandBytes(tmhash.Size),
ValidatorsHash: valSet.Hash(),
NextValidatorsHash: valSet.CopyIncrementProposerPriority(1).Hash(),
ConsensusHash: params.Hash(),
LastResultsHash: crypto.CRandBytes(tmhash.Size),
EvidenceHash: crypto.CRandBytes(tmhash.Size),
ProposerAddress: crypto.CRandBytes(crypto.AddressSize),
},
LastCommit: &types.Commit{Height: height - 1},
}
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
require.NoError(t, err)
blockStore.SaveBlock(block, partSet, &types.Commit{Height: block.Height})
currState := state.State{
Version: tmstate.Version{
Consensus: block.Header.Version,
Software: version.TMCoreSemVer,
},
LastBlockHeight: block.Height,
LastBlockTime: block.Time,
AppHash: crypto.CRandBytes(tmhash.Size),
LastValidators: valSet,
Validators: valSet.CopyIncrementProposerPriority(1),
NextValidators: valSet.CopyIncrementProposerPriority(2),
ConsensusParams: *params,
LastHeightConsensusParamsChanged: height + 1,
LastHeightValidatorsChanged: height + 1,
LastResultsHash: crypto.CRandBytes(tmhash.Size),
}
require.NoError(t, stateStore.Bootstrap(currState))
nextBlock := &types.Block{
Header: types.Header{
Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1},
ChainID: block.ChainID,
Time: block.Time,
Height: currState.LastBlockHeight + 1,
AppHash: currState.AppHash,
LastBlockID: types.BlockID{Hash: block.Hash(), PartSetHeader: partSet.Header()},
LastCommitHash: crypto.CRandBytes(tmhash.Size),
DataHash: crypto.CRandBytes(tmhash.Size),
ValidatorsHash: valSet.CopyIncrementProposerPriority(1).Hash(),
NextValidatorsHash: valSet.CopyIncrementProposerPriority(2).Hash(),
ConsensusHash: params.Hash(),
LastResultsHash: currState.LastResultsHash,
EvidenceHash: crypto.CRandBytes(tmhash.Size),
ProposerAddress: crypto.CRandBytes(crypto.AddressSize),
},
LastCommit: &types.Commit{Height: currState.LastBlockHeight},
}
nextPartSet, err := nextBlock.MakePartSet(types.BlockPartSizeBytes)
require.NoError(t, err)
blockStore.SaveBlock(nextBlock, nextPartSet, &types.Commit{Height: nextBlock.Height})
rollbackHeight, rollbackHash, err := state.Rollback(blockStore, stateStore, true)
require.NoError(t, err)
require.Equal(t, rollbackHeight, currState.LastBlockHeight)
require.Equal(t, rollbackHash, currState.AppHash)
// state should not have been changed
loadedState, err := stateStore.Load()
require.NoError(t, err)
require.Equal(t, currState, loadedState)
// resave the same block
blockStore.SaveBlock(nextBlock, nextPartSet, &types.Commit{Height: nextBlock.Height})
params.Version.App = 11
nextState := state.State{
Version: tmstate.Version{
Consensus: block.Header.Version,
Software: version.TMCoreSemVer,
},
LastBlockHeight: nextBlock.Height,
LastBlockTime: nextBlock.Time,
AppHash: crypto.CRandBytes(tmhash.Size),
LastValidators: valSet.CopyIncrementProposerPriority(1),
Validators: valSet.CopyIncrementProposerPriority(2),
NextValidators: valSet.CopyIncrementProposerPriority(3),
ConsensusParams: *params,
LastHeightConsensusParamsChanged: nextBlock.Height + 1,
LastHeightValidatorsChanged: nextBlock.Height + 1,
LastResultsHash: crypto.CRandBytes(tmhash.Size),
}
require.NoError(t, stateStore.Save(nextState))
rollbackHeight, rollbackHash, err = state.Rollback(blockStore, stateStore, true)
require.NoError(t, err)
require.Equal(t, rollbackHeight, currState.LastBlockHeight)
require.Equal(t, rollbackHash, currState.AppHash)
}
func TestRollbackNoState(t *testing.T) {
stateStore := state.NewStore(dbm.NewMemDB(),
state.StoreOptions{
DiscardABCIResponses: false,
})
blockStore := &mocks.BlockStore{}
_, _, err := state.Rollback(blockStore, stateStore, false)
require.Error(t, err)
require.Contains(t, err.Error(), "no state found")
}
func TestRollbackNoBlocks(t *testing.T) {
const height = int64(100)
stateStore := setupStateStore(t, height)
blockStore := &mocks.BlockStore{}
blockStore.On("Height").Return(height)
blockStore.On("LoadBlockMeta", height).Return(nil)
blockStore.On("LoadBlockMeta", height-1).Return(nil)
_, _, err := state.Rollback(blockStore, stateStore, false)
require.Error(t, err)
require.Contains(t, err.Error(), "block at height 99 not found")
}
func TestRollbackDifferentStateHeight(t *testing.T) {
const height = int64(100)
stateStore := setupStateStore(t, height)
blockStore := &mocks.BlockStore{}
blockStore.On("Height").Return(height + 2)
_, _, err := state.Rollback(blockStore, stateStore, false)
require.Error(t, err)
require.Equal(t, err.Error(), "statestore height (100) is not one below or equal to blockstore height (102)")
}
func setupStateStore(t *testing.T, height int64) state.Store {
stateStore := state.NewStore(dbm.NewMemDB(), state.StoreOptions{DiscardABCIResponses: false})
valSet, _ := types.RandValidatorSet(5, 10)
params := types.DefaultConsensusParams()
params.Version.App = 10
initialState := state.State{
Version: tmstate.Version{
Consensus: tmversion.Consensus{
Block: version.BlockProtocol,
App: 10,
},
Software: version.TMCoreSemVer,
},
ChainID: "test-chain",
InitialHeight: 10,
LastBlockID: makeBlockIDRandom(),
AppHash: tmhash.Sum([]byte("app_hash")),
LastResultsHash: tmhash.Sum([]byte("last_results_hash")),
LastBlockHeight: height,
LastBlockTime: time.Now(),
LastValidators: valSet,
Validators: valSet.CopyIncrementProposerPriority(1),
NextValidators: valSet.CopyIncrementProposerPriority(2),
LastHeightValidatorsChanged: height + 1,
ConsensusParams: *params,
LastHeightConsensusParamsChanged: height + 1,
}
require.NoError(t, stateStore.Bootstrap(initialState))
return stateStore
}
func makeBlockIDRandom() types.BlockID {
var (
blockHash = make([]byte, tmhash.Size)
partSetHash = make([]byte, tmhash.Size)
)
rand.Read(blockHash) //nolint: errcheck // ignore errcheck for read
rand.Read(partSetHash) //nolint: errcheck // ignore errcheck for read
return types.BlockID{
Hash: blockHash,
PartSetHeader: types.PartSetHeader{
Total: 123,
Hash: partSetHash,
},
}
}