|
|
- package state
-
- import (
- "bytes"
- "errors"
-
- "github.com/ebuchman/fail-test"
-
- . "github.com/tendermint/go-common"
- cfg "github.com/tendermint/go-config"
- "github.com/tendermint/tendermint/proxy"
- "github.com/tendermint/tendermint/types"
- tmsp "github.com/tendermint/tmsp/types"
- )
-
- //--------------------------------------------------
- // Execute the block
-
- // Execute the block to mutate State.
- // Validates block and then executes Data.Txs in the block.
- func (s *State) ExecBlock(eventCache types.Fireable, proxyAppConn proxy.AppConnConsensus, block *types.Block, blockPartsHeader types.PartSetHeader) error {
-
- // Validate the block.
- err := s.validateBlock(block)
- if err != nil {
- return ErrInvalidBlock(err)
- }
-
- // Update the validator set
- valSet := s.Validators.Copy()
- // Update valSet with signatures from block.
- updateValidatorsWithBlock(s.LastValidators, valSet, block)
- // TODO: Update the validator set (e.g. block.Data.ValidatorUpdates?)
- nextValSet := valSet.Copy()
-
- // Execute the block txs
- err = s.execBlockOnProxyApp(eventCache, proxyAppConn, block)
- if err != nil {
- // There was some error in proxyApp
- // TODO Report error and wait for proxyApp to be available.
- return ErrProxyAppConn(err)
- }
-
- // All good!
- // Update validator accums and set state variables
- nextValSet.IncrementAccum(1)
- s.SetBlockAndValidators(block.Header, blockPartsHeader, valSet, nextValSet)
-
- // save state with updated height/blockhash/validators
- // but stale apphash, in case we fail between Commit and Save
- s.Save()
-
- return nil
- }
-
- // Executes block's transactions on proxyAppConn.
- // TODO: Generate a bitmap or otherwise store tx validity in state.
- func (s *State) execBlockOnProxyApp(eventCache types.Fireable, proxyAppConn proxy.AppConnConsensus, block *types.Block) error {
-
- var validTxs, invalidTxs = 0, 0
-
- // Execute transactions and get hash
- proxyCb := func(req *tmsp.Request, res *tmsp.Response) {
- switch r := res.Value.(type) {
- case *tmsp.Response_AppendTx:
- // TODO: make use of res.Log
- // TODO: make use of this info
- // Blocks may include invalid txs.
- // reqAppendTx := req.(tmsp.RequestAppendTx)
- txError := ""
- apTx := r.AppendTx
- if apTx.Code == tmsp.CodeType_OK {
- validTxs += 1
- } else {
- log.Debug("Invalid tx", "code", r.AppendTx.Code, "log", r.AppendTx.Log)
- invalidTxs += 1
- txError = apTx.Code.String()
- }
- // NOTE: if we count we can access the tx from the block instead of
- // pulling it from the req
- event := types.EventDataTx{
- Tx: req.GetAppendTx().Tx,
- Result: apTx.Data,
- Code: apTx.Code,
- Log: apTx.Log,
- Error: txError,
- }
- types.FireEventTx(eventCache, event)
- }
- }
- proxyAppConn.SetResponseCallback(proxyCb)
-
- // Begin block
- err := proxyAppConn.BeginBlockSync(block.Hash(), types.TM2PB.Header(block.Header))
- if err != nil {
- log.Warn("Error in proxyAppConn.BeginBlock", "error", err)
- return err
- }
-
- fail.Fail() // XXX
-
- // Run txs of block
- for _, tx := range block.Txs {
- fail.FailRand(len(block.Txs)) // XXX
- proxyAppConn.AppendTxAsync(tx)
- if err := proxyAppConn.Error(); err != nil {
- return err
- }
- }
-
- fail.Fail() // XXX
-
- // End block
- changedValidators, err := proxyAppConn.EndBlockSync(uint64(block.Height))
- if err != nil {
- log.Warn("Error in proxyAppConn.EndBlock", "error", err)
- return err
- }
-
- fail.Fail() // XXX
-
- // TODO: Do something with changedValidators
- log.Debug("TODO: Do something with changedValidators", "changedValidators", changedValidators)
-
- log.Info(Fmt("ExecBlock got %v valid txs and %v invalid txs", validTxs, invalidTxs))
- return nil
- }
-
- // Updates the LastCommitHeight of the validators in valSet, in place.
- // Assumes that lastValSet matches the valset of block.LastCommit
- // CONTRACT: lastValSet is not mutated.
- func updateValidatorsWithBlock(lastValSet *types.ValidatorSet, valSet *types.ValidatorSet, block *types.Block) {
-
- for i, precommit := range block.LastCommit.Precommits {
- if precommit == nil {
- continue
- }
- _, val := lastValSet.GetByIndex(i)
- if val == nil {
- PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
- }
- if _, val_ := valSet.GetByAddress(val.Address); val_ != nil {
- val_.LastCommitHeight = block.Height - 1
- updated := valSet.Update(val_)
- if !updated {
- PanicCrisis("Failed to update validator LastCommitHeight")
- }
- } else {
- // XXX This is not an error if validator was removed.
- // But, we don't mutate validators yet so go ahead and panic.
- PanicCrisis("Could not find validator")
- }
- }
-
- }
-
- //-----------------------------------------------------
- // Validate block
-
- func (s *State) ValidateBlock(block *types.Block) error {
- return s.validateBlock(block)
- }
-
- func (s *State) validateBlock(block *types.Block) error {
- // Basic block validation.
- err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash)
- if err != nil {
- return err
- }
-
- // Validate block LastCommit.
- if block.Height == 1 {
- if len(block.LastCommit.Precommits) != 0 {
- return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
- }
- } else {
- if len(block.LastCommit.Precommits) != s.LastValidators.Size() {
- return errors.New(Fmt("Invalid block commit size. Expected %v, got %v",
- s.LastValidators.Size(), len(block.LastCommit.Precommits)))
- }
- err := s.LastValidators.VerifyCommit(
- s.ChainID, s.LastBlockID, block.Height-1, block.LastCommit)
- if err != nil {
- return err
- }
- }
-
- return nil
- }
-
- //-----------------------------------------------------------------------------
- // ApplyBlock executes the block, then commits and updates the mempool atomically
-
- // Execute and commit block against app, save block and state
- func (s *State) ApplyBlock(eventCache types.Fireable, proxyAppConn proxy.AppConnConsensus,
- block *types.Block, partsHeader types.PartSetHeader, mempool Mempool) error {
-
- // Run the block on the State:
- // + update validator sets
- // + run txs on the proxyAppConn
- err := s.ExecBlock(eventCache, proxyAppConn, block, partsHeader)
- if err != nil {
- return errors.New(Fmt("Exec failed for application: %v", err))
- }
-
- // lock mempool, commit state, update mempoool
- err = s.CommitStateUpdateMempool(proxyAppConn, block, mempool)
- if err != nil {
- return errors.New(Fmt("Commit failed for application: %v", err))
- }
- return nil
- }
-
- // 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 Mempool) error {
- mempool.Lock()
- defer mempool.Unlock()
-
- // Commit block, get hash back
- res := proxyAppConn.CommitSync()
- if res.IsErr() {
- log.Warn("Error in proxyAppConn.CommitSync", "error", res)
- return res
- }
- if res.Log != "" {
- log.Debug("Commit.Log: " + res.Log)
- }
-
- // Set the state's new AppHash
- s.AppHash = res.Data
-
- // Update mempool.
- mempool.Update(block.Height, block.Txs)
-
- return nil
- }
-
- // Updates to the mempool need to be synchronized with committing a block
- // so apps can reset their transient state on Commit
- type Mempool interface {
- Lock()
- Unlock()
- Update(height int, txs []types.Tx)
- }
-
- type mockMempool struct {
- }
-
- func (m mockMempool) Lock() {}
- func (m mockMempool) Unlock() {}
- func (m mockMempool) Update(height int, txs []types.Tx) {}
-
- //----------------------------------------------------------------
- // Handshake with app to sync to latest state of core by replaying blocks
-
- // TODO: Should we move blockchain/store.go to its own package?
- type BlockStore interface {
- Height() int
- LoadBlock(height int) *types.Block
- }
-
- type Handshaker struct {
- config cfg.Config
- state *State
- store BlockStore
-
- nBlocks int // number of blocks applied to the state
- }
-
- func NewHandshaker(config cfg.Config, state *State, store BlockStore) *Handshaker {
- return &Handshaker{config, state, store, 0}
- }
-
- // TODO: retry the handshake once if it fails the first time
- // ... let Info take an argument determining its behaviour
- func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
- // handshake is done via info request on the query conn
- res, tmspInfo, blockInfo, configInfo := proxyApp.Query().InfoSync()
- if res.IsErr() {
- return errors.New(Fmt("Error calling Info. Code: %v; Data: %X; Log: %s", res.Code, res.Data, res.Log))
- }
-
- if blockInfo == nil {
- log.Warn("blockInfo is nil, aborting handshake")
- return nil
- }
-
- log.Notice("TMSP Handshake", "height", blockInfo.BlockHeight, "block_hash", blockInfo.BlockHash, "app_hash", blockInfo.AppHash)
-
- blockHeight := int(blockInfo.BlockHeight) // safe, should be an int32
- blockHash := blockInfo.BlockHash
- appHash := blockInfo.AppHash
-
- if tmspInfo != nil {
- // TODO: check tmsp version (or do this in the tmspcli?)
- _ = tmspInfo
- }
-
- // last block (nil if we starting from 0)
- var header *types.Header
- var partsHeader types.PartSetHeader
-
- // replay all blocks after blockHeight
- // if blockHeight == 0, we will replay everything
- if blockHeight != 0 {
- block := h.store.LoadBlock(blockHeight)
- if block == nil {
- return ErrUnknownBlock{blockHeight}
- }
-
- // check block hash
- if !bytes.Equal(block.Hash(), blockHash) {
- return ErrBlockHashMismatch{block.Hash(), blockHash, blockHeight}
- }
-
- // NOTE: app hash should be in the next block ...
- // check app hash
- /*if !bytes.Equal(block.Header.AppHash, appHash) {
- return fmt.Errorf("Handshake error. App hash at height %d does not match. Got %X, expected %X", blockHeight, appHash, block.Header.AppHash)
- }*/
-
- header = block.Header
- partsHeader = block.MakePartSet(h.config.GetInt("block_part_size")).Header()
- }
-
- if configInfo != nil {
- // TODO: set config info
- _ = configInfo
- }
-
- // replay blocks up to the latest in the blockstore
- err := h.ReplayBlocks(appHash, header, partsHeader, proxyApp.Consensus())
- if err != nil {
- return errors.New(Fmt("Error on replay: %v", err))
- }
-
- // TODO: (on restart) replay mempool
-
- return nil
- }
-
- // Replay all blocks after blockHeight and ensure the result matches the current state.
- func (h *Handshaker) ReplayBlocks(appHash []byte, header *types.Header, partsHeader types.PartSetHeader,
- appConnConsensus proxy.AppConnConsensus) error {
-
- // NOTE/TODO: tendermint may crash after the app commits
- // but before it can save the new state root.
- // it should save all eg. valset changes before calling Commit.
- // then, if tm state is behind app state, the only thing missing can be app hash
-
- // get a fresh state and reset to the apps latest
- stateCopy := h.state.Copy()
-
- // TODO: put validators in iavl tree so we can set the state with an older validator set
- lastVals, nextVals := stateCopy.GetValidators()
- if header == nil {
- stateCopy.LastBlockHeight = 0
- stateCopy.LastBlockID = types.BlockID{}
- // stateCopy.LastBlockTime = ... doesnt matter
- stateCopy.Validators = nextVals
- stateCopy.LastValidators = lastVals
- } else {
- stateCopy.SetBlockAndValidators(header, partsHeader, lastVals, nextVals)
- }
- stateCopy.Stale = false
- stateCopy.AppHash = appHash
-
- appBlockHeight := stateCopy.LastBlockHeight
- coreBlockHeight := h.store.Height()
- if coreBlockHeight < appBlockHeight {
- // if the app is ahead, there's nothing we can do
- return ErrAppBlockHeightTooHigh{coreBlockHeight, appBlockHeight}
-
- } else if coreBlockHeight == appBlockHeight {
- // if we crashed between Commit and SaveState,
- // the state's app hash is stale.
- // otherwise we're synced
- if h.state.Stale {
- h.state.Stale = false
- h.state.AppHash = appHash
- }
- return checkState(h.state, stateCopy)
-
- } else if h.state.LastBlockHeight == appBlockHeight {
- // core is ahead of app but core's state height is at apps height
- // this happens if we crashed after saving the block,
- // but before committing it. We should be 1 ahead
- if coreBlockHeight != appBlockHeight+1 {
- PanicSanity(Fmt("core.state.height == app.height but core.height (%d) > app.height+1 (%d)", coreBlockHeight, appBlockHeight+1))
- }
-
- // check that the blocks last apphash is the states apphash
- block := h.store.LoadBlock(coreBlockHeight)
- if !bytes.Equal(block.Header.AppHash, appHash) {
- return ErrLastStateMismatch{coreBlockHeight, block.Header.AppHash, appHash}
- }
-
- // replay the block against the actual tendermint state (not the copy)
- return h.loadApplyBlock(coreBlockHeight, h.state, appConnConsensus)
-
- } else {
- // either we're caught up or there's blocks to replay
- // replay all blocks starting with appBlockHeight+1
- for i := appBlockHeight + 1; i <= coreBlockHeight; i++ {
- h.loadApplyBlock(i, stateCopy, appConnConsensus)
- }
- return checkState(h.state, stateCopy)
- }
- }
-
- func checkState(s, stateCopy *State) error {
- // The computed state and the previously set state should be identical
- if !s.Equals(stateCopy) {
- return ErrStateMismatch{stateCopy, s}
- }
- return nil
- }
-
- func (h *Handshaker) loadApplyBlock(blockIndex int, state *State, appConnConsensus proxy.AppConnConsensus) error {
- h.nBlocks += 1
- block := h.store.LoadBlock(blockIndex)
- panicOnNilBlock(blockIndex, h.store.Height(), block) // XXX
- var eventCache types.Fireable // nil
- return state.ApplyBlock(eventCache, appConnConsensus, block, block.MakePartSet(h.config.GetInt("block_part_size")).Header(), mockMempool{})
- }
-
- func panicOnNilBlock(height, bsHeight int, block *types.Block) {
- if block == nil {
- // Sanity?
- PanicCrisis(Fmt(`
- block is nil for height <= blockStore.Height() (%d <= %d).
- Block: %v,
- `, height, bsHeight, block))
-
- }
- }
|