diff --git a/CHANGELOG.md b/CHANGELOG.md index 5060074cc5..8a4982270d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,13 @@ Providers can provide the hash through `wasm_hash_url` instead of hard coding th If the hash of downloaded wasm doesn’t match the provided hash (`wasm_hash`, `wasm_hash_url` or read from mainnet state tree), dfx deps won’t abort. Instead, it will print a warning message. +### feat: create canister on specific subnets or subnet types + +`dfx deploy`, `dfx canister create`, and `dfx ledger create-canister` now support the option `--subnet ` to create canisters on specific subnets. + +`dfx canister create` and `dfx deploy` now support the option `--subnet-type ` to create canisters on a random subnet of a certain type. +Use `dfx ledger show-subnet-types` to list the available subnet types + ### feat!: update `dfx cycles` commands with mainnet `cycles-ledger` canister ID The `dfx cycles` command no longer needs nor accepts the `--cycles-ledger-canister-id ` parameter. diff --git a/docs/cli-reference/dfx-canister.md b/docs/cli-reference/dfx-canister.md index ffc06eed56..a98fc060cf 100644 --- a/docs/cli-reference/dfx-canister.md +++ b/docs/cli-reference/dfx-canister.md @@ -220,6 +220,8 @@ You can use the following options with the `dfx canister create` command. | `--no-wallet` | Performs the call with the user Identity as the Sender of messages. Bypasses the Wallet canister. Enabled by default. | | `--with-cycles ` | Specifies the initial cycle balance to deposit into the newly created canister. The specified amount needs to take the canister create fee into account. This amount is deducted from the wallet's cycle balance. | | `--specified-id ` | Attempts to create the canister with this Canister ID | +| `--subnet-type ` | Specify the subnet type to create the canister on. If no subnet type is provided, the canister will be created on a random default application subnet. | +| `--subnet ` | Specify the subnet to create the canister on. If no subnet is provided, the canister will be created on a random default application subnet. | ### Arguments diff --git a/docs/cli-reference/dfx-deploy.md b/docs/cli-reference/dfx-deploy.md index c247aa164a..426b16525a 100644 --- a/docs/cli-reference/dfx-deploy.md +++ b/docs/cli-reference/dfx-deploy.md @@ -30,6 +30,8 @@ You can use the following options with the `dfx deploy` command. | `--specified-id ` | Attempts to create the canister with this Canister ID | | `--by-proposal` | Upload proposed changed assets, but do not commit them. Follow up by calling either commit_proposed_batch() or delete_batch(). | | `--compute-evidence` | Build a frontend canister, determine batch operations required to synchronize asset canister contents, and compute a hash over those operations. Displays this hash ("evidence"), which should match the evidence displayed by `dfx deploy --by-proposal`. | +| `--subnet-type ` | Specify the subnet type to create the canister on. If no subnet type is provided, the canister will be created on a random default application subnet. | +| `--subnet ` | Specify the subnet to create the canister on. If no subnet is provided, the canister will be created on a random default application subnet. | ### Arguments diff --git a/docs/cli-reference/dfx-ledger.md b/docs/cli-reference/dfx-ledger.md index bb44cdb942..8e23e5e95b 100644 --- a/docs/cli-reference/dfx-ledger.md +++ b/docs/cli-reference/dfx-ledger.md @@ -121,6 +121,7 @@ You can specify the following argument for the `dfx ledger create-canister` comm | `--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. | +| `--subnet ` | Specify the optional subnet to create the canister on. If no subnet is provided, the canister will be created on a random default application subnet. | | `--created-at-time `| Specify the timestamp-nanoseconds for the `created_at_time` field on the ledger transfer request. Useful for controlling transaction-de-duplication. https://internetcomputer.org/docs/current/developer-docs/integrations/icrc-1/#transaction-deduplication- | ### Examples diff --git a/e2e/assets/cycles-ledger/dfx.json b/e2e/assets/cycles-ledger/dfx.json index 8b092b0d7b..feafffb36b 100644 --- a/e2e/assets/cycles-ledger/dfx.json +++ b/e2e/assets/cycles-ledger/dfx.json @@ -5,10 +5,10 @@ "wasm": "cycles-ledger.wasm.gz", "candid": "cycles-ledger.did" }, - "cycles-depositor": { + "depositor": { "type": "custom", - "wasm": "cycles-depositor.wasm.gz", - "candid": "cycles-depositor.did" + "wasm": "depositor.wasm.gz", + "candid": "depositor.did" } } -} +} \ No newline at end of file diff --git a/e2e/assets/fake_cmc/dfx.json b/e2e/assets/fake_cmc/dfx.json new file mode 100644 index 0000000000..bd690ec3a8 --- /dev/null +++ b/e2e/assets/fake_cmc/dfx.json @@ -0,0 +1,9 @@ +{ + "canisters": { + "fake-cmc": { + "type": "custom", + "wasm": "fake-cmc.wasm.gz", + "candid": "fake-cmc.did" + } + } +} \ No newline at end of file diff --git a/e2e/tests-dfx/create.bash b/e2e/tests-dfx/create.bash index 63fdb4b34f..8aa20344b9 100644 --- a/e2e/tests-dfx/create.bash +++ b/e2e/tests-dfx/create.bash @@ -1,6 +1,7 @@ #!/usr/bin/env bats load ../utils/_ +load ../utils/cycles-ledger setup() { standard_setup @@ -269,3 +270,28 @@ teardown() { assert_command dfx wallet upgrade --identity alice assert_command dfx canister create --all --controller alice --controller bob --identity alice } + +@test "create canister - subnet targetting" { + # fake cmc setup + cd .. + dfx_new fake_cmc + install_asset fake_cmc + install_cycles_ledger_canisters + dfx_start + assert_command dfx deploy fake-cmc --specified-id "rkp4c-7iaaa-aaaaa-aaaca-cai" # CMC canister id + cd ../e2e_project + + # use --subnet + SUBNET_ID="5kdm2-62fc6-fwnja-hutkz-ycsnm-4z33i-woh43-4cenu-ev7mi-gii6t-4ae" # a random, valid principal + assert_command dfx canister create e2e_project_backend --subnet "$SUBNET_ID" + cd ../fake_cmc + assert_command dfx canister call fake-cmc last_create_canister_args + assert_contains "subnet = principal \"$SUBNET_ID\";" + + # use --subnet-type + cd ../e2e_project + assert_command dfx canister create e2e_project_frontend --subnet-type custom_subnet_type + cd ../fake_cmc + assert_command dfx canister call fake-cmc last_create_canister_args + assert_contains 'subnet_type = opt "custom_subnet_type"' +} diff --git a/e2e/tests-dfx/cycles-ledger.bash b/e2e/tests-dfx/cycles-ledger.bash index 0d12dd70a0..7f0fda70b4 100644 --- a/e2e/tests-dfx/cycles-ledger.bash +++ b/e2e/tests-dfx/cycles-ledger.bash @@ -31,7 +31,7 @@ add_cycles_ledger_canisters_to_project() { deploy_cycles_ledger() { assert_command dfx deploy cycles-ledger --specified-id "um5iw-rqaaa-aaaaq-qaaba-cai" --argument '(variant { Init = record { max_transactions_per_request = 100; index_id = null; } })' - assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000 --specified-id "ul4oc-4iaaa-aaaaq-qaabq-cai" + assert_command dfx deploy depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000 --specified-id "ul4oc-4iaaa-aaaaq-qaabq-cai" } current_time_nanoseconds() { @@ -61,16 +61,16 @@ current_time_nanoseconds() { assert_eq "0.000 TC (trillion cycles)." - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 1_700_400_200_150;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 1_700_400_200_150;})" --identity cycle-giver assert_eq "(record { balance = 1_700_400_200_150 : nat; block_index = 0 : nat })" - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 3_750_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 3_750_000_000_000;})" --identity cycle-giver assert_eq "(record { balance = 3_750_000_000_000 : nat; block_index = 1 : nat })" - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT2_CANDID\"};cycles = 760_500_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT2_CANDID\"};cycles = 760_500_000_000;})" --identity cycle-giver assert_eq "(record { balance = 760_500_000_000 : nat; block_index = 2 : nat })" - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_900_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_900_000_000_000;})" --identity cycle-giver assert_eq "(record { balance = 2_900_000_000_000 : nat; block_index = 3 : nat })" @@ -122,9 +122,9 @@ current_time_nanoseconds() { deploy_cycles_ledger - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_000_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT2_CANDID\"};cycles = 1_000_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_000_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT2_CANDID\"};cycles = 1_000_000_000_000;})" --identity cycle-giver # account to account assert_command dfx cycles balance --precise --identity alice @@ -195,7 +195,7 @@ current_time_nanoseconds() { deploy_cycles_ledger - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver assert_command dfx cycles balance --precise --identity alice assert_eq "3000000000000 cycles." @@ -272,9 +272,9 @@ current_time_nanoseconds() { assert_command dfx deploy - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_400_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT2_CANDID\"};cycles = 2_700_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_400_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT2_CANDID\"};cycles = 2_700_000_000_000;})" --identity cycle-giver # account to canister assert_command dfx cycles balance --precise --identity bob @@ -331,9 +331,9 @@ current_time_nanoseconds() { assert_command dfx deploy - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_400_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT2_CANDID\"};cycles = 2_700_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_400_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT2_CANDID\"};cycles = 2_700_000_000_000;})" --identity cycle-giver # account to canister assert_command dfx cycles balance --precise --identity bob @@ -376,10 +376,10 @@ current_time_nanoseconds() { assert_command dfx ledger balance --identity cycle-giver assert_eq "1000000000.00000000 ICP" - assert_command dfx canister status cycles-depositor + assert_command dfx canister status depositor assert_contains "Balance: 10_000_000_000_000 Cycles" - dfx canister status cycles-depositor + dfx canister status depositor assert_command dfx cycles balance --identity alice --precise assert_eq "0 cycles." @@ -388,10 +388,10 @@ current_time_nanoseconds() { assert_eq "0 cycles." - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 500_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 500_000_000;})" --identity cycle-giver assert_eq "(record { balance = 500_000_000 : nat; block_index = 0 : nat })" - assert_command dfx canister status cycles-depositor + assert_command dfx canister status depositor assert_contains "Balance: 9_999_500_000_000 Cycles" assert_command dfx cycles balance --identity alice --precise @@ -406,13 +406,13 @@ current_time_nanoseconds() { assert_command dfx cycles balance --identity bob --precise assert_eq "100000 cycles." - assert_command dfx cycles top-up cycles-depositor 100000 --identity alice + assert_command dfx cycles top-up depositor 100000 --identity alice assert_eq "Transfer sent at block index 2" assert_command dfx cycles balance --identity alice --precise assert_eq "299800000 cycles." - assert_command dfx canister status cycles-depositor + assert_command dfx canister status depositor assert_contains "Balance: 9_999_500_100_000 Cycles" } @@ -429,14 +429,14 @@ current_time_nanoseconds() { assert_command deploy_cycles_ledger CYCLES_LEDGER_ID=$(dfx canister id cycles-ledger) echo "Cycles ledger deployed at id $CYCLES_LEDGER_ID" - assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" - echo "Cycles depositor deployed at id $(dfx canister id cycles-depositor)" - assert_command dfx ledger fabricate-cycles --canister cycles-depositor --t 9999 + assert_command dfx deploy depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" + echo "Cycles depositor deployed at id $(dfx canister id depositor)" + assert_command dfx ledger fabricate-cycles --canister depositor --t 9999 assert_command dfx deploy - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 13_400_000_000_000;})" --identity cycle-giver - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 13_400_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver cd .. dfx_new @@ -514,18 +514,18 @@ current_time_nanoseconds() { assert_command deploy_cycles_ledger CYCLES_LEDGER_ID=$(dfx canister id cycles-ledger) echo "Cycles ledger deployed at id $CYCLES_LEDGER_ID" - assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" - echo "Cycles depositor deployed at id $(dfx canister id cycles-depositor)" - assert_command dfx ledger fabricate-cycles --canister cycles-depositor --t 9999 + assert_command dfx deploy depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" + echo "Cycles depositor deployed at id $(dfx canister id depositor)" + assert_command dfx ledger fabricate-cycles --canister depositor --t 9999 assert_command dfx deploy - assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 22_400_000_000_000;})" --identity cycle-giver + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 22_400_000_000_000;})" --identity cycle-giver cd .. dfx_new # setup done dfx identity use alice - # shellcheck disable=SC2031 + # shellcheck disable=SC2030,SC2031 export DFX_DISABLE_AUTO_WALLET=1 assert_command dfx canister create --all --with-cycles 10T assert_command dfx cycles balance --precise @@ -544,4 +544,41 @@ current_time_nanoseconds() { assert_command dfx canister delete "${FRONTEND_ID}" assert_command dfx cycles balance assert_eq "22.379 TC (trillion cycles)." +} + +@test "create canister on specific subnet" { + skip "can't be properly tested with feature flag turned off (CYCLES_LEDGER_ENABLED). TODO(SDK-1331): re-enable this test" + dfx_new temporary + add_cycles_ledger_canisters_to_project + install_cycles_ledger_canisters + + ALICE=$(dfx identity get-principal --identity alice) + + assert_command deploy_cycles_ledger + CYCLES_LEDGER_ID=$(dfx canister id cycles-ledger) + echo "Cycles ledger deployed at id $CYCLES_LEDGER_ID" + assert_command dfx deploy depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" + echo "Cycles depositor deployed at id $(dfx canister id depositor)" + assert_command dfx ledger fabricate-cycles --canister depositor --t 9999 + + assert_command dfx deploy + + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 13_400_000_000_000;})" --identity cycle-giver + cd .. + dfx_new + + dfx identity use alice + # shellcheck disable=SC2030,SC2031 + export DFX_DISABLE_AUTO_WALLET=1 + # setup done + + # use --subnet + SUBNET_ID="5kdm2-62fc6-fwnja-hutkz-ycsnm-4z33i-woh43-4cenu-ev7mi-gii6t-4ae" # a random, valid principal + assert_command_fail dfx canister create e2e_project_backend --subnet "$SUBNET_ID" + assert_contains "Subnet $SUBNET_ID does not exist" + + # use --subnet-type + cd ../e2e_project + assert_command_fail dfx canister create e2e_project_frontend --subnet-type custom_subnet_type + assert_contains "Provided subnet type custom_subnet_type does not exist" } \ No newline at end of file diff --git a/e2e/tests-dfx/deploy.bash b/e2e/tests-dfx/deploy.bash index 87a69c4a32..eebef20f60 100644 --- a/e2e/tests-dfx/deploy.bash +++ b/e2e/tests-dfx/deploy.bash @@ -1,6 +1,7 @@ #!/usr/bin/env bats load ../utils/_ +load ../utils/cycles-ledger setup() { standard_setup @@ -157,3 +158,28 @@ teardown() { assert_command dfx deploy assert_contains "hello_frontend: http://127.0.0.1" } + +@test "subnet targetting" { + # fake cmc setup + cd .. + dfx_new fake_cmc + install_asset fake_cmc + install_cycles_ledger_canisters + dfx_start + assert_command dfx deploy fake-cmc --specified-id "rkp4c-7iaaa-aaaaa-aaaca-cai" # CMC canister id + cd ../hello + + # use --subnet + SUBNET_ID="5kdm2-62fc6-fwnja-hutkz-ycsnm-4z33i-woh43-4cenu-ev7mi-gii6t-4ae" # a random, valid principal + assert_command dfx deploy hello_backend --subnet "$SUBNET_ID" + cd ../fake_cmc + assert_command dfx canister call fake-cmc last_create_canister_args + assert_contains "subnet = principal \"$SUBNET_ID\";" + + # use --subnet-type + cd ../hello + assert_command dfx deploy hello_frontend --subnet-type custom_subnet_type + cd ../fake_cmc + assert_command dfx canister call fake-cmc last_create_canister_args + assert_contains 'subnet_type = opt "custom_subnet_type"' +} diff --git a/e2e/tests-dfx/ledger.bash b/e2e/tests-dfx/ledger.bash index b22c6f0c99..bca7a3dc06 100644 --- a/e2e/tests-dfx/ledger.bash +++ b/e2e/tests-dfx/ledger.bash @@ -199,6 +199,12 @@ tc_to_num() { assert_match "Refunded at block height" assert_match "with message: Provided subnet type type1 does not exist" + SUBNET_ID="5kdm2-62fc6-fwnja-hutkz-ycsnm-4z33i-woh43-4cenu-ev7mi-gii6t-4ae" # a random, valid principal + assert_command dfx ledger create-canister --amount=100 --subnet "$SUBNET_ID" "$(dfx identity get-principal)" + assert_match "Transfer sent at block height" + assert_match "Refunded at block height" + assert_match "with message: Subnet $SUBNET_ID does not exist" + # Transaction Deduplication t=$(current_time_nanoseconds) diff --git a/e2e/utils/cycles-ledger.bash b/e2e/utils/cycles-ledger.bash index 0abce204ce..97bbcbc914 100644 --- a/e2e/utils/cycles-ledger.bash +++ b/e2e/utils/cycles-ledger.bash @@ -1,4 +1,4 @@ -CYCLES_LEDGER_VERSION="0.2.6" +CYCLES_LEDGER_VERSION="0.2.8" build_artifact_url() { echo "https://github.com/dfinity/cycles-ledger/releases/download/cycles-ledger-v$CYCLES_LEDGER_VERSION/${1}" @@ -19,14 +19,14 @@ download_cycles_ledger_canisters() { rm -rf "$DOWNLOAD_DIR" mkdir -p "$DOWNLOAD_DIR" "$(dirname "$DEST_DIR")" - for name in cycles-ledger cycles-depositor; do + for name in cycles-ledger depositor fake-cmc; do for ext in wasm.gz wasm.gz.sha256 did; do URL=$(build_artifact_url "${name}.${ext}") curl -v -L --fail -o "$DOWNLOAD_DIR/${name}.${ext}" "$URL" done done - ( cd "$DOWNLOAD_DIR" && shasum -c cycles-ledger.wasm.gz.sha256 && shasum -c cycles-depositor.wasm.gz.sha256 ) + ( cd "$DOWNLOAD_DIR" && shasum -c cycles-ledger.wasm.gz.sha256 && shasum -c depositor.wasm.gz.sha256 ) mv "$DOWNLOAD_DIR" "$DEST_DIR" } diff --git a/src/dfx/src/commands/canister/create.rs b/src/dfx/src/commands/canister/create.rs index 03e00d58df..024d9f3976 100644 --- a/src/dfx/src/commands/canister/create.rs +++ b/src/dfx/src/commands/canister/create.rs @@ -14,6 +14,7 @@ use crate::util::clap::parsers::{ reserved_cycles_limit_parser, }; use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser}; +use crate::util::clap::subnet_selection_opt::SubnetSelectionOpt; use anyhow::{bail, Context}; use byte_unit::Byte; use candid::Principal as CanisterId; @@ -93,6 +94,9 @@ pub struct CanisterCreateOpts { //TODO(SDK-1331): unhide #[arg(long, value_parser = icrc_subaccount_parser, hide = true)] from_subaccount: Option, + + #[command(flatten)] + subnet_selection: SubnetSelectionOpt, } pub async fn exec( @@ -170,6 +174,7 @@ pub async fn exec( }) .transpose() .context("Failed to determine controllers.")?; + let subnet_selection = opts.subnet_selection.into_subnet_selection(); let pull_canisters_in_config = get_pull_canisters_in_config(env)?; if let Some(canister_name) = opts.canister_name.as_deref() { @@ -224,6 +229,7 @@ pub async fn exec( reserved_cycles_limit, }, opts.created_at_time, + subnet_selection, ) .await?; Ok(()) @@ -294,6 +300,7 @@ pub async fn exec( reserved_cycles_limit, }, opts.created_at_time, + subnet_selection.clone(), ) .await?; } diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index b546047c57..c956491695 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -9,6 +9,7 @@ use crate::lib::operations::canister::deploy_canisters::DeployMode::{ use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::{environment::Environment, named_canister}; use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser}; +use crate::util::clap::subnet_selection_opt::SubnetSelectionOpt; use anyhow::{anyhow, bail, Context}; use candid::Principal; use clap::Parser; @@ -111,6 +112,9 @@ pub struct DeployOpts { //TODO(SDK-1331): unhide #[arg(long, value_parser = icrc_subaccount_parser, hide = true)] from_subaccount: Option, + + #[command(flatten)] + subnet_selection: SubnetSelectionOpt, } pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { @@ -128,7 +132,7 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { .context("Failed to parse InstallMode.")?; let config = env.get_config_or_anyhow()?; let env_file = config.get_output_env_file(opts.output_env_file)?; - + let subnet_selection = opts.subnet_selection.into_subnet_selection(); let with_cycles = opts.with_cycles; let deploy_mode = match (mode, canister_name) { @@ -187,6 +191,7 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { opts.yes, env_file, opts.no_asset_upgrade, + subnet_selection, ))?; if matches!(deploy_mode, NormalDeploy | ForceReinstallSingleCanister(_)) { diff --git a/src/dfx/src/commands/ledger/create_canister.rs b/src/dfx/src/commands/ledger/create_canister.rs index 0f1bd2fdec..e47c3b0e2b 100644 --- a/src/dfx/src/commands/ledger/create_canister.rs +++ b/src/dfx/src/commands/ledger/create_canister.rs @@ -9,6 +9,7 @@ use crate::lib::nns_types::icpts::{ICPTs, TRANSACTION_FEE}; use crate::lib::operations::cmc::{notify_create, transfer_cmc}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers::e8s_parser; +use crate::util::clap::subnet_selection_opt::SubnetSelectionOpt; use anyhow::{bail, Context}; use candid::Principal; use clap::Parser; @@ -51,11 +52,8 @@ pub struct CreateCanisterOpts { #[arg(long)] created_at_time: 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. - #[arg(long)] - subnet_type: Option, + #[command(flatten)] + subnet_selection: SubnetSelectionOpt, } pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult { @@ -88,7 +86,8 @@ pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult .await?; println!("Using transfer at block height {height}"); - let result = notify_create(agent, controller, height, opts.subnet_type).await; + let subnet_selection = opts.subnet_selection.into_subnet_selection(); + let result = notify_create(agent, controller, height, subnet_selection).await; match result { Ok(principal) => { diff --git a/src/dfx/src/commands/ledger/notify/create_canister.rs b/src/dfx/src/commands/ledger/notify/create_canister.rs index a9e2f53e65..32d44b43da 100644 --- a/src/dfx/src/commands/ledger/notify/create_canister.rs +++ b/src/dfx/src/commands/ledger/notify/create_canister.rs @@ -3,23 +3,21 @@ use crate::lib::ledger_types::NotifyError::Refunded; use crate::lib::operations::cmc::notify_create; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::{environment::Environment, error::DfxResult}; +use crate::util::clap::subnet_selection_opt::SubnetSelectionOpt; use anyhow::bail; use candid::Principal; use clap::Parser; #[derive(Parser)] pub struct NotifyCreateOpts { - /// BlockHeight at which the send transation was recorded. + /// BlockHeight at which the send transaction was recorded. block_height: u64, /// The controller of the created canister. controller: Principal, - /// 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. - #[arg(long)] - subnet_type: Option, + #[command(flatten)] + subnet_selection: SubnetSelectionOpt, } pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult { @@ -31,7 +29,8 @@ 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, opts.subnet_type).await; + let subnet_selection = opts.subnet_selection.into_subnet_selection(); + let result = notify_create(agent, controller, block_height, subnet_selection).await; match result { Ok(principal) => { diff --git a/src/dfx/src/lib/cycles_ledger_types/create_canister.rs b/src/dfx/src/lib/cycles_ledger_types/create_canister.rs new file mode 100644 index 0000000000..f9bfd4c7ed --- /dev/null +++ b/src/dfx/src/lib/cycles_ledger_types/create_canister.rs @@ -0,0 +1,72 @@ +use candid::{CandidType, Nat, Principal}; +use ic_utils::interfaces::management_canister::builders::CanisterSettings; +use serde::Deserialize; +use thiserror::Error; + +#[derive(CandidType, Clone, Debug)] +pub struct CreateCanisterArgs { + pub from_subaccount: Option, + pub created_at_time: Option, + pub amount: u128, + pub creation_args: Option, +} +#[derive(CandidType, Clone, Debug)] +pub struct CmcCreateCanisterArgs { + pub subnet_selection: Option, + pub settings: Option, +} + +#[derive(Deserialize, CandidType, Clone, Debug, PartialEq, Eq, Error)] +pub enum CmcCreateCanisterError { + #[error("Failed to create canister: {create_error}\n{refund_amount} cycles refunded.")] + Refunded { + refund_amount: u128, + create_error: String, + }, + #[error("Failed to create canister: {create_error}\nRefund failed: {refund_error}")] + RefundFailed { + create_error: String, + refund_error: String, + }, +} + +#[derive(CandidType, Clone, Debug)] +pub enum SubnetSelection { + /// Choose a random subnet that satisfies the specified properties + Filter(SubnetFilter), + /// Choose a specific subnet + Subnet { subnet: Principal }, +} +#[derive(CandidType, Clone, Debug)] +pub struct SubnetFilter { + pub subnet_type: Option, +} +#[derive(CandidType, Clone, Debug, Deserialize, Error)] +pub enum CreateCanisterError { + #[error("Insufficient funds. Current balance: {balance}")] + InsufficientFunds { balance: u128 }, + #[error("Local clock too far behind.")] + TooOld, + #[error("Local clock too far ahead.")] + CreatedInFuture { ledger_time: u64 }, + #[error("Cycles ledger temporarily unavailable.")] + TemporarilyUnavailable, + #[error("Duplicate of block {duplicate_of}.")] + Duplicate { + duplicate_of: Nat, + canister_id: Option, + }, + #[error("Cycles ledger failed to create canister: {error}")] + FailedToCreate { + fee_block: Option, + refund_block: Option, + error: String, + }, + #[error("Ledger error {error_code}: {message}")] + GenericError { error_code: Nat, message: String }, +} +#[derive(Deserialize, CandidType, Clone, Debug, PartialEq, Eq)] +pub struct CreateCanisterSuccess { + pub block_id: Nat, + pub canister_id: Principal, +} diff --git a/src/dfx/src/lib/cycles_ledger_types/deposit.rs b/src/dfx/src/lib/cycles_ledger_types/deposit.rs new file mode 100644 index 0000000000..84cace6d2d --- /dev/null +++ b/src/dfx/src/lib/cycles_ledger_types/deposit.rs @@ -0,0 +1,8 @@ +use candid::CandidType; +use icrc_ledger_types::icrc1::account::Account; + +#[derive(CandidType, Debug, Clone)] +pub struct DepositArg { + pub to: Account, + pub memo: Option>, +} diff --git a/src/dfx/src/lib/cycles_ledger_types/mod.rs b/src/dfx/src/lib/cycles_ledger_types/mod.rs index 5c9ff69435..f8bbaab7f7 100644 --- a/src/dfx/src/lib/cycles_ledger_types/mod.rs +++ b/src/dfx/src/lib/cycles_ledger_types/mod.rs @@ -1 +1,4 @@ +// TODO(FI-1022): Import types from cycles ledger crate once available +pub mod create_canister; +pub mod deposit; pub mod send; diff --git a/src/dfx/src/lib/ic_attributes/mod.rs b/src/dfx/src/lib/ic_attributes/mod.rs index 2722283d0a..0b3bf7a7af 100644 --- a/src/dfx/src/lib/ic_attributes/mod.rs +++ b/src/dfx/src/lib/ic_attributes/mod.rs @@ -1,5 +1,5 @@ use crate::lib::error::DfxResult; -use anyhow::{anyhow, Context}; +use anyhow::{anyhow, Context, Error}; use byte_unit::Byte; use candid::Principal; use dfx_core::config::model::dfinity::ConfigInterface; @@ -7,6 +7,7 @@ use fn_error_context::context; use ic_utils::interfaces::management_canister::attributes::{ ComputeAllocation, FreezingThreshold, MemoryAllocation, ReservedCyclesLimit, }; +use num_traits::ToPrimitive; use std::convert::TryFrom; #[derive(Default, Debug, Clone)] @@ -44,6 +45,53 @@ impl From } } +impl TryFrom + for CanisterSettings +{ + type Error = Error; + fn try_from( + value: ic_utils::interfaces::management_canister::builders::CanisterSettings, + ) -> Result { + Ok(Self { + controllers: value.controllers, + compute_allocation: value + .compute_allocation + .and_then(|alloc| alloc.0.to_u8()) + .map(|alloc| { + ComputeAllocation::try_from(alloc) + .context("Compute allocation must be a percentage.") + }) + .transpose()?, + memory_allocation: value + .memory_allocation + .and_then(|alloc| alloc.0.to_u64()) + .map(|alloc| { + MemoryAllocation::try_from(alloc).context( + "Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.", + ) + }) + .transpose()?, + freezing_threshold: value + .freezing_threshold + .and_then(|threshold| threshold.0.to_u64()) + .map(|threshold| { + FreezingThreshold::try_from(threshold) + .context("Freezing threshold must be between 0 and 2^64-1, inclusively.") + }) + .transpose()?, + reserved_cycles_limit: value + .reserved_cycles_limit + .and_then(|limit| limit.0.to_u128()) + .map(|limit| { + ReservedCyclesLimit::try_from(limit).context( + "Reserved cycles limit must be between 0 and 2^128-1, inclusively.", + ) + }) + .transpose()?, + }) + } +} + #[context("Failed to get compute allocation.")] pub fn get_compute_allocation( compute_allocation: Option, diff --git a/src/dfx/src/lib/ledger_types/mod.rs b/src/dfx/src/lib/ledger_types/mod.rs index c274a5f1b1..6f9fe6abb4 100644 --- a/src/dfx/src/lib/ledger_types/mod.rs +++ b/src/dfx/src/lib/ledger_types/mod.rs @@ -9,6 +9,8 @@ use candid::Principal; use serde::{Deserialize, Serialize}; use std::fmt; +use super::cycles_ledger_types::create_canister::SubnetSelection; + /// Id of the ledger canister on the IC. pub const MAINNET_LEDGER_CANISTER_ID: Principal = Principal::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01]); @@ -127,7 +129,7 @@ pub struct TimeStamp { pub struct NotifyCreateCanisterArg { pub block_index: BlockIndex, pub controller: Principal, - pub subnet_type: Option, + pub subnet_selection: Option, } #[derive(CandidType)] diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index 7e0f0c0fef..72e0e088cc 100644 --- a/src/dfx/src/lib/operations/canister/create_canister.rs +++ b/src/dfx/src/lib/operations/canister/create_canister.rs @@ -1,6 +1,10 @@ +use crate::lib::cycles_ledger_types::create_canister::{ + CmcCreateCanisterArgs, CmcCreateCanisterError, SubnetSelection, +}; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::ic_attributes::CanisterSettings as DfxCanisterSettings; +use crate::lib::ledger_types::MAINNET_CYCLE_MINTER_CANISTER_ID; use crate::lib::operations::canister::motoko_playground::reserve_canister_with_playground; use crate::lib::operations::cycles_ledger::{create_with_cycles_ledger, CYCLES_LEDGER_ENABLED}; use anyhow::{anyhow, bail, Context}; @@ -12,7 +16,9 @@ use fn_error_context::context; use ic_agent::agent::{RejectCode, RejectResponse}; use ic_agent::agent_error::HttpErrorPayload; use ic_agent::{Agent, AgentError}; +use ic_utils::interfaces::management_canister::builders::CanisterSettings; use ic_utils::interfaces::ManagementCanister; +use ic_utils::Argument; use icrc_ledger_types::icrc1::account::Subaccount; use slog::info; use std::format; @@ -22,6 +28,7 @@ pub const CANISTER_CREATE_FEE: u128 = 100_000_000_000_u128; // We do not know the minimum cycle balance a canister should have. // For now create the canister with 3T cycle balance. pub const CANISTER_INITIAL_CYCLE_BALANCE: u128 = 3_000_000_000_000_u128; +pub const CMC_CREATE_CANISTER_METHOD: &str = "create_canister"; #[context("Failed to create canister '{}'.", canister_name)] pub async fn create_canister( @@ -33,6 +40,7 @@ pub async fn create_canister( from_subaccount: Option, settings: DfxCanisterSettings, created_at_time: Option, + subnet_selection: Option, ) -> DfxResult { let log = env.get_logger(); info!(log, "Creating canister {}...", canister_name); @@ -91,6 +99,7 @@ pub async fn create_canister( from_subaccount, settings, created_at_time, + subnet_selection, ) .await } else { @@ -99,7 +108,7 @@ pub async fn create_canister( } } CallSender::Wallet(wallet_id) => { - create_with_wallet(agent, wallet_id, with_cycles, settings).await + create_with_wallet(agent, wallet_id, with_cycles, settings, subnet_selection).await } }?; let canister_id = cid.to_text(); @@ -168,28 +177,72 @@ async fn create_with_wallet( wallet_id: &Principal, with_cycles: Option, settings: DfxCanisterSettings, + subnet_selection: Option, ) -> DfxResult { let wallet = build_wallet_canister(*wallet_id, agent).await?; let cycles = with_cycles.unwrap_or(CANISTER_CREATE_FEE + CANISTER_INITIAL_CYCLE_BALANCE); - if settings.reserved_cycles_limit.is_some() { - bail!( - "Cannot create a canister using a wallet if the reserved_cycles_limit is set. Please create with --no-wallet or use dfx canister update-settings instead.") - } - match wallet - .wallet_create_canister( - cycles, - settings.controllers, - settings.compute_allocation, - settings.memory_allocation, - settings.freezing_threshold, - ) - .await - { - Ok(result) => Ok(result.canister_id), - Err(AgentError::WalletUpgradeRequired(s)) => Err(anyhow!( - "{}\nTo upgrade, run dfx wallet upgrade.", - AgentError::WalletUpgradeRequired(s) - )), - Err(other) => Err(anyhow!(other)), + + if let Some(subnet_selection) = subnet_selection { + // `wallet_create_canister` only calls the management canister, which means that canisters only get created on the subnet the wallet is on. + // For any other targeting we need to use the CMC. + + let settings = if settings.controllers.is_some() { + settings + } else { + let identity = agent + .get_principal() + .map_err(|err| anyhow!("Failed to get selected identity principal: {err}"))?; + DfxCanisterSettings { + controllers: Some(vec![*wallet_id, identity]), + ..settings + } + }; + + let call_result: Result< + (Result,), + ic_agent::AgentError, + > = wallet + .call128( + MAINNET_CYCLE_MINTER_CANISTER_ID, + CMC_CREATE_CANISTER_METHOD, + Argument::from_candid((CmcCreateCanisterArgs { + settings: Some(CanisterSettings::from(settings)), + subnet_selection: Some(subnet_selection), + },)), + cycles, + ) + .call_and_wait() + .await; + match call_result { + Ok((Ok(canister_id),)) => Ok(canister_id), + Ok((Err(err),)) => Err(anyhow!(err)), + Err(AgentError::WalletUpgradeRequired(s)) => Err(anyhow!( + "{}\nTo upgrade, run dfx wallet upgrade.", + AgentError::WalletUpgradeRequired(s) + )), + Err(other) => Err(anyhow!(other)), + } + } else { + if settings.reserved_cycles_limit.is_some() { + bail!( + "Cannot create a canister using a wallet if the reserved_cycles_limit is set. Please create with --no-wallet or use dfx canister update-settings instead.") + } + match wallet + .wallet_create_canister( + cycles, + settings.controllers, + settings.compute_allocation, + settings.memory_allocation, + settings.freezing_threshold, + ) + .await + { + Ok(result) => Ok(result.canister_id), + Err(AgentError::WalletUpgradeRequired(s)) => Err(anyhow!( + "{}\nTo upgrade, run dfx wallet upgrade.", + AgentError::WalletUpgradeRequired(s) + )), + Err(other) => Err(anyhow!(other)), + } } } diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index ecf1baa015..96f1ebdaf2 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -1,6 +1,7 @@ use crate::lib::builders::BuildConfig; use crate::lib::canister_info::assets::AssetsCanisterInfo; use crate::lib::canister_info::CanisterInfo; +use crate::lib::cycles_ledger_types::create_canister::SubnetSelection; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::ic_attributes::CanisterSettings; @@ -53,6 +54,7 @@ pub async fn deploy_canisters( skip_consent: bool, env_file: Option, no_asset_upgrade: bool, + subnet_selection: Option, ) -> DfxResult { let log = env.get_logger(); @@ -143,6 +145,7 @@ pub async fn deploy_canisters( from_subaccount, created_at_time, &config, + subnet_selection, ) .await?; } else { @@ -215,6 +218,7 @@ async fn register_canisters( from_subaccount: Option, created_at_time: Option, config: &Config, + subnet_selection: Option, ) -> DfxResult { let canisters_to_create = canister_names .iter() @@ -279,6 +283,7 @@ async fn register_canisters( reserved_cycles_limit, }, created_at_time, + subnet_selection.clone(), ) .await?; } diff --git a/src/dfx/src/lib/operations/cmc.rs b/src/dfx/src/lib/operations/cmc.rs index 695ee3c65d..63239f3cb4 100644 --- a/src/dfx/src/lib/operations/cmc.rs +++ b/src/dfx/src/lib/operations/cmc.rs @@ -1,3 +1,4 @@ +use crate::lib::cycles_ledger_types::create_canister::SubnetSelection; use crate::lib::error::{DfxResult, NotifyCreateCanisterError, NotifyTopUpError}; use crate::lib::ledger_types::{ BlockHeight, Memo, NotifyCreateCanisterArg, NotifyCreateCanisterResult, NotifyTopUpArg, @@ -44,7 +45,7 @@ pub async fn notify_create( agent: &Agent, controller: Principal, block_height: BlockHeight, - subnet_type: Option, + subnet_selection: Option, ) -> Result { let result = agent .update( @@ -55,7 +56,7 @@ pub async fn notify_create( Encode!(&NotifyCreateCanisterArg { block_index: block_height, controller, - subnet_type, + subnet_selection, }) .map_err(NotifyCreateCanisterError::EncodeArguments)?, ) diff --git a/src/dfx/src/lib/operations/cycles_ledger.rs b/src/dfx/src/lib/operations/cycles_ledger.rs index 265e476323..02fe589815 100644 --- a/src/dfx/src/lib/operations/cycles_ledger.rs +++ b/src/dfx/src/lib/operations/cycles_ledger.rs @@ -1,6 +1,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::lib::cycles_ledger_types; +use crate::lib::cycles_ledger_types::create_canister::{ + CmcCreateCanisterArgs, CreateCanisterArgs, CreateCanisterError, CreateCanisterSuccess, + SubnetSelection, +}; +use crate::lib::cycles_ledger_types::deposit::DepositArg; use crate::lib::cycles_ledger_types::send::SendError; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; @@ -12,19 +17,16 @@ use crate::lib::retryable::retryable; use anyhow::{anyhow, bail, Context}; use backoff::future::retry; use backoff::ExponentialBackoff; -use candid::{CandidType, Decode, Encode, Nat, Principal}; +use candid::{Decode, Encode, Nat, Principal}; use dfx_core::canister::build_wallet_canister; use fn_error_context::context; use ic_agent::Agent; use ic_utils::call::SyncCall; -use ic_utils::interfaces::management_canister::builders::CanisterSettings; use ic_utils::{Argument, Canister}; use icrc_ledger_types::icrc1; use icrc_ledger_types::icrc1::account::{Account, Subaccount}; use icrc_ledger_types::icrc1::transfer::{BlockIndex, TransferError}; -use serde::Deserialize; use slog::{info, Logger}; -use thiserror::Error; /// Cycles ledger feature flag to turn off behavior that would be confusing while cycles ledger is not enabled yet. //TODO(SDK-1331): feature flag can be removed @@ -198,62 +200,8 @@ pub async fn create_with_cycles_ledger( from_subaccount: Option, settings: DfxCanisterSettings, created_at_time: Option, + subnet_selection: Option, ) -> DfxResult { - #[derive(CandidType, Clone, Debug)] - // TODO(FI-1022): Import types from cycles ledger crate once available - struct CreateCanisterArgs { - pub from_subaccount: Option, - pub created_at_time: Option, - pub amount: u128, - pub creation_args: Option, - } - #[derive(CandidType, Clone, Debug)] - struct CmcCreateCanisterArgs { - pub subnet_selection: Option, - pub settings: Option, - } - #[derive(CandidType, Clone, Debug)] - #[allow(dead_code)] - enum SubnetSelection { - /// Choose a random subnet that satisfies the specified properties - Filter(SubnetFilter), - /// Choose a specific subnet - Subnet { subnet: Principal }, - } - #[derive(CandidType, Clone, Debug)] - struct SubnetFilter { - pub subnet_type: Option, - } - #[derive(CandidType, Clone, Debug, Deserialize, Error)] - enum CreateCanisterError { - #[error("Insufficient funds. Current balance: {balance}")] - InsufficientFunds { balance: u128 }, - #[error("Local clock too far behind.")] - TooOld, - #[error("Local clock too far ahead.")] - CreatedInFuture { ledger_time: u64 }, - #[error("Cycles ledger temporarily unavailable.")] - TemporarilyUnavailable, - #[error("Duplicate of block {duplicate_of}.")] - Duplicate { - duplicate_of: Nat, - canister_id: Option, - }, - #[error("Cycles ledger failed to create canister: {error}")] - FailedToCreate { - fee_block: Option, - refund_block: Option, - error: String, - }, - #[error("Ledger error {error_code}: {message}")] - GenericError { error_code: Nat, message: String }, - } - #[derive(Deserialize, CandidType, Clone, Debug, PartialEq, Eq)] - struct CreateCanisterSuccess { - pub block_id: Nat, - pub canister_id: Principal, - } - let cycles = with_cycles.unwrap_or(CANISTER_CREATE_FEE + CANISTER_INITIAL_CYCLE_BALANCE); let created_at_time = created_at_time.or_else(|| { let now = SystemTime::now() @@ -267,21 +215,20 @@ pub async fn create_with_cycles_ledger( Some(now) }); + let arg = Encode!(&CreateCanisterArgs { + from_subaccount, + created_at_time, + amount: cycles, + creation_args: Some(CmcCreateCanisterArgs { + settings: Some(settings.into()), + subnet_selection, + }), + }) + .unwrap(); let result = loop { match agent .update(&CYCLES_LEDGER_CANISTER_ID, CREATE_CANISTER_METHOD) - .with_arg( - Encode!(&CreateCanisterArgs { - from_subaccount, - created_at_time, - amount: cycles, - creation_args: Some(CmcCreateCanisterArgs { - settings: Some(settings.clone().into()), - subnet_selection: None, - }), - }) - .unwrap(), - ) + .with_arg(arg.clone()) .call_and_wait() .await { @@ -328,13 +275,6 @@ pub async fn wallet_deposit_to_cycles_ledger( cycles_to_withdraw: u128, to: Account, ) -> DfxResult { - // TODO(FI-1022): Import types from cycles ledger crate once available - #[derive(CandidType)] - pub struct DepositArg { - pub to: Account, - pub memo: Option>, - } - build_wallet_canister(wallet_id, agent) .await? .call128( diff --git a/src/dfx/src/util/clap/mod.rs b/src/dfx/src/util/clap/mod.rs index 7171d1aea9..0f5392078b 100644 --- a/src/dfx/src/util/clap/mod.rs +++ b/src/dfx/src/util/clap/mod.rs @@ -2,6 +2,7 @@ use anstyle::{AnsiColor, Style}; use clap::builder::Styles; pub mod parsers; +pub mod subnet_selection_opt; pub fn style() -> Styles { let green = Style::new().fg_color(Some(AnsiColor::Green.into())); diff --git a/src/dfx/src/util/clap/subnet_selection_opt.rs b/src/dfx/src/util/clap/subnet_selection_opt.rs new file mode 100644 index 0000000000..05763be989 --- /dev/null +++ b/src/dfx/src/util/clap/subnet_selection_opt.rs @@ -0,0 +1,32 @@ +use candid::Principal; +use clap::{ArgGroup, Args}; + +use crate::lib::cycles_ledger_types::create_canister::{SubnetFilter, SubnetSelection}; + +#[derive(Args, Clone, Debug, Default)] +#[clap( +group(ArgGroup::new("subnet-selection").multiple(false)), +)] +pub struct SubnetSelectionOpt { + /// Specify the optional subnet type to create canisters on. If no + /// subnet type is provided, the canister will be created on a random + /// default application subnet. + #[arg(long, group = "subnet-selection")] + subnet_type: Option, + + /// Specify a specific subnet on which to create canisters on. + #[arg(long, group = "subnet-selection")] + subnet: Option, +} + +impl SubnetSelectionOpt { + pub fn into_subnet_selection(self) -> Option { + self.subnet_type + .map(|subnet_type| { + SubnetSelection::Filter(SubnetFilter { + subnet_type: Some(subnet_type), + }) + }) + .or_else(|| self.subnet.map(|subnet| SubnetSelection::Subnet { subnet })) + } +} diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index aa3b8b6b68..62afddd5fa 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -57,7 +57,7 @@ pub fn get_reusable_socket_addr(ip: IpAddr, port: u16) -> DfxResult .context("Failed to set linger duration of tcp listener.")?; listener .local_addr() - .context("Failed to fectch local address.") + .context("Failed to fetch local address.") } /// Deserialize and print return values from canister method.