Skip to content
Snippets Groups Projects
execution.go 39.5 KiB
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.

Silas Davis's avatar
Silas Davis committed
package execution
Silas Davis's avatar
Silas Davis committed
	"sync"
	acm "github.com/hyperledger/burrow/account"
	"github.com/hyperledger/burrow/account/state"
Silas Davis's avatar
Silas Davis committed
	"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)

Silas Davis's avatar
Silas Davis committed
type BatchExecutor interface {
Sean Young's avatar
Sean Young committed
	state.Reader
Silas Davis's avatar
Silas Davis committed
	// Execute transaction against block cache (i.e. block buffer)
	Execute(tx txs.Tx) error
	// Reset executor to underlying State
	Reset() error
Silas Davis's avatar
Silas Davis committed
// 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)
}
Silas Davis's avatar
Silas Davis committed
type executor struct {
	sync.Mutex
	chainID      string
	tip          bcm.Tip
	runCall      bool
	state        *State
	nameRegCache *NameRegCache
	publisher    event.Publisher
	eventCache   *event.Cache
Silas Davis's avatar
Silas Davis committed
	logger       *logging.Logger
Silas Davis's avatar
Silas Davis committed
}
Silas Davis's avatar
Silas Davis committed
var _ BatchExecutor = (*executor)(nil)
Silas Davis's avatar
Silas Davis committed
// 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...)
Silas Davis's avatar
Silas Davis committed
}
Silas Davis's avatar
Silas Davis committed
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...)
Silas Davis's avatar
Silas Davis committed
func newExecutor(runCall bool,
Silas Davis's avatar
Silas Davis committed
	chainID string,
	tip bcm.Tip,
	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
Silas Davis's avatar
Silas Davis committed
// Accounts
func (exe *executor) GetAccount(address acm.Address) (acm.Account, error) {
	return exe.stateCache.GetAccount(address)
Silas Davis's avatar
Silas Davis committed
}
Silas Davis's avatar
Silas Davis committed
// 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) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered from panic in executor.Commit(): %v", r)
		}
	}()
	err = exe.stateCache.Flush(exe.state)
	if err != nil {
		return nil, err
	}
	err = exe.nameRegCache.Flush(exe.state)
	if err != nil {
		return nil, err
	}
Silas Davis's avatar
Silas Davis committed
	// save state to disk
	// flush events to listeners
Silas Davis's avatar
Silas Davis committed
	exe.eventCache.Flush()
	return exe.state.Hash(), nil
Silas Davis's avatar
Silas Davis committed
func (exe *executor) Reset() error {
	exe.stateCache.Reset(exe.state)
	exe.nameRegCache.Reset(exe.state)
Silas Davis's avatar
Silas Davis committed
	return nil
}

// 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)
Silas Davis's avatar
Silas Davis committed
	logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With(
		"run_call", exe.runCall,
		"tx_hash", txHash)
Silas Davis's avatar
Silas Davis committed
	logger.TraceMsg("Executing transaction", "tx", tx.String())
	// TODO: do something with fees
Silas Davis's avatar
Silas Davis committed
	fees := uint64(0)

	// 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) {
Silas Davis's avatar
Silas Davis committed
			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)
Silas Davis's avatar
Silas Davis committed
		signBytes := acm.SignBytes(exe.chainID, tx)
		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)
Silas Davis's avatar
Silas Davis committed
		// if the exe.eventCache is nil, nothing will happen
		if exe.eventCache != nil {
				events.PublishAccountInput(exe.eventCache, i.Address, txHash, tx, nil, "")
				events.PublishAccountOutput(exe.eventCache, o.Address, txHash, tx, nil, "")
Silas Davis's avatar
Silas Davis committed
		var inAcc acm.MutableAccount
		var outAcc acm.Account
		inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address)
Silas Davis's avatar
Silas Davis committed
		if err != nil {
			return err
		}
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find input account",
				"tx_input", tx.Input)
