ブロックチェーン上でWEBアプリをホストする その1(事前準備編) Internet Computer Protocol(ICP)
0.目的
分散型クラウドと呼ばれるInternet Computer Protocol(ICP)
は、フロントエンドも
バックエンドもブロックチェーン上でホスト可能です。さらにICPならではの特徴的な機能として、スマートコントラクトからチェーン外部とHTTPS通信を可能とするHTTPS outcalls
があります。
これはもはや、ブロックチェーン上でなんでも出来ちゃうのでは...?
と期待に胸を膨らませ、2章 dapp開発を参考に色々動かしてみたので
備忘録を兼ねて残すことにしました。内容に誤りがあればご指摘いただけますと幸いです。
1.準備
まずは、Canister software development kit(SDK) であるdfx, dfxvm
を
curlコマンドでインストールする。
dfx:canisterの作成・デプロイ・管理において使用されるCLIツール
dfxvm:dfx のバージョンマネージャー
$sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
実行結果
$ sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
info: Executing dfxvm install script, commit: 4a2960eda721f685b988de91b971386cde8cde7c
info: Downloading latest release...
info: Checking integrity of tarball...
dfxvm-x86_64-unknown-linux-gnu.tar.gz: OK
Welcome to dfxvm!
This will install dfxvm, and download and install dfx.
The dfxvm and dfx commands will be added to the following directory:
/home/user/.local/share/dfx/bin
This path will then be added to your PATH environment variable by
modifying the profile files located at:
/home/user/.profile
/home/user/.bashrc
The following binaries were found on your PATH and will be deleted:
/home/user/bin/dfx
/usr/local/bin/dfx
Current installation options:
dfx version: latest
delete dfx on PATH: yes
modify PATH variable: yes
Proceed with installation?: Proceed with installation (default)
info: deleted: /home/user/.cache/dfinity/uninstall.sh
info: deleted: /home/user/bin/dfx
The following binaries could not be deleted:
/usr/local/bin/dfx
You can either delete these files manually, or I can call sudo rm for you,
which will likely prompt you for your password.
How would you like to proceed?: Call sudo rm for me (I'll enter my password)
[sudo] password for user:
info: creating /home/user/.local/share/dfx/env
info: fetching https://sdk.dfinity.org/manifest.json
info: latest dfx version is 0.20.1
info: installing dfx 0.20.1
info: downloading https://github.com/dfinity/sdk/releases/download/0.20.1/dfx-x86_64-unknown-linux-gnu.tar.gz.sha256
[00:00:01] [###################################################################################] 102B/102B (77B/s, 0s)
info: downloaded https://github.com/dfinity/sdk/releases/download/0.20.1/dfx-x86_64-unknown-linux-gnu.tar.gz.sha256
info: downloading https://github.com/dfinity/sdk/releases/download/0.20.1/dfx-x86_64-unknown-linux-gnu.tar.gz
[00:00:25] [##########################################################################] 93.78MB/93.78MB (3.61MB/s, 0s)
info: downloaded https://github.com/dfinity/sdk/releases/download/0.20.1/dfx-x86_64-unknown-linux-gnu.tar.gz
info: verified checksum 1e4824e40df3204cd8342a2744c6f7fcbff9cfee635ab67389ae2011a88a9db6
info: extracted archive
info: installed dfx 0.20.1
info: set default version to dfx 0.20.1
info: already updates path: /home/user/.profile
info: already updates path: /home/user/.bashrc
dfxvm is installed now.
To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
the dfxvm bin directory.
To configure your shell, run:
source "$HOME/.local/share/dfx/env"
dfx
, dfxvm
がインストールされたことを確認する
$ dfx --version
dfx 0.20.1
$ dfxvm list
0.15.2
0.20.1 (default)
SDKをインストールできた。続いて、dfx new
コマンドでプロジェクトを作成する。
実行すると Backend で扱う言語、Frontend で扱うフレームワーク、拡張機能 を聞かれるので、Rust
と React
, Internet Identity
を今回は選択する。
$ dfx new hellorust
dfx new 実行結果
$ dfx new hellorust
✔ Select a backend language: · Rust
✔ Select a frontend framework: · React
✔ Add extra features (space to select, enter to confirm) ·
Fetching manifest https://sdk.dfinity.org/manifest.json
Version v0.20.1 installed successfully.
Creating new project "hellorust"...
CREATE hellorust/.gitignore (260B)...
CREATE hellorust/README.md (2.46KiB)...
CREATE hellorust/dfx.json (179B)...
CREATE hellorust/tsconfig.json (280B)...
CREATE hellorust/package.json (474B)...
CREATE hellorust/Cargo.toml (69B)...
CREATE hellorust/src/hellorust_backend/hellorust_backend.did (51B)...
CREATE hellorust/src/hellorust_backend/Cargo.toml (334B)...
CREATE hellorust/src/hellorust_backend/src/lib.rs (86B)...
npm notice
npm notice New minor version of npm available! 10.2.4 -> 10.8.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.8.1
npm notice Run npm install -g npm@10.8.1 to update!
npm notice
CREATE hellorust/src/hellorust_frontend/tsconfig.json (535B)...
CREATE hellorust/src/hellorust_frontend/public/logo2.svg (14.78KiB)...
CREATE hellorust/src/hellorust_frontend/public/.ic-assets.json5 (5.33KiB)...
CREATE hellorust/src/hellorust_frontend/public/favicon.ico (15.04KiB)...
CREATE hellorust/src/hellorust_frontend/package.json (853B)...
CREATE hellorust/src/hellorust_frontend/vite.config.js (863B)...
CREATE hellorust/src/hellorust_frontend/index.html (330B)...
CREATE hellorust/src/hellorust_frontend/src/index.scss (497B)...
CREATE hellorust/src/hellorust_frontend/src/App.jsx (808B)...
CREATE hellorust/src/hellorust_frontend/src/vite-env.d.ts (38B)...
CREATE hellorust/src/hellorust_frontend/src/main.jsx (237B)...
⠒ Installing node dependencies...
added 134 packages, and audited 136 packages in 16s
15 packages are looking for funding
run `npm fund` for details
Done.
Creating git repository...
WARN: Failed to run `cargo update` - is Cargo installed? You will need to run it yourself (or a similar command like `cargo vendor`), because `dfx build` will use the --locked flag with Cargo.
===============================================================================
Welcome to the internet computer developer community!
You're using dfx 0.20.1
To learn more before you start coding, see the documentation available online:
- Quick Start: https://internetcomputer.org/docs/current/tutorials/deploy_sample_app
- SDK Developer Tools: https://internetcomputer.org/docs/current/developer-docs/setup/install/
- Motoko Language Guide: https://internetcomputer.org/docs/current/motoko/main/about-this-guide
- Motoko Quick Reference: https://internetcomputer.org/docs/current/motoko/main/language-manual
- Rust CDK Guide: https://internetcomputer.org/docs/current/developer-docs/backend/rust/
If you want to work on programs right away, try the following commands to get started:
cd hellorust
dfx help
dfx new --help
===============================================================================
hellorust
が作成されたので、プロジェクトの構成を見てみる。
src
ディレクトリの下に<プロジェクト名>_backend
、<プロジェクト名>_frontend
が作成され、選択したプログラミング言語(Rust)、フロントエンドフレームワーク(React)でプロジェクトが作成された。
$ ls -l
drwxr-xr-x 5 user user 4096 Jun 8 13:38 hellorust
$ cd hellorust/
$ ls -la
total 116
drwxr-xr-x 5 user user 4096 Jun 8 13:38 ./
drwxr-xr-x 17 user user 4096 Jun 8 13:38 ../
drwxr-xr-x 8 user user 4096 Jun 8 13:38 .git/
-rw-r--r-- 1 user user 260 Jun 8 13:38 .gitignore
-rw-r--r-- 1 user user 69 Jun 8 13:38 Cargo.toml
-rw-r--r-- 1 user user 2814 Jun 8 13:38 README.md
-rw-r--r-- 1 user user 530 Jun 8 13:38 dfx.json
drwxr-xr-x 96 user user 4096 Jun 8 13:38 node_modules/
-rw-r--r-- 1 user user 69958 Jun 8 13:38 package-lock.json
-rw-r--r-- 1 user user 462 Jun 8 13:38 package.json
drwxr-xr-x 4 user user 4096 Jun 8 13:38 src/
-rw-r--r-- 1 user user 280 Jun 8 13:38 tsconfig.json
$ cd src/
$ ls -la
total 16
drwxr-xr-x 4 user user 4096 Jun 8 13:38 ./
drwxr-xr-x 5 user user 4096 Jun 8 13:38 ../
drwxr-xr-x 3 user user 4096 Jun 8 13:38 hello_rust_backend/
drwxr-xr-x 4 user user 4096 Jun 8 13:38 hello_rust_frontend/
プロジェクト作成できたので、早速ローカルでcanister実行環境を起動してみる。
コマンドオプションには、--background
と--clean
を指定しておくのが良いらしい。
dfx start
コマンドで起動して、dfx deploy
コマンドでビルド・デプロイ
$ cd ./hellorust
$ dfx start --clean --background
$ dfx deploy
停止したい場合は、dfx stop
コマンドで停止する
$ dfx stop
まずは、dfx start
コマンドで起動してみる
dfx start の実行結果
$ cd hello_rust/
$ dfx start --background --clean
Running dfx start for version 0.20.1
Using the default definition for the 'local' shared network because /home/user/.config/dfx/networks.json does not exist.
Initialized replica.
Dashboard: http://localhost:35909/_/dashboard
起動できたので、ビルド・デプロイする
dfx deploy の実行結果
$ cd hello_rust/
$ dfx deploy
Deploying all canisters.
Creating canisters...
Creating canister hello_rust_backend...
Creating a wallet canister on the local network.
The wallet canister on the "local" network for user "default" is "bnz7o-iuaaa-aaaaa-qaaaa-cai"
hello_rust_backend canister created with canister id: bkyz2-fmaaa-aaaaa-qaaaq-cai
Creating canister hello_rust_frontend...
hello_rust_frontend canister created with canister id: bd3sg-teaaa-aaaaa-qaaba-cai
Building canisters...
Error: Failed while trying to deploy canisters.
Caused by: Failed to build all canisters.
Caused by: Failed while trying to build all canisters.
Caused by: Failed while trying to build all canisters in the canister pool.
Caused by: The pre-build all step failed
Caused by: Failed step_prebuild_all.
Caused by: Failed to run 'cargo locate-project'.
Caused by: No such file or directory (os error 2)
deploy に失敗した...
そもそも Rust, cargo(Rustのパッケージマネージャ)
をインストールしてないせいだった。
次の記事を参考に curl コマンドでインストールする。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
実行結果
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer
Welcome to Rust!
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:
/home/user/.rustup
This can be modified with the RUSTUP_HOME environment variable.
The Cargo home directory is located at:
/home/user/.cargo
This can be modified with the CARGO_HOME environment variable.
The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:
/home/user/.cargo/bin
This path will then be added to your PATH environment variable by
modifying the profile files located at:
/home/user/.profile
/home/user/.bashrc
You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
Current installation options:
default host triple: x86_64-unknown-linux-gnu
default toolchain: stable (default)
profile: default
modify PATH variable: yes
1) Proceed with standard installation (default - just press enter)
2) Customize installation
3) Cancel installation
>1
info: profile set to 'default'
info: default host triple is x86_64-unknown-linux-gnu
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2024-05-02, rust version 1.78.0 (9b00956e5 2024-04-29)
info: downloading component 'cargo'
8.0 MiB / 8.0 MiB (100 %) 5.5 MiB/s in 1s ETA: 0s
info: downloading component 'clippy'
info: downloading component 'rust-docs'
15.1 MiB / 15.1 MiB (100 %) 2.9 MiB/s in 4s ETA: 0s
info: downloading component 'rust-std'
24.3 MiB / 24.3 MiB (100 %) 2.7 MiB/s in 9s ETA: 0s
info: downloading component 'rustc'
63.7 MiB / 63.7 MiB (100 %) 5.3 MiB/s in 11s ETA: 0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
15.1 MiB / 15.1 MiB (100 %) 2.0 MiB/s in 6s ETA: 0s
info: installing component 'rust-std'
24.3 MiB / 24.3 MiB (100 %) 10.6 MiB/s in 2s ETA: 0s
info: installing component 'rustc'
63.7 MiB / 63.7 MiB (100 %) 6.8 MiB/s in 8s ETA: 0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-unknown-linux-gnu'
stable-x86_64-unknown-linux-gnu installed - rustc 1.78.0 (9b00956e5 2024-04-29)
Rust is installed now. Great!
To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).
To configure your current shell, you need to source
the corresponding env file under $HOME/.cargo.
This is usually done by running one of the following (note the leading DOT):
. "$HOME/.cargo/env" # For sh/bash/zsh/ash/dash/pdksh
source "$HOME/.cargo/env.fish" # For fish
user@LAPTOP-F0C74KT3:~$
$ source $HOME/.cargo/env
$ rustc --version
rustc 1.78.0 (9b00956e5 2024-04-29)
$
dfx deploy
をリベンジしてみると、今度は Cargo.lock
ファイルを作れとのことらしいので作成する。
cargo build --locked
dfx deploy
を再度リベンジすると、今度は Wasm をコンパイルターゲットに追加しろと言われるので追加する。これで Rust から Wasm 出力できるようになるらしい。
rustup target add wasm32-unknown-unknown
実行結果
$ rustup target add wasm32-unknown-unknown
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
17.8 MiB / 17.8 MiB (100 %) 16.9 MiB/s in 1s ETA: 0s
dfx deploy
を再々リベンジすると、ビルドしろと言われる。そりゃそう。
target
オプションを使って直前に追加した wasm32-unknown-unknown
をターゲットアーキテクチャに指定してビルドする。これで Wasm 出力される。
ちなみにビルドされたバイナリは、target/wasm32-unknown-unknown/debug 以下に出力される。
$ cargo build --target=wasm32-unknown-unknown
実行結果
$ cargo build --target=wasm32-unknown-unknown
Compiling proc-macro2 v1.0.85
Compiling unicode-ident v1.0.12
Compiling typenum v1.17.0
Compiling version_check v0.9.4
Compiling serde v1.0.203
Compiling autocfg v1.3.0
Compiling syn v1.0.109
Compiling thiserror v1.0.61
Compiling cc v1.0.99
Compiling anyhow v1.0.86
Compiling rustversion v1.0.17
Compiling either v1.12.0
Compiling libc v0.2.155
Compiling paste v1.0.15
Compiling generic-array v0.14.7
Compiling num-traits v0.2.19
Compiling cfg-if v1.0.0
Compiling crc32fast v1.4.2
Compiling lazy_static v1.4.0
Compiling typed-arena v2.0.2
Compiling unicode-width v0.1.13
Compiling data-encoding v2.6.0
Compiling arrayvec v0.5.2
Compiling byteorder v1.5.0
Compiling quote v1.0.36
Compiling pretty v0.12.3
Compiling syn v2.0.66
Compiling hex v0.4.3
Compiling leb128 v0.2.5
Compiling ic0 v0.21.1
Compiling crypto-common v0.1.6
Compiling block-buffer v0.10.4
Compiling num-integer v0.1.46
Compiling digest v0.10.7
Compiling psm v0.1.21
Compiling stacker v0.1.15
Compiling sha2 v0.10.8
Compiling serde_derive v1.0.203
Compiling thiserror-impl v1.0.61
Compiling candid_derive v0.6.6
Compiling binread_derive v2.1.0
Compiling binread v2.2.0
Compiling num-bigint v0.4.5
Compiling ic_principal v0.1.1
Compiling serde_bytes v0.11.14
Compiling serde_tokenstream v0.1.7
Compiling candid v0.10.8
Compiling ic-cdk-macros v0.8.4
Compiling ic-cdk v0.12.1
Compiling hellorust_backend v0.1.0 (/home/user/hellorust/src/hellorust_backend)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 18.40s
dfx deploy
を再々々リベンジすると起動できた。環境構築はこれでひと段落。
$ cd hellorust
$ dfx deploy
(省略)
Committing batch.
Committing batch with 16 operations.
Deployed canisters.
URLs:
Frontend canister via browser
hellorust_frontend:
- http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai
- http://bd3sg-teaaa-aaaaa-qaaba-cai.localhost:4943/
Backend canister via Candid interface:
hellorust_backend: http://127.0.0.1:4943/?canisterId=be2us-64aaa-aaaaa-qaabq-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai
$
2.起動確認する
backend はRust
で実装することにしたので、Rust用のライブラリ仕様を参考にする。
#[ic_cdk::query]
を付与することで、付与した関数を外部呼び出し可能なキャニスターのインタフェースとして公開できる。引数型はString
、レスポンス型もString
であるgreet
メソッドをquery function
として公開する
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
Canister のインタフェースである .did ファイルを確認する。
.did ファイルには Candid フォーマットによるキャニスターのインタフェース情報を定義する
src/ic_todolist_backend/ic_todolist_backend.did
service : {
"greet": (text) -> (text) query;
}
Candid とは?
Candid はインターフェース記述言語。その主な目的は、通常、インターネット コンピュータ上で実行されるキャニスターが提供するサービスのパブリックインターフェースを記述すること。Candid は、言語に依存せず、Motoko、Rust、JavaScript などのさまざまなプログラミング言語で記述されたサービスとフロントエンド間の相互運用が可能である点が大きなメリット。
3.Canisterのメソッドを呼び出す(querycall/updatecall)
バックエンド canister のメソッドの種類には、update call
とquery call
がある。
update call
は state を変更でき、query call
は state を変更しない照会用として
用いられる。このupdate call
とquery call
を用いて read/write してみる。
3.1 Backend Canister(Rust)
スレッドローカルな静的変数STORE
を用意する、データ型はRefCell
query call を受けたときの動作を記述した関数には#[query]
アトリビュートを付与して、
同様にupdate call の場合は#[update]
アトリビュートを付与する。
use std::cell::RefCell;
use ic_cdk::{query, update};
thread_local! {
static STORE: RefCell<String> = RefCell::default();
}
#[query(name = "getMessage")]
fn get_message()-> String {
STORE.with(|store| {
store.borrow().clone() // STOREに書き込まれた値を返却
})
}
#[update(name = "setMessage")]
fn set_message(text: String) {
STORE.with(|store| {
*store.borrow_mut() = text; // 仮引数の値をSTOREに書き込む
});
}
3.2 Backend Canister(Candid) を用意
Rustの場合はCandidファイルにインタフェース定義が必要なので、追記する
service : {
"greet": (text)->(text) query;
"getMessage": ()-> (text) query; // 追記
"setMessage": (text)-> (); // 追記
}
3.3 Frontend Canister を用意
画面からupdate call
、query call
するボタンを追加する。
テキストフィールドの値を引数としてupdate call
させたいのでテキストフィールドも追加
onst Message: React.FC = () => {
const [inputMessage, setInputMessage] = useState('');
const [message, setMessage] = useState('');
const registerMessage = () => {
hellorust_backend.setMessage(inputMessage).then(() => {
});
}
const getMessage = () => {
hellorust_backend.getMessage().then((message: string) => {
setMessage(message)
});
}
const handleInputMessage = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputMessage(event.target.value);
}
return (
<main>
<Stack spacing={2}>
<Stack direction="row" spacing={2}>
<TextField
label="Message"
variant="outlined"
value={inputMessage}
onChange={handleInputMessage}
/>
<Box>メッセージ:{message}</Box>
</Stack>
<Stack direction="row" spacing={2}>
<Button variant="contained" onClick={registerMessage}>UpdateCall</Button>
<Button variant="contained" onClick={getMessage}>QueryCall</Button>
</Stack>
</Stack>
</main>
);
}
3.4 動かしてみる
テキスト入力
→ update call
→ query call
→ 繰り返し
で
query call
のたび入力したテキストが表示されいて、Canister のメソッドを呼び出して
read/write できていることが確認できた。
4.さいごに
Canister
が提供するメソッド(サービス)を呼び出せることが確認できたわけですが、
ここまでであれば、苦労する人は少なく記事があっても嬉しい人は少ないと思います。
なので、今後はICPでの開発に必要な主要技術を動かしてみて取り上げていく予定です。
Discussion