Skip to content

Commit

Permalink
feat: Add subnet type argument when creating canisters (#2584)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
dsarlis committed Sep 27, 2022
1 parent af74ba7 commit 06a1deb
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
53 changes: 48 additions & 5 deletions docs/cli-reference/dfx-ledger.md
Expand Up @@ -133,11 +133,13 @@ You can specify the following argument for the `dfx ledger create-canister` comm

| Option | Description |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--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. |
| `--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. |
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
| `--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. |
| `--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. |
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
| `--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. |


### Examples

Expand Down Expand Up @@ -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 <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.
Expand Down
22 changes: 22 additions & 0 deletions 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")]),
];
}
};

}
1 change: 1 addition & 0 deletions e2e/assets/cmc/patch.bash
@@ -0,0 +1 @@
jq '.canisters.cmc.main="main.mo"' dfx.json | sponge dfx.json
18 changes: 18 additions & 0 deletions e2e/tests-dfx/ledger.bash
Expand Up @@ -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"]'
}
9 changes: 8 additions & 1 deletion src/dfx/src/commands/ledger/create_canister.rs
Expand Up @@ -46,6 +46,12 @@ pub struct CreateCanisterOpts {
/// Max fee, default is 10000 e8s.
#[clap(long, validator(icpts_amount_validator))]
max_fee: Option<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<String>,
}

pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult {
Expand Down Expand Up @@ -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) => {
Expand Down
5 changes: 5 additions & 0 deletions src/dfx/src/commands/ledger/mod.rs
Expand Up @@ -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;

Expand All @@ -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),
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -199,13 +202,15 @@ pub async fn notify_create(
agent: &Agent,
controller: Principal,
block_height: BlockHeight,
subnet_type: Option<String>,
) -> DfxResult<NotifyCreateCanisterResult> {
let result = agent
.update(&MAINNET_CYCLE_MINTER_CANISTER_ID, NOTIFY_CREATE_METHOD)
.with_arg(
Encode!(&NotifyCreateCanisterArg {
block_index: block_height,
controller,
subnet_type,
})
.context("Failed to encode notify arguments.")?,
)
Expand Down
8 changes: 7 additions & 1 deletion src/dfx/src/commands/ledger/notify/create_canister.rs
Expand Up @@ -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<String>,
}

pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult {
Expand All @@ -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) => {
Expand Down
51 changes: 51 additions & 0 deletions 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<Principal>,
}

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<String> = result.data.into_iter().map(|(x, _)| x).collect();

println!("{:?}", available_subnet_types);

Ok(())
}
2 changes: 1 addition & 1 deletion src/dfx/src/commands/quickstart.rs
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/dfx/src/lib/ledger_types/mod.rs
Expand Up @@ -130,6 +130,7 @@ pub struct TimeStamp {
pub struct NotifyCreateCanisterArg {
pub block_index: BlockIndex,
pub controller: Principal,
pub subnet_type: Option<String>,
}

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

pub type NotifyTopUpResult = Result<u128, NotifyError>;

#[derive(CandidType, Deserialize, Debug)]
pub struct GetSubnetTypesToSubnetsResult {
pub data: Vec<(String, Vec<Principal>)>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit 06a1deb

Please sign in to comment.