From 228bba799ddc14604788899c3493cf3534e2fdfd Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 21 Jun 2019 17:29:29 -0400 Subject: [PATCH] state: add more tests for block validation (#3674) * Expose priv validators for use in testing * Generalize block header validation test past height 1 * Remove ineffectual assignment * Remove redundant SaveState call * Reorder comment for clarity * Use the block executor ApplyBlock function instead of implementing a stripped-down version of it * Remove commented-out code * Remove unnecessary test The required tests already appear to be implemented (implicitly) through the TestValidateBlockHeader test. * Allow for catching of specific error types during TestValidateBlockCommit * Make return error testable * Clean up and add TestValidateBlockCommit code * Fix formatting * Extract function to create a new mock test app * Update comment for clarity * Fix comment * Add skeleton code for evidence-related test * Allow for addressing priv val by address * Generalize test beyond a single validator * Generalize TestValidateBlockEvidence past first height * Reorder code to clearly separate tests and utility code * Use a common constant for stop height for testing in state/validation_test.go * Refactor errors to resemble existing conventions * Fix formatting * Extract common helper functions Having the tests littered with helper functions makes them less easily readable imho, so I've pulled them out into a separate file. This also makes it easier to see what helper functions are available during testing, so we minimize the chance of duplication when writing new tests. * Remove unused parameter * Remove unused parameters * Add field keys * Remove unused height constant * Fix typo * Fix incorrect return error * Add field keys * Use separate package for tests This refactors all of the state package's tests into a state_test package, so as to keep any usage of the state package's internal methods explicit. Any internal methods/constants used by tests are now explicitly exported in state/export_test.go * Refactor: extract helper function to make, validate, execute and commit a block * Rename state function to makeState * Remove redundant constant for number of validators * Refactor mock evidence registration into TestMain * Remove extraneous nVals variable * Replace function-level TODOs with file-level TODO and explanation * Remove extraneous comment * Fix linting issues brought up by GolangCI (pulled in from latest merge from develop) --- state/execution_test.go | 117 ++-------------- state/export_test.go | 62 +++++++++ state/helpers_test.go | 280 +++++++++++++++++++++++++++++++++++++++ state/main_test.go | 13 ++ state/services.go | 2 +- state/state_test.go | 219 ++++++++++-------------------- state/store_test.go | 31 ++--- state/tx_filter_test.go | 19 +-- state/validation.go | 5 +- state/validation_test.go | 213 ++++++++++++++++++----------- types/errors.go | 41 ++++++ types/validator_set.go | 4 +- 12 files changed, 636 insertions(+), 370 deletions(-) create mode 100644 state/export_test.go create mode 100644 state/helpers_test.go create mode 100644 state/main_test.go create mode 100644 types/errors.go diff --git a/state/execution_test.go b/state/execution_test.go index ac7b9cc3..38301df7 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -1,22 +1,20 @@ -package state +package state_test import ( "context" - "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/mock" "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -34,10 +32,10 @@ func TestApplyBlock(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) + state, stateDB, _ := makeState(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), - mock.Mempool{}, MockEvidencePool{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), + mock.Mempool{}, sm.MockEvidencePool{}) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} @@ -58,7 +56,7 @@ func TestBeginBlockValidators(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(2, 2) + state, stateDB, _ := makeState(2, 2) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} @@ -84,7 +82,7 @@ func TestBeginBlockValidators(t *testing.T) { // block for height 2 block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) - _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) + _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app receives a list of validators with a bool indicating if they signed @@ -111,7 +109,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(2, 12) + state, stateDB, _ := makeState(2, 12) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} @@ -145,7 +143,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) block.Time = now block.Evidence.Evidence = tc.evidence - _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) + _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app must receive an index of the byzantine validator @@ -213,7 +211,7 @@ func TestValidateValidatorUpdates(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := validateValidatorUpdates(tc.abciUpdates, tc.validatorParams) + err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams) if tc.shouldErr { assert.Error(t, err) } else { @@ -307,9 +305,9 @@ func TestEndBlockValidatorUpdates(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) + state, stateDB, _ := makeState(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, MockEvidencePool{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) eventBus := types.NewEventBus() err = eventBus.Start() @@ -365,8 +363,8 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, MockEvidencePool{}) + state, stateDB, _ := makeState(1, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} @@ -381,90 +379,3 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { assert.NotEmpty(t, state.NextValidators.Validators) } - -//---------------------------------------------------------------------------- - -// make some bogus txs -func makeTxs(height int64) (txs []types.Tx) { - for i := 0; i < nTxsPerBlock; i++ { - txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) - } - return txs -} - -func state(nVals, height int) (State, dbm.DB) { - vals := make([]types.GenesisValidator, nVals) - for i := 0; i < nVals; i++ { - secret := []byte(fmt.Sprintf("test%d", i)) - pk := ed25519.GenPrivKeyFromSecret(secret) - vals[i] = types.GenesisValidator{ - Address: pk.PubKey().Address(), - PubKey: pk.PubKey(), - Power: 1000, - Name: fmt.Sprintf("test%d", i), - } - } - s, _ := MakeGenesisState(&types.GenesisDoc{ - ChainID: chainID, - Validators: vals, - AppHash: nil, - }) - - // save validators to db for 2 heights - stateDB := dbm.NewMemDB() - SaveState(stateDB, s) - - for i := 1; i < height; i++ { - s.LastBlockHeight++ - s.LastValidators = s.Validators.Copy() - SaveState(stateDB, s) - } - return s, stateDB -} - -func makeBlock(state State, height int64) *types.Block { - block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit), nil, state.Validators.GetProposer().Address) - return block -} - -//---------------------------------------------------------------------------- - -type testApp struct { - abci.BaseApplication - - CommitVotes []abci.VoteInfo - ByzantineValidators []abci.Evidence - ValidatorUpdates []abci.ValidatorUpdate -} - -var _ abci.Application = (*testApp)(nil) - -func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { - return abci.ResponseInfo{} -} - -func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { - app.CommitVotes = req.LastCommitInfo.Votes - app.ByzantineValidators = req.ByzantineValidators - return abci.ResponseBeginBlock{} -} - -func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { - return abci.ResponseEndBlock{ValidatorUpdates: app.ValidatorUpdates} -} - -func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { - return abci.ResponseDeliverTx{Events: []abci.Event{}} -} - -func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { - return abci.ResponseCheckTx{} -} - -func (app *testApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{} -} - -func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - return -} diff --git a/state/export_test.go b/state/export_test.go new file mode 100644 index 00000000..af7f5cc2 --- /dev/null +++ b/state/export_test.go @@ -0,0 +1,62 @@ +package state + +import ( + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/types" +) + +// +// TODO: Remove dependence on all entities exported from this file. +// +// Every entity exported here is dependent on a private entity from the `state` +// package. Currently, these functions are only made available to tests in the +// `state_test` package, but we should not be relying on them for our testing. +// Instead, we should be exclusively relying on exported entities for our +// testing, and should be refactoring exported entities to make them more +// easily testable from outside of the package. +// + +const ValSetCheckpointInterval = valSetCheckpointInterval + +// UpdateState is an alias for updateState exported from execution.go, +// exclusively and explicitly for testing. +func UpdateState( + state State, + blockID types.BlockID, + header *types.Header, + abciResponses *ABCIResponses, + validatorUpdates []*types.Validator, +) (State, error) { + return updateState(state, blockID, header, abciResponses, validatorUpdates) +} + +// ValidateValidatorUpdates is an alias for validateValidatorUpdates exported +// from execution.go, exclusively and explicitly for testing. +func ValidateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, params types.ValidatorParams) error { + return validateValidatorUpdates(abciUpdates, params) +} + +// CalcValidatorsKey is an alias for the private calcValidatorsKey method in +// store.go, exported exclusively and explicitly for testing. +func CalcValidatorsKey(height int64) []byte { + return calcValidatorsKey(height) +} + +// SaveABCIResponses is an alias for the private saveABCIResponses method in +// store.go, exported exclusively and explicitly for testing. +func SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { + saveABCIResponses(db, height, abciResponses) +} + +// SaveConsensusParamsInfo is an alias for the private saveConsensusParamsInfo +// method in store.go, exported exclusively and explicitly for testing. +func SaveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params types.ConsensusParams) { + saveConsensusParamsInfo(db, nextHeight, changeHeight, params) +} + +// SaveValidatorsInfo is an alias for the private saveValidatorsInfo method in +// store.go, exported exclusively and explicitly for testing. +func SaveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) { + saveValidatorsInfo(db, height, lastHeightChanged, valSet) +} diff --git a/state/helpers_test.go b/state/helpers_test.go new file mode 100644 index 00000000..e8cb2758 --- /dev/null +++ b/state/helpers_test.go @@ -0,0 +1,280 @@ +package state_test + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +type paramsChangeTestCase struct { + height int64 + params types.ConsensusParams +} + +// always returns true if asked if any evidence was already committed. +type mockEvPoolAlwaysCommitted struct{} + +func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil } +func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil } +func (m mockEvPoolAlwaysCommitted) Update(*types.Block, sm.State) {} +func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true } + +func newTestApp() proxy.AppConns { + app := &testApp{} + cc := proxy.NewLocalClientCreator(app) + return proxy.NewAppConns(cc) +} + +func makeAndCommitGoodBlock( + state sm.State, + height int64, + lastCommit *types.Commit, + proposerAddr []byte, + blockExec *sm.BlockExecutor, + privVals map[string]types.PrivValidator, + evidence []types.Evidence) (sm.State, types.BlockID, *types.Commit, error) { + // A good block passes + state, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence) + if err != nil { + return state, types.BlockID{}, nil, err + } + + // Simulate a lastCommit for this block from all validators for the next height + commit, err := makeValidCommit(height, blockID, state.Validators, privVals) + if err != nil { + return state, types.BlockID{}, nil, err + } + return state, blockID, commit, nil +} + +func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, + blockExec *sm.BlockExecutor, evidence []types.Evidence) (sm.State, types.BlockID, error) { + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) + if err := blockExec.ValidateBlock(state, block); err != nil { + return state, types.BlockID{}, err + } + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}} + state, err := blockExec.ApplyBlock(state, blockID, block) + if err != nil { + return state, types.BlockID{}, err + } + return state, blockID, nil +} + +func makeVote(height int64, blockID types.BlockID, valSet *types.ValidatorSet, privVal types.PrivValidator) (*types.Vote, error) { + addr := privVal.GetPubKey().Address() + idx, _ := valSet.GetByAddress(addr) + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: idx, + Height: height, + Round: 0, + Timestamp: tmtime.Now(), + Type: types.PrecommitType, + BlockID: blockID, + } + if err := privVal.SignVote(chainID, vote); err != nil { + return nil, err + } + return vote, nil +} + +func makeValidCommit(height int64, blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator) (*types.Commit, error) { + sigs := make([]*types.CommitSig, 0) + for i := 0; i < vals.Size(); i++ { + _, val := vals.GetByIndex(i) + vote, err := makeVote(height, blockID, vals, privVals[val.Address.String()]) + if err != nil { + return nil, err + } + sigs = append(sigs, vote.CommitSig()) + } + return types.NewCommit(blockID, sigs), nil +} + +// make some bogus txs +func makeTxs(height int64) (txs []types.Tx) { + for i := 0; i < nTxsPerBlock; i++ { + txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) + } + return txs +} + +func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { + vals := make([]types.GenesisValidator, nVals) + privVals := make(map[string]types.PrivValidator, nVals) + for i := 0; i < nVals; i++ { + secret := []byte(fmt.Sprintf("test%d", i)) + pk := ed25519.GenPrivKeyFromSecret(secret) + valAddr := pk.PubKey().Address() + vals[i] = types.GenesisValidator{ + Address: valAddr, + PubKey: pk.PubKey(), + Power: 1000, + Name: fmt.Sprintf("test%d", i), + } + privVals[valAddr.String()] = types.NewMockPVWithParams(pk, false, false) + } + s, _ := sm.MakeGenesisState(&types.GenesisDoc{ + ChainID: chainID, + Validators: vals, + AppHash: nil, + }) + + stateDB := dbm.NewMemDB() + sm.SaveState(stateDB, s) + + for i := 1; i < height; i++ { + s.LastBlockHeight++ + s.LastValidators = s.Validators.Copy() + sm.SaveState(stateDB, s) + } + return s, stateDB, privVals +} + +func makeBlock(state sm.State, height int64) *types.Block { + block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit), nil, state.Validators.GetProposer().Address) + return block +} + +func genValSet(size int) *types.ValidatorSet { + vals := make([]*types.Validator, size) + for i := 0; i < size; i++ { + vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) + } + return types.NewValidatorSet(vals) +} + +func makeConsensusParams( + blockBytes, blockGas int64, + blockTimeIotaMs int64, + evidenceAge int64, +) types.ConsensusParams { + return types.ConsensusParams{ + Block: types.BlockParams{ + MaxBytes: blockBytes, + MaxGas: blockGas, + TimeIotaMs: blockTimeIotaMs, + }, + Evidence: types.EvidenceParams{ + MaxAge: evidenceAge, + }, + } +} + +func makeHeaderPartsResponsesValPubKeyChange(state sm.State, pubkey crypto.PubKey) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + + // If the pubkey is new, remove the old and add the new. + _, val := state.NextValidators.GetByIndex(0) + if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { + abciResponses.EndBlock = &abci.ResponseEndBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{ + types.TM2PB.NewValidatorUpdate(val.PubKey, 0), + types.TM2PB.NewValidatorUpdate(pubkey, 10), + }, + } + } + + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func makeHeaderPartsResponsesValPowerChange(state sm.State, power int64) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + + // If the pubkey is new, remove the old and add the new. + _, val := state.NextValidators.GetByIndex(0) + if val.VotingPower != power { + abciResponses.EndBlock = &abci.ResponseEndBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{ + types.TM2PB.NewValidatorUpdate(val.PubKey, power), + }, + } + } + + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func makeHeaderPartsResponsesParams(state sm.State, params types.ConsensusParams) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, + } + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func randomGenesisDoc() *types.GenesisDoc { + pubkey := ed25519.GenPrivKey().PubKey() + return &types.GenesisDoc{ + GenesisTime: tmtime.Now(), + ChainID: "abc", + Validators: []types.GenesisValidator{ + { + Address: pubkey.Address(), + PubKey: pubkey, + Power: 10, + Name: "myval", + }, + }, + ConsensusParams: types.DefaultConsensusParams(), + } +} + +//---------------------------------------------------------------------------- + +type testApp struct { + abci.BaseApplication + + CommitVotes []abci.VoteInfo + ByzantineValidators []abci.Evidence + ValidatorUpdates []abci.ValidatorUpdate +} + +var _ abci.Application = (*testApp)(nil) + +func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { + return abci.ResponseInfo{} +} + +func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { + app.CommitVotes = req.LastCommitInfo.Votes + app.ByzantineValidators = req.ByzantineValidators + return abci.ResponseBeginBlock{} +} + +func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { + return abci.ResponseEndBlock{ValidatorUpdates: app.ValidatorUpdates} +} + +func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + return abci.ResponseDeliverTx{Events: []abci.Event{}} +} + +func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + return abci.ResponseCheckTx{} +} + +func (app *testApp) Commit() abci.ResponseCommit { + return abci.ResponseCommit{} +} + +func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { + return +} diff --git a/state/main_test.go b/state/main_test.go new file mode 100644 index 00000000..00ecf268 --- /dev/null +++ b/state/main_test.go @@ -0,0 +1,13 @@ +package state_test + +import ( + "os" + "testing" + + "github.com/tendermint/tendermint/types" +) + +func TestMain(m *testing.M) { + types.RegisterMockEvidencesGlobal() + os.Exit(m.Run()) +} diff --git a/state/services.go b/state/services.go index 98f6afce..10b389ee 100644 --- a/state/services.go +++ b/state/services.go @@ -43,7 +43,7 @@ type EvidencePool interface { IsCommitted(types.Evidence) bool } -// MockEvidencePool is an empty implementation of a Mempool, useful for testing. +// MockEvidencePool is an empty implementation of EvidencePool, useful for testing. type MockEvidencePool struct{} func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil } diff --git a/state/state_test.go b/state/state_test.go index 1ff09ccd..a0f7a4a2 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "bytes" @@ -10,23 +10,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" ) // setupTestCase does setup common to all test cases. -func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, State) { +func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, sm.State) { config := cfg.ResetTestRoot("state_") dbType := dbm.DBBackendType(config.DBBackend) stateDB := dbm.NewDB("state", dbType, config.DBDir()) - state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) assert.NoError(t, err, "expected no error on LoadStateFromDBOrGenesisFile") tearDown := func(t *testing.T) { os.RemoveAll(config.RootDir) } @@ -59,7 +58,7 @@ func TestMakeGenesisStateNilValidators(t *testing.T) { Validators: nil, } require.Nil(t, doc.ValidateAndComplete()) - state, err := MakeGenesisState(&doc) + state, err := sm.MakeGenesisState(&doc) require.Nil(t, err) require.Equal(t, 0, len(state.Validators.Validators)) require.Equal(t, 0, len(state.NextValidators.Validators)) @@ -73,9 +72,9 @@ func TestStateSaveLoad(t *testing.T) { assert := assert.New(t) state.LastBlockHeight++ - SaveState(stateDB, state) + sm.SaveState(stateDB, state) - loadedState := LoadState(stateDB) + loadedState := sm.LoadState(stateDB) assert.True(state.Equals(loadedState), fmt.Sprintf("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n", loadedState, state)) @@ -92,15 +91,15 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { // Build mock responses. block := makeBlock(state, 2) - abciResponses := NewABCIResponses(block) + abciResponses := sm.NewABCIResponses(block) abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Events: nil} abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Events: nil} abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{ types.TM2PB.NewValidatorUpdate(ed25519.GenPrivKey().PubKey(), 10), }} - saveABCIResponses(stateDB, block.Height, abciResponses) - loadedABCIResponses, err := LoadABCIResponses(stateDB, block.Height) + sm.SaveABCIResponses(stateDB, block.Height, abciResponses) + loadedABCIResponses, err := sm.LoadABCIResponses(stateDB, block.Height) assert.Nil(err) assert.Equal(abciResponses, loadedABCIResponses, fmt.Sprintf("ABCIResponses don't match:\ngot: %v\nexpected: %v\n", @@ -155,24 +154,24 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // Query all before, this should return error. for i := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(stateDB, h) + res, err := sm.LoadABCIResponses(stateDB, h) assert.Error(err, "%d: %#v", i, res) } // Add all cases. for i, tc := range cases { h := int64(i + 1) // last block height, one below what we save - responses := &ABCIResponses{ + responses := &sm.ABCIResponses{ DeliverTx: tc.added, EndBlock: &abci.ResponseEndBlock{}, } - saveABCIResponses(stateDB, h, responses) + sm.SaveABCIResponses(stateDB, h, responses) } // Query all before, should return expected value. for i, tc := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(stateDB, h) + res, err := sm.LoadABCIResponses(stateDB, h) assert.NoError(err, "%d", i) assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i) } @@ -186,26 +185,26 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert := assert.New(t) // Can't load anything for height 0. - v, err := LoadValidators(stateDB, 0) - assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0") + v, err := sm.LoadValidators(stateDB, 0) + assert.IsType(sm.ErrNoValSetForHeight{}, err, "expected err at height 0") // Should be able to load for height 1. - v, err = LoadValidators(stateDB, 1) + v, err = sm.LoadValidators(stateDB, 1) assert.Nil(err, "expected no err at height 1") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // Should be able to load for height 2. - v, err = LoadValidators(stateDB, 2) + v, err = sm.LoadValidators(stateDB, 2) assert.Nil(err, "expected no err at height 2") assert.Equal(v.Hash(), state.NextValidators.Hash(), "expected validator hashes to match") // Increment height, save; should be able to load for next & next next height. state.LastBlockHeight++ nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) - vp0, err := LoadValidators(stateDB, nextHeight+0) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + vp0, err := sm.LoadValidators(stateDB, nextHeight+0) assert.Nil(err, "expected no err") - vp1, err := LoadValidators(stateDB, nextHeight+1) + vp1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(err, "expected no err") assert.Equal(vp0.Hash(), state.Validators.Hash(), "expected validator hashes to match") assert.Equal(vp1.Hash(), state.NextValidators.Hash(), "expected next validator hashes to match") @@ -234,13 +233,13 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { changeIndex++ power++ } - header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) + header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, power) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.NoError(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) } // On each height change, increment the power by one. @@ -258,7 +257,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } for i, power := range testCases { - v, err := LoadValidators(stateDB, int64(i+1+1)) // +1 because vset changes delayed by 1 block. + v, err := sm.LoadValidators(stateDB, int64(i+1+1)) // +1 because vset changes delayed by 1 block. assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i)) assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size()) _, val := v.GetByIndex(0) @@ -405,12 +404,12 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { block := makeBlock(state, state.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) curTotal := val1VotingPower // one increment step and one validator: 0 + power - total_power == 0 @@ -422,7 +421,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val2VotingPower} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -461,7 +460,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { // this will cause the diff of priorities (77) // to be larger than threshold == 2*totalVotingPower (22): - updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState3.NextValidators.Validators), 2) @@ -516,13 +515,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { block := makeBlock(state, state.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 @@ -537,7 +536,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -574,7 +573,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) @@ -598,13 +597,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // no changes in voting power and both validators have same voting power // -> proposers should alternate: oldState := updatedState3 - abciResponses = &ABCIResponses{ + abciResponses = &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - oldState, err = updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + oldState, err = sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) expectedVal1Prio2 = 1 expectedVal2Prio2 = -1 @@ -613,13 +612,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { for i := 0; i < 1000; i++ { // no validator updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) // alternate (and cyclic priorities): assert.NotEqual(t, updatedState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) @@ -660,7 +659,7 @@ func TestLargeGenesisValidator(t *testing.T) { oldState := state for i := 0; i < 10; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -669,7 +668,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, // than -Total == -Voting) @@ -689,18 +688,18 @@ func TestLargeGenesisValidator(t *testing.T) { firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower} validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) assert.NoError(t, err) - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) lastState := updatedState for i := 0; i < 200; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -709,7 +708,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(lastState, lastState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedStateInner, err := updateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedStateInner, err := sm.UpdateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) lastState = updatedStateInner } @@ -734,26 +733,26 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) assert.NoError(t, err) - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) } require.Equal(t, 10+2, len(state.NextValidators.Validators)) // remove genesis validator: removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0} - abciResponses = &ABCIResponses{ + abciResponses = &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, } block = makeBlock(oldState, oldState.LastBlockHeight+1) blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) // only the first added val (not the genesis val) should be left assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) @@ -764,14 +763,14 @@ func TestLargeGenesisValidator(t *testing.T) { count := 0 isProposerUnchanged := true for isProposerUnchanged { - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) block = makeBlock(curState, curState.LastBlockHeight+1) blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - curState, err = updateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) + curState, err = sm.UpdateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { isProposerUnchanged = false @@ -787,7 +786,7 @@ func TestLargeGenesisValidator(t *testing.T) { proposers := make([]*types.Validator, numVals) for i := 0; i < 100; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -796,7 +795,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(updatedState, updatedState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err = updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err = sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): if proposers[i%numVals] == nil { @@ -814,15 +813,15 @@ func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { defer tearDown(t) state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) nextHeight := state.LastBlockHeight + 1 - v0, err := LoadValidators(stateDB, nextHeight) + v0, err := sm.LoadValidators(stateDB, nextHeight) assert.Nil(t, err) acc0 := v0.Validators[0].ProposerPriority - v1, err := LoadValidators(stateDB, nextHeight+1) + v1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(t, err) acc1 := v1.Validators[0].ProposerPriority @@ -838,28 +837,27 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { require.Equal(t, int64(0), state.LastBlockHeight) state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) _, valOld := state.Validators.GetByIndex(0) var pubkeyOld = valOld.PubKey pubkey := ed25519.GenPrivKey().PubKey() - const height = 1 // Swap the first validator with a new one (validator set size stays the same). - header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey) + header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, pubkey) // Save state etc. var err error var validatorUpdates []*types.Validator validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) // Load nextheight, it should be the oldpubkey. - v0, err := LoadValidators(stateDB, nextHeight) + v0, err := sm.LoadValidators(stateDB, nextHeight) assert.Nil(t, err) assert.Equal(t, valSetSize, v0.Size()) index, val := v0.GetByAddress(pubkeyOld.Address()) @@ -869,7 +867,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { } // Load nextheight+1, it should be the new pubkey. - v1, err := LoadValidators(stateDB, nextHeight+1) + v1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(t, err) assert.Equal(t, valSetSize, v1.Size()) index, val = v1.GetByAddress(pubkey.Address()) @@ -879,14 +877,6 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { } } -func genValSet(size int) *types.ValidatorSet { - vals := make([]*types.Validator, size) - for i := 0; i < size; i++ { - vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) - } - return types.NewValidatorSet(vals) -} - func TestStateMakeBlock(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -932,14 +922,14 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { changeIndex++ cp = params[changeIndex] } - header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp) + header, blockID, responses := makeHeaderPartsResponsesParams(state, cp) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) + sm.SaveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) } // Make all the test cases by using the same params until after the change. @@ -957,32 +947,15 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } for _, testCase := range testCases { - p, err := LoadConsensusParams(stateDB, testCase.height) + p, err := sm.LoadConsensusParams(stateDB, testCase.height) assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height)) assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at height %d`, testCase.height)) } } -func makeParams( - blockBytes, blockGas int64, - blockTimeIotaMs int64, - evidenceAge int64, -) types.ConsensusParams { - return types.ConsensusParams{ - Block: types.BlockParams{ - MaxBytes: blockBytes, - MaxGas: blockGas, - TimeIotaMs: blockTimeIotaMs, - }, - Evidence: types.EvidenceParams{ - MaxAge: evidenceAge, - }, - } -} - func TestApplyUpdates(t *testing.T) { - initParams := makeParams(1, 2, 3, 4) + initParams := makeConsensusParams(1, 2, 3, 4) cases := [...]struct { init types.ConsensusParams @@ -998,14 +971,14 @@ func TestApplyUpdates(t *testing.T) { MaxGas: 55, }, }, - makeParams(44, 55, 3, 4)}, + makeConsensusParams(44, 55, 3, 4)}, 3: {initParams, abci.ConsensusParams{ Evidence: &abci.EvidenceParams{ MaxAge: 66, }, }, - makeParams(1, 2, 3, 66)}, + makeConsensusParams(1, 2, 3, 66)}, } for i, tc := range cases { @@ -1013,61 +986,3 @@ func TestApplyUpdates(t *testing.T) { assert.Equal(t, tc.expected, res, "case %d", i) } } - -func makeHeaderPartsResponsesValPubKeyChange(state State, height int64, - pubkey crypto.PubKey) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, - } - - // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) - if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { - abciResponses.EndBlock = &abci.ResponseEndBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{ - types.TM2PB.NewValidatorUpdate(val.PubKey, 0), - types.TM2PB.NewValidatorUpdate(pubkey, 10), - }, - } - } - - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -func makeHeaderPartsResponsesValPowerChange(state State, height int64, - power int64) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, - } - - // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) - if val.VotingPower != power { - abciResponses.EndBlock = &abci.ResponseEndBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{ - types.TM2PB.NewValidatorUpdate(val.PubKey, power), - }, - } - } - - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -func makeHeaderPartsResponsesParams(state State, height int64, - params types.ConsensusParams) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, - } - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -type paramsChangeTestCase struct { - height int64 - params types.ConsensusParams -} diff --git a/state/store_test.go b/state/store_test.go index 06adeefa..0cf21772 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "fmt" @@ -10,6 +10,7 @@ import ( cfg "github.com/tendermint/tendermint/config" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -19,9 +20,9 @@ func TestStoreLoadValidators(t *testing.T) { vals := types.NewValidatorSet([]*types.Validator{val}) // 1) LoadValidators loads validators using a height where they were last changed - saveValidatorsInfo(stateDB, 1, 1, vals) - saveValidatorsInfo(stateDB, 2, 1, vals) - loadedVals, err := LoadValidators(stateDB, 2) + 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()) @@ -30,13 +31,13 @@ func TestStoreLoadValidators(t *testing.T) { // TODO(melekes): REMOVE in 0.33 release // https://github.com/tendermint/tendermint/issues/3543 // for releases prior to v0.31.4, it uses last height changed - valInfo := &ValidatorsInfo{ - LastHeightChanged: valSetCheckpointInterval, + valInfo := &sm.ValidatorsInfo{ + LastHeightChanged: sm.ValSetCheckpointInterval, } - stateDB.Set(calcValidatorsKey(valSetCheckpointInterval), valInfo.Bytes()) + stateDB.Set(sm.CalcValidatorsKey(sm.ValSetCheckpointInterval), valInfo.Bytes()) assert.NotPanics(t, func() { - saveValidatorsInfo(stateDB, valSetCheckpointInterval+1, 1, vals) - loadedVals, err := LoadValidators(stateDB, valSetCheckpointInterval+1) + sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval+1, 1, vals) + loadedVals, err := sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval+1) if err != nil { t.Fatal(err) } @@ -46,9 +47,9 @@ func TestStoreLoadValidators(t *testing.T) { }) // ENDREMOVE - saveValidatorsInfo(stateDB, valSetCheckpointInterval, 1, vals) + sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, vals) - loadedVals, err = LoadValidators(stateDB, valSetCheckpointInterval) + loadedVals, err = sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval) require.NoError(t, err) assert.NotZero(t, loadedVals.Size()) } @@ -60,20 +61,20 @@ func BenchmarkLoadValidators(b *testing.B) { defer os.RemoveAll(config.RootDir) dbType := dbm.DBBackendType(config.DBBackend) stateDB := dbm.NewDB("state", dbType, config.DBDir()) - state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) if err != nil { b.Fatal(err) } state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... - saveValidatorsInfo(stateDB, int64(i), state.LastHeightValidatorsChanged, state.NextValidators) + 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 := LoadValidators(stateDB, int64(i)) + _, err := sm.LoadValidators(stateDB, int64(i)) if err != nil { b.Fatal(err) } diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index e48ad2c3..bd324316 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "os" @@ -7,11 +7,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - tmtime "github.com/tendermint/tendermint/types/time" ) func TestTxFilter(t *testing.T) { @@ -34,10 +33,10 @@ func TestTxFilter(t *testing.T) { for i, tc := range testCases { stateDB := dbm.NewDB("state", "memdb", os.TempDir()) - state, err := LoadStateFromDBOrGenesisDoc(stateDB, genDoc) + state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) require.NoError(t, err) - f := TxPreCheck(state) + f := sm.TxPreCheck(state) if tc.isErr { assert.NotNil(t, f(tc.tx), "#%v", i) } else { @@ -45,13 +44,3 @@ func TestTxFilter(t *testing.T) { } } } - -func randomGenesisDoc() *types.GenesisDoc { - pubkey := ed25519.GenPrivKey().PubKey() - return &types.GenesisDoc{ - GenesisTime: tmtime.Now(), - ChainID: "abc", - Validators: []types.GenesisValidator{{Address: pubkey.Address(), PubKey: pubkey, Power: 10, Name: "myval"}}, - ConsensusParams: types.DefaultConsensusParams(), - } -} diff --git a/state/validation.go b/state/validation.go index 3c63c35b..1d365e90 100644 --- a/state/validation.go +++ b/state/validation.go @@ -94,10 +94,7 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block } } else { if len(block.LastCommit.Precommits) != state.LastValidators.Size() { - return fmt.Errorf("Invalid block commit size. Expected %v, got %v", - state.LastValidators.Size(), - len(block.LastCommit.Precommits), - ) + return types.NewErrInvalidCommitPrecommits(state.LastValidators.Size(), len(block.LastCommit.Precommits)) } err := state.LastValidators.VerifyCommit( state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit) diff --git a/state/validation_test.go b/state/validation_test.go index 705f843d..c53cf010 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -1,31 +1,30 @@ -package state +package state_test import ( "testing" "time" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/mock" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) -// TODO(#2589): -// - generalize this past the first height -// - add txs and build up full State properly -// - test block.Time (see #2587 - there are no conditions on time for the first height) +const validationTestsStopHeight int64 = 10 + func TestValidateBlockHeader(t *testing.T) { - var height int64 = 1 // TODO(#2589): generalize - state, stateDB := state(1, int(height)) + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) - - // A good block passes. - block := makeBlock(state, height) - err := blockExec.ValidateBlock(state, block) - require.NoError(t, err) + state, stateDB, privVals := makeState(3, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) // some bad values wrongHash := tmhash.Sum([]byte("this hash is wrong")) @@ -43,7 +42,7 @@ func TestValidateBlockHeader(t *testing.T) { {"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }}, {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, {"Height wrong", func(block *types.Block) { block.Height += 10 }}, - {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 3600 * 24) }}, + {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }}, {"NumTxs wrong", func(block *types.Block) { block.NumTxs += 10 }}, {"TotalTxs wrong", func(block *types.Block) { block.TotalTxs += 10 }}, @@ -62,78 +61,145 @@ func TestValidateBlockHeader(t *testing.T) { {"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }}, } - for _, tc := range testCases { - block := makeBlock(state, height) - tc.malleateBlock(block) - err := blockExec.ValidateBlock(state, block) - require.Error(t, err, tc.name) + // Build up state for multiple heights + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + /* + Invalid blocks don't pass + */ + for _, tc := range testCases { + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, proposerAddr) + tc.malleateBlock(block) + err := blockExec.ValidateBlock(state, block) + require.Error(t, err, tc.name) + } + + /* + A good block passes + */ + var err error + state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil) + require.NoError(t, err, "height %d", height) } } -/* - TODO(#2589): - - test Block.Data.Hash() == Block.DataHash - - test len(Block.Data.Txs) == Block.NumTxs -*/ -func TestValidateBlockData(t *testing.T) { -} - -/* - TODO(#2589): - - test len(block.LastCommit.Precommits) == state.LastValidators.Size() - - test state.LastValidators.VerifyCommit -*/ func TestValidateBlockCommit(t *testing.T) { -} + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() -/* - TODO(#2589): - - test good/bad evidence in block -*/ -func TestValidateBlockEvidence(t *testing.T) { - var height int64 = 1 // TODO(#2589): generalize - state, stateDB := state(1, int(height)) + state, stateDB, privVals := makeState(1, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) + wrongPrecommitsCommit := types.NewCommit(types.BlockID{}, nil) + badPrivVal := types.NewMockPV() - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + if height > 1 { + /* + #2589: ensure state.LastValidators.VerifyCommit fails here + */ + // should be height-1 instead of height + wrongHeightVote, err := makeVote(height, state.LastBlockID, state.Validators, privVals[proposerAddr.String()]) + require.NoError(t, err, "height %d", height) + wrongHeightCommit := types.NewCommit(state.LastBlockID, []*types.CommitSig{wrongHeightVote.CommitSig()}) + block, _ := state.MakeBlock(height, makeTxs(height), wrongHeightCommit, nil, proposerAddr) + err = blockExec.ValidateBlock(state, block) + _, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight) + require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err) - // make some evidence - addr, _ := state.Validators.GetByIndex(0) - goodEvidence := types.NewMockGoodEvidence(height, 0, addr) + /* + #2589: test len(block.LastCommit.Precommits) == state.LastValidators.Size() + */ + block, _ = state.MakeBlock(height, makeTxs(height), wrongPrecommitsCommit, nil, proposerAddr) + err = blockExec.ValidateBlock(state, block) + _, isErrInvalidCommitPrecommits := err.(types.ErrInvalidCommitPrecommits) + require.True(t, isErrInvalidCommitPrecommits, "expected ErrInvalidCommitPrecommits at height %d but got: %v", height, err) + } - // A block with a couple pieces of evidence passes. - block := makeBlock(state, height) - block.Evidence.Evidence = []types.Evidence{goodEvidence, goodEvidence} - block.EvidenceHash = block.Evidence.Hash() - err := blockExec.ValidateBlock(state, block) - require.NoError(t, err) + /* + A good block passes + */ + var err error + var blockID types.BlockID + state, blockID, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil) + require.NoError(t, err, "height %d", height) - // A block with too much evidence fails. - maxBlockSize := state.ConsensusParams.Block.MaxBytes - maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) - require.True(t, maxNumEvidence > 2) - for i := int64(0); i < maxNumEvidence; i++ { - block.Evidence.Evidence = append(block.Evidence.Evidence, goodEvidence) + /* + wrongPrecommitsCommit is fine except for the extra bad precommit + */ + goodVote, err := makeVote(height, blockID, state.Validators, privVals[proposerAddr.String()]) + require.NoError(t, err, "height %d", height) + badVote := &types.Vote{ + ValidatorAddress: badPrivVal.GetPubKey().Address(), + ValidatorIndex: 0, + Height: height, + Round: 0, + Timestamp: tmtime.Now(), + Type: types.PrecommitType, + BlockID: blockID, + } + err = badPrivVal.SignVote(chainID, goodVote) + require.NoError(t, err, "height %d", height) + wrongPrecommitsCommit = types.NewCommit(blockID, []*types.CommitSig{goodVote.CommitSig(), badVote.CommitSig()}) } - block.EvidenceHash = block.Evidence.Hash() - err = blockExec.ValidateBlock(state, block) - require.Error(t, err) - _, ok := err.(*types.ErrEvidenceOverflow) - require.True(t, ok) } -// always returns true if asked if any evidence was already committed. -type mockEvPoolAlwaysCommitted struct{} +func TestValidateBlockEvidence(t *testing.T) { + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() -func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil } -func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil } -func (m mockEvPoolAlwaysCommitted) Update(*types.Block, State) {} -func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true } + state, stateDB, privVals := makeState(3, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) + + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + proposerIdx, _ := state.Validators.GetByAddress(proposerAddr) + goodEvidence := types.NewMockGoodEvidence(height, proposerIdx, proposerAddr) + if height > 1 { + /* + A block with too much evidence fails + */ + maxBlockSize := state.ConsensusParams.Block.MaxBytes + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) + require.True(t, maxNumEvidence > 2) + evidence := make([]types.Evidence, 0) + // one more than the maximum allowed evidence + for i := int64(0); i <= maxNumEvidence; i++ { + evidence = append(evidence, goodEvidence) + } + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) + err := blockExec.ValidateBlock(state, block) + _, ok := err.(*types.ErrEvidenceOverflow) + require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d", height) + } + + /* + A good block with several pieces of good evidence passes + */ + maxBlockSize := state.ConsensusParams.Block.MaxBytes + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) + require.True(t, maxNumEvidence > 2) + evidence := make([]types.Evidence, 0) + // precisely the amount of allowed evidence + for i := int64(0); i < maxNumEvidence; i++ { + evidence = append(evidence, goodEvidence) + } + + var err error + state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, evidence) + require.NoError(t, err, "height %d", height) + } +} func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { var height int64 = 1 - state, stateDB := state(1, int(height)) + state, stateDB, _ := makeState(1, int(height)) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, mockEvPoolAlwaysCommitted{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, mockEvPoolAlwaysCommitted{}) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) addr, _ := state.Validators.GetByIndex(0) @@ -145,12 +211,3 @@ func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { require.Error(t, err) require.IsType(t, err, &types.ErrEvidenceInvalid{}) } - -/* - TODO(#2589): - - test unmarshalling BlockParts that are too big into a Block that - (note this logic happens in the consensus, not in the validation here). - - test making blocks from the types.MaxXXX functions works/fails as expected -*/ -func TestValidateBlockSize(t *testing.T) { -} diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 00000000..603ac51d --- /dev/null +++ b/types/errors.go @@ -0,0 +1,41 @@ +package types + +import "fmt" + +type ( + // ErrInvalidCommitHeight is returned when we encounter a commit with an + // unexpected height. + ErrInvalidCommitHeight struct { + Expected int64 + Actual int64 + } + + // ErrInvalidCommitPrecommits is returned when we encounter a commit where + // the number of precommits doesn't match the number of validators. + ErrInvalidCommitPrecommits struct { + Expected int + Actual int + } +) + +func NewErrInvalidCommitHeight(expected, actual int64) ErrInvalidCommitHeight { + return ErrInvalidCommitHeight{ + Expected: expected, + Actual: actual, + } +} + +func (e ErrInvalidCommitHeight) Error() string { + return fmt.Sprintf("Invalid commit -- wrong height: %v vs %v", e.Expected, e.Actual) +} + +func NewErrInvalidCommitPrecommits(expected, actual int) ErrInvalidCommitPrecommits { + return ErrInvalidCommitPrecommits{ + Expected: expected, + Actual: actual, + } +} + +func (e ErrInvalidCommitPrecommits) Error() string { + return fmt.Sprintf("Invalid commit -- wrong set size: %v vs %v", e.Expected, e.Actual) +} diff --git a/types/validator_set.go b/types/validator_set.go index 9e78fbc7..65358714 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -594,10 +594,10 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i return err } if vals.Size() != len(commit.Precommits) { - return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits)) + return NewErrInvalidCommitPrecommits(vals.Size(), len(commit.Precommits)) } if height != commit.Height() { - return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height()) + return NewErrInvalidCommitHeight(height, commit.Height()) } if !blockID.Equals(commit.BlockID) { return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v",