♾️

Asset Canisterの仕様

2024/08/31に公開

はじめに

Internet Computer(IC)は、Blockchain技術を応用した分散型クラウドと言われており、AWS、Google Clooud、Azureなどの特定クラウドプロバイダーに依存することなく、Canisterと呼ばれるSmart Contractを介して、インターネット上でコンピューティングを実現する仕組みを提供しています。
Blockchain技術によって分散化されたPaaS環境と捉えるとわかりやすいかもしれません。

CanisterにはWebAssemblyとデータが格納されていて、Cycleという単位の計算リソース使用料を支払うことにより、誰でもCanisterを作成して様々なサービスを提供できます。
RustやTypeScript、専用言語であるMotokoなど複数の言語でBackendを作成することができるほか、Dfinity Foundationが開発しているAsset Canisterを使用して、WebアプリケーションのFrontendを作成することもできます。

私は現在、数百年後の未来に家族の大切なデータを遺していく方法を模索しており、その一つの方法としてCanisterが利用できるのではないかと考えております。
そこで、その実現性を検討するため、まずはAsset Canisterについて少し調査してみました。


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

Asset Canisterの使用例

Asset CanisterはアップロードしたファイルをHTTPプロトコル経由で取得する仕組みを備えています。

Asset Canisterとは何かを知るには、実際に使用してみるのが分かりやすいでしょう。Local Canister実行環境、または、Playground環境にデプロイしてみましょう。

もっともシンプルなAsset Canisterの使用例として、Canisterの設定ファイルdfx.jsonと、Canisterに配置するindex.htmlの2つを用意します。
ディレクトリ構造は、dfx newコマンドが出力する合わせて深くしていますが、好みに応じて変更することもできます。

1. プロジェクトの作成

ディレクトリ構成
icptest
├── dfx.json
└── src
    └── icptest_frontend
        └── dist
            └── index.html
dfx.json
dfx.json
{
  "canisters": {
    "icptest_frontend": {
      "source": [
        "src/icptest_frontend/dist"
      ],
      "type": "assets",
      "workspace": "icptest_frontend"
    }
  },
  "defaults": {
    "build": {
      "args": "",
      "packtool": ""
    }
  },
  "output_env_file": ".env",
  "version": 1
}
src/icptest_frontend/dist/index.html
src/icptest_frontend/dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>icp test</title>
  </head>
  <body>
    <h1>hello, world</h1>
  </body>
</html>

2. Deploy

Local Canister実行環境にデプロイするには、以下のようにするとよいでしょう。
Asset Canisterへのファイル配置はdfx deployコマンドと深く結びついているようで、コマンドの実行契機でdfx.json内のsource項目に記載されたローカルディレクトリとの同期が行われています。

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

Internet Computer上のPlaygroundにデプロイするには、--playgroundオプションを指定します。
また、--identityオプションを指定すれば、ローカル開発時に使用するIDENTITYとは別にすることができます。

$ dfx deploy --identity <IDENTITY> --playground

Asset Canisterの仕様

Asset Canister I/F

Asset Canisterの仕組みを理解するために、まずは公開されているI/Fを確認してみることにしましょう。

2024年8月時点の公式ドキュメントには、以下のような記載があります。

https://internetcomputer.org/docs/current/references/asset-canister

#[update]
async fn authorize(other: Principal)

#[update]
async fn grant_permission(arg: GrantPermissionArguments)

#[update]
async fn validate_grant_permission(arg: GrantPermissionArguments) -> Result<String, String>

#[update]
async fn deauthorize(other: Principal)

#[update]
async fn revoke_permission(arg: RevokePermissionArguments)

#[update]
async fn validate_revoke_permission(arg: RevokePermissionArguments) -> Result<String, String>

#[update]
fn list_authorized() -> Vec<Principal>

#[query(manual_reply = true)]
fn list_permitted(arg: ListPermittedArguments) -> ManualReply<Vec<Principal>>

#[update]
async fn take_ownership()

#[query]
fn retrieve(key: Key) -> RcBytes

#[update(guard = "can_commit")]
fn store(arg: StoreArg)

#[update(guard = "can_prepare")]
fn create_batch() -> CreateBatchResponse

#[update(guard = "can_prepare")]
fn create_chunk(arg: CreateChunkArg) -> CreateChunkResponse

#[update(guard = "can_commit")]
fn create_asset(arg: CreateAssetArguments)

#[update(guard = "can_commit")]
fn set_asset_content(arg: SetAssetContentArguments)

#[update(guard = "can_commit")]
fn unset_asset_content(arg: UnsetAssetContentArguments)

#[update(guard = "can_commit")]
fn delete_asset(arg: DeleteAssetArguments)

#[update(guard = "can_commit")]
fn clear()

#[update(guard = "can_commit")]
fn commit_batch(arg: CommitBatchArguments)

#[query]
fn get(arg: GetArg) -> EncodedAsset

#[query]
fn get_chunk(arg: GetChunkArg) -> GetChunkResponse

#[query]
fn list() -> Vec<AssetDetails>

#[query]
fn certified_tree() -> CertifiedTree

SDK v0.22.0のDIDは以下に格納されています。

Gitリポジトリ

以下のリポジトリが用意されており、サーバ側機能はic-certified-assetsで、クライアント側機能はic-assetで実装されています。

https://github.com/dfinity/sdk/tree/master/src/canisters/frontend

Project 概要 備考
ic-asset a library for manipulating assets in an asset canister.
ic-certified-assets Certified Assets Library Rust CanisterにCertified assets機能を提供
ic-frontend-canister Asset Canister ic-certified-assetsに依存
icx-asset icx-assetコマンド ic-assetに依存

ファイル一覧の取得

サーバにアップロードされているファイルの一覧は、list()メソッドで取得できます。

