mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-20 23:12:44 +00:00
8a238fdcb4
When testing rollback feature in the Cosmos SDK, we found that the app hash in Tendermint after rollback was the value after the latest block, rather than before it. Co-authored-by: Callum Waters <cmwaters19@gmail.com>
149 lines
4.8 KiB
Go
149 lines
4.8 KiB
Go
package state_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/internal/state"
|
|
"github.com/tendermint/tendermint/internal/state/mocks"
|
|
"github.com/tendermint/tendermint/internal/test/factory"
|
|
"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.AppVersion = 11
|
|
newParams.Block.MaxBytes = 1000
|
|
nextState := initialState.Copy()
|
|
nextState.LastBlockHeight = nextHeight
|
|
nextState.Version.Consensus.App = 11
|
|
nextState.LastBlockID = factory.MakeBlockID()
|
|
nextState.AppHash = factory.RandomHash()
|
|
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,
|
|
AppHash: factory.RandomHash(),
|
|
LastBlockID: factory.MakeBlockID(),
|
|
LastResultsHash: initialState.LastResultsHash,
|
|
},
|
|
}
|
|
nextBlock := &types.BlockMeta{
|
|
BlockID: initialState.LastBlockID,
|
|
Header: types.Header{
|
|
Height: nextState.LastBlockHeight,
|
|
AppHash: initialState.AppHash,
|
|
LastBlockID: block.BlockID,
|
|
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)
|
|
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 TestRollbackNoState(t *testing.T) {
|
|
stateStore := state.NewStore(dbm.NewMemDB())
|
|
blockStore := &mocks.BlockStore{}
|
|
|
|
_, _, err := state.Rollback(blockStore, stateStore)
|
|
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)
|
|
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)
|
|
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())
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
valSet, _ := factory.ValidatorSet(ctx, t, 5, 10)
|
|
|
|
params := types.DefaultConsensusParams()
|
|
params.Version.AppVersion = 10
|
|
|
|
initialState := state.State{
|
|
Version: state.Version{
|
|
Consensus: version.Consensus{
|
|
Block: version.BlockProtocol,
|
|
App: 10,
|
|
},
|
|
Software: version.TMVersion,
|
|
},
|
|
ChainID: factory.DefaultTestChainID,
|
|
InitialHeight: 10,
|
|
LastBlockID: factory.MakeBlockID(),
|
|
AppHash: factory.RandomHash(),
|
|
LastResultsHash: factory.RandomHash(),
|
|
LastBlockHeight: height,
|
|
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
|
|
}
|