WebAssembly火了,它在區塊鏈上還有這些用處

儘管 WebAssembly 的名字裏有"Web",但其實它是一個通用的運行時,如今除了 Web 之外有許多平臺都開始關注這一技術。在這篇博文中,作者探討了一個應用場景,那就是將 WebAssembly 用作區塊鏈上的智能合約引擎。

瀏覽器之外的 WebAssembly

WebAssembly 是爲瀏覽器打造的一種新型底層語言和運行時,它是多種現代編程語言的編譯目標。它提供了可預測的運行時性能,並且與等效的 JavaScript 實現相比,WebAssembly 更容易被瀏覽器解碼和編譯。WebAssembly 的相關工作始於 2015 年,來自谷歌、微軟、蘋果和 Mozilla 的工程師共同合作,爲 Web 創建了新的運行時。僅僅兩年後,這一運行時的第一個版本就正式發佈,並獲得了所有主流瀏覽器的支持。

如果你剛剛開始接觸 WebAssembly,我強烈建議你閱讀 Lin Clark 的卡通指南,這份指南以直觀的方式說明了它是什麼,以及我們爲什麼需要它!

https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/

儘管名稱中有“Web”,但 WebAssembly 並沒有侷限在 Web 或瀏覽器的範圍之內。在運行時,它與實現所需接口的“主機”環境互操作,Web 瀏覽器就是這樣一種主機。

自 WebAssembly 在 2017 年發佈以來,人們越來越有興趣在瀏覽器“外部”使用這一技術。它的安全沙箱、簡單的指令集、缺乏內置的 I/O 操作、語言和廠商中立等諸多特性,使它被用作雲函數的輕量級運行時、智能合約的執行引擎,甚至是獨立的運行時。

越來越多的規範和行業團體希望將 WebAssembly 的功能擴展到瀏覽器之外。最著名的是 WebAssembly 系統接口(WASI),其正在創建一個標準的系統級接口;另外,還有最近誕生的字節碼聯盟,其目的是爲“爲所有平臺創建默認安全的 WebAssembly 生態系統”。

值得探索的事物顯然有很多!在這篇博文中,我將仔細研究其中的一項瀏覽器以外的用例,也就是將 WebAssembly 作爲區塊鏈的智能合約引擎。

區塊鏈的簡短入門

如果你已經很瞭解區塊鏈和智能合約了,請隨意跳過本節!

區塊鏈是一種底層分佈式賬本技術,也是比特幣的基礎。它將加密技術和被稱爲“工作量證明”的共識機制相結合,就能處理交易並將其寫入共享的不可變總賬,而無需受信任的授權機構或中央服務器。比特幣網絡使用一種簡單的腳本語言來編碼和執行交易。這種語言是受限的,有意設計爲圖靈不完備(例如缺少循環),從而帶來確定性和“安全性”。

繼比特幣的早期成功之後,以太坊網絡於 2014 年啓動。該網絡基於一組與比特幣類似的核心概念,也就是去中心化的分佈式賬本,但具有顯著增強的計算能力。藉助以太坊,開發人員可以編寫分佈式應用(dApp),這些應用由部署到區塊鏈網絡的智能合約組成。這些合約以 Solidity 編寫,這是一種專門爲合約編寫而設計的面嚮對象語言,並由在以太坊網絡內節點上運行的以太坊虛擬機(EVM)執行。

我認爲,以太坊與雲計算的共同點比起比特幣來說更多一些,但這裏所說的雲不是由某一家公司(例如亞馬遜、微軟、谷歌等)來運營或控制的。取而代之的是,它由世界各地運營雲節點的人們來運作,並由規範區塊鏈運行的算法來控制。

當然這裏沒有免費的午餐,運營以太坊節點的人們的確也需要得到補償。這就是爲什麼以太坊擁有自己稱爲以太幣的貨幣的原因所在。執行智能合約會消耗“汽油”,這些汽油必須用以太幣支付(就像花費美元在 AWS 上執行雲函數一樣)。

那麼,所有這些與 WebAssembly 有什麼關係呢?好問題!

區塊鏈上的 WebAseembly

