以太坊2.0-Ewasm

  • PP
  • 更新于 2020-03-04 10:23
  • 阅读 7741

Ewasm 是以太坊2.0时代用于替换evm虚拟机的一个新的虚拟机实现,它汲取了webAssembly 的优势,将其应用到以太坊2.0虚拟机的实现,可以方便开发者利用多种语言实现智能合约并且运行在以太坊平台

以太坊2.0-Ewasm

Ewasm介绍

在介绍Ewasm之前,还是得先介绍下WebAssembly,

什么是webAssembly

WebAssembly(或Wasm)是一种新的,可移植的,节省大小和加载时间有效的字节码格式。WebAssembly旨在通过利用各种平台上可用的通用硬件功能以本机速度执行。WebAssembly目前被W3C社区组织设计为开放标准。

WebAssembly介绍WebAssembly设计WebAssembly指令集

一些基本的概念

  • WebAssembly定义了一个指令集,以及 中间源格式(wast) wast其实是一个wat 的改进版 ,这个是人类可以读的文件,并且可以进行更改,方便进行调试,和生成二进制编码格式(wasm)。

  • WebAssembly具有一些更高级别的功能,import export module,它的moudle导出后续应该也可以被javascript 可以使用,这样扩展性也就变得更强。

  • LLVM 包含一个WebAssembly后端来生成Wasm输出,这意味着只要能被llvm编译成wasm的语言,即可在浏览器等其他地方大放异彩。

  • wasm是可以在栈式虚拟机上运行的一种二进制指令格式,设计的首要目标是性能,很适合用在区块链项目中。

WebAssembly的优势

  • 可扩展性好

  • 32/64位的操作符

  • 支持更多的高级语言(C,C++, Rust,Go等)

  • 由W3C领导,Google,Apple,MicroSoft等大公司支持,更广阔的社区

  • 主流浏览器支持,已有项目支持在非浏览器环境运行(wabt,wavm等)

什么是Ewasm?

