深入理解以太坊系列(8): 以太坊虚拟机(EVM)

以太坊设计了自己的虚拟机(EVM),用于执行交易代码,这是以太坊与其他系统的核心区别。EVM是图灵完备的,由于以太坊系统中引入了gas的概念,所以原则上,在EVM中可执行的计算总量受gas总量限制。EVM采用了基于栈(stack)的架构,也就是后进先出(LIFO)的方式。

在以太坊设计原理中描述了EVM的设计目标:

简单:操作码尽可能的少并且低级,数据类型尽可能少,虚拟机的结构尽可能少
结果明确:在VM规范语句中,没有任何可能产生歧义的空间,结果应该是完全确定的。此外,计算步骤应该是精确的,以便可以测量gas的消耗量;
节约空间:EVM组件应尽可能紧凑;
预期应用应具备专业化能力:在VM上构建的应用应能处理20字节的地址,以及32位的自定义加密值,拥有用于自定义加密的模数运算、读取区块和交易数据与状态交互等能力;
简单安全:为了让VM不被利用,应该能够容易地让建立一套gas消耗成本模型的操作;
优化友好:应该易于优化,以便即时编译(JIT)和VM的加速版本能够构建出来。

同时EVM也有如下特殊设计:

1. 区分临时存储(Memory,存在于VM的每个实例中,并在VM执行结束后消失)和永久存储(Storage,存在于区块链状态层)
2. 采用基于栈(stack)的架构
3. 单词(word)大小为32字节
4. 使用了可变、可扩展的内存大小
5. 栈大小没有限制
6. 1024调用深度限制
7. 没有类型

EVM有自己内含的语言——EVM操作码。类似其他高级语言,EVM也有自己的高级语言,当下流行的是Solidity和Viper,编译后可在EVM中执行。

通过使用高级语言编写合约,可以不必关心底层的操作,不过了解一下EVM操作码可以更好的理解能执行的运算。

算术运算:ADD,MUL,SUB,DIV,SDIV,MOD,SMOD,EXP…
逻辑运算:LT,GT,EQ,ISZERO,AND,XOR,OR,NOT…
业务功能:SHA3,ADDRESS,BALANCE,ORIGIN,CALLER,GASPRICE,LOG…

以下是以太坊设计原理中列出的较难理解的操作码,做了特别的解释:

ADDMOD, MULMOD 
大多数情况下,addmod(a, b, c) = a * b % c,但在椭圆曲线算法中,使用的是32字节模数运算,直接执行a * b % c 实际上是在执行((a * b) % 2^256) % c会得到完全不同的结果。计算公式a * b % c 使用32字节空间的32字节值是非常不常见且繁琐。

SIGNEXTEND
SIGNEXTEND操作码的作用是为了方便从大的有符号整数到小的有符号整数的类型转换。小的有符号整数是很有用的,因为未来的即时编译虚拟机可能会检测长时间运行的代码块,小的有符号整数能加快处理。

SHA3
在以太坊代码中SHA3作为安全的高强度的hash集合应用非常广泛,通常在使用存储器时需要使用Hash函数来确保安全,以防止恶意冲突,在验证默克尔树和类似以太坊的数据结构时也需要使用到Hash函数。重要的是,与SHA3的相似的hash函数,如SHA256,ECRECVOR,RIPEM160不是以操作码的形式包含在里面,而是以伪合约的形式。这样做的目的是将它们放在一个单独的类别中,如果当我们以后提出适当的“本地扩展”系统时,可以添加更多这样的合约,而不需要扩展操作码。

ORIGIN
ORIGIN操作码由交易的发送者提供,主要的作用是允许合约退回支付的gas。

COINBASE
COINBASE的主要作用是:1.如果使用COINBASE操作码,则允许子货币对网络安全作出贡献;2.开放使用矿工作为一个去中心化的经济体,来设置基于子共识的应用,如Schellingcoin。

PREVHASH
PREVHASH作为一个半安全的随机来源,允许合约评估上一个区块的默克尔树状态证明,而不需要高度复杂的递归结构“以太坊轻客户端”。

EXTCODESIZE, EXTCODECOPY
主要的作用是让合约依据模板检查其他合约的代码,甚至是在与其他合约交互前,模拟它们。

JUMPDEST
当跳转(jump)目的地限制在几个索引时(尤其是,动态目的跳转的计算复杂度是O(log(有效挑战目的数量)),而静态跳转总是恒定的),JIT虚拟机实现起来更简单。于是,我们需要:1.对有效变量跳转目的地做限制;2.使用静态而不是动态跳转的激励方式。为了达到这两个目标,我们定下了以下规则:1.紧接着push后的跳转可以跳到任何地方,而不仅是另一个jump;2.其他的jump只能跳转到JUMPDEST。对跳转的限制是必须的,你可以通过查看代码中的先前操作来决定是静态还是动态的跳转。缺乏对静态跳转的需求是激励使用它们的原因。禁止跳转进入push数据也会加快JIT虚拟机的编译和执行。

CALLCODE
该操作码允许合约使用自己的存储器,在单独的栈空间和内存中调用其他合约的“函数”。这样可以在区块链上灵活实现标准库代码。

SELFDESTRUCT
允许合约删除它自己,前提是它已经不需要存在了。SELFDESTRUCT并非立即执行,而是在交易执行完之后执行。这是因为这样做可以恢复那些执行后大大增加了缓存复杂度的SELFDESTRUCT操作码。

PC
尽管理论上不需要PC操作码,因为所有的PC操作码都可以根据将push操作的索引加入实际程序计数器来代替实现,但使用PC可以创建独立代码的位置(可复制粘贴到其他合约的编译函数,如果它们以不同索引结束,不要打断)。