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.

163 lines
5.3 KiB

  1. package state_test
  2. import (
  3. "crypto/rand"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. dbm "github.com/tendermint/tm-db"
  7. "github.com/tendermint/tendermint/crypto"
  8. "github.com/tendermint/tendermint/crypto/tmhash"
  9. tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
  10. tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
  11. "github.com/tendermint/tendermint/state"
  12. "github.com/tendermint/tendermint/state/mocks"
  13. "github.com/tendermint/tendermint/types"
  14. "github.com/tendermint/tendermint/version"
  15. )
  16. func TestRollback(t *testing.T) {
  17. var (
  18. height int64 = 100
  19. nextHeight int64 = 101
  20. )
  21. blockStore := &mocks.BlockStore{}
  22. stateStore := setupStateStore(t, height)
  23. initialState, err := stateStore.Load()
  24. require.NoError(t, err)
  25. // perform the rollback over a version bump
  26. newParams := types.DefaultConsensusParams()
  27. newParams.Version.AppVersion = 11
  28. newParams.Block.MaxBytes = 1000
  29. nextState := initialState.Copy()
  30. nextState.LastBlockHeight = nextHeight
  31. nextState.Version.Consensus.App = 11
  32. nextState.LastBlockID = makeBlockIDRandom()
  33. nextState.AppHash = tmhash.Sum([]byte("app_hash"))
  34. nextState.LastValidators = initialState.Validators
  35. nextState.Validators = initialState.NextValidators
  36. nextState.NextValidators = initialState.NextValidators.CopyIncrementProposerPriority(1)
  37. nextState.ConsensusParams = *newParams
  38. nextState.LastHeightConsensusParamsChanged = nextHeight + 1
  39. nextState.LastHeightValidatorsChanged = nextHeight + 1
  40. // update the state
  41. require.NoError(t, stateStore.Save(nextState))
  42. block := &types.BlockMeta{
  43. BlockID: initialState.LastBlockID,
  44. Header: types.Header{
  45. Height: initialState.LastBlockHeight,
  46. AppHash: crypto.CRandBytes(tmhash.Size),
  47. LastBlockID: makeBlockIDRandom(),
  48. LastResultsHash: initialState.LastResultsHash,
  49. },
  50. }
  51. nextBlock := &types.BlockMeta{
  52. BlockID: initialState.LastBlockID,
  53. Header: types.Header{
  54. Height: nextState.LastBlockHeight,
  55. AppHash: initialState.AppHash,
  56. LastBlockID: block.BlockID,
  57. LastResultsHash: nextState.LastResultsHash,
  58. },
  59. }
  60. blockStore.On("LoadBlockMeta", height).Return(block)
  61. blockStore.On("LoadBlockMeta", nextHeight).Return(nextBlock)
  62. blockStore.On("Height").Return(nextHeight)
  63. // rollback the state
  64. rollbackHeight, rollbackHash, err := state.Rollback(blockStore, stateStore)
  65. require.NoError(t, err)
  66. require.EqualValues(t, height, rollbackHeight)
  67. require.EqualValues(t, initialState.AppHash, rollbackHash)
  68. blockStore.AssertExpectations(t)
  69. // assert that we've recovered the prior state
  70. loadedState, err := stateStore.Load()
  71. require.NoError(t, err)
  72. require.EqualValues(t, initialState, loadedState)
  73. }
  74. func TestRollbackNoState(t *testing.T) {
  75. stateStore := state.NewStore(dbm.NewMemDB())
  76. blockStore := &mocks.BlockStore{}
  77. _, _, err := state.Rollback(blockStore, stateStore)
  78. require.Error(t, err)
  79. require.Contains(t, err.Error(), "no state found")
  80. }
  81. func TestRollbackNoBlocks(t *testing.T) {
  82. const height = int64(100)
  83. stateStore := setupStateStore(t, height)
  84. blockStore := &mocks.BlockStore{}
  85. blockStore.On("Height").Return(height)
  86. blockStore.On("LoadBlockMeta", height).Return(nil)
  87. blockStore.On("LoadBlockMeta", height-1).Return(nil)
  88. _, _, err := state.Rollback(blockStore, stateStore)
  89. require.Error(t, err)
  90. require.Contains(t, err.Error(), "block at height 99 not found")
  91. }
  92. func TestRollbackDifferentStateHeight(t *testing.T) {
  93. const height = int64(100)
  94. stateStore := setupStateStore(t, height)
  95. blockStore := &mocks.BlockStore{}
  96. blockStore.On("Height").Return(height + 2)
  97. _, _, err := state.Rollback(blockStore, stateStore)
  98. require.Error(t, err)
  99. require.Equal(t, err.Error(), "statestore height (100) is not one below or equal to blockstore height (102)")
  100. }
  101. func setupStateStore(t *testing.T, height int64) state.Store {
  102. stateStore := state.NewStore(dbm.NewMemDB())
  103. valSet, _ := types.RandValidatorSet(5, 10)
  104. params := types.DefaultConsensusParams()
  105. params.Version.AppVersion = 10
  106. initialState := state.State{
  107. Version: tmstate.Version{
  108. Consensus: tmversion.Consensus{
  109. Block: version.BlockProtocol,
  110. App: 10,
  111. },
  112. Software: version.TMCoreSemVer,
  113. },
  114. ChainID: "test-chain",
  115. InitialHeight: 10,
  116. LastBlockID: makeBlockIDRandom(),
  117. AppHash: tmhash.Sum([]byte("app_hash")),
  118. LastResultsHash: tmhash.Sum([]byte("last_results_hash")),
  119. LastBlockHeight: height,
  120. LastValidators: valSet,
  121. Validators: valSet.CopyIncrementProposerPriority(1),
  122. NextValidators: valSet.CopyIncrementProposerPriority(2),
  123. LastHeightValidatorsChanged: height + 1,
  124. ConsensusParams: *params,
  125. LastHeightConsensusParamsChanged: height + 1,
  126. }
  127. require.NoError(t, stateStore.Bootstrap(initialState))
  128. return stateStore
  129. }
  130. func makeBlockIDRandom() types.BlockID {
  131. var (
  132. blockHash = make([]byte, tmhash.Size)
  133. partSetHash = make([]byte, tmhash.Size)
  134. )
  135. rand.Read(blockHash) //nolint: errcheck // ignore errcheck for read
  136. rand.Read(partSetHash) //nolint: errcheck // ignore errcheck for read
  137. return types.BlockID{
  138. Hash: blockHash,
  139. PartSetHeader: types.PartSetHeader{
  140. Total: 123,
  141. Hash: partSetHash,
  142. },
  143. }
  144. }