From f55135578c5b6a5aa06012c17cb41ff20b00975f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 14:27:37 -0500 Subject: [PATCH 01/14] state: move methods to funcs --- consensus/replay.go | 2 +- evidence/pool.go | 12 +- rpc/core/blocks.go | 3 +- rpc/core/consensus.go | 3 +- state/db.go | 199 +++++++++++++++++++++++++++++++++ state/execution.go | 77 +++++++++---- state/state.go | 248 ++---------------------------------------- state/state_test.go | 41 ++++--- types/services.go | 9 -- 9 files changed, 299 insertions(+), 295 deletions(-) create mode 100644 state/db.go diff --git a/consensus/replay.go b/consensus/replay.go index 209ea597..a9aaeefc 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -301,7 +301,7 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp } else if appBlockHeight == storeBlockHeight { // We ran Commit, but didn't save the state, so replayBlock with mock app - abciResponses, err := h.state.LoadABCIResponses(storeBlockHeight) + abciResponses, err := sm.LoadABCIResponses(h.state.DB(), storeBlockHeight) if err != nil { return nil, err } diff --git a/evidence/pool.go b/evidence/pool.go index 2296ac02..1965d063 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -9,22 +9,24 @@ import ( // EvidencePool maintains a pool of valid evidence // in an EvidenceStore. type EvidencePool struct { - params types.EvidenceParams logger log.Logger - state types.State // TODO: update this on commit! evidenceStore *EvidenceStore + chainID string + lastBlockHeight int64 + params types.EvidenceParams + // never close evidenceChan chan types.Evidence } -func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state types.State) *EvidencePool { +func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state *types.State) *EvidencePool { evpool := &EvidencePool{ params: params, logger: log.NewNopLogger(), evidenceStore: evidenceStore, - state: state, + state: *state, evidenceChan: make(chan types.Evidence), } return evpool @@ -56,7 +58,7 @@ func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) { // TODO: check if we already have evidence for this // validator at this height so we dont get spammed - priority, err := evpool.state.VerifyEvidence(evidence) + priority, err := sm.VerifyEvidence(evpool.state, evidence) if err != nil { // TODO: if err is just that we cant find it cuz we pruned, ignore. // TODO: if its actually bad evidence, punish peer diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 43edcd35..8b0ee459 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -4,6 +4,7 @@ import ( "fmt" ctypes "github.com/tendermint/tendermint/rpc/core/types" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" ) @@ -337,7 +338,7 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { // load the results state := consensusState.GetState() - results, err := state.LoadABCIResponses(height) + results, err := sm.LoadABCIResponses(state.DB(), height) if err != nil { return nil, err } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index e358c487..eedcce27 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -4,6 +4,7 @@ import ( cm "github.com/tendermint/tendermint/consensus" cstypes "github.com/tendermint/tendermint/consensus/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -50,7 +51,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { } state := consensusState.GetState() - validators, err := state.LoadValidators(height) + validators, err := sm.LoadValidators(state.DB(), height) if err != nil { return nil, err } diff --git a/state/db.go b/state/db.go new file mode 100644 index 00000000..5174b9cb --- /dev/null +++ b/state/db.go @@ -0,0 +1,199 @@ +package state + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/abci/types" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tendermint/types" + cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" +) + +//------------------------------------------------------------------------ + +// ABCIResponses retains the responses +// of the various ABCI calls during block processing. +// It is persisted to disk for each height before calling Commit. +type ABCIResponses struct { + DeliverTx []*abci.ResponseDeliverTx + EndBlock *abci.ResponseEndBlock +} + +// NewABCIResponses returns a new ABCIResponses +func NewABCIResponses(block *types.Block) *ABCIResponses { + return &ABCIResponses{ + DeliverTx: make([]*abci.ResponseDeliverTx, block.NumTxs), + } +} + +// Bytes serializes the ABCIResponse using go-wire +func (a *ABCIResponses) Bytes() []byte { + return wire.BinaryBytes(*a) +} + +func (a *ABCIResponses) ResultsHash() []byte { + results := types.NewResults(a.DeliverTx) + return results.Hash() +} + +// LoadABCIResponses loads the ABCIResponses for the given height from the database. +// This is useful for recovering from crashes where we called app.Commit and before we called +// s.Save(). It can also be used to produce Merkle proofs of the result of txs. +func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) { + buf := db.Get(calcABCIResponsesKey(height)) + if len(buf) == 0 { + return nil, ErrNoABCIResponsesForHeight{height} + } + + abciResponses := new(ABCIResponses) + r, n, err := bytes.NewReader(buf), new(int), new(error) + wire.ReadBinaryPtr(abciResponses, r, 0, n, err) + if *err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + cmn.Exit(cmn.Fmt(`LoadABCIResponses: Data has been corrupted or its spec has + changed: %v\n`, *err)) + } + // TODO: ensure that buf is completely read. + + return abciResponses, nil +} + +// 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) { + db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes()) +} + +//----------------------------------------------------------------------------- + +// ValidatorsInfo represents the latest validator set, or the last height it changed +type ValidatorsInfo struct { + ValidatorSet *types.ValidatorSet + LastHeightChanged int64 +} + +// Bytes serializes the ValidatorsInfo using go-wire +func (valInfo *ValidatorsInfo) Bytes() []byte { + return wire.BinaryBytes(*valInfo) +} + +// LoadValidators loads the ValidatorSet for a given height. +// Returns ErrNoValSetForHeight if the validator set can't be found for this height. +func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) { + valInfo := loadValidatorsInfo(db, height) + if valInfo == nil { + return nil, ErrNoValSetForHeight{height} + } + + if valInfo.ValidatorSet == nil { + valInfo = loadValidatorsInfo(db, valInfo.LastHeightChanged) + if valInfo == nil { + cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as + last changed from height %d`, valInfo.LastHeightChanged, height)) + } + } + + return valInfo.ValidatorSet, nil +} + +func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo { + buf := db.Get(calcValidatorsKey(height)) + if len(buf) == 0 { + return nil + } + + v := new(ValidatorsInfo) + r, n, err := bytes.NewReader(buf), new(int), new(error) + wire.ReadBinaryPtr(v, r, 0, n, err) + if *err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + cmn.Exit(cmn.Fmt(`LoadValidators: Data has been corrupted or its spec has changed: + %v\n`, *err)) + } + // TODO: ensure that buf is completely read. + + return v +} + +// saveValidatorsInfo persists the validator set for the next block to disk. +// It should be called from s.Save(), right before the state itself is persisted. +// If the validator set did not change after processing the latest block, +// only the last height for which the validators changed is persisted. +func saveValidatorsInfo(db dbm.DB, nextHeight, changeHeight int64, valSet *types.ValidatorSet) { + valInfo := &ValidatorsInfo{ + LastHeightChanged: changeHeight, + } + if changeHeight == nextHeight { + valInfo.ValidatorSet = valSet + } + db.SetSync(calcValidatorsKey(nextHeight), valInfo.Bytes()) +} + +//----------------------------------------------------------------------------- + +// ConsensusParamsInfo represents the latest consensus params, or the last height it changed +type ConsensusParamsInfo struct { + ConsensusParams types.ConsensusParams + LastHeightChanged int64 +} + +// Bytes serializes the ConsensusParamsInfo using go-wire +func (params ConsensusParamsInfo) Bytes() []byte { + return wire.BinaryBytes(params) +} + +// LoadConsensusParams loads the ConsensusParams for a given height. +func LoadConsensusParams(db dbm.DB, height int64) (types.ConsensusParams, error) { + empty := types.ConsensusParams{} + + paramsInfo := loadConsensusParamsInfo(db, height) + if paramsInfo == nil { + return empty, ErrNoConsensusParamsForHeight{height} + } + + if paramsInfo.ConsensusParams == empty { + paramsInfo = loadConsensusParamsInfo(db, paramsInfo.LastHeightChanged) + if paramsInfo == nil { + cmn.PanicSanity(fmt.Sprintf(`Couldn't find consensus params at height %d as + last changed from height %d`, paramsInfo.LastHeightChanged, height)) + } + } + + return paramsInfo.ConsensusParams, nil +} + +func loadConsensusParamsInfo(db dbm.DB, height int64) *ConsensusParamsInfo { + buf := db.Get(calcConsensusParamsKey(height)) + if len(buf) == 0 { + return nil + } + + paramsInfo := new(ConsensusParamsInfo) + r, n, err := bytes.NewReader(buf), new(int), new(error) + wire.ReadBinaryPtr(paramsInfo, r, 0, n, err) + if *err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed: + %v\n`, *err)) + } + // TODO: ensure that buf is completely read. + + return paramsInfo +} + +// saveConsensusParamsInfo persists the consensus params for the next block to disk. +// It should be called from s.Save(), right before the state itself is persisted. +// If the consensus params did not change after processing the latest block, +// only the last height for which they changed is persisted. +func saveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params types.ConsensusParams) { + paramsInfo := &ConsensusParamsInfo{ + LastHeightChanged: changeHeight, + } + if changeHeight == nextHeight { + paramsInfo.ConsensusParams = params + } + db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) +} diff --git a/state/execution.go b/state/execution.go index c9686152..b56f61f9 100644 --- a/state/execution.go +++ b/state/execution.go @@ -209,28 +209,9 @@ func changeInVotingPowerMoreOrEqualToOneThird(currentSet *types.ValidatorSet, up return false, nil } -// return a bit array of validators that signed the last commit -// NOTE: assumes commits have already been authenticated -/* function is currently unused -func commitBitArrayFromBlock(block *types.Block) *cmn.BitArray { - signed := cmn.NewBitArray(len(block.LastCommit.Precommits)) - for i, precommit := range block.LastCommit.Precommits { - if precommit != nil { - signed.SetIndex(i, true) // val_.LastCommitHeight = block.Height - 1 - } - } - return signed -} -*/ - //----------------------------------------------------- // Validate block -// ValidateBlock validates the block against the state. -func (s *State) ValidateBlock(block *types.Block) error { - return s.validateBlock(block) -} - // 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 @@ -248,7 +229,12 @@ func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (* return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes) } -func (s *State) validateBlock(b *types.Block) error { +// 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 @@ -310,7 +296,7 @@ func (s *State) validateBlock(b *types.Block) error { } for _, ev := range b.Evidence.Evidence { - if _, err := s.VerifyEvidence(ev); err != nil { + if _, err := VerifyEvidence(s, ev); err != nil { return types.NewEvidenceInvalidErr(ev, err) } } @@ -318,10 +304,57 @@ func (s *State) validateBlock(b *types.Block) error { return nil } +// VerifyEvidence verifies the evidence fully by checking it is internally +// consistent and corresponds to an existing or previous validator. +// It returns the priority of this evidence, or an error. +// NOTE: return error may be ErrNoValSetForHeight, in which case the validator set +// for the evidence height could not be loaded. +func VerifyEvidence(s State, evidence types.Evidence) (priority int64, err error) { + height := s.LastBlockHeight + evidenceAge := height - evidence.Height() + maxAge := s.ConsensusParams.EvidenceParams.MaxAge + if evidenceAge > maxAge { + return priority, 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 priority, err + } + + // The address must have been an active validator at the height + ev := evidence + height, addr, idx := ev.Height(), ev.Address(), ev.Index() + valset, err := LoadValidators(s.db, 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 priority, err + } + 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 { + txEventPublisher types.TxEventPublisher + proxyApp proxy.AppConnConsensus + + mempool types.Mempool + evpool types.EvidencePool +} + // 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. @@ -337,7 +370,7 @@ func (s *State) ApplyBlock(txEventPublisher types.TxEventPublisher, proxyAppConn fail.Fail() // XXX // save the results before we commit - s.SaveABCIResponses(block.Height, abciResponses) + SaveABCIResponses(s.db, block.Height, abciResponses) fail.Fail() // XXX diff --git a/state/state.go b/state/state.go index 773b46fc..1aaddeb3 100644 --- a/state/state.go +++ b/state/state.go @@ -4,11 +4,8 @@ import ( "bytes" "fmt" "io/ioutil" - "sync" "time" - abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -44,9 +41,7 @@ func calcABCIResponsesKey(height int64) []byte { // but the fields should only be changed by calling state.SetBlockAndValidators. // NOTE: not goroutine-safe. type State struct { - // mtx for writing to db - mtx sync.Mutex - db dbm.DB + db dbm.DB // Immutable ChainID string @@ -82,6 +77,10 @@ type State struct { logger log.Logger } +func (s *State) DB() dbm.DB { + return s.db +} + // 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. @@ -157,150 +156,13 @@ func (s *State) Copy() *State { // Save persists the State to the database. func (s *State) Save() { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.saveValidatorsInfo() - s.saveConsensusParamsInfo() - s.db.SetSync(stateKey, s.Bytes()) -} - -// 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 (s *State) SaveABCIResponses(height int64, abciResponses *ABCIResponses) { - s.db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes()) -} - -// LoadABCIResponses loads the ABCIResponses for the given height from the database. -// This is useful for recovering from crashes where we called app.Commit and before we called -// s.Save(). It can also be used to produce Merkle proofs of the result of txs. -func (s *State) LoadABCIResponses(height int64) (*ABCIResponses, error) { - buf := s.db.Get(calcABCIResponsesKey(height)) - if len(buf) == 0 { - return nil, ErrNoABCIResponsesForHeight{height} - } - - abciResponses := new(ABCIResponses) - r, n, err := bytes.NewReader(buf), new(int), new(error) - wire.ReadBinaryPtr(abciResponses, r, 0, n, err) - if *err != nil { - // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - cmn.Exit(cmn.Fmt(`LoadABCIResponses: Data has been corrupted or its spec has - changed: %v\n`, *err)) - } - // TODO: ensure that buf is completely read. - - return abciResponses, nil -} - -// LoadValidators loads the ValidatorSet for a given height. -// Returns ErrNoValSetForHeight if the validator set can't be found for this height. -func (s *State) LoadValidators(height int64) (*types.ValidatorSet, error) { - valInfo := s.loadValidatorsInfo(height) - if valInfo == nil { - return nil, ErrNoValSetForHeight{height} - } - - if valInfo.ValidatorSet == nil { - valInfo = s.loadValidatorsInfo(valInfo.LastHeightChanged) - if valInfo == nil { - cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as - last changed from height %d`, valInfo.LastHeightChanged, height)) - } - } - - return valInfo.ValidatorSet, nil -} - -func (s *State) loadValidatorsInfo(height int64) *ValidatorsInfo { - buf := s.db.Get(calcValidatorsKey(height)) - if len(buf) == 0 { - return nil - } - - v := new(ValidatorsInfo) - r, n, err := bytes.NewReader(buf), new(int), new(error) - wire.ReadBinaryPtr(v, r, 0, n, err) - if *err != nil { - // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - cmn.Exit(cmn.Fmt(`LoadValidators: Data has been corrupted or its spec has changed: - %v\n`, *err)) - } - // TODO: ensure that buf is completely read. - - return v -} - -// saveValidatorsInfo persists the validator set for the next block to disk. -// It should be called from s.Save(), right before the state itself is persisted. -// If the validator set did not change after processing the latest block, -// only the last height for which the validators changed is persisted. -func (s *State) saveValidatorsInfo() { - changeHeight := s.LastHeightValidatorsChanged nextHeight := s.LastBlockHeight + 1 - valInfo := &ValidatorsInfo{ - LastHeightChanged: changeHeight, - } - if changeHeight == nextHeight { - valInfo.ValidatorSet = s.Validators - } - s.db.SetSync(calcValidatorsKey(nextHeight), valInfo.Bytes()) -} -// LoadConsensusParams loads the ConsensusParams for a given height. -func (s *State) LoadConsensusParams(height int64) (types.ConsensusParams, error) { - empty := types.ConsensusParams{} - - paramsInfo := s.loadConsensusParamsInfo(height) - if paramsInfo == nil { - return empty, ErrNoConsensusParamsForHeight{height} - } - - if paramsInfo.ConsensusParams == empty { - paramsInfo = s.loadConsensusParamsInfo(paramsInfo.LastHeightChanged) - if paramsInfo == nil { - cmn.PanicSanity(fmt.Sprintf(`Couldn't find consensus params at height %d as - last changed from height %d`, paramsInfo.LastHeightChanged, height)) - } - } - - return paramsInfo.ConsensusParams, nil -} - -func (s *State) loadConsensusParamsInfo(height int64) *ConsensusParamsInfo { - buf := s.db.Get(calcConsensusParamsKey(height)) - if len(buf) == 0 { - return nil - } - - paramsInfo := new(ConsensusParamsInfo) - r, n, err := bytes.NewReader(buf), new(int), new(error) - wire.ReadBinaryPtr(paramsInfo, r, 0, n, err) - if *err != nil { - // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed: - %v\n`, *err)) - } - // TODO: ensure that buf is completely read. - - return paramsInfo -} - -// saveConsensusParamsInfo persists the consensus params for the next block to disk. -// It should be called from s.Save(), right before the state itself is persisted. -// If the consensus params did not change after processing the latest block, -// only the last height for which they changed is persisted. -func (s *State) saveConsensusParamsInfo() { - changeHeight := s.LastHeightConsensusParamsChanged - nextHeight := s.LastBlockHeight + 1 - paramsInfo := &ConsensusParamsInfo{ - LastHeightChanged: changeHeight, - } - if changeHeight == nextHeight { - paramsInfo.ConsensusParams = s.ConsensusParams - } - s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) + // persist everything to db + db := s.db + 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. @@ -383,96 +245,6 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida return s.LastValidators, s.Validators } -// VerifyEvidence verifies the evidence fully by checking it is internally -// consistent and corresponds to an existing or previous validator. -// It returns the priority of this evidence, or an error. -// NOTE: return error may be ErrNoValSetForHeight, in which case the validator set -// for the evidence height could not be loaded. -func (s *State) VerifyEvidence(evidence types.Evidence) (priority int64, err error) { - evidenceAge := s.LastBlockHeight - evidence.Height() - maxAge := s.ConsensusParams.EvidenceParams.MaxAge - if evidenceAge > maxAge { - return priority, fmt.Errorf("Evidence from height %d is too old. Min height is %d", - evidence.Height(), s.LastBlockHeight-maxAge) - } - - if err := evidence.Verify(s.ChainID); err != nil { - return priority, err - } - - // The address must have been an active validator at the height - ev := evidence - height, addr, idx := ev.Height(), ev.Address(), ev.Index() - valset, err := s.LoadValidators(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 priority, err - } - 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 -} - -//------------------------------------------------------------------------ - -// ABCIResponses retains the responses -// of the various ABCI calls during block processing. -// It is persisted to disk for each height before calling Commit. -type ABCIResponses struct { - DeliverTx []*abci.ResponseDeliverTx - EndBlock *abci.ResponseEndBlock -} - -// NewABCIResponses returns a new ABCIResponses -func NewABCIResponses(block *types.Block) *ABCIResponses { - return &ABCIResponses{ - DeliverTx: make([]*abci.ResponseDeliverTx, block.NumTxs), - } -} - -// Bytes serializes the ABCIResponse using go-wire -func (a *ABCIResponses) Bytes() []byte { - return wire.BinaryBytes(*a) -} - -func (a *ABCIResponses) ResultsHash() []byte { - results := types.NewResults(a.DeliverTx) - return results.Hash() -} - -//----------------------------------------------------------------------------- - -// ValidatorsInfo represents the latest validator set, or the last height it changed -type ValidatorsInfo struct { - ValidatorSet *types.ValidatorSet - LastHeightChanged int64 -} - -// Bytes serializes the ValidatorsInfo using go-wire -func (valInfo *ValidatorsInfo) Bytes() []byte { - return wire.BinaryBytes(*valInfo) -} - -//----------------------------------------------------------------------------- - -// ConsensusParamsInfo represents the latest consensus params, or the last height it changed -type ConsensusParamsInfo struct { - ConsensusParams types.ConsensusParams - LastHeightChanged int64 -} - -// Bytes serializes the ConsensusParamsInfo using go-wire -func (params ConsensusParamsInfo) Bytes() []byte { - return wire.BinaryBytes(params) -} - //------------------------------------------------------------------------ // Genesis diff --git a/state/state_test.go b/state/state_test.go index b1adc0d0..486ad24a 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -88,8 +88,8 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { }, }} - state.SaveABCIResponses(block.Height, abciResponses) - loadedAbciResponses, err := state.LoadABCIResponses(block.Height) + SaveABCIResponses(state.db, block.Height, abciResponses) + loadedAbciResponses, err := LoadABCIResponses(state.db, block.Height) assert.Nil(err) assert.Equal(abciResponses, loadedAbciResponses, cmn.Fmt(`ABCIResponses don't match: Got %v, Expected %v`, loadedAbciResponses, @@ -142,7 +142,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // query all before, should return error for i := range cases { h := int64(i + 1) - res, err := state.LoadABCIResponses(h) + res, err := LoadABCIResponses(state.db, h) assert.Error(err, "%d: %#v", i, res) } @@ -153,13 +153,13 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { DeliverTx: tc.added, EndBlock: &abci.ResponseEndBlock{}, } - state.SaveABCIResponses(h, responses) + SaveABCIResponses(state.db, h, responses) } // query all before, should return expected value for i, tc := range cases { h := int64(i + 1) - res, err := state.LoadABCIResponses(h) + res, err := LoadABCIResponses(state.db, h) assert.NoError(err, "%d", i) assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i) } @@ -173,30 +173,32 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert := assert.New(t) // can't load anything for height 0 - v, err := state.LoadValidators(0) + v, err := LoadValidators(state.db, 0) assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0") // should be able to load for height 1 - v, err = state.LoadValidators(1) + v, err = LoadValidators(state.db, 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++ - state.saveValidatorsInfo() - v, err = state.LoadValidators(state.LastBlockHeight + 1) + nextHeight := state.LastBlockHeight + 1 + saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + v, err = LoadValidators(state.db, 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 - state.saveValidatorsInfo() - v, err = state.LoadValidators(state.LastBlockHeight + 1) + nextHeight = state.LastBlockHeight + 1 + saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) + v, err = LoadValidators(state.db, 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 = state.LoadValidators(state.LastBlockHeight + 2) + _, err = LoadValidators(state.db, state.LastBlockHeight+2) assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height") } @@ -225,7 +227,8 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { header, parts, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) err := state.SetBlockAndValidators(header, parts, responses) assert.Nil(t, err) - state.saveValidatorsInfo() + nextHeight := state.LastBlockHeight + 1 + saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) } // on each change height, increment the power by one. @@ -243,7 +246,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } for i, power := range testCases { - v, err := state.LoadValidators(int64(i + 1)) + v, err := LoadValidators(state.db, 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) @@ -268,9 +271,10 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { header, parts, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey) err := state.SetBlockAndValidators(header, parts, responses) require.Nil(t, err) - state.saveValidatorsInfo() + nextHeight := state.LastBlockHeight + 1 + saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators) - v, err := state.LoadValidators(height + 1) + v, err := LoadValidators(state.db, height+1) assert.Nil(t, err) assert.Equal(t, valSetSize, v.Size()) @@ -323,7 +327,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp) err := state.SetBlockAndValidators(header, parts, responses) require.Nil(t, err) - state.saveConsensusParamsInfo() + nextHeight := state.LastBlockHeight + 1 + saveConsensusParamsInfo(state.db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) } // make all the test cases by using the same params until after the change @@ -341,7 +346,7 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } for _, testCase := range testCases { - p, err := state.LoadConsensusParams(testCase.height) + p, err := LoadConsensusParams(state.db, 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)) diff --git a/types/services.go b/types/services.go index 787b1b99..a901898f 100644 --- a/types/services.go +++ b/types/services.go @@ -70,15 +70,6 @@ type BlockStore interface { SaveBlock(block *Block, blockParts *PartSet, seenCommit *Commit) } -//------------------------------------------------------ -// state - -// State defines the stateful interface used to verify evidence. -// UNSTABLE -type State interface { - VerifyEvidence(Evidence) (priority int64, err error) -} - //------------------------------------------------------ // evidence pool From c915719f85f229e58de13876d414bbb63987bc6e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 17:50:16 -0500 Subject: [PATCH 02/14] *State->State; SetBlockAndValidators->NextState --- evidence/pool.go | 2 +- state/db.go | 41 +++++++++++++ state/execution.go | 4 +- state/state.go | 140 +++++++++++---------------------------------- 4 files changed, 76 insertions(+), 111 deletions(-) diff --git a/evidence/pool.go b/evidence/pool.go index 1965d063..381801df 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -21,7 +21,7 @@ type EvidencePool struct { evidenceChan chan types.Evidence } -func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state *types.State) *EvidencePool { +func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state types.State) *EvidencePool { evpool := &EvidencePool{ params: params, logger: log.NewNopLogger(), diff --git a/state/db.go b/state/db.go index 5174b9cb..08da59cb 100644 --- a/state/db.go +++ b/state/db.go @@ -11,6 +11,47 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) +// 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) { + state := LoadState(stateDB) + if state == nil { + var err error + state, err = MakeGenesisStateFromFile(stateDB, genesisFile) + if err != nil { + return nil, err + } + state.Save() + } + + return state, nil +} + +// LoadState loads the State from the database. +func LoadState(db dbm.DB) *State { + return loadState(db, stateKey) +} + +func loadState(db dbm.DB, key []byte) *State { + buf := db.Get(key) + if len(buf) == 0 { + return nil + } + + s := &State{db: db} + r, n, err := bytes.NewReader(buf), new(int), new(error) + wire.ReadBinaryPtr(&s, 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: + %v\n`, *err)) + } + // TODO: ensure that buf is completely read. + + return s +} + //------------------------------------------------------------------------ // ABCIResponses retains the responses diff --git a/state/execution.go b/state/execution.go index b56f61f9..d05e043d 100644 --- a/state/execution.go +++ b/state/execution.go @@ -213,7 +213,7 @@ func changeInVotingPowerMoreOrEqualToOneThird(currentSet *types.ValidatorSet, up // Validate block // 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) { +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) @@ -309,7 +309,7 @@ func (s State) validateBlock(b *types.Block) error { // It returns the priority of this evidence, or an error. // NOTE: return error may be ErrNoValSetForHeight, in which case the validator set // for the evidence height could not be loaded. -func VerifyEvidence(s State, evidence types.Evidence) (priority int64, err error) { +func (s State) VerifyEvidence(evidence types.Evidence) (priority int64, err error) { height := s.LastBlockHeight evidenceAge := height - evidence.Height() maxAge := s.ConsensusParams.EvidenceParams.MaxAge diff --git a/state/state.go b/state/state.go index 1aaddeb3..ca9cf16b 100644 --- a/state/state.go +++ b/state/state.go @@ -8,7 +8,6 @@ import ( cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" wire "github.com/tendermint/go-wire" @@ -38,16 +37,13 @@ func calcABCIResponsesKey(height int64) []byte { // It keeps all information necessary to validate new blocks, // including the last validator set and the consensus params. // All fields are exposed so the struct can be easily serialized, -// but the fields should only be changed by calling state.SetBlockAndValidators. +// but none of them should be mutated directly. +// Instead, use state.Copy() ro state.NextState(...). // NOTE: not goroutine-safe. type State struct { - db dbm.DB - // Immutable ChainID string - // Exposed fields are updated by SetBlockAndValidators. - // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) LastBlockHeight int64 LastBlockTotalTx int64 @@ -73,65 +69,11 @@ type State struct { // The latest AppHash we've received from calling abci.Commit() AppHash []byte - - logger log.Logger -} - -func (s *State) DB() dbm.DB { - return s.db -} - -// 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) { - state := LoadState(stateDB) - if state == nil { - var err error - state, err = MakeGenesisStateFromFile(stateDB, genesisFile) - if err != nil { - return nil, err - } - state.Save() - } - - return state, nil -} - -// LoadState loads the State from the database. -func LoadState(db dbm.DB) *State { - return loadState(db, stateKey) -} - -func loadState(db dbm.DB, key []byte) *State { - buf := db.Get(key) - if len(buf) == 0 { - return nil - } - - s := &State{db: db} - r, n, err := bytes.NewReader(buf), new(int), new(error) - wire.ReadBinaryPtr(&s, 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: - %v\n`, *err)) - } - // TODO: ensure that buf is completely read. - - return s -} - -// SetLogger sets the logger on the State. -func (s *State) SetLogger(l log.Logger) { - s.logger = l } // Copy makes a copy of the State for mutating. -func (s *State) Copy() *State { +func (s State) Copy() State { return &State{ - db: s.db, - ChainID: s.ChainID, LastBlockHeight: s.LastBlockHeight, @@ -149,36 +91,30 @@ func (s *State) Copy() *State { AppHash: s.AppHash, LastResultsHash: s.LastResultsHash, - - logger: s.logger, } } -// Save persists the State to the database. -func (s *State) Save() { +// Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. +func (s State) Save(db dbm.DB) { nextHeight := s.LastBlockHeight + 1 - - // persist everything to db - db := s.db 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 { +func (s State) Equals(s2 State) bool { return bytes.Equal(s.Bytes(), s2.Bytes()) } // Bytes serializes the State using go-wire. -func (s *State) Bytes() []byte { +func (s State) Bytes() []byte { return wire.BinaryBytes(s) } -// SetBlockAndValidators mutates State variables -// to update block and validators after running EndBlock. -func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader types.PartSetHeader, - abciResponses *ABCIResponses) error { +// NextState returns a new State updated according to the header and responses. +func (s State) NextState(header *types.Header, blockPartsHeader types.PartSetHeader, + abciResponses *ABCIResponses) (State, error) { // copy the valset so we can apply changes from EndBlock // and update s.LastValidators and s.Validators @@ -186,13 +122,14 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ 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 fmt.Errorf("Error changing validator set: %v", err) } // change results from this height but only applies to the next height - s.LastHeightValidatorsChanged = header.Height + 1 + lastHeightValsChanged = header.Height + 1 } // Update validator accums and set state variables @@ -200,6 +137,7 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ // 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) @@ -208,40 +146,27 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ return fmt.Errorf("Error updating consensus params: %v", err) } // change results from this height but only applies to the next height - s.LastHeightConsensusParamsChanged = header.Height + 1 + lastHeightParamsChanged = header.Height + 1 } - s.setBlockAndValidators(header.Height, - header.NumTxs, - types.BlockID{header.Hash(), blockPartsHeader}, - header.Time, - nextValSet, - nextParams, - abciResponses.ResultsHash()) - return nil -} - -func (s *State) setBlockAndValidators(height int64, - newTxs int64, blockID types.BlockID, blockTime time.Time, - valSet *types.ValidatorSet, - params types.ConsensusParams, - resultsHash []byte) { - - s.LastBlockHeight = height - s.LastBlockTotalTx += newTxs - s.LastBlockID = blockID - s.LastBlockTime = blockTime - - s.LastValidators = s.Validators.Copy() - s.Validators = valSet - - s.ConsensusParams = params - - s.LastResultsHash = resultsHash + return State{ + ChainID: s.ChainID, + LastBlockHeight: header.Height, + LastBlockTotalTx: s.LastBlockTotalTx + header.NumTxs, + LastBlockID: types.BlockID{header.Hash(), blockPartsHeader}, + LastBlockTime: header.Time, + Validators: nextValSet, + LastValidators: s.Validators.Copy(), + LastHeightValidatorsChanged: lastHeightValsChanged, + ConsensusParams: nextParams, + LastHeightConsensusParamsChanged: lastHeightParamsChanged, + LastResultsHash: abciResponses.ResultsHash(), + AppHash: nil, + } } // GetValidators returns the last and current validator sets. -func (s *State) GetValidators() (last *types.ValidatorSet, current *types.ValidatorSet) { +func (s State) GetValidators() (last *types.ValidatorSet, current *types.ValidatorSet) { return s.LastValidators, s.Validators } @@ -252,12 +177,12 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida // file. // // Used during replay and in tests. -func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*State, error) { +func MakeGenesisStateFromFile(genDocFile string) (*State, error) { genDoc, err := MakeGenesisDocFromFile(genDocFile) if err != nil { return nil, err } - return MakeGenesisState(db, genDoc) + return MakeGenesisState(genDoc) } // MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file. @@ -274,7 +199,7 @@ func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) { } // MakeGenesisState creates state from types.GenesisDoc. -func MakeGenesisState(db dbm.DB, 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) @@ -295,7 +220,6 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { } return &State{ - db: db, ChainID: genDoc.ChainID, From 9e6d0887574136fcde247885d7a3032d4eaa96f3 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 19:21:16 -0500 Subject: [PATCH 03/14] state: BlockExecutor --- state/db.go | 6 +- state/execution.go | 152 ++++++++++++++++++++++++++------------------- state/state.go | 18 +++--- 3 files changed, 103 insertions(+), 73 deletions(-) diff --git a/state/db.go b/state/db.go index 08da59cb..08dd61d1 100644 --- a/state/db.go +++ b/state/db.go @@ -18,11 +18,11 @@ func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { state := LoadState(stateDB) if state == nil { var err error - state, err = MakeGenesisStateFromFile(stateDB, genesisFile) + state, err = MakeGenesisStateFromFile(genesisFile) if err != nil { return nil, err } - state.Save() + state.Save(stateDB, state.AppHash) } return state, nil @@ -39,7 +39,7 @@ func loadState(db dbm.DB, key []byte) *State { return nil } - s := &State{db: db} + s := new(State) r, n, err := bytes.NewReader(buf), new(int), new(error) wire.ReadBinaryPtr(&s, r, 0, n, err) if *err != nil { diff --git a/state/execution.go b/state/execution.go index d05e043d..969c3328 100644 --- a/state/execution.go +++ b/state/execution.go @@ -10,23 +10,22 @@ import ( crypto "github.com/tendermint/go-crypto" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" ) //-------------------------------------------------- // Execute the block -// ValExecBlock executes the block, but does NOT mutate State. +// ValExecBlock executes the block and returns the responses. It does NOT mutate State. // + validates the block // + executes block.Txs on the proxyAppConn -func (s *State) ValExecBlock(txEventPublisher types.TxEventPublisher, proxyAppConn proxy.AppConnConsensus, block *types.Block) (*ABCIResponses, error) { - // Validate the block. +func (blockExec *BlockExecutor) ValExecBlock(s State, block *types.Block) (*ABCIResponses, error) { if err := s.validateBlock(block); err != nil { return nil, ErrInvalidBlock(err) } - // Execute the block txs - abciResponses, err := execBlockOnProxyApp(txEventPublisher, proxyAppConn, block, s.logger, s.LastValidators) + 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. @@ -38,8 +37,7 @@ func (s *State) ValExecBlock(txEventPublisher types.TxEventPublisher, proxyAppCo // Executes block's transactions on proxyAppConn. // Returns a list of transaction results and updates to the validator set -// TODO: Generate a bitmap or otherwise store tx validity in state. -func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn proxy.AppConnConsensus, block *types.Block, logger log.Logger, lastValidators *types.ValidatorSet) (*ABCIResponses, error) { +func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, block *types.Block) (*ABCIResponses, error) { var validTxs, invalidTxs = 0, 0 txIndex := 0 @@ -59,17 +57,6 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p logger.Debug("Invalid tx", "code", txRes.Code, "log", txRes.Log) invalidTxs++ } - - // NOTE: if we count we can access the tx from the block instead of - // pulling it from the req - tx := types.Tx(req.GetDeliverTx().Tx) - txEventPublisher.PublishEventTx(types.EventDataTx{types.TxResult{ - Height: block.Height, - Index: uint32(txIndex), - Tx: tx, - Result: *txRes, - }}) - abciResponses.DeliverTx[txIndex] = txRes txIndex++ } @@ -296,41 +283,52 @@ func (s State) validateBlock(b *types.Block) error { } for _, ev := range b.Evidence.Evidence { - if _, err := VerifyEvidence(s, ev); err != nil { + 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 corresponds to an existing or previous validator. -// It returns the priority of this evidence, or an error. -// NOTE: return error may be ErrNoValSetForHeight, in which case the validator set -// for the evidence height could not be loaded. -func (s State) VerifyEvidence(evidence types.Evidence) (priority int64, err error) { +// 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 priority, fmt.Errorf("Evidence from height %d is too old. Min height is %d", + 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 priority, err + 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() - valset, err := LoadValidators(s.db, 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 priority, err - } valIdx, val := valset.GetByAddress(addr) if val == nil { return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height) @@ -348,6 +346,9 @@ func (s State) VerifyEvidence(evidence types.Evidence) (priority int64, err erro // 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 @@ -355,81 +356,106 @@ type BlockExecutor struct { 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. -func (s *State) ApplyBlock(txEventPublisher types.TxEventPublisher, proxyAppConn proxy.AppConnConsensus, - block *types.Block, partsHeader types.PartSetHeader, - mempool types.Mempool, evpool types.EvidencePool) error { +// 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 := s.ValExecBlock(txEventPublisher, proxyAppConn, block) + abciResponses, err := blockExec.ValExecBlock(s, block) if err != nil { - return fmt.Errorf("Exec failed for application: %v", err) + return s, fmt.Errorf("Exec failed for application: %v", err) } + // TODO: Fire events + /* + tx := types.Tx(req.GetDeliverTx().Tx) + txEventPublisher.PublishEventTx(types.EventDataTx{types.TxResult{ + Height: block.Height, + Index: uint32(txIndex), + Tx: tx, + Result: *txRes, + }}) + */ + fail.Fail() // XXX // save the results before we commit - SaveABCIResponses(s.db, block.Height, abciResponses) + SaveABCIResponses(blockExec.db, block.Height, abciResponses) fail.Fail() // XXX - // now update the block and validators - err = s.SetBlockAndValidators(block.Header, partsHeader, abciResponses) + // update the state with the block and responses + s, err = s.NextState(blockID, block.Header, abciResponses) if err != nil { - return fmt.Errorf("Commit failed for application: %v", err) + return s, fmt.Errorf("Commit failed for application: %v", err) } // lock mempool, commit state, update mempoool - err = s.CommitStateUpdateMempool(proxyAppConn, block, mempool) + appHash, err := blockExec.Commit(block) if err != nil { - return fmt.Errorf("Commit failed for application: %v", err) + return s, fmt.Errorf("Commit failed for application: %v", err) } fail.Fail() // XXX - evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) - // save the state and the validators - s.Save() + s.Save(blockExec.db, appHash) - return nil + return s, nil } -// CommitStateUpdateMempool locks the mempool, runs the ABCI Commit message, and updates the mempool. +// 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 (s *State) CommitStateUpdateMempool(proxyAppConn proxy.AppConnConsensus, block *types.Block, mempool types.Mempool) error { - mempool.Lock() - defer mempool.Unlock() +func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { + blockExec.mempool.Lock() + defer blockExec.mempool.Unlock() // Commit block, get hash back - res, err := proxyAppConn.CommitSync() + res, err := blockExec.proxyApp.CommitSync() if err != nil { - s.logger.Error("Client error during proxyAppConn.CommitSync", "err", err) - return err + blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err) + return nil, err } if res.IsErr() { - s.logger.Error("Error in proxyAppConn.CommitSync", "err", res) - return res + blockExec.logger.Error("Error in proxyAppConn.CommitSync", "err", res) + return nil, res } if res.Log != "" { - s.logger.Debug("Commit.Log: " + res.Log) + blockExec.logger.Debug("Commit.Log: " + res.Log) } - s.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data) + blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data) - // Set the state's new AppHash - s.AppHash = res.Data + // Update evpool + blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) // Update mempool. - return mempool.Update(block.Height, block.Txs) + if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil { + return nil, err + } + + return res.Data, nil } // 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). -func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block, logger log.Logger, lastValidators *types.ValidatorSet) ([]byte, error) { - _, err := execBlockOnProxyApp(types.NopEventBus{}, appConnConsensus, block, logger, lastValidators) +func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block, logger log.Logger) ([]byte, error) { + _, err := execBlockOnProxyApp(logger, appConnConsensus, block) if err != nil { logger.Error("Error executing block on proxy app", "height", block.Height, "err", err) return nil, err diff --git a/state/state.go b/state/state.go index ca9cf16b..723a3255 100644 --- a/state/state.go +++ b/state/state.go @@ -73,7 +73,7 @@ type State struct { // Copy makes a copy of the State for mutating. func (s State) Copy() State { - return &State{ + return State{ ChainID: s.ChainID, LastBlockHeight: s.LastBlockHeight, @@ -95,7 +95,9 @@ func (s State) Copy() State { } // Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. -func (s State) Save(db dbm.DB) { +// 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) @@ -113,7 +115,7 @@ func (s State) Bytes() []byte { } // NextState returns a new State updated according to the header and responses. -func (s State) NextState(header *types.Header, blockPartsHeader types.PartSetHeader, +func (s State) NextState(blockID types.BlockID, header *types.Header, abciResponses *ABCIResponses) (State, error) { // copy the valset so we can apply changes from EndBlock @@ -126,7 +128,7 @@ func (s State) NextState(header *types.Header, blockPartsHeader types.PartSetHea if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates) if err != nil { - return fmt.Errorf("Error changing validator set: %v", 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 @@ -143,17 +145,19 @@ func (s State) NextState(header *types.Header, blockPartsHeader types.PartSetHea nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates) err := nextParams.Validate() if err != nil { - return fmt.Errorf("Error updating consensus params: %v", err) + 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: types.BlockID{header.Hash(), blockPartsHeader}, + LastBlockID: blockID, LastBlockTime: header.Time, Validators: nextValSet, LastValidators: s.Validators.Copy(), @@ -162,7 +166,7 @@ func (s State) NextState(header *types.Header, blockPartsHeader types.PartSetHea LastHeightConsensusParamsChanged: lastHeightParamsChanged, LastResultsHash: abciResponses.ResultsHash(), AppHash: nil, - } + }, nil } // GetValidators returns the last and current validator sets. From f82b7e2a133909e59387a39580d5dcc26922ec61 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 20:03:48 -0500 Subject: [PATCH 04/14] 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 08dd61d1..32f62584 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 969c3328..88ee1127 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 7cda5c1d..1a63d3ed 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 723a3255..ed8a2013 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 486ad24a..cbd3c813 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 00000000..69200840 --- /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 00000000..a8e4d42e --- /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) +} From bac60f2067cfd08f09e02e060bab520bb51f3c3c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 20:40:36 -0500 Subject: [PATCH 05/14] blockchain: update for new state --- blockchain/reactor.go | 67 +++++++++++++++++++++++--------------- blockchain/reactor_test.go | 19 +++++++---- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index f985e284..c8e794a1 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -4,11 +4,11 @@ import ( "bytes" "errors" "reflect" + "sync" "time" wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" @@ -34,29 +34,33 @@ const ( type consensusReactor interface { // for when we switch from blockchain reactor and fast sync to // the consensus machine - SwitchToConsensus(*sm.State, int) + SwitchToConsensus(sm.State, int) } // BlockchainReactor handles long-term catchup syncing. type BlockchainReactor struct { p2p.BaseReactor - state *sm.State - proxyAppConn proxy.AppConnConsensus // same as consensus.proxyAppConn - store *BlockStore - pool *BlockPool - fastSync bool - requestsCh chan BlockRequest - timeoutsCh chan string + mtx sync.Mutex + params types.ConsensusParams - eventBus *types.EventBus + // immutable + initialState sm.State + + blockExec *sm.BlockExecutor + store *BlockStore + pool *BlockPool + fastSync bool + requestsCh chan BlockRequest + timeoutsCh chan string } // NewBlockchainReactor returns new reactor instance. -func NewBlockchainReactor(state *sm.State, proxyAppConn proxy.AppConnConsensus, store *BlockStore, fastSync bool) *BlockchainReactor { +func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *BlockStore, fastSync bool) *BlockchainReactor { if state.LastBlockHeight != store.Height() { cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight, store.Height())) } + requestsCh := make(chan BlockRequest, defaultChannelCapacity) timeoutsCh := make(chan string, defaultChannelCapacity) pool := NewBlockPool( @@ -65,8 +69,9 @@ func NewBlockchainReactor(state *sm.State, proxyAppConn proxy.AppConnConsensus, timeoutsCh, ) bcR := &BlockchainReactor{ - state: state, - proxyAppConn: proxyAppConn, + params: state.ConsensusParams, + initialState: state, + blockExec: blockExec, store: store, pool: pool, fastSync: fastSync, @@ -183,7 +188,16 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) // maxMsgSize returns the maximum allowable size of a // message on the blockchain reactor. func (bcR *BlockchainReactor) maxMsgSize() int { - return bcR.state.ConsensusParams.BlockSize.MaxBytes + 2 + bcR.mtx.Lock() + defer bcR.mtx.Unlock() + return bcR.params.BlockSize.MaxBytes + 2 +} + +// updateConsensusParams updates the internal consensus params +func (bcR *BlockchainReactor) updateConsensusParams(params types.ConsensusParams) { + bcR.mtx.Lock() + defer bcR.mtx.Unlock() + bcR.params = params } // Handle messages from the poolReactor telling the reactor what to do. @@ -197,7 +211,8 @@ func (bcR *BlockchainReactor) poolRoutine() { blocksSynced := 0 - chainID := bcR.state.ChainID + chainID := bcR.initialState.ChainID + state := bcR.initialState lastHundred := time.Now() lastRate := 0.0 @@ -236,7 +251,7 @@ FOR_LOOP: bcR.pool.Stop() conR := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) - conR.SwitchToConsensus(bcR.state, blocksSynced) + conR.SwitchToConsensus(state, blocksSynced) break FOR_LOOP } @@ -251,14 +266,15 @@ FOR_LOOP: // We need both to sync the first block. break SYNC_LOOP } - firstParts := first.MakePartSet(bcR.state.ConsensusParams.BlockPartSizeBytes) + firstParts := first.MakePartSet(state.ConsensusParams.BlockPartSizeBytes) firstPartsHeader := firstParts.Header() + firstID := types.BlockID{first.Hash(), firstPartsHeader} // Finally, verify the first block using the second's commit // NOTE: we can probably make this more efficient, but note that calling // first.Hash() doesn't verify the tx contents, so MakePartSet() is // currently necessary. - err := bcR.state.Validators.VerifyCommit( - chainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit) + err := state.Validators.VerifyCommit( + chainID, firstID, first.Height, second.LastCommit) if err != nil { bcR.Logger.Error("Error in validation", "err", err) bcR.pool.RedoRequest(first.Height) @@ -272,15 +288,17 @@ FOR_LOOP: // NOTE: we could improve performance if we // didn't make the app commit to disk every block // ... but we would need a way to get the hash without it persisting - err := bcR.state.ApplyBlock(bcR.eventBus, bcR.proxyAppConn, - first, firstPartsHeader, - types.MockMempool{}, types.MockEvidencePool{}) // TODO unmock! + var err error + state, err = bcR.blockExec.ApplyBlock(state, firstID, first) if err != nil { // TODO This is bad, are we zombie? cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) } blocksSynced += 1 + // update the consensus params + bcR.updateConsensusParams(state.ConsensusParams) + if blocksSynced%100 == 0 { lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds()) bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height, @@ -302,11 +320,6 @@ func (bcR *BlockchainReactor) BroadcastStatusRequest() error { return nil } -// SetEventBus sets event bus. -func (bcR *BlockchainReactor) SetEventBus(b *types.EventBus) { - bcR.eventBus = b -} - //----------------------------------------------------------------------------- // Messages diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 36cdc080..ecb4a9e6 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -14,14 +14,14 @@ import ( "github.com/tendermint/tendermint/types" ) -func makeStateAndBlockStore(logger log.Logger) (*sm.State, *BlockStore) { +func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { config := cfg.ResetTestRoot("blockchain_reactor_test") blockStore := NewBlockStore(dbm.NewMemDB()) // Get State - state, _ := sm.GetState(dbm.NewMemDB(), config.GenesisFile()) - state.SetLogger(logger.With("module", "state")) - state.Save() + stateDB := dbm.NewMemDB() + state, _ := sm.GetState(stateDB, config.GenesisFile()) + sm.SaveState(stateDB, state, state.AppHash) return state, blockStore } @@ -31,7 +31,10 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe // Make the blockchainReactor itself fastSync := true - bcReactor := NewBlockchainReactor(state.Copy(), nil, blockStore, fastSync) + blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), + nil, nil, types.MockMempool{}, types.MockEvidencePool{}) + + bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor.SetLogger(logger.With("module", "blockchain")) // Next: we need to set a switch in order for peers to be added in @@ -51,7 +54,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe func TestNoBlockMessageResponse(t *testing.T) { maxBlockHeight := int64(20) - bcr := newBlockchainReactor(log.NewNopLogger(), maxBlockHeight) + bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) bcr.Start() defer bcr.Stop() @@ -71,6 +74,8 @@ func TestNoBlockMessageResponse(t *testing.T) { {100, false}, } + // receive a request message from peer, + // wait to hear response for _, tt := range tests { reqBlockMsg := &bcBlockRequestMessage{tt.height} reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg}) @@ -104,7 +109,7 @@ func makeTxs(height int64) (txs []types.Tx) { return txs } -func makeBlock(height int64, state *sm.State) *types.Block { +func makeBlock(height int64, state sm.State) *types.Block { block, _ := state.MakeBlock(height, makeTxs(height), new(types.Commit)) return block } From 0acca7fe69a95f33f4521aaf0b0aca270f7cbf4d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 22:09:48 -0500 Subject: [PATCH 06/14] final updates for state --- blockchain/reactor_test.go | 2 +- consensus/common_test.go | 36 +++++++++---------- consensus/reactor.go | 2 +- consensus/replay.go | 69 +++++++++++++++++++++--------------- consensus/replay_file.go | 22 +++++++----- consensus/replay_test.go | 71 ++++++++++++++++++-------------------- consensus/state.go | 39 ++++++++++----------- consensus/wal_generator.go | 8 ++--- evidence/pool.go | 21 ++++++----- node/node.go | 38 +++++++++++--------- rpc/core/blocks.go | 3 +- rpc/core/consensus.go | 3 +- rpc/core/pipe.go | 8 ++++- state/db.go | 6 ++-- state/execution.go | 21 ++++++----- state/state.go | 2 +- state/state_test.go | 6 ++-- 17 files changed, 192 insertions(+), 165 deletions(-) diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index ecb4a9e6..f58b8394 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -21,7 +21,7 @@ func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { // Get State stateDB := dbm.NewMemDB() state, _ := sm.GetState(stateDB, config.GenesisFile()) - sm.SaveState(stateDB, state, state.AppHash) + sm.SaveState(stateDB, state) return state, blockStore } diff --git a/consensus/common_test.go b/consensus/common_test.go index 6598c15e..ba3564aa 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -235,16 +235,16 @@ func subscribeToVoter(cs *ConsensusState, addr []byte) chan interface{} { //------------------------------------------------------------------------------- // consensus states -func newConsensusState(state *sm.State, pv types.PrivValidator, app abci.Application) *ConsensusState { +func newConsensusState(state sm.State, pv types.PrivValidator, app abci.Application) *ConsensusState { return newConsensusStateWithConfig(config, state, pv, app) } -func newConsensusStateWithConfig(thisConfig *cfg.Config, state *sm.State, pv types.PrivValidator, app abci.Application) *ConsensusState { +func newConsensusStateWithConfig(thisConfig *cfg.Config, state sm.State, pv types.PrivValidator, app abci.Application) *ConsensusState { blockDB := dbm.NewMemDB() return newConsensusStateWithConfigAndBlockStore(thisConfig, state, pv, app, blockDB) } -func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state *sm.State, pv types.PrivValidator, app abci.Application, blockDB dbm.DB) *ConsensusState { +func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.State, pv types.PrivValidator, app abci.Application, blockDB dbm.DB) *ConsensusState { // Get BlockStore blockStore := bc.NewBlockStore(blockDB) @@ -264,7 +264,10 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state *sm. evpool := types.MockEvidencePool{} // Make ConsensusReactor - cs := NewConsensusState(thisConfig.Consensus, state, proxyAppConnCon, blockStore, mempool, evpool) + stateDB := dbm.NewMemDB() // XXX !! + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), + nil, proxyAppConnCon, mempool, evpool) + cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(log.TestingLogger()) cs.SetPrivValidator(pv) @@ -284,9 +287,7 @@ func loadPrivValidator(config *cfg.Config) *types.PrivValidatorFS { } func fixedConsensusStateDummy(config *cfg.Config, logger log.Logger) *ConsensusState { - stateDB := dbm.NewMemDB() - state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) - state.SetLogger(logger.With("module", "state")) + state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile()) privValidator := loadPrivValidator(config) cs := newConsensusState(state, privValidator, dummy.NewDummyApplication()) cs.SetLogger(logger) @@ -354,10 +355,9 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou css := make([]*ConsensusState, nValidators) logger := consensusLogger() for i := 0; i < nValidators; i++ { - db := dbm.NewMemDB() // each state needs its own db - state, _ := sm.MakeGenesisState(db, genDoc) - state.SetLogger(logger.With("module", "state", "validator", i)) - state.Save() + stateDB := dbm.NewMemDB() // each state needs its own db + state, _ := sm.MakeGenesisState(genDoc) + sm.SaveState(stateDB, state) thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) for _, opt := range configOpts { opt(thisConfig) @@ -380,10 +380,9 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF css := make([]*ConsensusState, nPeers) logger := consensusLogger() for i := 0; i < nPeers; i++ { - db := dbm.NewMemDB() // each state needs its own db - state, _ := sm.MakeGenesisState(db, genDoc) - state.SetLogger(logger.With("module", "state", "validator", i)) - state.Save() + stateDB := dbm.NewMemDB() // each state needs its own db + state, _ := sm.MakeGenesisState(genDoc) + sm.SaveState(stateDB, state) thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal var privVal types.PrivValidator @@ -437,12 +436,11 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G }, privValidators } -func randGenesisState(numValidators int, randPower bool, minPower int64) (*sm.State, []*types.PrivValidatorFS) { +func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []*types.PrivValidatorFS) { genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) + s0, _ := sm.MakeGenesisState(genDoc) db := dbm.NewMemDB() - s0, _ := sm.MakeGenesisState(db, genDoc) - s0.SetLogger(log.TestingLogger().With("module", "state")) - s0.Save() + sm.SaveState(db, s0) return s0, privValidators } diff --git a/consensus/reactor.go b/consensus/reactor.go index eb752ee1..9b3393e9 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -82,7 +82,7 @@ func (conR *ConsensusReactor) OnStop() { // SwitchToConsensus switches from fast_sync mode to consensus mode. // It resets the state, turns off fast_sync, and starts the consensus state-machine -func (conR *ConsensusReactor) SwitchToConsensus(state *sm.State, blocksSynced int) { +func (conR *ConsensusReactor) SwitchToConsensus(state sm.State, blocksSynced int) { conR.Logger.Info("SwitchToConsensus") conR.conS.reconstructLastCommit(state) // NOTE: The line below causes broadcastNewRoundStepRoutine() to diff --git a/consensus/replay.go b/consensus/replay.go index a9aaeefc..55c29178 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -13,6 +13,7 @@ import ( abci "github.com/tendermint/abci/types" //auto "github.com/tendermint/tmlibs/autofile" cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" "github.com/tendermint/tendermint/proxy" @@ -186,15 +187,16 @@ func makeHeightSearchFunc(height int64) auto.SearchFunc { // we were last and using the WAL to recover there type Handshaker struct { - state *sm.State - store types.BlockStore - logger log.Logger + stateDB dbm.DB + initialState sm.State + store types.BlockStore + logger log.Logger nBlocks int // number of blocks applied to the state } -func NewHandshaker(state *sm.State, store types.BlockStore) *Handshaker { - return &Handshaker{state, store, log.NewNopLogger(), 0} +func NewHandshaker(stateDB dbm.DB, state sm.State, store types.BlockStore) *Handshaker { + return &Handshaker{stateDB, state, store, log.NewNopLogger(), 0} } func (h *Handshaker) SetLogger(l log.Logger) { @@ -224,7 +226,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { // TODO: check version // replay blocks up to the latest in the blockstore - _, err = h.ReplayBlocks(appHash, blockHeight, proxyApp) + _, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp) if err != nil { return fmt.Errorf("Error on replay: %v", err) } @@ -238,15 +240,15 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { // Replay all blocks since appBlockHeight and ensure the result matches the current state. // Returns the final AppHash or an error -func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) { +func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) { storeBlockHeight := h.store.Height() - stateBlockHeight := h.state.LastBlockHeight + stateBlockHeight := state.LastBlockHeight h.logger.Info("ABCI Replay Blocks", "appHeight", appBlockHeight, "storeHeight", storeBlockHeight, "stateHeight", stateBlockHeight) // If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain if appBlockHeight == 0 { - validators := types.TM2PB.Validators(h.state.Validators) + validators := types.TM2PB.Validators(state.Validators) if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators}); err != nil { return nil, err } @@ -254,7 +256,7 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp // First handle edge cases and constraints on the storeBlockHeight if storeBlockHeight == 0 { - return appHash, h.checkAppHash(appHash) + return appHash, checkAppHash(state, appHash) } else if storeBlockHeight < appBlockHeight { // the app should never be ahead of the store (but this is under app's control) @@ -269,6 +271,7 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp cmn.PanicSanity(cmn.Fmt("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1)) } + var err error // Now either store is equal to state, or one ahead. // For each, consider all cases of where the app could be, given app <= store if storeBlockHeight == stateBlockHeight { @@ -276,11 +279,11 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp // Either the app is asking for replay, or we're all synced up. if appBlockHeight < storeBlockHeight { // the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store) - return h.replayBlocks(proxyApp, appBlockHeight, storeBlockHeight, false) + return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, false) } else if appBlockHeight == storeBlockHeight { // We're good! - return appHash, h.checkAppHash(appHash) + return appHash, checkAppHash(state, appHash) } } else if storeBlockHeight == stateBlockHeight+1 { @@ -289,7 +292,7 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp if appBlockHeight < stateBlockHeight { // the app is further behind than it should be, so replay blocks // but leave the last block to go through the WAL - return h.replayBlocks(proxyApp, appBlockHeight, storeBlockHeight, true) + return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, true) } else if appBlockHeight == stateBlockHeight { // We haven't run Commit (both the state and app are one block behind), @@ -297,17 +300,19 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp // NOTE: We could instead use the cs.WAL on cs.Start, // but we'd have to allow the WAL to replay a block that wrote it's ENDHEIGHT h.logger.Info("Replay last block using real app") - return h.replayBlock(storeBlockHeight, proxyApp.Consensus()) + state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus()) + return state.AppHash, err } else if appBlockHeight == storeBlockHeight { // We ran Commit, but didn't save the state, so replayBlock with mock app - abciResponses, err := sm.LoadABCIResponses(h.state.DB(), storeBlockHeight) + abciResponses, err := sm.LoadABCIResponses(h.stateDB, storeBlockHeight) if err != nil { return nil, err } mockApp := newMockProxyApp(appHash, abciResponses) h.logger.Info("Replay last block using mock app") - return h.replayBlock(storeBlockHeight, mockApp) + state, err = h.replayBlock(state, storeBlockHeight, mockApp) + return state.AppHash, err } } @@ -316,7 +321,7 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp return nil, nil } -func (h *Handshaker) replayBlocks(proxyApp proxy.AppConns, appBlockHeight, storeBlockHeight int64, mutateState bool) ([]byte, error) { +func (h *Handshaker) replayBlocks(state sm.State, proxyApp proxy.AppConns, appBlockHeight, storeBlockHeight int64, mutateState bool) ([]byte, error) { // App is further behind than it should be, so we need to replay blocks. // We replay all blocks from appBlockHeight+1. // @@ -336,7 +341,7 @@ func (h *Handshaker) replayBlocks(proxyApp proxy.AppConns, appBlockHeight, store for i := appBlockHeight + 1; i <= finalBlock; i++ { h.logger.Info("Applying block", "height", i) block := h.store.LoadBlock(i) - appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.state.LastValidators) + appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger) if err != nil { return nil, err } @@ -346,33 +351,41 @@ func (h *Handshaker) replayBlocks(proxyApp proxy.AppConns, appBlockHeight, store if mutateState { // sync the final block - return h.replayBlock(storeBlockHeight, proxyApp.Consensus()) + state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus()) + if err != nil { + return nil, err + } + appHash = state.AppHash } - return appHash, h.checkAppHash(appHash) + return appHash, checkAppHash(state, appHash) } // ApplyBlock on the proxyApp with the last block. -func (h *Handshaker) replayBlock(height int64, proxyApp proxy.AppConnConsensus) ([]byte, error) { +func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.AppConnConsensus) (sm.State, error) { mempool := types.MockMempool{} evpool := types.MockEvidencePool{} block := h.store.LoadBlock(height) meta := h.store.LoadBlockMeta(height) - if err := h.state.ApplyBlock(types.NopEventBus{}, proxyApp, - block, meta.BlockID.PartsHeader, mempool, evpool); err != nil { - return nil, err + blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, + types.NopEventBus{}, proxyApp, mempool, evpool) + + var err error + state, err = blockExec.ApplyBlock(state, meta.BlockID, block) + if err != nil { + return sm.State{}, err } h.nBlocks += 1 - return h.state.AppHash, nil + return state, nil } -func (h *Handshaker) checkAppHash(appHash []byte) error { - if !bytes.Equal(h.state.AppHash, appHash) { - panic(fmt.Errorf("Tendermint state.AppHash does not match AppHash after replay. Got %X, expected %X", appHash, h.state.AppHash).Error()) +func checkAppHash(state sm.State, appHash []byte) error { + if !bytes.Equal(state.AppHash, appHash) { + panic(fmt.Errorf("Tendermint state.AppHash does not match AppHash after replay. Got %X, expected %X", appHash, state.AppHash).Error()) } return nil } diff --git a/consensus/replay_file.go b/consensus/replay_file.go index 4db58ada..d832abad 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -18,6 +18,7 @@ import ( "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" ) const ( @@ -104,11 +105,11 @@ type playback struct { count int // how many lines/msgs into the file are we // replays can be reset to beginning - fileName string // so we can close/reopen the file - genesisState *sm.State // so the replay session knows where to restart from + fileName string // so we can close/reopen the file + genesisState sm.State // so the replay session knows where to restart from } -func newPlayback(fileName string, fp *os.File, cs *ConsensusState, genState *sm.State) *playback { +func newPlayback(fileName string, fp *os.File, cs *ConsensusState, genState sm.State) *playback { return &playback{ cs: cs, fp: fp, @@ -123,7 +124,7 @@ func (pb *playback) replayReset(count int, newStepCh chan interface{}) error { pb.cs.Stop() pb.cs.Wait() - newCS := NewConsensusState(pb.cs.config, pb.genesisState.Copy(), pb.cs.proxyAppConn, + newCS := NewConsensusState(pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec, pb.cs.blockStore, pb.cs.mempool, pb.cs.evpool) newCS.SetEventBus(pb.cs.eventBus) newCS.startForReplay() @@ -285,14 +286,14 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo // Get State stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir()) - state, err := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + state, err := sm.MakeGenesisStateFromFile(config.GenesisFile()) if err != nil { cmn.Exit(err.Error()) } // Create proxyAppConn connection (consensus, mempool, query) clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()) - proxyApp := proxy.NewAppConns(clientCreator, NewHandshaker(state, blockStore)) + proxyApp := proxy.NewAppConns(clientCreator, NewHandshaker(stateDB, state, blockStore)) err = proxyApp.Start() if err != nil { cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err)) @@ -303,8 +304,13 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo cmn.Exit(cmn.Fmt("Failed to start event bus: %v", err)) } - consensusState := NewConsensusState(csConfig, state.Copy(), proxyApp.Consensus(), - blockStore, types.MockMempool{}, types.MockEvidencePool{}) + mempool, evpool := types.MockMempool{}, types.MockEvidencePool{} + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), + nil, proxyApp.Consensus(), + mempool, evpool) + + consensusState := NewConsensusState(csConfig, state.Copy(), blockExec, + blockStore, mempool, evpool) consensusState.SetEventBus(eventBus) return consensusState diff --git a/consensus/replay_test.go b/consensus/replay_test.go index f1a060ec..4647ff3d 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -54,7 +54,6 @@ func init() { func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64, blockDB dbm.DB, stateDB dbm.DB) { logger := log.TestingLogger() state, _ := sm.GetState(stateDB, consensusReplayConfig.GenesisFile()) - state.SetLogger(logger.With("module", "state")) privValidator := loadPrivValidator(consensusReplayConfig) cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, dummy.NewDummyApplication(), blockDB) cs.SetLogger(logger) @@ -98,22 +97,22 @@ func sendTxs(cs *ConsensusState, ctx context.Context) { func TestWALCrash(t *testing.T) { testCases := []struct { name string - initFn func(*ConsensusState, context.Context) + initFn func(dbm.DB, *ConsensusState, context.Context) heightToStop int64 }{ {"empty block", - func(cs *ConsensusState, ctx context.Context) {}, + func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) {}, 1}, {"block with a smaller part size", - func(cs *ConsensusState, ctx context.Context) { + func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) { // XXX: is there a better way to change BlockPartSizeBytes? cs.state.ConsensusParams.BlockPartSizeBytes = 512 - cs.state.Save() + sm.SaveState(stateDB, cs.state) go sendTxs(cs, ctx) }, 1}, {"many non-empty blocks", - func(cs *ConsensusState, ctx context.Context) { + func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) { go sendTxs(cs, ctx) }, 3}, @@ -126,7 +125,7 @@ func TestWALCrash(t *testing.T) { } } -func crashWALandCheckLiveness(t *testing.T, initFn func(*ConsensusState, context.Context), heightToStop int64) { +func crashWALandCheckLiveness(t *testing.T, initFn func(dbm.DB, *ConsensusState, context.Context), heightToStop int64) { walPaniced := make(chan error) crashingWal := &crashingWAL{panicCh: walPaniced, heightToStop: heightToStop} @@ -139,8 +138,7 @@ LOOP: // create consensus state from a clean slate logger := log.NewNopLogger() stateDB := dbm.NewMemDB() - state, _ := sm.MakeGenesisStateFromFile(stateDB, consensusReplayConfig.GenesisFile()) - state.SetLogger(logger.With("module", "state")) + state, _ := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile()) privValidator := loadPrivValidator(consensusReplayConfig) blockDB := dbm.NewMemDB() cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, dummy.NewDummyApplication(), blockDB) @@ -148,7 +146,7 @@ LOOP: // start sending transactions ctx, cancel := context.WithCancel(context.Background()) - initFn(cs, ctx) + initFn(stateDB, cs, ctx) // clean up WAL file from the previous iteration walFile := cs.config.WalFile() @@ -344,12 +342,13 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { t.Fatalf(err.Error()) } - state, store := stateAndStore(config, privVal.GetPubKey()) + stateDB, state, store := stateAndStore(config, privVal.GetPubKey()) store.chain = chain store.commits = commits // run the chain through state.ApplyBlock to build up the tendermint state - latestAppHash := buildTMStateFromChain(config, state, chain, mode) + state = buildTMStateFromChain(config, stateDB, state, chain, mode) + latestAppHash := state.AppHash // make a new client creator dummyApp := dummy.NewPersistentDummyApplication(path.Join(config.DBDir(), "2")) @@ -358,12 +357,12 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { // run nBlocks against a new client to build up the app state. // use a throwaway tendermint state proxyApp := proxy.NewAppConns(clientCreator2, nil) - state, _ := stateAndStore(config, privVal.GetPubKey()) - buildAppStateFromChain(proxyApp, state, chain, nBlocks, mode) + stateDB, state, _ := stateAndStore(config, privVal.GetPubKey()) + buildAppStateFromChain(proxyApp, stateDB, state, chain, nBlocks, mode) } // now start the app using the handshake - it should sync - handshaker := NewHandshaker(state, store) + handshaker := NewHandshaker(stateDB, state, store) proxyApp := proxy.NewAppConns(clientCreator2, handshaker) if err := proxyApp.Start(); err != nil { t.Fatalf("Error starting proxy app connections: %v", err) @@ -393,16 +392,21 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { } } -func applyBlock(st *sm.State, blk *types.Block, proxyApp proxy.AppConns) { +func applyBlock(stateDB dbm.DB, st sm.State, blk *types.Block, proxyApp proxy.AppConns) sm.State { testPartSize := st.ConsensusParams.BlockPartSizeBytes - err := st.ApplyBlock(types.NopEventBus{}, proxyApp.Consensus(), blk, blk.MakePartSet(testPartSize).Header(), mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), + types.NopEventBus{}, proxyApp.Consensus(), mempool, evpool) + + blkID := types.BlockID{blk.Hash(), blk.MakePartSet(testPartSize).Header()} + newState, err := blockExec.ApplyBlock(st, blkID, blk) if err != nil { panic(err) } + return newState } -func buildAppStateFromChain(proxyApp proxy.AppConns, - state *sm.State, chain []*types.Block, nBlocks int, mode uint) { +func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB, + state sm.State, chain []*types.Block, nBlocks int, mode uint) { // start a new app without handshake, play nBlocks blocks if err := proxyApp.Start(); err != nil { panic(err) @@ -418,24 +422,24 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, case 0: for i := 0; i < nBlocks; i++ { block := chain[i] - applyBlock(state, block, proxyApp) + state = applyBlock(stateDB, state, block, proxyApp) } case 1, 2: for i := 0; i < nBlocks-1; i++ { block := chain[i] - applyBlock(state, block, proxyApp) + state = applyBlock(stateDB, state, block, proxyApp) } if mode == 2 { // update the dummy height and apphash // as if we ran commit but not - applyBlock(state, chain[nBlocks-1], proxyApp) + state = applyBlock(stateDB, state, chain[nBlocks-1], proxyApp) } } } -func buildTMStateFromChain(config *cfg.Config, state *sm.State, chain []*types.Block, mode uint) []byte { +func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, chain []*types.Block, mode uint) sm.State { // run the whole chain against this client to build up the tendermint state clientCreator := proxy.NewLocalClientCreator(dummy.NewPersistentDummyApplication(path.Join(config.DBDir(), "1"))) proxyApp := proxy.NewAppConns(clientCreator, nil) // sm.NewHandshaker(config, state, store, ReplayLastBlock)) @@ -449,31 +453,26 @@ func buildTMStateFromChain(config *cfg.Config, state *sm.State, chain []*types.B panic(err) } - var latestAppHash []byte - switch mode { case 0: // sync right up for _, block := range chain { - applyBlock(state, block, proxyApp) + state = applyBlock(stateDB, state, block, proxyApp) } - latestAppHash = state.AppHash case 1, 2: // sync up to the penultimate as if we stored the block. // whether we commit or not depends on the appHash for _, block := range chain[:len(chain)-1] { - applyBlock(state, block, proxyApp) + state = applyBlock(stateDB, state, block, proxyApp) } // apply the final block to a state copy so we can // get the right next appHash but keep the state back - stateCopy := state.Copy() - applyBlock(stateCopy, chain[len(chain)-1], proxyApp) - latestAppHash = stateCopy.AppHash + applyBlock(stateDB, state, chain[len(chain)-1], proxyApp) } - return latestAppHash + return state } //-------------------------- @@ -587,13 +586,11 @@ func readPieceFromWAL(msg *TimedWALMessage) interface{} { } // fresh state and mock store -func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBlockStore) { +func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (dbm.DB, sm.State, *mockBlockStore) { stateDB := dbm.NewMemDB() - state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) - state.SetLogger(log.TestingLogger().With("module", "state")) - + state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile()) store := NewMockBlockStore(config, state.ConsensusParams) - return state, store + return stateDB, state, store } //---------------------------------- diff --git a/consensus/state.go b/consensus/state.go index 5e83e6a5..477d872b 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -17,7 +17,6 @@ import ( cfg "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" - "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -75,15 +74,16 @@ type ConsensusState struct { privValidator types.PrivValidator // for signing votes // services for creating and executing blocks - proxyAppConn proxy.AppConnConsensus - blockStore types.BlockStore - mempool types.Mempool - evpool types.EvidencePool + // TODO: encapsulate all of this in one "BlockManager" + blockExec *sm.BlockExecutor + blockStore types.BlockStore + mempool types.Mempool + evpool types.EvidencePool // internal state mtx sync.Mutex cstypes.RoundState - state *sm.State // State until height-1. + state sm.State // State until height-1. // state changes may be triggered by msgs from peers, // msgs from ourself, or by timeouts @@ -114,10 +114,10 @@ type ConsensusState struct { } // NewConsensusState returns a new ConsensusState. -func NewConsensusState(config *cfg.ConsensusConfig, state *sm.State, proxyAppConn proxy.AppConnConsensus, blockStore types.BlockStore, mempool types.Mempool, evpool types.EvidencePool) *ConsensusState { +func NewConsensusState(config *cfg.ConsensusConfig, state sm.State, blockExec *sm.BlockExecutor, blockStore types.BlockStore, mempool types.Mempool, evpool types.EvidencePool) *ConsensusState { cs := &ConsensusState{ config: config, - proxyAppConn: proxyAppConn, + blockExec: blockExec, blockStore: blockStore, mempool: mempool, peerMsgQueue: make(chan msgInfo, msgQueueSize), @@ -162,7 +162,7 @@ func (cs *ConsensusState) String() string { } // GetState returns a copy of the chain state. -func (cs *ConsensusState) GetState() *sm.State { +func (cs *ConsensusState) GetState() sm.State { cs.mtx.Lock() defer cs.mtx.Unlock() return cs.state.Copy() @@ -399,7 +399,7 @@ func (cs *ConsensusState) sendInternalMessage(mi msgInfo) { // Reconstruct LastCommit from SeenCommit, which we saved along with the block, // (which happens even before saving the state) -func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { +func (cs *ConsensusState) reconstructLastCommit(state sm.State) { if state.LastBlockHeight == 0 { return } @@ -422,12 +422,12 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { // Updates ConsensusState and increments height to match that of state. // The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight. -func (cs *ConsensusState) updateToState(state *sm.State) { +func (cs *ConsensusState) updateToState(state sm.State) { if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { cmn.PanicSanity(cmn.Fmt("updateToState() expected state height of %v but found %v", cs.Height, state.LastBlockHeight)) } - if cs.state != nil && cs.state.LastBlockHeight+1 != cs.Height { + if !cs.state.IsEmpty() && cs.state.LastBlockHeight+1 != cs.Height { // This might happen when someone else is mutating cs.state. // Someone forgot to pass in state.Copy() somewhere?! cmn.PanicSanity(cmn.Fmt("Inconsistent cs.state.LastBlockHeight+1 %v vs cs.Height %v", @@ -437,7 +437,7 @@ func (cs *ConsensusState) updateToState(state *sm.State) { // If state isn't further out than cs.state, just ignore. // This happens when SwitchToConsensus() is called in the reactor. // We don't want to reset e.g. the Votes. - if cs.state != nil && (state.LastBlockHeight <= cs.state.LastBlockHeight) { + if !cs.state.IsEmpty() && (state.LastBlockHeight <= cs.state.LastBlockHeight) { cs.Logger.Info("Ignoring updateToState()", "newHeight", state.LastBlockHeight+1, "oldHeight", cs.state.LastBlockHeight+1) return } @@ -922,7 +922,7 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { } // Validate proposal block - err := cs.state.ValidateBlock(cs.ProposalBlock) + err := sm.ValidateBlock(cs.state, cs.ProposalBlock) if err != nil { // ProposalBlock is invalid, prevote nil. logger.Error("enterPrevote: ProposalBlock is invalid", "err", err) @@ -1030,7 +1030,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.Logger.Info("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash) // Validate the block. - if err := cs.state.ValidateBlock(cs.ProposalBlock); err != nil { + if err := sm.ValidateBlock(cs.state, cs.ProposalBlock); err != nil { cmn.PanicConsensus(cmn.Fmt("enterPrecommit: +2/3 prevoted for an invalid block: %v", err)) } cs.LockedRound = round @@ -1165,7 +1165,7 @@ func (cs *ConsensusState) finalizeCommit(height int64) { if !block.HashesTo(blockID.Hash) { cmn.PanicSanity(cmn.Fmt("Cannot finalizeCommit, ProposalBlock does not hash to commit hash")) } - if err := cs.state.ValidateBlock(block); err != nil { + if err := sm.ValidateBlock(cs.state, block); err != nil { cmn.PanicConsensus(cmn.Fmt("+2/3 committed an invalid block: %v", err)) } @@ -1204,13 +1204,12 @@ func (cs *ConsensusState) finalizeCommit(height int64) { // and an event cache for txs stateCopy := cs.state.Copy() txEventBuffer := types.NewTxEventBuffer(cs.eventBus, int(block.NumTxs)) + cs.blockExec.SetTxEventPublisher(txEventBuffer) // Execute and commit the block, update and save the state, and update the mempool. - // All calls to the proxyAppConn come here. // NOTE: the block.AppHash wont reflect these txs until the next block - err := stateCopy.ApplyBlock(txEventBuffer, cs.proxyAppConn, - block, blockParts.Header(), - cs.mempool, cs.evpool) + var err error + stateCopy, err = cs.blockExec.ApplyBlock(stateCopy, types.BlockID{block.Hash(), blockParts.Header()}, block) if err != nil { cs.Logger.Error("Error on ApplyBlock. Did the application crash? Please restart tendermint", "err", err) err := cmn.Kill() diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index 73ad3e7f..fe9066b3 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -47,13 +47,12 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { } stateDB := db.NewMemDB() blockStoreDB := db.NewMemDB() - state, err := sm.MakeGenesisState(stateDB, genDoc) - state.SetLogger(logger.With("module", "state")) + state, err := sm.MakeGenesisState(genDoc) if err != nil { return nil, errors.Wrap(err, "failed to make genesis state") } blockStore := bc.NewBlockStore(blockStoreDB) - handshaker := NewHandshaker(state, blockStore) + handshaker := NewHandshaker(stateDB, state, blockStore) proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker) proxyApp.SetLogger(logger.With("module", "proxy")) if err := proxyApp.Start(); err != nil { @@ -68,7 +67,8 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { defer eventBus.Stop() mempool := types.MockMempool{} evpool := types.MockEvidencePool{} - consensusState := NewConsensusState(config.Consensus, state.Copy(), proxyApp.Consensus(), blockStore, mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), nil, proxyApp.Consensus(), mempool, evpool) + consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool) consensusState.SetLogger(logger) consensusState.SetEventBus(eventBus) if privValidator != nil { diff --git a/evidence/pool.go b/evidence/pool.go index 381801df..2e7cd470 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -21,13 +21,13 @@ type EvidencePool struct { evidenceChan chan types.Evidence } -func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state types.State) *EvidencePool { +func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore) *EvidencePool { evpool := &EvidencePool{ params: params, logger: log.NewNopLogger(), evidenceStore: evidenceStore, - state: *state, - evidenceChan: make(chan types.Evidence), + // state: *state, + evidenceChan: make(chan types.Evidence), } return evpool } @@ -58,12 +58,15 @@ func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) { // TODO: check if we already have evidence for this // validator at this height so we dont get spammed - priority, err := sm.VerifyEvidence(evpool.state, evidence) - if err != nil { - // TODO: if err is just that we cant find it cuz we pruned, ignore. - // TODO: if its actually bad evidence, punish peer - return err - } + // TODO + var priority int64 + /* + priority, err := sm.VerifyEvidence(evpool.state, evidence) + if err != nil { + // TODO: if err is just that we cant find it cuz we pruned, ignore. + // TODO: if its actually bad evidence, punish peer + return err + }*/ added := evpool.evidenceStore.AddNewEvidence(evidence, priority) if !added { diff --git a/node/node.go b/node/node.go index 53eab6e0..15e4f043 100644 --- a/node/node.go +++ b/node/node.go @@ -102,7 +102,8 @@ type Node struct { trustMetricStore *trust.TrustMetricStore // trust metrics for all peers // services - eventBus *types.EventBus // pub/sub for services + eventBus *types.EventBus // pub/sub for services + stateDB dbm.DB blockStore *bc.BlockStore // store the blockchain to disk bcReactor *bc.BlockchainReactor // for fast-syncing mempoolReactor *mempl.MempoolReactor // for gossipping transactions @@ -148,21 +149,20 @@ func NewNode(config *cfg.Config, saveGenesisDoc(stateDB, genDoc) } - stateLogger := logger.With("module", "state") state := sm.LoadState(stateDB) - if state == nil { - state, err = sm.MakeGenesisState(stateDB, genDoc) + if state.IsEmpty() { + state, err = sm.MakeGenesisState(genDoc) if err != nil { return nil, err } - state.Save() + sm.SaveState(stateDB, state) } - state.SetLogger(stateLogger) // Create the proxyApp, which manages connections (consensus, mempool, query) - // and sync tendermint and the app by replaying any necessary blocks + // and sync tendermint and the app by performing a handshake + // and replaying any necessary blocks consensusLogger := logger.With("module", "consensus") - handshaker := consensus.NewHandshaker(state, blockStore) + handshaker := consensus.NewHandshaker(stateDB, state, blockStore) handshaker.SetLogger(consensusLogger) proxyApp := proxy.NewAppConns(clientCreator, handshaker) proxyApp.SetLogger(logger.With("module", "proxy")) @@ -172,7 +172,6 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) - state.SetLogger(stateLogger) // Generate node PrivKey privKey := crypto.GenPrivKeyEd25519() @@ -194,10 +193,6 @@ func NewNode(config *cfg.Config, consensusLogger.Info("This node is not a validator") } - // Make BlockchainReactor - bcReactor := bc.NewBlockchainReactor(state.Copy(), proxyApp.Consensus(), blockStore, fastSync) - bcReactor.SetLogger(logger.With("module", "blockchain")) - // Make MempoolReactor mempoolLogger := logger.With("module", "mempool") mempool := mempl.NewMempool(config.Mempool, proxyApp.Mempool(), state.LastBlockHeight) @@ -216,14 +211,24 @@ func NewNode(config *cfg.Config, } evidenceLogger := logger.With("module", "evidence") evidenceStore := evidence.NewEvidenceStore(evidenceDB) - evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore, state.Copy()) + evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore) // , state.Copy()) evidencePool.SetLogger(evidenceLogger) evidenceReactor := evidence.NewEvidenceReactor(evidencePool) evidenceReactor.SetLogger(evidenceLogger) + blockExecLogger := logger.With("module", "state") + // make block executor for consensus and blockchain reactors to execute blocks + blockExec := sm.NewBlockExecutor(stateDB, blockExecLogger, + nil, proxyApp.Consensus(), + mempool, evidencePool) + + // Make BlockchainReactor + bcReactor := bc.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor.SetLogger(logger.With("module", "blockchain")) + // Make ConsensusReactor consensusState := consensus.NewConsensusState(config.Consensus, state.Copy(), - proxyApp.Consensus(), blockStore, mempool, evidencePool) + blockExec, blockStore, mempool, evidencePool) consensusState.SetLogger(consensusLogger) if privValidator != nil { consensusState.SetPrivValidator(privValidator) @@ -291,7 +296,6 @@ func NewNode(config *cfg.Config, eventBus.SetLogger(logger.With("module", "events")) // services which will be publishing and/or subscribing for messages (events) - bcReactor.SetEventBus(eventBus) consensusReactor.SetEventBus(eventBus) // Transaction indexing @@ -333,6 +337,7 @@ func NewNode(config *cfg.Config, addrBook: addrBook, trustMetricStore: trustMetricStore, + stateDB: stateDB, blockStore: blockStore, bcReactor: bcReactor, mempoolReactor: mempoolReactor, @@ -429,6 +434,7 @@ func (n *Node) AddListener(l p2p.Listener) { // ConfigureRPC sets all variables in rpccore so they will serve // rpc calls from this node func (n *Node) ConfigureRPC() { + rpccore.SetStateDB(n.stateDB) rpccore.SetBlockStore(n.blockStore) rpccore.SetConsensusState(n.consensusState) rpccore.SetMempool(n.mempoolReactor.Mempool) diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 8b0ee459..853bb0f7 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -337,8 +337,7 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { } // load the results - state := consensusState.GetState() - results, err := sm.LoadABCIResponses(state.DB(), height) + results, err := sm.LoadABCIResponses(stateDB, height) if err != nil { return nil, err } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index eedcce27..65c9fc36 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -50,8 +50,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { return nil, err } - state := consensusState.GetState() - validators, err := sm.LoadValidators(state.DB(), height) + validators, err := sm.LoadValidators(stateDB, height) if err != nil { return nil, err } diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 325625c7..927d7cca 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -11,6 +11,7 @@ import ( sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/txindex" "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" ) @@ -20,7 +21,7 @@ var subscribeTimeout = 5 * time.Second // These interfaces are used by RPC and must be thread safe type Consensus interface { - GetState() *sm.State + GetState() sm.State GetValidators() (int64, []*types.Validator) GetRoundState() *cstypes.RoundState } @@ -43,6 +44,7 @@ var ( proxyAppQuery proxy.AppConnQuery // interfaces defined in types and above + stateDB dbm.DB blockStore types.BlockStore mempool types.Mempool evidencePool types.EvidencePool @@ -60,6 +62,10 @@ var ( logger log.Logger ) +func SetStateDB(db dbm.DB) { + stateDB = db +} + func SetBlockStore(bs types.BlockStore) { blockStore = bs } diff --git a/state/db.go b/state/db.go index 32f62584..fbe99863 100644 --- a/state/db.go +++ b/state/db.go @@ -36,7 +36,7 @@ func GetState(stateDB dbm.DB, genesisFile string) (State, error) { if err != nil { return state, err } - SaveState(stateDB, state, state.AppHash) + SaveState(stateDB, state) } return state, nil @@ -66,9 +66,7 @@ func loadState(db dbm.DB, key []byte) (state 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 +func SaveState(db dbm.DB, s State) { nextHeight := s.LastBlockHeight + 1 saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators) saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams) diff --git a/state/execution.go b/state/execution.go index 88ee1127..ce45e4c4 100644 --- a/state/execution.go +++ b/state/execution.go @@ -30,6 +30,10 @@ type BlockExecutor struct { evpool types.EvidencePool } +func (blockExec *BlockExecutor) SetTxEventPublisher(txEventPublisher types.TxEventPublisher) { + blockExec.txEventPublisher = txEventPublisher +} + // NewBlockExecutor returns a new BlockExecutor. func NewBlockExecutor(db dbm.DB, logger log.Logger, txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus, @@ -82,8 +86,9 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block fail.Fail() // XXX - // save the state and the validators - SaveState(blockExec.db, s, appHash) + // update the app hash and save the state + s.AppHash = appHash + SaveState(blockExec.db, s) return s, nil } @@ -110,7 +115,7 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { blockExec.logger.Debug("Commit.Log: " + res.Log) } - blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data) + blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "appHash", res.Data) // Update evpool blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) @@ -343,16 +348,14 @@ func updateState(s State, blockID types.BlockID, header *types.Header, } func fireEvents(txEventPublisher types.TxEventPublisher, block *types.Block, abciResponses *ABCIResponses) { - // TODO: Fire events - /* - tx := types.Tx(req.GetDeliverTx().Tx) + for i, tx := range block.Data.Txs { txEventPublisher.PublishEventTx(types.EventDataTx{types.TxResult{ Height: block.Height, - Index: uint32(txIndex), + Index: uint32(i), Tx: tx, - Result: *txRes, + Result: *(abciResponses.DeliverTx[i]), }}) - */ + } } //---------------------------------------------------------------------------------------------------- diff --git a/state/state.go b/state/state.go index ed8a2013..7ffa56ed 100644 --- a/state/state.go +++ b/state/state.go @@ -91,7 +91,7 @@ func (s State) Bytes() []byte { // 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 + return s.Validators == nil // XXX can't compare to Empty } // GetValidators returns the last and current validator sets. diff --git a/state/state_test.go b/state/state_test.go index cbd3c813..7e9ed2cf 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -57,7 +57,7 @@ func TestStateSaveLoad(t *testing.T) { assert := assert.New(t) state.LastBlockHeight++ - SaveState(stateDB, state, state.AppHash) + SaveState(stateDB, state) loadedState := LoadState(stateDB) assert.True(state.Equals(loadedState), @@ -261,7 +261,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { const valSetSize = 7 tearDown, stateDB, state := setupTestCase(t) state.Validators = genValSet(valSetSize) - SaveState(stateDB, state, state.AppHash) + SaveState(stateDB, state) defer tearDown(t) const height = 1 @@ -425,7 +425,7 @@ func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) { for i, tc := range testCases { tearDown, stateDB, state := setupTestCase(t) state.Validators = genValSet(tc.initialValSetSize) - SaveState(stateDB, state, state.AppHash) + SaveState(stateDB, state) height := state.LastBlockHeight + 1 block := makeBlock(state, height) abciResponses := &ABCIResponses{ From 537b0dfa1a0cb0048c6100b54c413c0877681c3a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 22:25:46 -0500 Subject: [PATCH 07/14] use NopEventBus --- blockchain/reactor_test.go | 2 +- consensus/common_test.go | 2 +- consensus/replay_file.go | 2 +- consensus/wal_generator.go | 2 +- node/node.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index f58b8394..2edff18b 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -32,7 +32,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe // Make the blockchainReactor itself fastSync := true blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), - nil, nil, types.MockMempool{}, types.MockEvidencePool{}) + types.NopEventBus{}, nil, types.MockMempool{}, types.MockEvidencePool{}) bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor.SetLogger(logger.With("module", "blockchain")) diff --git a/consensus/common_test.go b/consensus/common_test.go index ba3564aa..1516365e 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -266,7 +266,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S // Make ConsensusReactor stateDB := dbm.NewMemDB() // XXX !! blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), - nil, proxyAppConnCon, mempool, evpool) + types.NopEventBus{}, proxyAppConnCon, mempool, evpool) cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(log.TestingLogger()) cs.SetPrivValidator(pv) diff --git a/consensus/replay_file.go b/consensus/replay_file.go index d832abad..4e490ccb 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -306,7 +306,7 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo mempool, evpool := types.MockMempool{}, types.MockEvidencePool{} blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), - nil, proxyApp.Consensus(), + types.NopEventBus{}, proxyApp.Consensus(), mempool, evpool) consensusState := NewConsensusState(csConfig, state.Copy(), blockExec, diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index fe9066b3..c4171b12 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -67,7 +67,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { defer eventBus.Stop() mempool := types.MockMempool{} evpool := types.MockEvidencePool{} - blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), nil, proxyApp.Consensus(), mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), types.NopEventBus{}, proxyApp.Consensus(), mempool, evpool) consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool) consensusState.SetLogger(logger) consensusState.SetEventBus(eventBus) diff --git a/node/node.go b/node/node.go index 15e4f043..603fafec 100644 --- a/node/node.go +++ b/node/node.go @@ -219,7 +219,7 @@ func NewNode(config *cfg.Config, blockExecLogger := logger.With("module", "state") // make block executor for consensus and blockchain reactors to execute blocks blockExec := sm.NewBlockExecutor(stateDB, blockExecLogger, - nil, proxyApp.Consensus(), + types.NopEventBus{}, proxyApp.Consensus(), mempool, evidencePool) // Make BlockchainReactor From 397251b0f4d7f63f447d9b2d76bcdde612bf8633 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 Dec 2017 22:39:58 -0500 Subject: [PATCH 08/14] fix evidence --- evidence/pool.go | 35 +++++++++++++++++++++++------------ evidence/pool_test.go | 11 +++-------- evidence/reactor_test.go | 3 +-- evidence/store_test.go | 6 ++++-- node/node.go | 2 +- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/evidence/pool.go b/evidence/pool.go index 2e7cd470..cdc01398 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -3,6 +3,7 @@ package evidence import ( "github.com/tendermint/tmlibs/log" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -13,16 +14,16 @@ type EvidencePool struct { evidenceStore *EvidenceStore - chainID string - lastBlockHeight int64 - params types.EvidenceParams + state sm.State + params types.EvidenceParams // never close evidenceChan chan types.Evidence } -func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore) *EvidencePool { +func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state sm.State) *EvidencePool { evpool := &EvidencePool{ + state: state, params: params, logger: log.NewNopLogger(), evidenceStore: evidenceStore, @@ -58,15 +59,25 @@ func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) { // TODO: check if we already have evidence for this // validator at this height so we dont get spammed - // TODO + if err := sm.VerifyEvidence(evpool.state, evidence); err != nil { + return err + } + var priority int64 - /* - priority, err := sm.VerifyEvidence(evpool.state, evidence) - if err != nil { - // TODO: if err is just that we cant find it cuz we pruned, ignore. - // TODO: if its actually bad evidence, punish peer - return err - }*/ + /* // Needs a db ... + // TODO: if err is just that we cant find it cuz we pruned, ignore. + // TODO: if its actually bad evidence, punish peer + + 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) + } + */ added := evpool.evidenceStore.AddNewEvidence(evidence, priority) if !added { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 0997505c..d7b94e88 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -6,24 +6,19 @@ import ( "github.com/stretchr/testify/assert" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tmlibs/db" ) -type mockState struct{} - -func (m mockState) VerifyEvidence(ev types.Evidence) (int64, error) { - err := ev.Verify("") - return 10, err -} +var mockState = sm.State{} func TestEvidencePool(t *testing.T) { assert := assert.New(t) params := types.EvidenceParams{} store := NewEvidenceStore(dbm.NewMemDB()) - state := mockState{} - pool := NewEvidencePool(params, store, state) + pool := NewEvidencePool(params, store, mockState) goodEvidence := newMockGoodEvidence(5, 1, []byte("val1")) badEvidence := MockBadEvidence{goodEvidence} diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index fb83667c..fc4ea571 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -39,8 +39,7 @@ func makeAndConnectEvidenceReactors(config *cfg.Config, N int) []*EvidenceReacto params := types.EvidenceParams{} store := NewEvidenceStore(dbm.NewMemDB()) - state := mockState{} - pool := NewEvidencePool(params, store, state) + pool := NewEvidencePool(params, store, mockState) reactors[i] = NewEvidenceReactor(pool) reactors[i].SetLogger(logger.With("validator", i)) } diff --git a/evidence/store_test.go b/evidence/store_test.go index 7828d37b..192aabc2 100644 --- a/evidence/store_test.go +++ b/evidence/store_test.go @@ -113,12 +113,14 @@ func TestStorePriority(t *testing.T) { //------------------------------------------- const ( - evidenceTypeMock = byte(0x01) + evidenceTypeMockGood = byte(0x01) + evidenceTypeMockBad = byte(0x02) ) var _ = wire.RegisterInterface( struct{ types.Evidence }{}, - wire.ConcreteType{MockGoodEvidence{}, evidenceTypeMock}, + wire.ConcreteType{MockGoodEvidence{}, evidenceTypeMockGood}, + wire.ConcreteType{MockBadEvidence{}, evidenceTypeMockBad}, ) type MockGoodEvidence struct { diff --git a/node/node.go b/node/node.go index 603fafec..cd1e6320 100644 --- a/node/node.go +++ b/node/node.go @@ -211,7 +211,7 @@ func NewNode(config *cfg.Config, } evidenceLogger := logger.With("module", "evidence") evidenceStore := evidence.NewEvidenceStore(evidenceDB) - evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore) // , state.Copy()) + evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore, state.Copy()) evidencePool.SetLogger(evidenceLogger) evidenceReactor := evidence.NewEvidenceReactor(evidencePool) evidenceReactor.SetLogger(evidenceLogger) From 1d6f00859dd46beae036146782f0f088b2e50e63 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 18:26:13 -0500 Subject: [PATCH 09/14] fixes from review --- blockchain/reactor_test.go | 12 ++++-------- consensus/common_test.go | 9 +++------ consensus/replay.go | 6 +----- consensus/replay_file.go | 4 +--- consensus/replay_test.go | 5 ++--- consensus/wal_generator.go | 2 +- evidence/pool.go | 3 +-- node/node.go | 15 +++++---------- state/execution.go | 35 +++++++++++++++++++++-------------- state/execution_test.go | 11 +---------- state/state.go | 2 +- state/state_test.go | 4 ++-- state/{db.go => store.go} | 29 +++++++++++++++++++++++++---- 13 files changed, 68 insertions(+), 69 deletions(-) rename state/{db.go => store.go} (90%) diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 2edff18b..fcb8a6f8 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -10,6 +10,7 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -17,12 +18,7 @@ import ( func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { config := cfg.ResetTestRoot("blockchain_reactor_test") blockStore := NewBlockStore(dbm.NewMemDB()) - - // Get State - stateDB := dbm.NewMemDB() - state, _ := sm.GetState(stateDB, config.GenesisFile()) - sm.SaveState(stateDB, state) - + state, _ := sm.LoadStateFromDBOrGenesisFile(dbm.NewMemDB(), config.GenesisFile()) return state, blockStore } @@ -31,8 +27,8 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe // Make the blockchainReactor itself fastSync := true - blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), - types.NopEventBus{}, nil, types.MockMempool{}, types.MockEvidencePool{}) + var nilApp proxy.AppConnConsensus + blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp, types.MockMempool{}, types.MockEvidencePool{}) bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor.SetLogger(logger.With("module", "blockchain")) diff --git a/consensus/common_test.go b/consensus/common_test.go index 1516365e..eb574a21 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -265,8 +265,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S // Make ConsensusReactor stateDB := dbm.NewMemDB() // XXX !! - blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), - types.NopEventBus{}, proxyAppConnCon, mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(log.TestingLogger()) cs.SetPrivValidator(pv) @@ -356,8 +355,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou logger := consensusLogger() for i := 0; i < nValidators; i++ { stateDB := dbm.NewMemDB() // each state needs its own db - state, _ := sm.MakeGenesisState(genDoc) - sm.SaveState(stateDB, state) + state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) for _, opt := range configOpts { opt(thisConfig) @@ -381,8 +379,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF logger := consensusLogger() for i := 0; i < nPeers; i++ { stateDB := dbm.NewMemDB() // each state needs its own db - state, _ := sm.MakeGenesisState(genDoc) - sm.SaveState(stateDB, state) + state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal var privVal types.PrivValidator diff --git a/consensus/replay.go b/consensus/replay.go index 55c29178..784e8bd6 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -363,14 +363,10 @@ func (h *Handshaker) replayBlocks(state sm.State, proxyApp proxy.AppConns, appBl // ApplyBlock on the proxyApp with the last block. func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.AppConnConsensus) (sm.State, error) { - mempool := types.MockMempool{} - evpool := types.MockEvidencePool{} - block := h.store.LoadBlock(height) meta := h.store.LoadBlockMeta(height) - blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, - types.NopEventBus{}, proxyApp, mempool, evpool) + blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, proxyApp, types.MockMempool{}, types.MockEvidencePool{}) var err error state, err = blockExec.ApplyBlock(state, meta.BlockID, block) diff --git a/consensus/replay_file.go b/consensus/replay_file.go index 4e490ccb..26b8baeb 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -305,9 +305,7 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo } mempool, evpool := types.MockMempool{}, types.MockEvidencePool{} - blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), - types.NopEventBus{}, proxyApp.Consensus(), - mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) consensusState := NewConsensusState(csConfig, state.Copy(), blockExec, blockStore, mempool, evpool) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 4647ff3d..c497ed54 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -53,7 +53,7 @@ func init() { func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64, blockDB dbm.DB, stateDB dbm.DB) { logger := log.TestingLogger() - state, _ := sm.GetState(stateDB, consensusReplayConfig.GenesisFile()) + state, _ := sm.LoadStateFromDBOrGenesisFile(stateDB, consensusReplayConfig.GenesisFile()) privValidator := loadPrivValidator(consensusReplayConfig) cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, dummy.NewDummyApplication(), blockDB) cs.SetLogger(logger) @@ -394,8 +394,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { func applyBlock(stateDB dbm.DB, st sm.State, blk *types.Block, proxyApp proxy.AppConns) sm.State { testPartSize := st.ConsensusParams.BlockPartSizeBytes - blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), - types.NopEventBus{}, proxyApp.Consensus(), mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) blkID := types.BlockID{blk.Hash(), blk.MakePartSet(testPartSize).Header()} newState, err := blockExec.ApplyBlock(st, blkID, blk) diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index c4171b12..45609e56 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -67,7 +67,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { defer eventBus.Stop() mempool := types.MockMempool{} evpool := types.MockEvidencePool{} - blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), types.NopEventBus{}, proxyApp.Consensus(), mempool, evpool) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool) consensusState.SetLogger(logger) consensusState.SetEventBus(eventBus) diff --git a/evidence/pool.go b/evidence/pool.go index cdc01398..4d706da8 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -27,8 +27,7 @@ func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, params: params, logger: log.NewNopLogger(), evidenceStore: evidenceStore, - // state: *state, - evidenceChan: make(chan types.Evidence), + evidenceChan: make(chan types.Evidence), } return evpool } diff --git a/node/node.go b/node/node.go index cd1e6320..2eea4ed2 100644 --- a/node/node.go +++ b/node/node.go @@ -138,6 +138,7 @@ func NewNode(config *cfg.Config, } // Get genesis doc + // TODO: move to state package? genDoc, err := loadGenesisDoc(stateDB) if err != nil { genDoc, err = genesisDocProvider() @@ -149,13 +150,9 @@ func NewNode(config *cfg.Config, saveGenesisDoc(stateDB, genDoc) } - state := sm.LoadState(stateDB) - if state.IsEmpty() { - state, err = sm.MakeGenesisState(genDoc) - if err != nil { - return nil, err - } - sm.SaveState(stateDB, state) + state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) + if err != nil { + return nil, err } // Create the proxyApp, which manages connections (consensus, mempool, query) @@ -218,9 +215,7 @@ func NewNode(config *cfg.Config, blockExecLogger := logger.With("module", "state") // make block executor for consensus and blockchain reactors to execute blocks - blockExec := sm.NewBlockExecutor(stateDB, blockExecLogger, - types.NopEventBus{}, proxyApp.Consensus(), - mempool, evidencePool) + blockExec := sm.NewBlockExecutor(stateDB, blockExecLogger, proxyApp.Consensus(), mempool, evidencePool) // Make BlockchainReactor bcReactor := bc.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) diff --git a/state/execution.go b/state/execution.go index ce45e4c4..68cb13bd 100644 --- a/state/execution.go +++ b/state/execution.go @@ -20,34 +20,41 @@ import ( // BlockExecutor provides the context and accessories for properly executing a block. type BlockExecutor struct { - db dbm.DB - logger log.Logger + // save state, validators, consensus params, abci responses here + db dbm.DB + // execute the app against this + proxyApp proxy.AppConnConsensus + + // tx events txEventPublisher types.TxEventPublisher - proxyApp proxy.AppConnConsensus + // update these with block results after commit mempool types.Mempool evpool types.EvidencePool -} -func (blockExec *BlockExecutor) SetTxEventPublisher(txEventPublisher types.TxEventPublisher) { - blockExec.txEventPublisher = txEventPublisher + logger log.Logger } // NewBlockExecutor returns a new BlockExecutor. -func NewBlockExecutor(db dbm.DB, logger log.Logger, - txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus, +func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus, mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor { return &BlockExecutor{ - db, - logger, - txEventer, - proxyApp, - mempool, - evpool, + db: db, + proxyApp: proxyApp, + txEventPublisher: types.NopEventBus{}, + mempool: mempool, + evpool: evpool, + logger: logger, } } +// SetTxEventPublisher - set the transaction event publisher. If not called, +// it defaults to types.NopEventBus. +func (blockExec *BlockExecutor) SetTxEventPublisher(txEventPublisher types.TxEventPublisher) { + blockExec.txEventPublisher = txEventPublisher +} + // 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. diff --git a/state/execution_test.go b/state/execution_test.go index 1a63d3ed..9db26911 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -32,8 +32,7 @@ func TestApplyBlock(t *testing.T) { state, stateDB := state(), dbm.NewMemDB() - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), - types.NopEventBus{}, proxyApp.Consensus(), + blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), types.MockMempool{}, types.MockEvidencePool{}) block := makeBlock(state, 1) @@ -56,14 +55,6 @@ func TestBeginBlockAbsentValidators(t *testing.T) { state := state() - // there were 2 validators - /*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{} prevBlockID := types.BlockID{prevHash, prevParts} diff --git a/state/state.go b/state/state.go index 7ffa56ed..575a1630 100644 --- a/state/state.go +++ b/state/state.go @@ -23,7 +23,7 @@ var ( // including the last validator set and the consensus params. // All fields are exposed so the struct can be easily serialized, // but none of them should be mutated directly. -// Instead, use state.Copy() ro state.NextState(...). +// Instead, use state.Copy() or state.NextState(...). // NOTE: not goroutine-safe. type State struct { // Immutable diff --git a/state/state_test.go b/state/state_test.go index 7e9ed2cf..61b3167b 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -23,8 +23,8 @@ import ( 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, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + assert.NoError(t, err, "expected no error on LoadStateFromDBOrGenesisFile") tearDown := func(t *testing.T) {} diff --git a/state/db.go b/state/store.go similarity index 90% rename from state/db.go rename to state/store.go index fbe99863..de2d4d67 100644 --- a/state/db.go +++ b/state/store.go @@ -25,14 +25,31 @@ 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 +// LoadStateFromDBOrGenesisFile loads the most recent state from the database, +// or creates a new one from the given genesisFilePath and persists the result // to the database. -func GetState(stateDB dbm.DB, genesisFile string) (State, error) { +func LoadStateFromDBOrGenesisFile(stateDB dbm.DB, genesisFilePath string) (State, error) { state := LoadState(stateDB) if state.IsEmpty() { var err error - state, err = MakeGenesisStateFromFile(genesisFile) + state, err = MakeGenesisStateFromFile(genesisFilePath) + if err != nil { + return state, err + } + SaveState(stateDB, state) + } + + return state, nil +} + +// LoadStateFromDBOrGenesisDoc loads the most recent state from the database, +// or creates a new one from the given genesisDoc and persists the result +// to the database. +func LoadStateFromDBOrGenesisDoc(stateDB dbm.DB, genesisDoc *types.GenesisDoc) (State, error) { + state := LoadState(stateDB) + if state.IsEmpty() { + var err error + state, err = MakeGenesisState(genesisDoc) if err != nil { return state, err } @@ -67,6 +84,10 @@ func loadState(db dbm.DB, key []byte) (state State) { // SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. func SaveState(db dbm.DB, s State) { + saveState(db, s, stateKey) +} + +func saveState(db dbm.DB, s State, key []byte) { nextHeight := s.LastBlockHeight + 1 saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators) saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams) From 8d8d63c94cc26a8b7456f9e85d869da4ab8fed6e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 18:28:27 -0500 Subject: [PATCH 10/14] changelog --- CHANGELOG.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f0809d..71fe223e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,7 @@ ## Roadmap BREAKING CHANGES: -- Upgrade the header to support better proofs on validtors, results, evidence, and possibly more - Better support for injecting randomness -- Pass evidence/voteInfo through ABCI - Upgrade consensus for more real-time use of evidence FEATURES: @@ -32,6 +30,27 @@ BUG FIXES: BREAKING CHANGES: - [p2p] enable the Peer Exchange reactor by default - [types] add Timestamp field to Proposal/Vote +- [types] add new fields to Header: TotalTxs, ConsensusParamsHash, LastResultsHash, EvidenceHash +- [types] add Evidence to Block +- [types] simplify ValidateBasic +- [state] updates to support changes to the header +- [state] Enforce <1/3 of validator set can change at a time + +FEATURES: +- [state] Send indices of absent validators and addresses of byzantine validators in BeginBlock +- [state] Historical ConsensusParams and ABCIResponses +- [docs] Specification for the base Tendermint data structures. +- [evidence] New evidence reactor for gossiping and managing evidence +- [rpc] `/block_results?height=X` returns the DeliverTx results for a given height. + +IMPROVEMENTS: +- [consensus] Better handling of corrupt WAL file + +BUG FIXES: +- [lite] fix race +- [state] validate block.Header.ValidatorsHash +- [p2p] allow seed addresses to be prefixed with eg. `tcp://` +- [cmd] fix `tendermint init` to ignore files that are there and generate files that aren't. ## 0.14.0 (December 11, 2017) From ae68fcb78a5400f0ab83a756ca8da28ea3c3cf6b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 18:58:05 -0500 Subject: [PATCH 11/14] move fireEvents to ApplyBlock --- blockchain/reactor.go | 1 - consensus/common_test.go | 2 +- consensus/state.go | 18 -------------- node/node.go | 1 + state/execution.go | 54 ++++++++++++++++++++++++++-------------- types/events.go | 7 ++++++ 6 files changed, 45 insertions(+), 38 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index c8e794a1..d4b803dd 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -284,7 +284,6 @@ FOR_LOOP: bcR.store.SaveBlock(first, firstParts, second.LastCommit) - // TODO: should we be firing events? need to fire NewBlock events manually ... // NOTE: we could improve performance if we // didn't make the app commit to disk every block // ... but we would need a way to get the hash without it persisting diff --git a/consensus/common_test.go b/consensus/common_test.go index eb574a21..249e7732 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -264,7 +264,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S evpool := types.MockEvidencePool{} // Make ConsensusReactor - stateDB := dbm.NewMemDB() // XXX !! + stateDB := dbm.NewMemDB() blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(log.TestingLogger()) diff --git a/consensus/state.go b/consensus/state.go index 477d872b..69858da0 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1203,8 +1203,6 @@ func (cs *ConsensusState) finalizeCommit(height int64) { // Create a copy of the state for staging // and an event cache for txs stateCopy := cs.state.Copy() - txEventBuffer := types.NewTxEventBuffer(cs.eventBus, int(block.NumTxs)) - cs.blockExec.SetTxEventPublisher(txEventBuffer) // Execute and commit the block, update and save the state, and update the mempool. // NOTE: the block.AppHash wont reflect these txs until the next block @@ -1221,22 +1219,6 @@ func (cs *ConsensusState) finalizeCommit(height int64) { fail.Fail() // XXX - // Fire event for new block. - // NOTE: If we fail before firing, these events will never fire - // - // TODO: Either - // * Fire before persisting state, in ApplyBlock - // * Fire on start up if we haven't written any new WAL msgs - // Both options mean we may fire more than once. Is that fine ? - cs.eventBus.PublishEventNewBlock(types.EventDataNewBlock{block}) - cs.eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{block.Header}) - err = txEventBuffer.Flush() - if err != nil { - cs.Logger.Error("Failed to flush event buffer", "err", err) - } - - fail.Fail() // XXX - // NewHeightStep! cs.updateToState(stateCopy) diff --git a/node/node.go b/node/node.go index 2eea4ed2..fe51b941 100644 --- a/node/node.go +++ b/node/node.go @@ -291,6 +291,7 @@ func NewNode(config *cfg.Config, eventBus.SetLogger(logger.With("module", "events")) // services which will be publishing and/or subscribing for messages (events) + blockExec.SetEventBus(eventBus) consensusReactor.SetEventBus(eventBus) // Transaction indexing diff --git a/state/execution.go b/state/execution.go index 68cb13bd..8b05733a 100644 --- a/state/execution.go +++ b/state/execution.go @@ -26,8 +26,8 @@ type BlockExecutor struct { // execute the app against this proxyApp proxy.AppConnConsensus - // tx events - txEventPublisher types.TxEventPublisher + // events + eventBus types.BlockEventPublisher // update these with block results after commit mempool types.Mempool @@ -36,27 +36,29 @@ type BlockExecutor struct { logger log.Logger } -// NewBlockExecutor returns a new BlockExecutor. +// NewBlockExecutor returns a new BlockExecutor with a NopEventBus. +// Call SetEventBus to provide one. func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus, mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor { return &BlockExecutor{ - db: db, - proxyApp: proxyApp, - txEventPublisher: types.NopEventBus{}, - mempool: mempool, - evpool: evpool, - logger: logger, + db: db, + proxyApp: proxyApp, + eventBus: types.NopEventBus{}, + mempool: mempool, + evpool: evpool, + logger: logger, } } -// SetTxEventPublisher - set the transaction event publisher. If not called, -// it defaults to types.NopEventBus. -func (blockExec *BlockExecutor) SetTxEventPublisher(txEventPublisher types.TxEventPublisher) { - blockExec.txEventPublisher = txEventPublisher +// SetEventBus - sets the event bus for publishing block related events. +// If not called, it defaults to types.NopEventBus. +func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) { + blockExec.eventBus = eventBus } // 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 +// fires the relevent events, commits the app, and saves the new state and responses. +// 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) { @@ -70,8 +72,6 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block return s, ErrProxyAppConn(err) } - fireEvents(blockExec.txEventPublisher, block, abciResponses) - fail.Fail() // XXX // save the results before we commit @@ -97,6 +97,12 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block s.AppHash = appHash SaveState(blockExec.db, s) + fail.Fail() // XXX + + // events are fired after everything else + // NOTE: if we crash between Commit and Save, events wont be fired during replay + fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses) + return s, nil } @@ -354,15 +360,27 @@ func updateState(s State, blockID types.BlockID, header *types.Header, }, nil } -func fireEvents(txEventPublisher types.TxEventPublisher, block *types.Block, abciResponses *ABCIResponses) { +// Fire NewBlock, NewBlockHeader. +// Fire TxEvent for every tx. +// NOTE: if Tendermint crashes before commit, some or all of these events may be published again. +func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, abciResponses *ABCIResponses) { + // NOTE: do we still need this buffer ? + txEventBuffer := types.NewTxEventBuffer(eventBus, int(block.NumTxs)) for i, tx := range block.Data.Txs { - txEventPublisher.PublishEventTx(types.EventDataTx{types.TxResult{ + txEventBuffer.PublishEventTx(types.EventDataTx{types.TxResult{ Height: block.Height, Index: uint32(i), Tx: tx, Result: *(abciResponses.DeliverTx[i]), }}) } + + eventBus.PublishEventNewBlock(types.EventDataNewBlock{block}) + eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{block.Header}) + err := txEventBuffer.Flush() + if err != nil { + logger.Error("Failed to flush event buffer", "err", err) + } } //---------------------------------------------------------------------------------------------------- diff --git a/types/events.go b/types/events.go index 5c41c6df..d6f7b012 100644 --- a/types/events.go +++ b/types/events.go @@ -175,6 +175,13 @@ func QueryForEvent(eventType string) tmpubsub.Query { return tmquery.MustParse(fmt.Sprintf("%s='%s'", EventTypeKey, eventType)) } +// BlockEventPublisher publishes all block related events +type BlockEventPublisher interface { + PublishEventNewBlock(block EventDataNewBlock) error + PublishEventNewBlockHeader(header EventDataNewBlockHeader) error + PublishEventTx(EventDataTx) error +} + type TxEventPublisher interface { PublishEventTx(EventDataTx) error } From 6112578d07c97033380ac4c5902c5bcb108d8340 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 19:35:56 -0500 Subject: [PATCH 12/14] ValidateBlock is a method on blockExec --- consensus/state.go | 7 ++++--- node/node.go | 2 +- state/execution.go | 10 +++++++++- state/validation.go | 7 ++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 69858da0..518d81c5 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -153,6 +153,7 @@ func (cs *ConsensusState) SetLogger(l log.Logger) { // SetEventBus sets event bus. func (cs *ConsensusState) SetEventBus(b *types.EventBus) { cs.eventBus = b + cs.blockExec.SetEventBus(b) } // String returns a string. @@ -922,7 +923,7 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { } // Validate proposal block - err := sm.ValidateBlock(cs.state, cs.ProposalBlock) + err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock) if err != nil { // ProposalBlock is invalid, prevote nil. logger.Error("enterPrevote: ProposalBlock is invalid", "err", err) @@ -1030,7 +1031,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.Logger.Info("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash) // Validate the block. - if err := sm.ValidateBlock(cs.state, cs.ProposalBlock); err != nil { + if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil { cmn.PanicConsensus(cmn.Fmt("enterPrecommit: +2/3 prevoted for an invalid block: %v", err)) } cs.LockedRound = round @@ -1165,7 +1166,7 @@ func (cs *ConsensusState) finalizeCommit(height int64) { if !block.HashesTo(blockID.Hash) { cmn.PanicSanity(cmn.Fmt("Cannot finalizeCommit, ProposalBlock does not hash to commit hash")) } - if err := sm.ValidateBlock(cs.state, block); err != nil { + if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil { cmn.PanicConsensus(cmn.Fmt("+2/3 committed an invalid block: %v", err)) } diff --git a/node/node.go b/node/node.go index fe51b941..04b1fb14 100644 --- a/node/node.go +++ b/node/node.go @@ -291,7 +291,7 @@ func NewNode(config *cfg.Config, eventBus.SetLogger(logger.With("module", "events")) // services which will be publishing and/or subscribing for messages (events) - blockExec.SetEventBus(eventBus) + // consensusReactor will set it on consensusState and blockExecutor consensusReactor.SetEventBus(eventBus) // Transaction indexing diff --git a/state/execution.go b/state/execution.go index 8b05733a..b3acd711 100644 --- a/state/execution.go +++ b/state/execution.go @@ -56,6 +56,14 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) blockExec.eventBus = eventBus } +// ValidateBlock validates the given block against the given state. +// If the block is invalid, it returns an error. +// Validation does not mutate state, but does require historical information from the stateDB, +// ie. to verify evidence from a validator at an old height. +func (blockExec *BlockExecutor) ValidateBlock(s State, block *types.Block) error { + return validateBlock(blockExec.db, s, block) +} + // ApplyBlock validates the block against the state, executes it against the app, // fires the relevent events, commits the app, and saves the new state and responses. // It's the only function that needs to be called @@ -63,7 +71,7 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) // 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 { + if err := blockExec.ValidateBlock(s, block); err != nil { return s, ErrInvalidBlock(err) } diff --git a/state/validation.go b/state/validation.go index 69200840..5c9197bc 100644 --- a/state/validation.go +++ b/state/validation.go @@ -6,17 +6,18 @@ import ( "fmt" "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" ) //----------------------------------------------------- // 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, block *types.Block) error { + return validateBlock(dbm.NewMemDB(), s, block) } -func validateBlock(s State, b *types.Block) error { +func validateBlock(stateDB dbm.DB, s State, b *types.Block) error { // validate internal consistency if err := b.ValidateBasic(); err != nil { return err From cb845ebff5d3772bedf7536b1b14fda0d0a0310a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 21:08:39 -0500 Subject: [PATCH 13/14] fix EvidencePool and VerifyEvidence --- evidence/pool.go | 63 ++++++++++++++++++++++++++-------------- evidence/pool_test.go | 39 +++++++++++++++++++++++-- evidence/reactor_test.go | 29 ++++++++++++------ node/node.go | 2 +- state/execution.go | 8 +++-- state/validation.go | 34 ++++++++-------------- state/validation_test.go | 24 ++++++++------- types/services.go | 8 ++--- 8 files changed, 134 insertions(+), 73 deletions(-) diff --git a/evidence/pool.go b/evidence/pool.go index 4d706da8..07c35134 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -1,6 +1,10 @@ package evidence import ( + "fmt" + "sync" + + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" sm "github.com/tendermint/tendermint/state" @@ -14,17 +18,21 @@ type EvidencePool struct { evidenceStore *EvidenceStore - state sm.State - params types.EvidenceParams + // needed to load validators to verify evidence + stateDB dbm.DB + + // latest state + mtx sync.Mutex + state sm.State // never close evidenceChan chan types.Evidence } -func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state sm.State) *EvidencePool { +func NewEvidencePool(stateDB dbm.DB, evidenceStore *EvidenceStore) *EvidencePool { evpool := &EvidencePool{ - state: state, - params: params, + stateDB: stateDB, + state: sm.LoadState(stateDB), logger: log.NewNopLogger(), evidenceStore: evidenceStore, evidenceChan: make(chan types.Evidence), @@ -52,31 +60,44 @@ func (evpool *EvidencePool) PendingEvidence() []types.Evidence { return evpool.evidenceStore.PendingEvidence() } +// State returns the current state of the evpool. +func (evpool *EvidencePool) State() sm.State { + evpool.mtx.Lock() + defer evpool.mtx.Unlock() + return evpool.state +} + +// Update loads the latest +func (evpool *EvidencePool) Update(block *types.Block) { + evpool.mtx.Lock() + defer evpool.mtx.Unlock() + + state := sm.LoadState(evpool.stateDB) + if state.LastBlockHeight != block.Height { + panic(fmt.Sprintf("EvidencePool.Update: loaded state with height %d when block.Height=%d", state.LastBlockHeight, block.Height)) + } + evpool.state = state + + // NOTE: shouldn't need the mutex + evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence) +} + // AddEvidence checks the evidence is valid and adds it to the pool. // Blocks on the EvidenceChan. func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) { + // TODO: check if we already have evidence for this // validator at this height so we dont get spammed - if err := sm.VerifyEvidence(evpool.state, evidence); err != nil { + if err := sm.VerifyEvidence(evpool.stateDB, evpool.State(), evidence); err != nil { return err } - var priority int64 - /* // Needs a db ... - // TODO: if err is just that we cant find it cuz we pruned, ignore. - // TODO: if its actually bad evidence, punish peer - - 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) - } - */ + // fetch the validator and return its voting power as its priority + // TODO: something better ? + valset, _ := sm.LoadValidators(evpool.stateDB, evidence.Height()) + _, val := valset.GetByAddress(evidence.Address()) + priority := val.VotingPower added := evpool.evidenceStore.AddNewEvidence(evidence, priority) if !added { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index d7b94e88..f5b5205b 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -3,6 +3,7 @@ package evidence import ( "sync" "testing" + "time" "github.com/stretchr/testify/assert" @@ -13,14 +14,46 @@ import ( var mockState = sm.State{} +func initializeValidatorState(valAddr []byte, height int64) dbm.DB { + stateDB := dbm.NewMemDB() + + // create validator set and state + valSet := &types.ValidatorSet{ + Validators: []*types.Validator{ + {Address: valAddr}, + }, + } + state := sm.State{ + LastBlockHeight: 0, + LastBlockTime: time.Now(), + Validators: valSet, + LastHeightValidatorsChanged: 1, + ConsensusParams: types.ConsensusParams{ + EvidenceParams: types.EvidenceParams{ + MaxAge: 1000000, + }, + }, + } + + // save all states up to height + for i := int64(0); i < height; i++ { + state.LastBlockHeight = i + sm.SaveState(stateDB, state) + } + + return stateDB +} + func TestEvidencePool(t *testing.T) { assert := assert.New(t) - params := types.EvidenceParams{} + valAddr := []byte("val1") + height := int64(5) + stateDB := initializeValidatorState(valAddr, height) store := NewEvidenceStore(dbm.NewMemDB()) - pool := NewEvidencePool(params, store, mockState) + pool := NewEvidencePool(stateDB, store) - goodEvidence := newMockGoodEvidence(5, 1, []byte("val1")) + goodEvidence := newMockGoodEvidence(height, 0, valAddr) badEvidence := MockBadEvidence{goodEvidence} err := pool.AddEvidence(badEvidence) diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index fc4ea571..77c58734 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -32,14 +32,14 @@ func evidenceLogger() log.Logger { } // connect N evidence reactors through N switches -func makeAndConnectEvidenceReactors(config *cfg.Config, N int) []*EvidenceReactor { +func makeAndConnectEvidenceReactors(config *cfg.Config, stateDBs []dbm.DB) []*EvidenceReactor { + N := len(stateDBs) reactors := make([]*EvidenceReactor, N) logger := evidenceLogger() for i := 0; i < N; i++ { - params := types.EvidenceParams{} store := NewEvidenceStore(dbm.NewMemDB()) - pool := NewEvidencePool(params, store, mockState) + pool := NewEvidencePool(stateDBs[i], store) reactors[i] = NewEvidenceReactor(pool) reactors[i].SetLogger(logger.With("validator", i)) } @@ -98,10 +98,10 @@ func _waitForEvidence(t *testing.T, wg *sync.WaitGroup, evs types.EvidenceList, wg.Done() } -func sendEvidence(t *testing.T, evpool *EvidencePool, n int) types.EvidenceList { +func sendEvidence(t *testing.T, evpool *EvidencePool, valAddr []byte, n int) types.EvidenceList { evList := make([]types.Evidence, n) for i := 0; i < n; i++ { - ev := newMockGoodEvidence(int64(i), 2, []byte("val")) + ev := newMockGoodEvidence(int64(i+1), 0, valAddr) err := evpool.AddEvidence(ev) assert.Nil(t, err) evList[i] = ev @@ -110,17 +110,28 @@ func sendEvidence(t *testing.T, evpool *EvidencePool, n int) types.EvidenceList } var ( - NUM_EVIDENCE = 1000 + NUM_EVIDENCE = 1 TIMEOUT = 120 * time.Second // ridiculously high because CircleCI is slow ) func TestReactorBroadcastEvidence(t *testing.T) { config := cfg.TestConfig() N := 7 - reactors := makeAndConnectEvidenceReactors(config, N) - // send a bunch of evidence to the first reactor's evpool + // create statedb for everyone + stateDBs := make([]dbm.DB, N) + valAddr := []byte("myval") + // we need validators saved for heights at least as high as we have evidence for + height := int64(NUM_EVIDENCE) + 10 + for i := 0; i < N; i++ { + stateDBs[i] = initializeValidatorState(valAddr, height) + } + + // make reactors from statedb + reactors := makeAndConnectEvidenceReactors(config, stateDBs) + + // send a bunch of valid evidence to the first reactor's evpool // and wait for them all to be received in the others - evList := sendEvidence(t, reactors[0].evpool, NUM_EVIDENCE) + evList := sendEvidence(t, reactors[0].evpool, valAddr, NUM_EVIDENCE) waitForEvidence(t, evList, reactors) } diff --git a/node/node.go b/node/node.go index 04b1fb14..f922d832 100644 --- a/node/node.go +++ b/node/node.go @@ -208,7 +208,7 @@ func NewNode(config *cfg.Config, } evidenceLogger := logger.With("module", "evidence") evidenceStore := evidence.NewEvidenceStore(evidenceDB) - evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore, state.Copy()) + evidencePool := evidence.NewEvidencePool(stateDB, evidenceStore) evidencePool.SetLogger(evidenceLogger) evidenceReactor := evidence.NewEvidenceReactor(evidencePool) evidenceReactor.SetLogger(evidenceLogger) diff --git a/state/execution.go b/state/execution.go index b3acd711..847ac131 100644 --- a/state/execution.go +++ b/state/execution.go @@ -107,6 +107,11 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block fail.Fail() // XXX + // Update evpool now that state is saved + // TODO: handle the crash/recover scenario + // ie. (may need to call Update for last block) + blockExec.evpool.Update(block) + // events are fired after everything else // NOTE: if we crash between Commit and Save, events wont be fired during replay fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses) @@ -138,9 +143,6 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "appHash", 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 diff --git a/state/validation.go b/state/validation.go index 5c9197bc..dfca78ac 100644 --- a/state/validation.go +++ b/state/validation.go @@ -79,20 +79,9 @@ func validateBlock(stateDB dbm.DB, s State, b *types.Block) error { } for _, ev := range b.Evidence.Evidence { - if err := VerifyEvidence(s, ev); err != nil { + if err := VerifyEvidence(stateDB, 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 @@ -103,7 +92,7 @@ func validateBlock(stateDB dbm.DB, s State, b *types.Block) error { // VerifyEvidence verifies the evidence fully by checking it is internally // consistent and sufficiently recent. -func VerifyEvidence(s State, evidence types.Evidence) error { +func VerifyEvidence(stateDB dbm.DB, s State, evidence types.Evidence) error { height := s.LastBlockHeight evidenceAge := height - evidence.Height() @@ -116,22 +105,23 @@ func VerifyEvidence(s State, evidence types.Evidence) error { 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) { + valset, err := LoadValidators(stateDB, evidence.Height()) + if err != nil { + // TODO: if err is just that we cant find it cuz we pruned, ignore. + // TODO: if its actually bad evidence, punish peer + return err + } + // 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) + return 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) + return fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx) } - priority = val.VotingPower - return priority, nil + return nil } diff --git a/state/validation_test.go b/state/validation_test.go index a8e4d42e..e0b7fe9e 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -4,61 +4,65 @@ import ( "testing" "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" ) -func _TestValidateBlock(t *testing.T) { +func TestValidateBlock(t *testing.T) { state := state() + blockExec := NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nil, nil, nil) + // proper block must pass block := makeBlock(state, 1) - err := ValidateBlock(state, block) + err := blockExec.ValidateBlock(state, block) require.NoError(t, err) // wrong chain fails block = makeBlock(state, 1) block.ChainID = "not-the-real-one" - err = ValidateBlock(state, block) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) // wrong height fails block = makeBlock(state, 1) block.Height += 10 - err = ValidateBlock(state, block) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) // wrong total tx fails block = makeBlock(state, 1) block.TotalTxs += 10 - err = ValidateBlock(state, block) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) // wrong blockid fails block = makeBlock(state, 1) block.LastBlockID.PartsHeader.Total += 10 - err = ValidateBlock(state, block) + err = blockExec.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) + err = blockExec.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) + err = blockExec.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) + err = blockExec.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) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) } diff --git a/types/services.go b/types/services.go index a901898f..6900fae7 100644 --- a/types/services.go +++ b/types/services.go @@ -78,7 +78,7 @@ type BlockStore interface { type EvidencePool interface { PendingEvidence() []Evidence AddEvidence(Evidence) error - MarkEvidenceAsCommitted([]Evidence) + Update(*Block) } // MockMempool is an empty implementation of a Mempool, useful for testing. @@ -86,6 +86,6 @@ type EvidencePool interface { type MockEvidencePool struct { } -func (m MockEvidencePool) PendingEvidence() []Evidence { return nil } -func (m MockEvidencePool) AddEvidence(Evidence) error { return nil } -func (m MockEvidencePool) MarkEvidenceAsCommitted([]Evidence) {} +func (m MockEvidencePool) PendingEvidence() []Evidence { return nil } +func (m MockEvidencePool) AddEvidence(Evidence) error { return nil } +func (m MockEvidencePool) Update(*Block) {} From 444db4c24294123784b84984b34634bd42caa9a6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 28 Dec 2017 22:10:23 -0500 Subject: [PATCH 14/14] metalinter --- state/execution.go | 2 +- state/validation.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/state/execution.go b/state/execution.go index 847ac131..4ccf87d4 100644 --- a/state/execution.go +++ b/state/execution.go @@ -65,7 +65,7 @@ func (blockExec *BlockExecutor) ValidateBlock(s State, block *types.Block) error } // ApplyBlock validates the block against the state, executes it against the app, -// fires the relevent events, commits the app, and saves the new state and responses. +// fires the relevant events, commits the app, and saves the new state and responses. // 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. diff --git a/state/validation.go b/state/validation.go index dfca78ac..fb3e8d13 100644 --- a/state/validation.go +++ b/state/validation.go @@ -12,11 +12,6 @@ import ( //----------------------------------------------------- // Validate block -// ValidateBlock validates the block against the state. -func _ValidateBlock(s State, block *types.Block) error { - return validateBlock(dbm.NewMemDB(), s, block) -} - func validateBlock(stateDB dbm.DB, s State, b *types.Block) error { // validate internal consistency if err := b.ValidateBasic(); err != nil {