以太坊面臨着許多技術挑戰,而隨着使用率的上升,以太坊卻沒能快速擴展,結果它的網絡變得非常慢,交易時間減慢到了幾秒鐘到幾分鐘不等。它也變得非常昂貴——比 AWS 高几個數量級。

以太坊團隊有一個 v2.0 路線圖,引入了許多重大架構更改,目的是解決這些問題。

在接下來的幾年中,以太坊團隊計劃遷移到權益證明共識機制上(作爲昂貴的工作量證明的替代方案),並將引入分片技術——兩者都是爲了使網絡更快、更便宜。他們要做的最後一件事是移除對 Solidity 的依賴,並替換他們目前使用的虛擬機(EVM)。使用 Solidity 編程語言時,數字類型爲 256 位,運行時需要在目標 CPU 上進行多個 32/64 位操作。

WebAssembly 已經成爲當前 EVM 的有力替代選項。它的主機獨立性、安全沙箱和整體簡潔性等特性使其成爲智能合約的理想運行時。此外,它還允許使用多種現代編程語言(Rust、C++、JavaScript 等)開發合約。以太坊團隊一直在試用一個基於 WebAssembly 的合約引擎 eWASM,並計劃在 2021 年的某個時候正式發佈它。

以太坊不是唯一一家正在調研 WebAssembly 技術的區塊鏈企業。還有很多人都在押注這項技術,包括 Perlin、Parity、NEAR、Tron、EOS、U°OS 和 Spacemesh 等等——它似乎已成爲構建新一代區塊鏈網絡的事實標準。

編寫一個“hello world”智能合約

我想嘗試編寫一個 WebAssembly 智能合約,因此調查了各種區塊鏈實現,想找出相對容易上手的一個。在嘗試了幾種選項之後,我最終選擇了 NEAR 協議,我發現它有最佳的開發人員體驗,還有一個託管的測試網絡、用於管理錢包的在線工具,以及一個線上演示平臺。此外,它對 Rust 和 AssemblyScript 都有很好的支持。

NEAR 協議:https://nearprotocol.com/

如他們的 Nightshade 白皮書中所述,NEAR 使用權益證明來達成共識,並使用分片來提高性能——如果你想深入瞭解其工作機制的一些技術細節,可以好好看看這份白皮書

下面我們來看一下編寫一個簡單的“hello world”智能合約的過程……

創建電子錢包帳戶

智能合約的部署和執行以及狀態持久性都需要“錢”,所以開發的第一步就是創建錢包帳戶。NEAR 網絡有一個在線錢包,你可以在它的測試網絡中創建帳戶。註冊時,你需要做的就是選擇一個不重名的用戶名而已。創建帳戶後,你的帳戶將獲得 10Ⓝ (也就是 10 個 NEAR 令牌——區塊鏈的貨幣單位)的資金,還會給你提供用於管理帳戶的公鑰和私鑰。

NEAR CLI 是與 NEAR 網絡交互的主要工具,下一步是安裝 CLI 並使用你的錢包帳戶登錄:

<span>$</span><span> npm install -g near-shell</span><p>
<span>$</span><span> near login</span>...</p>

開始編寫一些代碼之前,最後一步是爲智能合約創建帳戶併爲其提供資金。

$ near create_account hello-world --masterAccount my-wallet-account<br mpa-from-tpl="t"></br>Account hello-world <span>for</span> network <span>"default"</span> was created.<p>$ near state hello-world</p><br mpa-from-tpl="t"></br>Account hello-world<br mpa-from-tpl="t"></br>{<br mpa-from-tpl="t"></br>  amoun<span>t:</span> <span>'1000000000000000000'</span>,<br mpa-from-tpl="t"></br>  code_hash: <span>'11111111111111111111111111111111'</span>,<br mpa-from-tpl="t"></br>  locked: <span>'0'</span>,<br mpa-from-tpl="t"></br>  storage_paid_a<span>t:</span> <span>1788914</span>,<br mpa-from-tpl="t"></br>  storage_usage: <span>182</span><br mpa-from-tpl="t"></br>}

上面已經創建了一個帳戶,並從我的測試錢包中爲其提供了資金。

一個 AssemblyScript 智能合約

