package state
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
|
|
. "github.com/tendermint/go-common"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
"github.com/tendermint/tendermint/types"
|
|
tmsp "github.com/tendermint/tmsp/types"
|
|
)
|
|
|
|
// Execute the block to mutate State.
|
|
// Also, execute txs on the proxyAppCtx and validate apphash
|
|
// Rolls back before executing transactions.
|
|
// Rolls back if invalid, but never commits.
|
|
func (s *State) ExecBlock(proxyAppCtx proxy.AppContext, block *types.Block, blockPartsHeader types.PartSetHeader) error {
|
|
|
|
// Validate the block.
|
|
err := s.validateBlock(block)
|
|
if err != nil {
|
|
return 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()
|
|
|
|
// First, rollback.
|
|
if err != nil {
|
|
proxyAppCtx.RollbackSync()
|
|
return err
|
|
}
|
|
|
|
// Execute, or rollback. (Does not commit)
|
|
err = s.execBlockOnProxyApp(proxyAppCtx, block)
|
|
if err != nil {
|
|
proxyAppCtx.RollbackSync()
|
|
return err
|
|
}
|
|
|
|
// All good!
|
|
nextValSet.IncrementAccum(1)
|
|
s.Validators = nextValSet
|
|
s.LastValidators = valSet
|
|
s.LastAppHash = block.AppHash
|
|
s.LastBlockHeight = block.Height
|
|
s.LastBlockHash = block.Hash()
|
|
s.LastBlockParts = blockPartsHeader
|
|
s.LastBlockTime = block.Time
|
|
|
|
return nil
|
|
}
|
|
|
|
// Commits block on proxyAppCtx.
|
|
func (s *State) Commit(proxyAppCtx proxy.AppContext) error {
|
|
err := proxyAppCtx.CommitSync()
|
|
return err
|
|
}
|
|
|
|
// Executes transactions on proxyAppCtx.
|
|
func (s *State) execBlockOnProxyApp(proxyAppCtx proxy.AppContext, block *types.Block) error {
|
|
// Execute transactions and get hash
|
|
var invalidTxErr error
|
|
proxyCb := func(req tmsp.Request, res tmsp.Response) {
|
|
switch res := res.(type) {
|
|
case tmsp.ResponseAppendTx:
|
|
reqAppendTx := req.(tmsp.RequestAppendTx)
|
|
if res.RetCode != tmsp.RetCodeOK {
|
|
if invalidTxErr == nil {
|
|
invalidTxErr = InvalidTxError{reqAppendTx.TxBytes, res.RetCode}
|
|
}
|
|
}
|
|
case tmsp.ResponseEvent:
|
|
s.evc.FireEvent(types.EventStringApp(), types.EventDataApp{res.Key, res.Data})
|
|
}
|
|
}
|
|
proxyAppCtx.SetResponseCallback(proxyCb)
|
|
for _, tx := range block.Data.Txs {
|
|
proxyAppCtx.AppendTxAsync(tx)
|
|
if err := proxyAppCtx.Error(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
hash, err := proxyAppCtx.GetHashSync()
|
|
if err != nil {
|
|
log.Warn("Error computing proxyAppCtx hash", "error", err)
|
|
return err
|
|
}
|
|
if invalidTxErr != nil {
|
|
log.Warn("Invalid transaction in block")
|
|
return invalidTxErr
|
|
}
|
|
|
|
// Check that appHash matches
|
|
if !bytes.Equal(block.AppHash, hash) {
|
|
log.Warn(Fmt("App hash in proposal was %X, computed %X instead", block.AppHash, hash))
|
|
return InvalidAppHashError{block.AppHash, hash}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *State) validateBlock(block *types.Block) error {
|
|
// Basic block validation.
|
|
err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate block LastValidation.
|
|
if block.Height == 1 {
|
|
if len(block.LastValidation.Precommits) != 0 {
|
|
return errors.New("Block at height 1 (first block) should have no LastValidation precommits")
|
|
}
|
|
} else {
|
|
if len(block.LastValidation.Precommits) != s.LastValidators.Size() {
|
|
return fmt.Errorf("Invalid block validation size. Expected %v, got %v",
|
|
s.LastValidators.Size(), len(block.LastValidation.Precommits))
|
|
}
|
|
err := s.LastValidators.VerifyValidation(
|
|
s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Updates the LastCommitHeight of the validators in valSet, in place.
|
|
// Assumes that lastValSet matches the valset of block.LastValidators
|
|
// CONTRACT: lastValSet is not mutated.
|
|
func updateValidatorsWithBlock(lastValSet *types.ValidatorSet, valSet *types.ValidatorSet, block *types.Block) {
|
|
|
|
for i, precommit := range block.LastValidation.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")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
type InvalidTxError struct {
|
|
Tx types.Tx
|
|
tmsp.RetCode
|
|
}
|
|
|
|
func (txErr InvalidTxError) Error() string {
|
|
return Fmt("Invalid tx: [%v] code: [%v]", txErr.Tx, txErr.RetCode)
|
|
}
|
|
|
|
type InvalidAppHashError struct {
|
|
Expected []byte
|
|
Got []byte
|
|
}
|
|
|
|
func (hashErr InvalidAppHashError) Error() string {
|
|
return Fmt("Invalid hash: [%X] got: [%X]", hashErr.Expected, hashErr.Got)
|
|
}
|