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