nicp_cdk — ICP Playground デプロイ機能 設計書
1. 概要
本設計では、nicp_cdk のCLIである nicp に、DFINITYが提供するICP Playgroundへcanisterを一時デプロイするための専用コマンドを追加する。
ユーザー向けコマンドは次の形とする。
特定canisterのみを対象とする場合は、
とする。
この機能は通常のdeployment機能の特殊モードではない。
のようなインターフェースは採用しない。
nicp playground は、
DFINITY Playground Poolを利用して、実際のICP mainnet上に短命なcanisterを確保・インストール・更新するための専用operation
として独立して実装する。
2. 目的
主な目的は、nicp_cdk を利用するアプリケーションについて、
Nim backend
+
frontend
+
ICP mainnet
を含む実環境に近い統合テストを、cyclesを消費せず短時間実行できるようにすることである。
想定する開発フローは次の通り。
unit test
↓
PocketIC / local
↓
nicp playground
↓
実ICP上で統合テスト
↓
staging / production
対象となるテストには、
Nim backend canisterの実ICP動作確認
frontend → backend通信
Candid interface統合
query / update call
ICP HTTP Gateway経由のアクセス
Internet Identity等を利用するブラウザ統合
HTTPS outcall等、実ネットワークに依存する機能
upgrade時の状態保持
frontend asset canisterとの統合
を含む。
3. 非目的
nicp playground は以下を目的としない。
通常環境については引き続き icp-cli を利用する。
icp deploy -e staging
icp deploy -e production
Playgroundだけが、
という専用経路を通る。
4. dfx --playground の調査結果
4.1 Playgroundは独立したtestnetではない
dfx の現行実装ではPlaygroundは、ICP mainnetのAPI Gatewayへ接続する特殊なnetwork descriptorとして定義されている。
デフォルト値は、
network name:
playground
provider:
ICP mainnet gateway
Playground Pool:
mwrha-maaaa-aaaab-qabqq-cai
TTL:
1200 seconds
である。
つまり構造は、
nicp / dfx
│
│ ICP ingress
▼
ICP mainnet
│
▼
Playground Pool Canister
mwrha-maaaa-aaaab-qabqq-cai
│
├── temporary canister A
├── temporary canister B
└── temporary canister C
となる。
したがってPlayground対応のために独自replicaや独自ネットワークを構築する必要はない。
5. Playgroundのcanister取得方式
通常のmainnet deploymentでは、cycles ledgerやManagement Canisterを利用してcanisterを新規作成する。
一方、dfx はPlaygroundの場合、
create_canister()
│
├── normal network
│ ↓
│ cycles ledger / management canister
│
└── playground
↓
reserve_canister_with_playground()
へ分岐する。
つまりPlaygroundでは、
canisterを作成するのではなく、Poolから短命なcanisterを借りる。
6. getCanisterId
canisterを確保する際、Playground Poolの、
をupdate callする。
概念的な入力は、
type Nonce = record {
timestamp : int;
nonce : nat;
};
type Origin = record {
origin : text;
tags : vec text;
};
getCanisterId : (Nonce, Origin) -> (CanisterInfo);
戻り値は、
type CanisterInfo = record {
id : principal;
timestamp : int;
};
である。dfx もcanister IDだけでなく取得timestampを保存している。
Playgroundにおいて、
の組は一つのopaque handleとして扱う必要がある。
canister IDだけを保存してはいけない。
7. Proof of Work
getCanisterId は簡易Proof of Workを要求する。
dfx は現在、
timestamp = current Unix time in nanoseconds
nonce = random u32
から、
"motoko-playground" + timestamp + nonce
という文字列を生成する。
その文字列にDJB2系hashを適用する。
概念的には、
hash = 5381
for UTF-16 code unit in input:
hash = hash * 33 + code_unit
で、32bit unsigned integerとしてoverflowさせる。
以下を満たせばPoW成功。
満たさなければ、
して再試行する。
8. Playground controllerモデル
Playgroundで借りたcanisterは、ユーザーidentityをcontrollerにはしない。
controllerは引き続き、
である。
そのためユーザーから直接、
Management Canister
├── install_code
├── update_settings
└── delete_canister
を実行することはできない。
したがって、Playground canisterに対して、
をそのまま使う設計は採用できない。
9. installCode
Wasmのinstall / upgrade / reinstallはPoolの、
を通して実施する。
概念的な型は次の通り。
type CanisterInfo = record {
id : principal;
timestamp : int;
};
type WasmMemoryPersistence = variant {
keep;
replace;
};
type InstallMode = variant {
install;
reinstall;
upgrade : opt record {
wasm_memory_persistence : opt WasmMemoryPersistence;
};
};
type InstallArgs = record {
arg : blob;
wasm_module : blob;
mode : InstallMode;
canister_id : principal;
};
type Origin = record {
origin : text;
tags : vec text;
};
type InstallConfig = record {
profiling : bool;
is_whitelisted : bool;
origin : Origin;
start_page : opt nat32;
page_limit : opt nat32;
};
処理は、
PlaygroundClient
↓
Pool.installCode
↓
Pool validates CanisterInfo
↓
Wasm transformation
↓
Management Canister.install_code
↓
Pool refresh
↓
new CanisterInfo
となる。dfx もPoolの installCode を直接利用している。
10. Wasm transformation
通常のbackend Wasmはそのままinstallされない。
Playground PoolはWasmをtransformし、Playground環境用の制約を適用する。
現在のPoolでは例えば、
heap memory <= 1 GiB
stable memory <= 1 GiB
という制限や、cycles・Management Canister callへの変換処理が行われる。
Pool自身にはManagement Canister互換のwrapperが存在し、
create_canister
install_code
uninstall_code
canister_status
start_canister
stop_canister
delete_canister
...
等をPlayground canisterから利用できるようにしている。
したがって、Playground対応のためにNim canister runtimeそのものへ特殊処理を組み込む必要はない。
Playground deploymentはCLI側の責務とする。
11. TTL
デフォルトPlayground PoolのTTLは、
1200 seconds
20 minutes
である。
さらに重要なのは、successful install / upgradeによってTTLが更新されることである。
installCode 完了後、Poolは、
を実行し、新しいtimestampを返す。
例:
12:00 reserve
12:01 install
↓
有効期限 ≈ 12:21
12:15 upgrade
↓
timestamp refresh
↓
有効期限 ≈ 12:35
したがって nicp playground は、installCode が返した最新timestampを必ず保存する。
12. CLI仕様
基本コマンド:
対象canister指定:
新しいcanisterを強制的に取得:
既存canisterへreinstall:
nicp playground --reinstall
frontend asset syncを省略:
nicp playground --no-sync
v1ではofficial Playgroundをデフォルトとする。
将来拡張として、
nicp playground --pool-canister <principal>
を検討する。
13. 採用しないCLI
以下は採用しない。
nicp deploy --playground
nicp deploy backend --playground
また、
proc deploy*(playground = false)
のようなAPIも採用しない。
通常deploymentとPlaygroundではcanister lifecycleそのものが異なるためである。
通常:
create
↓
user-controlled canister
↓
install_code
↓
persistent
Playground:
reserve
↓
pool-controlled canister
↓
Pool.installCode
↓
TTL
↓
reuse
これらを一つのdeploy abstractionへ統合すると、
cycles
controller
settings
install method
state
expiration
frontend authorization
retry
等のPlayground固有分岐が通常deployment側へ漏れる。
14. CLI entrypoint
現在の src/cli/nicp.nim に、
import ./nicp_functions/playground_impl
を追加する。
dispatchMulti は、
when isMainModule:
import cligen
dispatchMulti(
[new_impl.new],
[c_headers_impl.cHeaders],
[development_build_impl.developmentBuild],
[production_build_impl.productionBuild],
[network_impl.network],
[playground_impl.playground]
)
とする。
現行CLIのentrypointは dispatchMulti に各operationを直接登録する構成なので、この追加方法が既存設計と整合する。
15. playground_impl.nim
新規ファイル:
src/cli/nicp_functions/playground_impl.nim
ユーザー向けentrypointは、
proc playground*(
canister: string = "",
fresh: bool = false,
reinstall: bool = false,
noSync: bool = false
): int
とする。
責務はCLIとの境界だけとする。
proc playground*(...): int =
let options = PlaygroundOptions(
canister: canister,
fresh: fresh,
reinstall: reinstall,
noSync: noSync
)
try:
let result = runPlayground(options)
printPlaygroundResult(result)
return 0
except PlaygroundError as e:
printPlaygroundError(e)
return 1
playground_impl.nim に、
PoW
Candid encoding
state storage
subprocess execution
Pool protocol
等は実装しない。
16. 内部関数名
CLI関数名:
内部orchestrator:
proc runPlayground*(
options: PlaygroundOptions
): PlaygroundResult
とする。
deploy() という名前はユーザー向けAPIには使用しない。
内部でも、
より、
runPlayground
reserveCanister
installPlaygroundCanister
syncPlaygroundAssets
のように責務が明示された名前を使用する。
17. モジュール構造
推奨構造:
src/
└── cli/
├── nicp.nim
│
└── nicp_functions/
├── new_impl.nim
├── c_headers_impl.nim
├── development_build_impl.nim
├── production_build_impl.nim
├── network_impl.nim
│
├── playground_impl.nim
│
└── playground/
├── config.nim
├── pow.nim
├── protocol.nim
├── transport.nim
├── state.nim
├── artifact_store.nim
├── asset.nim
├── project.nim
├── runner.nim
└── playground.did
18. モジュール責務
config.nim
Playground固有定数。
const
OfficialPlaygroundCanister* =
"mwrha-maaaa-aaaab-qabqq-cai"
PlaygroundTtlSeconds* = 1200
PlaygroundEnvironment* = "playground"
pow.nim
Proof of Work。
type
PlaygroundNonce* = object
timestamp*: CandidInt
nonce*: CandidNat
proc createPlaygroundNonce*(): PlaygroundNonce
proc motokoHash*(input: string): uint32
proc validProof*(timestamp: CandidInt, nonce: CandidNat): bool
ネットワーク依存性を持たせない。
protocol.nim
Playground protocolの高レベル型。
type
PlaygroundCanisterInfo* = object
id*: Principal
timestamp*: CandidInt
PlaygroundInstallMode* = enum
pimInstall
pimUpgrade
pimReinstall
PlaygroundClient* = ref object
transport*: PlaygroundTransport
poolCanister*: Principal
API:
proc reserve*(
client: PlaygroundClient
): PlaygroundCanisterInfo
proc install*(
client: PlaygroundClient,
info: PlaygroundCanisterInfo,
wasm: seq[byte],
initArg: seq[byte],
mode: PlaygroundInstallMode,
isAssetCanister: bool
): PlaygroundCanisterInfo
proc forward*(
client: PlaygroundClient,
info: PlaygroundCanisterInfo,
methodName: string,
args: seq[byte]
): seq[byte]
transport.nim
icp-cli を利用したnetwork transport。
type
PlaygroundTransport* = ref object
proc call*(
transport: PlaygroundTransport,
canisterId: Principal,
methodName: string,
candidFile: string,
argsFile: string
): PlaygroundCallResult
identity・署名・ingress・request polling等は icp-cli に委譲する。
state.nim
Playground reservationの状態管理。
artifact_store.nim
icp build が生成したWasm artifactの取得。
asset.nim
frontend asset canisterの、
を担当する。
project.nim
icp.yaml のPlayground environmentや対象canisterの検証。
runner.nim
Playground operation全体のorchestration。
中心API:
proc runPlayground*(
options: PlaygroundOptions
): PlaygroundResult
19. icp-cli をtransportとして使用する
Playground protocolを実装するために、Nim側で、
HTTP transport
request envelope
CBOR
identity signing
delegation
ingress expiry
request status polling
certificate verification
を再実装するのは避ける。
代わりに、
nicp
│
│ subprocess
▼
icp-cli
│
│ signed ICP ingress
▼
ICP mainnet
とする。
現行 icp canister call は任意canister IDに対し、Candid interface・引数形式・update/query等を扱えるため、このtransport用途に利用できる。
20. playground.did
nicp_cdk repository内にPlayground Pool用Candid interfaceを保持する。
例:
type CanisterInfo = record {
id : principal;
timestamp : int;
};
type Nonce = record {
timestamp : int;
nonce : nat;
};
type Origin = record {
origin : text;
tags : vec text;
};
type WasmMemoryPersistence = variant {
keep;
replace;
};
type InstallMode = variant {
install;
reinstall;
upgrade : opt record {
wasm_memory_persistence : opt WasmMemoryPersistence;
};
};
type InstallArgs = record {
arg : blob;
wasm_module : blob;
mode : InstallMode;
canister_id : principal;
};
type InstallConfig = record {
profiling : bool;
is_whitelisted : bool;
origin : Origin;
start_page : opt nat32;
page_limit : opt nat32;
};
service : {
getCanisterId :
(Nonce, Origin) -> (CanisterInfo);
installCode :
(CanisterInfo, InstallArgs, InstallConfig)
-> (CanisterInfo);
callForward :
(CanisterInfo, text, blob)
-> (blob);
};
Pool upstream変更時に差分を確認できるよう、このファイルには参照元DFINITY SDK revisionをコメントで残す。
21. Candid int / nat 型
Playground実装を契機として、nicp_cdk のCandid型対応も修正する。
Candidの、
は固定bit幅整数ではない。
したがって、
Candid int ≠ Nim int
Candid int ≠ int64
Candid nat ≠ uint
Candid nat ≠ uint64
とする。
22. CandidNat
推奨型:
type
CandidNat* = object
limbs*: seq[uint32]
little-endian limbとする。
正規化条件:
zero:
limbs.len == 0
non-zero:
limbs[^1] != 0
23. CandidInt
type
CandidInt* = object
negative*: bool
magnitude*: CandidNat
zeroの場合、
negative = false
magnitude = 0
へ正規化する。
24. CandidValue変更
現状の、
of ctInt:
intVal*: int
of ctNat:
natVal*: uint
を、
of ctInt:
intVal*: CandidInt
of ctNat:
natVal*: CandidNat
へ変更する。
固定幅型は変更しない。
int8 → int8
int16 → int16
int32 → int32
int64 → int64
nat8 → uint8
nat16 → uint16
nat32 → uint32
nat64 → uint64
25. 任意精度LEB128
既存の、
proc encodeSLEB128*(n: int32): seq[byte]
は固定幅整数用helperとして残してもよいが、Candid int encodingには使用しない。
追加API:
proc encodeULEB128*(
value: CandidNat
): seq[byte]
proc encodeSLEB128*(
value: CandidInt
): seq[byte]
proc decodeULEB128Big*(
data: openArray[byte],
offset: var int
): CandidNat
proc decodeSLEB128Big*(
data: openArray[byte],
offset: var int
): CandidInt
現在のencoderでは ctInt が固定幅整数へ狭められる経路があるため、任意精度化は独立した修正対象になる。
26. convenience conversion
ユーザーが常にlimbを直接扱う必要はない。
proc candidInt*(value: int): CandidInt
proc candidInt*(value: int64): CandidInt
proc candidInt*(value: string): CandidInt
proc candidNat*(value: uint): CandidNat
proc candidNat*(value: uint64): CandidNat
proc candidNat*(value: string): CandidNat
を提供する。
例えば、
let timestamp =
candidInt("1787000000000000000")
のように利用できる。
27. Playground v1とCandid encoderの依存関係
Playground v1ではtransportに icp-cli を利用するため、Pool callの引数はCandid textとして渡せる。
したがって、
Playground機能完成をCandid arbitrary precision対応でブロックしない。
実装順として、
Playground transport
│
└── icp-cli Candid encoding
を利用可能とする。
ただし nicp_cdk 全体としては CandidInt / CandidNat 対応を並行して実装する。
最終的にはPlayground protocol内部型にも CandidInt / CandidNat を使用する。
28. Origin
dfx はPlayground requestのoriginとして、
を使用する。
nicp はこれを偽装しない。
origin = "nicp"
tags = []
とする。
Poolは空origin等を拒否する実装になっている。
これによってDFINITY側の統計でも、
を区別できる。
29. Playground state
保存場所:
.icp/cache/nicp-playground/state.json
形式:
{
"schema_version": 1,
"pool_canister": "mwrha-maaaa-aaaab-qabqq-cai",
"ttl_seconds": 1200,
"environment": "playground",
"canisters": {
"backend": {
"id": "xxxxx-xxxxx-...",
"timestamp_nanos": "1787000000000000000"
},
"frontend": {
"id": "yyyyy-yyyyy-...",
"timestamp_nanos": "1787000000000000000"
}
}
}
timestampはJSON numberではなくdecimal stringとする。
理由:
30. state validity
local state上では、
なら再利用候補とする。
ただし境界付近のraceを避けるため安全marginを置く。
例えば、
TTL = 1200 sec
safety = 30 sec
reuse limit = 1170 sec
とする。
age < 1170 sec
↓
upgrade候補
age >= 1170 sec
↓
新規reserve
local clock skewやPool側state不一致があるため、これはあくまでoptimistic判定である。
最終的な正しさはPoolの installCode responseで判断する。
31. icp-cli environment
生成する icp.yaml に、
environments:
name: playground
network: ic
canisters:
を追加する。
このenvironmentは、
を実行するためのものではない。
nicp playground が、
icp canister link
icp build
icp sync
等のicp-cli project functionalityを利用するために存在する。
READMEには、
The playground environment is managed by nicp playground.
Do not deploy it with icp deploy -e playground.
と明記する。
32. icp canister link
Poolから得たcanister IDを icp-cli project modelへ登録する。
icp canister link
backend
<principal>
-e playground
--force
icp-cli は外部で作成されたcanister IDをenvironmentへ関連付ける canister link を正式に持っている。
構造:
Playground Pool
↓
CanisterInfo
↓
nicp
↓
icp canister link
↓
icp-cli ID mapping
33. ephemeral mapping
connected networkのID mappingは icp-cli により、
.icp/data/mappings/<environment>.ids.json
へ保存される。
そのためPlaygroundでは、
.icp/data/mappings/playground.ids.json
が生成される。
これは20分で無効になるため、Gitへcommitしてはいけない。
生成テンプレートの .gitignore に、
# Ephemeral ICP Playground mapping
.icp/data/mappings/playground.ids.json
を追加する。
一方、
.icp/data/mappings/staging.ids.json
.icp/data/mappings/production.ids.json
等には影響を与えない。
34. nicp new の変更
現在生成されるプロジェクトに、
- name: playground
network: ic
を追加する。
frontendあり:
environments:
name: local
network: local
canisters:
name: playground
network: ic
canisters:
name: staging
network: ic
canisters:
name: production
network: ic
canisters:
backend only:
- name: playground
network: ic
canisters:
backend
35. Playgroundではsettingsを付けない
現在のテンプレートではstaging/productionに、
settings:
backend:
environment_variables:
APP_ENV: "staging"
等を設定している。
Playgroundではこれを設定しない。
- name: playground
network: ic
canisters:
backend
frontendのみとする。
理由は、canister controllerがPlayground Poolであり、通常の update_settings をユーザーidentityから実行できないからである。Pool側も設定変更を制限している。
36. unsupported settings検出
nicp playground のpreflightで、Playground environmentに通常canister settingsが指定されていないか検査する。
対象例:
controllers
environment_variables
compute_allocation
memory_allocation
freezing_threshold
reserved_cycles_limit
...
存在する場合、silent ignoreしない。
エラー例:
Playground canisters remain controlled by the ICP Playground pool.
The following canister settings cannot be applied:
backend.environment_variables
Remove these settings from the playground environment.
37. backend build
全target canisterについて、
を完了してからbuildする。
順序:
Resolve ALL canister IDs
↓
Link ALL canister IDs
↓
Build
↓
Install
とする。
これはfrontend/backend間でID依存がある場合に、build開始前に全IDを確定させるためである。
38. 現行Nim build scriptの修正
現行生成テンプレートでは、
を使ってdevelopment / production buildを切り替えている。
icp-cli ベースのprojectとしては、
を使用するべきである。
修正:
if [ "${ICP_CLI_ENVIRONMENT:-local}" = "local" ]; then
nicp developmentBuild
else
nicp productionBuild
fi
icp-cli はbuild scriptへ ICP_CLI_ENVIRONMENT と ICP_WASM_OUTPUT_PATH を渡す。
結果:
local → developmentBuild
playground → productionBuild
staging → productionBuild
production → productionBuild
39. artifact取得
icp build のWasm artifactを取得する。
現在の icp-cli はbuild artifact storeを、
配下に持つ。
ただしこれは icp-cli 内部実装であるため、直接参照箇所を、
だけに限定する。
proc findBuiltWasm*(
projectRoot: string,
canisterName: string
): string
将来 icp-cli がartifact APIを公開した場合は、この実装だけ差し替える。
40. gzip Wasm
Playground Poolへ渡すWasmはraw Wasmを基本とする。
artifact先頭が、
ならgzip artifactと判定する。
v1では自動展開よりも明示的エラーを推奨する。
The built Wasm artifact is gzip-compressed.
ICP Playground installation requires an uncompressed Wasm module.
必要になれば将来transparent decompressionを追加する。
41. install mode
状態によってmodeを決定する。
新規reserve:
有効なstateあり:
--reinstall:
--fresh:
existing stateを使用せずreserve
→ install
42. --fresh
デフォルトでは有効な既存Playground canisterを再利用する。
理由:
canister IDを維持できる
frontend/backend参照が維持される
Pool capacityを不必要に消費しない
upgradeでTTLを更新できる
--fresh の場合のみ新規reservationを取得する。
古いcanisterはPoolのTTL回収に任せる。
43. stale state recovery
state上では有効でも、Pool側では既に無効になっていることがある。
例:
state:
backend = { id=A, timestamp=T }
Pool:
A already expired/recycled
installCode が、
等でrejectした場合、
install
↓
stale error
↓
remove local state
↓
reserve new ID
↓
icp canister link --force
↓
rebuild if required
↓
retry install once
とする。
retry回数は1回に限定する。
無限retryは禁止。
44. ID変更時のrebuild
一つでもcanister IDが変わった場合、frontend等にIDが埋め込まれる可能性がある。
したがって、
backend A
↓ stale
backend B
となったら、
ID resolution phase
↓
全canister ID再確定
↓
build phaseから再開
する。
個別installだけをretryしない。
45. frontend asset canister
frontendが存在する場合、
backend
frontend asset canister
の2つをPlaygroundからreserveする。
frontendのasset canister WasmはPoolのallowlist対象としてinstallする。
そのため、
を指定する。dfx もasset canisterの場合にこの扱いを行う。
backendについては、
とする。
46. asset uploader authorization
Poolがfrontend canisterのcontrollerなので、user identityをasset uploaderとしてauthorizeする必要がある。
まず、
で現在identityのPrincipalを取得する。icp-cli は選択中identityのPrincipalを出力するcommandを提供している。
次に、
Pool.callForward(
frontend CanisterInfo,
"authorize",
candid(user principal)
)
を実行する。
dfx も同じ callForward を使ってasset uploaderをauthorizeしている。
47. frontend sync
authorize後、
icp sync frontend -e playground
を実行する。
ただし icp-cli のsync recipeがPlayground linked canisterでそのまま利用可能かはintegration testで固定する。
v1の実装では、
install asset Wasm
↓
authorize identity
↓
icp sync
を標準経路とし、失敗時はupstream stderrを保持する。
48. frontendのcanister ID discovery問題
現行 icp-cli のfrontendでは、
PUBLIC_CANISTER_ID:backend
等をcanister environment variableとして設定し、asset canisterが ic_env cookieを提供する仕組みがある。
これらのbinding設定は update_settings を利用する。
しかしPlaygroundではユーザーはcontrollerではない。
したがって、
通常の icp-cli deploymentが行うruntime canister environment bindingを、そのままPlaygroundで再現することはできない。
49. nicp生成frontendの解決策
nicp new が生成するfrontendには、Playground専用fallbackを設ける。
backend ID確定後、
VITE_NICP_CANISTER_ID_BACKEND=<principal>
VITE_NICP_NETWORK=playground
をbuild/sync subprocess environmentへ注入する。
frontend側:
const backendCanisterId =
canisterEnv?.["PUBLIC_CANISTER_ID:backend"] ??
import.meta.env.VITE_NICP_CANISTER_ID_BACKEND;
通常環境:
PUBLIC_CANISTER_ID:backend
Playground:
VITE_NICP_CANISTER_ID_BACKEND
を利用する。
これにより通常deploymentの仕組みを維持しながらPlaygroundだけfallbackできる。
50. generic frontendへの対応
任意の既存frontendについて自動変換までは行わない。
runtime bindingだけに依存するfrontendを検出できた場合は、
This frontend requires runtime PUBLIC_CANISTER_ID bindings.
ICP Playground canisters are controlled by the Playground pool,
so these settings cannot be applied automatically.
Configure a Playground build-time canister ID fallback.
と明示する。
silent failureは禁止。
51. init arguments
Playground installは、
を必要とする。
backendがinit args不要の場合は、Candid空tuple、
をserializeして使用する。
将来的には、
proc resolveInitArgs*(
canisterName: string,
environment: string
): seq[byte]
を実装する。
ただし icp.yaml resolutionロジックを nicp に丸ごと複製するのは避ける。
可能な限り icp-cli 側のmanifest resolutionを再利用する。
52. runner.nim
中心データ型:
type
PlaygroundOptions* = object
canister*: string
fresh*: bool
reinstall*: bool
noSync*: bool
PlaygroundDeployment* = object
name*: string
canisterId*: Principal
timestamp*: CandidInt
isFrontend*: bool
PlaygroundResult* = object
canisters*: seq[PlaygroundDeployment]
中心関数:
proc runPlayground*(
options: PlaygroundOptions
): PlaygroundResult
53. 全体state machine
nicp playground
│
▼
┌────────────────────┐
│ Preflight │
├────────────────────┤
│ icp-cli exists │
│ icp.yaml exists │
│ playground env │
│ settings validate │
│ CI restriction │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Load State │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Resolve Targets │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Resolve IDs │
├────────────────────┤
│ reuse or reserve │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Link All IDs │
│ via icp-cli │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Build │
│ -e playground │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Install Backend │
│ via Pool │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Install Frontend │
│ if present │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Authorize Uploader │
└──────────┬─────────┘
▼
┌────────────────────┐
│ icp sync │
│ unless --no-sync │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Save State │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Print Result │
└────────────────────┘
54. preflight
最低限以下を検査する。
icp executable exists
icp.yaml exists
playground environment exists
target canister exists
no unsupported Playground settings
official Playground not used from CI
Playground DID available
project state directory writable
frontendありの場合:
frontend fallback configuration
asset sync capability
も検査する。
55. CI制限
現行 dfx はCIを検知し、official mainnet Playground Poolへのreservationを拒否する。
nicp もこの方針を維持する。
if CI == true
and pool == OfficialPlaygroundCanister
→ error
--force-ci のような回避optionは設けない。
将来custom/local Playground Poolをサポートした場合、そのPoolについてはCI利用を許可できる。
56. Pool capacity
Poolに利用可能なcanisterがない場合、Poolは待機時間を含むrejectを返す実装を持つ。
この場合自動retryは行わない。
表示:
ICP Playground is currently at capacity.
The Playground pool reported that no temporary canister is available.
Try again after approximately N seconds.
57. asset Wasm allowlist mismatch
asset canister WasmがPool allowlistに存在しない場合を専用errorとする。
dfx もこのケースを特別扱いしている。
表示:
The asset canister Wasm used by the installed icp-cli
is not currently allowlisted by ICP Playground.
This is an upstream Playground / asset-canister version mismatch.
58. Error model
type
PlaygroundErrorKind* = enum
peIcpCliMissing
peProjectNotFound
peEnvironmentMissing
peTargetNotFound
peUnsupportedSettings
peCiNotAllowed
pePoolUnavailable
pePoolCapacity
peInvalidProof
peInvalidResponse
peStaleCanister
peBuildFailed
peArtifactMissing
peCompressedArtifact
peInstallRejected
peAssetNotWhitelisted
peAuthorizeFailed
peSyncFailed
peStateReadFailed
peStateWriteFailed
高レベル理由とupstream stderrを分離する。
例:
Failed to reserve an ICP Playground canister.
Cause:
Playground proof-of-work was rejected.
Upstream:
<icp-cli / replica error>
59. subprocess abstraction
外部commandを直接各moduleから呼ばない。
type
CommandRunner* = ref object
proc run*(
runner: CommandRunner,
command: string,
args: seq[string],
extraEnv: Table[string, string] = initTablestring, string
): CommandResult
これを、
icp canister call
icp canister link
icp build
icp sync
icp identity principal
全てに利用する。
testではfake runnerへ交換できる。
60. security
以下を遵守する。
identity secretを nicp が読む必要はない
PEM/private keyをstateへ保存しない
subprocess command lineへsecretを追加しない
Playground stateはcanister IDとtimestampのみ
upstream outputにsecretが含まれ得る場合はlog levelを考慮
custom pool principalは必ずPrincipal validationする
identity管理は icp-cli に委譲する。
61. Logging
通常:
Reserving Playground canister 'backend'...
Reserved backend: xxxxx-...
Linking Playground canister IDs...
Building project for playground...
Installing backend...
Installed backend.
Installing frontend...
Authorizing asset uploader...
Syncing frontend assets...
ICP Playground deployment completed.
--debug 相当の既存logging方針があれば、
exact icp-cli command
raw Pool response
state transition
selected install mode
を追加表示する。
62. 成功時出力
backend only:
ICP Playground deployment completed.
backend
Canister ID: xxxxx-xxxxx-...
Expires: approximately 20 minutes after the latest install
frontendあり:
ICP Playground deployment completed.
backend
Canister ID: xxxxx-xxxxx-...
frontend
Canister ID: yyyyy-yyyyy-...
URL: https://yyyyy-yyyyy-....icp0.io
Temporary canisters expire approximately 20 minutes
after their latest successful installation.
63. Unit test — PoW
固定vectorで検証する。
timestamp
nonce
expected hash
expected valid/invalid
DFINITY SDKのRust実装と同じ値になるfixtureを用意する。
境界:
hash & 0xC0000000 == 0
hash & 0xC0000000 != 0
64. Unit test — Candid integer
最低限、
0
1
-1
127
128
-128
-129
int64.high
int64.low
int64.high + 1
10^100
-(10^100)
をencode/decode round tripする。
nat:
0
127
128
uint64.high
uint64.high + 1
10^100
を確認する。
65. Unit test — state
検証項目:
66. Unit test — protocol
fake PlaygroundTransport を使う。
検証:
reserve
install
upgrade
reinstall
callForward
asset install
Candid argumentsもfixture化する。
67. Unit test — orchestration
fake CommandRunner とfake PlaygroundClient を利用する。
期待されるcommand順:
reserve backend
reserve frontend
link backend
link frontend
build
install backend
install frontend
identity principal
authorize frontend
sync frontend
を検証する。
68. stale retry test
existing state A
↓
install A
↓
stale error
↓
reserve B
↓
link B
↓
rebuild
↓
install B
を確認する。
二回目も失敗した場合は終了する。
69. integration test
DFINITY SDKにあるPlayground backend実装を利用または参考にし、ローカルPoolで検証する。
backend test:
reserve
↓
build Nim greet
↓
install
↓
call greet
↓
upgrade
↓
same canister ID
↓
timestamp changed
frontend test:
reserve backend
reserve frontend
↓
install backend
install asset
↓
authorize
↓
sync
↓
browser
↓
frontend → backend
70. official Playground smoke test
official Poolを使う自動CIテストは行わない。
release前のmanual smoke testとする。
最低確認:
その後、
icp canister call backend greet '("ICP")' -e playground
等で実canisterを確認する。
続けて、
を再実行し、
same canister ID
new timestamp
upgrade succeeded
を確認する。
71. frontend smoke test
実行後、
frontend URL
↓
Browser
↓
frontend asset canister
↓
backend Playground canister
が成立することを確認する。
Playwright等による手動/ローカルE2Eも可能にする。
official Poolを使ったCI自動化は行わない。
72. 実装Phase 1 — backend
最初にbackend onlyを完成させる。
対象:
playground command
playground_impl
runner
config
PoW
transport
protocol
state
project validation
reserve
canister link
build
artifact lookup
installCode
upgrade
reinstall
stale recovery
logging
unit tests
受け入れ条件:
で実ICP上にNim backendをデプロイできる。
73. Phase 2 — Candid arbitrary precision
並行またはPhase 1直後に、
CandidInt
CandidNat
big ULEB128
big SLEB128
CandidValue migration
tests
を実装する。
Playground timestampを含め、Candid仕様に正しい整数モデルへ移行する。
74. Phase 3 — frontend
追加:
asset canister detection
whitelisted install
identity principal
callForward authorize
icp sync
build-time canister ID fallback
frontend template changes
browser integration test
75. Phase 4 — hardening
追加候補:
environment-specific init args
arbitrary multi-canister projects
custom Playground Pool
local Playground Pool
asset canister version detection
icp-cli artifact path依存の除去
generic frontend ID injection hooks
richer Playground status command
manual release/retire operation
76. nicp playground の責務境界
nicp playground が行うもの:
PoW
Pool reservation
Playground state
icp-cli ID link
Playground build orchestration
Pool.installCode
asset authorization
frontend sync
TTL handling
stale recovery
行わないもの:
normal canister creation
cycles ledger
wallet handling
production controller management
normal icp deploy
identity private key management
ICP Agent implementation
77. architecture summary
nicp
│
▼
playground_impl
│
▼
PlaygroundRunner
│
┌──────────────┼───────────────┐
│ │ │
▼ ▼ ▼
ProjectResolver StateStore PlaygroundClient
│
┌───────┴───────┐
│ │
▼ ▼
PoW Transport
│
▼
icp-cli
│
┌────────────────────────────┼─────────────┐
│ │ │
▼ ▼ ▼
build/link call sync
│ │ │
└───────────────┬────────────┴─────────────┘
▼
ICP mainnet
│
▼
Playground Pool
│
┌─────────┴──────────┐
▼ ▼
backend frontend
78. 最重要設計判断
| 項目 |
判断 |
| CLI |
nicp playground |
| CLI関数 |
playground_impl.playground |
| 汎用deploy関数 |
作らない |
| orchestrator |
runner.runPlayground |
| 通常deployment |
icp deploy に任せる |
| Playground create |
Pool getCanisterId |
| Playground install |
Pool installCode |
| identity / signing |
icp-cli |
| project ID integration |
icp canister link |
| build |
icp build -e playground |
| frontend upload |
authorize後 icp sync |
| state |
{id, timestamp} |
| TTL |
1200秒 |
| stale recovery |
1回だけ再reserve |
| Candid int |
CandidInt 任意精度 |
| Candid nat |
CandidNat 任意精度 |
| official Playground CI |
禁止 |
| environment settings |
Playgroundでは禁止 |
| frontend ID |
build-time fallback |
79. Acceptance criteria
Backend
以下をすべて満たす。
✓ dfx不要
✓ icp-cli + nicpのみで動作
✓ cycles不要
✓ nicp playground backend が成功
✓ 実ICP mainnet上へNim Wasmをinstall
✓ 初回はPoolからcanisterをreserve
✓ 同一セッション内の再実行では同じIDへupgrade
✓ upgradeでTTL更新
✓ expired/stale canisterから自動recover
✓ retryは最大1回
✓ ephemeral mappingをGitへcommitしない
✓ production/staging mappingを変更しない
✓ official PlaygroundをCIで使用しない
Frontend
✓ frontend canisterもPoolからreserve
✓ asset canister Wasmをallowlisted install
✓ current identityをauthorize
✓ icp syncでasset upload
✓ generated frontendからbackend IDを解決
✓ frontend → backend call成功
✓ unsupported runtime bindingをsilent ignoreしない
Candid
✓ Candid intを固定幅Nim intとして扱わない
✓ Candid natを固定幅uintとして扱わない
✓ arbitrary precision LEB128を実装
✓ Playground timestampをlosslessに扱う
80. 結論
nicp_cdk におけるICP Playground対応は、通常deploymentのoptionとして実装しない。
外部インターフェースは、
とし、実装入口も、
playground_impl.playground()
とする。
内部処理は、
nicp playground
↓
runPlayground()
↓
PoW
↓
Pool.getCanisterId
↓
CanisterInfo { id, timestamp }
↓
icp canister link
↓
icp build -e playground
↓
Pool.installCode
↓
new CanisterInfo
↓
state保存
を基本経路とする。
frontendが存在する場合のみ、
asset install
↓
Pool.callForward("authorize")
↓
icp sync
を追加する。
この構成にすることで、
dfx 非依存
icp-cli project modelとの整合
通常deploymentとの明確な分離
Playground固有ロジックの隔離
実ICPを利用した20分程度の統合テスト
frontend/backendのfull-stackテスト
将来の icp-cli native Playground対応への移行
を同時に満たす。
特に設計上の中心原則は、
nicp playground はdeployの一モードではなく、Playground Poolを操作する独立した一時環境operationである。
とする。
nicp_cdk — ICP Playground デプロイ機能 設計書
1. 概要
本設計では、
nicp_cdkのCLIであるnicpに、DFINITYが提供するICP Playgroundへcanisterを一時デプロイするための専用コマンドを追加する。ユーザー向けコマンドは次の形とする。
特定canisterのみを対象とする場合は、
とする。
この機能は通常のdeployment機能の特殊モードではない。
のようなインターフェースは採用しない。
nicp playgroundは、として独立して実装する。
2. 目的
主な目的は、
nicp_cdkを利用するアプリケーションについて、を含む実環境に近い統合テストを、cyclesを消費せず短時間実行できるようにすることである。
想定する開発フローは次の通り。
対象となるテストには、
Nim backend canisterの実ICP動作確認
frontend → backend通信
Candid interface統合
query / update call
ICP HTTP Gateway経由のアクセス
Internet Identity等を利用するブラウザ統合
HTTPS outcall等、実ネットワークに依存する機能
upgrade時の状態保持
frontend asset canisterとの統合
を含む。
3. 非目的
nicp playgroundは以下を目的としない。通常mainnet deploymentの代替
staging環境の代替
長時間稼働環境
cyclesを利用した通常canister作成
production controller設定
production canister settings管理
icp deployのwrapperdfxの完全再実装IC AgentやICP ingress protocolそのもののNim再実装
通常環境については引き続き
icp-cliを利用する。Playgroundだけが、
という専用経路を通る。
4.
dfx --playgroundの調査結果4.1 Playgroundは独立したtestnetではない
dfxの現行実装ではPlaygroundは、ICP mainnetのAPI Gatewayへ接続する特殊なnetwork descriptorとして定義されている。デフォルト値は、
である。
つまり構造は、
となる。
したがってPlayground対応のために独自replicaや独自ネットワークを構築する必要はない。
5. Playgroundのcanister取得方式
通常のmainnet deploymentでは、cycles ledgerやManagement Canisterを利用してcanisterを新規作成する。
一方、
dfxはPlaygroundの場合、へ分岐する。
つまりPlaygroundでは、
6.
getCanisterIdcanisterを確保する際、Playground Poolの、
をupdate callする。
概念的な入力は、
戻り値は、
である。
dfxもcanister IDだけでなく取得timestampを保存している。Playgroundにおいて、
の組は一つのopaque handleとして扱う必要がある。
canister IDだけを保存してはいけない。
7. Proof of Work
getCanisterIdは簡易Proof of Workを要求する。dfxは現在、から、
という文字列を生成する。
その文字列にDJB2系hashを適用する。
概念的には、
で、32bit unsigned integerとしてoverflowさせる。
以下を満たせばPoW成功。
満たさなければ、
して再試行する。
8. Playground controllerモデル
Playgroundで借りたcanisterは、ユーザーidentityをcontrollerにはしない。
controllerは引き続き、
である。
そのためユーザーから直接、
を実行することはできない。
したがって、Playground canisterに対して、
をそのまま使う設計は採用できない。
9.
installCodeWasmのinstall / upgrade / reinstallはPoolの、
を通して実施する。
概念的な型は次の通り。
処理は、
となる。
dfxもPoolのinstallCodeを直接利用している。10. Wasm transformation
通常のbackend Wasmはそのままinstallされない。
Playground PoolはWasmをtransformし、Playground環境用の制約を適用する。
現在のPoolでは例えば、
という制限や、cycles・Management Canister callへの変換処理が行われる。
Pool自身にはManagement Canister互換のwrapperが存在し、
等をPlayground canisterから利用できるようにしている。
したがって、Playground対応のためにNim canister runtimeそのものへ特殊処理を組み込む必要はない。
Playground deploymentはCLI側の責務とする。
11. TTL
デフォルトPlayground PoolのTTLは、
である。
さらに重要なのは、successful install / upgradeによってTTLが更新されることである。
installCode完了後、Poolは、を実行し、新しいtimestampを返す。
例:
したがって
nicp playgroundは、installCodeが返した最新timestampを必ず保存する。12. CLI仕様
基本コマンド:
対象canister指定:
新しいcanisterを強制的に取得:
既存canisterへreinstall:
frontend asset syncを省略:
v1ではofficial Playgroundをデフォルトとする。
将来拡張として、
を検討する。
13. 採用しないCLI
以下は採用しない。
また、
のようなAPIも採用しない。
通常deploymentとPlaygroundではcanister lifecycleそのものが異なるためである。
通常:
Playground:
これらを一つの
deployabstractionへ統合すると、cycles
controller
settings
install method
state
expiration
frontend authorization
retry
等のPlayground固有分岐が通常deployment側へ漏れる。
14. CLI entrypoint
現在の
src/cli/nicp.nimに、を追加する。
dispatchMultiは、とする。
現行CLIのentrypointは
dispatchMultiに各operationを直接登録する構成なので、この追加方法が既存設計と整合する。15.
playground_impl.nim新規ファイル:
ユーザー向けentrypointは、
とする。
責務はCLIとの境界だけとする。
playground_impl.nimに、PoW
Candid encoding
state storage
subprocess execution
Pool protocol
等は実装しない。
16. 内部関数名
CLI関数名:
内部orchestrator:
とする。
deploy()という名前はユーザー向けAPIには使用しない。内部でも、
より、
のように責務が明示された名前を使用する。
17. モジュール構造
推奨構造:
18. モジュール責務
config.nimPlayground固有定数。
pow.nimProof of Work。
ネットワーク依存性を持たせない。
protocol.nimPlayground protocolの高レベル型。
API:
transport.nimicp-cliを利用したnetwork transport。identity・署名・ingress・request polling等は
icp-cliに委譲する。state.nimPlayground reservationの状態管理。
artifact_store.nimicp buildが生成したWasm artifactの取得。asset.nimfrontend asset canisterの、
install
authorize
sync
を担当する。
project.nimicp.yamlのPlayground environmentや対象canisterの検証。runner.nimPlayground operation全体のorchestration。
中心API:
19.
icp-cliをtransportとして使用するPlayground protocolを実装するために、Nim側で、
HTTP transport
request envelope
CBOR
identity signing
delegation
ingress expiry
request status polling
certificate verification
を再実装するのは避ける。
代わりに、
とする。
現行
icp canister callは任意canister IDに対し、Candid interface・引数形式・update/query等を扱えるため、このtransport用途に利用できる。20.
playground.didnicp_cdkrepository内にPlayground Pool用Candid interfaceを保持する。例:
Pool upstream変更時に差分を確認できるよう、このファイルには参照元DFINITY SDK revisionをコメントで残す。
21. Candid
int/nat型Playground実装を契機として、
nicp_cdkのCandid型対応も修正する。Candidの、
は固定bit幅整数ではない。
したがって、
とする。
22.
CandidNat推奨型:
little-endian limbとする。
正規化条件:
23.
CandidIntzeroの場合、
へ正規化する。
24. CandidValue変更
現状の、
を、
へ変更する。
固定幅型は変更しない。
25. 任意精度LEB128
既存の、
は固定幅整数用helperとして残してもよいが、Candid
intencodingには使用しない。追加API:
現在のencoderでは
ctIntが固定幅整数へ狭められる経路があるため、任意精度化は独立した修正対象になる。26. convenience conversion
ユーザーが常にlimbを直接扱う必要はない。
を提供する。
例えば、
のように利用できる。
27. Playground v1とCandid encoderの依存関係
Playground v1ではtransportに
icp-cliを利用するため、Pool callの引数はCandid textとして渡せる。したがって、
実装順として、
を利用可能とする。
ただし
nicp_cdk全体としてはCandidInt/CandidNat対応を並行して実装する。最終的にはPlayground protocol内部型にも
CandidInt/CandidNatを使用する。28. Origin
dfxはPlayground requestのoriginとして、を使用する。
nicpはこれを偽装しない。とする。
Poolは空origin等を拒否する実装になっている。
これによってDFINITY側の統計でも、
を区別できる。
29. Playground state
保存場所:
形式:
timestampはJSON numberではなくdecimal stringとする。
理由:
Candid
intは任意精度JavaScript等でJSONを読んでもprecisionを失わない
将来timestamp representationが拡張されても安全
30. state validity
local state上では、
なら再利用候補とする。
ただし境界付近のraceを避けるため安全marginを置く。
例えば、
とする。
local clock skewやPool側state不一致があるため、これはあくまでoptimistic判定である。
最終的な正しさはPoolの
installCoderesponseで判断する。31.
icp-clienvironment生成する
icp.yamlに、name: playground
network: ic
canisters:
frontendを追加する。
このenvironmentは、
を実行するためのものではない。
nicp playgroundが、等の
icp-cliproject functionalityを利用するために存在する。READMEには、
と明記する。
32.
icp canister linkPoolから得たcanister IDを
icp-cliproject modelへ登録する。icp-cliは外部で作成されたcanister IDをenvironmentへ関連付けるcanister linkを正式に持っている。構造:
33. ephemeral mapping
connected networkのID mappingは
icp-cliにより、へ保存される。
そのためPlaygroundでは、
が生成される。
これは20分で無効になるため、Gitへcommitしてはいけない。
生成テンプレートの
.gitignoreに、を追加する。
一方、
等には影響を与えない。
34.
nicp newの変更現在生成されるプロジェクトに、
を追加する。
frontendあり:
name: production
network: ic
canisters:
frontendbackend only:
backend35. Playgroundではsettingsを付けない
現在のテンプレートではstaging/productionに、
等を設定している。
Playgroundではこれを設定しない。
frontendのみとする。
理由は、canister controllerがPlayground Poolであり、通常の
update_settingsをユーザーidentityから実行できないからである。Pool側も設定変更を制限している。36. unsupported settings検出
nicp playgroundのpreflightで、Playground environmentに通常canister settingsが指定されていないか検査する。対象例:
存在する場合、silent ignoreしない。
エラー例:
37. backend build
全target canisterについて、
を完了してからbuildする。
順序:
とする。
これはfrontend/backend間でID依存がある場合に、build開始前に全IDを確定させるためである。
38. 現行Nim build scriptの修正
現行生成テンプレートでは、
を使ってdevelopment / production buildを切り替えている。
icp-cliベースのprojectとしては、を使用するべきである。
修正:
icp-cliはbuild scriptへICP_CLI_ENVIRONMENTとICP_WASM_OUTPUT_PATHを渡す。結果:
39. artifact取得
icp buildのWasm artifactを取得する。現在の
icp-cliはbuild artifact storeを、配下に持つ。
ただしこれは
icp-cli内部実装であるため、直接参照箇所を、だけに限定する。
将来
icp-cliがartifact APIを公開した場合は、この実装だけ差し替える。40. gzip Wasm
Playground Poolへ渡すWasmはraw Wasmを基本とする。
artifact先頭が、
ならgzip artifactと判定する。
v1では自動展開よりも明示的エラーを推奨する。
必要になれば将来transparent decompressionを追加する。
41. install mode
状態によってmodeを決定する。
新規reserve:
有効なstateあり:
--reinstall:--fresh:42.
--freshデフォルトでは有効な既存Playground canisterを再利用する。
理由:
canister IDを維持できる
frontend/backend参照が維持される
Pool capacityを不必要に消費しない
upgradeでTTLを更新できる
--freshの場合のみ新規reservationを取得する。古いcanisterはPoolのTTL回収に任せる。
43. stale state recovery
state上では有効でも、Pool側では既に無効になっていることがある。
例:
installCodeが、等でrejectした場合、
とする。
retry回数は1回に限定する。
無限retryは禁止。
44. ID変更時のrebuild
一つでもcanister IDが変わった場合、frontend等にIDが埋め込まれる可能性がある。
したがって、
となったら、
する。
個別installだけをretryしない。
45. frontend asset canister
frontendが存在する場合、
の2つをPlaygroundからreserveする。
frontendのasset canister WasmはPoolのallowlist対象としてinstallする。
そのため、
を指定する。
dfxもasset canisterの場合にこの扱いを行う。backendについては、
とする。
46. asset uploader authorization
Poolがfrontend canisterのcontrollerなので、user identityをasset uploaderとしてauthorizeする必要がある。
まず、
で現在identityのPrincipalを取得する。
icp-cliは選択中identityのPrincipalを出力するcommandを提供している。次に、
を実行する。
dfxも同じcallForwardを使ってasset uploaderをauthorizeしている。47. frontend sync
authorize後、
を実行する。
ただし
icp-cliのsync recipeがPlayground linked canisterでそのまま利用可能かはintegration testで固定する。v1の実装では、
を標準経路とし、失敗時はupstream stderrを保持する。
48. frontendのcanister ID discovery問題
現行
icp-cliのfrontendでは、等をcanister environment variableとして設定し、asset canisterが
ic_envcookieを提供する仕組みがある。これらのbinding設定は
update_settingsを利用する。しかしPlaygroundではユーザーはcontrollerではない。
したがって、
49. nicp生成frontendの解決策
nicp newが生成するfrontendには、Playground専用fallbackを設ける。backend ID確定後、
をbuild/sync subprocess environmentへ注入する。
frontend側:
通常環境:
Playground:
を利用する。
これにより通常deploymentの仕組みを維持しながらPlaygroundだけfallbackできる。
50. generic frontendへの対応
任意の既存frontendについて自動変換までは行わない。
runtime bindingだけに依存するfrontendを検出できた場合は、
と明示する。
silent failureは禁止。
51. init arguments
Playground installは、
を必要とする。
backendがinit args不要の場合は、Candid空tuple、
をserializeして使用する。
将来的には、
を実装する。
ただし
icp.yamlresolutionロジックをnicpに丸ごと複製するのは避ける。可能な限り
icp-cli側のmanifest resolutionを再利用する。52.
runner.nim中心データ型:
中心関数:
53. 全体state machine
54. preflight
最低限以下を検査する。
frontendありの場合:
も検査する。
55. CI制限
現行
dfxはCIを検知し、official mainnet Playground Poolへのreservationを拒否する。nicpもこの方針を維持する。--force-ciのような回避optionは設けない。将来custom/local Playground Poolをサポートした場合、そのPoolについてはCI利用を許可できる。
56. Pool capacity
Poolに利用可能なcanisterがない場合、Poolは待機時間を含むrejectを返す実装を持つ。
この場合自動retryは行わない。
表示:
57. asset Wasm allowlist mismatch
asset canister WasmがPool allowlistに存在しない場合を専用errorとする。
dfxもこのケースを特別扱いしている。表示:
58. Error model
高レベル理由とupstream stderrを分離する。
例:
59. subprocess abstraction
外部commandを直接各moduleから呼ばない。
これを、
icp canister callicp canister linkicp buildicp syncicp identity principal全てに利用する。
testではfake runnerへ交換できる。
60. security
以下を遵守する。
identity secretを
nicpが読む必要はないPEM/private keyをstateへ保存しない
subprocess command lineへsecretを追加しない
Playground stateはcanister IDとtimestampのみ
upstream outputにsecretが含まれ得る場合はlog levelを考慮
custom pool principalは必ずPrincipal validationする
identity管理は
icp-cliに委譲する。61. Logging
通常:
--debug相当の既存logging方針があれば、exact
icp-clicommandraw Pool response
state transition
selected install mode
を追加表示する。
62. 成功時出力
backend only:
frontendあり:
63. Unit test — PoW
固定vectorで検証する。
DFINITY SDKのRust実装と同じ値になるfixtureを用意する。
境界:
64. Unit test — Candid integer
最低限、
をencode/decode round tripする。
nat:を確認する。
65. Unit test — state
検証項目:
empty state
save/load
decimal timestamp
malformed JSON
unsupported schema version
TTL valid
TTL expired
safety margin
pool principal mismatch
timestamp refresh
--fresh66. Unit test — protocol
fake
PlaygroundTransportを使う。検証:
Candid argumentsもfixture化する。
67. Unit test — orchestration
fake
CommandRunnerとfakePlaygroundClientを利用する。期待されるcommand順:
を検証する。
68. stale retry test
を確認する。
二回目も失敗した場合は終了する。
69. integration test
DFINITY SDKにあるPlayground backend実装を利用または参考にし、ローカルPoolで検証する。
backend test:
frontend test:
70. official Playground smoke test
official Poolを使う自動CIテストは行わない。
release前のmanual smoke testとする。
最低確認:
その後、
等で実canisterを確認する。
続けて、
を再実行し、
を確認する。
71. frontend smoke test
実行後、
が成立することを確認する。
Playwright等による手動/ローカルE2Eも可能にする。
official Poolを使ったCI自動化は行わない。
72. 実装Phase 1 — backend
最初にbackend onlyを完成させる。
対象:
受け入れ条件:
で実ICP上にNim backendをデプロイできる。
73. Phase 2 — Candid arbitrary precision
並行またはPhase 1直後に、
を実装する。
Playground timestampを含め、Candid仕様に正しい整数モデルへ移行する。
74. Phase 3 — frontend
追加:
75. Phase 4 — hardening
追加候補:
environment-specific init args
arbitrary multi-canister projects
custom Playground Pool
local Playground Pool
asset canister version detection
icp-cliartifact path依存の除去generic frontend ID injection hooks
richer Playground status command
manual release/retire operation
76.
nicp playgroundの責務境界nicp playgroundが行うもの:行わないもの:
77. architecture summary
78. 最重要設計判断
79. Acceptance criteria
Backend
以下をすべて満たす。
Frontend
Candid
80. 結論
nicp_cdkにおけるICP Playground対応は、通常deploymentのoptionとして実装しない。外部インターフェースは、
とし、実装入口も、
とする。
内部処理は、
を基本経路とする。
frontendが存在する場合のみ、
を追加する。
この構成にすることで、
dfx非依存icp-cliproject modelとの整合通常deploymentとの明確な分離
Playground固有ロジックの隔離
実ICPを利用した20分程度の統合テスト
frontend/backendのfull-stackテスト
将来の
icp-clinative Playground対応への移行を同時に満たす。
特に設計上の中心原則は、
とする。