使用 NEAR 時,你可以用 Rust 或 AssemblyScript 編寫智能合約。WebAssembly 不支持 JavaScript(缺少靜態類型和垃圾回收是一個挑戰),但它確實支持 AssemblyScript,這是 TypeScript(即帶有類型的 JavaScript)的子集。

下面是一個非常簡單的 AssemblyScript 智能合約,它返回“hello world”字符串:

`<span>export</span> <span><span>function</span> <span>helloWorld</span>(<span></span>): <span>string</span> </span>{<br mpa-from-tpl="t"></br>  <span>return</span> <span>"hello world"</span>;<br mpa-from-tpl="t"></br>}`

下面是一個具有所需依賴項的對應的 package.json 文件:

`{<br mpa-from-tpl="t"></br>  <span>"name"</span>: <span>"smart-contract-hello-world"</span>,<br mpa-from-tpl="t"></br>  <span>"version"</span>: <span>"0.0.1"</span>,<br mpa-from-tpl="t"></br>  <span>"dependencies"</span>: {<br mpa-from-tpl="t"></br>    <span>"near-runtime-ts"</span>: <span>"nearprotocol/near-runtime-ts"</span><br mpa-from-tpl="t"></br>  },<br mpa-from-tpl="t"></br>  <span>"devDependencies"</span>: {<br mpa-from-tpl="t"></br>    <span>"assemblyscript"</span>: <span>"^0.8.1"</span>,<br mpa-from-tpl="t"></br>    <span>"near-shell"</span>: <span>"nearprotocol/near-shell"</span><br mpa-from-tpl="t"></br>  }<br mpa-from-tpl="t"></br>}`

這包括提供 WebAssembly 編譯器的 assemblyscript,提供一些必需的編譯實用工具的 near-shell,還有 near-runtime-ts,提供額外的一些合約與底層區塊鏈通信所需的 AssemblyScript 代碼。

你可以從 shell 使用一個簡單的 compile 函數來編譯智能合約:

`const nearUtils = <span>require</span>(<span>"near-shell/gulp-utils"</span>);<br mpa-from-tpl="t"></br>nearUtils.compile(<span>"./assembly/main.ts"</span>, <span>"./out/main.wasm"</span>, <span><span>()</span> =></span> {});`

注意:你不需要 Gulp 即可構建 NEAR 智能合約,我提出了一個提案,其中包含一些簡化此流程的建議。

https://github.com/nearprotocol/near-shell/issues/205

執行上述操作將創建一個大約 9KB 的 wasm 文件。

最後,可以使用 NEAR CLI 將 wasm 文件部署到網絡上:

`$ near deploy --contractName hello-world<br mpa-from-tpl="t"></br>Starting deployment. Account id: hello-world,<br mpa-from-tpl="t"></br>  node: http<span>s:</span>//rpc.nearprotocol.<span>com</span>, <span>file</span>: ./out/main.wasm`

NEAR 有一個 JavaScript SDK,名爲 nearlib,用來與區塊鏈網絡交互。以下示例使用這個 SDK 連接到網絡,然後加載"hello-world”智能合約並與其交互:

`<br mpa-from-tpl="t"></br><span>const</span> nearConfig = {<br mpa-from-tpl="t"></br>  nodeUrl: <span>"https://rpc.nearprotocol.com"</span>,<br mpa-from-tpl="t"></br>  deps: { keyStore: <span>new</span> nearlib.keyStores.BrowserLocalStorageKeyStore() }<br mpa-from-tpl="t"></br>};<p><span>const</span> near = <span>await</span> nearlib.connect(nearConfig);</p><p><span>const</span> contract = <span>await</span> near.loadContract(<span>"hello-world"</span>, {</p><br mpa-from-tpl="t"></br>  viewMethods: [<span>"helloWorld"</span>],<br mpa-from-tpl="t"></br>  changeMethods: []<br mpa-from-tpl="t"></br>});<p><span>const</span> greeting = <span>await</span> contract.helloWorld();</p><br mpa-from-tpl="t"></br><span>console</span>.log(greeting);`

執行上述代碼會將“hello world”問候語寫入控制檯。

在區塊鏈上的存儲狀態

智能合約還可以存儲狀態,該狀態記錄在區塊鏈中。我們將修改“hello world”智能合約,以記錄其被調用的次數,來演示這個功能。