Silas Davis's avatar
Silas Davis committed
		createContract := tx.Address == nil
			if !hasCreateContractPermission(exe.stateCache, inAcc, logger) {
Silas Davis's avatar
Silas Davis committed
				return fmt.Errorf("account %s does not have CreateContract permission", tx.Input.Address)
			if !hasCallPermission(exe.stateCache, inAcc, logger) {
Silas Davis's avatar
Silas Davis committed
				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 {
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find public key for input account",
				"tx_input", tx.Input)
Silas Davis's avatar
Silas Davis committed
		signBytes := acm.SignBytes(exe.chainID, tx)
		err = validateInput(inAcc, signBytes, tx.Input)
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("validateInput failed",
				"tx_input", tx.Input, structure.ErrorKey, err)
			return err
		}
		if tx.Input.Amount < tx.Fee {
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Sender did not send enough to cover the fee",
				"tx_input", tx.Input)
			return txs.ErrTxInsufficientFunds
		}

		if !createContract {
			// check if its a native contract
Silas Davis's avatar
Silas Davis committed
			if evm.RegisteredNativeContract(tx.Address.Word256()) {
				return fmt.Errorf("attempt to call a native contract at %s, "+
					"but native contracts cannot be called using CallTx. Use a "+
					"contract that calls the native contract or the appropriate tx "+
Silas Davis's avatar
Silas Davis committed
					"type (eg. PermissionsTx, NameTx)", tx.Address)
			}

			// 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
Silas Davis's avatar
Silas Davis committed
			// Note: tx.Address == nil iff createContract so dereference is okay
			outAcc, err = exe.stateCache.GetAccount(*tx.Address)
Silas Davis's avatar
Silas Davis committed
			if err != nil {
				return err
			}
Silas Davis's avatar
Silas Davis committed
		logger.Trace.Log("output_account", outAcc)

		// Good!
		value := tx.Input.Amount - tx.Fee
Silas Davis's avatar
Silas Davis committed
		logger.TraceMsg("Incrementing sequence number for CallTx",
			"tag", "sequence",
Silas Davis's avatar
Silas Davis committed
			"account", inAcc.Address(),
			"old_sequence", inAcc.Sequence(),
			"new_sequence", inAcc.Sequence()+1)

		inAcc, err = inAcc.IncSequence().SubtractFromBalance(tx.Fee)
		if err != nil {
			return err
		}
Silas Davis's avatar
Silas Davis committed

		exe.stateCache.UpdateAccount(inAcc)

		// The logic in runCall MUST NOT return.
Silas Davis's avatar
Silas Davis committed
		if exe.runCall {
Silas Davis's avatar
Silas Davis committed
				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)
Silas Davis's avatar
Silas Davis committed
				params                     = evm.Params{
					BlockHeight: exe.tip.LastBlockHeight(),
					BlockHash:   binary.LeftPadWord256(exe.tip.LastBlockHash()),
					BlockTime:   exe.tip.LastBlockTime().Unix(),
					GasLimit:    GasLimit,
Silas Davis's avatar
Silas Davis committed
			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 {
Silas Davis's avatar
Silas Davis committed
					logger.InfoMsg("Call to address that does not exist",
						"caller_address", inAcc.Address(),
						"callee_address", tx.Address)
Silas Davis's avatar
Silas Davis committed
					logger.InfoMsg("Call to address that holds no code",
						"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,
					))
Silas Davis's avatar
Silas Davis committed
				logger.TraceMsg("Creating new contract",
					"contract_address", callee.Address(),
					"init_code", code)
Silas Davis's avatar
Silas Davis committed
				callee = acm.AsMutableAccount(outAcc)
				code = callee.Code()
Silas Davis's avatar
Silas Davis committed
				logger.TraceMsg("Calling existing contract",
					"contract_address", callee.Address(),
					"input", tx.Data,
					"contract_code", code)
Silas Davis's avatar
Silas Davis committed
			logger.Trace.Log("callee", callee.Address().String())
Silas Davis's avatar
Silas Davis committed
			// Run VM call and sync txCache to exe.blockCache.
			{ // 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.
Silas Davis's avatar
Silas Davis committed
					logger.InfoMsg("Error on execution",
Silas Davis's avatar
Silas Davis committed
				logger.TraceMsg("Successful execution")
Silas Davis's avatar
Silas Davis committed
					callee.SetCode(ret)
			}

		CALL_COMPLETE: // err may or may not be nil.

			// Create a receipt from the ret and whether it erred.
Silas Davis's avatar
Silas Davis committed
			logger.TraceMsg("VM call complete",
				"caller", caller,
				"callee", callee,
				"return", ret,

			// Fire Events for sender and receiver
			// a separate event will be fired from vm for each additional call
Silas Davis's avatar
Silas Davis committed
			if exe.eventCache != nil {
				exception := ""
				if err != nil {
					exception = err.Error()
				}
				txHash := tx.Hash(exe.chainID)
				events.PublishAccountInput(exe.eventCache, tx.Input.Address, txHash, tx, ret, exception)
Silas Davis's avatar
Silas Davis committed
				if tx.Address != nil {
					events.PublishAccountOutput(exe.eventCache, *tx.Address, txHash, tx, ret, exception)
Silas Davis's avatar
Silas Davis committed
				}
			}
		} 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
			}
Silas Davis's avatar
Silas Davis committed
				// This is done by DeriveNewAccount when runCall == true
Silas Davis's avatar
Silas Davis committed
				logger.TraceMsg("Incrementing sequence number since creates contract",
					"tag", "sequence",
Silas Davis's avatar
Silas Davis committed
					"account", inAcc.Address(),
					"old_sequence", inAcc.Sequence(),
					"new_sequence", inAcc.Sequence()+1)
				inAcc.IncSequence()
			exe.stateCache.UpdateAccount(inAcc)
		inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address)
Silas Davis's avatar
Silas Davis committed
		if err != nil {
			return err
		}
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find input account",
				"tx_input", tx.Input)
			return txs.ErrTxInvalidAddress
		}
		// check permission
		if !hasNamePermission(exe.stateCache, inAcc, logger) {
Silas Davis's avatar
Silas Davis committed
			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 {
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find public key for input account",
				"tx_input", tx.Input)
Silas Davis's avatar
Silas Davis committed
		signBytes := acm.SignBytes(exe.chainID, tx)
		err = validateInput(inAcc, signBytes, tx.Input)
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("validateInput failed",
				"tx_input", tx.Input, structure.ErrorKey, err)
			return err
		}
		if tx.Input.Amount < tx.Fee {
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Sender did not send enough to cover the fee",
				"tx_input", tx.Input)
			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))
