mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-20 15:02:33 +00:00
89bb82617a
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>
Co-authored-by: yihuang <huang@crypto.com>
(cherry picked from commit 8a238fdcb4)
Inline factory function that does not exist in this branch.
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
164 lines
5.3 KiB
Go
164 lines
5.3 KiB
Go
package state_test
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"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/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 = 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,
|
|
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,
|
|
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())
|
|
valSet, _ := types.RandValidatorSet(5, 10)
|
|
|
|
params := types.DefaultConsensusParams()
|
|
params.Version.AppVersion = 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,
|
|
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,
|
|
},
|
|
}
|
|
}
|