Newer
Older
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import (
"fmt"
"runtime/debug"
acm "github.com/hyperledger/burrow/account"
"github.com/hyperledger/burrow/account/state"
"github.com/hyperledger/burrow/binary"
bcm "github.com/hyperledger/burrow/blockchain"
"github.com/hyperledger/burrow/event"
"github.com/hyperledger/burrow/execution/events"
"github.com/hyperledger/burrow/execution/evm"
"github.com/hyperledger/burrow/logging"
"github.com/hyperledger/burrow/logging/structure"
"github.com/hyperledger/burrow/permission"
ptypes "github.com/hyperledger/burrow/permission/types"
"github.com/hyperledger/burrow/txs"
// TODO
const GasLimit = uint64(1000000)
// Execute transaction against block cache (i.e. block buffer)
Execute(tx txs.Tx) error
// Reset executor to underlying State
Reset() error
// Executes transactions
type BatchCommitter interface {
BatchExecutor
// Commit execution results to underlying State and provide opportunity
// to mutate state before it is saved
Commit() (stateHash []byte, err error)
}
sync.Mutex
chainID string
tip bcm.Tip
runCall bool
state *State
stateCache state.Cache
nameRegCache *NameRegCache
publisher event.Publisher
eventCache *event.Cache
vmOptions []func(*evm.VM)
// Wraps a cache of what is variously known as the 'check cache' and 'mempool'
func NewBatchChecker(state *State,
chainID string,
tip bcm.Tip,
logger *logging.Logger,
options ...ExecutionOption) BatchExecutor {
return newExecutor(false, state, chainID, tip, event.NewNoOpPublisher(),
logger.WithScope("NewBatchExecutor"), options...)
func NewBatchCommitter(state *State,
chainID string,
tip bcm.Tip,
publisher event.Publisher,
logger *logging.Logger,
options ...ExecutionOption) BatchCommitter {
return newExecutor(true, state, chainID, tip, publisher,
logger.WithScope("NewBatchCommitter"), options...)
backend *State,
eventFireable event.Publisher,
logger *logging.Logger,
options ...ExecutionOption) *executor {
exe := &executor{
chainID: chainID,
tip: tip,
runCall: runCall,
state: backend,
stateCache: state.NewCache(backend),
nameRegCache: NewNameRegCache(backend),
publisher: eventFireable,
eventCache: event.NewEventCache(eventFireable),
logger: logger.With(structure.ComponentKey, "Executor"),
for _, option := range options {
option(exe)
}
return exe
// Accounts
func (exe *executor) GetAccount(address acm.Address) (acm.Account, error) {
return exe.stateCache.GetAccount(address)
// Storage
func (exe *executor) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) {
return exe.stateCache.GetStorage(address, key)
func (exe *executor) Commit() (hash []byte, err error) {
exe.Lock()
defer exe.Unlock()
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic in executor.Commit(): %v", r)
}
}()
// flush the caches
err = exe.stateCache.Flush(exe.state)
if err != nil {
return nil, err
}
err = exe.nameRegCache.Flush(exe.state)
if err != nil {
return nil, err
}
err = exe.state.Save()
if err != nil {
return nil, err
}
// flush events to listeners
return exe.state.Hash(), nil
exe.stateCache.Reset(exe.state)
exe.nameRegCache.Reset(exe.state)
}
// If the tx is invalid, an error will be returned.
// Unlike ExecBlock(), state will not be altered.
func (exe *executor) Execute(tx txs.Tx) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic in executor.Execute(%s): %v\n%s", tx.String(), r,
debug.Stack())
}
}()
txHash := tx.Hash(exe.chainID)
logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With(
"run_call", exe.runCall,
"tx_hash", txHash)
logger.TraceMsg("Executing transaction", "tx", tx.String())
// TODO: do something with fees
// Exec tx
switch tx := tx.(type) {
case *txs.SendTx:
accounts, err := getInputs(exe.stateCache, tx.Inputs)
if err != nil {
return err
}
// ensure all inputs have send permissions
if !hasSendPermission(exe.stateCache, accounts, logger) {
return fmt.Errorf("at least one input lacks permission for SendTx")
}
// add outputs to accounts map
// if any outputs don't exist, all inputs must have CreateAccount perm
accounts, err = getOrMakeOutputs(exe.stateCache, accounts, tx.Outputs, logger)
if err != nil {
return err
}
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := validateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return txs.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
err = adjustByInputs(accounts, tx.Inputs, logger)
if err != nil {
return err
}
err = adjustByOutputs(accounts, tx.Outputs)
if err != nil {
return err
}
for _, acc := range accounts {
exe.stateCache.UpdateAccount(acc)
// if the exe.eventCache is nil, nothing will happen
if exe.eventCache != nil {
for _, i := range tx.Inputs {
events.PublishAccountInput(exe.eventCache, i.Address, txHash, tx, nil, "")
}
for _, o := range tx.Outputs {
events.PublishAccountOutput(exe.eventCache, o.Address, txHash, tx, nil, "")
}
}
return nil
case *txs.CallTx:
// Validate input
inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address)
if inAcc == nil {
return txs.ErrTxInvalidAddress
}
if createContract {
if !hasCreateContractPermission(exe.stateCache, inAcc, logger) {
return fmt.Errorf("account %s does not have CreateContract permission", tx.Input.Address)
}
} else {
if !hasCallPermission(exe.stateCache, inAcc, logger) {
return fmt.Errorf("account %s does not have Call permission", tx.Input.Address)
}
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
logger.InfoMsg("Cannot find public key for input account",
return err
}
signBytes := acm.SignBytes(exe.chainID, tx)
err = validateInput(inAcc, signBytes, tx.Input)
if err != nil {
"tx_input", tx.Input, structure.ErrorKey, err)
return err
}
if tx.Input.Amount < tx.Fee {
logger.InfoMsg("Sender did not send enough to cover the fee",
return txs.ErrTxInsufficientFunds
}
if !createContract {
// check if its a native contract
if evm.RegisteredNativeContract(tx.Address.Word256()) {
return fmt.Errorf("attempt to call a native contract at %s, "+
Silas Davis
committed
"but native contracts cannot be called using CallTx. Use a "+
"contract that calls the native contract or the appropriate tx "+
}
// Output account may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
// Note: tx.Address == nil iff createContract so dereference is okay
outAcc, err = exe.stateCache.GetAccount(*tx.Address)
// Good!
value := tx.Input.Amount - tx.Fee
logger.TraceMsg("Incrementing sequence number for CallTx",
"account", inAcc.Address(),
"old_sequence", inAcc.Sequence(),
"new_sequence", inAcc.Sequence()+1)
inAcc, err = inAcc.IncSequence().SubtractFromBalance(tx.Fee)
if err != nil {
return err
}
exe.stateCache.UpdateAccount(inAcc)
// The logic in runCall MUST NOT return.
// VM call variables
var (
gas uint64 = tx.GasLimit
err error = nil
caller acm.MutableAccount = acm.AsMutableAccount(inAcc)
callee acm.MutableAccount = nil // initialized below
code []byte = nil
ret []byte = nil
txCache = state.NewCache(exe.stateCache)
params = evm.Params{
BlockHeight: exe.tip.LastBlockHeight(),
BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()),
BlockTime: exe.tip.LastBlockTime().Unix(),
GasLimit: GasLimit,
}
)
if !createContract && (outAcc == nil || len(outAcc.Code()) == 0) {
// if you call an account that doesn't exist
// or an account with no code then we take fees (sorry pal)
// NOTE: it's fine to create a contract and call it within one
// block (sequence number will prevent re-ordering of those txs)
// but to create with one contract and call with another
// you have to wait a block to avoid a re-ordering attack
// that will take your fees
if outAcc == nil {
"caller_address", inAcc.Address(),
"callee_address", tx.Address)
} else {
"caller_address", inAcc.Address(),
"callee_address", tx.Address)
}
err = txs.ErrTxInvalidAddress
goto CALL_COMPLETE
}
// get or create callee
if createContract {
// We already checked for permission
callee = evm.DeriveNewAccount(caller, state.GlobalAccountPermissions(exe.state),
logger.With(
"tx", tx.String(),
"tx_hash", txHash,
"run_call", exe.runCall,
))
code = tx.Data
"contract_address", callee.Address(),
"init_code", code)
} else {
callee = acm.AsMutableAccount(outAcc)
code = callee.Code()
"contract_address", callee.Address(),
"input", tx.Data,
"contract_code", code)
{ // Capture scope for goto.
// Write caller/callee to txCache.
txCache.UpdateAccount(caller)
txCache.UpdateAccount(callee)
vmach := evm.NewVM(txCache, params, caller.Address(), tx.Hash(exe.chainID), logger, exe.vmOptions...)
vmach.SetPublisher(exe.eventCache)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
structure.ErrorKey, err)
goto CALL_COMPLETE
}
if createContract {
txCache.Sync(exe.stateCache)
}
CALL_COMPLETE: // err may or may not be nil.
// Create a receipt from the ret and whether it erred.
"caller", caller,
"callee", callee,
"return", ret,
structure.ErrorKey, err)
// Fire Events for sender and receiver
// a separate event will be fired from vm for each additional call
exception := ""
if err != nil {
exception = err.Error()
}
txHash := tx.Hash(exe.chainID)
events.PublishAccountInput(exe.eventCache, tx.Input.Address, txHash, tx, ret, exception)
events.PublishAccountOutput(exe.eventCache, *tx.Address, txHash, tx, ret, exception)
}
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc, err = inAcc.SubtractFromBalance(value)
if err != nil {
return err
}
if createContract {
logger.TraceMsg("Incrementing sequence number since creates contract",
"account", inAcc.Address(),
"old_sequence", inAcc.Sequence(),
"new_sequence", inAcc.Sequence()+1)
inAcc.IncSequence()
exe.stateCache.UpdateAccount(inAcc)
}
return nil
case *txs.NameTx:
// Validate input
inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address)
if inAcc == nil {
return txs.ErrTxInvalidAddress
}
// check permission
if !hasNamePermission(exe.stateCache, inAcc, logger) {
return fmt.Errorf("account %s does not have Name permission", tx.Input.Address)
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
logger.InfoMsg("Cannot find public key for input account",
return err
}
signBytes := acm.SignBytes(exe.chainID, tx)
err = validateInput(inAcc, signBytes, tx.Input)
if err != nil {
"tx_input", tx.Input, structure.ErrorKey, err)
return err
}
if tx.Input.Amount < tx.Fee {
logger.InfoMsg("Sender did not send enough to cover the fee",
return txs.ErrTxInsufficientFunds
}
// validate the input strings
if err := tx.ValidateStrings(); err != nil {
return err
}
value := tx.Input.Amount - tx.Fee
// let's say cost of a name for one block is len(data) + 32
costPerBlock := txs.NameCostPerBlock(txs.NameBaseCost(tx.Name, tx.Data))
expiresIn := value / uint64(costPerBlock)
lastBlockHeight := exe.tip.LastBlockHeight()
"value", value,
"cost_per_block", costPerBlock,
"expires_in", expiresIn,
"last_block_height", lastBlockHeight)
// check if the name exists
entry, err := exe.nameRegCache.GetNameRegEntry(tx.Name)
if err != nil {
return err
}
if entry != nil {
var expired bool
// if the entry already exists, and hasn't expired, we must be owner
if entry.Expires > lastBlockHeight {
// ensure we are owner
if entry.Owner != tx.Input.Address {
return fmt.Errorf("permission denied: sender %s is trying to update a name (%s) for "+
"which they are not an owner", tx.Input.Address, tx.Name)
}
} else {
expired = true
}
// no value and empty data means delete the entry
if value == 0 && len(tx.Data) == 0 {
// maybe we reward you for telling us we can delete this crap
// (owners if not expired, anyone if expired)
logger.TraceMsg("Removing NameReg entry (no value and empty data in tx requests this)",
err := exe.nameRegCache.RemoveNameRegEntry(entry.Name)
if err != nil {
return err
}
} else {
// update the entry by bumping the expiry
// and changing the data
if expired {
if expiresIn < txs.MinNameRegistrationPeriod {
return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)
}
entry.Expires = lastBlockHeight + expiresIn
entry.Owner = tx.Input.Address
logger.TraceMsg("An old NameReg entry has expired and been reclaimed",
"name", entry.Name,
"expires_in", expiresIn,
"owner", entry.Owner)
} else {
// since the size of the data may have changed
// we use the total amount of "credit"
oldCredit := (entry.Expires - lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data)
credit := oldCredit + value
if expiresIn < txs.MinNameRegistrationPeriod {
return fmt.Errorf("names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)
}
entry.Expires = lastBlockHeight + expiresIn
"name", entry.Name,
"expires_in", expiresIn,
"old_credit", oldCredit,
"value", value,
"credit", credit)
}
entry.Data = tx.Data
err := exe.nameRegCache.UpdateNameRegEntry(entry)
if err != nil {
return err
}
}
} else {
if expiresIn < txs.MinNameRegistrationPeriod {
return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)
}
// entry does not exist, so create it
Name: tx.Name,
Owner: tx.Input.Address,
Data: tx.Data,
Expires: lastBlockHeight + expiresIn,
}
"name", entry.Name,
"expires_in", expiresIn)
err := exe.nameRegCache.UpdateNameRegEntry(entry)
if err != nil {
return err
}
}
// TODO: something with the value sent?
// Good!
logger.TraceMsg("Incrementing sequence number for NameTx",
"tag", "sequence",
"account", inAcc.Address(),
"old_sequence", inAcc.Sequence(),
"new_sequence", inAcc.Sequence()+1)
inAcc, err = inAcc.SubtractFromBalance(value)
if err != nil {
return err
}
exe.stateCache.UpdateAccount(inAcc)
// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
txHash := tx.Hash(exe.chainID)
events.PublishAccountInput(exe.eventCache, tx.Input.Address, txHash, tx, nil, "")
events.PublishNameReg(exe.eventCache, txHash, tx)
}
return nil
// Consensus related Txs inactivated for now
// TODO!
/*
case *txs.BondTx:
valInfo := exe.blockCache.State().GetValidatorInfo(tx.PublicKey().Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
if err != nil {
return err
}
// add outputs to accounts map
// if any outputs don't exist, all inputs must have CreateAccount perm
// though outputs aren't created until unbonding/release time
canCreate := hasCreateAccountPermission(exe.blockCache, accounts)
for _, out := range tx.UnbondTo {
if acc == nil && !canCreate {
return fmt.Errorf("At least one input does not have permission to create accounts")
}
}
bondAcc := exe.blockCache.GetAccount(tx.PublicKey().Address())
if !hasBondPermission(exe.blockCache, bondAcc) {
return fmt.Errorf("The bonder does not have permission to bond")
}
return fmt.Errorf("At least one input lacks permission to bond")
}
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
return txs.ErrTxInvalidSignature
}
outTotal, err := validateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return txs.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
for _, acc := range accounts {
}
// Add ValidatorInfo
_s.SetValidatorInfo(&txs.ValidatorInfo{
Address: tx.PublicKey().Address(),
PublicKey: tx.PublicKey(),
UnbondTo: tx.UnbondTo,
FirstBondAmount: outTotal,
})
// Add Validator
added := _s.BondedValidators.Add(&txs.Validator{
Address: tx.PublicKey().Address(),
PublicKey: tx.PublicKey(),
BondHeight: _s.lastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
PanicCrisis("Failed to add validator")
}
// TODO: fire for all inputs
exe.eventCache.Fire(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""})
}
return nil
case *txs.UnbondTx:
// The validator must be active
_, val := _s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return txs.ErrTxInvalidAddress
}
// Verify the signature
signBytes := acm.SignBytes(exe.chainID, tx)
if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) {
return txs.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
_s.unbondValidator(val)
if exe.eventCache != nil {
exe.eventCache.Fire(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""})
}
return nil
case *txs.RebondTx:
// The validator must be inactive
_, val := _s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return txs.ErrTxInvalidAddress
}
// Verify the signature
signBytes := acm.SignBytes(exe.chainID, tx)
if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) {
return txs.ErrTxInvalidSignature
}
// tx.Height must be in a suitable range
minRebondHeight := _s.lastBlockHeight - (validatorTimeoutBlocks / 2)
maxRebondHeight := _s.lastBlockHeight + 2
if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) {
return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v",
minRebondHeight, tx.Height, maxRebondHeight))
}
// Good!
_s.rebondValidator(val)
if exe.eventCache != nil {
exe.eventCache.Fire(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""})
}
return nil
*/
case *txs.PermissionsTx:
// Validate input
inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address)
if inAcc == nil {
return txs.ErrTxInvalidAddress
}
err = tx.PermArgs.EnsureValid()
if err != nil {
return fmt.Errorf("PermissionsTx received containing invalid PermArgs: %v", err)
}
// check permission
if !HasPermission(exe.stateCache, inAcc, permFlag, logger) {
return fmt.Errorf("account %s does not have moderator permission %s (%b)", tx.Input.Address,
permission.PermFlagToString(permFlag), permFlag)
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
logger.InfoMsg("Cannot find public key for input account",
return err
}
signBytes := acm.SignBytes(exe.chainID, tx)
err = validateInput(inAcc, signBytes, tx.Input)
if err != nil {
structure.ErrorKey, err)
return err
}
value := tx.Input.Amount
"perm_args", tx.PermArgs.String())
var permAcc acm.Account
switch tx.PermArgs.PermFlag {
case permission.HasBase:
// this one doesn't make sense from txs
return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value)
permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
return perms.Base.Unset(*tx.PermArgs.Permission)
permAcc, err = mutatePermissions(exe.stateCache, acm.GlobalPermissionsAddress,
return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value)
return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
if !perms.AddRole(*tx.PermArgs.Role) {
return fmt.Errorf("role (%s) already exists for account %s",
*tx.PermArgs.Role, *tx.PermArgs.Address)
permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
if !perms.RmRole(*tx.PermArgs.Role) {
return fmt.Errorf("role (%s) does not exist for account %s",
*tx.PermArgs.Role, *tx.PermArgs.Address)
default:
return fmt.Errorf("invalid permission function: %s", permission.PermFlagToString(permFlag))
}
// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
if err != nil {
return err
}
// Good!
logger.TraceMsg("Incrementing sequence number for PermissionsTx",
"tag", "sequence",
"account", inAcc.Address(),
"old_sequence", inAcc.Sequence(),
"new_sequence", inAcc.Sequence()+1)
inAcc, err = inAcc.SubtractFromBalance(value)
if err != nil {
return err
}
exe.stateCache.UpdateAccount(inAcc)
if permAcc != nil {
exe.stateCache.UpdateAccount(permAcc)
txHash := tx.Hash(exe.chainID)
events.PublishAccountInput(exe.eventCache, tx.Input.Address, txHash, tx, nil, "")
events.PublishPermissions(exe.eventCache, permission.PermFlagToString(permFlag), txHash, tx)
}
return nil
default:
// binary decoding should not let this happen
return fmt.Errorf("unknown Tx type: %#v", tx)
}
}
func mutatePermissions(stateReader state.Reader, address acm.Address,
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
mutator func(*ptypes.AccountPermissions) error) (acm.Account, error) {
account, err := stateReader.GetAccount(address)
if err != nil {
return nil, err
}
if account == nil {
return nil, fmt.Errorf("could not get account at address %s in order to alter permissions", address)
}
mutableAccount := acm.AsMutableAccount(account)
return mutableAccount, mutator(mutableAccount.MutablePermissions())
}
// ExecBlock stuff is now taken care of by the consensus engine.
// But we leave here for now for reference when we have to do validator updates
/*
// 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 *txs.Block, blockPartsHeader txs.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 *txs.Block, blockPartsHeader txs.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.chainID, s.lastBlockHeight, s.lastBlockAppHash, 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.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.LastValidation.Precommits)))
}
err := s.LastBondedValidators.VerifyValidation(
s.chainID, s.lastBlockAppHash, 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.LastBondedValidators.GetByIndex(i)
if val == nil {
PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
PanicCrisis("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
PanicCrisis("Failed to update unbonding validator LastCommitHeight")
}
} else {
PanicCrisis("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Create BlockCache to cache changes to state.
blockCache := NewBlockCache(s)
// Execute each tx
for _, tx := range block.Data.Txs {
err := ExecTx(blockCache, tx, true, s.eventCache)
if err != nil {
return InvalidTxError{tx, err}
}
}
// Now sync the BlockCache to the backend.
blockCache.Sync()
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*txs.Validator{}
s.UnbondingValidators.Iterate(func(index int, val *txs.Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*txs.Validator{}
s.BondedValidators.Iterate(func(index int, val *txs.Validator) bool {
lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight)