您好,登錄后才能下訂單哦!
Nervos CKB 腳本編程中怎樣在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,然后在 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 上工作!我確信更復雜的程序可能會遇到需要單獨調整的錯誤,但是您已經了解了整個流程,并且知道在發生錯誤時應該去哪里查找 :)
我們已經在 AssemblyScript 部分看到了一些簡單的例子。讓我們在 Rust 部分嘗試一些更有趣的東西:我們可以在 Rust 代碼中實現一個完整的簽名驗證嗎?
事實證明我們可以!但這遠遠超出了我們在本博客文章中所能包含的內容。我已經準備好了一個演示項目來展示這一點。它用純 Rust 語言實現了使用 secp256k1 庫進行簽名驗證的過程。如果您按照文件中的說明進行操作,您應該可以重現以下具體步驟:
將復雜的 Rust 程序編譯到 WebAssembly 中
將 WebAssembly 程序轉換為 RISC-V
在 CKB 虛擬機上運行生成的 RISC-V 程序
還有一件事我們需要提到:如果您檢查了 Rust secp256k1 演示庫的 Bindgen 分支,并嘗試相同的步驟,您將遇到以下錯誤:
/riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o: in function `core::result::unwrap_failed': /code/secp.c:342: undefined reference to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:344: undefined reference to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:344: undefined reference to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:347: undefined reference to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:350: undefined reference to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o:/code/secp.c:353: more undefined references to `Z___wbindgen_placeholder__Z___wbindgen_describeZ_vi' follow /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o: in function `i32_store': /code/secp.c:56: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_set_nullZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o: in function `i32_load': /code/secp.c:42: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_set_nullZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o: in function `i32_store': /code/secp.c:56: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_set_nullZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:56: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_set_nullZ_vi' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/ccYMiL3C.o: in function `i32_load': /code/secp.c:42: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_growZ_ii' /riscv/lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /code/secp.c:42: undefined reference to `Z___wbindgen_anyref_xform__Z___wbindgen_anyref_table_growZ_ii' collect2: error: ld returned 1 exit status
按照 AssemblyScript 示例中的相同步驟,我們可以在 WebAssembly 文件中進行某些 imports:
$ wabt/bin/wasm2wat wasm_secp256k1_test.wasm -o secp.wat $ cat secp.wat | grep "(import" (import "__wbindgen_placeholder__" "__wbindgen_describe" (func $__wbindgen_describe (type 3))) (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_grow" (func $__wbindgen_anyref_table_grow (type 4))) (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_set_null" (func $__wbindgen_anyref_table_set_null (type 3)))
這些實際上是在 Rust wasm-bindgen 中所需的綁定環境的函數。我們將繼續努力提供與 CKB 環境兼容的綁定。但是現在讓我們退一步想想:這里所需要的環境功能并不是 WebAssembly 標準的一部分。標準中要求的是,當找不到導入條目時,WebAssembly VM 將停止執行,并出現錯誤。為了實現不同的特性,不同的基于 WebAssembly 的區塊鏈可能會在這里注入不同的導入。使得編寫一個兼容不同區塊鏈的 WebAssembly 程序變得困難。
然而,在 CKB 環境中,我們可以根據需要加入任何環境函數。因此,支持所有針對不同區塊鏈的 WebAssembly 程序。更重要的是,我們可以使用 imports 來為現有的 WebAssembly 程序引入新的特性。由于導入函數是與 WebAssembly 程序一起提供的,因此 CKB 本身不需要做任何事情來支持它。所有的奇跡都發生在一個 CKB 腳本中。對于支持 WebAssembly 的區塊鏈,這些環境函數最有可能是固定的,并且是共識規則的一部分。你不能隨意引進新的。類似地,這種在 CKB 上基于轉換的流程,將使得支持新的 WebAssembly 功能(如垃圾收集或者線程處理)變得更加容易,這實際上只是將所需的支持功能寫進 CKB 腳本的問題,這意味著當 WebAssembly 虛擬機更新時,你不需要再等待 6 個月,等待進行下一個硬分叉后(才能實現這些新的功能)。
您可能會有一個疑問:「我明白了,您在 RISC-V 創建了 WebAssembly,但我也可以在 WebAssembly 再現 RISC-V!WebAssembly 是靈活的!」從某種意義上說,這是可行的,一旦一種語言或虛擬機超過了一定的靈活性,它就可以被用來構建許多(甚至是瘋狂的)東西。jslinux 的第一個版本,甚至可以用純 JavaScript 模擬了完整的 x86。但這個問題的另一面是,易于實施的。在 RISC-V 上構建 WebAssembly 感覺更加自然,因為 WebAssembly 被抽象到了更高的級別,具有許多高級特性。例如更高級別的控制流、垃圾回收等。另一方面,RISC-V 模擬了真正的 CPU 所能做的事情,它是在計算機內部運行的實際 CPU 之上的一個非常薄的層。因此,雖然這兩個方向確實都是可能的,但在 RISC-V 上搭建的 WebAssembly ,某些功能是更容易實現的。而在WebAssembly 上實現 RISC-V,可能會遇到很多問題。
一個可供選擇的例子是 EVM,EVM 多年來一直在提倡圖靈完備,但可悲的事實是,它幾乎不可能在 EVM 上構建任意復雜的算法:要么是編碼部分太難,要么是 gas 的消耗將是不合理的。人們不得不想出各種方法來介紹 EVM 上的最新算法,當伊斯坦布爾硬分叉完成時,我們只能在 EVM 中使用 Blake2b 算法。但是許多其他算法呢?
所有這些都是我們選擇 RISC-V 的理由:我們希望在這一代的 CPU 架構上找到最小的一層,而 RISC-V 是我們在確保安全性和性能的同時,能夠在區塊鏈世界中找到的最顯然的可選擇模型。任何不同的模型,比如 WebAssembly、EVM 等,都應該是 RISC-V模型之上的一層,可以通過 RISC-V 模型自然地實現。然而,另一個方向可能根本不會讓人感覺如此順暢。
關于Nervos CKB 腳本編程中怎樣在CKB上實現WebAssembly問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。