diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e3370903..c7dfea4235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -408,6 +408,10 @@ Changed the text in this case to read: ### chore: add retry logic to dfx download script +### feat: Add subnet type argument when creating canisters + +`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. + ## Dependencies ### Replica diff --git a/docs/cli-reference/dfx-ledger.md b/docs/cli-reference/dfx-ledger.md index 1b882278db..edde47bf3c 100644 --- a/docs/cli-reference/dfx-ledger.md +++ b/docs/cli-reference/dfx-ledger.md @@ -133,11 +133,13 @@ You can specify the following argument for the `dfx ledger create-canister` comm | Option | Description | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--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. | -| `--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. | -| `--fee ` | Specify a transaction fee. The default is 10000 e8s. | -| `--icp ` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. | -| `--max-fee ` | Specify a maximum transaction fee. The default is 10000 e8s. | +| `--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. | +| `--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. | +| `--fee ` | Specify a transaction fee. The default is 10000 e8s. | +| `--icp ` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. | +| `--max-fee ` | Specify a maximum transaction fee. The default is 10000 e8s. | +| `--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. | + ### Examples @@ -264,6 +266,47 @@ The following example illustrates sending a `notify` message to the ledger in re dfx ledger notify 75948 tsqwz-udeik-5migd-ehrev-pvoqv-szx2g-akh5s-fkyqc-zy6q7-snav6-uqe --network ic ``` +## dfx ledger show-subnet-types + +Use the `dfx ledger show-subnet-types` command to list the available subnet types that can be chosen to create a canister on. + +### Basic usage + +``` bash +dfx ledger show-subnet-types [options] [flag] +``` + +### Flags + +You can use the following optional flags with the `dfx ledger show-subnet-types` command. + +| Flag | Description | +|-------------------|-------------------------------| +| `-h`, `--help` | Displays usage information. | +| `-V`, `--version` | Displays version information. | + +### Options + +You can specify the following options for the `dfx ledger show-subnet-types` command. + +| Option | Description | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--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. | + +### Examples + +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. + +For example, you can run the following command to get the subnet types available on mainnet: + +``` bash +dfx ledger show-subnet-types +``` + +This command displays output similar to the following: + + ["Type1", "Type2", ..., "TypeN"] + ## dfx ledger top-up Use the `dfx ledger top-up` command to top up a canister with cycles minted from ICP tokens. diff --git a/e2e/assets/cmc/main.mo b/e2e/assets/cmc/main.mo new file mode 100644 index 0000000000..8a94b4db4d --- /dev/null +++ b/e2e/assets/cmc/main.mo @@ -0,0 +1,22 @@ +import Array "mo:base/Array"; +import P "mo:base/Principal"; +import Text "mo:base/Text"; +import Prim "mo:⛔"; + +actor Call { + type SubnetTypesToSubnetsResponse = { + data: [(Text, [Principal])]; + }; + + public query func get_subnet_types_to_subnets() : async SubnetTypesToSubnetsResponse { + let type1 = "type1"; + let type2 = "type2"; + { + data = [ + (type1, [Prim.principalOfBlob("\00")]), + (type2, [Prim.principalOfBlob("\01"), Prim.principalOfBlob("\02")]), + ]; + } + }; + +} diff --git a/e2e/assets/cmc/patch.bash b/e2e/assets/cmc/patch.bash new file mode 100644 index 0000000000..c4b425ef2b --- /dev/null +++ b/e2e/assets/cmc/patch.bash @@ -0,0 +1 @@ +jq '.canisters.cmc.main="main.mo"' dfx.json | sponge dfx.json \ No newline at end of file diff --git a/e2e/tests-dfx/ledger.bash b/e2e/tests-dfx/ledger.bash index 6d3559f992..9f91d29c72 100644 --- a/e2e/tests-dfx/ledger.bash +++ b/e2e/tests-dfx/ledger.bash @@ -134,3 +134,21 @@ tc_to_num() { (( balance_now - balance > 4000000000000 )) } + +@test "ledger create-canister" { + dfx identity use alice + assert_command dfx ledger create-canister --amount=100 --subnet-type "type1" "$(dfx identity get-principal)" + assert_match "Transfer sent at block height 6" + assert_match "Refunded at block height 7 with message: Provided subnet type type1 does not exist" +} + +@test "ledger show-subnet-types" { + install_asset cmc + + dfx deploy cmc + + CANISTER_ID=$(dfx canister id cmc) + + assert_command dfx ledger show-subnet-types --cycles-minting-canister-id "$CANISTER_ID" + assert_eq '["type1", "type2"]' +} diff --git a/src/dfx/src/commands/ledger/create_canister.rs b/src/dfx/src/commands/ledger/create_canister.rs index 852c19c06b..1c4f09eadb 100644 --- a/src/dfx/src/commands/ledger/create_canister.rs +++ b/src/dfx/src/commands/ledger/create_canister.rs @@ -46,6 +46,12 @@ pub struct CreateCanisterOpts { /// Max fee, default is 10000 e8s. #[clap(long, validator(icpts_amount_validator))] max_fee: Option, + + /// 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. + #[clap(long)] + subnet_type: Option, } pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult { @@ -73,9 +79,10 @@ pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; fetch_root_key_if_needed(env).await?; + let height = transfer_cmc(agent, memo, amount, fee, opts.from_subaccount, controller).await?; println!("Transfer sent at block height {height}"); - let result = notify_create(agent, controller, height).await?; + let result = notify_create(agent, controller, height, opts.subnet_type).await?; match result { Ok(principal) => { diff --git a/src/dfx/src/commands/ledger/mod.rs b/src/dfx/src/commands/ledger/mod.rs index 3ea6f5743d..3cb58bff0b 100644 --- a/src/dfx/src/commands/ledger/mod.rs +++ b/src/dfx/src/commands/ledger/mod.rs @@ -33,6 +33,7 @@ mod balance; pub mod create_canister; mod fabricate_cycles; mod notify; +pub mod show_subnet_types; mod top_up; mod transfer; @@ -54,6 +55,7 @@ enum SubCommand { CreateCanister(create_canister::CreateCanisterOpts), FabricateCycles(fabricate_cycles::FabricateCyclesOpts), Notify(notify::NotifyOpts), + ShowSubnetTypes(show_subnet_types::ShowSubnetTypesOpts), TopUp(top_up::TopUpOpts), Transfer(transfer::TransferOpts), } @@ -68,6 +70,7 @@ pub fn exec(env: &dyn Environment, opts: LedgerOpts) -> DfxResult { SubCommand::CreateCanister(v) => create_canister::exec(&agent_env, v).await, SubCommand::FabricateCycles(v) => fabricate_cycles::exec(&agent_env, v).await, SubCommand::Notify(v) => notify::exec(&agent_env, v).await, + SubCommand::ShowSubnetTypes(v) => show_subnet_types::exec(&agent_env, v).await, SubCommand::TopUp(v) => top_up::exec(&agent_env, v).await, SubCommand::Transfer(v) => transfer::exec(&agent_env, v).await, } @@ -199,6 +202,7 @@ pub async fn notify_create( agent: &Agent, controller: Principal, block_height: BlockHeight, + subnet_type: Option, ) -> DfxResult { let result = agent .update(&MAINNET_CYCLE_MINTER_CANISTER_ID, NOTIFY_CREATE_METHOD) @@ -206,6 +210,7 @@ pub async fn notify_create( Encode!(&NotifyCreateCanisterArg { block_index: block_height, controller, + subnet_type, }) .context("Failed to encode notify arguments.")?, ) diff --git a/src/dfx/src/commands/ledger/notify/create_canister.rs b/src/dfx/src/commands/ledger/notify/create_canister.rs index e2231daa21..b12a6b67e9 100644 --- a/src/dfx/src/commands/ledger/notify/create_canister.rs +++ b/src/dfx/src/commands/ledger/notify/create_canister.rs @@ -16,6 +16,12 @@ pub struct NotifyCreateOpts { /// The controller of the created canister. controller: String, + + /// 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. + #[clap(long)] + subnet_type: Option, } pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult { @@ -34,7 +40,7 @@ pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult { fetch_root_key_if_needed(env).await?; - let result = notify_create(agent, controller, block_height).await?; + let result = notify_create(agent, controller, block_height, opts.subnet_type).await?; match result { Ok(principal) => { diff --git a/src/dfx/src/commands/ledger/show_subnet_types.rs b/src/dfx/src/commands/ledger/show_subnet_types.rs new file mode 100644 index 0000000000..1105b431ab --- /dev/null +++ b/src/dfx/src/commands/ledger/show_subnet_types.rs @@ -0,0 +1,51 @@ +use crate::lib::environment::Environment; +use crate::lib::error::DfxResult; +use crate::lib::ledger_types::{GetSubnetTypesToSubnetsResult, MAINNET_CYCLE_MINTER_CANISTER_ID}; +use crate::lib::waiter::waiter_with_timeout; +use crate::util::expiry_duration; + +use crate::lib::root_key::fetch_root_key_if_needed; + +use anyhow::{anyhow, Context}; +use candid::{Decode, Encode, Principal}; +use clap::Parser; + +const GET_SUBNET_TYPES_TO_SUBNETS_METHOD: &str = "get_subnet_types_to_subnets"; + +/// Show available subnet types in the cycles minting canister. +#[derive(Parser)] +pub struct ShowSubnetTypesOpts { + #[clap(long)] + /// Canister ID of the cycles minting canister. + cycles_minting_canister_id: Option, +} + +pub async fn exec(env: &dyn Environment, opts: ShowSubnetTypesOpts) -> DfxResult { + let agent = env + .get_agent() + .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + + fetch_root_key_if_needed(env).await?; + + let cycles_minting_canister_id = opts + .cycles_minting_canister_id + .unwrap_or(MAINNET_CYCLE_MINTER_CANISTER_ID); + + let result = agent + .update( + &cycles_minting_canister_id, + GET_SUBNET_TYPES_TO_SUBNETS_METHOD, + ) + .with_arg(Encode!(&()).context("Failed to encode get_subnet_types_to_subnets arguments.")?) + .call_and_wait(waiter_with_timeout(expiry_duration())) + .await + .context("get_subnet_types_to_subnets call failed.")?; + let result = Decode!(&result, GetSubnetTypesToSubnetsResult) + .context("Failed to decode get_subnet_types_to_subnets response")?; + + let available_subnet_types: Vec = result.data.into_iter().map(|(x, _)| x).collect(); + + println!("{:?}", available_subnet_types); + + Ok(()) +} diff --git a/src/dfx/src/commands/quickstart.rs b/src/dfx/src/commands/quickstart.rs index e375e8c3d5..4046f89ade 100644 --- a/src/dfx/src/commands/quickstart.rs +++ b/src/dfx/src/commands/quickstart.rs @@ -171,7 +171,7 @@ async fn step_interact_ledger( let notify_spinner = ProgressBar::new_spinner(); notify_spinner.set_message("Notifying the the cycles minting canister..."); notify_spinner.enable_steady_tick(100); - let res = notify_create(agent, ident_principal, height).await + let res = notify_create(agent, ident_principal, height, None).await .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`."))?; let wallet = match res { Ok(principal) => principal, diff --git a/src/dfx/src/lib/ledger_types/mod.rs b/src/dfx/src/lib/ledger_types/mod.rs index 7ad8b31ab5..0a90fbb3c1 100644 --- a/src/dfx/src/lib/ledger_types/mod.rs +++ b/src/dfx/src/lib/ledger_types/mod.rs @@ -130,6 +130,7 @@ pub struct TimeStamp { pub struct NotifyCreateCanisterArg { pub block_index: BlockIndex, pub controller: Principal, + pub subnet_type: Option, } #[derive(CandidType)] @@ -157,6 +158,11 @@ pub type NotifyCreateCanisterResult = Result; pub type NotifyTopUpResult = Result; +#[derive(CandidType, Deserialize, Debug)] +pub struct GetSubnetTypesToSubnetsResult { + pub data: Vec<(String, Vec)>, +} + #[cfg(test)] mod tests { use super::*;