尽管 WebAssembly 的名字里有"Web",但其实它是一个通用的运行时,如今除了 Web 之外有许多平台都开始关注这一技术。在这篇博文中,作者探讨了一个应用场景,那就是将 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 有什么关系呢?好问题!
以太坊面临着许多技术挑战,而随着使用率的上升,以太坊却没能快速扩展,结果它的网络变得非常慢,交易时间减慢到了几秒钟到几分钟不等。它也变得非常昂贵——比 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 等等——它似乎已成为构建新一代区块链网络的事实标准。
我想尝试编写一个 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
Account hello-world for network "default" was created.
$ near state hello-world
Account hello-world
{
amount: '1000000000000000000',
code_hash: '11111111111111111111111111111111',
locked: '0',
storage_paid_at: 1788914,
storage_usage: 182
}
上面已经创建了一个帐户,并从我的测试钱包中为其提供了资金。
使用 NEAR 时,你可以用 Rust 或 AssemblyScript 编写智能合约。WebAssembly 不支持 JavaScript(缺少静态类型和垃圾回收是一个挑战),但它确实支持 AssemblyScript,这是 TypeScript(即带有类型的 JavaScript)的子集。
下面是一个非常简单的 AssemblyScript 智能合约,它返回“hello world”字符串:
export function helloWorld(): string {
return "hello world";
}
下面是一个具有所需依赖项的对应的 package.json 文件:
{
"name": "smart-contract-hello-world",
"version": "0.0.1",
"dependencies": {
"near-runtime-ts": "nearprotocol/near-runtime-ts"
},
"devDependencies": {
"assemblyscript": "^0.8.1",
"near-shell": "nearprotocol/near-shell"
}
}
这包括提供 WebAssembly 编译器的 assemblyscript,提供一些必需的编译实用工具的 near-shell,还有 near-runtime-ts,提供额外的一些合约与底层区块链通信所需的 AssemblyScript 代码。
你可以从 shell 使用一个简单的 compile 函数来编译智能合约:
const nearUtils = require("near-shell/gulp-utils");
nearUtils.compile("./assembly/main.ts", "./out/main.wasm", () => {});
注意:你不需要 Gulp 即可构建 NEAR 智能合约,我提出了一个提案,其中包含一些简化此流程的建议。
https://github.com/nearprotocol/near-shell/issues/205
执行上述操作将创建一个大约 9KB 的 wasm 文件。
最后,可以使用 NEAR CLI 将 wasm 文件部署到网络上:
$ near deploy --contractName hello-world
Starting deployment. Account id: hello-world,
node: https://rpc.nearprotocol.com, file: ./out/main.wasm
NEAR 有一个 JavaScript SDK,名为 nearlib,用来与区块链网络交互。以下示例使用这个 SDK 连接到网络,然后加载"hello-world”智能合约并与其交互:
// 这个配置需要连接到网络
const nearConfig = {
nodeUrl: "https://rpc.nearprotocol.com",
deps: { keyStore: new nearlib.keyStores.BrowserLocalStorageKeyStore() }
};
// 连接到 near 网络
const near = await nearlib.connect(nearConfig);
// 加载智能合约
const contract = await near.loadContract("hello-world", {
viewMethods: ["helloWorld"],
changeMethods: []
});
// 调用智能合约
const greeting = await contract.helloWorld();
console.log(greeting);
执行上述代码会将“hello world”问候语写入控制台。
智能合约还可以存储状态,该状态记录在区块链中。我们将修改“hello world”智能合约,以记录其被调用的次数,来演示这个功能。
NEAR 运行时提供了一个键值存储,你可以在其中存储 string、bytes 或 u64 类型的对象。下面的示例存储了一个带有 count 键的 u64 值:
import { storage } from "near-runtime-ts";
export function helloWorld(): string {
const greetingCount: u64 = storage.getPrimitive<u64>("count", 0);
storage.set<u64>("count", greetingCount + 1);
return "hello world " + greetingCount.toString();
}
合约现在影响了区块链的状态,这被称为“更改方法”。为了从客户端使用 nearlib 执行此方法,用户必须登录到一个有效的电子钱包帐户:
// 如果钱包账户不存在,则要求登录钱包
walletAccount = new nearlib.WalletAccount(near);
if (!walletAccount.getAccountId()) {
walletAccount.requestSignIn("hello-world", "Hello World");
}
// 加载合约
const contract = await near.loadContract("hello-world", {
viewMethods: [],
changeMethods: ["helloWorld"],
sender: walletAccount.getAccountId()
});
// 调用智能合约
const greeting = await contract.helloWorld();
这里的 requestSignIn 重定向到电子钱包应用程序,从而允许智能合约请求访问你的电子钱包帐户:
授予访问权限后,你将重定向回 hello world 应用,然后应用会调用合约,并返回附带调用计数的问候语,比如 hello world 0,hello world 1……
这里发生了很多事情,因此我们将详细介绍一番。
首先要注意的是,你需要为网络使用付费,还需要为包括合约部署、存储、合约执行等在内的各种事务付费。你可以通过 near state 命令确定合约的当前帐户余额:
$ near state hello-world
Account hello-world
{
amount: '999999999977548614',
code_hash: '9mfQA5pUKDmH5g3Emi6yqanWXyahLEuauvZEXFZYSNhj',
locked: '0',
storage_paid_at: 1759774,
storage_usage: 12216
}
请注意,在测试网上表示合约余额的 amount 属性是随机的。但如果重新部署或执行合约,你会注意到这个数字减少了。另外,你可以使用 near send 命令为合约充值,也就是将余额从一个帐户转移到另一个帐户。
部署后,合约将分布在网络(或网络分片)中的所有节点上。当你执行一个“更改方法”时,网络中的所有节点都将执行合约以确定新的状态和返回值。权益证明共识算法可确保所有节点都一致,并且将新块添加到链中。
你可以通过在线浏览器查看区块链,在这里你应该能够找到记录(执行合约所产生的)交易的区块。
你可能会注意到,更改方法的返回速度比视图方法慢,这是因为更改方法会导致区块链状态的更改,并且状态更改必须记录在一个块中。使用 testnet Explorer,你可以看到链上每秒添加一个新块,而不管是否发生任何交易。
我想尝试创建一个比简单的计数应用更有意义的智能合约。碰巧我正要在 FinJS 会议上就 WebAssembly“不在浏览器中”的主题发表演讲。我认为创建一个名为 FinWASM 的伪聚会很有趣,在这个聚会中,注册过程由智能合约管理!
下面是智能合约代码,这里总共分配了 50 张票,允许(拥有 NEAR 钱包帐户的)人们注册并获得一张票:
import { context, PersistentVector } from "near-runtime-ts";
let tickets = new PersistentVector<string>("m");
const TOTAL_TICKET_COUNT = 50;
export function getRemainingTicketCount(): i32 {
return TOTAL_TICKET_COUNT - tickets.length;
}
export function hasSignedUp(): boolean {
for (let i = 0; i < tickets.length; i++) {
if (tickets[i] == context.sender) {
return true;
}
}
return false;
}
export function signUp(): string {
if (hasSignedUp()) {
return "already_signed_up";
}
if (getRemainingTicketCount() === 0) {
return "event_sold_out";
}
tickets.push(context.sender);
return "success";
}
该合约使用了 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
领取专属 10元无门槛券
私享最新 技术干货