以太坊源码解读(18)以太坊交易执行流程

之前分析挖矿模块,miner从TxPool拿来的交易,交给worker对象。后者要调用commitTransaction在本地执行交易,生成receipt,更改世界状态,打包成挖矿的block最后递交给engine进行挖矿。而这一节我们关注的是在commitTransaction中,如何在本地执行交易。

func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
	snap := w.current.state.Snapshot()

	receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})
	if err != nil {
		w.current.state.RevertToSnapshot(snap)
		return nil, err
	}
	w.current.txs = append(w.current.txs, tx)
	w.current.receipts = append(w.current.receipts, receipt)

	return receipt.Logs, nil
}

core.ApplyTransaction就是执行交易的入口,而交易的执行就离不开大名鼎鼎的以太坊虚拟机。

一、ApplyTransaction函数

该函数的调用有两种情况:
1、是在将区块插入区块链前需要验证区块合法性
bc.insertChain ——> bc.processor.Process  ——> stateProcessor.Process ——> ApplyTransaction
2、是worker挖矿过程中执行交易时
Worker.commitTransaction ——> ApplyTransaction

主要功能是:将交易转化成Message,创建EVM对象,调用ApplyMessage执行交易,生成日志对象;
1、将交易转换成Message;
2、初始化一个EVM的执行环境;
3、执行交易,改变stateDB世界状态,然后生成收据

func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
        // 转换一个Message对象
	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
	if err != nil {
		return nil, 0, err
	}
	// 使用message初始化一个执行上下文
	context := NewEVMContext(msg, header, bc, author)

	// 使用上下文初始化一个EVM执行环境
	vmenv := vm.NewEVM(context, statedb, config, cfg)

	// 执行交易,返回使用的gas数量
	_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
	if err != nil {
		return nil, 0, err
	}
	// 更改stateDB世界状态
	var root []byte
        // 如果是拜占庭硬分叉,清理世界状态
	if config.IsByzantium(header.Number) {
		statedb.Finalise(true)
	} else {
        // 否则计算状态树根,用于产生收据
		root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
	}
        // 增加header中的usedGas
	*usedGas += gas

	// 用root和usedGas产生收据,即收据里包含了该交易执行时的世界状态,这样方便进行校验
	receipt := types.NewReceipt(root, failed, *usedGas)
	receipt.TxHash = tx.Hash()
	receipt.GasUsed = gas
	// 如果交易创建了一个合约,要把合约地址放在收据里
	if msg.To() == nil {
		receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
	}
	// Set the receipt logs and create a bloom for filtering
	receipt.Logs = statedb.GetLogs(tx.Hash())
	receipt.Bloom = types.CreateBloom(types.Receipts{receipt})

	return receipt, gas, err
}

二、StateTransition.TransitionDb

交易工作环境(StateTransition)的数据结构如下:

type StateTransition struct {
	gp         *GasPool   // 区块工作环境中的gas剩余额度,就是header中的gasLimit
	msg        Message    // 交易转化的message
	gas        uint64     // 交易的gas余额,最开始等于initialGas,随着交易执行会递减
	gasPrice   *big.Int   
	initialGas uint64     // 初始gas,等于交易的gasLimit
	value      *big.Int   // 交易转账额度
	data       []byte     // 交易的input,如果是合约创建,data就是合约代码
	state      vm.StateDB // 状态树
	evm        *vm.EVM    // evm对象
}

这里gp是整个区块所有交易可用的gas,其实就是来自于header的gaslimit,而header的gasLimit是通过父区块的gasUsed推算出来的。initialGas是交易的gasLimit,gas是余额(等于initialGas减去交易usedGas)。

在ApplyMessage中,首先新建一个交易工作环境,然后紧接着调用TransitionDb方法:

func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
	return NewStateTransition(evm, msg, gp).TransitionDb()
}

TransitionDb()的主要功能是初始化交易工作环境,执行交易,然后处理交易执行前后的gas增减。

