ブロックチェーンで使われるWebAssembly Runtime

はじめに

こんにちは。Blockchain Labの高橋です。

以前、同じくBlockchain Labに所属している高瀬がスマートコントラクトとWebAssemblyの親和性について記事を公開しました (Web 以外でも期待される WebAssembly – Blockchain との親和性について)。

その中でも少し述べられていたようにWebAssembly Runtime を組み込んで VM として使用し、WebAssembly 形式でのスマートコントラクトをサポートするブロックチェーンが登場しつつあります。

そこで、今回は WebAssembly 形式でのスマートコントラクトをサポートしているブロックチェーンがどのような WebAssembly Runtime を VM に組み込んでいるのかを紹介します。

標準化の取り組み – WASI –

ブロックチェーンで使われている WebAssembly Runtime を紹介する前に、Web以外で WebAssembly を利用するための標準化の取り組みである WASI について紹介します。WASI は WebAssembly System Interface のことで、WebAssembly をブラウザ以外の環境で実行するため、ホストのファイルシステムやネットワークなどのOS機能へのアクセスを提供するための仕様です。WASI の重要なポイントは以下の2つです。

1. ポータビリティ

WASI はプラットフォームに依存しないシステムインタフェースになっています。通常、Cなどのコードがコンパイルされるとき、コンパイラのツールチェーンがシステムコールのインタフェースの実装をターゲットのシステムに応じて選択します。この実装は OS の API に含まれる関数を使っているため、システムごとに固有となります。Windows マシンをターゲットとしてコンパイルされるときには実際のマシンとのやり取りに Windows API を利用でき、Mac や Linux がターゲットのときには POSIX APIを利用できます。しかし、WebAssembly の場合にはターゲットとなる OS がコンパイル時に決定しません。そのため1度のコンパイルで全てのマシンで実行可能になるようなポータブルなバイナリを生成する必要があります。

2. セキュリティ

悪意のあるプログラムが実行されたとしてもホストを安全に保つために WASI の動作はサンドボックスで実行されます。コードは OS と直接やり取りを行わず、システムのリソースを利用するためにホストがサンドボックスの中で利用するための関数を用意します。つまり、動作の対象がサンドボックスで限定されています。例えば、WASI で WebAssembly アプリケーションがファイルオープンの呼び出しを行う場合、そのアプリケーションに読み書きの権限が明示的に与えられているディレクトリーのファイルのみが対象となります。

wasm コードはそのままマシンコードとして実行できる実行ファイルではありません。そのため、実行可能なマシンコードに変換する必要があります。WebAssembly の文脈における Runtime とは wasm コードを変換し、実行する環境のことを指します。ブラウザ外で wasm を実行する場合、Runtime が必要になります。このとき、実行可能なポータビリティを持ったバイナリを作成するためにはコンパイラとして、以下の2つのいずれかが選択されます。

1. JIT(Just-In-Time) コンパイル

インタプリタでのプログラム実行時に、あらかじめ用意された中間コードをプログラムの実行時にネイティブコードにコンパイルする方式です。同じコードが実行される場合、すでにコンパイルされたネイティブコードが実行されるので実行速度が向上がします。Firefox で使われている JavaScript と WebAssembly のエンジンである SpiderMonkey は JIT コンパイルを採用しています。

2. AOT コンパイル

WebAssembly をネイティブのマシンコードにコンパイルしてから実行する方式です。例えば、WebAssembly では Google Chrome で使われている JavaScript とWebAssembly のエンジンである V8 は AOT コンパイルを採用しています。

JIT / AOT Compilerの実行までの流れ

また、コンパイラ以外にもインタプリタを使用することもあります。この場合、プロセスが起動してからすぐに実行できるメリットがありますが、実行速度は事前にコンパイルするよりも遅くなります。

Interpreterの実行までの流れ

ブロックチェーンで使われているWebAssembly Runtime

2021年7月時点でブラウザ外で WebAssembly を使うための実装として多くのプロジェクトが進んでいます。今回はその中でもブロックチェーンで使われている Runtime を紹介します。 

Wasmer

Wasmerは、サンドボックス化された環境でネイティブに近いパフォーマンスで WebAssembly を動作させることが可能な Runtime です。Runtime と言いましたが、wasm コードを実行するだけでなく、多くの機能を備えており、Runtime と一言で表現できるのかは微妙なところです(Wasmer というプロジェクトと言った方が正解かもしれません)。Near protocol や CosmWasm などのブロックチェーンで使われています。

1. Pluggable なインフラストラクチャ

Wasmer はJIT コンパイルとAOT コンパイルをサポートしており、目的に応じてバックエンドを選択することができます。各バックエンドには以下のような特徴があります。

  • Singlepass バックエンド

Singlepass バックエンドは Dynasm を使ってマシンコードを生成しています。そのため生成に後処理や分析が含まれないため、コンパイル時間が非常に高速になっています。ただし、実行される最適化はごくわずかなので、実行時のパフォーマンスは他の2つのバックエンドよりも悪くなります。JIT-Bomb(実行よりもコンパイルに時間がかかってしまう問題)を避けるために Wasmer はブロックチェーンのスマートコントラクトには Singlepass を利用することを勧めています。

  • Cranelift バックエンド

Cranelift バックエンドは BytecodeAlliance が作っているRustで記述されたコンパイラフレームワークです。コンパイルの速度と実行時のパフォーマンスが Singlepass と LLVM の中間に位置しているバックエンドです。

  • LLVM バックエンド

