作者:Xuejie

原文链接:https://xuejie.space/2019_10_09_introduction_to_ckb_script_programming_wasm_on_ckb/

Nervos CKB 脚本编程简介[4]:在 CKB 上实现 WebAssembly

自从我们选择使用 RISC-V 构建 CKB-VM(Virtual Machine 虚拟机)以来,我们几乎每一天都会被问及这样一个问题:为什么不像别人那样在 WebAssembly 上构建你的虚拟机呢?

在这个选择的背后其实有非常多的原因,可能需要另一篇文章或者一次演讲才能解释完其中的原因。从根本上来说,有一个相当重要的原因:构建软件最主要的就是找到正确的抽象概念,我们相信对于无需许可的区块链而言 RISC-V 比 WebAssembly 是一个更好抽象概念。

当然 WebAssembly 相比于其他更高级的编程语言和第一代区块链虚拟机而言已经是一个巨大的进步了,但 RISC-V 的运行级别还要比 WebAssembly 低得多,这使得它非常适合用于那些希望未来在运行几十年的公有链。

但还有一个问题没有得到回答:目前区块链行业很大一部分人都押注在 WebAssembly 上,(可以说) 基于 WebAssembly 的 dapps 构建了一个很好的生态系统。那么 CKB 如何与之竞争呢?正如上面所提到的,RISC-V 实际上位于一个比 WebAssembly 更低的抽象级别上,我们可以移植现有的 WebAssembly 的程序,并直接在 CKB-VM 上运行他们。通过这种方式,我们既可以享受 RISC-V 提供的灵活性和稳定性,同时也可以拥抱 WebAssembly 的生态系统。

在本文中,我们将展示如何在 CKB-VM 中运行 WebAssembly 程序,我们还会展示通过这种方式运行(程序),会比直接使用 WebAssembly VM 具有更多的优势。

就我个人而言,虽然我相信 WebAssembly 会有一些有趣的特性来支持不同的用例,但是我不相信 WebAssembly 会在区块链领域创建一个更好的生态。环顾四周,在基于 WebAssembly 的区块链中构建 DApp 可能只有两种成熟的选择:Rust 和 AssemblyScript。

人们一直在吹嘘 WebAssembly 在单个抽象的 VM 中支持任意语言的能力(我个人拒绝将WebAssembly 称为 low-level 的虚拟机),但是在这里创建一个真正的 DApp,只能在这两种语言内选择其一。我认为,如果将仅支持 2 种的编程语言的虚拟机称为良好的 VM 生态系统,那么我们可能会有不同的定义。当然还有一些其他语言也在迎头赶上,但它们还没有稳定到可以被认为是一个繁荣的生态系统的阶段。虽然一些有趣的语言在基于 WebAssembly 的环境中具有潜力,但是还没有人注意到,并去支持它们。

如果你仔细观察,就会发现,使用 WebAssembly 的两个不同的区块链项目之间是否可以彼此共享合约,这目前仍然是个问题。当然,有人可能会说:「嗯,这只是一个时间问题,随着时间的推移,更有活力的 WebAssembly 生态系统将会发芽。」但同样的论点也适用于任何地方:为什么随着时间的推移,RISC-V 的生态系统不会变得更好?

咆哮到此为止,现在我们只是假设,WebAssembly 确实拥有一个区块链生态系统,我们可以证明,其中两个被广泛使用的语言:AssemblyScript 和 Rust,都可以在 CKB-VM 环境中得到支持。

AssemblyScript

我相信没有比演示更能说明问题的了。所以,让我们试试官方的 AssemblyScript,然后在 CKB 上运行编译好的程序。我们将只使用 AssemblyScript 简介页面中的官方示例:

$ cat fib.ts
export function fib(n: i32): i32 {
var a = 0, b = 1;
for (let i = 0; i < n; i++) {
let t = a + b; a = b; b = t;
}
return b;
}

关于如何安装,请参考 AssemblyScripts 的文档。为了方便起见,我提供了一些步骤,您可以在这里复制粘贴。

$ git clone https://github.com/AssemblyScript/assemblyscript.git
$ cd assemblyscript
$ npm install
$ bin/asc ../fib.ts -b ../fib.wasm -O3
$ cd ..

这样我们就有了一个编译好的 WebAssembly 程序,我们可以调用一个名为 wasm2c 的程序将它编译成 C 语言的源文件,然后通过 RISC-V 编译器将它编译成 RISC-V 程序,并在 CKB-VM 上运行。

我敢肯定你会说:这是一个黑客行为!它这里对 WASM 程序进行了反编译,然后使它可以运行,你这是在作弊。这个问题的答案是,是但又不是:

一方面,我是在作弊,但我要提出的问题是:我们应该关心的是最终的结果,如果结果足够好,我们为什么要关心这是否是作弊呢?另外,现代编译器已经足够复杂了,就像一个完全的黑盒,我们怎么能确定这个反编译会得到更糟糕的结果呢?

