以太坊虚拟机(EVM)
- EVM是智能合约的运行环境
- 作为区块验证协议的一部分,参与网络的每个节点都会运行EVM,审查节点会检查验证正在验证的区块中列出的交易,并运行EVM中交易触发的代码
- EVM是沙盒封装的,并且是完全隔离的,即EVM中运行的代码是无法访问网络、文件系统和其他的进程,甚至合约之间的访问也是相互受到限制
- 合约以字节码格式存在于区块链上
- 合约通常以高级语言(solidity)编写,通过EVM编译器编译为字节码,最终通过客户端部署到区块链网络中
EVM和账户
- 以太坊中有两类账户:外部账户和合约账户,他们共用EVM中同一个地址空间
- 无论账户是否存储代码,两类账户对于EVM来说处理方式是一样的
- 每个账户在EVM中都有一个键值队形式的持久化存储。其中key和value的长度都是256位,称之为存储空间(storage)
EVM和交易
- 交易是指一个账户发送到另一个账户的消息,消息包含二进制数据(payload)和以太币
- 如果目标账户含有代码,这个代码就会在EVM中执行,并以payload作为入参,这就是合约的调用
- 如果目标账户是零账户(账户地址是0x000。。。),这个交易就会创建一个一个新合约,这个用来创建合约的交易的payload会被转换为EVM字节码并执行,执行的输出作为合约代码永久存储
EVM和gas
- 合约被交易触发调用时,指令会在全网的每个节点上执行,这需要消耗算力成本,每一个指令的执行都会有特定的消耗,gas是用来量化表示这个成本的消耗
- 一经创建,每一笔交易都会按照一定数量的gas预支付一笔费用,目的是限制交易所需要的工作量和为交易支付手续费
- EVM执行交易的时候,gas会按照特定的规则逐渐耗尽
- gas price是交易发送者设置的一个数值,作为发送者预先支付的手续费的单价,如果交易完成之后,gas还有剩余,会原路返回
- 无论执行到什么位置,一旦gas被耗尽(比如降为负值)将会触发out-of-gas的异常,当前调用帧(call frame)所做的所有状态修改就会回滚
EVM数据存储
storage
- 每一个账户都会有一个持久化的存储空间,称之为storage,这是一个将256位字映射到256位的key-value存储区,可以理解为合约的数据库
- 永久存储在区块链中,由于永久保存合约状态变量,因此读写的gas开销也是最大的
Memory(内存)
- 每一次消息的调用,合约会临时获取一块干净的内存空间
- 生命周期仅为整个方法的执行期间,函数的调用回收,因为仅仅保存临时变量,所以gas的开销较小
Stack(栈)
- EVM不是基于寄存器的,而是基于栈的,因此所有的计算都是在一个被称为栈(stack)的区域执行
- 存放部分局部值类型的变量,几乎免费使用的内存,但是会有数量的限制
EVM指令集
- 所有指令都是针对“256位的字”这个基本的数据类型来进行操作
- 具备常用的算数、位、逻辑和比较操作,也可以做到有条件和无条件跳转
- 合约可以访问当前区块的相关属性,比如区块的高度和时间戳
消息调用(Massage calls)
- 合约可以通过消息调用的方式来调用其它合约或者发送以太币到非合约账户
- 合约可以决定在其内部的消息调用中,对于剩余的gas,应该发送和保留多少
- 如果在内部消息调用时发生了out-of-gas异常(或者其它任何的异常),这将由一个被压入栈顶的错误值所指明;此时,只有与该内部消息调用一起发送的gas会被消耗掉
委托调用(Delegatecall)
- 一种特殊类型的消息调用
- 目标地址的代码将会在发起调用的合约的上下文中执行,并且msg.sender和msg.value不会变
- 可以由实现“库”(library):可以反复使用的代码库放到一个合约的存储上,通过委托调用引入相应代码
合约的创建和自毁
- 通过一个特殊的消息调用create calls,合约可以创建其它合约(不是简简单单的调用零地址)
- 合约代码从区块链上移除的唯一的方式就是合约在合约地址上执行自毁操作selfdestruct;合约账户剩余的以太币会发送指定的目标,然后其存储和代码都会从状态中被移除