Silas Davis's avatar
Silas Davis committed
		expiresIn := value / uint64(costPerBlock)
		lastBlockHeight := exe.tip.LastBlockHeight()
Silas Davis's avatar
Silas Davis committed
		logger.TraceMsg("New NameTx",
			"value", value,
			"cost_per_block", costPerBlock,
			"expires_in", expiresIn,
			"last_block_height", lastBlockHeight)
		entry, err := exe.nameRegCache.GetNameRegEntry(tx.Name)
		if err != nil {
			return err
		}
Silas Davis's avatar
Silas Davis committed

			// if the entry already exists, and hasn't expired, we must be owner
			if entry.Expires > lastBlockHeight {
				// ensure we are owner
Silas Davis's avatar
Silas Davis committed
				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)
Silas Davis's avatar
Silas Davis committed
				logger.TraceMsg("Removing NameReg entry (no value and empty data in tx requests this)",
					"name", entry.Name)
				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
Silas Davis's avatar
Silas Davis committed
					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"
Silas Davis's avatar
Silas Davis committed
					oldCredit := (entry.Expires - lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data)
Silas Davis's avatar
Silas Davis committed
					expiresIn = uint64(credit / costPerBlock)
					if expiresIn < txs.MinNameRegistrationPeriod {
Silas Davis's avatar
Silas Davis committed
						return fmt.Errorf("names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)
					}
					entry.Expires = lastBlockHeight + expiresIn
Silas Davis's avatar
Silas Davis committed
					logger.TraceMsg("Updated NameReg entry",
						"name", entry.Name,
						"expires_in", expiresIn,
						"old_credit", oldCredit,
						"value", value,
						"credit", credit)
				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
Silas Davis's avatar
Silas Davis committed
			entry = &NameRegEntry{
				Name:    tx.Name,
				Owner:   tx.Input.Address,
				Data:    tx.Data,
				Expires: lastBlockHeight + expiresIn,
			}
Silas Davis's avatar
Silas Davis committed
			logger.TraceMsg("Creating NameReg entry",
				"name", entry.Name,
				"expires_in", expiresIn)
			err := exe.nameRegCache.UpdateNameRegEntry(entry)
			if err != nil {
				return err
			}
		}

		// TODO: something with the value sent?

		// Good!
