👏

ブロックチェーン上でWEBアプリをホストする その1(事前準備編) Internet Computer Protocol(ICP)

2024/07/20に公開

0.目的

分散型クラウドと呼ばれるInternet Computer Protocol(ICP) は、フロントエンドも
バックエンドもブロックチェーン上でホスト可能です。さらにICPならではの特徴的な機能として、スマートコントラクトからチェーン外部とHTTPS通信を可能とするHTTPS outcallsがあります。

これはもはや、ブロックチェーン上でなんでも出来ちゃうのでは...?

と期待に胸を膨らませ、2章 dapp開発を参考に色々動かしてみたので
備忘録を兼ねて残すことにしました。内容に誤りがあればご指摘いただけますと幸いです。
https://techbookfest.org/product/99rzkWcsBYAuZq91sCWQEU?productVariantID=kY29JjsML3QcZ0b3dhVfdc

1.準備

まずは、Canister software development kit(SDK) であるdfx, dfxvm
curlコマンドでインストールする。

  • dfx:canisterの作成・デプロイ・管理において使用されるCLIツール
  • dfxvm:dfx のバージョンマネージャー
terminal
$sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
実行結果
terminal
$  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がインストールされたことを確認する

terminal
$ dfx --version
dfx 0.20.1
$ dfxvm list
0.15.2
0.20.1 (default)

SDKをインストールできた。続いて、dfx newコマンドでプロジェクトを作成する。
実行すると Backend で扱う言語、Frontend で扱うフレームワーク、拡張機能 を聞かれるので、RustReact, Internet Identity を今回は選択する。

terminal
$ 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)でプロジェクトが作成された。

terminel
$ 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 コマンドでビルド・デプロイ

terminal(起動時)
$ cd ./hellorust
$ dfx start  --clean --background
$ dfx deploy

停止したい場合は、dfx stop コマンドで停止する

terminal(停止時)
$ 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 コマンドでインストールする。
https://ja.linux-console.net/?p=18030

terminal
$ 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ファイルを作れとのことらしいので作成する。

terminal
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 を再々々リベンジすると起動できた。環境構築はこれでひと段落。

terminal(正常起動)
$ 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用のライブラリ仕様を参考にする。
https://docs.rs/ic-cdk/latest/ic_cdk/attr.query.html

#[ic_cdk::query] を付与することで、付与した関数を外部呼び出し可能なキャニスターのインタフェースとして公開できる。引数型はString、レスポンス型もStringであるgreetメソッドをquery functionとして公開する

lib.rs
#[ic_cdk::query]
fn greet(name: String) -> String {
    format!("Hello, {}!", name)
}

Canister のインタフェースである .did ファイルを確認する。
.did ファイルには Candid フォーマットによるキャニスターのインタフェース情報を定義する

hellorust_backend.did
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 callquery callがある。
update callは state を変更でき、query callは state を変更しない照会用として
用いられる。このupdate callquery callを用いて read/write してみる。

3.1 Backend Canister(Rust)

スレッドローカルな静的変数STOREを用意する、データ型はRefCell
query call を受けたときの動作を記述した関数には#[query]アトリビュートを付与して、
同様にupdate call の場合は#[update]アトリビュートを付与する。

lib.rs
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ファイルにインタフェース定義が必要なので、追記する

hellorust_backend.did
service : {
    "greet": (text)->(text) query;
    "getMessage": ()-> (text) query; // 追記
    "setMessage": (text)-> ();    // 追記
}

3.3 Frontend Canister を用意

画面からupdate callquery callするボタンを追加する。
テキストフィールドの値を引数としてupdate callさせたいのでテキストフィールドも追加

Message.tsx
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 callquery call繰り返し
query call のたび入力したテキストが表示されいて、Canister のメソッドを呼び出して
read/write できていることが確認できた。

4.さいごに

Canisterが提供するメソッド(サービス)を呼び出せることが確認できたわけですが、
ここまでであれば、苦労する人は少なく記事があっても嬉しい人は少ないと思います。
なので、今後はICPでの開発に必要な主要技術を動かしてみて取り上げていく予定です。

Discussion