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)
This commit is contained in:
Thane Thomson 2019-06-21 17:29:29 -04:00 committed by Ethan Buchman
parent 59497c362b
commit 228bba799d
12 changed files with 636 additions and 370 deletions

View File

@ -1,22 +1,20 @@
package state package state_test
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/example/kvstore"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/mock" "github.com/tendermint/tendermint/mock"
"github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/proxy"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time" tmtime "github.com/tendermint/tendermint/types/time"
) )
@ -34,10 +32,10 @@ func TestApplyBlock(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
defer proxyApp.Stop() defer proxyApp.Stop()
state, stateDB := state(1, 1) state, stateDB, _ := makeState(1, 1)
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(),
mock.Mempool{}, MockEvidencePool{}) mock.Mempool{}, sm.MockEvidencePool{})
block := makeBlock(state, 1) block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()}
@ -58,7 +56,7 @@ func TestBeginBlockValidators(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
defer proxyApp.Stop() defer proxyApp.Stop()
state, stateDB := state(2, 2) state, stateDB, _ := makeState(2, 2)
prevHash := state.LastBlockID.Hash prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{} prevParts := types.PartSetHeader{}
@ -84,7 +82,7 @@ func TestBeginBlockValidators(t *testing.T) {
// block for height 2 // block for height 2
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) 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) require.Nil(t, err, tc.desc)
// -> app receives a list of validators with a bool indicating if they signed // -> 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) require.Nil(t, err)
defer proxyApp.Stop() defer proxyApp.Stop()
state, stateDB := state(2, 12) state, stateDB, _ := makeState(2, 12)
prevHash := state.LastBlockID.Hash prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{} 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, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
block.Time = now block.Time = now
block.Evidence.Evidence = tc.evidence 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) require.Nil(t, err, tc.desc)
// -> app must receive an index of the byzantine validator // -> app must receive an index of the byzantine validator
@ -213,7 +211,7 @@ func TestValidateValidatorUpdates(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
err := validateValidatorUpdates(tc.abciUpdates, tc.validatorParams) err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams)
if tc.shouldErr { if tc.shouldErr {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
@ -307,9 +305,9 @@ func TestEndBlockValidatorUpdates(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
defer proxyApp.Stop() 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() eventBus := types.NewEventBus()
err = eventBus.Start() err = eventBus.Start()
@ -365,8 +363,8 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
defer proxyApp.Stop() 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) block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) 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
}

62
state/export_test.go Normal file
View File

@ -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)
}

280
state/helpers_test.go Normal file
View File

@ -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(&params)},
}
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
}

13
state/main_test.go Normal file
View File

@ -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())
}

View File

@ -43,7 +43,7 @@ type EvidencePool interface {
IsCommitted(types.Evidence) bool 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{} type MockEvidencePool struct{}
func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil } func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil }

View File