​ EVM 一直是被当做eth1.0存在的, 暂且抛开各种代码逻辑层面的问题和solidity的问题不说,就光是解释速度就能被无数人诟病,而Ewsam 则采用的 更加高效的wasm 指令集,它更加接近cpu指令集。Ewasm 是Eth2的一个重要模块,它将负责改变以太坊交易执行,交易合约语言独立的现状。它将变得更加高效,执行速度更快,而且有丰富的语言和社区支持。 Ewasm 其实是wasm 的一个子集,因为它限制了合约的执行环境必须在以太坊标准环境中,为了达到这样的要求,以及为了兼容EVM之前的合约,Ewasm目前的设计如下:(此设计后续会不断变换

VM语义

为了兼容EVM1的合约,Ewasm必须做到以下几点

  1. 无论什么时候从state 加载合约,必须检验合约签名。如果合约是当前的(新的),则执行当做Ewasm 合约执行,否则按照EVM1 合约执行

  2. 如果当前客户端不支持EVM1 ,则使用EVM Transcompiler 工具来将EVM1合约转换为Ewasm2合约,目前solidity还没有这样的成熟工具出现,之前有过一个evm2wasm项目,现在代码也一年多没更新了。因为官方在设计的时候发现的问题太多。社区里面有人提到说有可能会先转为YUL 代码,然后从YUL 转为wasm代码。

  3. 部署wasm合约时,要有一个预编译内置合约(合约名称Sentinel Contract)对合约进行检查,要求合约符合EVM2.0的要求,比如不能包括float类型,不能有限定外的操作。它还会对wasm 字节码进行注入gas消耗,然后将注入gas后的字节码返回。

Ewasm Contract Interface (ECI)

Ewasm 合约也是遵循wasm字节码规范的,可以认为它是wasm的一个子集

  • Ewasm合约只能导入以太坊标准环境接口(EEI)中指定的内容, 实际上,这意味着ewasm模块指定的所有导入必须来自ethereum命名空间,并且具有与EEI中指定的函数直接对应的函数签名和名称,这确保了以太坊合约执行始终是一个沙盒环境。
  • 有一个debug名称空间,可以进行信息打印用来合约调试,但在生产系统部署时必须去掉debug 命名空间。
  • 每个合约必须提供两个export方法,一个是main,供VM执行调用,一个是memory,供EEI调用,保存结果。
  • wasm module中的start function需要被disable。
  • Trap 如果执行wasm 代码触发了 wasm 陷阱,

Ewasm Environment Interface(EEI)

EEI 将以太坊环境的核心api暴露给Ewasm环境,这样Ewasm环境就可以通过api进行状态存取。以太坊的module 将以以太坊客户端的nvtive 语言实现。所有参数和返回值必须采用32或者64位 integers. 不允许出现浮点数。

EEI 定义了下面的数据类型和API,数据类型又分为以太坊数据类型和webAssembly数据类型

以太坊数据类型

  • bytes: 不定长bytes 数组

  • bytes32: 32字节定长数组

  • address: 20字节定长数组

  • u128: 128位无符号整数,小端存储

  • u256: 256位无符号整数,小端存储

webAssembly 数据类型

  • i32: 等同于webAssembly 中的i32
  • i32ptr: 等同于webAssembly 中的i32,但是是一个指针,指向webAssembly的内存偏移量
  • i64: 等同于webAssembly 中的i64

API

useGas 减掉消耗的gas

getAddress 获取正在执行合约的账户地址,并将其存储在给定的内存偏移量里面

getExternalBalance 获取给定地址余额,并将其存储在给定的内存偏移量中

getBlockHash 获取指定区块hash,并将其存储在给定的内存偏移量中

call 给定gas ,address, value, dataoffset, datalenght 进行函数调用

callDataCopy 将call的data拷贝进内存

getCallDataSize 获取call 的 data的大小

callCode 功能类似codedelegate,目前已经接近弃用

callDelegate 使用被调用者的代码进行调用其他合约,但是保留原始的调用者和value值

callStaic 将包含任意数据的消息发送到给定的地址路径,但不允许状态修改。这包括logcreateselfdestructcall具有非零值

storageStore 将内存中256位的一个值持久化进storage

storageLoad 从storage 中将256位的值load进内存

getCallValue 获取call 中的value

codeCopy 拷贝当前环境中的code进内存

getCodeSize 获取当前环境中的code的size

getBlockCoinbase 获取区块受益者地址,并加载进内存

create 根据指定值创建新合约

getBlockDifficulty 获取区块难度

externalCodeCopy 获取账户中的code

getExternalCodeSize 获取账户中的code size

getGasLeft 返回当前剩余gas

getBlockGasLimit 获取区块gas 限制

getTxGasPrice获取当前环境交易gas price

log 在当前环境下创建log

getBlockNumber 获取区块号

getTxOrigin 获取交易最开始账户地址,永远不可能是合约账户地址

finish 设置执行完成之后的返回数据,这将会立即终止执行

revert 设置执行完成之后的返回数据。这将立即停止执行并将执行结果设置为“恢复”。

getReturnDataSize 获取当前返回数据缓冲区的大小到内存。这包含从执行的最后一个返回的数据callcallCodecallDelegatecallStaticcreate

returnDataCopy 将当前返回数据缓冲区复制到内存。这包含从去年执行的返回据call

callCodecallDelegatecallStaticcreate

selfDestruct 将帐户标记为以后删除,并将剩余余额提供给指定的受益人地址。这将导致webAssmebly trap,执行将立即中止。

getBlockTimestamp 获取区块时间戳

以太坊系统合约

系统合约是定义为合约的接口,ewasm VM实现可以选择本地实现这些接口,也可以依赖于使用ewasm编写的实现。这些合约中的每一个都有一个预定义的地址,可以合约调用来执行。

哨兵合约

地址: 0x000000000000000000000000000000000000000a

前面提到每个新部署的ewasm合约必须经过哨兵合约进行处理,哨兵将执行三个非常重要的处理步骤:

  • 验证ewasm语义

  • 注入gas计量代码

  • 将处理后的代码返回给部署者

Evm 代码转换合约

地址:0x000000000000000000000000000000000000000b

它的主要作用就是将evm1.0的合约的bytecode转换为ewasm 合约的bytecode

以太坊预编译合约

以太坊黄皮书里面定义的EVM1.0的预编译合约也将同样适用于ewasm

Metring

给定一组操作和每个操作的相应成本集合,可以通过总计每个操作的执行成本来确定地运行任意数量的成本单位的计算。我们将成本单位称为gas,它作为计算时间的估计。

在wasm 中进行gas注入,这里有个专门项目进行实现

计量主要包括分支计量和内存消耗计量。

分支计量会提前定义好导致分支的一些操作,例如

const branching_ops = new Set(['end', 'br', 'br_table', 'br_if', 'if', 'else', 'return', 'loop']

还有一个 cost_table 记录了与之相对应的gas 消耗

cost_table = {
  'op': cost
}

内存计量:

内存计量主要是为初始分配内存以及分配增加内存进行计费

关于Metring 的详细设计可以看Metring

限制非确定性行为

指定EVM1的向后兼容升级路径

Ewasm 的目标

  • 提供ewasm合约语义和以太坊接口规范

  • 提供EVM到ewasm合约的转换器,最好是由一个ewasm合约来完成

  • 提供gas 计量,最好也是一个ewasm 合约来实现

  • 提供用于执行ewasm合同的VM实现

  • 在Solidity编译器中实现ewasm后端

  • 提供在Rust中编写合约的库和说明

  • 提供用C编写合约的库和说明

为什么需要Ewasm?

  • 快速高效:要真正将以太坊与世界计算机区分开来,我们需要拥有一台高性能的虚拟机。VM的当前架构是原始性能的最大阻碍之一。WebAssembly旨在利用各种平台上可用的通用硬件功能,以接近本机的速度执行。这将为需要性能/吞吐量的各种用途打开大门。
  • 安全性:随着ewasm的性能提升,我们将能够实现以太网的部分内容,例如VM本身的预编译合同之类的功能,这将最大限度地减少我们可信赖的计算基础。
  • 标准化指令集:WebAssembly目前被W3C社区组织设计为开放标准,并且正在由Mozilla,Google,Microsoft和Apple的工程师积极开发。
  • 工具链兼容性:Wasm的LLVM前端是MVP的一部分。这将允许开发人员编写合同并重用使用通用语言编写的应用程序,例如C / C ++,go和rust。
  • 可移植性:Wasm的目标是部署在所有主要的Web浏览器中,这将使其成为部署最广泛的VM架构之一。编译为ewasm的合同将与任何标准的Wasm环境共享兼容性。这将使得直接在以太坊,云托管环境或本地机器上运行程序
  • 可选和灵活的计量:计量VM会增加开销,但对于运行不受信任的代码至关重要。如果代码是可信的,则计量可能是可选的 ewasm将计量定义为适应这些用例的可选层。
  • 此外,Wasm的一些高级设计目标主要适用于以太坊,包括:

定义可移植,大小和加载时间有效的二进制格式作为编译目标,可以通过利用各种平台(包括移动和物联网)上的常用硬件功能,编译为以本机速度执行。

Ewasm 示例合约

为了读者更方便的理解Ewasm示例合约代码,这里首先介绍WebAssembly 相关的知识点

S-表达式

WebAssembly 文本格式是以S-表达式表示的,S-表达式是用于描述树状结构的一种简单文本形式,其特征是树的每个结点均被一对圆括号“(...)” 包围,结点可以包含子结点。

最简单的WebAssembly 模块如下:

(module)

在S-表达式中,这表示一颗根结点为module的树,尽管module什么都没有,但是这仍然是一个合法的WebAssembly 模块。

在WebAssembly文本格式中,左括号“(” 后紧跟的是结点的类型,如刚才例子中的module, 随后是由分隔符(空格、换行符等)分割的属性和子结点列表。下面的代码描述了一个WebAssmebly 模块,该模块包含了一个什么也没做空函数,以及属性为1的memory:

(module 
    (func)
    (memory 1)
)

在WebAssembly文本格式中,结点类型如下表

结点 类型
module WebAssembly模块根结点,即module
memory Memory
data Memory 初始值
table Table
elem Table 元素初始值
import 导入对象
export 导出对象
type 函数签名
global 全局变量
func 函数
param 函数参数
result 函数返回值
local 局部变量
start 开始函数

下面展示了一段可以正常在以太坊测试网络执行的wasm合约,我们将它转为下面wast的可读形式进行分析

(module
  (type (;0;) (func (param i32 i32)))
  (type (;1;) (func))
  (import "ethereum" "storageStore" (func (;0;) (type 0)))
  (func (;1;) (type 1)
    i32.const 0
    i32.const 32
    call 0)
  (memory (;0;) 1)
  (export "memory" (memory 0))
  (export "main" (func 1))
  (data (i32.const 32) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01"))

首先是定义了一个webAssembly模块,接下来定义了两种函数签名, 第一个函数签名索引为0 , 该函数接受两个 i32 类型的参数, 没有返回值。 第二个函数签名索引为1, 没有参数,也没有返回值。

接下来导入了一个以太坊EEI模块中的storageStore, 用于账本数据存储,该函数在定义的函数列表索引为0, 函数签名类型为之前定义的type 0,该函数函数体在以太坊代码里面用navite code实现。

然后定义了一个函数索引为1 的,函数签名类型为 type 1 的 函数。 这个函数的函数体 先使用 i32.const 0 将 内存地址偏移量为0 压栈。 然后 使用i32.const 32 将从内存中偏移量0开始,长度为32字节的数据压入栈。

然后调用 函数0, 函数0就是 storageStore 的具体实现,这个代码会通过wasm虚拟机 执行调用, 该函数会从栈上取出内存偏移量和数据长度,并将数据写入账本。

(memory (;0;) 1) 定义了一段在webAssmebly中使用的内存, 这段内存的索引为0, 初始大小为1页, 也就是64KB = 65535字节。

然后将memory 导出, 导出的内容索引为0的memory

然后将main 导出,导出的函数为 索引为1 的函数。

data 部分是 memory 初始化后存储的值, 代码段表示 在 内存的偏移量为32字节处存储i32类型的值为1。

Ewasm 测试网络

Ewasm项目的设计目标之一是使用一系列工具支持各种语言的智能合约开发,包括现有的成熟工具链,如LLVM(C / C ++,Kotlin,Rust)和JavaScript /TypeScript。理论上,任何可以编译为Wasm的语言都可以用来编写智能合约(只要它实现了EEI和ECI)。

与主网络差异

Ewasm testnet支持执行EVM 1.0(拜占庭)字节码 Ewasm字节码。链ID设置为0x42(十进制66)

还有两个技术差异:

  • 字节码大小限制已被解除,并且没有上限(同样的合约,Wasm字节码比EVM长很多)
  • 合同字节码中的零字节在部署期间不会refund(它们的成本与非零字节相同)

以太坊目前有个简单的测试网络,可以供开发人员进行测试ewasm合约。但是目前针对c和c++相关的资料较少,大都是一些个人项目,而且不一定能测试通过。

Ewasm 测试网络可用实现方案

随着Eth2.0的发展以及为了支持Ewasm, 以太坊的客户端逐渐和VM 划分开来,通过EEI 和ECI 就可以实现这种划分,这样也是得客户端和VM更加丰富多样化,只要实现了相应的API 就可以进行相应的组合。

现在主流的本地测试方法如下,这里只介绍可能跟我们有关系的项目,像Rust python 之类的客户端,不在这里一一列举,感兴趣的读者可以自己探索。

Geth + Hera(EVMC)

Hera 虚拟机与客户端组合图:

heraevmc.png

Geth 客户端要选择特定的go-ethereum 分支,这里需要选择ewasm项目下的testnet里面fork的这版本,官方的go-ethereum目前并不支持。

EVMC是以太坊虚拟机(EVM)和以太坊客户端之间的低级别ABI。在EVM方面,它支持经典的EVM1和Ewasm。在客户端,它定义了EVM实现访问以太坊环境和状态的接口。EVM-C的作用是分离客户端和EVM,客户端在执行合约中只负责与链上信息的交互,把对合约的解析和执行任务交给EVM,他们之间的通信接口就是EVM-C。

Hera 正是实现了EVMC 的相关接口的一个虚拟机。Hera内部包含了wasm VM,目前wasm的VM有多个实现版本,Hera目前完全支持Bineryen,WABT,WAVM(EOS也使用WAVM)。

不管是哪个版本的客户端,只要是使用EVMC接口就可以与Hera进行交互。总之只要执行指令过程中有与链上的信息交互就通过接口与客户端通信,其他low level 的 wasm指令由内置的VM执行。

目前在geth启动参数里面配置hera的动态链接库路径,就可以实现 这种模式。

hera工作流程:

Hera.png

Geth + Wagon (Golang 解释器)

这个Geth 客户端跟 上面是一样的,都不是官方的go-ethereum 客户端,github上有个项目已经实现了Geth 调用Wagon 解释器的geth,这两个是可以无缝结合的,因为都是golang实现的。

wagon 是一个golang 实现的解释器,它可以实现下面两个功能:

  • 解码 wasm 二进制文件
  • 加载并执行wasm bytecode

wagon不关心wasm二进制文件的生成; 这些文件应该用另一工具来制造(例如WABTbinaryenwagon 提供一个实用程序来产生wasm从文件wastwat文件(并且反之亦然)。

wagon主要目标是提供构建块,以便能够为Go代码构建解释器,可以嵌入任何Go程序中。

geth+wagon 工作流程

wasm合约的执行过程在没有真正到达wasm vm执行代码代码之前都是与evm的种种行为保持一致的,所以它对外的接口也是两个大类,一个是create一个是call,接下来分别详细解释wasm合约的执行流程

Geth-wagon 优缺点

优点:

  • wagon解释器成熟度高,代码容易实现,而且采用wagon解释器的项目也比较多

  • 后续可能会有以太坊官方实现版本,可以随时更新跟进

缺点:

  • ewasm 与evm耦合过高,ewasm里面多次用到evm里变量。
  • 日志没有统一管理
  • 合约每次执行都需要重新解析module, 浪费了性能,可以将解析后的module缓存起来

Ewasm 总体设计图

EWASM.png

点赞 5
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
PP
PP
江湖只有他的大名,没有他的介绍。