关于以太坊虚拟机 EVM

一、什么是EVM

EVM是以太坊协议的一部分,它用来处理智能合约的部署和执行。除了在EOA(由用户私钥控制的所谓“外部账户”)之间的简单转账交易以外,其他所有涉及状态更新的操作都是通过EVM来计算的。从高层抽象的角度,运行在以太坊区块链上的EVM可以想象成一个包含了数百万可执行对象的全球化去中心化计算机,这些可执行对象都拥有它们各自的永久数据存储。

EVM是一个“准”图灵完备的状态机,因为在其中进行的任意智能合约的执行都必须限定在一个由可用的gas总量所限制的、有限的计算步骤数量之内。

EVM是一个基于栈的架构,在一个栈中保存了所有的内存数值。EVM的数据处理单位被定义为256位的“字”,并且它还有以下数据组件:

一个不可变的程序代码存储区ROM,加载了要执行的智能合约字节码。

一个内容可变的内存,它被严格初始化为全0的数值。

一个永久存储,它是作为以太坊状态的一部分而存在的,也会被初始化为0

 

二、与现有技术的比较

所谓“虚拟机”的概念通常用于对真实计算机的虚拟化,一般是通过一个像virtualbox或者QEMU这样的“管理程序”,或者像Linux上的KVM这样的完整操作系统实例来实现的。这些方案中都必须分别对实际的硬件、系统调用和其他内核功能提供一个软件抽象。

而EVM则运行在一个更局限的领域,它仅仅是一个计算引擎,仅仅提供对计算和存储的抽象。

就像Java虚拟机(JVM)那样。从高级视角来看,JVM的设计提供了一个无须知晓底层宿主OS或硬件的运行环境,从而提供了跨不同系统平台的兼容性。像Java、Scala(基于JVM)或者C#(基于.NET)这样的高级程序设计语言会被编译为与它们对应的虚拟机字节码指令集。 同样的,像LLL、SerPent、Mutan或solidity这样的高级智能合约开发语言,也会被编译为由EVM执行的字节码指令集

EVM没有可调度性,因为执行顺序是由外部所组织好的,也就是由以太坊客户端通过验证区块中的交易来决定哪些智能合约应该运行,以及他们的执行顺序应该是什么。从这个角度讲,以太坊世界计算机就像JavaScript引擎那样是“单线程”的。EVM既没有任何"系统接口”,也没有“硬件支持”,因为并没有任何物理机器需要与之交互。以太坊世界计算机是完全虚拟化的。

EVM指令集(字节码)

EVM指令有很多标准机器码指令组成,包含:算术和位运算逻辑操作;执行上下文查询;栈、内存和存储访问;处理流程操作;日志、跳转和其他操作

 

三、以太坊的状态

EVM的任务是基于以太坊协议、根据智能合约的代码的执行来计算合法的状态转换,用以更新以太坊的状态。 

这也就是将以太坊作为基于交易的状态机所描述的层面,同时也是外部用户(即账户持有人和矿工)通过创建、接受交易和对交易进行排序打包来引发状态转换所反映的层面。

在最高级,我们有以太坊世界状态的概念。世界状态是一个以太坊地址到账户数据的映射。

具体来说,每个以太坊账户地址所对应的账户数据都由以下几个部分组成:一个以太币余额,一个nonce, 账户的存储(仅供智能合约使用的永久数据存储)以及账户的程序代码(如果账户是智能合约账户),一个EOA永远不会有代码,且只有全空的存储。

当一个交易最终反映为一次智能合约代码的执行时,一个EVM实例会基于当前正在创建的区块信息和这个交易的信息被初始化出来。具体的说,就是会将调用的合约账户所对应的代码加载到EVM的ROM中,程序计数器置0,从调用的合约账户所对应的存储中加载存储数据,将内存清零并将于区块和其他环境变量相关的信息设置好。这个执行中的关键变量就是提供给这次执行的gas,这个gas被设定为原始交易开始时交易发送者支付的gas总量。在执行的过程中,gas的供给会基于操作执行的消耗相应减少。

无论何时,只要gas的供给减小到0,我们就会得到一个“out of gas”的异常,执行会立即终止,相应的交易也即失败。

把EVM的运行想象为将以太坊世界状态复制到一个沙盒中,如果执行因为任何原因没有完成,那么这个沙盒中的状态就会被丢弃。不过,如果执行成功结束,那么真正的世界状态就会被更新为这个沙盒的状态,包含所有对调用过的合约的存储数据的修改、新创建的合约以及其间所有以太币余额的转移。

请注意,因为智能合约可以自己产生交易,所以代码执行时一个递归的过程。一个合约可以调用其他合约,每个调用都会最终都会反映为另一个基于新目标的EVM执行。

每次EVM的实例化都会基于之前的EVM沙盒来构造。每次实例化也会获得一个指定数量的gas供给,它自己可能因为获得的gas过少而无法完成它的执行。在这种情况下,沙盒的状态就会被丢弃,执行会返回到上一层调用。

四、合约部署代码

 我们创建和部署一个新合约到以太坊平台上所使用的代码和合约代码本身有一个很重要但又很微妙的区别。

为了创建一个新合约,我们需要一个特殊的交易,其目标地址to字段需要被指定为0x0,并且它的data字段需要被设置为合约的初始化代码。

当这样一个合约创建交易被处理的时候,新合约账户的代码其实并不是交易数据的data字段所附带的代码。一个EVM实例会将这个交易所附带的data字段作为程序代码加载到其ROM中,并将这个部署代码的执行输出结果作为新合约账户的关联代码,通过这样的方式,新合约就可以使用部署时的以太坊世界状态,以编程方式被初始化,并更改合约的存储数据甚至发送以太币或者创建更多的新合约。

当我们编译一个合约,比如在命令行使用solc,就会获得部署字节码,也会获得运行时字节码。

使用部署字节码来执行初始化新合约账户的所有操作,包含实际交易调用这个新合约时需要执行的字节码(即运行时字节码)以及在合约的构造函数中进行初始化处理的代码。

换句话说,运行时字节码就是当新合约被调用时,所执行的所有字节码,仅此而已,其中并不包含需要在部署中使用的用来初始化合约的字节码。

 

五、图灵完备和gas

用简单的术语来解释,如果一个系统或者编程语言能够解决你交给它的所有问题,它就是图灵完备的。然而这种能力伴随着一个非常重要的警示:有些问题需要无限的资源去解决。

由于停机问题,以太坊世界计算机就有了一个被程序要求永远执行下去的风险。这可能是由于某些意外情况或者恶意的目的。我们曾讨论过以太坊就像是一个单线程的计算机,没有调度程序,所以它会被无限循环卡住,而这将会使得它变得不可用。

在有了gas之后,也就有了一个解决方案:如果在一个预先指定的最大计算量被用尽的时候计算还没有结束,那么所有的处理都会无条件地停止。这就使得EVM成为一个准图灵完备的机器:它可以解决你交给它的所有问题,但前提是这个问题可以在一定量的计算量内被解决。

 

六、区块的gas限制

区块的gas限制指的是一个区块中的所有交易总共能消耗的最大gas数量,它也限定了一个区块中能包含多少交易。

例如,我们假定有5个交易,它们的gas上限分别为30000、30000、40000、50000和50000。如果区块的gas限制是180000,那么任意四个交易都可以包含到一个区块中,第5个区块则需要等待后续的区块。如果是一个矿工尝试包含一个gas消耗超过区块gas限制的交易,那么这个区块将会被网络解决。

 

posted on 2020-04-22 10:44  ccbupt  阅读(1228)  评论(2编辑  收藏  举报

导航