Silas Davis's avatar
Silas Davis committed
		logger.TraceMsg("Incrementing sequence number for NameTx",
			"tag", "sequence",
			"account", inAcc.Address(),
			"old_sequence", inAcc.Sequence(),
			"new_sequence", inAcc.Sequence()+1)
Silas Davis's avatar
Silas Davis committed
		inAcc.IncSequence()
		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?

Silas Davis's avatar
Silas Davis committed
		if exe.eventCache != nil {
			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:
Silas Davis's avatar
Silas Davis committed
						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")
						}

Silas Davis's avatar
Silas Davis committed
						accounts, err := getInputs(exe.blockCache, tx.Inputs)
						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
Silas Davis's avatar
Silas Davis committed
						canCreate := hasCreateAccountPermission(exe.blockCache, accounts)
						for _, out := range tx.UnbondTo {
Silas Davis's avatar
Silas Davis committed
							acc := exe.blockCache.GetAccount(out.Address)
							if acc == nil && !canCreate {
								return fmt.Errorf("At least one input does not have permission to create accounts")
							}
						}

Silas Davis's avatar
Silas Davis committed
						bondAcc := exe.blockCache.GetAccount(tx.PublicKey().Address())
						if !hasBondPermission(exe.blockCache, bondAcc) {
							return fmt.Errorf("The bonder does not have permission to bond")
						}

Silas Davis's avatar
Silas Davis committed
						if !hasBondOrSendPermission(exe.blockCache, accounts) {
							return fmt.Errorf("At least one input lacks permission to bond")
						}

Silas Davis's avatar
Silas Davis committed
						signBytes := acm.SignBytes(exe.chainID, tx)
						inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
						if err != nil {
							return err
						}
Silas Davis's avatar
Silas Davis committed
						if !tx.PublicKey().VerifyBytes(signBytes, tx.Signature) {
							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 {
Silas Davis's avatar
Silas Davis committed
							exe.blockCache.UpdateAccount(acc)
						}
						// Add ValidatorInfo
						_s.SetValidatorInfo(&txs.ValidatorInfo{
Silas Davis's avatar
Silas Davis committed
							Address:         tx.PublicKey().Address(),
							PublicKey:          tx.PublicKey(),
Silas Davis's avatar
Silas Davis committed
							FirstBondHeight: _s.lastBlockHeight + 1,
							FirstBondAmount: outTotal,
						})
						// Add Validator
						added := _s.BondedValidators.Add(&txs.Validator{
Silas Davis's avatar
Silas Davis committed
							Address:     tx.PublicKey().Address(),
							PublicKey:      tx.PublicKey(),
							BondHeight:  _s.lastBlockHeight + 1,
							VotingPower: outTotal,
							Accum:       0,
						})
						if !added {
							PanicCrisis("Failed to add validator")
						}
Silas Davis's avatar
Silas Davis committed
						if exe.eventCache != nil {
Silas Davis's avatar
Silas Davis committed
							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
Silas Davis's avatar
Silas Davis committed
						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)
Silas Davis's avatar
Silas Davis committed
						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
Silas Davis's avatar
Silas Davis committed
						signBytes := acm.SignBytes(exe.chainID, tx)
						if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) {
							return txs.ErrTxInvalidSignature
						}

						// tx.Height must be in a suitable range
