package state_test import ( "fmt" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" cfg "github.com/tendermint/tendermint/config" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) func TestStoreLoadValidators(t *testing.T) { stateDB := dbm.NewMemDB() val, _ := types.RandValidator(true, 10) vals := types.NewValidatorSet([]*types.Validator{val}) // 1) LoadValidators loads validators using a height where they were last changed sm.SaveValidatorsInfo(stateDB, 1, 1, vals) sm.SaveValidatorsInfo(stateDB, 2, 1, vals) loadedVals, err := sm.LoadValidators(stateDB, 2) require.NoError(t, err) assert.NotZero(t, loadedVals.Size()) // 2) LoadValidators loads validators using a checkpoint height sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, vals) loadedVals, err = sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval) require.NoError(t, err) assert.NotZero(t, loadedVals.Size()) } func BenchmarkLoadValidators(b *testing.B) { const valSetSize = 100 config := cfg.ResetTestRoot("state_") defer os.RemoveAll(config.RootDir) dbType := dbm.BackendType(config.DBBackend) stateDB := dbm.NewDB("state", dbType, config.DBDir()) state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) if err != nil { b.Fatal(err) } state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) sm.SaveState(stateDB, state) for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... i := i sm.SaveValidatorsInfo(stateDB, int64(i), state.LastHeightValidatorsChanged, state.NextValidators) b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) { for n := 0; n < b.N; n++ { _, err := sm.LoadValidators(stateDB, int64(i)) if err != nil { b.Fatal(err) } } }) } } func TestPruneStates(t *testing.T) { testcases := map[string]struct { makeHeights int64 pruneFrom int64 pruneTo int64 expectErr bool expectVals []int64 expectParams []int64 expectABCI []int64 }{ "error on pruning from 0": {100, 0, 5, true, nil, nil, nil}, "error when from > to": {100, 3, 2, true, nil, nil, nil}, "error when from == to": {100, 3, 3, true, nil, nil, nil}, "error when to does not exist": {100, 1, 101, true, nil, nil, nil}, "prune all": {100, 1, 100, false, []int64{93, 100}, []int64{95, 100}, []int64{100}}, "prune some": {10, 2, 8, false, []int64{1, 3, 8, 9, 10}, []int64{1, 5, 8, 9, 10}, []int64{1, 8, 9, 10}}, "prune across checkpoint": {100001, 1, 100001, false, []int64{99993, 100000, 100001}, []int64{99995, 100001}, []int64{100001}}, } for name, tc := range testcases { tc := tc t.Run(name, func(t *testing.T) { db := dbm.NewMemDB() // Generate a bunch of state data. Validators change for heights ending with 3, and // parameters when ending with 5. validator := &types.Validator{Address: []byte{1, 2, 3}, VotingPower: 100} validatorSet := &types.ValidatorSet{ Validators: []*types.Validator{validator}, Proposer: validator, } valsChanged := int64(0) paramsChanged := int64(0) for h := int64(1); h <= tc.makeHeights; h++ { if valsChanged == 0 || h%10 == 2 { valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored } if paramsChanged == 0 || h%10 == 5 { paramsChanged = h } sm.SaveState(db, sm.State{ LastBlockHeight: h - 1, Validators: validatorSet, NextValidators: validatorSet, ConsensusParams: types.ConsensusParams{ Block: types.BlockParams{MaxBytes: 10e6}, }, LastHeightValidatorsChanged: valsChanged, LastHeightConsensusParamsChanged: paramsChanged, }) sm.SaveABCIResponses(db, h, sm.NewABCIResponses(&types.Block{ Header: types.Header{Height: h}, Data: types.Data{ Txs: types.Txs{ []byte{1}, []byte{2}, []byte{3}, }, }, })) } // Test assertions err := sm.PruneStates(db, tc.pruneFrom, tc.pruneTo) if tc.expectErr { require.Error(t, err) return } require.NoError(t, err) expectVals := sliceToMap(tc.expectVals) expectParams := sliceToMap(tc.expectParams) expectABCI := sliceToMap(tc.expectABCI) for h := int64(1); h <= tc.makeHeights; h++ { vals, err := sm.LoadValidators(db, h) if expectVals[h] { require.NoError(t, err, "validators height %v", h) require.NotNil(t, vals) } else { require.Error(t, err, "validators height %v", h) require.Equal(t, sm.ErrNoValSetForHeight{Height: h}, err) } params, err := sm.LoadConsensusParams(db, h) if expectParams[h] { require.NoError(t, err, "params height %v", h) require.False(t, params.Equals(&types.ConsensusParams{})) } else { require.Error(t, err, "params height %v", h) require.Equal(t, sm.ErrNoConsensusParamsForHeight{Height: h}, err) } abci, err := sm.LoadABCIResponses(db, h) if expectABCI[h] { require.NoError(t, err, "abci height %v", h) require.NotNil(t, abci) } else { require.Error(t, err, "abci height %v", h) require.Equal(t, sm.ErrNoABCIResponsesForHeight{Height: h}, err) } } }) } } func sliceToMap(s []int64) map[int64]bool { m := make(map[int64]bool, len(s)) for _, i := range s { m[i] = true } return m }