From 8d53b03ff98a5f06b7a94b7d59d43caa04486833 Mon Sep 17 00:00:00 2001 From: 0xZensh Date: Mon, 30 Dec 2024 12:44:41 +0800 Subject: [PATCH] feat: add 'admin_create_bucket_on' API --- src/ic_oss_cluster/ic_oss_cluster.did | 8 ++++ src/ic_oss_cluster/src/api_admin.rs | 59 ++++++++++++++++++++++- src/ic_oss_cluster/src/lib.rs | 69 ++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/ic_oss_cluster/ic_oss_cluster.did b/src/ic_oss_cluster/ic_oss_cluster.did index 05b9b7b..5e8ddd0 100644 --- a/src/ic_oss_cluster/ic_oss_cluster.did +++ b/src/ic_oss_cluster/ic_oss_cluster.did @@ -118,6 +118,9 @@ service : (opt ChainArgs) -> { admin_attach_policies : (Token) -> (Result_1); admin_batch_call_buckets : (vec principal, text, opt blob) -> (Result_2); admin_create_bucket : (opt CanisterSettings, opt blob) -> (Result_3); + admin_create_bucket_on : (principal, opt CanisterSettings, opt blob) -> ( + Result_3, + ); admin_deploy_bucket : (DeployWasmInput, opt blob) -> (Result_1); admin_detach_policies : (Token) -> (Result_1); admin_ed25519_access_token : (Token) -> (Result); @@ -156,6 +159,11 @@ service : (opt ChainArgs) -> { validate_admin_create_bucket : (opt CanisterSettings, opt blob) -> ( Result_11, ); + validate_admin_create_bucket_on : ( + principal, + opt CanisterSettings, + opt blob, + ) -> (Result_11); validate_admin_deploy_bucket : (DeployWasmInput, opt blob) -> (Result_1); validate_admin_remove_committers : (vec principal) -> (Result_11); validate_admin_remove_managers : (vec principal) -> (Result_11); diff --git a/src/ic_oss_cluster/src/api_admin.rs b/src/ic_oss_cluster/src/api_admin.rs index 2f16a90..cd4dbf4 100644 --- a/src/ic_oss_cluster/src/api_admin.rs +++ b/src/ic_oss_cluster/src/api_admin.rs @@ -12,8 +12,9 @@ use std::collections::BTreeSet; use std::time::Duration; use crate::{ - ecdsa, is_controller, is_controller_or_manager, is_controller_or_manager_or_committer, schnorr, - store, validate_principals, MILLISECONDS, SECONDS, TOKEN_KEY_DERIVATION_PATH, + create_canister_on, ecdsa, is_controller, is_controller_or_manager, + is_controller_or_manager_or_committer, schnorr, store, validate_principals, MILLISECONDS, + SECONDS, TOKEN_KEY_DERIVATION_PATH, }; // encoded candid arguments: () @@ -265,6 +266,50 @@ async fn admin_create_bucket( Ok(canister_id) } +#[ic_cdk::update(guard = "is_controller")] +async fn admin_create_bucket_on( + subnet: Principal, + settings: Option, + args: Option, +) -> Result { + let self_id = ic_cdk::id(); + let mut settings = settings.unwrap_or_default(); + let controllers = settings.controllers.get_or_insert_with(Default::default); + if !controllers.contains(&self_id) { + controllers.push(self_id); + } + + let canister_id = create_canister_on(subnet, Some(settings), 2_000_000_000_000) + .await + .map_err(format_error)?; + let (hash, wasm) = store::wasm::get_latest()?; + let arg = args.unwrap_or_else(|| ByteBuf::from(EMPTY_CANDID_ARGS)); + let res = install_code(InstallCodeArgument { + mode: CanisterInstallMode::Install, + canister_id, + wasm_module: wasm.wasm.into_vec(), + arg: arg.clone().into_vec(), + }) + .await + .map_err(format_error); + + let id = store::wasm::add_log(store::DeployLog { + deploy_at: ic_cdk::api::time() / MILLISECONDS, + canister: canister_id, + prev_hash: Default::default(), + wasm_hash: hash, + args: arg, + error: res.clone().err(), + })?; + + if res.is_ok() { + store::state::with_mut(|s| { + s.bucket_deployed_list.insert(canister_id, (id, hash)); + }) + } + Ok(canister_id) +} + #[ic_cdk::update] fn validate_admin_create_bucket( _settings: Option, @@ -274,6 +319,16 @@ fn validate_admin_create_bucket( Ok("ok".to_string()) } +#[ic_cdk::update] +fn validate_admin_create_bucket_on( + _subnet: Principal, + _settings: Option, + _args: Option, +) -> Result { + let _ = store::wasm::get_latest()?; + Ok("ok".to_string()) +} + #[ic_cdk::update(guard = "is_controller")] async fn admin_deploy_bucket( args: DeployWasmInput, diff --git a/src/ic_oss_cluster/src/lib.rs b/src/ic_oss_cluster/src/lib.rs index b0f9e84..8c7ab41 100644 --- a/src/ic_oss_cluster/src/lib.rs +++ b/src/ic_oss_cluster/src/lib.rs @@ -1,4 +1,4 @@ -use candid::{Nat, Principal}; +use candid::{utils::ArgumentEncoder, CandidType, Nat, Principal}; use ic_cdk::api::management_canister::main::{ CanisterSettings, CanisterStatusResponse, UpdateSettingsArgument, }; @@ -6,6 +6,7 @@ use ic_oss_types::{ cluster::{AddWasmInput, BucketDeploymentInfo, ClusterInfo, DeployWasmInput, WasmInfo}, cose::Token, }; +use serde::{Deserialize, Serialize}; use serde_bytes::{ByteArray, ByteBuf}; use std::collections::{BTreeMap, BTreeSet}; @@ -20,6 +21,8 @@ mod store; use crate::init::ChainArgs; static ANONYMOUS: Principal = Principal::anonymous(); +// NNS Cycles Minting Canister: "rkp4c-7iaaa-aaaaa-aaaca-cai" +static CMC_PRINCIPAL: Principal = Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 4, 1, 1]); static TOKEN_KEY_DERIVATION_PATH: &[u8] = b"ic_oss_cluster"; const SECONDS: u64 = 1_000_000_000; const MILLISECONDS: u64 = 1_000_000; @@ -68,6 +71,70 @@ pub fn validate_principals(principals: &BTreeSet) -> Result<(), Strin Ok(()) } +async fn call(id: Principal, method: &str, args: In, cycles: u128) -> Result +where + In: ArgumentEncoder + Send, + Out: candid::CandidType + for<'a> candid::Deserialize<'a>, +{ + let (res,): (Out,) = ic_cdk::api::call::call_with_payment128(id, method, args, cycles) + .await + .map_err(|(code, msg)| { + format!( + "failed to call {} on {:?}, code: {}, message: {}", + method, &id, code as u32, msg + ) + })?; + Ok(res) +} + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)] +pub struct SubnetId { + pub principal_id: String, +} + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)] +pub enum SubnetSelection { + /// Choose a specific subnet + Subnet { subnet: SubnetId }, + // Skip the SubnetFilter on the CMC SubnetSelection for simplification. + // https://github.com/dfinity/ic/blob/master/rs/nns/cmc/cmc.did#L35 +} + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)] +struct CreateCanisterInput { + pub settings: Option, + pub subnet_selection: Option, + pub subnet_type: Option, +} + +/// Error for create_canister. +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] +pub enum CreateCanisterOutput { + Refunded { + refund_amount: u128, + create_error: String, + }, +} + +async fn create_canister_on( + subnet: Principal, + settings: Option, + cycles: u128, +) -> Result { + let arg = CreateCanisterInput { + settings, + subnet_type: None, + subnet_selection: Some(SubnetSelection::Subnet { + subnet: SubnetId { + principal_id: subnet.to_text(), + }, + }), + }; + let res: Result = + call(CMC_PRINCIPAL, "create_canister", (arg,), cycles).await?; + res.map_err(|err| format!("failed to create canister, error: {:?}", err)) +} + #[cfg(all( target_arch = "wasm32", target_vendor = "unknown",