【以太坊剖析】以太坊虚拟机(EVM)之基本定义
以太坊虚拟机(EVM)
以太坊虚拟机(Ethereum Virtual Machine,简称 EVM)是一个基于栈的虚拟机,基于特定的环境数据,执行一系列的字节代码形式的指令,以修改系统状态。EVM 目前提供了 11 类,140 个指令。
EVM 是一个准图灵机,这个“准”的限定来源于其中的运算是通过参数 gas 来限制的,也就是限定了可以执行的运算总量。EVM 的具体逻辑定义为代码执行函数(Ξ):
其中,函数的参数部分,σ表示状态,g 表示可用 gas,I 表示执行环境数据;函数的返回值部分,σ'表示计算后的状态,g'表示剩余 gas,A 表示累积子状态,o 表示结果输出。
EVM 的具体实现逻辑包含:
基本定义: 对环境、指令集、EVM 状态的定义
合约代码执行: 将合约字节代码解析为一系列指令及其参数,并循环执行。
指令执行: 根据执行环境和 EVM 状态,对单条指令进行解析、计费、执行
指令解析: 获取将字节码解析为指令及参数
GAS 计费: 计算指令执行所要消耗的 gas 数量,并校验 gas 是否充足
栈操作: 出入栈操作,及其校验
内存操作: 读写内存操作,内存分配等
指令运算: 执行指令的具体逻辑
异常停止: 因 Gas 不足、栈溢出等问题导致的程序执行终止。
正常停止: 按照代码逻辑完成指令的执行,并正常退出和输出结果。
状态修改: 基于执行前的状态数据,完成 gas 支付、空账户清除(及其补贴用于抵扣部分 gas)等状态修改操作。
基本定义
指令集
目前,EVM 提供了 140 个指令。其中,以太坊黄皮书中定义了 135 个,包含特定的无效指令INVALID
;以太坊改进提案(Ethereum Improvement Proposals,下文中简称 EIPs)中定义了 5 个。
指令定义
每个指令的定义信息包括:
字节码 :或称指令编号,数值范围 0x00 - 0xff。
助记符 :或称指令名称
出栈数量
入栈数量
指令含义 :指令的基本描述、Gas 费用、运算逻辑,以及机器状态(栈、内存)的操作
指定定义示例
下面以加法算术指令ADD
指令为例,其指令定义为:
字节码:
0x01
出栈数量:
2
,即ADD
指令参数,被加数与加数入栈数量:
1
,即ADD
指令运算结果,被加数与加数之和Gas 费用级别:
VeryLowTier
,对应 gas 数量为3
指令类别
以太坊黄皮书中将指令划分为 11 个类别,且每类指令的编号的数值范围不同。比如,停止和算术运算类指令编号的数组区间为 0x00 - 0x0f(表示为 0s)。
指令类别及其部分指令:
0s:停止和算术运算
停止指令:
STOP
算术类(部分):
ADD
(加)、SUB
(减)、MUL
(乘)、DIV
(除)、MOD
(取模)、EXP
(指数)10s:比较和按位逻辑运算
比较逻辑运算(部分):
LT
(小于)、GT
(大于)、EQ
(等于)、ISZERO
(否,结合 EQ 指令实现不等于)按位逻辑运算:
AND
(与)、OR
(或)、NOT
(非)、XOR
(异或)20s:SHA3 运算,
SHA3
(Keccak-256 哈希)30s:环境信息(部分),
ADDRESS
(获取 I<sub>a</sub>)、GASPRICE
(获取 I<sub>p</sub>)、BALANCE
(获取给定账户余额)40s:区块信息
BLOCKHASH
:获取指定高度的祖先区块的头部哈希COINBASE
:获取该区块的矿工账户地址TIMESTAMP
:获取该区块的时间戳NUMBER
:获取区块的号码DIFFICULTY
:获得区块的难度GASLIMIT
:获得区块的 gas 上限50s:栈,内存,存储和流程操作
栈:
POP
(出栈)内存:
MLOAD
(从内存中加载字)、MSTORE
(将字保存到内存)、MSTORE8
(将字节保存到内存)、MSIZE
(获取活动内存的大小)存储:
SLOAD
(从存储加载字)、SSTORE
(将字保存到存储)流程控制:
JUMP
(跳转)、JUMPI
(有条件的跳转)、JUMPDEST
(μ<sub>pc</sub>加 1)、PC
(获取μ<sub>pc</sub>)、GAS
(获取μ<sub>g</sub>)60s & 70s: 入栈(Push)操作,
PUSH1
-PUSH32
80s: 复制(Duplicate)操作,
DUP1
-DUP16
90s: 替换(Exchange)操作,
SWAP1
-SWAP16
a0s: 日志操作,
LOG0
-LOG4
f0s: 系统操作(部分)
CREATE
:创建合约CALL
:消息调用合约CALLCODE
:使用指定帐户的代码对当前帐户进行消息调用RETURN
:停止执行,返回输出数据DELEGATECALL
:使用指定帐户的代码对当前帐户进行消息调用,但保留 sender 和 value 属性现有的值。INVALID
:保留的无效指令SELFDESTRUCT
:销毁合约
此外,还在 EIPs 中定义了如下指令:
EIP-145 中定义了 3 个按位逻辑运算(10s)
SHL
:0x1b,左移 (shift left)SHR
:0x1c,逻辑右移 (logical shift right)SAR
:0x1d,算术右移 (arithmetic shift right)EIP-1014 中定义了 1 个系统操作指令(f0s)
CREATE2
:0xf5,创建指定地址的合约账户EIP-1052 中定义了 1 个环境信息获取指令(30s)
EXTCODEHASH
:0x3f,获取合同代码的 keccak256 哈希
执行环境
执行环境数据,表示为I
,包含属性:
<code>I<sub>a</sub></code>,拥有正在执行的代码的账户地址,一般是指交易的发送方账户<code>S(T)</code>,在合约调用合约的情况下则不同。
<code>I<sub>o</sub></code>,触发这次执行的初始交易的发送者地址,初始交易是指由外部账户发起的交易<code>S(T)</code>。
<code>I<sub>p</sub></code> ,触发这次执行的初始交易的 gas 价格。
<code>I<sub>d</sub></code> ,这次执行的输入数据字节数组;如果执行代理是一个交易,这就是交易数据。
<code>I<sub>s</sub></code>,触发这次执行的账户地址;如果执行代理是一个交易,则为交易发送者地址。
<code>I<sub>v</sub></code> ,作为这次执行的一部分传到当前账户的转账金额,以 Wei 为单位;如果执行代理是一个交易, 这就是交易的转账金额。
<code>I<sub>b</sub></code>,所要执行的 EVM 字节码数组。
<code>I<sub>H</sub></code>,当前区块的区块头。
<code>I<sub>e</sub></code>,当前消息调用或合约创建的深度(也就是当前已经被执行的
CALL
或CREATE
的数量)。<code>I<sub>w</sub></code>,修改状态的许可。
这里我们来回顾一下交易数据与待执行代码的对照关系:
消息调用交易
<code>I<sub>b</sub></code>:即交易的被调用合约<code>T<sub>t</sub></code>,存储在世界状态树中的合约代码<code>σ[T<sub>t</sub>]<sub>c</sub></code>
<code>I<sub>d</sub></code>:交易的消息调用输入数据<code>T<sub>d</sub></code>,存储在交易附言(Transaction.data)属性。
合约创建交易
<code>I<sub>b</sub></code>:即交易的合约初始化代码<code>T<sub>i</sub></code>,也存储在交易附言(Transaction.data)属性。
<code>I<sub>d</sub></code>:此类交易无输入数据
EVM 状态
EVM 状态,也称机器状态(Machine State),表示为μ
。
可用 Gas
可用 gas,表示为<code>μ<sub>g</sub></code>,根据交易 gas 上限和已用 gas 数量计算:gasLimit - gasUsed。
程序计数器
程序计数器,表示为<code>μ<sub>pc</sub></code>,初始值为 0。
内存
内存,表示为<code>μ<sub>m</sub></code>,内存模型是基于字寻址(word-addressed)的字节数组,所有内存中的数据都会初始化为 0。
内存已激活字数
内存已激活字数,表示为μ<sub>i</sub>,初始值为 0。
栈
栈,表示为<code>μ<sub>s</sub></code>,其中栈中数据项的大小(即字长)是 256 位,最大深度为 1024,初始值为空序列。
结果数据
主要是指消息调用指令的结果输出数据,表示为<code>μ<sub>o</sub></code>,初始值为空序列。
交易子状态
交易执行过程中会产生一些特定的信息,记录交易相关的账户、日志等内容,也称为交易子状态或累积子状态。
自销毁账户集合
合约代码执行过程中,通过SELFDESTRUCT
指令销毁账户的地址的集合,表示为<code>A<sub>s</sub></code>,初始值为空集合。
接触账户集合
交易执行过程中所接触账户的地址的集合,表示为<code>A<sub>t</sub></code>,初始值为空集合。此集合的特征是交易执行过程中账户状态发生过变化,包含新创建的合约账户、消息调用接收方账户、销毁账户余额的继承账户等,交易执行完成时将删除其中的空账户。
日志集合
合约代码执行中,输出的一系列带有主题和数据内容的日志,并且记录了输出日志的账户地址,表示为<code>A<sub>l</sub></code>,初始值为空。其中,日志的所属账户地址和主题可以作为检索条件,通过区块头部和交易收据中的 Bloom 过滤器,实现区块、交易二级索引的信息查询。合约开发者通过日志,可以获取合约执行的详细状态,方便合约的上层应用实现一些类似于异步通知或回调功能,为应用最终用户提供更加友好的体验。
返还 Gas 数量
以太坊为了鼓励合约开发者尽可能少的占用计算资源,对于释放已占用资源的操作给予一定的 gas 补贴以抵扣部分交易费用,如通过SSTORE
指令释放合约存储资源,通过SELFDESTRUCT
指令清空指定账户的状态数据。合约代码执行中,将根据约定计算返还 gas 数量,表示为<code>A<sub>r</sub></code>,初始值为 0。
版权声明: 本文为 InfoQ 作者【秦鹏飞】的原创文章。
原文链接:【http://xie.infoq.cn/article/a0def5995690f8719b0f3d594】。未经作者许可,禁止转载。
评论