Skip to content

Commit

Permalink
feat: add 'admin_create_bucket_on' API
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Dec 30, 2024
1 parent 5d30e91 commit 8d53b03
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 3 deletions.
8 changes: 8 additions & 0 deletions src/ic_oss_cluster/ic_oss_cluster.did
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
59 changes: 57 additions & 2 deletions src/ic_oss_cluster/src/api_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: ()
Expand Down Expand Up @@ -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<CanisterSettings>,
args: Option<ByteBuf>,
) -> Result<Principal, String> {
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<CanisterSettings>,
Expand All @@ -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<CanisterSettings>,
_args: Option<ByteBuf>,
) -> Result<String, String> {
let _ = store::wasm::get_latest()?;
Ok("ok".to_string())
}

#[ic_cdk::update(guard = "is_controller")]
async fn admin_deploy_bucket(
args: DeployWasmInput,
Expand Down
69 changes: 68 additions & 1 deletion src/ic_oss_cluster/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use candid::{Nat, Principal};
use candid::{utils::ArgumentEncoder, CandidType, Nat, Principal};
use ic_cdk::api::management_canister::main::{
CanisterSettings, CanisterStatusResponse, UpdateSettingsArgument,
};
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};

Expand All @@ -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;
Expand Down Expand Up @@ -68,6 +71,70 @@ pub fn validate_principals(principals: &BTreeSet<Principal>) -> Result<(), Strin
Ok(())
}

async fn call<In, Out>(id: Principal, method: &str, args: In, cycles: u128) -> Result<Out, String>
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<CanisterSettings>,
pub subnet_selection: Option<SubnetSelection>,
pub subnet_type: Option<String>,
}

/// 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<CanisterSettings>,
cycles: u128,
) -> Result<Principal, String> {
let arg = CreateCanisterInput {
settings,
subnet_type: None,
subnet_selection: Some(SubnetSelection::Subnet {
subnet: SubnetId {
principal_id: subnet.to_text(),
},
}),
};
let res: Result<Principal, CreateCanisterOutput> =
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",
Expand Down

0 comments on commit 8d53b03

Please sign in to comment.