以太坊EVM源码注释之执行流程

以太坊EVM源码分析之执行流程

业务流程概述

EVM是用来执行智能合约的。输入一笔交易,内部会将之转换成一个Message对象,传入 EVM 执行。在合约中,msg 全局变量记录了附带当前合约的交易的信息,可能是为了一致,这里也将transaction转换成Message传给 EVM 对象。
EVM业务流程

如果是普通转账交易,执行时完全不需要EVM的操作(EVM进行的是空操作),直接修改 StateDB 中对应的账户余额即可。
如果是智能合约的创建或者调用,则通过 EVM 中的解释器加载和执行字节码,执行过程中可能会查询或者修改StateDB。[6]
Bootstrap of EVM in Geth
接下来我们按照这个顺序分析源码。

Create EVM

core/state_processor.go

// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
// ApplyTransaction尝试将事务应用于给定的状态数据库,并为其环境使用输入参数。
// 它返回事务的receipt、使用的gas,如果事务失败,返回一个错误指示块无效。
// 将交易的信息记录到以太坊状态数据库(state.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, error) {
   
	// 将交易转化为message,在合约中,msg 全局变量记录了附带当前合约的交易信息, 可能是为了一致,这里也将`transaction`转换成`Message`传给 EVM 对象。
	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
	if err != nil {
   
		return nil, err
	}
	// Create a new context to be used in the EVM environment
	// 创建一个要在EVM环境中使用的上下文
	context := NewEVMContext(msg, header, bc, author)
	// Create a new environment which holds all relevant information
	// about the transaction and calling mechanisms.
	// 创建一个新环境,其中包含关于事务和调用机制的所有相关信息。
	vmenv := vm.NewEVM(context, statedb, config, cfg)
	// Apply the transaction to the current state (included in the env)
	// 将事务应用于当前状态
	_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
	if err != nil {
   
		return nil, err
	}
	// Update the state with pending changes
	// 使用挂起的更改更新状态
	var root []byte
	// 根据版本采用不同的状态更新方法
	if config.IsByzantium(header.Number) {
   
		statedb.Finalise(true)
	} else {
   
		root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
	}
	*usedGas += gas

	// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
	// based on the eip phase, we're passing whether the root  accounts.
	// 为交易创建一个新的receipt,存储中间根和基于eip阶段交易使用的gas,我们正在传递是否根touch-delete帐户。
	receipt := types.NewReceipt(root, failed, *usedGas)
	receipt.TxHash = tx.Hash()
	receipt.GasUsed = gas
	// if the transaction created a contract, store the creation address in the receipt.
	// 如果事务创建了一个合约,则将创建地址存储在receipt中。
	if msg.To() == nil {
   
		receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
	}
	// Set the receipt logs and create a bloom for filtering
	// 设置receipt日志并创建用于过滤的bloom
	receipt.Logs = statedb.GetLogs(tx.Hash())
	receipt.Bloom = types.CreateBloom(types.Receipts{
   receipt})
	receipt.BlockHash = statedb.BlockHash()
	receipt.BlockNumber = header.Number
	receipt.TransactionIndex = uint(statedb.TxIndex())

	return receipt, err
}

这个函数首先将transaction转换成了Message,然后创建了一个Context,接下来调用vm.NewEVM创建了新的EVM,通过ApplyMessage执行相关功能。也就是说每处理一笔交易,就要创建一个 EVM 来执行交易中的数据。执行完成后,该函数更新状态、创建receipt以及进行日志记录。ApplyMessage只有一行代码,调用了StateTransition.TransitionDb函数。

状态转换模型

core/state_tansaction.go