Silas Davis's avatar
Silas Davis committed
						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)
Silas Davis's avatar
Silas Davis committed
						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)
Silas Davis's avatar
Silas Davis committed
		if err != nil {
			return err
		}
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find input account",
				"tx_input", tx.Input)
		err = tx.PermArgs.EnsureValid()
		if err != nil {
			return fmt.Errorf("PermissionsTx received containing invalid PermArgs: %v", err)
		}

Silas Davis's avatar
Silas Davis committed
		permFlag := tx.PermArgs.PermFlag
		if !HasPermission(exe.stateCache, inAcc, permFlag, logger) {
Silas Davis's avatar
Silas Davis committed
			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 {
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("Cannot find public key for input account",
				"tx_input", tx.Input)
Silas Davis's avatar
Silas Davis committed
		signBytes := acm.SignBytes(exe.chainID, tx)
		err = validateInput(inAcc, signBytes, tx.Input)
Silas Davis's avatar
Silas Davis committed
			logger.InfoMsg("validateInput failed",
				"tx_input", tx.Input,
Silas Davis's avatar
Silas Davis committed
		logger.TraceMsg("New PermissionsTx",
			"perm_args", tx.PermArgs.String())
Silas Davis's avatar
Silas Davis committed
		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")
Silas Davis's avatar
Silas Davis committed
		case permission.SetBase:
			permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
Silas Davis's avatar
Silas Davis committed
				func(perms *ptypes.AccountPermissions) error {
					return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value)
Silas Davis's avatar
Silas Davis committed
				})
		case permission.UnsetBase:
			permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
Silas Davis's avatar
Silas Davis committed
				func(perms *ptypes.AccountPermissions) error {
					return perms.Base.Unset(*tx.PermArgs.Permission)
Silas Davis's avatar
Silas Davis committed
				})
		case permission.SetGlobal:
			permAcc, err = mutatePermissions(exe.stateCache, acm.GlobalPermissionsAddress,
Silas Davis's avatar
Silas Davis committed
				func(perms *ptypes.AccountPermissions) error {
					return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value)
Silas Davis's avatar
Silas Davis committed
				})
		case permission.HasRole:
			return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
Silas Davis's avatar
Silas Davis committed
		case permission.AddRole:
			permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
Silas Davis's avatar
Silas Davis committed
				func(perms *ptypes.AccountPermissions) error {
					if !perms.AddRole(*tx.PermArgs.Role) {
						return fmt.Errorf("role (%s) already exists for account %s",
							*tx.PermArgs.Role, *tx.PermArgs.Address)
Silas Davis's avatar
Silas Davis committed
					}
					return nil
				})
		case permission.RemoveRole:
			permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address,
Silas Davis's avatar
Silas Davis committed
				func(perms *ptypes.AccountPermissions) error {
					if !perms.RmRole(*tx.PermArgs.Role) {
						return fmt.Errorf("role (%s) does not exist for account %s",
							*tx.PermArgs.Role, *tx.PermArgs.Address)
Silas Davis's avatar
Silas Davis committed
					}
					return nil
				})
			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!
Silas Davis's avatar
Silas Davis committed
		logger.TraceMsg("Incrementing sequence number for PermissionsTx",
			"tag", "sequence",
			"account", inAcc.Address(),
			"old_sequence", inAcc.Sequence(),
			"new_sequence", inAcc.Sequence()+1)
Silas Davis's avatar
Silas Davis committed
		inAcc.IncSequence()
		inAcc, err = inAcc.SubtractFromBalance(value)
		if err != nil {
			return err
		}
		exe.stateCache.UpdateAccount(inAcc)
			exe.stateCache.UpdateAccount(permAcc)
Silas Davis's avatar
Silas Davis committed
		if exe.eventCache != nil {
			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,
Silas Davis's avatar
Silas Davis committed
	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)