Skip to content

Commit dccebcd

Browse files
feat: Add support for creating canisters on a cloud engine (#420)
* Add support for creating on a cloud engine * changelog --------- Co-authored-by: raymondk <raymond.khalife@dfinity.org>
1 parent 4c2447b commit dccebcd

File tree

9 files changed

+121
-66
lines changed

9 files changed

+121
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
* feat: Added support for creating canisters on cloud engine subnets. Note that local networks cannot yet create these subnets.
34
* feat: `icp canister logs` supports filtering by timestamp (`--since`, `--until`) and log index (`--since-index`, `--until-index`)
45
* feat: Support `log_memory_limit` canister setting in `icp canister settings update` and `icp canister settings sync`
56
* feat: Leaving off the method name parameter in `icp canister call` prompts you with an interactive list of methods

Cargo.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/icp-canister-interfaces/src/cycles_ledger.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use candid::{CandidType, Nat, Principal};
22
use serde::Deserialize;
33

4+
use crate::management_canister::CanisterSettingsArg;
5+
46
/// 100m cycles
57
pub const CYCLES_LEDGER_BLOCK_FEE: u128 = 100_000_000;
68
pub const CYCLES_LEDGER_DECIMALS: i64 = 12;
@@ -9,26 +11,6 @@ pub const CYCLES_LEDGER_CID: &str = "um5iw-rqaaa-aaaaq-qaaba-cai";
911
pub const CYCLES_LEDGER_PRINCIPAL: Principal =
1012
Principal::from_slice(&[0, 0, 0, 0, 2, 16, 0, 2, 1, 1]);
1113

12-
/// Log visibility setting for a canister.
13-
/// Matches the cycles ledger's LogVisibility variant type.
14-
#[derive(Clone, Debug, CandidType, Deserialize)]
15-
#[serde(rename_all = "snake_case")]
16-
pub enum LogVisibility {
17-
Controllers,
18-
Public,
19-
AllowedViewers(Vec<Principal>),
20-
}
21-
22-
#[derive(Clone, Debug, CandidType, Deserialize)]
23-
pub struct CanisterSettingsArg {
24-
pub freezing_threshold: Option<Nat>,
25-
pub controllers: Option<Vec<Principal>>,
26-
pub reserved_cycles_limit: Option<Nat>,
27-
pub log_visibility: Option<LogVisibility>,
28-
pub memory_allocation: Option<Nat>,
29-
pub compute_allocation: Option<Nat>,
30-
}
31-
3214
#[derive(Clone, Debug, CandidType, Deserialize)]
3315
pub enum SubnetSelectionArg {
3416
Filter { subnet_type: Option<String> },

crates/icp-canister-interfaces/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod cycles_minting_canister;
44
pub mod governance;
55
pub mod icp_ledger;
66
pub mod internet_identity;
7+
pub mod management_canister;
78
pub mod nns_migration;
89
pub mod nns_root;
910
pub mod proxy;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use candid::{CandidType, Nat, Principal};
2+
use serde::Deserialize;
3+
4+
#[derive(Clone, Debug, CandidType, Deserialize)]
5+
pub struct MgmtCreateCanisterArgs {
6+
pub settings: Option<CanisterSettingsArg>,
7+
pub sender_canister_version: Option<u64>,
8+
}
9+
10+
#[derive(Clone, Debug, CandidType, Deserialize)]
11+
pub struct MgmtCreateCanisterResponse {
12+
pub canister_id: Principal,
13+
}
14+
15+
#[derive(Clone, Debug, CandidType, Deserialize)]
16+
pub struct CanisterSettingsArg {
17+
pub freezing_threshold: Option<Nat>,
18+
pub controllers: Option<Vec<Principal>>,
19+
pub reserved_cycles_limit: Option<Nat>,
20+
pub log_visibility: Option<LogVisibility>,
21+
pub memory_allocation: Option<Nat>,
22+
pub compute_allocation: Option<Nat>,
23+
}
24+
25+
/// Log visibility setting for a canister.
26+
/// Matches the cycles ledger's LogVisibility variant type.
27+
#[derive(Clone, Debug, CandidType, Deserialize)]
28+
#[serde(rename_all = "snake_case")]
29+
pub enum LogVisibility {
30+
Controllers,
31+
Public,
32+
AllowedViewers(Vec<Principal>),
33+
}

crates/icp-cli/src/commands/canister/create.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clap::{ArgGroup, Args, Parser};
44
use icp::context::Context;
55
use icp::parsers::{CyclesAmount, DurationAmount, MemoryAmount};
66
use icp::{Canister, context::CanisterSelection, prelude::*};
7-
use icp_canister_interfaces::cycles_ledger::CanisterSettingsArg;
7+
use icp_canister_interfaces::management_canister::CanisterSettingsArg;
88

99
use crate::{commands::args, operations::create::CreateOperation};
1010

crates/icp-cli/src/operations/create.rs

Lines changed: 72 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
use candid::{Decode, Encode, Nat, Principal};
2-
use ic_agent::{Agent, AgentError};
2+
use ic_agent::{
3+
Agent, AgentError,
4+
agent::{Subnet, SubnetType},
5+
};
36
use icp_canister_interfaces::{
47
cycles_ledger::{
5-
CYCLES_LEDGER_PRINCIPAL, CanisterSettingsArg, CreateCanisterArgs, CreateCanisterResponse,
6-
CreationArgs, SubnetSelectionArg,
8+
CYCLES_LEDGER_PRINCIPAL, CreateCanisterArgs, CreateCanisterResponse, CreationArgs,
9+
SubnetSelectionArg,
710
},
811
cycles_minting_canister::CYCLES_MINTING_CANISTER_PRINCIPAL,
9-
registry::{GetSubnetForCanisterRequest, GetSubnetForCanisterResult, REGISTRY_PRINCIPAL},
12+
management_canister::{
13+
CanisterSettingsArg, MgmtCreateCanisterArgs, MgmtCreateCanisterResponse,
14+
},
1015
};
1116
use rand::seq::IndexedRandom;
12-
use snafu::{ResultExt, Snafu};
17+
use snafu::{OptionExt, ResultExt, Snafu};
1318
use std::sync::Arc;
1419
use tokio::sync::OnceCell;
1520

@@ -91,13 +96,35 @@ impl CreateOperation {
9196
pub async fn create(
9297
&self,
9398
settings: &CanisterSettingsArg,
99+
) -> Result<Principal, CreateOperationError> {
100+
let selected_subnet = self
101+
.get_subnet()
102+
.await
103+
.map_err(|e| CreateOperationError::SubnetResolution { message: e })?;
104+
let subnet_info = self
105+
.inner
106+
.agent
107+
.get_subnet_by_id(&selected_subnet)
108+
.await
109+
.context(GetSubnetSnafu)?;
110+
let cid = if let Some(SubnetType::Unknown(kind)) = subnet_info.subnet_type()
111+
&& kind == "cloud_engine"
112+
{
113+
self.create_mgmt(settings, &subnet_info).await?
114+
} else {
115+
self.create_ledger(settings, selected_subnet).await?
116+
};
117+
Ok(cid)
118+
}
119+
120+
async fn create_ledger(
121+
&self,
122+
settings: &CanisterSettingsArg,
123+
selected_subnet: Principal,
94124
) -> Result<Principal, CreateOperationError> {
95125
let creation_args = CreationArgs {
96126
subnet_selection: Some(SubnetSelectionArg::Subnet {
97-
subnet: self
98-
.get_subnet()
99-
.await
100-
.map_err(|e| CreateOperationError::SubnetResolution { message: e })?,
127+
subnet: selected_subnet,
101128
}),
102129
settings: Some(settings.clone()),
103130
};
@@ -128,10 +155,40 @@ impl CreateOperation {
128155
.fail();
129156
}
130157
};
131-
132158
Ok(cid)
133159
}
134160

161+
async fn create_mgmt(
162+
&self,
163+
settings: &CanisterSettingsArg,
164+
selected_subnet: &Subnet,
165+
) -> Result<Principal, CreateOperationError> {
166+
let arg = MgmtCreateCanisterArgs {
167+
settings: Some(settings.clone()),
168+
sender_canister_version: None,
169+
};
170+
171+
// Call management canister create_canister
172+
let resp = self
173+
.inner
174+
.agent
175+
.update(&Principal::management_canister(), "create_canister")
176+
.with_arg(Encode!(&arg).context(CandidEncodeSnafu)?)
177+
.with_effective_canister_id(
178+
*selected_subnet
179+
.iter_canister_ranges()
180+
.next()
181+
.context(CreateCanisterSnafu {
182+
message: "subnet did not contain canister ranges",
183+
})?
184+
.start(),
185+
)
186+
.await
187+
.context(AgentSnafu)?;
188+
let resp = Decode!(&resp, MgmtCreateCanisterResponse).context(CandidDecodeSnafu)?;
189+
Ok(resp.canister_id)
190+
}
191+
135192
/// 1. If a subnet is explicitly provided, use it
136193
/// 2. If no canisters exist yet, pick a random available subnet
137194
/// 3. If canisters exist, use the same subnet as the first existing canister
@@ -148,10 +205,13 @@ impl CreateOperation {
148205
}
149206

150207
if let Some(canister) = self.inner.existing_canisters.first() {
151-
let subnet = get_canister_subnet(&self.inner.agent, *canister)
208+
let subnet = &self
209+
.inner
210+
.agent
211+
.get_subnet_by_canister(canister)
152212
.await
153213
.map_err(|e| e.to_string())?;
154-
Ok(subnet)
214+
Ok(subnet.id())
155215
} else {
156216
// If no canisters exist, pick a random available subnet
157217
let subnets = get_available_subnets(&self.inner.agent)
@@ -170,31 +230,6 @@ impl CreateOperation {
170230
}
171231
}
172232

173-
async fn get_canister_subnet(
174-
agent: &Agent,
175-
canister: Principal,
176-
) -> Result<Principal, CreateOperationError> {
177-
let args = &GetSubnetForCanisterRequest {
178-
principal: Some(canister),
179-
};
180-
181-
let bs = agent
182-
.query(&REGISTRY_PRINCIPAL, "get_subnet_for_canister")
183-
.with_arg(Encode!(args).context(CandidEncodeSnafu)?)
184-
.call()
185-
.await
186-
.context(GetSubnetSnafu)?;
187-
188-
let resp = Decode!(&bs, GetSubnetForCanisterResult).context(CandidDecodeSnafu)?;
189-
190-
let out = resp
191-
.map_err(|err| CreateOperationError::Registry { message: err })?
192-
.subnet_id
193-
.ok_or(CreateOperationError::MissingSubnetId)?;
194-
195-
Ok(out)
196-
}
197-
198233
async fn get_available_subnets(agent: &Agent) -> Result<Vec<Principal>, CreateOperationError> {
199234
let bs = agent
200235
.query(&CYCLES_MINTING_CANISTER_PRINCIPAL, "get_default_subnets")

crates/icp/src/canister/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashMap;
22

33
use candid::{Nat, Principal};
4-
use icp_canister_interfaces::cycles_ledger::CanisterSettingsArg;
4+
use icp_canister_interfaces::management_canister::CanisterSettingsArg;
55
use schemars::JsonSchema;
66
use serde::{Deserialize, Serialize};
77

@@ -187,9 +187,9 @@ impl From<LogVisibility> for ic_management_canister_types::LogVisibility {
187187
}
188188
}
189189

190-
impl From<LogVisibility> for icp_canister_interfaces::cycles_ledger::LogVisibility {
190+
impl From<LogVisibility> for icp_canister_interfaces::management_canister::LogVisibility {
191191
fn from(value: LogVisibility) -> Self {
192-
use icp_canister_interfaces::cycles_ledger::LogVisibility as CyclesLedgerLogVisibility;
192+
use icp_canister_interfaces::management_canister::LogVisibility as CyclesLedgerLogVisibility;
193193
match value {
194194
LogVisibility::Controllers => CyclesLedgerLogVisibility::Controllers,
195195
LogVisibility::Public => CyclesLedgerLogVisibility::Public,

crates/icp/src/network/managed/run.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ use ic_ledger_types::{AccountIdentifier, Memo, Subaccount, Tokens, TransferArgs,
1111
use ic_utils::interfaces::management_canister::builders::CanisterInstallMode;
1212
use icp_canister_interfaces::{
1313
cycles_ledger::{
14-
CYCLES_LEDGER_BLOCK_FEE, CYCLES_LEDGER_PRINCIPAL, CanisterSettingsArg, CreateCanisterArgs,
14+
CYCLES_LEDGER_BLOCK_FEE, CYCLES_LEDGER_PRINCIPAL, CreateCanisterArgs,
1515
CreateCanisterResponse, CreationArgs,
1616
},
1717
cycles_minting_canister::{
1818
CYCLES_MINTING_CANISTER_PRINCIPAL, ConversionRateResponse, MEMO_MINT_CYCLES,
1919
NotifyMintArgs, NotifyMintResponse,
2020
},
2121
icp_ledger::{ICP_LEDGER_BLOCK_FEE_E8S, ICP_LEDGER_PRINCIPAL},
22+
management_canister::CanisterSettingsArg,
2223
};
2324
use icrc_ledger_types::icrc1::{
2425
account::Account,

0 commit comments

Comments
 (0)