首页 资讯 正文

无版本区别的EVM(以太坊智能合约虚拟机)

以太坊爱好者 2019年09月09日 00:15

编者注:本文为 Parity 开发者 Wei Tang 写作的,关于如何增强 EVM 后向兼容性的文章,改进 Gas 机制的方案堪称大胆。

如果我们有机会可以重新设计 EVM、提升其后向兼容性、让它对功能升级更为友好,而且可以完全不必考虑我们现有的历史包袱,我们该怎么做呢?

在这篇文章中,我会探究这个问题,并记录下由此演化出来的技术说明和设计哲学。

目标

Web 是没有版本区别的,而且已经存在了几十年。因此我在此假设,我们想做一个同样没有版本区别的 EVM。

我们同样希望保证,这种虚拟机具备良好的后向兼容性(这是一种必需的属性)。也就是说,至少能良好兼容我们现有的合约(在当前的 EVM 中,甚至单个操作码的 Gas 耗费量变更都有可能破坏现有的合约),而且,也可以轻松加入新功能。

无效操作码

要设计一个永续的 EVM,最简单可能也最重要的改动便是为合约部署添加一个验证过程。并非所有的字节序列都是有效的 EVM 代码,任何无效的操作码都不应该被部署到链上,因为在未来,这些代码可能会被分配以一个新的操作码,有不一样的功能。

此种检查的技术详述初次成文化是在 EIP-1712 中。简要来说,在执行合约创建的状态转变函数(或者说给状态添加合约代码)之前,执行下列检查:

  • 遍历代码的字节码

  • 如果代码是一个 PUSH(n) 操作码,则跳过接下来 n 个字节

  • 如果字节码是一个有效的操作码,或者指定了无效指令(0xfe),继续

  • 否则,捕捉到错误(trap)

上述检查有点类似于 jump destination 检查。注意,对于例外情形,我们在这里使用的是 “trap”,下文我们会详细解释。

功能调查(Feature Probe)

如果 EVM 要消弭掉版本的差别,基于 EVM 的代码执行应有能力调查出底层环境是否支持一种特定的功能。给定 EVM 所承担的角色,我们总是希望一个已经定义好的操作码的功能可以保持不变,并且还可以引入新的操作码来添加功能。而一些合约可能在引入某些特定功能之前就已经部署上去了。这些合约可以安排一个备用的子程序,在 EVM 不支持某功能的时候就运行子程序,而一旦硬分叉激活后就立即开始使用新功能。功能调查组件就像这里要用到的跳转器。因此,我们正式地定义一种新的操作码 HAS_FEATURE

  • 该操作码接收一个堆栈参数。它会检查该参数是否位于 02^8 之间,如果不是,就捕捉错误

  • 如果参数(解释为一种操作码)不受支持,就把 0x0 推回栈中;否则就推入 0x1

例外与捕捉(Exceptions and Trap)

在 EVM 的运行过程中,可能有很多因素会导致执行失败(fail)。单个交易可能因为耗尽 Gas 而失败;调用栈中的每一层都可能单独失败,而其错误必须被父调用框架明确处理。这些特性给了我们一定的弹性,但对于要运行在区块链上的合约来说,并不必然就是好事。这里,我们想重新定义一下,任何 EVM 本身发出的异常,都可以有 trap 行为,作为对 fail 的替代。也就是说,所有调用框架的所有执行过程中、消耗任意 gas 的时候、甚至被当前的状态函数回滚变更的时候,都可以有 trap。合约接下来就被会鼓励使用返回值,在它们想跟父调用者交流非致命错误的时候。

Gas 消耗量

过去的经验已经证明,我们总是想调整 Gas 消耗量。因为我们要这样做,我们不希望合约开发者对交易的 Gas 消耗量甚至是任何操作码的 Gas 消耗量作任何假设。要实现这一点,只需将 EVM 内所有关于 Gas 消耗量的公开信息都移除。这样 Gas 消耗量就成了一个外在于 EVM、被隐藏起来的 “实现上的细节”,只需在区块层执行中妥善处理。正式地移除 0x5aGas 操作码。此外,重新定义 CALLCALLCODEDELEGATECALL ,不再使用 gas 栈参数,而是采取现有执行框架中所有可用的 Gas。

(完)


原文链接:

https://that.world/~essay/nevm/

作者: Wei Tang

翻译: 阿剑