♾️

ICP × Deno - Agent of Cloud 3.0

2024/10/12に公開

はじめに

分散型クラウド『Internet Computer (IC)』上に配置されたCanisterに対して、Denoからアクセスするエージェントのサンプルを解説します。

近年Denoのnpmモジュール互換性がだいぶ高くなってきていて、先日ついにDeno2がリリースされましたので、改めてICP関連のnpmパッケージが動作するようになったか試したところ見事に動作しました。

Node.js使えばいいじゃんと言われてしまえばその通りなのですが、私個人が感じるDenoの魅力の一つは、Node.jsのようなパッケージ管理をしなくても、小さい規模のプログラムならさっと書いて簡単に動かせるという点です。
それなりのプログラムをつくる場合にはdeno.jsonpackage.jsonでパッケージ管理する方がよいですが、外部パッケージを使用するTypeScriptのプログラムをその場で書いて実行できるシンプルさと快適さは、まさに『10 Things I Regret About Node.js』に通じるものでしょう。

Denoのランタイム実行環境は明示的にネットワークやファイルアクセス許可を求める、という点も安心して利用できる素晴らしい仕組みだと考えています。

参考情報

https://internetcomputer.org/docs/current/developer-docs/developer-tools/off-chain/agents/nodejs

サンプル概要

呼び出し元のPrincipalを返すだけの単純なCanisterをRustで作成します。
作成したCanisterに対して、TypeScriptで書いたプログラムからアクセスします。

Canister開発

Canister Development Kit (CDK)を用いてCanisterを開発します。

1. Canisterプロジェクト作成

dfx newコマンドでCanisterを作成します。

(1) Backend language

開発に使用するプログラミング言語は本記事ではRustを選択します。

$ dfx new whoami
? Select a backend language: ›  
  Motoko
❯ Rust
  Python (Kybra)
  Typescript (Azle)

(2) Frontend framework

今回のサンプルではBackendのみ用意しますのでFrontendはどれを選択しても構いません。

? Select a frontend framework: ›  
  SvelteKit
  React
  Vue
  Vanilla JS
  No JS template
❯ None

(3) Extra Features

今回のサンプルでは不要です。

? Add extra features (space to select, enter to confirm) ›
⬚ Internet Identity
⬚ Bitcoin (Regtest)

2. Canisterプログラム修正

whoamiという公開インタフェースを定義し、それに合わせてRustプログラムを修正します。

whoami/src/whoami_backend/whoami_backend.did
service : {
  "whoami": () -> (text) query;
}
whoami/src/whoami_backend/src/lib.rs
#[ic_cdk::query]
fn whoami() -> String {
  return ic_cdk::caller().to_string();
}

3. Local Canister実行環境への配備

$ cd whoami
$ dfx start --background --clean
$ dfx deploy

4. dfxコマンドによる動作確認

まずはdfx canister callコマンドでCanisterの動作を確認してみます。
--identityオプションで呼び出し元を切り替えることができます。

$ dfx canister call --identity anonymous whoami_backend whoami
("2vxsx-fae")

※2vxsx-faeは匿名を表すPrincipalです。

Denoエージェント開発

ローカルの開発環境にCanisterを配置しましたので、まずはそこにアクセスするエージェントを書いてみます。

1. Clientプログラム作成

プログラム例を記載します。
importしたActorHttpAgentIDLの関係性や、Canisterを呼び出すためのコードなど分かりづらいですが、今はあまり気にすることはないでしょう。
1つのソースファイルに収めるようためにあえて記述していますが、dfx generateコマンドを使えば、公開インターフェースに対応した呼び出し部分のJavaScriptコードは生成できます。

main.ts
import { Actor, HttpAgent } from 'npm:@dfinity/agent';
import type { IDL } from 'npm:@dfinity/candid';

// Config
const host = "http://127.0.0.1:4943"; //for Local;  for IC:"https://icp-api.io"
const shouldFetchRootKey = true; // Fetch root key for certificate validation during development
const canisterId = "bkyz2-fmaaa-aaaaa-qaaaq-cai"; // for Local

// Interface
export const idlFactory: IDL.InterfaceFactory = ({ IDL }) => {
  return IDL.Service({
    'whoami' : IDL.Func([], [IDL.Text], ['query']),
  });
};

const agent = await HttpAgent.create({
  host,
  shouldFetchRootKey
});

const backend = Actor.createActor(idlFactory, {
  agent,
  canisterId,
});

const result = await backend.whoami();
console.log(result);

2. 匿名Identityでのアクセス

まずは匿名Identityで実行してみます。

$ deno run --allow-net=127.0.0.1 main.ts
2vxsx-fae

3. 特定Identityでのアクセス

以下の3箇所を修正することで、secp256k1 ECDSA秘密鍵に紐づいたIdentityでアクセスできます。

