15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 以太坊源码分析 虚拟机 EVM

以太坊源码分析 虚拟机 EVM

时间:2023-07-12 15:12:01 | 来源:网站运营

时间:2023-07-12 15:12:01 来源:网站运营

以太坊源码分析 虚拟机 EVM:

以太坊虚拟机,简称EVM,用来执行以太坊上的交易。

EVM 的业务流程参见下图:







输入一笔交易,内部会转换成一个Message对象,传入EVM执行。

如果是一笔普通转账交易,那么直接修改StateDB中对应的账户余额即可。

如果是智能合约的创建或者调用,则通过EVM中的解释器加载和执行字节码,执行过程中可能会查询或者修改StateDB。

1. 固定油费(Intrinsic Gas)

每笔交易过来,不管三七二十一先需要收取一笔固定油费,计算方法如下:







如果你的交易不带额外数据(Payload),比如普通转账,那么需要收取21000的油费。

如果你的交易携带额外数据,那么这部分数据也是需要收费的,具体来说是按字节收费:字节为0的收4块,字节不为0收68块,所以你会看到很多做合约优化的,目的就是减少数据中不为0的字节数量,从而降低油费消耗。

2. 生成Contract对象

交易会被转换成一个Message对象传入EVM,而EVM则会根据Message生成一个Contract对象以便后续执行:







可以看到,Contract中会根据合约地址,从StateDB中加载对应的代码,后面就可以送入解释器执行了。

另外,执行合约能够消耗的油费有一个上限,就是节点配置的每个区块能够容纳的GasLimit。

3.送入解释器执行

代码跟输入都有了,就可以送入解释器执行了。EVM是基于栈的虚拟机,解释器中需要操作四大组件:







具体解释执行的流程参见下图:





EVM的每条指令称为一个OpCode,占用一个字节,所以指令集最多不超过256,具体描述参见:https://ethervm.io。比如下图就是一个示例(PUSH1=0x60, MSTORE=0x52):



首先PC会从合约代码中读取一个OpCode,然后从一个JumpTable中检索出对应的operation,也就是与其相关联的函数集合。接下来会计算该操作需要消耗的油费,如果油费耗光则执行失败,返回ErrOutOfGas错误。如果油费充足,则调用execute()执行该指令,根据指令类型的不同,会分别对Stack、Memory或者StateDB进行读写操作。

4. 调用合约函数

前面分析完了EVM解释执行的主要流程,可能有些同学会问:那么EVM怎么知道交易想调用的是合约里的哪个函数呢?别急,前面提到跟合约代码一起送到解释器里的还有一个Input,而这个Input数据是由交易提供的。





Input数据通常分为两个部分:

前面 4 个字节被称为“4-byte signature”,是某个函数签名的 Keccak 哈希值的前4个字节,作为该函数的唯一标识。(查询目前所有的函数签名:https://www.4byte.directory

后面跟的就是调用该函数需要提供的参数了,长度不定。

举个例子:我在部署完A合约后,调用add(1)对应的Input数据是0x87db03b70000000000000000000000000000000000000000000000000000000000000001

而在我们编译智能合约的时候,编译器会自动在生成的字节码的最前面增加一段函数选择逻辑:

首先通过CALLDATALOAD指令将“4-byte signature”压入堆栈中,然后依次跟该合约中包含的函数进行比对,如果匹配则调用JUMPI指令跳入该段代码继续执行。

这么讲可能有点抽象,我们可以看一看上图中的合约对应的反汇编代码就一目了然了:








这里提到了CALLDATALOAD,就顺便讲一下数据加载相关的指令,一共有4种:

最后一个EXTCODECOPY不太常用,一般是为了审计第三方合约的字节码是否符合规范,消耗的gas一般也比较多。这些指令对应的操作如下图所示:







5. 合约调用合约

合约内部调用另外一个合约,有4种调用方式:

后面会专门写篇文章比较它们的异同,这里先以最简单的CALL为例,调用流程如下图所示:





可以看到,调用者把调用参数存储在内存中,然后执行CALL指令。

CALL指令执行时会创建新的Contract对象,并以内存中的调用参数作为其Input。

解释器会为新合约的执行创建新的Stack和Memory,从而不会破环原合约的执行环境。

新合约执行完成后,通过RETURN指令把执行结果写入之前指定的内存地址,然后原合约继续向后执行。

6. 创建合约

前面都是讨论的合约调用,那么创建合约的流程时怎么样的呢?

如果某一笔交易的to地址为nil,则表明该交易是用于创建智能合约的。

首先需要创建合约地址,采用下面的计算公式:Keccak(RLP(call_addr, nonce))[:12]。也就是说,对交易发起人的地址和nonce进行RLP编码,再算出Keccak哈希值,取后20个字节作为该合约的地址。

下一步就是根据合约地址创建对应的stateObject,然后存储交易中包含的合约代码。该合约的所有状态变化会存储在一个storage trie中,最终以Key-Value的形式存储到StateDB中。代码一经存储则无法改变,而storage trie中的内容则是可以通过调用合约进行修改的,比如通过SSTORE指令。







7.油费计算

最后啰嗦一下油费的计算,计算公式基本上是根据以太坊黄皮书中的定义:http://gavwood.com/paper.pdf





当然你可以直接read the fucking code,代码位于core/vm/gas.go和core/vm/gas_table.go中。

智能合约开发、审计点击联系

关键词:虚拟,分析

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