Skip to content

Commit 06a1deb

Browse files
authored
feat: Add subnet type argument when creating canisters (#2584)
Adds a new optional argument when creating canisters through dfx that allows users to choose a specific "type" of subnet that their canister can be created on. These user-facing types are different than the existing types of subnets in the registry (system,verified, application) and are mostly useful for allowing users to choose a subnet with some certain characteristics. Additionally, the ability to list available subnet types is added through a new subcommand of `dfx ledger`.
1 parent af74ba7 commit 06a1deb

File tree

11 files changed

+171
-8
lines changed

11 files changed

+171
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ Changed the text in this case to read:
408408
409409
### chore: add retry logic to dfx download script
410410
411+
### feat: Add subnet type argument when creating canisters
412+
413+
`dfx ledger create-canister` gets a new option `--subnet-type` that allows users to choose a type of subnet that their canister can be created on. Additionally, a `dfx ledger show-subnet-types` is introduced which allows to list the available subnet types.
414+
411415
## Dependencies
412416
413417
### Replica

docs/cli-reference/dfx-ledger.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,13 @@ You can specify the following argument for the `dfx ledger create-canister` comm
133133

134134
| Option | Description |
135135
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
136-
| `--amount <amount>` | Specify the number of ICP tokens to mint into cycles and deposit into destination canister. You can specify an amount as a number with up to eight (8) decimal places. |
137-
| `--e8s <e8s>` | Specify ICP token fractional units—called e8s—as a whole number, where one e8 is smallest partition of an ICP token. For example, 1.05000000 is 1 ICP and 5000000 e8s. You can use this option on its own or in conjunction with the `--icp` option. |
138-
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
139-
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
140-
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
136+
| `--amount <amount>` | Specify the number of ICP tokens to mint into cycles and deposit into destination canister. You can specify an amount as a number with up to eight (8) decimal places. |
137+
| `--e8s <e8s>` | Specify ICP token fractional units—called e8s—as a whole number, where one e8 is smallest partition of an ICP token. For example, 1.05000000 is 1 ICP and 5000000 e8s. You can use this option on its own or in conjunction with the `--icp` option. |
138+
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
139+
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
140+
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
141+
| `--subnet-type <subnet-type>` | Specify the optional subnet type to create the canister on. If no subnet type is provided, the canister will be created on a random default application subnet. |
142+
141143

142144
### Examples
143145

@@ -264,6 +266,47 @@ The following example illustrates sending a `notify` message to the ledger in re
264266
dfx ledger notify 75948 tsqwz-udeik-5migd-ehrev-pvoqv-szx2g-akh5s-fkyqc-zy6q7-snav6-uqe --network ic
265267
```
266268

269+
## dfx ledger show-subnet-types
270+
271+
Use the `dfx ledger show-subnet-types` command to list the available subnet types that can be chosen to create a canister on.
272+
273+
### Basic usage
274+
275+
``` bash
276+
dfx ledger show-subnet-types [options] [flag]
277+
```
278+
279+
### Flags
280+
281+
You can use the following optional flags with the `dfx ledger show-subnet-types` command.
282+
283+
| Flag | Description |
284+
|-------------------|-------------------------------|
285+
| `-h`, `--help` | Displays usage information. |
286+
| `-V`, `--version` | Displays version information. |
287+
288+
### Options
289+
290+
You can specify the following options for the `dfx ledger show-subnet-types` command.
291+
292+
| Option | Description |
293+
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
294+
| `--cycles-minting-canister-id <cycles-minting-canister-id>` | Canister id of the cycles minting canister. Useful if you want to test locally with a different id for the cycles minting canister. |
295+
296+
### Examples
297+
298+
You can use the `dfx ledger show-subnet-types` command to list the available subnet types that can be chosen to create a canister on. If a specific cycles minting canister id is not provided, then the mainnet cycles minting canister id will be used.
299+
300+
For example, you can run the following command to get the subnet types available on mainnet:
301+
302+
``` bash
303+
dfx ledger show-subnet-types
304+
```
305+
306+
This command displays output similar to the following:
307+
308+
["Type1", "Type2", ..., "TypeN"]
309+
267310
## dfx ledger top-up
268311

269312
Use the `dfx ledger top-up` command to top up a canister with cycles minted from ICP tokens.

e2e/assets/cmc/main.mo

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Array "mo:base/Array";
2+
import P "mo:base/Principal";
3+
import Text "mo:base/Text";
4+
import Prim "mo:⛔";
5+
6+
actor Call {
7+
type SubnetTypesToSubnetsResponse = {
8+
data: [(Text, [Principal])];
9+
};
10+
11+
public query func get_subnet_types_to_subnets() : async SubnetTypesToSubnetsResponse {
12+
let type1 = "type1";
13+
let type2 = "type2";
14+
{
15+
data = [
16+
(type1, [Prim.principalOfBlob("\00")]),
17+
(type2, [Prim.principalOfBlob("\01"), Prim.principalOfBlob("\02")]),
18+
];
19+
}
20+
};
21+
22+
}

e2e/assets/cmc/patch.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jq '.canisters.cmc.main="main.mo"' dfx.json | sponge dfx.json

e2e/tests-dfx/ledger.bash

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,21 @@ tc_to_num() {
134134

135135
(( balance_now - balance > 4000000000000 ))
136136
}
137+
138+
@test "ledger create-canister" {
139+
dfx identity use alice
140+
assert_command dfx ledger create-canister --amount=100 --subnet-type "type1" "$(dfx identity get-principal)"
141+
assert_match "Transfer sent at block height 6"
142+
assert_match "Refunded at block height 7 with message: Provided subnet type type1 does not exist"
143+
}
144+
145+
@test "ledger show-subnet-types" {
146+
install_asset cmc
147+
148+
dfx deploy cmc
149+
150+
CANISTER_ID=$(dfx canister id cmc)
151+
152+
assert_command dfx ledger show-subnet-types --cycles-minting-canister-id "$CANISTER_ID"
153+
assert_eq '["type1", "type2"]'
154+
}

src/dfx/src/commands/ledger/create_canister.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ pub struct CreateCanisterOpts {
4646
/// Max fee, default is 10000 e8s.
4747
#[clap(long, validator(icpts_amount_validator))]
4848
max_fee: Option<String>,
49+
50+
/// Specify the optional subnet type to create the canister on. If no
51+
/// subnet type is provided, the canister will be created on a random
52+
/// default application subnet.
53+
#[clap(long)]
54+
subnet_type: Option<String>,
4955
}
5056

5157
pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult {
@@ -73,9 +79,10 @@ pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult
7379
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;
7480

7581
fetch_root_key_if_needed(env).await?;
82+
7683
let height = transfer_cmc(agent, memo, amount, fee, opts.from_subaccount, controller).await?;
7784
println!("Transfer sent at block height {height}");
78-
let result = notify_create(agent, controller, height).await?;
85+
let result = notify_create(agent, controller, height, opts.subnet_type).await?;
7986

8087
match result {
8188
Ok(principal) => {

src/dfx/src/commands/ledger/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod balance;
3333
pub mod create_canister;
3434
mod fabricate_cycles;
3535
mod notify;
36+
pub mod show_subnet_types;
3637
mod top_up;
3738
mod transfer;
3839

@@ -54,6 +55,7 @@ enum SubCommand {
5455
CreateCanister(create_canister::CreateCanisterOpts),
5556
FabricateCycles(fabricate_cycles::FabricateCyclesOpts),
5657
Notify(notify::NotifyOpts),
58+
ShowSubnetTypes(show_subnet_types::ShowSubnetTypesOpts),
5759
TopUp(top_up::TopUpOpts),
5860
Transfer(transfer::TransferOpts),
5961
}
@@ -68,6 +70,7 @@ pub fn exec(env: &dyn Environment, opts: LedgerOpts) -> DfxResult {
6870
SubCommand::CreateCanister(v) => create_canister::exec(&agent_env, v).await,
6971
SubCommand::FabricateCycles(v) => fabricate_cycles::exec(&agent_env, v).await,
7072
SubCommand::Notify(v) => notify::exec(&agent_env, v).await,
73+
SubCommand::ShowSubnetTypes(v) => show_subnet_types::exec(&agent_env, v).await,
7174
SubCommand::TopUp(v) => top_up::exec(&agent_env, v).await,
7275
SubCommand::Transfer(v) => transfer::exec(&agent_env, v).await,
7376
}
@@ -199,13 +202,15 @@ pub async fn notify_create(
199202
agent: &Agent,
200203
controller: Principal,
201204
block_height: BlockHeight,
205+
subnet_type: Option<String>,
202206
) -> DfxResult<NotifyCreateCanisterResult> {
203207
let result = agent
204208
.update(&MAINNET_CYCLE_MINTER_CANISTER_ID, NOTIFY_CREATE_METHOD)
205209
.with_arg(
206210
Encode!(&NotifyCreateCanisterArg {
207211
block_index: block_height,
208212
controller,
213+
subnet_type,
209214
})
210215
.context("Failed to encode notify arguments.")?,
211216
)

src/dfx/src/commands/ledger/notify/create_canister.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ pub struct NotifyCreateOpts {
1616

1717
/// The controller of the created canister.
1818
controller: String,
19+
20+
/// Specify the optional subnet type to create the canister on. If no
21+
/// subnet type is provided, the canister will be created on a random
22+
/// default application subnet.
23+
#[clap(long)]
24+
subnet_type: Option<String>,
1925
}
2026

2127
pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult {
@@ -34,7 +40,7 @@ pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult {
3440

3541
fetch_root_key_if_needed(env).await?;
3642

37-
let result = notify_create(agent, controller, block_height).await?;
43+
let result = notify_create(agent, controller, block_height, opts.subnet_type).await?;
3844

3945
match result {
4046
Ok(principal) => {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::lib::environment::Environment;
2+
use crate::lib::error::DfxResult;
3+
use crate::lib::ledger_types::{GetSubnetTypesToSubnetsResult, MAINNET_CYCLE_MINTER_CANISTER_ID};
4+
use crate::lib::waiter::waiter_with_timeout;
5+
use crate::util::expiry_duration;
6+
7+
use crate::lib::root_key::fetch_root_key_if_needed;
8+
9+
use anyhow::{anyhow, Context};
10+
use candid::{Decode, Encode, Principal};
11+
use clap::Parser;
12+
13+
const GET_SUBNET_TYPES_TO_SUBNETS_METHOD: &str = "get_subnet_types_to_subnets";
14+
15+
/// Show available subnet types in the cycles minting canister.
16+
#[derive(Parser)]
17+
pub struct ShowSubnetTypesOpts {
18+
#[clap(long)]
19+
/// Canister ID of the cycles minting canister.
20+
cycles_minting_canister_id: Option<Principal>,
21+
}
22+
23+
pub async fn exec(env: &dyn Environment, opts: ShowSubnetTypesOpts) -> DfxResult {
24+
let agent = env
25+
.get_agent()
26+
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;
27+
28+
fetch_root_key_if_needed(env).await?;
29+
30+
let cycles_minting_canister_id = opts
31+
.cycles_minting_canister_id
32+
.unwrap_or(MAINNET_CYCLE_MINTER_CANISTER_ID);
33+
34+
let result = agent
35+
.update(
36+
&cycles_minting_canister_id,
37+
GET_SUBNET_TYPES_TO_SUBNETS_METHOD,
38+
)
39+
.with_arg(Encode!(&()).context("Failed to encode get_subnet_types_to_subnets arguments.")?)
40+
.call_and_wait(waiter_with_timeout(expiry_duration()))
41+
.await
42+
.context("get_subnet_types_to_subnets call failed.")?;
43+
let result = Decode!(&result, GetSubnetTypesToSubnetsResult)
44+
.context("Failed to decode get_subnet_types_to_subnets response")?;
45+
46+
let available_subnet_types: Vec<String> = result.data.into_iter().map(|(x, _)| x).collect();
47+
48+
println!("{:?}", available_subnet_types);
49+
50+
Ok(())
51+
}

src/dfx/src/commands/quickstart.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ async fn step_interact_ledger(
171171
let notify_spinner = ProgressBar::new_spinner();
172172
notify_spinner.set_message("Notifying the the cycles minting canister...");
173173
notify_spinner.enable_steady_tick(100);
174-
let res = notify_create(agent, ident_principal, height).await
174+
let res = notify_create(agent, ident_principal, height, None).await
175175
.with_context(|| format!("Failed to notify the CMC of the transfer. Write down that height ({height}), and once the error is fixed, use `dfx ledger notify create-canister`."))?;
176176
let wallet = match res {
177177
Ok(principal) => principal,

src/dfx/src/lib/ledger_types/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub struct TimeStamp {
130130
pub struct NotifyCreateCanisterArg {
131131
pub block_index: BlockIndex,
132132
pub controller: Principal,
133+
pub subnet_type: Option<String>,
133134
}
134135

135136
#[derive(CandidType)]
@@ -157,6 +158,11 @@ pub type NotifyCreateCanisterResult = Result<Principal, NotifyError>;
157158

158159
pub type NotifyTopUpResult = Result<u128, NotifyError>;
159160

161+
#[derive(CandidType, Deserialize, Debug)]
162+
pub struct GetSubnetTypesToSubnetsResult {
163+
pub data: Vec<(String, Vec<Principal>)>,
164+
}
165+
160166
#[cfg(test)]
161167
mod tests {
162168
use super::*;

0 commit comments

Comments
 (0)