另一方面,这只是将 WebAssembly 转换为 RISC-V 的一种方法。还有许多其他方法都可以实现相同的结果。我们将在后面的重述部分再次讨论这一点。

启动 wasm2c 然后转换 WebAssembly 程序:

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
$ cd ../..
$ wabt/bin/wasm2c fib.wasm -o fib.c

您将在当前目录中看到一对 fib.c 和 fib.h 文件,它们包含 WebAssembly 程序转换的结果,当编译和调用正确时,它们将实现与 WebAssembly 程序相同的功能。

我们可以使用一个小的包装器 C 文件来调用 WebAssembly 程序:

$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include "fib.h"
int main(int argc, char** argv) {
if (argc < 2) return 2;
u8 x = atoi(argv[1]);
init();
u8 result = Z_fibZ_ii(x);
return result;
}

这只是从 CLI 参数中读取一个整数,在 WebAssembly 程序中调用 Fibonacci 函数,然后返回结果。让我们先来编译它:

$ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:xenial bash
(docker) $ cd /code
(docker) $ riscv64-unknown-elf-gcc -o fib_riscv64 -O3 -g main.c fib.c /code/wabt/wasm2c/wasm-rt-impl.c -I /code/wabt/wasm2c
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccfUDYhE.o: in function `__retain':
/code/fib.c:1602: undefined reference to `Z_envZ_abortZ_viiii'
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccfUDYhE.o: in function `i32_load':
/code/fib.c:42: undefined reference to `Z_envZ_abortZ_viiii'
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccfUDYhE.o: in function `f17':
/code/fib.c:1564: undefined reference to `Z_envZ_abortZ_viiii'
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/fib.c:1564: undefined reference to `Z_envZ_abortZ_viiii'
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccfUDYhE.o: in function `f6':
/code/fib.c:1011: undefined reference to `Z_envZ_abortZ_viiii'
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccfUDYhE.o:/code/fib.c:1012: more undefined references to `Z_envZ_abortZ_viiii' follow
collect2: error: ld returned 1 exit status
(docker) $ exit

如上所示,这里有一个报错。它告诉我们有一个 Z_ENVZ_ABORTZ_VIII 函数没有定义。让我们深入了解为什么会发生这种情况。

首先,让我们将原始的 WebAssembly 文件转换为可读的形式:

$ wabt/bin/wasm2wat fib.wasm -o fib.wast
$ cat fib.wast | grep "(import"
(import "env" "abort" (func (;0;) (type 2)))

那么问题来了,WebAssembly 可以导入外部函数,在调用的时候,提供了额外的功能,事实上,著名的 WASI 就是基于 import 功能实现的,后面我们会看到 import 可以用来实现更多基于 WebAssembly 的区块链虚拟机不可能实现的有趣功能。

现在,让我们尝试一个 abort 执行,来修复报错:

$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include "fib.h"
void (*Z_envZ_abortZ_viiii)(u32, u32, u32, u32);
void env_abort(u32 a, u32 b, u32 c, u32 d) {
abort();
}
int main(int argc, char** argv) {
if (argc < 2) return 2;
u8 x = atoi(argv[1]);
Z_envZ_abortZ_viiii = &env_abort;
init();
u8 result = Z_fibZ_ii(x);
return result;
}
$ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:xenial bash
(docker) $ cd /code
(docker) $ riscv64-unknown-elf-gcc -o fib_riscv64 -O3 -g main.c fib.c /code/wabt/wasm2c/wasm-rt-impl.c -I /code/wabt/wasm2c
(docker) $ exit

当然,您可以在 CKB 上测试已编译好的 fib_riscv64 程序。但是,有一个小技巧,在测试套件中有一个简单的 CKB-VM 二进制文件,我们可以使用它来运行这个特定的程序。值得一提的是,这个 CKB-VM 二进制文件的工作方式与 CKB 中的 VM 略有不同。在当前示例中测试 WebAssembly 程序已经足够了。但是为了测试正确的 CKB 脚本,您可能希望使用新构建的独立调试器,它遵循所有 CKB 的语义。后面的文章将解释调试器是如何工作的。

让我们在测试套件中编译二进制文件并运行程序:

$ git clone --recursive https://github.com/nervosnetwork/ckb-vm-test-suite
$ cd ckb-vm-test-suite
$ git clone https://github.com/nervosnetwork/ckb-vm
$ cd binary
$ cargo build --release
$ cd ../..
$ ckb-vm-test-suite/binary/target/release/asm64 fib_riscv64 5
Error result: Ok(8)
$ ckb-vm-test-suite/binary/target/release/asm64 fib_riscv64 10
Error result: Ok(89)

这里的报错稍微有点误导,二进制将把程序中的任何非零结果都视为错误。由于测试的程序返回斐波那契计算结果作为返回值,二进制会把返回值(很可能不是零)视为错误,但是我们可以看到实际的错误值包含正确的斐波那契值。

