EVM源码分析(二)
author:Thomas_Xu
在上一文中我们分析了EVM的代码架构以及主要的两个合约。而在交易交给EVM处理之前,其实还有一系列的数据转移操作。
这篇文章,我们将从交易入手,来看看以太坊究竟是怎么处理交易的。
从Geth客户端收到交易开始:
当一个geth客户端接收到其他节点提交的交易后,它会首先将这笔交易提交给evm进行处理。
commitTransaction
1 | // minner/worker.go |
一笔交易提交到EVM前的主要过程就是上述代码所描述的
- 创建当前stateDB的
snapshot
, 创建snapshot
其实就是将leveldb
的revisionId
自增1,然后将这个revisionId
加入到revisionId
列表里,然后返回创建的id。 - 将交易发送到evm,执行交易, 这步骤后面会重点分析,这个就是我们这次文章主要分析的重点EVM的执行交易过程。
- 判断执行结果是否出错,如果出错,则回滚snapshot。 首先找到在
revisionId
列表里面找到需要回滚的revisionId
, 然后将此revisionId
里面的所有snapshot
依次回滚。 - 将当前交易加入到交易列表
- 将交易收据加入到交易收据列表
Process
Process是个入口函数,所有的交易都需要经过此函数来运行调度。
1 | func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { |
ApplyTransaction
接下来我们主要分析ApplyTransaction函数
1 | // core/state_processor.go |
这里其实就出现了最核心的TransitionDb函数,我们后面会讲到
AsMessage 函数
1 | // core/types/transactions.go |
将tx 里面的数据填充到msg里面, 这个过程主要是将交易里面的form address 利用 ecrevoer函数恢复出来。
NewEVMContext 函数
1 | //core/vm/evm.go |
填充vm.Context的各项内容,并返回一个Context对象
NewEVM函数
1 | func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { |
ApplyMessage函数
1 | // ApplyMessage 通过给定的message计算新的DB状态,继而改变旧的DB状态 |
这个函数的分为两个函数执行一个是NewStateTransition 函数,这个函数主要是设置一些交易执行的必要参数。
TransitionDb 这个函数则是主要负责执行交易,影响Db状态。
TransitionDb 函数
1 | // core/state_transation.go |
preCheck 函数主要进行执行交易前的检查,目前包含下面两个步骤
1.1 检查msg 里面的nonce值与db里面存储的账户的nonce值是否一致。
1.2 buyGas方法主要是判断交易账户是否可以支付足够的gas执行交易,如果可以支付,则设置stateTransaction 的gas值 和 initialGas 值。并且从交易执行账户扣除相应的gas值。
先获取固定交易的基础费用,根据当前分叉版本和交易类型来决定基础费用,如果是创建合约则是至少是53000gas,如果是普通交易则至少是21000gas ,如果data部分不为空,则具体来说是按字节收费:零字节收4gas,零字节0收68gas(在EIP2028之后是16gas),所以你会看到很多做合约优化的,目的就是减少数据中不为0的字节数量,从而降低油费消耗。具体代码如下
1 | //IntrinsicGas 计算给定数据的固定gas消耗 |
true
...
...
This is copyright.