github.com/PikeEcosystem/tendermint@v0.0.4/state/store_test.go (about) 1 package state_test 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 abci "github.com/tendermint/tendermint/abci/types" 13 tmstate "github.com/tendermint/tendermint/proto/tendermint/state" 14 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 15 dbm "github.com/tendermint/tm-db" 16 17 cfg "github.com/PikeEcosystem/tendermint/config" 18 "github.com/PikeEcosystem/tendermint/crypto" 19 "github.com/PikeEcosystem/tendermint/crypto/ed25519" 20 tmrand "github.com/PikeEcosystem/tendermint/libs/rand" 21 ocstate "github.com/PikeEcosystem/tendermint/proto/tendermint/state" 22 sm "github.com/PikeEcosystem/tendermint/state" 23 statemocks "github.com/PikeEcosystem/tendermint/state/mocks" 24 "github.com/PikeEcosystem/tendermint/types" 25 ) 26 27 const ( 28 // persist validators every valSetCheckpointInterval blocks to avoid 29 // LoadValidators taking too much time. 30 // https://github.com/tendermint/tendermint/pull/3438 31 // 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators) 32 valSetCheckpointInterval = 100000 33 ) 34 35 func TestStoreLoadValidators(t *testing.T) { 36 stateDB := dbm.NewMemDB() 37 stateStore := sm.NewStore(stateDB) 38 val, _ := types.RandValidator(true, 10) 39 vals := types.NewValidatorSet([]*types.Validator{val}) 40 41 // 1) LoadValidators loads validators using a height where they were last changed 42 err := sm.SaveValidatorsInfo(stateDB, 1, 1, []byte{}, vals) 43 require.NoError(t, err) 44 err = sm.SaveValidatorsInfo(stateDB, 2, 1, []byte{}, vals) 45 require.NoError(t, err) 46 loadedVals, err := stateStore.LoadValidators(2) 47 require.NoError(t, err) 48 assert.NotZero(t, loadedVals.Size()) 49 50 // 2) LoadValidators loads validators using a checkpoint height 51 52 err = sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, []byte{}, vals) 53 require.NoError(t, err) 54 55 loadedVals, err = stateStore.LoadValidators(sm.ValSetCheckpointInterval) 56 require.NoError(t, err) 57 assert.NotZero(t, loadedVals.Size()) 58 } 59 60 func BenchmarkLoadValidators(b *testing.B) { 61 const valSetSize = 100 62 63 config := cfg.ResetTestRoot("state_") 64 defer os.RemoveAll(config.RootDir) 65 dbType := dbm.BackendType(config.DBBackend) 66 stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) 67 require.NoError(b, err) 68 stateStore := sm.NewStore(stateDB) 69 state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) 70 if err != nil { 71 b.Fatal(err) 72 } 73 74 state.Validators = genValSet(valSetSize) 75 state.Validators.SelectProposer([]byte{}, 1, 0) 76 state.NextValidators = state.Validators.Copy() 77 state.NextValidators.SelectProposer([]byte{}, 2, 0) 78 err = stateStore.Save(state) 79 require.NoError(b, err) 80 81 for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... 82 i := i 83 if err := sm.SaveValidatorsInfo(stateDB, 84 int64(i), state.LastHeightValidatorsChanged, []byte{}, state.NextValidators); err != nil { 85 b.Fatal(err) 86 } 87 88 b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) { 89 for n := 0; n < b.N; n++ { 90 _, err := stateStore.LoadValidators(int64(i)) 91 if err != nil { 92 b.Fatal(err) 93 } 94 } 95 }) 96 } 97 } 98 99 func createState(height, valsChanged, paramsChanged int64, validatorSet *types.ValidatorSet) sm.State { 100 if height < 1 { 101 panic(height) 102 } 103 state := sm.State{ 104 InitialHeight: 1, 105 LastBlockHeight: height - 1, 106 Validators: validatorSet, 107 NextValidators: validatorSet, 108 ConsensusParams: tmproto.ConsensusParams{ 109 Block: tmproto.BlockParams{MaxBytes: 10e6}, 110 }, 111 LastHeightValidatorsChanged: valsChanged, 112 LastHeightConsensusParamsChanged: paramsChanged, 113 LastProofHash: []byte{0}, 114 } 115 if state.LastBlockHeight >= 1 { 116 state.LastValidators = state.Validators 117 } 118 return state 119 } 120 121 func createStates(makeHeights int64) []sm.State { 122 states := []sm.State{} 123 pk := ed25519.GenPrivKey().PubKey() 124 125 // Generate a bunch of state data. Validators change for heights ending with 3, and 126 // parameters when ending with 5. 127 validator := &types.Validator{Address: tmrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk} 128 validatorSet := &types.ValidatorSet{ 129 Validators: []*types.Validator{validator}, 130 } 131 valsChanged := int64(0) 132 paramsChanged := int64(0) 133 134 for h := int64(1); h <= makeHeights; h++ { 135 if valsChanged == 0 || h%10 == 2 { 136 valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored 137 } 138 if paramsChanged == 0 || h%10 == 5 { 139 paramsChanged = h 140 } 141 142 state := createState(h, valsChanged, paramsChanged, validatorSet) 143 states = append(states, state) 144 } 145 return states 146 } 147 148 func TestPruneStates(t *testing.T) { 149 testcases := map[string]struct { 150 makeHeights int64 151 pruneFrom int64 152 pruneTo int64 153 expectErr bool 154 expectVals []int64 155 expectParams []int64 156 expectABCI []int64 157 }{ 158 "error on pruning from 0": {100, 0, 5, true, nil, nil, nil}, 159 "error when from > to": {100, 3, 2, true, nil, nil, nil}, 160 "error when from == to": {100, 3, 3, true, nil, nil, nil}, 161 "error when to does not exist": {100, 1, 101, true, nil, nil, nil}, 162 "prune all": {100, 1, 100, false, []int64{93, 100}, []int64{95, 100}, []int64{100}}, 163 "prune some": {10, 2, 8, false, []int64{1, 3, 8, 9, 10}, 164 []int64{1, 5, 8, 9, 10}, []int64{1, 8, 9, 10}}, 165 "prune across checkpoint": {100001, 1, 100001, false, []int64{99993, 100000, 100001}, 166 []int64{99995, 100001}, []int64{100001}}, 167 } 168 for name, tc := range testcases { 169 tc := tc 170 t.Run(name, func(t *testing.T) { 171 db := dbm.NewMemDB() 172 stateStore := sm.NewStore(db) 173 174 states := createStates(tc.makeHeights) 175 176 for _, state := range states { 177 err := stateStore.Save(state) 178 require.NoError(t, err) 179 180 currentHeight := state.LastBlockHeight + int64(1) 181 err = stateStore.SaveABCIResponses(currentHeight, &ocstate.ABCIResponses{ 182 DeliverTxs: []*abci.ResponseDeliverTx{ 183 {Data: []byte{1}}, 184 {Data: []byte{2}}, 185 {Data: []byte{3}}, 186 }, 187 }) 188 require.NoError(t, err) 189 } 190 191 // Test assertions 192 err := stateStore.PruneStates(tc.pruneFrom, tc.pruneTo) 193 if tc.expectErr { 194 require.Error(t, err) 195 return 196 } 197 require.NoError(t, err) 198 199 expectVals := sliceToMap(tc.expectVals) 200 expectParams := sliceToMap(tc.expectParams) 201 expectABCI := sliceToMap(tc.expectABCI) 202 203 for h := int64(1); h <= tc.makeHeights; h++ { 204 vals, err := stateStore.LoadValidators(h) 205 if expectVals[h] { 206 require.NoError(t, err, "validators height %v", h) 207 require.NotNil(t, vals) 208 } else { 209 require.Error(t, err, "validators height %v", h) 210 require.Equal(t, sm.ErrNoValSetForHeight{Height: h}, err) 211 } 212 213 params, err := stateStore.LoadConsensusParams(h) 214 if expectParams[h] { 215 require.NoError(t, err, "params height %v", h) 216 require.False(t, params.Equal(&tmproto.ConsensusParams{})) 217 } else { 218 require.Error(t, err, "params height %v", h) 219 } 220 221 abci, err := stateStore.LoadABCIResponses(h) 222 if expectABCI[h] { 223 require.NoError(t, err, "abci height %v", h) 224 require.NotNil(t, abci) 225 } else { 226 require.Error(t, err, "abci height %v", h) 227 require.Equal(t, sm.ErrNoABCIResponsesForHeight{Height: h}, err) 228 } 229 } 230 }) 231 } 232 } 233 234 func TestPruneStatesDeleteErrHandle(t *testing.T) { 235 testcases := map[string]struct { 236 deleteValidatorsRet error 237 deleteConsensusParamsRet error 238 deleteProofHashRet error 239 }{ 240 "error on deleting validators": {errors.New("error"), nil, nil}, 241 "error on deleting consensus params": {nil, errors.New("error"), nil}, 242 "error on deleting proof hash": {nil, nil, errors.New("error")}, 243 "error on deleting all": {errors.New("error"), errors.New("error"), errors.New("error")}, 244 } 245 for name, tc := range testcases { 246 tc := tc 247 t.Run(name, func(t *testing.T) { 248 batchMock := &statemocks.Batch{} 249 batchMock.On("Close").Return(nil) 250 dbMock := &statemocks.DB{} 251 dbMock.On("NewBatch").Return(batchMock) 252 253 states := createStates(10) 254 255 for _, state := range states { 256 // Prepare a mock for prune states 257 nextHeight := state.LastBlockHeight + 1 258 if nextHeight == 1 { 259 nextHeight = state.InitialHeight 260 bufValidators, err := validatorsInfoToByte(nextHeight, nextHeight, state.Validators) 261 require.NoError(t, err) 262 batchMock.On("Delete", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight))).Return(nil) 263 dbMock.On("Get", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight))).Return(bufValidators, nil) 264 } 265 // create validators mock method 266 bufValidators, err := validatorsInfoToByte(nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) 267 require.NoError(t, err) 268 batchMock.On("Delete", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight+1))).Return(tc.deleteValidatorsRet) 269 dbMock.On("Get", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight+1))).Return(bufValidators, nil) 270 271 // create consensus params mock method 272 bufConsensusParams, err := consensusParamsInfoToByte(nextHeight, 273 state.LastHeightConsensusParamsChanged, state.ConsensusParams) 274 require.NoError(t, err) 275 batchMock.On("Delete", []byte(fmt.Sprintf("consensusParamsKey:%v", nextHeight))).Return(tc.deleteConsensusParamsRet) 276 dbMock.On("Get", []byte(fmt.Sprintf("consensusParamsKey:%v", nextHeight))).Return(bufConsensusParams, nil) 277 278 // create proof hash mock method 279 batchMock.On("Delete", []byte(fmt.Sprintf("proofHashKey:%v", nextHeight))).Return(tc.deleteProofHashRet) 280 dbMock.On("Get", []byte(fmt.Sprintf("proofHashKey:%v", nextHeight))).Return(state.LastProofHash, nil) 281 } 282 283 stateStoreInMock := sm.NewStore(dbMock) 284 err := stateStoreInMock.PruneStates(1, 10) 285 require.Error(t, err) 286 }) 287 } 288 } 289 290 func TestABCIResponsesResultsHash(t *testing.T) { 291 responses := &ocstate.ABCIResponses{ 292 BeginBlock: &abci.ResponseBeginBlock{}, 293 DeliverTxs: []*abci.ResponseDeliverTx{ 294 {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, 295 }, 296 EndBlock: &abci.ResponseEndBlock{}, 297 } 298 299 root := sm.ABCIResponsesResultsHash(responses) 300 301 // root should be Merkle tree root of DeliverTxs responses 302 results := types.NewResults(responses.DeliverTxs) 303 assert.Equal(t, root, results.Hash()) 304 305 // test we can prove first DeliverTx 306 proof := results.ProveResult(0) 307 bz, err := results[0].Marshal() 308 require.NoError(t, err) 309 assert.NoError(t, proof.Verify(root, bz)) 310 } 311 312 func sliceToMap(s []int64) map[int64]bool { 313 m := make(map[int64]bool, len(s)) 314 for _, i := range s { 315 m[i] = true 316 } 317 return m 318 } 319 320 func validatorsInfoToByte(height, lastHeightChanged int64, valSet *types.ValidatorSet) ([]byte, error) { 321 valInfo := &tmstate.ValidatorsInfo{ 322 LastHeightChanged: lastHeightChanged, 323 } 324 if height == lastHeightChanged || height%valSetCheckpointInterval == 0 { 325 pv, err := valSet.ToProto() 326 if err != nil { 327 return nil, err 328 } 329 valInfo.ValidatorSet = pv 330 } 331 332 bz, err := valInfo.Marshal() 333 if err != nil { 334 return nil, err 335 } 336 337 return bz, nil 338 } 339 340 func consensusParamsInfoToByte(nextHeight, changeHeight int64, params tmproto.ConsensusParams) ([]byte, error) { 341 paramsInfo := &tmstate.ConsensusParamsInfo{ 342 LastHeightChanged: changeHeight, 343 } 344 345 if changeHeight == nextHeight { 346 paramsInfo.ConsensusParams = params 347 } 348 bz, err := paramsInfo.Marshal() 349 if err != nil { 350 return nil, err 351 } 352 353 return bz, nil 354 }