From f82b7e2a133909e59387a39580d5dcc26922ec61 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 20:03:48 -0500 Subject: [PATCH] state: re-order funcs. fix tests --- state/db.go | 45 +++-- state/execution.go | 394 +++++++++++++++------------------------ state/execution_test.go | 84 ++------- state/state.go | 111 +++-------- state/state_test.go | 106 +++++------ state/validation.go | 136 ++++++++++++++ state/validation_test.go | 64 +++++++ 7 files changed, 479 insertions(+), 461 deletions(-) create mode 100644 state/validation.go create mode 100644 state/validation_test.go diff --git a/state/db.go b/state/db.go index 08dd61d1d..32f625841 100644 --- a/state/db.go +++ b/state/db.go @@ -11,37 +11,50 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) +//------------------------------------------------------------------------ + +func calcValidatorsKey(height int64) []byte { + return []byte(cmn.Fmt("validatorsKey:%v", height)) +} + +func calcConsensusParamsKey(height int64) []byte { + return []byte(cmn.Fmt("consensusParamsKey:%v", height)) +} + +func calcABCIResponsesKey(height int64) []byte { + return []byte(cmn.Fmt("abciResponsesKey:%v", height)) +} + // GetState loads the most recent state from the database, // or creates a new one from the given genesisFile and persists the result // to the database. -func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { +func GetState(stateDB dbm.DB, genesisFile string) (State, error) { state := LoadState(stateDB) - if state == nil { + if state.IsEmpty() { var err error state, err = MakeGenesisStateFromFile(genesisFile) if err != nil { - return nil, err + return state, err } - state.Save(stateDB, state.AppHash) + SaveState(stateDB, state, state.AppHash) } return state, nil } // LoadState loads the State from the database. -func LoadState(db dbm.DB) *State { +func LoadState(db dbm.DB) State { return loadState(db, stateKey) } -func loadState(db dbm.DB, key []byte) *State { +func loadState(db dbm.DB, key []byte) (state State) { buf := db.Get(key) if len(buf) == 0 { - return nil + return state } - s := new(State) r, n, err := bytes.NewReader(buf), new(int), new(error) - wire.ReadBinaryPtr(&s, r, 0, n, err) + wire.ReadBinaryPtr(&state, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED cmn.Exit(cmn.Fmt(`LoadState: Data has been corrupted or its spec has changed: @@ -49,7 +62,17 @@ func loadState(db dbm.DB, key []byte) *State { } // TODO: ensure that buf is completely read. - return s + return state +} + +// SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. +// It sets the given appHash on the state before persisting. +func SaveState(db dbm.DB, s State, appHash []byte) { + s.AppHash = appHash + nextHeight := s.LastBlockHeight + 1 + saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators) + saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams) + db.SetSync(stateKey, s.Bytes()) } //------------------------------------------------------------------------ @@ -104,7 +127,7 @@ func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) { // SaveABCIResponses persists the ABCIResponses to the database. // This is useful in case we crash after app.Commit and before s.Save(). // Responses are indexed by height so they can also be loaded later to produce Merkle proofs. -func SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { +func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes()) } diff --git a/state/execution.go b/state/execution.go index 969c33288..88ee1127a 100644 --- a/state/execution.go +++ b/state/execution.go @@ -1,7 +1,6 @@ package state import ( - "bytes" "errors" "fmt" @@ -14,27 +13,119 @@ import ( "github.com/tendermint/tmlibs/log" ) -//-------------------------------------------------- -// Execute the block +//----------------------------------------------------------------------------- +// BlockExecutor handles block execution and state updates. +// It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses, +// then commits and updates the mempool atomically, then saves state. -// ValExecBlock executes the block and returns the responses. It does NOT mutate State. -// + validates the block -// + executes block.Txs on the proxyAppConn -func (blockExec *BlockExecutor) ValExecBlock(s State, block *types.Block) (*ABCIResponses, error) { - if err := s.validateBlock(block); err != nil { - return nil, ErrInvalidBlock(err) +// BlockExecutor provides the context and accessories for properly executing a block. +type BlockExecutor struct { + db dbm.DB + logger log.Logger + + txEventPublisher types.TxEventPublisher + proxyApp proxy.AppConnConsensus + + mempool types.Mempool + evpool types.EvidencePool +} + +// NewBlockExecutor returns a new BlockExecutor. +func NewBlockExecutor(db dbm.DB, logger log.Logger, + txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus, + mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor { + return &BlockExecutor{ + db, + logger, + txEventer, + proxyApp, + mempool, + evpool, + } +} + +// ApplyBlock validates the block against the state, executes it against the app, +// commits it, and saves the block and state. It's the only function that needs to be called +// from outside this package to process and commit an entire block. +// It takes a blockID to avoid recomputing the parts hash. +func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block *types.Block) (State, error) { + + if err := validateBlock(s, block); err != nil { + return s, ErrInvalidBlock(err) } abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block) if err != nil { - // There was some error in proxyApp - // TODO Report error and wait for proxyApp to be available. - return nil, ErrProxyAppConn(err) + return s, ErrProxyAppConn(err) } - return abciResponses, nil + fireEvents(blockExec.txEventPublisher, block, abciResponses) + + fail.Fail() // XXX + + // save the results before we commit + saveABCIResponses(blockExec.db, block.Height, abciResponses) + + fail.Fail() // XXX + + // update the state with the block and responses + s, err = updateState(s, blockID, block.Header, abciResponses) + if err != nil { + return s, fmt.Errorf("Commit failed for application: %v", err) + } + + // lock mempool, commit state, update mempoool + appHash, err := blockExec.Commit(block) + if err != nil { + return s, fmt.Errorf("Commit failed for application: %v", err) + } + + fail.Fail() // XXX + + // save the state and the validators + SaveState(blockExec.db, s, appHash) + + return s, nil } +// Commit locks the mempool, runs the ABCI Commit message, and updates the mempool. +// It returns the result of calling abci.Commit (the AppHash), and an error. +// The Mempool must be locked during commit and update because state is typically reset on Commit and old txs must be replayed +// against committed state before new txs are run in the mempool, lest they be invalid. +func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { + blockExec.mempool.Lock() + defer blockExec.mempool.Unlock() + + // Commit block, get hash back + res, err := blockExec.proxyApp.CommitSync() + if err != nil { + blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err) + return nil, err + } + if res.IsErr() { + blockExec.logger.Error("Error in proxyAppConn.CommitSync", "err", res) + return nil, res + } + if res.Log != "" { + blockExec.logger.Debug("Commit.Log: " + res.Log) + } + + blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data) + + // Update evpool + blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) + + // Update mempool. + if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil { + return nil, err + } + + return res.Data, nil +} + +//--------------------------------------------------------- +// Helper functions for executing blocks and updating state + // Executes block's transactions on proxyAppConn. // Returns a list of transaction results and updates to the validator set func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, block *types.Block) (*ABCIResponses, error) { @@ -196,189 +287,62 @@ func changeInVotingPowerMoreOrEqualToOneThird(currentSet *types.ValidatorSet, up return false, nil } -//----------------------------------------------------- -// Validate block +// updateState returns a new State updated according to the header and responses. +func updateState(s State, blockID types.BlockID, header *types.Header, + abciResponses *ABCIResponses) (State, error) { -// MakeBlock builds a block with the given txs and commit from the current state. -func (s State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) { - // build base block - block := types.MakeBlock(height, txs, commit) + // copy the valset so we can apply changes from EndBlock + // and update s.LastValidators and s.Validators + prevValSet := s.Validators.Copy() + nextValSet := prevValSet.Copy() - // fill header with state data - block.ChainID = s.ChainID - block.TotalTxs = s.LastBlockTotalTx + block.NumTxs - block.LastBlockID = s.LastBlockID - block.ValidatorsHash = s.Validators.Hash() - block.AppHash = s.AppHash - block.ConsensusHash = s.ConsensusParams.Hash() - block.LastResultsHash = s.LastResultsHash - - return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes) -} - -// ValidateBlock validates the block against the state. -func (s State) ValidateBlock(block *types.Block) error { - return s.validateBlock(block) -} - -func (s State) validateBlock(b *types.Block) error { - // validate internal consistency - if err := b.ValidateBasic(); err != nil { - return err - } - - // validate basic info - if b.ChainID != s.ChainID { - return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID) - } - if b.Height != s.LastBlockHeight+1 { - return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.LastBlockHeight+1, b.Height) - } - /* TODO: Determine bounds for Time - See blockchain/reactor "stopSyncingDurationMinutes" - - if !b.Time.After(lastBlockTime) { - return errors.New("Invalid Block.Header.Time") - } - */ - - // validate prev block info - if !b.LastBlockID.Equals(s.LastBlockID) { - return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID) - } - newTxs := int64(len(b.Data.Txs)) - if b.TotalTxs != s.LastBlockTotalTx+newTxs { - return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs) - } - - // validate app info - if !bytes.Equal(b.AppHash, s.AppHash) { - return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash) - } - if !bytes.Equal(b.ConsensusHash, s.ConsensusParams.Hash()) { - return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.ConsensusHash) - } - if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) { - return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash) - } - if !bytes.Equal(b.ValidatorsHash, s.Validators.Hash()) { - return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.ValidatorsHash) - } - - // Validate block LastCommit. - if b.Height == 1 { - if len(b.LastCommit.Precommits) != 0 { - return errors.New("Block at height 1 (first block) should have no LastCommit precommits") - } - } else { - if len(b.LastCommit.Precommits) != s.LastValidators.Size() { - return fmt.Errorf("Invalid block commit size. Expected %v, got %v", - s.LastValidators.Size(), len(b.LastCommit.Precommits)) - } - err := s.LastValidators.VerifyCommit( - s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit) + // update the validator set with the latest abciResponses + lastHeightValsChanged := s.LastHeightValidatorsChanged + if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { + err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates) if err != nil { - return err + return s, fmt.Errorf("Error changing validator set: %v", err) } + // change results from this height but only applies to the next height + lastHeightValsChanged = header.Height + 1 } - for _, ev := range b.Evidence.Evidence { - if err := VerifyEvidence(s, ev); err != nil { - return types.NewEvidenceInvalidErr(ev, err) - } - /* // Needs a db ... - valset, err := LoadValidators(s.db, ev.Height()) + // Update validator accums and set state variables + nextValSet.IncrementAccum(1) + + // update the params with the latest abciResponses + nextParams := s.ConsensusParams + lastHeightParamsChanged := s.LastHeightConsensusParamsChanged + if abciResponses.EndBlock.ConsensusParamUpdates != nil { + // NOTE: must not mutate s.ConsensusParams + nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates) + err := nextParams.Validate() if err != nil { - // XXX/TODO: what do we do if we can't load the valset? - // eg. if we have pruned the state or height is too high? - return err + return s, fmt.Errorf("Error updating consensus params: %v", err) } - if err := VerifyEvidenceValidator(valSet, ev); err != nil { - return types.NewEvidenceInvalidErr(ev, err) - } - */ + // change results from this height but only applies to the next height + lastHeightParamsChanged = header.Height + 1 } - return nil + // NOTE: the AppHash has not been populated. + // It will be filled on state.Save. + return State{ + ChainID: s.ChainID, + LastBlockHeight: header.Height, + LastBlockTotalTx: s.LastBlockTotalTx + header.NumTxs, + LastBlockID: blockID, + LastBlockTime: header.Time, + Validators: nextValSet, + LastValidators: s.Validators.Copy(), + LastHeightValidatorsChanged: lastHeightValsChanged, + ConsensusParams: nextParams, + LastHeightConsensusParamsChanged: lastHeightParamsChanged, + LastResultsHash: abciResponses.ResultsHash(), + AppHash: nil, + }, nil } -// XXX: What's cheaper (ie. what should be checked first): -// evidence internal validity (ie. sig checks) or validator existed (fetch historical val set from db) - -// VerifyEvidence verifies the evidence fully by checking it is internally -// consistent and sufficiently recent. -func VerifyEvidence(s State, evidence types.Evidence) error { - height := s.LastBlockHeight - - evidenceAge := height - evidence.Height() - maxAge := s.ConsensusParams.EvidenceParams.MaxAge - if evidenceAge > maxAge { - return fmt.Errorf("Evidence from height %d is too old. Min height is %d", - evidence.Height(), height-maxAge) - } - - if err := evidence.Verify(s.ChainID); err != nil { - return err - } - return nil -} - -// VerifyEvidenceValidator returns the voting power of the validator at the height of the evidence. -// It returns an error if the validator did not exist or does not match that loaded from the historical validator set. -func VerifyEvidenceValidator(valset *types.ValidatorSet, evidence types.Evidence) (priority int64, err error) { - // The address must have been an active validator at the height - ev := evidence - height, addr, idx := ev.Height(), ev.Address(), ev.Index() - valIdx, val := valset.GetByAddress(addr) - if val == nil { - return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height) - } else if idx != valIdx { - return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx) - } - - priority = val.VotingPower - return priority, nil -} - -//----------------------------------------------------------------------------- -// ApplyBlock validates & executes the block, updates state w/ ABCI responses, -// then commits and updates the mempool atomically, then saves state. - -// BlockExecutor provides the context and accessories for properly executing a block. -type BlockExecutor struct { - db dbm.DB - logger log.Logger - - txEventPublisher types.TxEventPublisher - proxyApp proxy.AppConnConsensus - - mempool types.Mempool - evpool types.EvidencePool -} - -func NewBlockExecutor(db dbm.DB, logger log.Logger, txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus, - mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor { - return &BlockExecutor{ - db, - logger, - txEventer, - proxyApp, - mempool, - evpool, - } -} - -// ApplyBlock validates the block against the state, executes it against the app, -// commits it, and saves the block and state. It's the only function that needs to be called -// from outside this package to process and commit an entire block. -// It takes a blockID to avoid recomputing the parts hash. -func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block *types.Block) (State, error) { - - abciResponses, err := blockExec.ValExecBlock(s, block) - if err != nil { - return s, fmt.Errorf("Exec failed for application: %v", err) - } - +func fireEvents(txEventPublisher types.TxEventPublisher, block *types.Block, abciResponses *ABCIResponses) { // TODO: Fire events /* tx := types.Tx(req.GetDeliverTx().Tx) @@ -389,68 +353,10 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block Result: *txRes, }}) */ - - fail.Fail() // XXX - - // save the results before we commit - SaveABCIResponses(blockExec.db, block.Height, abciResponses) - - fail.Fail() // XXX - - // update the state with the block and responses - s, err = s.NextState(blockID, block.Header, abciResponses) - if err != nil { - return s, fmt.Errorf("Commit failed for application: %v", err) - } - - // lock mempool, commit state, update mempoool - appHash, err := blockExec.Commit(block) - if err != nil { - return s, fmt.Errorf("Commit failed for application: %v", err) - } - - fail.Fail() // XXX - - // save the state and the validators - s.Save(blockExec.db, appHash) - - return s, nil } -// Commit locks the mempool, runs the ABCI Commit message, and updates the mempool. -// It returns the result of calling abci.Commit (the AppHash), and an error. -// The Mempool must be locked during commit and update because state is typically reset on Commit and old txs must be replayed -// against committed state before new txs are run in the mempool, lest they be invalid. -func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { - blockExec.mempool.Lock() - defer blockExec.mempool.Unlock() - - // Commit block, get hash back - res, err := blockExec.proxyApp.CommitSync() - if err != nil { - blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err) - return nil, err - } - if res.IsErr() { - blockExec.logger.Error("Error in proxyAppConn.CommitSync", "err", res) - return nil, res - } - if res.Log != "" { - blockExec.logger.Debug("Commit.Log: " + res.Log) - } - - blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data) - - // Update evpool - blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) - - // Update mempool. - if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil { - return nil, err - } - - return res.Data, nil -} +//---------------------------------------------------------------------------------------------------- +// Execute block without state. TODO: eliminate // ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state. // It returns the application root hash (result of abci.Commit). diff --git a/state/execution_test.go b/state/execution_test.go index 7cda5c1da..1a63d3ed0 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -23,64 +23,6 @@ var ( nTxsPerBlock = 10 ) -func TestValidateBlock(t *testing.T) { - state := state() - state.SetLogger(log.TestingLogger()) - - // proper block must pass - block := makeBlock(state, 1) - err := state.ValidateBlock(block) - require.NoError(t, err) - - // wrong chain fails - block = makeBlock(state, 1) - block.ChainID = "not-the-real-one" - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong height fails - block = makeBlock(state, 1) - block.Height += 10 - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong total tx fails - block = makeBlock(state, 1) - block.TotalTxs += 10 - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong blockid fails - block = makeBlock(state, 1) - block.LastBlockID.PartsHeader.Total += 10 - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong app hash fails - block = makeBlock(state, 1) - block.AppHash = []byte("wrong app hash") - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong consensus hash fails - block = makeBlock(state, 1) - block.ConsensusHash = []byte("wrong consensus hash") - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong results hash fails - block = makeBlock(state, 1) - block.LastResultsHash = []byte("wrong results hash") - err = state.ValidateBlock(block) - require.Error(t, err) - - // wrong validators hash fails - block = makeBlock(state, 1) - block.ValidatorsHash = []byte("wrong validators hash") - err = state.ValidateBlock(block) - require.Error(t, err) -} - func TestApplyBlock(t *testing.T) { cc := proxy.NewLocalClientCreator(dummy.NewDummyApplication()) proxyApp := proxy.NewAppConns(cc, nil) @@ -88,15 +30,16 @@ func TestApplyBlock(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state := state() - state.SetLogger(log.TestingLogger()) + state, stateDB := state(), dbm.NewMemDB() - block := makeBlock(state, 1) - - err = state.ApplyBlock(types.NopEventBus{}, proxyApp.Consensus(), - block, block.MakePartSet(testPartSize).Header(), + blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), + types.NopEventBus{}, proxyApp.Consensus(), types.MockMempool{}, types.MockEvidencePool{}) + block := makeBlock(state, 1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + + state, err = blockExec.ApplyBlock(state, blockID, block) require.Nil(t, err) // TODO check state and mempool @@ -112,15 +55,14 @@ func TestBeginBlockAbsentValidators(t *testing.T) { defer proxyApp.Stop() state := state() - state.SetLogger(log.TestingLogger()) // there were 2 validators - val1PrivKey := crypto.GenPrivKeyEd25519() + /*val1PrivKey := crypto.GenPrivKeyEd25519() val2PrivKey := crypto.GenPrivKeyEd25519() lastValidators := types.NewValidatorSet([]*types.Validator{ types.NewValidator(val1PrivKey.PubKey(), 10), types.NewValidator(val2PrivKey.PubKey(), 5), - }) + })*/ prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} @@ -141,7 +83,7 @@ func TestBeginBlockAbsentValidators(t *testing.T) { lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits} block, _ := state.MakeBlock(2, makeTxs(2), lastCommit) - _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), lastValidators) + _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger()) require.Nil(t, err, tc.desc) // -> app must receive an index of the absent validator @@ -159,8 +101,8 @@ func makeTxs(height int64) (txs []types.Tx) { return txs } -func state() *State { - s, _ := MakeGenesisState(dbm.NewMemDB(), &types.GenesisDoc{ +func state() State { + s, _ := MakeGenesisState(&types.GenesisDoc{ ChainID: chainID, Validators: []types.GenesisValidator{ {privKey.PubKey(), 10000, "test"}, @@ -170,7 +112,7 @@ func state() *State { return s } -func makeBlock(state *State, height int64) *types.Block { +func makeBlock(state State, height int64) *types.Block { block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit)) return block } diff --git a/state/state.go b/state/state.go index 723a32552..ed8a20137 100644 --- a/state/state.go +++ b/state/state.go @@ -6,9 +6,6 @@ import ( "io/ioutil" "time" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" @@ -19,18 +16,6 @@ var ( stateKey = []byte("stateKey") ) -func calcValidatorsKey(height int64) []byte { - return []byte(cmn.Fmt("validatorsKey:%v", height)) -} - -func calcConsensusParamsKey(height int64) []byte { - return []byte(cmn.Fmt("consensusParamsKey:%v", height)) -} - -func calcABCIResponsesKey(height int64) []byte { - return []byte(cmn.Fmt("abciResponsesKey:%v", height)) -} - //----------------------------------------------------------------------------- // State is a short description of the latest committed block of the Tendermint consensus. @@ -94,16 +79,6 @@ func (s State) Copy() State { } } -// Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. -// It sets the given appHash on the state before persisting. -func (s State) Save(db dbm.DB, appHash []byte) { - s.AppHash = appHash - nextHeight := s.LastBlockHeight + 1 - saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators) - saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams) - db.SetSync(stateKey, s.Bytes()) -} - // Equals returns true if the States are identical. func (s State) Equals(s2 State) bool { return bytes.Equal(s.Bytes(), s2.Bytes()) @@ -114,59 +89,9 @@ func (s State) Bytes() []byte { return wire.BinaryBytes(s) } -// NextState returns a new State updated according to the header and responses. -func (s State) NextState(blockID types.BlockID, header *types.Header, - abciResponses *ABCIResponses) (State, error) { - - // copy the valset so we can apply changes from EndBlock - // and update s.LastValidators and s.Validators - prevValSet := s.Validators.Copy() - nextValSet := prevValSet.Copy() - - // update the validator set with the latest abciResponses - lastHeightValsChanged := s.LastHeightValidatorsChanged - if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { - err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates) - if err != nil { - return s, fmt.Errorf("Error changing validator set: %v", err) - } - // change results from this height but only applies to the next height - lastHeightValsChanged = header.Height + 1 - } - - // Update validator accums and set state variables - nextValSet.IncrementAccum(1) - - // update the params with the latest abciResponses - nextParams := s.ConsensusParams - lastHeightParamsChanged := s.LastHeightConsensusParamsChanged - if abciResponses.EndBlock.ConsensusParamUpdates != nil { - // NOTE: must not mutate s.ConsensusParams - nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates) - err := nextParams.Validate() - if err != nil { - return s, fmt.Errorf("Error updating consensus params: %v", err) - } - // change results from this height but only applies to the next height - lastHeightParamsChanged = header.Height + 1 - } - - // NOTE: the AppHash has not been populated. - // It will be filled on state.Save. - return State{ - ChainID: s.ChainID, - LastBlockHeight: header.Height, - LastBlockTotalTx: s.LastBlockTotalTx + header.NumTxs, - LastBlockID: blockID, - LastBlockTime: header.Time, - Validators: nextValSet, - LastValidators: s.Validators.Copy(), - LastHeightValidatorsChanged: lastHeightValsChanged, - ConsensusParams: nextParams, - LastHeightConsensusParamsChanged: lastHeightParamsChanged, - LastResultsHash: abciResponses.ResultsHash(), - AppHash: nil, - }, nil +// IsEmpty returns true if the State is equal to the empty State. +func (s State) IsEmpty() bool { + return s.LastBlockHeight == 0 // XXX can't compare to Empty } // GetValidators returns the last and current validator sets. @@ -174,6 +99,26 @@ func (s State) GetValidators() (last *types.ValidatorSet, current *types.Validat return s.LastValidators, s.Validators } +//------------------------------------------------------------------------ +// Create a block from the latest state + +// MakeBlock builds a block with the given txs and commit from the current state. +func (s State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) { + // build base block + block := types.MakeBlock(height, txs, commit) + + // fill header with state data + block.ChainID = s.ChainID + block.TotalTxs = s.LastBlockTotalTx + block.NumTxs + block.LastBlockID = s.LastBlockID + block.ValidatorsHash = s.Validators.Hash() + block.AppHash = s.AppHash + block.ConsensusHash = s.ConsensusParams.Hash() + block.LastResultsHash = s.LastResultsHash + + return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes) +} + //------------------------------------------------------------------------ // Genesis @@ -181,10 +126,10 @@ func (s State) GetValidators() (last *types.ValidatorSet, current *types.Validat // file. // // Used during replay and in tests. -func MakeGenesisStateFromFile(genDocFile string) (*State, error) { +func MakeGenesisStateFromFile(genDocFile string) (State, error) { genDoc, err := MakeGenesisDocFromFile(genDocFile) if err != nil { - return nil, err + return State{}, err } return MakeGenesisState(genDoc) } @@ -203,10 +148,10 @@ func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) { } // MakeGenesisState creates state from types.GenesisDoc. -func MakeGenesisState(genDoc *types.GenesisDoc) (*State, error) { +func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { err := genDoc.ValidateAndComplete() if err != nil { - return nil, fmt.Errorf("Error in genesis file: %v", err) + return State{}, fmt.Errorf("Error in genesis file: %v", err) } // Make validators slice @@ -223,7 +168,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (*State, error) { } } - return &State{ + return State{ ChainID: genDoc.ChainID, diff --git a/state/state_test.go b/state/state_test.go index 486ad24aa..cbd3c8137 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -14,19 +14,17 @@ import ( cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" 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, State) { config := cfg.ResetTestRoot("state_") stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir()) state, err := GetState(stateDB, config.GenesisFile()) assert.NoError(t, err, "expected no error on GetState") - state.SetLogger(log.TestingLogger()) tearDown := func(t *testing.T) {} @@ -59,7 +57,7 @@ func TestStateSaveLoad(t *testing.T) { assert := assert.New(t) state.LastBlockHeight++ - state.Save() + SaveState(stateDB, state, state.AppHash) loadedState := LoadState(stateDB) assert.True(state.Equals(loadedState), @@ -69,7 +67,7 @@ func TestStateSaveLoad(t *testing.T) { // TestABCIResponsesSaveLoad tests saving and loading ABCIResponses. func TestABCIResponsesSaveLoad1(t *testing.T) { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) // nolint: vetshadow assert := assert.New(t) @@ -88,8 +86,8 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { }, }} - SaveABCIResponses(state.db, block.Height, abciResponses) - loadedAbciResponses, err := LoadABCIResponses(state.db, block.Height) + saveABCIResponses(stateDB, block.Height, abciResponses) + loadedAbciResponses, err := LoadABCIResponses(stateDB, block.Height) assert.Nil(err) assert.Equal(abciResponses, loadedAbciResponses, cmn.Fmt(`ABCIResponses don't match: Got %v, Expected %v`, loadedAbciResponses, @@ -98,7 +96,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { // TestResultsSaveLoad tests saving and loading abci results. func TestABCIResponsesSaveLoad2(t *testing.T) { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, _ := setupTestCase(t) defer tearDown(t) // nolint: vetshadow assert := assert.New(t) @@ -142,7 +140,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // query all before, should return error for i := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(state.db, h) + res, err := LoadABCIResponses(stateDB, h) assert.Error(err, "%d: %#v", i, res) } @@ -153,13 +151,13 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { DeliverTx: tc.added, EndBlock: &abci.ResponseEndBlock{}, } - SaveABCIResponses(state.db, h, responses) + saveABCIResponses(stateDB, h, responses) } // query all before, should return expected value for i, tc := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(state.db, h) + res, err := LoadABCIResponses(stateDB, h) assert.NoError(err, "%d", i) assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i) } @@ -167,56 +165,57 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // TestValidatorSimpleSaveLoad tests saving and loading validators. func TestValidatorSimpleSaveLoad(t *testing.T) { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) // nolint: vetshadow assert := assert.New(t) // can't load anything for height 0 - v, err := LoadValidators(state.db, 0) + v, err := LoadValidators(stateDB, 0) assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0") // should be able to load for height 1 - v, err = LoadValidators(state.db, 1) + v, err = LoadValidators(stateDB, 1) assert.Nil(err, "expected no err at height 1") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // increment height, save; should be able to load for next height state.LastBlockHeight++ nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) - v, err = LoadValidators(state.db, nextHeight) + saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + v, err = LoadValidators(stateDB, nextHeight) assert.Nil(err, "expected no err") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // increment height, save; should be able to load for next height state.LastBlockHeight += 10 nextHeight = state.LastBlockHeight + 1 - saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) - v, err = LoadValidators(state.db, nextHeight) + saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + v, err = LoadValidators(stateDB, nextHeight) assert.Nil(err, "expected no err") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // should be able to load for next next height - _, err = LoadValidators(state.db, state.LastBlockHeight+2) + _, err = LoadValidators(stateDB, state.LastBlockHeight+2) assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height") } // TestValidatorChangesSaveLoad tests saving and loading a validator set with changes. func TestOneValidatorChangesSaveLoad(t *testing.T) { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) // change vals at these heights changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20} N := len(changeHeights) - // build the validator history by running SetBlockAndValidators + // build the validator history by running updateState // with the right validator set for each height highestHeight := changeHeights[N-1] + 5 changeIndex := 0 _, val := state.Validators.GetByIndex(0) power := val.VotingPower + var err error for i := int64(1); i < highestHeight; i++ { // when we get to a change height, // use the next pubkey @@ -224,11 +223,11 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { changeIndex++ power += 1 } - header, parts, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) - err := state.SetBlockAndValidators(header, parts, responses) + header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) + state, err = updateState(state, blockID, header, responses) assert.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators) } // on each change height, increment the power by one. @@ -246,7 +245,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } for i, power := range testCases { - v, err := LoadValidators(state.db, int64(i+1)) + v, err := LoadValidators(stateDB, int64(i+1)) 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) @@ -260,21 +259,22 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { // changes. func TestManyValidatorChangesSaveLoad(t *testing.T) { const valSetSize = 7 - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) state.Validators = genValSet(valSetSize) - state.Save() + SaveState(stateDB, state, state.AppHash) defer tearDown(t) const height = 1 pubkey := crypto.GenPrivKeyEd25519().PubKey() // swap the first validator with a new one ^^^ (validator set size stays the same) - header, parts, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey) - err := state.SetBlockAndValidators(header, parts, responses) + header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey) + var err error + state, err = updateState(state, blockID, header, responses) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators) - v, err := LoadValidators(state.db, height+1) + v, err := LoadValidators(stateDB, height+1) assert.Nil(t, err) assert.Equal(t, valSetSize, v.Size()) @@ -296,7 +296,7 @@ func genValSet(size int) *types.ValidatorSet { // TestConsensusParamsChangesSaveLoad tests saving and loading consensus params // with changes. func TestConsensusParamsChangesSaveLoad(t *testing.T) { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) // change vals at these heights @@ -312,11 +312,12 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { params[i].BlockSize.MaxBytes += i } - // build the params history by running SetBlockAndValidators + // build the params history by running updateState // with the right params set for each height highestHeight := changeHeights[N-1] + 5 changeIndex := 0 cp := params[changeIndex] + var err error for i := int64(1); i < highestHeight; i++ { // when we get to a change height, // use the next params @@ -324,11 +325,12 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { changeIndex++ cp = params[changeIndex] } - header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp) - err := state.SetBlockAndValidators(header, parts, responses) + header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp) + state, err = updateState(state, blockID, header, responses) + require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveConsensusParamsInfo(state.db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) + saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) } // make all the test cases by using the same params until after the change @@ -346,7 +348,7 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } for _, testCase := range testCases { - p, err := LoadConsensusParams(state.db, testCase.height) + p, err := 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)) @@ -421,15 +423,15 @@ func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) { } for i, tc := range testCases { - tearDown, _, state := setupTestCase(t) + tearDown, stateDB, state := setupTestCase(t) state.Validators = genValSet(tc.initialValSetSize) - state.Save() + SaveState(stateDB, state, state.AppHash) height := state.LastBlockHeight + 1 block := makeBlock(state, height) abciResponses := &ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: tc.valUpdatesFn(state.Validators)}, } - err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses) + state, err := updateState(state, types.BlockID{block.Hash(), types.PartSetHeader{}}, block.Header, abciResponses) if tc.shouldErr { assert.Error(t, err, "#%d", i) } else { @@ -489,8 +491,8 @@ func TestApplyUpdates(t *testing.T) { } } -func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64, - pubkey crypto.PubKey) (*types.Header, types.PartSetHeader, *ABCIResponses) { +func makeHeaderPartsResponsesValPubKeyChange(state State, height int64, + pubkey crypto.PubKey) (*types.Header, types.BlockID, *ABCIResponses) { block := makeBlock(state, height) abciResponses := &ABCIResponses{ @@ -508,11 +510,11 @@ func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64, } } - return block.Header, types.PartSetHeader{}, abciResponses + return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses } -func makeHeaderPartsResponsesValPowerChange(state *State, height int64, - power int64) (*types.Header, types.PartSetHeader, *ABCIResponses) { +func makeHeaderPartsResponsesValPowerChange(state State, height int64, + power int64) (*types.Header, types.BlockID, *ABCIResponses) { block := makeBlock(state, height) abciResponses := &ABCIResponses{ @@ -529,17 +531,17 @@ func makeHeaderPartsResponsesValPowerChange(state *State, height int64, } } - return block.Header, types.PartSetHeader{}, abciResponses + return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses } -func makeHeaderPartsResponsesParams(state *State, height int64, - params types.ConsensusParams) (*types.Header, types.PartSetHeader, *ABCIResponses) { +func makeHeaderPartsResponsesParams(state State, height int64, + params types.ConsensusParams) (*types.Header, types.BlockID, *ABCIResponses) { block := makeBlock(state, height) abciResponses := &ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, } - return block.Header, types.PartSetHeader{}, abciResponses + return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses } type paramsChangeTestCase struct { @@ -547,13 +549,13 @@ type paramsChangeTestCase struct { params types.ConsensusParams } -func makeHeaderPartsResults(state *State, height int64, - results []*abci.ResponseDeliverTx) (*types.Header, types.PartSetHeader, *ABCIResponses) { +func makeHeaderPartsResults(state State, height int64, + results []*abci.ResponseDeliverTx) (*types.Header, types.BlockID, *ABCIResponses) { block := makeBlock(state, height) abciResponses := &ABCIResponses{ DeliverTx: results, EndBlock: &abci.ResponseEndBlock{}, } - return block.Header, types.PartSetHeader{}, abciResponses + return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses } diff --git a/state/validation.go b/state/validation.go new file mode 100644 index 000000000..692008406 --- /dev/null +++ b/state/validation.go @@ -0,0 +1,136 @@ +package state + +import ( + "bytes" + "errors" + "fmt" + + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------- +// Validate block + +// ValidateBlock validates the block against the state. +func ValidateBlock(s State, block *types.Block) error { + return validateBlock(s, block) +} + +func validateBlock(s State, b *types.Block) error { + // validate internal consistency + if err := b.ValidateBasic(); err != nil { + return err + } + + // validate basic info + if b.ChainID != s.ChainID { + return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID) + } + if b.Height != s.LastBlockHeight+1 { + return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.LastBlockHeight+1, b.Height) + } + /* TODO: Determine bounds for Time + See blockchain/reactor "stopSyncingDurationMinutes" + + if !b.Time.After(lastBlockTime) { + return errors.New("Invalid Block.Header.Time") + } + */ + + // validate prev block info + if !b.LastBlockID.Equals(s.LastBlockID) { + return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID) + } + newTxs := int64(len(b.Data.Txs)) + if b.TotalTxs != s.LastBlockTotalTx+newTxs { + return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs) + } + + // validate app info + if !bytes.Equal(b.AppHash, s.AppHash) { + return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash) + } + if !bytes.Equal(b.ConsensusHash, s.ConsensusParams.Hash()) { + return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.ConsensusHash) + } + if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) { + return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash) + } + if !bytes.Equal(b.ValidatorsHash, s.Validators.Hash()) { + return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.ValidatorsHash) + } + + // Validate block LastCommit. + if b.Height == 1 { + if len(b.LastCommit.Precommits) != 0 { + return errors.New("Block at height 1 (first block) should have no LastCommit precommits") + } + } else { + if len(b.LastCommit.Precommits) != s.LastValidators.Size() { + return fmt.Errorf("Invalid block commit size. Expected %v, got %v", + s.LastValidators.Size(), len(b.LastCommit.Precommits)) + } + err := s.LastValidators.VerifyCommit( + s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit) + if err != nil { + return err + } + } + + for _, ev := range b.Evidence.Evidence { + if err := VerifyEvidence(s, ev); err != nil { + return types.NewEvidenceInvalidErr(ev, err) + } + /* // Needs a db ... + valset, err := LoadValidators(s.db, ev.Height()) + if err != nil { + // XXX/TODO: what do we do if we can't load the valset? + // eg. if we have pruned the state or height is too high? + return err + } + if err := VerifyEvidenceValidator(valSet, ev); err != nil { + return types.NewEvidenceInvalidErr(ev, err) + } + */ + } + + return nil +} + +// XXX: What's cheaper (ie. what should be checked first): +// evidence internal validity (ie. sig checks) or validator existed (fetch historical val set from db) + +// VerifyEvidence verifies the evidence fully by checking it is internally +// consistent and sufficiently recent. +func VerifyEvidence(s State, evidence types.Evidence) error { + height := s.LastBlockHeight + + evidenceAge := height - evidence.Height() + maxAge := s.ConsensusParams.EvidenceParams.MaxAge + if evidenceAge > maxAge { + return fmt.Errorf("Evidence from height %d is too old. Min height is %d", + evidence.Height(), height-maxAge) + } + + if err := evidence.Verify(s.ChainID); err != nil { + return err + } + return nil +} + +// VerifyEvidenceValidator returns the voting power of the validator at the height of the evidence. +// It returns an error if the validator did not exist or does not match that loaded from the historical validator set. +func VerifyEvidenceValidator(valset *types.ValidatorSet, evidence types.Evidence) (priority int64, err error) { + // The address must have been an active validator at the height + ev := evidence + height, addr, idx := ev.Height(), ev.Address(), ev.Index() + valIdx, val := valset.GetByAddress(addr) + if val == nil { + return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height) + } else if idx != valIdx { + return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx) + } + + priority = val.VotingPower + return priority, nil +} diff --git a/state/validation_test.go b/state/validation_test.go new file mode 100644 index 000000000..a8e4d42ed --- /dev/null +++ b/state/validation_test.go @@ -0,0 +1,64 @@ +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func _TestValidateBlock(t *testing.T) { + state := state() + + // proper block must pass + block := makeBlock(state, 1) + err := ValidateBlock(state, block) + require.NoError(t, err) + + // wrong chain fails + block = makeBlock(state, 1) + block.ChainID = "not-the-real-one" + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong height fails + block = makeBlock(state, 1) + block.Height += 10 + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong total tx fails + block = makeBlock(state, 1) + block.TotalTxs += 10 + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong blockid fails + block = makeBlock(state, 1) + block.LastBlockID.PartsHeader.Total += 10 + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong app hash fails + block = makeBlock(state, 1) + block.AppHash = []byte("wrong app hash") + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong consensus hash fails + block = makeBlock(state, 1) + block.ConsensusHash = []byte("wrong consensus hash") + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong results hash fails + block = makeBlock(state, 1) + block.LastResultsHash = []byte("wrong results hash") + err = ValidateBlock(state, block) + require.Error(t, err) + + // wrong validators hash fails + block = makeBlock(state, 1) + block.ValidatorsHash = []byte("wrong validators hash") + err = ValidateBlock(state, block) + require.Error(t, err) +}