wasi2icコマンド
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上で動作できるようにする仕組みのようです。
詳細は、以下のフォーラムで議論されています。
いくつかのリポジトリによって構成されており、たとえば、Stable Memoryを簡易なファイルシステムとして扱うためのstable-fsも用意されているようです。
wasi2ic
について解説されている動画です。
Supporting WASI binaries on the Internet Computer
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
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
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