(1) Secp256k1KeyIdentityを取り込む

main.ts (抜粋)
import { Secp256k1KeyIdentity } from 'npm:@dfinity/identity-secp256k1';

(2) Identityの作成

たとえば、Seed phraseからSecp256k1秘密鍵を復元するには、Secp256k1KeyIdentityモジュールのfromSeedPhrase()関数を利用するとよいでしょう。
その他、pemファイルから復元するなどの関数も用意されているようです。

main.ts (抜粋)
const seed = "test test test test test test test test test test test test";
const identity = Secp256k1KeyIdentity.fromSeedPhrase(seed);

※Secp256k1のSeed phraseはdfx identity new <名前>を実行することでも生成できます。取り扱いにはくれぐれもご注意ください。

(3) Identityを指定したAgent作成

main.ts (抜粋)
const agent = await HttpAgent.create({
  host,
  shouldFetchRootKey,
  identity
});

(4) プログラム実行

$ deno run --allow-net=127.0.0.1 main.ts
rwbxt-jvr66-qvpbz-2kbh3-u226q-w6djk-b45cp-66ewo-tpvng-thbkh-wae

Playgroundの利用

Local Canisterではなく、実際のInternet Computerにアクセスしたい場合、本番環境にCanisterに配備するとコストがかかります。
そのため、20分間だけCanisterを配備して試用できるPlayground環境を使うとよいでしょう。

1. Playgroundへの配置

dfx deployコマンドに--playgoundオプションを指定します。配備する際に--identity XXXXXdefault以外のIdentityを指定するとよいでしょう。

$ dfx deploy --playground --identity XXXXX
Deploying all canisters.
Reserving canisters in playground...
Reserved canister 'whoami_backend' with id 4k2wq-cqaaa-aaaab-qac7q-cai with the playground.
Building canisters...
︙
Installing canisters...
Installing code for canister whoami_backend, with canister ID 4k2wq-cqaaa-aaaab-qac7q-cai
Something is installed in canister 4k2wq-cqaaa-aaaab-qac7q-cai. Assuming new code is installed.
Deployed canisters.
URLs:
  Backend canister via Candid interface:
    whoami_backend: https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=4k2wq-cqaaa-aaaab-qac7q-cai

2. Playgroundへのアクセス

プログラム中のhostCanisterIdなどを修正します。
canisterIdには、dfx deploy --playgroundを実行した際に出力された値を指定します。

main.ts (抜粋)
const host = "https://icp-api.io"
const shouldFetchRootKey = false;
const canisterId = "4k2wq-cqaaa-aaaab-qac7q-cai"; // CanisterId in Playground

deno runコマンドを実行する際、リクエスト先が変わりますので--allow-netで許可するホストを変更します。

$  deno run --allow-net=icp-api.io main.ts
rwbxt-jvr66-qvpbz-2kbh3-u226q-w6djk-b45cp-66ewo-tpvng-thbkh-wae

将来の展望

どうしてDenoを使ってInternet ComputerのCanisterにアクセスする記事を書いたのか。

『ICP × Deno』という組み合わせは単なる自分の好みにすぎません。

Denoの視点から見ればDeno Deployをはじめ便利にWebサービスを配置できる環境がありますので、技術的にまだ成熟しているとは言えない分散型クラウドを利用するケースはまだ多くないと思います。

また、Internet Computerの視点で考えれば、Node.jsやRust、Motokoを利用するのが一般的かと思いますので、あまりDenoを利用することはないのかもしれません。

今後ますますAzure、Google Cloud、AWSをはじめ商用Cloudが活用されていくのは間違いないでしょう。その一方で、この流れとうまく棲み分けをしながら、特定ベンダーには依存せずオープンな仕組みで実現された分散型コンピューティングや分散型ストレージ、いわゆる『Cloud 3.0』が適材適所で使われていくと考えています。

Cloud 3.0を謳っているInternet Computerには、『Chain-Key Cryptography』と呼ばれる暗号技術によって実現する『しきい値署名』、BTCやEthereumなど異なるBlockchain間を結ぶ『Chain Fusion』、『VetKD (Verifiably Encrypted Threshold Key Derivation)』によるEnd-to-end encryptionの実現など様々な特徴があります。

今後Internet Computer上に自分で開発したCanisterや一般公に開されたCanisterなどが数多く配置されていく未来を予想すると、こうしたCanisterを組み合わせたサービスを実現するために、CLIからCanisterにアクセスしたり、Internet Computerの外部にあるシステムからCanisterにアクセスするケースも増えてくるでしょう。

今回のサンプルで感じたのは、現状Canisterを呼び出すだけなのによく分からないコードをたくさん書く必要がある点です。このような泥臭い部分はモジュール内に隠蔽化し、JSRのようなリポジトリから、importしてすぐ使えるようになれば便利になっていくでしょう。

Discussion