From 8a3ea16360408b69f3f58d683c6c17c70d50e14e Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Thu, 16 May 2024 12:56:18 +0000 Subject: [PATCH] chore: add testnet for Subnet Rental Canister --- Cargo.lock | 5 + rs/tests/gix/nns_dapp_test.rs | 4 +- rs/tests/src/nns_dapp.rs | 51 ++++- .../testing_verification/testnets/BUILD.bazel | 24 +++ .../testing_verification/testnets/Cargo.toml | 9 + .../testing_verification/testnets/large.rs | 28 ++- .../testnets/small_nns.rs | 4 +- .../testnets/sns_testing.rs | 8 +- .../testnets/src_testing.rs | 200 ++++++++++++++++++ 9 files changed, 316 insertions(+), 17 deletions(-) create mode 100644 rs/tests/testing_verification/testnets/src_testing.rs diff --git a/Cargo.lock b/Cargo.lock index 573b23fcc40..e7b9753630b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18684,10 +18684,15 @@ name = "testnets" version = "0.9.0" dependencies = [ "anyhow", + "candid", + "ic-base-types", "ic-mainnet-nns-recovery", + "ic-registry-subnet-features", "ic-registry-subnet-type", + "ic-xrc-types", "slog", "tests", + "xrc-mock", ] [[package]] diff --git a/rs/tests/gix/nns_dapp_test.rs b/rs/tests/gix/nns_dapp_test.rs index ab4f50d0cdc..6ecf53bc8c4 100644 --- a/rs/tests/gix/nns_dapp_test.rs +++ b/rs/tests/gix/nns_dapp_test.rs @@ -27,7 +27,7 @@ use ic_tests::driver::{ test_env_api::{retry, secs, HasTopologySnapshot, NnsCanisterWasmStrategy}, }; use ic_tests::nns_dapp::{ - install_ii_and_nns_dapp, nns_dapp_customizations, set_authorized_subnets, + install_ii_nns_dapp_and_subnet_rental, nns_dapp_customizations, set_authorized_subnets, }; use ic_tests::orchestrator::utils::rw_message::install_nns_with_customizations_and_check_progress; use ic_tests::retry_with_msg; @@ -121,7 +121,7 @@ fn get_html(env: &TestEnv, farm_url: &str, canister_id: Principal, dapp_anchor: pub fn test(env: TestEnv) { let (ii_canister_id, nns_dapp_canister_id) = - install_ii_and_nns_dapp(&env, BOUNDARY_NODE_NAME, None); + install_ii_nns_dapp_and_subnet_rental(&env, BOUNDARY_NODE_NAME, None); let boundary_node = env .get_deployed_boundary_node(BOUNDARY_NODE_NAME) .unwrap() diff --git a/rs/tests/src/nns_dapp.rs b/rs/tests/src/nns_dapp.rs index 7a6403a1aed..419095687a4 100644 --- a/rs/tests/src/nns_dapp.rs +++ b/rs/tests/src/nns_dapp.rs @@ -6,7 +6,7 @@ use crate::driver::{ NnsCustomizations, }, }; -use crate::nns::set_authorized_subnetwork_list; +use crate::nns::{set_authorized_subnetwork_list, update_xdr_per_icp}; use crate::sns_client::add_subnet_to_sns_deploy_whitelist; use crate::util::{block_on, create_canister, install_canister, runtime_from_url}; use candid::Principal; @@ -14,6 +14,7 @@ use candid::{CandidType, Encode}; use ic_base_types::SubnetId; use ic_icrc1_ledger::{InitArgsBuilder, LedgerArgument}; use ic_ledger_core::Tokens; +use ic_nns_constants::SUBNET_RENTAL_CANISTER_ID; use ic_registry_subnet_type::SubnetType; use icp_ledger::AccountIdentifier; use serde::{Deserialize, Serialize}; @@ -47,6 +48,13 @@ pub struct CanisterArguments { pub schema: Option, } +/// Initializes the ICP ledger canister with 1e9 ICP on an account +/// controlled by a secret key with the following PEM file: +/// -----BEGIN EC PRIVATE KEY----- +/// MHQCAQEEICJxApEbuZznKFpV+VKACRK30i6+7u5Z13/DOl18cIC+oAcGBSuBBAAK +/// oUQDQgAEPas6Iag4TUx+Uop+3NhE6s3FlayFtbwdhRVjvOar0kPTfE/N8N6btRnd +/// 74ly5xXEBNSXiENyxhEuzOZrIWMCNQ== +/// -----END EC PRIVATE KEY----- pub fn nns_dapp_customizations() -> NnsCustomizations { let mut ledger_balances = HashMap::new(); ledger_balances.insert( @@ -107,7 +115,11 @@ pub fn install_sns_aggregator( }) } -pub fn install_ii_and_nns_dapp( +/// Installs II, NNS dapp, and Subnet Rental Canister. +/// The Subnet Rental Canister is installed since otherwise +/// the canister ID of the ckETH ledger (required by the NNS dapp) +/// would conflict with the Subnet Rental Canister ID on mainnet. +pub fn install_ii_nns_dapp_and_subnet_rental( env: &TestEnv, boundary_node_name: &str, sns_aggregator_canister_id: Option, @@ -119,23 +131,29 @@ pub fn install_ii_and_nns_dapp( .unwrap(); let farm_url = boundary_node.get_playnet().unwrap(); let https_farm_url = format!("https://{}", farm_url); - let topology = env.topology_snapshot(); + // deploy the II canister + let topology = env.topology_snapshot(); let nns_node = topology.root_subnet().nodes().next().unwrap(); - let nns_agent = nns_node.build_default_agent(); - let ii_canister_id = nns_node.create_and_install_canister_with_arg(INTERNET_IDENTITY_WASM, None); + + // create the NNS dapp canister so that its canister ID is allocated + // and the Subnet Rental Canister gets its mainnet canister ID in the next step + // it can't be installed yet since we need to get the ckETH ledger canister ID first + let nns_agent = nns_node.build_default_agent(); let nns_dapp_canister_id = block_on( async move { create_canister(&nns_agent, nns_node.effective_canister_id()).await }, ); + // deploy the Subnet Rental Canister let nns_node = topology.root_subnet().nodes().next().unwrap(); - let nns_agent = nns_node.build_default_agent(); - - nns_node.create_and_install_canister_with_arg(SUBNET_RENTAL_CANISTER_WASM, None); + let subnet_rental_canister_id = + nns_node.create_and_install_canister_with_arg(SUBNET_RENTAL_CANISTER_WASM, None); + assert_eq!(subnet_rental_canister_id, SUBNET_RENTAL_CANISTER_ID.into()); + // deploy the ckETH ledger canister (ICRC1-ledger with "ckETH" as token symbol and name) required by NNS dapp let cketh_init_args = InitArgsBuilder::for_tests() .with_token_symbol("ckETH".to_string()) .with_token_name("ckETH".to_string()) @@ -145,6 +163,8 @@ pub fn install_ii_and_nns_dapp( Some(Encode!(&(LedgerArgument::Init(cketh_init_args))).unwrap()), ); + // now that we know all required canister IDs, install the NNS dapp + let nns_agent = nns_node.build_default_agent(); let nns_dapp_wasm = env.load_wasm(NNS_DAPP_WASM); let logger = env.logger(); block_on(async move { @@ -191,6 +211,21 @@ pub fn install_ii_and_nns_dapp( }) } +pub fn set_icp_xdr_exchange_rate(env: &TestEnv, xdr_permyriad_per_icp: u64) { + let topology = env.topology_snapshot(); + let nns_node = topology.root_subnet().nodes().next().unwrap(); + let nns = runtime_from_url(nns_node.get_public_url(), nns_node.effective_canister_id()); + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + block_on(async move { + update_xdr_per_icp(&nns, timestamp, xdr_permyriad_per_icp) + .await + .unwrap(); + }); +} + pub fn set_authorized_subnets(env: &TestEnv) { let topology = env.topology_snapshot(); let nns_node = topology.root_subnet().nodes().next().unwrap(); diff --git a/rs/tests/testing_verification/testnets/BUILD.bazel b/rs/tests/testing_verification/testnets/BUILD.bazel index 184c5829df5..652ef1bb390 100644 --- a/rs/tests/testing_verification/testnets/BUILD.bazel +++ b/rs/tests/testing_verification/testnets/BUILD.bazel @@ -147,6 +147,30 @@ system_test( deps = DEPENDENCIES + ["//rs/tests"], ) +system_test( + name = "src_testing", + flaky = False, + proc_macro_deps = MACRO_DEPENDENCIES, + tags = [ + "dynamic_testnet", + "manual", + ], + target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS + runtime_deps = GUESTOS_RUNTIME_DEPS + NNS_CANISTER_RUNTIME_DEPS + SNS_CANISTER_RUNTIME_DEPS + BOUNDARY_NODE_GUESTOS_RUNTIME_DEPS + GRAFANA_RUNTIME_DEPS + [ + "//rs/rosetta-api/icrc1/ledger:ledger_canister.wasm.gz", + "//rs/rosetta-api/tvl/xrc_mock:xrc_mock_canister.wasm.gz", + "@ii_dev_canister//file", + "@nns_dapp_canister//file", + "@sns_aggregator//file", + "@subnet_rental_canister//file", + ], + deps = DEPENDENCIES + [ + "//rs/rosetta-api/tvl/xrc_mock", + "//rs/tests", + "@crate_index//:ic-xrc-types", + ], +) + # recovered_mainnet_nns is a manual system-test that deploys an IC with a NNS that is recovered from the latest mainnet state. system_test( name = "recovered_mainnet_nns", diff --git a/rs/tests/testing_verification/testnets/Cargo.toml b/rs/tests/testing_verification/testnets/Cargo.toml index a5ee8c84419..0ea3c526f76 100644 --- a/rs/tests/testing_verification/testnets/Cargo.toml +++ b/rs/tests/testing_verification/testnets/Cargo.toml @@ -8,10 +8,15 @@ documentation.workspace = true [dependencies] anyhow = { workspace = true } +candid = { workspace = true } +ic-base-types = { path = "../../../types/base_types" } +ic-registry-subnet-features = { path = "../../../registry/subnet_features" } ic-registry-subnet-type = { path = "../../../registry/subnet_type" } tests = { path = "../.." } ic-mainnet-nns-recovery = { path = "../../nns/ic_mainnet_nns_recovery" } +ic-xrc-types = "1.0.0" slog = { workspace = true } +xrc-mock = { path = "../../../rosetta-api/tvl/xrc_mock" } [[bin]] name = "large" @@ -44,3 +49,7 @@ path = "small.rs" [[bin]] name = "sns_testing" path = "sns_testing.rs" + +[[bin]] +name = "src_testing" +path = "src_testing.rs" diff --git a/rs/tests/testing_verification/testnets/large.rs b/rs/tests/testing_verification/testnets/large.rs index e790b8e7d05..256c5413a73 100644 --- a/rs/tests/testing_verification/testnets/large.rs +++ b/rs/tests/testing_verification/testnets/large.rs @@ -55,8 +55,8 @@ use ic_tests::driver::{ }, }; use ic_tests::nns_dapp::{ - install_ii_and_nns_dapp, install_sns_aggregator, nns_dapp_customizations, - set_authorized_subnets, set_sns_subnet, + install_ii_nns_dapp_and_subnet_rental, install_sns_aggregator, nns_dapp_customizations, + set_authorized_subnets, set_icp_xdr_exchange_rate, set_sns_subnet, }; use ic_tests::orchestrator::utils::rw_message::install_nns_with_customizations_and_check_progress; use ic_tests::sns_client::add_all_wasms_to_sns_wasm; @@ -73,9 +73,12 @@ fn main() -> Result<()> { } pub fn setup(env: TestEnv) { + // start p8s for metrics and dashboards PrometheusVm::default() .start(&env) .expect("Failed to start prometheus VM"); + + // set up IC overriding the default resources to be more powerful let vm_resources = VmResources { vcpus: Some(NrOfVCPUs::new(64)), memory_kibibytes: Some(AmountOfMemoryKiB::new(480 << 20)), @@ -91,12 +94,21 @@ pub fn setup(env: TestEnv) { } ic.setup_and_start(&env) .expect("Failed to setup IC under test"); + + // set up NNS canisters install_nns_with_customizations_and_check_progress( env.topology_snapshot(), NnsCanisterWasmStrategy::TakeBuiltFromSources, nns_dapp_customizations(), ); + + // sets the exchange rate to 12 XDR per 1 ICP + set_icp_xdr_exchange_rate(&env, 12_0000); + + // sets the exchange rate to 12 XDR per 1 ICP set_authorized_subnets(&env); + + // deploys the boundary node(s) let mut farm_url: Option = None; for i in 0..NUM_BN { let bn_name = format!("boundary-node-{}", i); @@ -117,20 +129,30 @@ pub fn setup(env: TestEnv) { } } env.sync_with_prometheus_by_name("", farm_url); + for i in 0..NUM_BN { let bn_name = format!("boundary-node-{}", i); await_boundary_node_healthy(&env, &bn_name); if i == 0 { + // pick an SNS subnet among the application subnets let topology = env.topology_snapshot(); let mut app_subnets = topology .subnets() .filter(|s| s.subnet_type() == SubnetType::Application); let sns_subnet = app_subnets.next().unwrap(); + + // install the SNS aggregator canister onto the SNS subnet let sns_node = sns_subnet.nodes().next().unwrap(); let sns_aggregator_canister_id = install_sns_aggregator(&env, &bn_name, sns_node); - install_ii_and_nns_dapp(&env, &bn_name, Some(sns_aggregator_canister_id)); + + // register the SNS subnet with the NNS set_sns_subnet(&env, sns_subnet.subnet_id); + + // upload SNS canister WASMs to the SNS-W canister add_all_wasms_to_sns_wasm(&env, NnsCanisterWasmStrategy::TakeBuiltFromSources); + + // install II, NNS dapp, and Subnet Rental Canister + install_ii_nns_dapp_and_subnet_rental(&env, &bn_name, Some(sns_aggregator_canister_id)); } } } diff --git a/rs/tests/testing_verification/testnets/small_nns.rs b/rs/tests/testing_verification/testnets/small_nns.rs index 781d49ee033..5c728aabf77 100644 --- a/rs/tests/testing_verification/testnets/small_nns.rs +++ b/rs/tests/testing_verification/testnets/small_nns.rs @@ -47,7 +47,7 @@ use ic_tests::driver::{ test_env_api::{await_boundary_node_healthy, HasTopologySnapshot, NnsCanisterWasmStrategy}, }; use ic_tests::nns_dapp::{ - install_ii_and_nns_dapp, nns_dapp_customizations, set_authorized_subnets, + install_ii_nns_dapp_and_subnet_rental, nns_dapp_customizations, set_authorized_subnets, }; use ic_tests::orchestrator::utils::rw_message::install_nns_with_customizations_and_check_progress; @@ -82,7 +82,7 @@ pub fn setup(env: TestEnv) { .use_real_certs_and_dns() .start(&env) .expect("failed to setup BoundaryNode VM"); - install_ii_and_nns_dapp(&env, BOUNDARY_NODE_NAME, None); + install_ii_nns_dapp_and_subnet_rental(&env, BOUNDARY_NODE_NAME, None); set_authorized_subnets(&env); let boundary_node = env .get_deployed_boundary_node(BOUNDARY_NODE_NAME) diff --git a/rs/tests/testing_verification/testnets/sns_testing.rs b/rs/tests/testing_verification/testnets/sns_testing.rs index 30c1b38223c..9e9bc6bfb93 100644 --- a/rs/tests/testing_verification/testnets/sns_testing.rs +++ b/rs/tests/testing_verification/testnets/sns_testing.rs @@ -49,7 +49,7 @@ use ic_tests::driver::{ }, }; use ic_tests::nns_dapp::{ - install_ii_and_nns_dapp, install_sns_aggregator, nns_dapp_customizations, + install_ii_nns_dapp_and_subnet_rental, install_sns_aggregator, nns_dapp_customizations, set_authorized_subnets, set_sns_subnet, }; use ic_tests::orchestrator::utils::rw_message::install_nns_with_customizations_and_check_progress; @@ -110,7 +110,11 @@ pub fn setup(env: TestEnv) { info!(logger, "Use {} as effective canister ID when creating canisters for your dapp, e.g., using --provisional-create-canister-effective-canister-id {} with DFX", app_effective_canister_id, app_effective_canister_id); let sns_aggregator_canister_id = install_sns_aggregator(&env, BOUNDARY_NODE_NAME, sns_node); - install_ii_and_nns_dapp(&env, BOUNDARY_NODE_NAME, Some(sns_aggregator_canister_id)); + install_ii_nns_dapp_and_subnet_rental( + &env, + BOUNDARY_NODE_NAME, + Some(sns_aggregator_canister_id), + ); set_authorized_subnets(&env); set_sns_subnet(&env, sns_subnet.subnet_id); add_all_wasms_to_sns_wasm(&env, NnsCanisterWasmStrategy::TakeBuiltFromSources); diff --git a/rs/tests/testing_verification/testnets/src_testing.rs b/rs/tests/testing_verification/testnets/src_testing.rs new file mode 100644 index 00000000000..033826c50d2 --- /dev/null +++ b/rs/tests/testing_verification/testnets/src_testing.rs @@ -0,0 +1,200 @@ +// Set up a testnet containing: +// one 1-node System subnet for the NNS, +// one 1-node System subnet for exchange rate canister, +// 32 1-node Application subnets filling the canister ID ranges between the NNS and the exchange rate canister and also used for the cycles wallet, +// a single boundary node, and a p8s (with grafana) VM. +// All replica nodes use the default resources. +// +// You can setup this testnet with a lifetime of 180 mins by executing the following commands: +// +// $ ./gitlab-ci/tools/docker-run +// $ ict testnet create src_testing --lifetime-mins=180 --output-dir=./src_testing -- --test_tmpdir=./src_testing +// +// The --output-dir=./src_testing will store the debug output of the test driver in the specified directory. +// The --test_tmpdir=./src_testing will store the remaining test output in the specified directory. +// This is useful to have access to in case you need to SSH into an IC node for example like: +// +// $ ssh -i src_testing/_tmp/*/setup/ssh/authorized_priv_keys/admin admin@$ipv6 +// +// Note that you can get the $ipv6 address of the IC node from the ict console output: +// +// { +// "nodes": [ +// { +// "id": "y4g5e-dpl4n-swwhv-la7ec-32ngk-w7f3f-pr5bt-kqw67-2lmfy-agipc-zae", +// "ipv6": "2a0b:21c0:4003:2:5034:46ff:fe3c:e76f" +// } +// ], +// "subnet_id": "5hv4k-srndq-xgw53-r6ldt-wtv4x-6xvbj-6lvpf-sbu5n-sqied-63bgv-eqe", +// "subnet_type": "application" +// }, +// +// To get access to P8s and Grafana look for the following lines in the ict console output: +// +// "prometheus": "Prometheus Web UI at http://prometheus.src_testing--1692597750709.testnet.farm.dfinity.systems", +// "grafana": "Grafana at http://grafana.src_testing--1692597750709.testnet.farm.dfinity.systems", +// "progress_clock": "IC Progress Clock at http://grafana.src_testing--1692597750709.testnet.farm.dfinity.systems/d/ic-progress-clock/ic-progress-clock?refresh=10s\u0026from=now-5m\u0026to=now", +// +// Happy testing! + +use anyhow::Result; + +use candid::Encode; +use ic_base_types::{CanisterId, PrincipalId}; +use ic_registry_subnet_features::SubnetFeatures; +use ic_registry_subnet_type::SubnetType; +use ic_tests::driver::boundary_node::BoundaryNodeVm; +use ic_tests::driver::ic::{InternetComputer, Subnet}; +use ic_tests::driver::{ + boundary_node::BoundaryNode, + group::SystemTestGroup, + prometheus_vm::{HasPrometheus, PrometheusVm}, + test_env::TestEnv, + test_env_api::{ + await_boundary_node_healthy, HasPublicApiUrl, HasTopologySnapshot, IcNodeContainer, + NnsCanisterWasmStrategy, + }, +}; +use ic_tests::nns_dapp::{ + install_ii_nns_dapp_and_subnet_rental, nns_dapp_customizations, set_authorized_subnets, + set_icp_xdr_exchange_rate, +}; +use ic_tests::orchestrator::utils::rw_message::install_nns_with_customizations_and_check_progress; +use ic_tests::util::{block_on, create_canister}; +use ic_xrc_types::{Asset, AssetClass, ExchangeRateMetadata}; +use std::str::FromStr; +use xrc_mock::{ExchangeRate, Response, XrcMockInitPayload}; + +const DEFAULT_XRC_PRINCIPAL_STR: &str = "uf6dk-hyaaa-aaaaq-qaaaq-cai"; + +pub const EXCHANGE_RATE_CANISTER_WASM: &str = + "rs/rosetta-api/tvl/xrc_mock/xrc_mock_canister.wasm.gz"; + +fn main() -> Result<()> { + SystemTestGroup::new() + .with_setup(setup) + .execute_from_args()?; + Ok(()) +} + +/// CXDR is an asset whose rate is derived from more sources than the XDR rate. +/// The input rate to this method is the integer multiple of 1e-9 CXDR that is worth 1 ICP, +/// e.g., `rate` equal to `12_000_000_000` corresponds to the conversion rate of 12 CXDR for 1 ICP. +fn new_icp_cxdr_mock_exchange_rate_canister_init_payload(rate: u64) -> XrcMockInitPayload { + XrcMockInitPayload { + response: Response::ExchangeRate(ExchangeRate { + rate, + base_asset: Some(Asset { + symbol: "ICP".to_string(), + class: AssetClass::Cryptocurrency, + }), + quote_asset: Some(Asset { + symbol: "CXDR".to_string(), + class: AssetClass::FiatCurrency, + }), + metadata: Some(ExchangeRateMetadata { + decimals: 9, + base_asset_num_queried_sources: 7, + base_asset_num_received_rates: 5, + quote_asset_num_queried_sources: 10, + quote_asset_num_received_rates: 4, + standard_deviation: 0, + forex_timestamp: None, + }), + }), + } +} + +pub fn setup(env: TestEnv) { + // start p8s for metrics and dashboards + PrometheusVm::default() + .start(&env) + .expect("Failed to start prometheus VM"); + + // set up IC + let mut ic = InternetComputer::new(); + // the following subnets are gonna have IDs 1, 2, 3, ... + for _ in 0..32 { + ic = ic.add_subnet(Subnet::new(SubnetType::Application).add_nodes(1)); + } + ic = ic.add_subnet( + Subnet::new(SubnetType::System) + .with_features(SubnetFeatures { + http_requests: true, + ..SubnetFeatures::default() + }) + .add_nodes(1), + ); + // the last system subnet is the root subnet with ID 0 + ic = ic.add_subnet(Subnet::new(SubnetType::System).add_nodes(1)); + ic.setup_and_start(&env) + .expect("Failed to setup IC under test"); + + // set up NNS canisters + install_nns_with_customizations_and_check_progress( + env.topology_snapshot(), + NnsCanisterWasmStrategy::TakeBuiltFromSources, + nns_dapp_customizations(), + ); + + // sets the exchange rate to 12 XDR per 1 ICP + set_icp_xdr_exchange_rate(&env, 12_0000); + + // sets the application subnets as "authorized" for canister creation by CMC + set_authorized_subnets(&env); + + // deploy boundary node + let bn_name = "boundary-node".to_string(); + BoundaryNode::new(bn_name.clone()) + .allocate_vm(&env) + .expect("Allocation of BoundaryNode failed.") + .for_ic(&env, "") + .use_real_certs_and_dns() + .start(&env) + .expect("failed to setup BoundaryNode VM"); + let boundary_node = env + .get_deployed_boundary_node(bn_name.as_str()) + .unwrap() + .get_snapshot() + .unwrap(); + env.sync_with_prometheus_by_name("", boundary_node.get_playnet()); + await_boundary_node_healthy(&env, &bn_name); + + // install II, NNS dapp, and Subnet Rental Canister + install_ii_nns_dapp_and_subnet_rental(&env, &bn_name, None); + + // install the Exchange Rate Canister + let topology = env.topology_snapshot(); + // define the Exchange Rate Canister ID on mainnet + let default_xrc_principal_id = PrincipalId::from_str(DEFAULT_XRC_PRINCIPAL_STR).unwrap(); + let default_xrc_canister_id: CanisterId = default_xrc_principal_id.try_into().unwrap(); + // find the subnet containing the Exchange Rate Canister ID + let xrc_subnets = topology + .subnets() + .filter(|s| { + s.subnet_canister_ranges() + .into_iter() + .any(|r| r.contains(&default_xrc_canister_id)) + }) + .collect::>(); + // this subnet must be unique + assert_eq!(xrc_subnets.len(), 1); + let xrc_subnet = xrc_subnets.into_iter().next().unwrap(); + // and of the system subnet type + assert_eq!(xrc_subnet.subnet_type(), SubnetType::System); + let xrc_node = xrc_subnet.nodes().next().unwrap(); + let xrc_agent = xrc_node.build_default_agent(); + // we create a trivial canister to fill the first canister ID on the Exchange Rate Canister subnet + block_on(async move { + create_canister(&xrc_agent, xrc_node.effective_canister_id()).await; + }); + // the second canister ID on the Exchange Rate Canister subnet belongs to the Exchange Rate Canister + let xrc_node = xrc_subnet.nodes().next().unwrap(); + // we set the exchange rate to 12 XDR per 1 ICP + let xrc_payload = new_icp_cxdr_mock_exchange_rate_canister_init_payload(12_000_000_000); + let xrc_canister_id = xrc_node.create_and_install_canister_with_arg( + EXCHANGE_RATE_CANISTER_WASM, + Some(Encode!(&xrc_payload).unwrap()), + ); + assert_eq!(xrc_canister_id, default_xrc_principal_id.into()); +}