mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-31 04:01:55 +00:00
Tendermint <-> Application refactor
This commit is contained in:
@@ -3,32 +3,109 @@ package state
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmsp "github.com/tendermint/tmsp/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)
|
||||
// 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
|
||||
}
|
||||
// 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))
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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 {
|
||||
@@ -42,8 +119,8 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
}
|
||||
} 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)))
|
||||
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)
|
||||
@@ -52,66 +129,53 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
}
|
||||
}
|
||||
|
||||
// Update Validator.LastCommitHeight as necessary.
|
||||
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 := s.LastValidators.GetByIndex(i)
|
||||
_, val := lastValSet.GetByIndex(i)
|
||||
if val == nil {
|
||||
PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
|
||||
}
|
||||
if _, val_ := s.Validators.GetByAddress(val.Address); val_ != nil {
|
||||
if _, val_ := valSet.GetByAddress(val.Address); val_ != nil {
|
||||
val_.LastCommitHeight = block.Height - 1
|
||||
updated := s.Validators.Update(val_)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
Tx types.Tx
|
||||
tmsp.RetCode
|
||||
}
|
||||
|
||||
func (txErr InvalidTxError) Error() string {
|
||||
return Fmt("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
|
||||
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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user