package state_test import ( "fmt" "os" "testing" "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/merkle" tmrand "github.com/tendermint/tendermint/libs/rand" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 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, err := dbm.NewDB("state", dbType, config.DBDir()) require.NoError(b, err) 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() pk := ed25519.GenPrivKey().PubKey() // Generate a bunch of state data. Validators change for heights ending with 3, and // parameters when ending with 5. validator := &types.Validator{Address: tmrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk} 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 } state := sm.State{ LastBlockHeight: h - 1, Validators: validatorSet, NextValidators: validatorSet, ConsensusParams: tmproto.ConsensusParams{ Block: tmproto.BlockParams{MaxBytes: 10e6}, }, LastHeightValidatorsChanged: valsChanged, LastHeightConsensusParamsChanged: paramsChanged, } if state.LastBlockHeight >= 1 { state.LastValidators = state.Validators } sm.SaveState(db, state) sm.SaveABCIResponses(db, h, &tmstate.ABCIResponses{ DeliverTxs: []*abci.ResponseDeliverTx{ {Data: []byte{1}}, {Data: []byte{2}}, {Data: []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.Equal(&tmproto.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 TestABCIResponsesResultsHash(t *testing.T) { responses := &tmstate.ABCIResponses{ BeginBlock: &abci.ResponseBeginBlock{}, DeliverTxs: []*abci.ResponseDeliverTx{ {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, }, EndBlock: &abci.ResponseEndBlock{}, } root := sm.ABCIResponsesResultsHash(responses) bbeBytes, _ := proto.Marshal(responses.BeginBlock) results := types.NewResults(responses.DeliverTxs) ebeBytes, _ := proto.Marshal(responses.EndBlock) root2, proofs := merkle.ProofsFromByteSlices([][]byte{bbeBytes, results.Hash(), ebeBytes}) assert.Equal(t, root2, root) assert.NoError(t, proofs[1].Verify(root, results.Hash())) } func sliceToMap(s []int64) map[int64]bool { m := make(map[int64]bool, len(s)) for _, i := range s { m[i] = true } return m }