以太坊源码解读(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的解释器有紧密的联系,所以我们这一节先从智能合约的创建和执行开始学习。 ...