Skip to content

Commit 8d53b03

Browse files
committed
feat: add 'admin_create_bucket_on' API
1 parent 5d30e91 commit 8d53b03

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

src/ic_oss_cluster/ic_oss_cluster.did

+8
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ service : (opt ChainArgs) -> {
118118
admin_attach_policies : (Token) -> (Result_1);
119119
admin_batch_call_buckets : (vec principal, text, opt blob) -> (Result_2);
120120
admin_create_bucket : (opt CanisterSettings, opt blob) -> (Result_3);
121+
admin_create_bucket_on : (principal, opt CanisterSettings, opt blob) -> (
122+
Result_3,
123+
);
121124
admin_deploy_bucket : (DeployWasmInput, opt blob) -> (Result_1);
122125
admin_detach_policies : (Token) -> (Result_1);
123126
admin_ed25519_access_token : (Token) -> (Result);
@@ -156,6 +159,11 @@ service : (opt ChainArgs) -> {
156159
validate_admin_create_bucket : (opt CanisterSettings, opt blob) -> (
157160
Result_11,
158161
);
162+
validate_admin_create_bucket_on : (
163+
principal,
164+
opt CanisterSettings,
165+
opt blob,
166+
) -> (Result_11);
159167
validate_admin_deploy_bucket : (DeployWasmInput, opt blob) -> (Result_1);
160168
validate_admin_remove_committers : (vec principal) -> (Result_11);
161169
validate_admin_remove_managers : (vec principal) -> (Result_11);

src/ic_oss_cluster/src/api_admin.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use std::collections::BTreeSet;
1212
use std::time::Duration;
1313

1414
use crate::{
15-
ecdsa, is_controller, is_controller_or_manager, is_controller_or_manager_or_committer, schnorr,
16-
store, validate_principals, MILLISECONDS, SECONDS, TOKEN_KEY_DERIVATION_PATH,
15+
create_canister_on, ecdsa, is_controller, is_controller_or_manager,
16+
is_controller_or_manager_or_committer, schnorr, store, validate_principals, MILLISECONDS,
17+
SECONDS, TOKEN_KEY_DERIVATION_PATH,
1718
};
1819

1920
// encoded candid arguments: ()
@@ -265,6 +266,50 @@ async fn admin_create_bucket(
265266
Ok(canister_id)
266267
}
267268

269+
#[ic_cdk::update(guard = "is_controller")]
270+
async fn admin_create_bucket_on(
271+
subnet: Principal,
272+
settings: Option<CanisterSettings>,
273+
args: Option<ByteBuf>,
274+
) -> Result<Principal, String> {
275+
let self_id = ic_cdk::id();
276+
let mut settings = settings.unwrap_or_default();
277+
let controllers = settings.controllers.get_or_insert_with(Default::default);
278+
if !controllers.contains(&self_id) {
279+
controllers.push(self_id);
280+
}
281+
282+
let canister_id = create_canister_on(subnet, Some(settings), 2_000_000_000_000)
283+
.await
284+
.map_err(format_error)?;
285+
let (hash, wasm) = store::wasm::get_latest()?;
286+
let arg = args.unwrap_or_else(|| ByteBuf::from(EMPTY_CANDID_ARGS));
287+
let res = install_code(InstallCodeArgument {
288+
mode: CanisterInstallMode::Install,
289+
canister_id,
290+
wasm_module: wasm.wasm.into_vec(),
291+
arg: arg.clone().into_vec(),
292+
})
293+
.await
294+
.map_err(format_error);
295+
296+
let id = store::wasm::add_log(store::DeployLog {
297+
deploy_at: ic_cdk::api::time() / MILLISECONDS,
298+
canister: canister_id,
299+
prev_hash: Default::default(),
300+
wasm_hash: hash,
301+
args: arg,
302+
error: res.clone().err(),
303+
})?;
304+
305+
if res.is_ok() {
306+
store::state::with_mut(|s| {
307+
s.bucket_deployed_list.insert(canister_id, (id, hash));
308+
})
309+
}
310+
Ok(canister_id)
311+
}
312+
268313
#[ic_cdk::update]
269314
fn validate_admin_create_bucket(
270315
_settings: Option<CanisterSettings>,
@@ -274,6 +319,16 @@ fn validate_admin_create_bucket(
274319
Ok("ok".to_string())
275320
}
276321

322+
#[ic_cdk::update]
323+
fn validate_admin_create_bucket_on(
324+
_subnet: Principal,
325+
_settings: Option<CanisterSettings>,
326+
_args: Option<ByteBuf>,
327+
) -> Result<String, String> {
328+
let _ = store::wasm::get_latest()?;
329+
Ok("ok".to_string())
330+
}
331+
277332
#[ic_cdk::update(guard = "is_controller")]
278333
async fn admin_deploy_bucket(
279334
args: DeployWasmInput,

src/ic_oss_cluster/src/lib.rs

+68-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use candid::{Nat, Principal};
1+
use candid::{utils::ArgumentEncoder, CandidType, Nat, Principal};
22
use ic_cdk::api::management_canister::main::{
33
CanisterSettings, CanisterStatusResponse, UpdateSettingsArgument,
44
};
55
use ic_oss_types::{
66
cluster::{AddWasmInput, BucketDeploymentInfo, ClusterInfo, DeployWasmInput, WasmInfo},
77
cose::Token,
88
};
9+
use serde::{Deserialize, Serialize};
910
use serde_bytes::{ByteArray, ByteBuf};
1011
use std::collections::{BTreeMap, BTreeSet};
1112

@@ -20,6 +21,8 @@ mod store;
2021
use crate::init::ChainArgs;
2122

2223
static ANONYMOUS: Principal = Principal::anonymous();
24+
// NNS Cycles Minting Canister: "rkp4c-7iaaa-aaaaa-aaaca-cai"
25+
static CMC_PRINCIPAL: Principal = Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 4, 1, 1]);
2326
static TOKEN_KEY_DERIVATION_PATH: &[u8] = b"ic_oss_cluster";
2427
const SECONDS: u64 = 1_000_000_000;
2528
const MILLISECONDS: u64 = 1_000_000;
@@ -68,6 +71,70 @@ pub fn validate_principals(principals: &BTreeSet<Principal>) -> Result<(), Strin
6871
Ok(())
6972
}
7073

74+
async fn call<In, Out>(id: Principal, method: &str, args: In, cycles: u128) -> Result<Out, String>
75+
where
76+
In: ArgumentEncoder + Send,
77+
Out: candid::CandidType + for<'a> candid::Deserialize<'a>,
78+
{
79+
let (res,): (Out,) = ic_cdk::api::call::call_with_payment128(id, method, args, cycles)
80+
.await
81+
.map_err(|(code, msg)| {
82+
format!(
83+
"failed to call {} on {:?}, code: {}, message: {}",
84+
method, &id, code as u32, msg
85+
)
86+
})?;
87+
Ok(res)
88+
}
89+
90+
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
91+
pub struct SubnetId {
92+
pub principal_id: String,
93+
}
94+
95+
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
96+
pub enum SubnetSelection {
97+
/// Choose a specific subnet
98+
Subnet { subnet: SubnetId },
99+
// Skip the SubnetFilter on the CMC SubnetSelection for simplification.
100+
// https://github.com/dfinity/ic/blob/master/rs/nns/cmc/cmc.did#L35
101+
}
102+
103+
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
104+
struct CreateCanisterInput {
105+
pub settings: Option<CanisterSettings>,
106+
pub subnet_selection: Option<SubnetSelection>,
107+
pub subnet_type: Option<String>,
108+
}
109+
110+
/// Error for create_canister.
111+
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)]
112+
pub enum CreateCanisterOutput {
113+
Refunded {
114+
refund_amount: u128,
115+
create_error: String,
116+
},
117+
}
118+
119+
async fn create_canister_on(
120+
subnet: Principal,
121+
settings: Option<CanisterSettings>,
122+
cycles: u128,
123+
) -> Result<Principal, String> {
124+
let arg = CreateCanisterInput {
125+
settings,
126+
subnet_type: None,
127+
subnet_selection: Some(SubnetSelection::Subnet {
128+
subnet: SubnetId {
129+
principal_id: subnet.to_text(),
130+
},
131+
}),
132+
};
133+
let res: Result<Principal, CreateCanisterOutput> =
134+
call(CMC_PRINCIPAL, "create_canister", (arg,), cycles).await?;
135+
res.map_err(|err| format!("failed to create canister, error: {:?}", err))
136+
}
137+
71138
#[cfg(all(
72139
target_arch = "wasm32",
73140
target_vendor = "unknown",

0 commit comments

Comments
 (0)