package state
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
|
|
. "github.com/tendermint/go-common"
|
|
"github.com/tendermint/tendermint/events"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// NOTE: If an error occurs during block execution, state will be left
|
|
// at an invalid state. Copy the state before calling ExecBlock!
|
|
func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
|
|
err := execBlock(s, block, blockPartsHeader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// State.Hash should match block.StateHash
|
|
stateHash := s.Hash()
|
|
if !bytes.Equal(stateHash, block.StateHash) {
|
|
return errors.New(Fmt("Invalid state hash. Expected %X, got %X",
|
|
stateHash, block.StateHash))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// executes transactions of a block, does not check block.StateHash
|
|
// NOTE: If an error occurs during block execution, state will be left
|
|
// at an invalid state. Copy the state before calling execBlock!
|
|
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) 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 errors.New(Fmt("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
|
|
}
|
|
}
|
|
|
|
// Update Validator.LastCommitHeight as necessary.
|
|
for i, precommit := range block.LastValidation.Precommits {
|
|
if precommit == nil {
|
|
continue
|
|
}
|
|
_, val := s.LastValidators.GetByIndex(i)
|
|
if val == nil {
|
|
PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
|
|
}
|
|
if _, val_ := s.Validators.GetByAddress(val.Address); val_ != nil {
|
|
val_.LastCommitHeight = block.Height - 1
|
|
updated := s.Validators.Update(val_)
|
|
if !updated {
|
|
PanicCrisis("Failed to update validator LastCommitHeight")
|
|
}
|
|
} else {
|
|
PanicCrisis("Could not find validator")
|
|
}
|
|
}
|
|
|
|
// Remember LastValidators
|
|
s.LastValidators = s.Validators.Copy()
|
|
|
|
// Execute each tx
|
|
for _, tx := range block.Data.Txs {
|
|
err := ExecTx(s, tx, s.evc)
|
|
if err != nil {
|
|
return InvalidTxError{tx, err}
|
|
}
|
|
}
|
|
|
|
// Increment validator AccumPowers
|
|
s.Validators.IncrementAccum(1)
|
|
s.LastBlockHeight = block.Height
|
|
s.LastBlockHash = block.Hash()
|
|
s.LastBlockParts = blockPartsHeader
|
|
s.LastBlockTime = block.Time
|
|
return nil
|
|
}
|
|
|
|
// If the tx is invalid, an error will be returned.
|
|
// Unlike ExecBlock(), state will not be altered.
|
|
func ExecTx(s *State, tx types.Tx, evc events.Fireable) (err error) {
|
|
|
|
// TODO: do something with fees
|
|
//fees := int64(0)
|
|
//_s := blockCache.State() // hack to access validators and block height
|
|
|
|
// XXX Query ledger application
|
|
return nil
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
type InvalidTxError struct {
|
|
Tx types.Tx
|
|
Reason error
|
|
}
|
|
|
|
func (txErr InvalidTxError) Error() string {
|
|
return Fmt("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
|
|
}
|