🛠️

wasi2icコマンド

2024/08/11に公開

Canisterとは

ICP(Internet Computer Protocol)のCanisterは、Smart Contractの一種であり、WebAssemblyのプログラムを実行できます。

Wasmランタイムとしてwasmtimeが使用されていて、2024年8月時点ではwasm32-unknown-unknownに対応しています。WASI (WebAssembly System Interface)やWASM 64ビットは計画されているものの、現時点では未対応という状況です。

現状Canisterに向けにはIC独自のSystem APIが用意されており、ストレージに相当するStable Memoryへもアクセスができますが、今後、WASI対応してStable Memoryをファイルシステムのように扱えるようになれば、使い勝手は向上し、既存のライブラリやアプリケーションの移植性も高まっていくものと期待されています。


引用元: https://internetcomputer.org/img/webassembly/webassembly-hero-image.webp

wasi2icコマンドの概要

現時点では、WASI対応のSmart Contractを実行させるためのソリューションの一つとして、wasi2icがあります。

ターゲットをwasm32-wasiでビルドし、そのWasmバイナリにWASIのPolyfillを追加して、Canister上で動作できるようにする仕組みのようです。

詳細は、以下のフォーラムで議論されています。

https://forum.dfinity.org/t/introducing-wasi-for-ic/18583

いくつかのリポジトリによって構成されており、たとえば、Stable Memoryを簡易なファイルシステムとして扱うためのstable-fsも用意されているようです。

wasi2icについて解説されている動画です。

Supporting WASI binaries on the Internet Computer

https://www.youtube.com/watch?v=oQb5TUiby7Q

wasi2icインストール方法

wasi2icをインストールする手順は以下の通りです。

$ git clone https://github.com/wasm-forge/wasi2ic
$ cd wasi2ic
$ cargo build
$ cargo install --path .
$ wasi2ic --version
wasi2ic 0.2.13

インストール手順の詳細は以下を参照してください。

https://github.com/wasm-forge/wasi2ic

検証サンプル

本記事では、wasi2icを用いてファイルI/Oが使用できることを検証するため、Canisterに、ファイル名を付けてテキストファイルを保存するsave()メソッド、ファイル名を指定してテキストファイルの内容を読み込むload()メソッドをRustで簡易実装してみます。

注意事項

本サンプルは、2024年8月時点の以下のサンプルを参考にしています。今後、仕様の変更等により変わる場合がございます。

https://github.com/wasm-forge/demo1

1. プロジェクト作成

$ dfx new icptest
✔ Select a backend language: · Rust
✔ Select a frontend framework: · No frontend canister
✔ Add extra features (space to select, enter to confirm) · 
Fetching manifest https://sdk.dfinity.org/manifest.json
⠁ Installing version 0.22.0 of dfx...
︙
$ cd icptest

2. ライブラリ追加

$ cargo add ic-stable-structures ic-wasi-polyfill

3. candid定義

src/icptest_backend/icptest_backend.did

src/icptest_backend/icptest_backend.did
service : {
    "load": (text) -> (variant { Ok: text; Err: text }) query;
    "save": (text, text) -> (variant { Ok; Err: text });
}

4. Rustプログラム

demo1から主に変更・追加している箇所は、load()関数とsave()関数です。
thread_local!ブロック、init()関数、post_upgrade()の内容はとくに編集していません。

src/icptest_backend/src/lib.rs

src/icptest_backend/src/lib.rs
use std::fs;
use std::cell::RefCell;
use ic_stable_structures::{memory_manager::{MemoryId, MemoryManager}, DefaultMemoryImpl};

const WASI_MEMORY_ID: MemoryId = MemoryId::new(0);

thread_local! {
    static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
        RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));
}

#[ic_cdk::query]
fn load(path: String) -> Result<String,String> {
    let content = fs::read_to_string(path).expect("Failed to read the file");
    Ok(content)
}

#[ic_cdk::update]
fn save(path: String, data: String) -> Result<(), String> {
    fs::write(path, data).expect("Failed to write the file");
    Ok(())
}

#[ic_cdk::init]
fn init() {
    let wasi_memory = MEMORY_MANAGER.with(|m| m.borrow().get(WASI_MEMORY_ID));
    ic_wasi_polyfill::init_with_memory(&[0u8; 32], &[], wasi_memory);
}

#[ic_cdk::post_upgrade]
fn post_upgrade() {
    let wasi_memory = MEMORY_MANAGER.with(|m| m.borrow().get(WASI_MEMORY_ID));
    ic_wasi_polyfill::init_with_memory(&[0u8; 32], &[], wasi_memory);    
}

5. Local Canister実行環境準備

$ dfx start --background --clean
$ dfx canister create --all

6. ビルド

ターゲットが異なることや、ビルド後のWasmモジュールをwasi2icで変換するため、dfx deployコマンドは使わず、手動でビルドと配備を行う必要があります。

$ cargo build --release --target wasm32-wasi

注意事項

Rustビルドにwasm32-wasiターゲットが追加されていない場合、事前に以下のコマンドで追加しておいてください。

$ rustup target add wasm32-wasi

7. wasi2icコマンド実行

cargo buildコマンドで生成された<プロジェクト名>_backend.wasmのWasmバイナリを変換します。念のためどこに生成されるか確認しておきましょう。

$ find . -name icptest_backend.wasm
./target/wasm32-wasi/release/icptest_backend.wasm

wasi2icコマンドを実行して、Wasmバイナリを変換します。ここでは、同じディレクトリにno_wasi.wasmというファイル名で変換したバイナリを出力します。

$ wasi2ic ./target/wasm32-wasi/release/icptest_backend.wasm ./target/wasm32-wasi/release/no_wasi.wasm
wasi2ic 0.2.13: processing input file: './target/wasm32-wasi/release/icptest_backend.wasm', writing output into './target/wasm32-wasi/release/no_wasi.wasm'

8. デプロイ

$ dfx canister install --mode reinstall --wasm ./target/wasm32-wasi/release/no_wasi.wasm icptest_backend
Creating UI canister on the local network.
The UI canister on the "local" network is "bd3sg-teaaa-aaaaa-qaaba-cai"
Reinstalling code for canister icptest_backend, with canister ID bkyz2-fmaaa-aaaaa-qaaaq-cai
WARNING!
You are about to reinstall the icptest_backend canister
This will OVERWRITE all the data and code in the canister.

YOU WILL LOSE ALL DATA IN THE CANISTER.
yes

9. 動作確認

$ dfx canister call icptest_backend save '("file1", "hello")'
(variant { Ok })
$ dfx canister call icptest_backend load '("file1")'
(variant { Ok = "hello" })

Discussion