LLVM バックエンドは LLVM コンパイラフレームワークを使用して、WebAssembly バイトコードから高度に最適化されたマシンコードを生成します。LLVM バックエンドを使用した場合コンパイルの時間は他の2つと比べて非常に遅くなりますが、実行時のパフォーマンスはネイティブコードとほぼ同じになります。

コンパイラに加えて、生成されたコードをメモリに直接プッシュするJITエンジンと、ディスク上にネイティブコードを生成するネイティブエンジンの2つのエンジンもサポートしていす。(v2.0からJIT エンジンはユニバーサルエンジン、ネイティブエンジンはdylib エンジンと名前が変更されています。)

2. ネイティブオブジェクト エンジン

v1.0以降、Wasm コードをネイティブオブジェクトにプリコンパイルするための機能(“wasmer compile –native” )がサポートされています。プリコンパイルされたオブジェクトやモジュールは、Wasmer CLI との互換性があり、Wasmer Embeddings(Rust、Python、Go、…)で使用できます。

3. Metering

また、Wasmer には計算時間とその他のリソースを監視し、制限を設定して、wasm コードの実行方法を制御するメータリング機能があります。

wasmi

wasmiは、OpenEthereum(EthereumのライトクライアントのRust 実装)や Polkadot で使われている WebAssembly のインタプリタです。

1. インタプリタ

事前に複雑なコンパイルを行わないので、JIT-Bomb が発生するリスクはありません。しかし、一方で実行速度はコンパイルする場合よりも遅くなります。

2. Deterministic

ブロックチェーンではスマートコントラクトを実行するときに同一のトランザクションが、すべてのノードと環境で同じ状態の変更を引き起こすことが必要になります。そのため、ブロックチェーンでスマートコントラクトを実行するために設計された Runtime である wasmi は例えば、非決定論的になる浮動小数点演算子を使った計算を許可していません。

3. no_stdのサポート

OS が存在しない環境でコードを実行する場合に必要となる no_std をサポートしています。

4. Metering

wasmi はメータリングの機能がないため、pwasm-utils を使用して wasm コードにメータリング機能をインジェクトします。

何を基準に採用するか

スマートコントラクトの実行環境として WebAssembly を使用する場合、何を優先するのかによって採用する Runtime が変わってきます。

1. Performance

パフォーマンスが必要な場合は Wasmer のような事前にコンパイルをして最適化を行ってから実行する Runtime が好ましいです。Holochain というプロジェクトでは当初 wasmi を利用していましたが、パフォーマンスを優先するため、Wasmer への移行を行いました。しかし、この場合、次に JIT-Bomb の問題に直面します。もともと Cranelift や LLVM はブロックチェーンのために開発されたものではないので、高度な最適化を伴う複雑なコンパイルを行うため、JIT-Bomb を引き起こす可能性があります。開発者が自由に開発するスマートコントラクトが JIT-Bomb を引き起こすかどうかを事前に検証することは不可能です。そこで複雑さを回避してコンパイルを行うために Singlepass バックエンドが導入されました。しかし、Cranelift や LLVM をバックエンドとして使った場合よりも大幅に性能が下がることになるので、WebAssembly を導入することの恩恵が低くなってしまいます。

2. Deterministic

JIT-Bomb を回避し、厳密な Deterministic を優先する場合は Polkadot のように wasmi を採用することになります。しかし、wasmi はインタプリタで実行されるため、速度が遅いというデメリットがあります。そのため、Polkadot では Deterministic の問題が解決されれば V8 もしくは SpiderMonkey に置き換えることも検討されています。

3. sgx などのTEEを利用する

Wasmer が使用しているJIT コンパイラは現在、(no_std をサポートしていないため)sgxのような TEE のセキュア環境では動作しません。そのため、TEE の内部でスマートコントラクトを実行する場合は wasmi を利用する必要があります。実際、TEE を用いて状態遷移の秘匿化を目指す Secret Network はスマートコントラクトの実行モジュールとして元々 CosmWasm を使っていましたが、Runtime を Wasmer から wasmi へ変更することを決めました。(https://scrt.network/blog/secretwasm-decentralized-private-computation/)

Runtime を変更した例、Polkadotは変更の予定

まとめ

WebAssembly 形式でのスマートコントラクトをサポートするブロックチェーンが登場しつつあり、その VM で使われているRuntime にはそれぞれ異なった特徴を持っています。実際に WebAssembly Runtime を使ってスマートコントラクトのための VM を開発する場合は優先するポイントによって決定する必要があります。

Blockchain Lab では、application-specific なブロックチェーンを構築するために Cosmos-SDK をもとに LINE が独自に開発したフレームワークの LINE Financial Blockchain SDK を提供しています。SDKの機能の1つとして、スマートコントラクトの実行を可能にするために Wasmer を使っている CosmWasm を改良し、パフォーマンス向上を行った VM の開発にも取り組んでいます。CBDC(中央銀行デジタル通貨)など金融分野のユースケースを検討すると、パフォーマンスに関してはまだまだ改善すべき点がたくさんあると感じています。

Blockchain Lab ではスマートコントラクトなどアプリケーションだけでなく、それを実行する VM のパフォーマンスの改善や追加機能の実装など、WebAssembly Runtime を含む VM の開発に意欲のある方も募集しています。詳しくはこちらの採用ページをご覧ください。