/*
The State Transitioning Model 状态转换模型

A state transition is a change made when a transaction is applied to the current world state
The state transitioning model does all the necessary work to work out a valid new state root.
状态转换是将事务应用到当前世界状态(world state)时所做的更改。状态转换模型执行所有必要的工作,以计算出一个有效的新状态根。

1) Nonce handling 处理Nonce
2) Pre pay gas  提前支付gas
3) Create a new state object if the recipient is \0*32  如果接收方是\0*32,则创建一个新的stateObject
4) Value transfer 价值转移
== If contract creation 如果是合约创建 ==
  4a) Attempt to run transaction data  尝试运行事务数据
  4b) If valid, use result as code for the new state object 如果有效,则使用result作为新stateObject的代码
== end ==
5) Run Script section 运行脚本部分
6) Derive new state root 导出新状态根
*/
// 记录了在处理一笔交易过程中的状态数据,比如 gas 的花费等
type StateTransition struct {
   
	gp         *GasPool
	msg        Message
	gas        uint64 // 此交易过程当前剩余的gas
	gasPrice   *big.Int
	initialGas uint64 // 此交易初始的gas,即消息发送者指定用于此交易的gas量
	value      *big.Int
	data       []byte
	state      vm.StateDB
	evm        *vm.EVM
}

// TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed.
// An error indicates a consensus issue.
// TransitionDb将通过应用当前消息来转换状态并返回结果(包括使用的gas)。如果失败,它将返回一个错误。错误表示存在共识问题。
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
   
	if err = st.preCheck(); err != nil {
   
		return
	}
	msg := st.msg
	sender := vm.AccountRef(msg.From())
	homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
	istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
	// 判断是否是创建新合约
	contractCreation := msg.To() == nil

	// Pay intrinsic gas
	// 支付固定gas
	gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
	if err != nil {
   
		return nil, 0, false, err
	}
	if err = st.useGas(gas); err != nil {
   
		return nil, 0, false, err
	}

	var (
		evm = st.evm
		// vm errors do not effect consensus and are therefor
		// not assigned to err, except for insufficient balance
		// error.
		// vm错误不会影响共识,因此不会被分配为err,除非余额不足。
		vmerr error
	)
	if contractCreation {
   
		// 调用evm.Create创建合约
		ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
	} else {
   
		// Increment the nonce for the next transaction
		// 为下一个事务增加nonce
		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)
		// The only possible consensus-error would be if there wasn't
		// sufficient balance to make the transfer happen. The first
		// balance transfer may never fail.
		// 唯一可能的共识错误是,如果没有足够的余额进行交易。
		if vmerr == vm.ErrInsufficientBalance {
   
			return nil, 0, false, vmerr
		}
	}
	// 返还gas,并将已消耗的 gas 计入矿工账户中
	st.refundGas()
	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
}

StateTransition.TransitionDb()方法首先调用 StateTransaction.preCheck 验证交易的 Nonce 值,并从交易的发送者账户余额扣除gasLimit*gasPrice,用来「购买」交易执行需要的 gas。具体可参考gas的详细介绍
然后先将交易的固有成本扣除。发送一笔交易的gas包含两部分:固有成本和执行成本。
执行成本根据该交易需要使用多少EVM的资源来运算而定,执行一笔交易所需的操作越多,则它的执行成本就越高。
固有成本(intrinsic gas)由交易的基础成本(base fee)和负载(payload)决定,每个零字节4 gas,非零字节68 gas。交易负载分为以下三种负载:

  • 若是创建智能合约,则负载就是创建智能合约的 EVM 代码
  • 若是调用智能合约的函数,则负载就是执行消息的输入数据
  • 若只是单纯在两个账户间转账,则负载为空

接下来判断当前的交易是否是创建合约,根据交易的接收者是否为空来判断。如果需要创建合约,则调用EVM.Create进行创建;如果不是,则调用EVM.Call执行合约代码。(如果是转账交易,接收者肯定不为空,那么就会调用 EVM.Call 实现转账功能)[5]。
EVM 对象执行完相关功能后,调用 StateTransaction.refundGas 将未用完的 gas 还给交易的发送者。然后将消耗的 gas 计入矿工账户中。

创建合约

/core/vm/evm.go

// create creates a new contract using code as deployment code.
// create使用code作为部署代码创建一个新合约
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
   
	// Depth check execution. Fail if we're trying to execute above the
	// limit.
	// 检查合约创建的递归调用次数。若超过深度限制(1024)执行代码,则失败。
	if evm.depth > 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值