现在我们证明 AssemblyScript 程序确实可以在 CKB-VM 上工作!我确信更复杂的程序可能会遇到需要单独调整的错误,但是您已经了解了整个流程,并且知道在发生错误时应该去哪里查找

【在 Nervos CKB 上做开发】Nervos CKB 脚本编程简介[4]:在 CKB 上实现 WebAssembly的更多相关文章

  1. 【在 Nervos CKB 上做开发】Nervos CKB脚本编程简介[2]:脚本基础

    CKB脚本编程简介[2]:脚本基础 原文作者:Xuejie 原文链接:Introduction to CKB Script Programming 2: Script 本文译者:Shooter,Jas ...

  2. 【在 Nervos CKB 上做开发】Nervos CKB 脚本编程简介[3]:自定义代币

    原文作者:Xuejie 原文链接:https://xuejie.space/2019_09_06_introduction_to_ckb_script_programming_udt/ Nervos ...

  3. 【在 Nervos CKB 上做开发】Nervos CKB 脚本编程简介[5]:调试 debug

    作者:Xuejie 原文链接:https://xuejie.space/2019_10_18_introduction_to_ckb_script_programming_debugging/ Ner ...

  4. 【在 Nervos CKB 上做开发】Nervos CKB 脚本编程简介[1]:验证模型

    CKB 脚本编程简介[1]: 验证模型 本文作者:Xuejie 原文链接:Introduction to CKB Script Programming 1: Validation Model 本文译者 ...

  5. HTML5文件上传器,纯脚本无插件的客户端文件上传器---Uploader 文件上传器类

    概述 客户端完全基于JavaScript的 浏览器文件上传器,不需要任何浏览器插件,但需要和jQuery框架协同工作,支持超大文件上传,其算法是将一个超大文件切片成N个数据块依次提交给服务 端处理,由 ...

  6. 软件开发架构,网络编程简介,OSI七层协议,TCP和UDP协议

    软件开发架构 什么是软件开发架构 1.软件架构是一个系统的草图. 2.软件架构描述的对象是直接构成系统的抽象组件. 3.各个组件之间的连接则明确和相对细致地描述组件之间的通讯. 4.在实现阶段,这些抽 ...

  7. Atitit.技术管理者要不要自己做开发??

    Atitit.技术管理者要不要自己做开发?? 1. 为什么很多管理者不能自己亲自做了1 1.1. 沟通成本多了1 1.2. .组织分散. 1 1.3. 会议多 .协调多 1 1.4. 问题的根源在于我 ...

  8. 转行做开发的Wiki:找好方向

    案 我是一个从建筑行业转行过来的后端工程师,转行来写代码了.最近发现经常有同学和网上的朋友问我一些转行的问题,零零散散地回答莫不如写一篇文章,以后回答此类问题就方便多了. 我的专业是给排水,属于非常传 ...

  9. Windows Phone 8.1上的开发人员请看

    1)SDK选择:如果你是在Windows Phone 8.1上做一个新App, 或者想把7.x/8.0的App移植到8.1上,请使用WinRT SDK,而不是Silverlight.当然Silverl ...

随机推荐

  1. QT POST/GET HTTP操作

    工程文件 Qt += network 举例 Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui-> ...

  2. sigaction信号处理

    1. sigaction int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); signu ...

  3. service---七月十九号实验

    目录 service---七月十九号实验 1 startService.bindService 2 分析生命周期变化 问题思考: service---七月十九号实验 1 startService.bi ...

  4. Spark源码执行逻辑分析【基于案例SparkPi】

    一.案例SparkPi代码 package scala import org.apache.spark.sql.SparkSession import scala.math.random /** Co ...

  5. js 字符串换行 显示 使用 \ 转义

     js 字符串 有没有 像C# @ 那种 换行也可以显示的方法\ 

  6. linux中find命令的使用详解(转载)

    常用命令 find  (目录)   [-type d | f]  (文件夹 | 文件)   -name   (名称,可使用正则表达式) find  /root  -name "*core&q ...

  7. MySQL数据物理备份之lvm快照

    使用lvm快照实现物理备份 优点: 几乎是热备(创建快照前把表上锁,创建完后立即释放) 支持所有存储引擎 备份速度快 无需使用昂贵的商业软件(它是操作系统级别的) 缺点: 可能需要跨部门协调(使用操作 ...

  8. Nginx+Mysql调优

    使用nginx实现反向代理作用,具备负载均衡的功能.     接受客户端的请求 | nginx(宿主机) | |-------------------| web1 web2 (客户机)   原理: 与 ...

  9. php对接app支付宝支付出错Cannot redeclare Decrypt()

    报错原因: alipaySDK中定义的Encrypt()/Decrypt()函数与Laravel中定义的Encrypt()/Decrypt()函数重名了. 解决办法: 修改alipaySDK中定义的函 ...

  10. Natas25-writeup

    前言 题目链接: http://natas25.natas.labs.overthewire.org 做这一题花了一些时间,也是由于自己知识点掌握不足,所以分享下解题过程. 题目分析 首先,登录后看到 ...