1、预先检查nonce和gas值,初始化交易工作环境的gas初始值;
2、计算并扣除固定gas消耗;
3、调用evm创建或执行交易;
4、奖励旷工;

func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
        // 先检查交易的nonce和当前状态nonce是否一致,然后调用buyGas;
        // buyGas是要从区块的gasPool中取出一定的gas用于执行交易
	if err = st.preCheck(); err != nil {
		return
	}
	msg := st.msg
	sender := vm.AccountRef(msg.From())
	homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
	contractCreation := msg.To() == nil

	// 计算并扣除固定的gas消耗
        // 固定的gas消耗(21000) + 非0值gas + 0值gas
	gas, err := IntrinsicGas(st.data, contractCreation, homestead)
	if err != nil {
		return nil, 0, false, err
	}
        // 更新剩余的gas值
	if err = st.useGas(gas); err != nil {
		return nil, 0, false, err
	}

	var (
		evm = st.evm
		vmerr error
	)
	if contractCreation {
                // 如果是合约创建建议,调用evm.Create
		ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
	} else {
		// 如果是交易或合约调用,则先设置交易发送方的nonce值+1
		st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
                // 调用evm.Call执行交易
		ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
	}
	if vmerr != nil {
		log.Debug("VM returned with error", "err", vmerr)
		// evm的错误不影响共识,当且仅当vmerr是ErrInsufficientBalance时,return的gasUsed等于0,此时不影响账户状态;
                // 否则,即使交易执行失败,gas还是会被扣除
		if vmerr == vm.ErrInsufficientBalance {
			return nil, 0, false, vmerr
		}
	}
        // 返回余额给发起方,它同时调整了StateTransition中gasPool的余额
	st.refundGas()
        // 奖励矿工,gas消耗
	st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))

	return ret, st.gasUsed(), vmerr != nil, err
}

交易执行之前,要调用bugGas()将initialGas和gas都设置为交易的gasLimit,并从总的gaspool里减去预支的gas(即gasLimit),交易发起者的账户也要减去相应的价值。

func (st *StateTransition) buyGas() error {
	mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
	if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
		return errInsufficientBalanceForGas
	}
	if err := st.gp.SubGas(st.msg.Gas()); err != nil {
		return err
	}
	st.gas += st.msg.Gas()

	st.initialGas = st.msg.Gas()
	st.state.SubBalance(st.msg.From(), mgval)
	return nil
}

这里实际上是preCheck中的内容,后面计算交易的具体gas后,还要调整gas的值:

func (st *StateTransition) useGas(amount uint64) error {
	if st.gas < amount {
		return vm.ErrOutOfGas
	}
	st.gas -= amount

	return nil
}

最后,如果实际使用的gas超过了gasLimit,即initialGas,就返回错误;如果顺利执行,则将剩余的gas返回原来的账户,同时可能还有一笔奖励给原账户(st.gas + refund)。最后,将st.gas即交易执行后的gas余额返回给交易执行环境(st.gp)。

func (st *StateTransition) refundGas() {
	// Apply refund counter, capped to half of the used gas.
	refund := st.gasUsed() / 2
	if refund > st.state.GetRefund() {
		refund = st.state.GetRefund()
	}
	st.gas += refund

	// Return ETH for remaining gas, exchanged at the original rate.
	remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
	st.state.AddBalance(st.msg.From(), remaining)

	// Also return remaining gas to the block gas counter so it is
	// available for the next transaction.
	st.gp.AddGas(st.gas)
}

执行过程中如果是合约创建交易,则调用evm.Create();如果是普通交易或调用智能合约,则调用evm.Call()方法。

if contractCreation {
    // 如果是合约创建建议,调用evm.Create
    ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
    // 如果是交易或合约调用,则先设置交易发送方的nonce值+1
    st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
    // 调用evm.Call执行交易
    ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)

 

我们要知道,evm解释器的执行上下文是stateTransition,是交易,但evm的服务对象是智能合约。智能合约与evm的解释器有紧密的联系,所以我们这一节先从智能合约的创建和执行开始学习。 ...