NEAR 運行時提供了一個鍵值存儲,你可以在其中存儲 string、bytes 或 u64 類型的對象。下面的示例存儲了一個帶有 count 鍵的 u64 值:

`<span>import</span> { storage } <span>from</span> <span>"near-runtime-ts"</span>;<p><span>export</span> <span><span>function</span> <span>helloWorld</span>(<span></span>): <span>string</span> </span>{</p><br mpa-from-tpl="t"></br>  <span>const</span> greetingCount: u64 = storage.getPrimitive<u64>(<span>"count"</span>, <span>0</span>);<br mpa-from-tpl="t"></br>  storage.set<u64>(<span>"count"</span>, greetingCount + <span>1</span>);<br mpa-from-tpl="t"></br>  <span>return</span> <span>"hello world "</span> + greetingCount.toString();<br mpa-from-tpl="t"></br>}`

合約現在影響了區塊鏈的狀態,這被稱爲“更改方法”。爲了從客戶端使用 nearlib 執行此方法,用戶必須登錄到一個有效的電子錢包帳戶:

`<br mpa-from-tpl="t"></br>walletAccount = <span>new</span> nearlib.WalletAccount(near);<br mpa-from-tpl="t"></br><span>if</span> (!walletAccount.getAccountId()) {<br mpa-from-tpl="t"></br>  walletAccount.requestSignIn(<span>"hello-world"</span>, <span>"Hello World"</span>);<br mpa-from-tpl="t"></br>}<p><span>const</span> contract = <span>await</span> near.loadContract(<span>"hello-world"</span>, {</p><br mpa-from-tpl="t"></br>  viewMethods: [],<br mpa-from-tpl="t"></br>  changeMethods: [<span>"helloWorld"</span>],<br mpa-from-tpl="t"></br>  sender: walletAccount.getAccountId()<br mpa-from-tpl="t"></br>});<p><span>const</span> greeting = <span>await</span> contract.helloWorld();</p>`

這裏的 requestSignIn 重定向到電子錢包應用程序,從而允許智能合約請求訪問你的電子錢包帳戶:

授予訪問權限後,你將重定向回 hello world 應用,然後應用會調用合約,並返回附帶調用計數的問候語,比如 hello world 0,hello world 1……

這裏發生了很多事情,因此我們將詳細介紹一番。

首先要注意的是,你需要爲網絡使用付費,還需要爲包括合約部署、存儲、合約執行等在內的各種事務付費。你可以通過 near state 命令確定合約的當前帳戶餘額:

`$ near state hello-world<br mpa-from-tpl="t"></br>Account hello-world<br mpa-from-tpl="t"></br>{<br mpa-from-tpl="t"></br>  amoun<span>t:</span> <span>'999999999977548614'</span>,<br mpa-from-tpl="t"></br>  code_hash: <span>'9mfQA5pUKDmH5g3Emi6yqanWXyahLEuauvZEXFZYSNhj'</span>,<br mpa-from-tpl="t"></br>  locked: <span>'0'</span>,<br mpa-from-tpl="t"></br>  storage_paid_a<span>t:</span> <span>1759774</span>,<br mpa-from-tpl="t"></br>  storage_usage: <span>12216</span><br mpa-from-tpl="t"></br>}`

請注意,在測試網上表示合約餘額的 amount 屬性是隨機的。但如果重新部署或執行合約,你會注意到這個數字減少了。另外,你可以使用 near send 命令爲合約充值,也就是將餘額從一個帳戶轉移到另一個帳戶。

部署後,合約將分佈在網絡(或網絡分片)中的所有節點上。當你執行一個“更改方法”時,網絡中的所有節點都將執行合約以確定新的狀態和返回值。權益證明共識算法可確保所有節點都一致,並且將新塊添加到鏈中。

你可以通過在線瀏覽器查看區塊鏈,在這裏你應該能夠找到記錄(執行合約所產生的)交易的區塊。

你可能會注意到,更改方法的返回速度比視圖方法慢,這是因爲更改方法會導致區塊鏈狀態的更改,並且狀態更改必須記錄在一個塊中。使用 testnet Explorer,你可以看到鏈上每秒添加一個新塊,而不管是否發生任何交易。

創建一個線上聚會小組

我想嘗試創建一個比簡單的計數應用更有意義的智能合約。碰巧我正要在 FinJS 會議上就 WebAssembly“不在瀏覽器中”的主題發表演講。我認爲創建一個名爲 FinWASM 的僞聚會很有趣,在這個聚會中,註冊過程由智能合約管理!

下面是智能合約代碼,這裏總共分配了 50 張票,允許(擁有 NEAR 錢包帳戶的)人們註冊並獲得一張票:

`<span>import</span> { context, PersistentVector } <span>from</span> <span>"near-runtime-ts"</span>;<p><span>let</span> tickets = <span>new</span> PersistentVector<string>(<span>"m"</span>);</p><p><span>const</span> TOTAL_TICKET_COUNT = <span>50</span>;</p><p><span>export</span> <span><span>function</span> <span>getRemainingTicketCount</span>(<span></span>): <span>i32</span> </span>{</p><br mpa-from-tpl="t"></br>  <span>return</span> TOTAL_TICKET_COUNT - tickets.length;<br mpa-from-tpl="t"></br>}<p><span>export</span> <span><span>function</span> <span>hasSignedUp</span>(<span></span>): <span>boolean</span> </span>{</p><br mpa-from-tpl="t"></br>  <span>for</span> (<span>let</span> i = <span>0</span>; i < tickets.length; i++) {<br mpa-from-tpl="t"></br>    <span>if</span> (tickets[i] == context.sender) {<br mpa-from-tpl="t"></br>      <span>return</span> <span>true</span>;<br mpa-from-tpl="t"></br>    }<br mpa-from-tpl="t"></br>  }<br mpa-from-tpl="t"></br>  <span>return</span> <span>false</span>;<br mpa-from-tpl="t"></br>}<p><span>export</span> <span><span>function</span> <span>signUp</span>(<span></span>): <span>string</span> </span>{</p><br mpa-from-tpl="t"></br>  <span>if</span> (hasSignedUp()) {<br mpa-from-tpl="t"></br>    <span>return</span> <span>"already_signed_up"</span>;<br mpa-from-tpl="t"></br>  }<br mpa-from-tpl="t"></br>  <span>if</span> (getRemainingTicketCount() === <span>0</span>) {<br mpa-from-tpl="t"></br>    <span>return</span> <span>"event_sold_out"</span>;<br mpa-from-tpl="t"></br>  }<br mpa-from-tpl="t"></br>  tickets.push(context.sender);<br mpa-from-tpl="t"></br>  <span>return</span> <span>"success"</span>;<br mpa-from-tpl="t"></br>}`

該合約使用了 PersistentVector,其允許存儲數組。除此之外,它是非常簡單的 JavaScript(或 AssemblyScript)代碼。

我不會展示與此合約交互的客戶端 JavaScript 代碼,這個部分非常簡單。如果你想看看它的運行效果,可訪問 FinWASM 活動網站:

https://colineberhardt.github.io/finwasm-smart-contract/

這個演示的所有代碼都放在了 GitHub 上。

https://github.com/ColinEberhardt/finwasm-smart-contract/

總 結

WebAssembly 已經開始在瀏覽器以外許多有趣的應用程序領域中吸引人們的注意力了,我個人認爲智能合約開發是最有趣的領域之一。對我們大多數人來說,這是一個陌生的世界;但是憑藉 WebAssembly 提供的廣泛語言支持,可能會有更多人來評估這項技術也說不定?

最後,雖然我的 FinWASM 聚會網站開了個玩笑,但我很快意識到這實際上是一種完全有效,且相當明智的區塊鏈用例。作爲合約作者,我定義瞭如何管理活動“門票”的規則,並預先分配了 50 張票。一旦部署到區塊鏈上,我就無法更改這些規則——我無法收回門票或將其全部贈予我的朋友。這套系統是公平的,規則也不能更改,即使是合約作者也不能。

智能合約確實是一個有趣且很少使用的概念。我希望看到這一領域涌現更多研究和實驗。

作者介紹:
Colin Eberhardt 是 Scott Logic 的技術總監,還是橫跨多個技術領域的多產技術作家、博客作者和演講者。

原文鏈接:
https://blog.scottlogic.com/2019/11/26/webassembly-on-the-blockchain.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章