@ -1,4 +1,4 @@
package state package state_test
import ( import (
"bytes" "bytes"
@ -10,23 +10,22 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
sm "github.com/tendermint/tendermint/state"
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
// setupTestCase does setup common to all test cases. // 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_") config := cfg.ResetTestRoot("state_")
dbType := dbm.DBBackendType(config.DBBackend) dbType := dbm.DBBackendType(config.DBBackend)
stateDB := dbm.NewDB("state", dbType, config.DBDir()) 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") assert.NoError(t, err, "expected no error on LoadStateFromDBOrGenesisFile")
tearDown := func(t *testing.T) { os.RemoveAll(config.RootDir) } tearDown := func(t *testing.T) { os.RemoveAll(config.RootDir) }
@ -59,7 +58,7 @@ func TestMakeGenesisStateNilValidators(t *testing.T) {
Validators: nil, Validators: nil,
} }
require.Nil(t, doc.ValidateAndComplete()) require.Nil(t, doc.ValidateAndComplete())
state, err := MakeGenesisState(&doc) state, err := sm.MakeGenesisState(&doc)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, len(state.Validators.Validators)) require.Equal(t, 0, len(state.Validators.Validators))
require.Equal(t, 0, len(state.NextValidators.Validators)) require.Equal(t, 0, len(state.NextValidators.Validators))
@ -73,9 +72,9 @@ func TestStateSaveLoad(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
state.LastBlockHeight++ state.LastBlockHeight++
SaveState(stateDB, state) sm.SaveState(stateDB, state)
loadedState := LoadState(stateDB) loadedState := sm.LoadState(stateDB)
assert.True(state.Equals(loadedState), assert.True(state.Equals(loadedState),
fmt.Sprintf("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n", fmt.Sprintf("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n",
loadedState, state)) loadedState, state))
@ -92,15 +91,15 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
// Build mock responses. // Build mock responses.
block := makeBlock(state, 2) block := makeBlock(state, 2)
abciResponses := NewABCIResponses(block) abciResponses := sm.NewABCIResponses(block)
abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Events: nil} abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Events: nil}
abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Events: nil} abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Events: nil}
abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{ abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{
types.TM2PB.NewValidatorUpdate(ed25519.GenPrivKey().PubKey(), 10), types.TM2PB.NewValidatorUpdate(ed25519.GenPrivKey().PubKey(), 10),
}} }}
saveABCIResponses(stateDB, block.Height, abciResponses) sm.SaveABCIResponses(stateDB, block.Height, abciResponses)
loadedABCIResponses, err := LoadABCIResponses(stateDB, block.Height) loadedABCIResponses, err := sm.LoadABCIResponses(stateDB, block.Height)
assert.Nil(err) assert.Nil(err)
assert.Equal(abciResponses, loadedABCIResponses, assert.Equal(abciResponses, loadedABCIResponses,
fmt.Sprintf("ABCIResponses don't match:\ngot: %v\nexpected: %v\n", 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. // Query all before, this should return error.
for i := range cases { for i := range cases {
h := int64(i + 1) h := int64(i + 1)
res, err := LoadABCIResponses(stateDB, h) res, err := sm.LoadABCIResponses(stateDB, h)
assert.Error(err, "%d: %#v", i, res) assert.Error(err, "%d: %#v", i, res)
} }
// Add all cases. // Add all cases.
for i, tc := range cases { for i, tc := range cases {
h := int64(i + 1) // last block height, one below what we save h := int64(i + 1) // last block height, one below what we save
responses := &ABCIResponses{ responses := &sm.ABCIResponses{
DeliverTx: tc.added, DeliverTx: tc.added,
EndBlock: &abci.ResponseEndBlock{}, EndBlock: &abci.ResponseEndBlock{},
} }
saveABCIResponses(stateDB, h, responses) sm.SaveABCIResponses(stateDB, h, responses)
} }
// Query all before, should return expected value. // Query all before, should return expected value.
for i, tc := range cases { for i, tc := range cases {
h := int64(i + 1) h := int64(i + 1)
res, err := LoadABCIResponses(stateDB, h) res, err := sm.LoadABCIResponses(stateDB, h)
assert.NoError(err, "%d", i) assert.NoError(err, "%d", i)
assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i) assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i)
} }
@ -186,26 +185,26 @@ func TestValidatorSimpleSaveLoad(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// Can't load anything for height 0. // Can't load anything for height 0.
v, err := LoadValidators(stateDB, 0) v, err := sm.LoadValidators(stateDB, 0)
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0") assert.IsType(sm.ErrNoValSetForHeight{}, err, "expected err at height 0")
// Should be able to load for height 1. // 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.Nil(err, "expected no err at height 1")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// Should be able to load for height 2. // 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.Nil(err, "expected no err at height 2")
assert.Equal(v.Hash(), state.NextValidators.Hash(), "expected validator hashes to match") 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. // Increment height, save; should be able to load for next & next next height.
state.LastBlockHeight++ state.LastBlockHeight++
nextHeight := state.LastBlockHeight + 1 nextHeight := state.LastBlockHeight + 1
saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
vp0, err := LoadValidators(stateDB, nextHeight+0) vp0, err := sm.LoadValidators(stateDB, nextHeight+0)
assert.Nil(err, "expected no err") 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.Nil(err, "expected no err")
assert.Equal(vp0.Hash(), state.Validators.Hash(), "expected validator hashes to match") 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") assert.Equal(vp1.Hash(), state.NextValidators.Hash(), "expected next validator hashes to match")
@ -234,13 +233,13 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
changeIndex++ changeIndex++
power++ power++
} }
header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, power)
validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) require.NoError(t, err)
nextHeight := state.LastBlockHeight + 1 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. // On each height change, increment the power by one.
@ -258,7 +257,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
} }
for i, power := range testCases { 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.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()) assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
_, val := v.GetByIndex(0) _, val := v.GetByIndex(0)
@ -405,12 +404,12 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
block := makeBlock(state, state.LastBlockHeight+1) block := makeBlock(state, state.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()}
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) assert.NoError(t, err)
curTotal := val1VotingPower curTotal := val1VotingPower
// one increment step and one validator: 0 + power - total_power == 0 // 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} updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val2VotingPower}
validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal})
assert.NoError(t, err) 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) assert.NoError(t, err)
require.Equal(t, len(updatedState2.NextValidators.Validators), 2) 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) // this will cause the diff of priorities (77)
// to be larger than threshold == 2*totalVotingPower (22): // 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) assert.NoError(t, err)
require.Equal(t, len(updatedState3.NextValidators.Validators), 2) require.Equal(t, len(updatedState3.NextValidators.Validators), 2)
@ -516,13 +515,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
block := makeBlock(state, state.LastBlockHeight+1) block := makeBlock(state, state.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()}
// no updates: // no updates:
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) assert.NoError(t, err)
// 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 // 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}) validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal})
assert.NoError(t, err) 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) assert.NoError(t, err)
require.Equal(t, len(updatedState2.NextValidators.Validators), 2) 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) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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.NoError(t, err)
assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) 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 // no changes in voting power and both validators have same voting power
// -> proposers should alternate: // -> proposers should alternate:
oldState := updatedState3 oldState := updatedState3
abciResponses = &ABCIResponses{ abciResponses = &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) assert.NoError(t, err)
expectedVal1Prio2 = 1 expectedVal1Prio2 = 1
expectedVal2Prio2 = -1 expectedVal2Prio2 = -1
@ -613,13 +612,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
// no validator updates: // no validator updates:
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) assert.NoError(t, err)
// alternate (and cyclic priorities): // alternate (and cyclic priorities):
assert.NotEqual(t, updatedState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) 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 oldState := state
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
// no updates: // no updates:
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
@ -669,7 +668,7 @@ func TestLargeGenesisValidator(t *testing.T) {
block := makeBlock(oldState, oldState.LastBlockHeight+1) block := makeBlock(oldState, oldState.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) require.NoError(t, err)
// no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0,
// than -Total == -Voting) // than -Total == -Voting)
@ -689,18 +688,18 @@ func TestLargeGenesisValidator(t *testing.T) {
firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower} firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower}
validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal})
assert.NoError(t, err) assert.NoError(t, err)
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}},
} }
block := makeBlock(oldState, oldState.LastBlockHeight+1) block := makeBlock(oldState, oldState.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) require.NoError(t, err)
lastState := updatedState lastState := updatedState
for i := 0; i < 200; i++ { for i := 0; i < 200; i++ {
// no updates: // no updates:
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
@ -709,7 +708,7 @@ func TestLargeGenesisValidator(t *testing.T) {
block := makeBlock(lastState, lastState.LastBlockHeight+1) block := makeBlock(lastState, lastState.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) require.NoError(t, err)
lastState = updatedStateInner lastState = updatedStateInner
} }
@ -734,26 +733,26 @@ func TestLargeGenesisValidator(t *testing.T) {
validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal})
assert.NoError(t, err) assert.NoError(t, err)
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}},
} }
block := makeBlock(oldState, oldState.LastBlockHeight+1) block := makeBlock(oldState, oldState.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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.NoError(t, err)
} }
require.Equal(t, 10+2, len(state.NextValidators.Validators)) require.Equal(t, 10+2, len(state.NextValidators.Validators))
// remove genesis validator: // remove genesis validator:
removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0} removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0}
abciResponses = &ABCIResponses{ abciResponses = &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}},
} }
block = makeBlock(oldState, oldState.LastBlockHeight+1) block = makeBlock(oldState, oldState.LastBlockHeight+1)
blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()}
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) require.NoError(t, err)
// only the first added val (not the genesis val) should be left // only the first added val (not the genesis val) should be left
assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) assert.Equal(t, 11, len(updatedState.NextValidators.Validators))
@ -764,14 +763,14 @@ func TestLargeGenesisValidator(t *testing.T) {
count := 0 count := 0
isProposerUnchanged := true isProposerUnchanged := true
for isProposerUnchanged { for isProposerUnchanged {
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err) require.NoError(t, err)
block = makeBlock(curState, curState.LastBlockHeight+1) block = makeBlock(curState, curState.LastBlockHeight+1)
blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) require.NoError(t, err)
if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) {
isProposerUnchanged = false isProposerUnchanged = false
@ -787,7 +786,7 @@ func TestLargeGenesisValidator(t *testing.T) {
proposers := make([]*types.Validator, numVals) proposers := make([]*types.Validator, numVals)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
// no updates: // no updates:
abciResponses := &ABCIResponses{ abciResponses := &sm.ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
} }
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
@ -796,7 +795,7 @@ func TestLargeGenesisValidator(t *testing.T) {
block := makeBlock(updatedState, updatedState.LastBlockHeight+1) block := makeBlock(updatedState, updatedState.LastBlockHeight+1)
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} 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) require.NoError(t, err)
if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks):
if proposers[i%numVals] == nil { if proposers[i%numVals] == nil {
@ -814,15 +813,15 @@ func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) {
defer tearDown(t) defer tearDown(t)
state.Validators = genValSet(valSetSize) state.Validators = genValSet(valSetSize)
state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
SaveState(stateDB, state) sm.SaveState(stateDB, state)
nextHeight := state.LastBlockHeight + 1 nextHeight := state.LastBlockHeight + 1
v0, err := LoadValidators(stateDB, nextHeight) v0, err := sm.LoadValidators(stateDB, nextHeight)
assert.Nil(t, err) assert.Nil(t, err)
acc0 := v0.Validators[0].ProposerPriority acc0 := v0.Validators[0].ProposerPriority
v1, err := LoadValidators(stateDB, nextHeight+1) v1, err := sm.LoadValidators(stateDB, nextHeight+1)
assert.Nil(t, err) assert.Nil(t, err)
acc1 := v1.Validators[0].ProposerPriority acc1 := v1.Validators[0].ProposerPriority
@ -838,28 +837,27 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) {
require.Equal(t, int64(0), state.LastBlockHeight) require.Equal(t, int64(0), state.LastBlockHeight)
state.Validators = genValSet(valSetSize) state.Validators = genValSet(valSetSize)
state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
SaveState(stateDB, state) sm.SaveState(stateDB, state)
_, valOld := state.Validators.GetByIndex(0) _, valOld := state.Validators.GetByIndex(0)
var pubkeyOld = valOld.PubKey var pubkeyOld = valOld.PubKey
pubkey := ed25519.GenPrivKey().PubKey() pubkey := ed25519.GenPrivKey().PubKey()
const height = 1
// Swap the first validator with a new one (validator set size stays the same). // 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. // Save state etc.
var err error var err error
var validatorUpdates []*types.Validator var validatorUpdates []*types.Validator
validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) require.Nil(t, err)
nextHeight := state.LastBlockHeight + 1 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. // Load nextheight, it should be the oldpubkey.
v0, err := LoadValidators(stateDB, nextHeight) v0, err := sm.LoadValidators(stateDB, nextHeight)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, valSetSize, v0.Size()) assert.Equal(t, valSetSize, v0.Size())
index, val := v0.GetByAddress(pubkeyOld.Address()) index, val := v0.GetByAddress(pubkeyOld.Address())
@ -869,7 +867,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) {
} }
// Load nextheight+1, it should be the new pubkey. // 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.Nil(t, err)
assert.Equal(t, valSetSize, v1.Size()) assert.Equal(t, valSetSize, v1.Size())
index, val = v1.GetByAddress(pubkey.Address()) 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) { func TestStateMakeBlock(t *testing.T) {
tearDown, _, state := setupTestCase(t) tearDown, _, state := setupTestCase(t)
defer tearDown(t) defer tearDown(t)
@ -932,14 +922,14 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
changeIndex++ changeIndex++
cp = params[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) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates)
require.NoError(t, err) 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) require.Nil(t, err)
nextHeight := state.LastBlockHeight + 1 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. // 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 { 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.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 assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at
height %d`, testCase.height)) 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) { func TestApplyUpdates(t *testing.T) {
initParams := makeParams(1, 2, 3, 4) initParams := makeConsensusParams(1, 2, 3, 4)
cases := [...]struct { cases := [...]struct {
init types.ConsensusParams init types.ConsensusParams
@ -998,14 +971,14 @@ func TestApplyUpdates(t *testing.T) {
MaxGas: 55, MaxGas: 55,
}, },
}, },
makeParams(44, 55, 3, 4)}, makeConsensusParams(44, 55, 3, 4)},
3: {initParams, 3: {initParams,
abci.ConsensusParams{ abci.ConsensusParams{
Evidence: &abci.EvidenceParams{ Evidence: &abci.EvidenceParams{
MaxAge: 66, MaxAge: 66,
}, },
}, },
makeParams(1, 2, 3, 66)}, makeConsensusParams(1, 2, 3, 66)},
} }
for i, tc := range cases { for i, tc := range cases {
@ -1013,61 +986,3 @@ func TestApplyUpdates(t *testing.T) {
assert.Equal(t, tc.expected, res, "case %d", i) 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(&params)},
}
return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses
}
type paramsChangeTestCase struct {
height int64
params types.ConsensusParams
}

View File

@ -1,4 +1,4 @@
package state package state_test
import ( import (
"fmt" "fmt"
@ -10,6 +10,7 @@ import (
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
@ -19,9 +20,9 @@ func TestStoreLoadValidators(t *testing.T) {
vals := types.NewValidatorSet([]*types.Validator{val}) vals := types.NewValidatorSet([]*types.Validator{val})
// 1) LoadValidators loads validators using a height where they were last changed // 1) LoadValidators loads validators using a height where they were last changed
saveValidatorsInfo(stateDB, 1, 1, vals) sm.SaveValidatorsInfo(stateDB, 1, 1, vals)
saveValidatorsInfo(stateDB, 2, 1, vals) sm.SaveValidatorsInfo(stateDB, 2, 1, vals)
loadedVals, err := LoadValidators(stateDB, 2) loadedVals, err := sm.LoadValidators(stateDB, 2)
require.NoError(t, err) require.NoError(t, err)
assert.NotZero(t, loadedVals.Size()) assert.NotZero(t, loadedVals.Size())
@ -30,13 +31,13 @@ func TestStoreLoadValidators(t *testing.T) {
// TODO(melekes): REMOVE in 0.33 release // TODO(melekes): REMOVE in 0.33 release
// https://github.com/tendermint/tendermint/issues/3543 // https://github.com/tendermint/tendermint/issues/3543
// for releases prior to v0.31.4, it uses last height changed // for releases prior to v0.31.4, it uses last height changed
valInfo := &ValidatorsInfo{ valInfo := &sm.ValidatorsInfo{
LastHeightChanged: valSetCheckpointInterval, LastHeightChanged: sm.ValSetCheckpointInterval,
} }
stateDB.Set(calcValidatorsKey(valSetCheckpointInterval), valInfo.Bytes()) stateDB.Set(sm.CalcValidatorsKey(sm.ValSetCheckpointInterval), valInfo.Bytes())
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
saveValidatorsInfo(stateDB, valSetCheckpointInterval+1, 1, vals) sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval+1, 1, vals)
loadedVals, err := LoadValidators(stateDB, valSetCheckpointInterval+1) loadedVals, err := sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval+1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -46,9 +47,9 @@ func TestStoreLoadValidators(t *testing.T) {
}) })
// ENDREMOVE // 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) require.NoError(t, err)
assert.NotZero(t, loadedVals.Size()) assert.NotZero(t, loadedVals.Size())
} }
@ -60,20 +61,20 @@ func BenchmarkLoadValidators(b *testing.B) {
defer os.RemoveAll(config.RootDir) defer os.RemoveAll(config.RootDir)
dbType := dbm.DBBackendType(config.DBBackend) dbType := dbm.DBBackendType(config.DBBackend)
stateDB := dbm.NewDB("state", dbType, config.DBDir()) stateDB := dbm.NewDB("state", dbType, config.DBDir())
state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile())
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
state.Validators = genValSet(valSetSize) state.Validators = genValSet(valSetSize)
state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
SaveState(stateDB, state) sm.SaveState(stateDB, state)
for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... 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) { b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
_, err := LoadValidators(stateDB, int64(i)) _, err := sm.LoadValidators(stateDB, int64(i))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@ -1,4 +1,4 @@
package state package state_test
import ( import (
"os" "os"
@ -7,11 +7,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
) )
func TestTxFilter(t *testing.T) { func TestTxFilter(t *testing.T) {
@ -34,10 +33,10 @@ func TestTxFilter(t *testing.T) {
for i, tc := range testCases { for i, tc := range testCases {
stateDB := dbm.NewDB("state", "memdb", os.TempDir()) stateDB := dbm.NewDB("state", "memdb", os.TempDir())
state, err := LoadStateFromDBOrGenesisDoc(stateDB, genDoc) state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
require.NoError(t, err) require.NoError(t, err)
f := TxPreCheck(state) f := sm.TxPreCheck(state)
if tc.isErr { if tc.isErr {
assert.NotNil(t, f(tc.tx), "#%v", i) assert.NotNil(t, f(tc.tx), "#%v", i)
} else { } 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(),
}
}

View File

@ -94,10 +94,7 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
} }
} else { } else {
if len(block.LastCommit.Precommits) != state.LastValidators.Size() { if len(block.LastCommit.Precommits) != state.LastValidators.Size() {
return fmt.Errorf("Invalid block commit size. Expected %v, got %v", return types.NewErrInvalidCommitPrecommits(state.LastValidators.Size(), len(block.LastCommit.Precommits))
state.LastValidators.Size(),
len(block.LastCommit.Precommits),
)
} }
err := state.LastValidators.VerifyCommit( err := state.LastValidators.VerifyCommit(
state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit) state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit)

View File

@ -1,31 +1,30 @@
package state package state_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/mock"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
) )
// TODO(#2589): const validationTestsStopHeight int64 = 10
// - 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)
func TestValidateBlockHeader(t *testing.T) { func TestValidateBlockHeader(t *testing.T) {
var height int64 = 1 // TODO(#2589): generalize proxyApp := newTestApp()
state, stateDB := state(1, int(height)) require.NoError(t, proxyApp.Start())
defer proxyApp.Stop()
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) state, stateDB, privVals := makeState(3, 1)
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{})
// A good block passes. lastCommit := types.NewCommit(types.BlockID{}, nil)
block := makeBlock(state, height)
err := blockExec.ValidateBlock(state, block)
require.NoError(t, err)
// some bad values // some bad values
wrongHash := tmhash.Sum([]byte("this hash is wrong")) 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 }}, {"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }},
{"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }},
{"Height wrong", func(block *types.Block) { block.Height += 10 }}, {"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 }}, {"NumTxs wrong", func(block *types.Block) { block.NumTxs += 10 }},
{"TotalTxs wrong", func(block *types.Block) { block.TotalTxs += 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") }}, {"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }},
} }
// 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 { for _, tc := range testCases {
block := makeBlock(state, height) block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, proposerAddr)
tc.malleateBlock(block) tc.malleateBlock(block)
err := blockExec.ValidateBlock(state, block) err := blockExec.ValidateBlock(state, block)
require.Error(t, err, tc.name) require.Error(t, err, tc.name)
} }
}
/* /*
TODO(#2589): A good block passes
- test Block.Data.Hash() == Block.DataHash
- test len(Block.Data.Txs) == Block.NumTxs
*/ */
func TestValidateBlockData(t *testing.T) { var err error
state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil)
require.NoError(t, err, "height %d", height)
}
} }
/*
TODO(#2589):
- test len(block.LastCommit.Precommits) == state.LastValidators.Size()
- test state.LastValidators.VerifyCommit
*/
func TestValidateBlockCommit(t *testing.T) { func TestValidateBlockCommit(t *testing.T) {
proxyApp := newTestApp()
require.NoError(t, proxyApp.Start())
defer proxyApp.Stop()
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()
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)
/*
#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)
} }
/* /*
TODO(#2589): A good block passes
- test good/bad evidence in block
*/ */
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)
/*
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()})
}
}
func TestValidateBlockEvidence(t *testing.T) { func TestValidateBlockEvidence(t *testing.T) {
var height int64 = 1 // TODO(#2589): generalize proxyApp := newTestApp()
state, stateDB := state(1, int(height)) require.NoError(t, proxyApp.Start())
defer proxyApp.Stop()
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) state, stateDB, privVals := makeState(3, 1)
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{})
lastCommit := types.NewCommit(types.BlockID{}, nil)
// make some evidence for height := int64(1); height < validationTestsStopHeight; height++ {
addr, _ := state.Validators.GetByIndex(0) proposerAddr := state.Validators.GetProposer().Address
goodEvidence := types.NewMockGoodEvidence(height, 0, addr) proposerIdx, _ := state.Validators.GetByAddress(proposerAddr)
goodEvidence := types.NewMockGoodEvidence(height, proposerIdx, proposerAddr)
// A block with a couple pieces of evidence passes. if height > 1 {
block := makeBlock(state, height) /*
block.Evidence.Evidence = []types.Evidence{goodEvidence, goodEvidence} A block with too much evidence fails
block.EvidenceHash = block.Evidence.Hash() */
err := blockExec.ValidateBlock(state, block)
require.NoError(t, err)
// A block with too much evidence fails.
maxBlockSize := state.ConsensusParams.Block.MaxBytes maxBlockSize := state.ConsensusParams.Block.MaxBytes
maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize)
require.True(t, maxNumEvidence > 2) require.True(t, maxNumEvidence > 2)
for i := int64(0); i < maxNumEvidence; i++ { evidence := make([]types.Evidence, 0)
block.Evidence.Evidence = append(block.Evidence.Evidence, goodEvidence) // one more than the maximum allowed evidence
for i := int64(0); i <= maxNumEvidence; i++ {
evidence = append(evidence, goodEvidence)
} }
block.EvidenceHash = block.Evidence.Hash() block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr)
err = blockExec.ValidateBlock(state, block) err := blockExec.ValidateBlock(state, block)
require.Error(t, err)
_, ok := err.(*types.ErrEvidenceOverflow) _, ok := err.(*types.ErrEvidenceOverflow)
require.True(t, ok) require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d", height)
} }
// always returns true if asked if any evidence was already committed. /*
type mockEvPoolAlwaysCommitted struct{} 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)
}
func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil } var err error
func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil } state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, evidence)
func (m mockEvPoolAlwaysCommitted) Update(*types.Block, State) {} require.NoError(t, err, "height %d", height)
func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true } }
}
func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { func TestValidateFailBlockOnCommittedEvidence(t *testing.T) {
var height int64 = 1 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. // A block with a couple pieces of evidence passes.
block := makeBlock(state, height) block := makeBlock(state, height)
addr, _ := state.Validators.GetByIndex(0) addr, _ := state.Validators.GetByIndex(0)
@ -145,12 +211,3 @@ func TestValidateFailBlockOnCommittedEvidence(t *testing.T) {
require.Error(t, err) require.Error(t, err)
require.IsType(t, err, &types.ErrEvidenceInvalid{}) 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) {
}

41
types/errors.go Normal file
View File

@ -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)
}

View File

@ -594,10 +594,10 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
return err return err
} }
if vals.Size() != len(commit.Precommits) { 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() { 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) { if !blockID.Equals(commit.BlockID) {
return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v", return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v",