以下に、Local Canister実行環境に配置したAsset Canisterに対して、Rustで書いたクライアントプログラムからアクセスする例を示します。

1. プロジェクト作成

$ cargo new client
$ cd client
$ cargo add ic-agent ic-utils candid serde
$ cargo add tokio --features "macros rt-multi-thread"

2. クライアントプログラム

src/main.rs
use candid::CandidType;
use ic_agent::Agent;
use ic_utils::call::SyncCall;
use ic_agent::identity::Secp256k1Identity;
use ic_agent::export::Principal;
use serde::Deserialize;
use std::env;
use std::collections::HashMap;

// [list]
/// Return a list of all assets in the canister.
#[derive(CandidType, Debug)]
pub struct ListAssetsRequest {}

/// Information about a content encoding stored for an asset.
#[derive(CandidType, Debug, Deserialize)]
pub struct AssetEncodingDetails {
    /// A content encoding, such as "gzip".
    pub content_encoding: String,

    /// By convention, the sha256 of the entire asset encoding.  This is calculated
    /// by the asset uploader.  It is not generated or validated by the canister.
    pub sha256: Option<Vec<u8>>,
}

/// Information about an asset stored in the canister.
#[derive(CandidType, Debug, Deserialize)]
pub struct AssetDetails {
    /// The key identifies the asset.
    pub key: String,
    /// A list of the encodings stored for the asset.
    pub encodings: Vec<AssetEncodingDetails>,
    /// The MIME type of the asset.
    pub content_type: String,
}


#[tokio::main]
async fn main() {
    // Identity
    let home = env::var("HOME").unwrap();
    let pem_path = format!("{}/.config/dfx/identity/default/identity.pem", home);
    let identity = Secp256k1Identity::from_pem_file(pem_path).unwrap();

    // Agent
    let url = "http://127.0.0.1:4943";
    let is_mainnet = false;
    let canister_id = Principal::from_text("bkyz2-fmaaa-aaaaa-qaaaq-cai").unwrap();

    let agent = Agent::builder()
        .with_url(url)
        .with_identity(identity)
        .build()
        .unwrap();
    if !is_mainnet {
        agent.fetch_root_key().await.unwrap();
    }

    // Canister
    let canister = ic_utils::Canister::builder()
        .with_agent(&agent)
        .with_canister_id(canister_id)
        .build()
        .unwrap();

    // List
    let (entries,): (Vec<AssetDetails>,) = canister
        .query("list")
        .with_arg(ListAssetsRequest {})
        .build()
        .call()
        .await.unwrap();
    let assets: HashMap<_, _> = entries.into_iter().map(|d| (d.key.clone(), d)).collect();
    println!("list: {:?}", assets);
}

3. プログラム実行

$ cargo run
list: {"/index.html": AssetDetails { key: "/index.html", encodings: [AssetEncodingDetails { content_encoding: "gzip", sha256: Some([68, 9, 14, 69, 197, 69, 48, 208, 49, 180, 87, 26, 156, 230, 121, 221, 225, 238, 251, 133, 187, 187, 162, 30, 26, 240, 129, 197, 119, 141, 81, 86]) }, AssetEncodingDetails { content_encoding: "identity", sha256: Some([136, 218, 251, 36, 243, 79, 112, 49, 76, 91, 117, 151, 73, 62, 179, 179, 192, 77, 240, 58, 38, 80, 116, 166, 112, 166, 91, 122, 160, 162, 13, 184]) }], content_type: "text/html" }}

ファイルアップロード

Asset Canisterへのファイルアップロードは、1回のリクエストサイズが2MB程度の上限があるため、クライアント-サーバ間のやりとりは少々複雑です。

大きいサイズのファイル、複数ファイルに対応したバッチアップロードを実現するために、create_batch()create_chunk()commit_batch()などいくつかのメソッドが用意されています。

ic-assetクレートを利用すれば、細かい通信手順を意識することなくファイルをアップロードを行うことができるようです。以下のような実装すればよさそうですが、ic-assetの最終アップデートが2年前と古く微妙な感じです…。

// Files
    let mut key_map: HashMap<String, PathBuf> = HashMap::new();
    key_map.insert("/favicon.ico".to_string(), PathBuf::from("favicon.ico"));

    // Upload
    let timeout = Duration::from_secs(30);
    ic_asset::upload(&canister, timeout, key_map).await.unwrap();

Node.js

Node.jsからAsset Canisterにアップロードする例は、以下のドキュメントにあるようです。

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

ファイルの格納先

Asset CanisterにアップロードされたファイルはHeap Memory上で管理されています。ディスクのような補助記憶装置に相当するStable Memoryには格納しません。
そのため、格納できるファイル容量は約1GBまでとなっています。

おわりに

Asset Canisterは、WebアプリケーションなどFrontendをデプロイする環境として十分に使えるものですが、分散型ファイルストレージという用途としては物足らなさを感じます。

Canisterを分散型ファイルストレージとして利用するには、クライアントとのI/Fをオープンな仕様に整理して使いやすくするとともに、データ格納先もStable Memoryとし、内部のデータ構造もOSのファイルシステムのようにしっかりとした仕組みが必要となります。

正直なところ、Google DriveOneDriveiCloudといった商用クラウドのオンラインストレージは、ローカルPCやスマートフォンとの自動同期が行えたり、ファイルの共有やアプリケーション連携等、とても便利で素晴らしいものです。

数百年後の未来に大切なデータを遺していくという目的のために、『特定ベンダーに依存せずにオープンな仕様でデータを管理していくこと』がそもそも最適解なのかという点や、Internet ComputerのCanisterを分散型ファイルストレージとして扱えるようにする場合、どのように設計していけば良いかについて、今後、考えていければと思います。

Discussion