From 76eec12e0e36998a635befdaff0107d21ebb5965 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 23 Jan 2025 11:44:27 +0100 Subject: [PATCH 01/12] nervous_system::integration_tests: add cycles prior installation Canister installation may fail due to insufficient cycles, so add cycles before installing the canister in 'install_canister_with_controllers'. The same is already done in 'install_canister_on_subnet'. --- rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs index 0ac6fe09568..89388e7acd5 100644 --- a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs @@ -181,10 +181,10 @@ pub async fn install_canister_with_controllers( .await .unwrap(); pocket_ic - .install_canister(canister_id, wasm.bytes(), arg, controller_principal) + .add_cycles(canister_id, STARTING_CYCLES_PER_CANISTER) .await; pocket_ic - .add_cycles(canister_id, STARTING_CYCLES_PER_CANISTER) + .install_canister(canister_id, wasm.bytes(), arg, controller_principal) .await; let subnet_id = pocket_ic.get_subnet(canister_id).await.unwrap(); println!( From 80b02238137bee0faf82b9499270e063089ab8da Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Fri, 24 Jan 2025 13:58:01 +0100 Subject: [PATCH 02/12] nervous_system::integration_tests: optionally install more NNS canisters Problem: NNS dapp needs ICP ledger index and Cycles minting canisters to be deployed. Currently 'install_nns_canisters' doesn't install them. Solution: Add 'install_nns_suite' helper that optionally installs ICP ledger index and Cycles minting canisters. The existing 'install_nns_canisters' reuses 'install_nns_suite' but doesn't install them. --- Cargo.lock | 1 + rs/ledger_suite/icp/index/src/lib.rs | 2 +- .../src/pocket_ic_helpers.rs | 39 +++++++++++++++---- rs/nns/test_utils/BUILD.bazel | 1 + rs/nns/test_utils/Cargo.toml | 1 + rs/nns/test_utils/src/common.rs | 18 +++++++++ 6 files changed, 53 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e158a510111..9aa27c4bfe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10956,6 +10956,7 @@ dependencies = [ "ic-crypto-test-utils-ni-dkg", "ic-crypto-test-utils-reproducible-rng", "ic-crypto-utils-ni-dkg", + "ic-icp-index", "ic-icrc1", "ic-management-canister-types-private", "ic-nervous-system-clients", diff --git a/rs/ledger_suite/icp/index/src/lib.rs b/rs/ledger_suite/icp/index/src/lib.rs index 76912ce9205..231ec789b5b 100644 --- a/rs/ledger_suite/icp/index/src/lib.rs +++ b/rs/ledger_suite/icp/index/src/lib.rs @@ -6,7 +6,7 @@ use serde_bytes::ByteBuf; pub mod logs; -#[derive(Debug, CandidType, Deserialize)] +#[derive(Clone, Debug, CandidType, Deserialize)] pub struct InitArg { pub ledger_id: Principal, } diff --git a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs index 89388e7acd5..bfca7165220 100644 --- a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs @@ -14,8 +14,8 @@ use ic_nervous_system_common_test_keys::{TEST_NEURON_1_ID, TEST_NEURON_1_OWNER_P use ic_nns_common::pb::v1::{NeuronId, ProposalId}; use ic_nns_constants::{ self, ALL_NNS_CANISTER_IDS, CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID, - LEDGER_CANISTER_ID, LIFELINE_CANISTER_ID, REGISTRY_CANISTER_ID, ROOT_CANISTER_ID, - SNS_WASM_CANISTER_ID, + LEDGER_CANISTER_ID, LEDGER_INDEX_CANISTER_ID, LIFELINE_CANISTER_ID, REGISTRY_CANISTER_ID, + ROOT_CANISTER_ID, SNS_WASM_CANISTER_ID, }; use ic_nns_governance_api::pb::v1::{ install_code::CanisterInstallMode, manage_neuron_response, CreateServiceNervousSystem, @@ -26,11 +26,11 @@ use ic_nns_governance_api::pb::v1::{ }; use ic_nns_test_utils::{ common::{ - build_cmc_wasm, build_governance_wasm, build_ledger_wasm, build_lifeline_wasm, - build_mainnet_cmc_wasm, build_mainnet_governance_wasm, build_mainnet_ledger_wasm, - build_mainnet_lifeline_wasm, build_mainnet_registry_wasm, build_mainnet_root_wasm, - build_mainnet_sns_wasms_wasm, build_registry_wasm, build_root_wasm, build_sns_wasms_wasm, - NnsInitPayloadsBuilder, + build_cmc_wasm, build_governance_wasm, build_index_wasm, build_ledger_wasm, + build_lifeline_wasm, build_mainnet_cmc_wasm, build_mainnet_governance_wasm, + build_mainnet_index_wasm, build_mainnet_ledger_wasm, build_mainnet_lifeline_wasm, + build_mainnet_registry_wasm, build_mainnet_root_wasm, build_mainnet_sns_wasms_wasm, + build_registry_wasm, build_root_wasm, build_sns_wasms_wasm, NnsInitPayloadsBuilder, }, sns_wasm::{ build_archive_sns_wasm, build_governance_sns_wasm, build_index_ng_sns_wasm, @@ -329,6 +329,7 @@ pub struct NnsInstaller { initial_balances: Vec<(AccountIdentifier, Tokens)>, with_cycles_minting_canister: bool, with_cycles_ledger: bool, + with_index_canister: bool, } impl NnsInstaller { @@ -389,6 +390,11 @@ impl NnsInstaller { self } + pub fn with_index_canister(&mut self) -> &mut Self { + self.with_index_canister = true; + self + } + /// Installs the NNS canister suite. /// /// Ensures that there is a whale neuron with `TEST_NEURON_1_ID`. @@ -547,6 +553,23 @@ impl NnsInstaller { cycles_ledger::install(pocket_ic).await; } + if self.with_index_canister { + let ledger_index_wasm = if with_mainnet_canister_versions { + build_mainnet_index_wasm() + } else { + build_index_wasm() + }; + install_canister( + pocket_ic, + "Index", + LEDGER_INDEX_CANISTER_ID, + Encode!(&nns_init_payload.index).unwrap(), + ledger_index_wasm, + Some(ROOT_CANISTER_ID.get()), + ) + .await; + } + nns_init_payload .governance .neurons @@ -694,7 +717,7 @@ pub mod cycles_ledger { /// test_user_icp_ledger_initial_balance)` pairs, representing some initial ICP balances. /// 3. `custom_registry_mutations` are custom mutations for the inital Registry. These /// should mutations should comply with Registry invariants, otherwise this function will fail. -/// 4. `maturity_equivalent_icp_e8s` - hotkeys of the 1st NNS (Neurons' Fund-participating) neuron. +/// 4. `neurons_fund_hotkeys` - hotkeys of the 1st NNS (Neurons' Fund-participating) neuron. /// /// Returns /// 1. A list of `controller_principal_id`s of pre-configured NNS neurons. diff --git a/rs/nns/test_utils/BUILD.bazel b/rs/nns/test_utils/BUILD.bazel index 1af0fe1748d..845996f9e42 100644 --- a/rs/nns/test_utils/BUILD.bazel +++ b/rs/nns/test_utils/BUILD.bazel @@ -15,6 +15,7 @@ BASE_DEPENDENCIES = [ "//rs/crypto/test_utils/reproducible_rng", "//rs/crypto/utils/ni_dkg", "//rs/ledger_suite/icp:icp_ledger", + "//rs/ledger_suite/icp/index:ic-icp-index", "//rs/ledger_suite/icrc1", "//rs/nervous_system/clients", "//rs/nervous_system/common", diff --git a/rs/nns/test_utils/Cargo.toml b/rs/nns/test_utils/Cargo.toml index 7339c806cfa..c482eef19c6 100644 --- a/rs/nns/test_utils/Cargo.toml +++ b/rs/nns/test_utils/Cargo.toml @@ -28,6 +28,7 @@ ic-crypto-sha2 = { path = "../../crypto/sha2" } ic-crypto-test-utils-ni-dkg = { path = "../../crypto/test_utils/ni-dkg" } ic-crypto-test-utils-reproducible-rng = { path = "../../crypto/test_utils/reproducible_rng" } ic-crypto-utils-ni-dkg = { path = "../../crypto/utils/ni_dkg" } +ic-icp-index = { path = "../../ledger_suite/icp/index" } ic-icrc1 = { path = "../../ledger_suite/icrc1" } ic-management-canister-types-private = { path = "../../types/management_canister_types" } ic-nervous-system-clients = { path = "../../nervous_system/clients" } diff --git a/rs/nns/test_utils/src/common.rs b/rs/nns/test_utils/src/common.rs index a7fc7624f55..01dd597bebf 100644 --- a/rs/nns/test_utils/src/common.rs +++ b/rs/nns/test_utils/src/common.rs @@ -41,6 +41,7 @@ pub struct NnsInitPayloads { pub lifeline: LifelineCanisterInitPayload, pub genesis_token: Gtc, pub sns_wasms: SnsWasmCanisterInitPayload, + pub index: ic_icp_index::InitArg, } /// Builder to help create the initial payloads for the NNS canisters. @@ -53,6 +54,7 @@ pub struct NnsInitPayloadsBuilder { pub lifeline: LifelineCanisterInitPayloadBuilder, pub genesis_token: GenesisTokenCanisterInitPayloadBuilder, pub sns_wasms: SnsWasmCanisterInitPayloadBuilder, + pub index: ic_icp_index::InitArg, } #[allow(clippy::new_without_default)] @@ -94,6 +96,9 @@ impl NnsInitPayloadsBuilder { lifeline: LifelineCanisterInitPayloadBuilder::new(), genesis_token: GenesisTokenCanisterInitPayloadBuilder::new(), sns_wasms: SnsWasmCanisterInitPayloadBuilder::new(), + index: ic_icp_index::InitArg { + ledger_id: LEDGER_CANISTER_ID.get().into(), + }, } } @@ -283,6 +288,7 @@ impl NnsInitPayloadsBuilder { lifeline: self.lifeline.build(), genesis_token: self.genesis_token.build(), sns_wasms: self.sns_wasms.build(), + index: self.index.clone(), } } } @@ -372,6 +378,12 @@ pub fn build_sns_wasms_wasm() -> Wasm { Project::cargo_bin_maybe_from_env("sns-wasm-canister", &features) } +/// Build Wasm for Index canister for the ICP Ledger +pub fn build_index_wasm() -> Wasm { + let features = []; + Project::cargo_bin_maybe_from_env("ic-icp-index", &features) +} + /// Build mainnet Wasm for NNS SnsWasm canister pub fn build_mainnet_sns_wasms_wasm() -> Wasm { let features = []; @@ -394,3 +406,9 @@ pub fn build_mainnet_governance_wasm() -> Wasm { let features = []; Project::cargo_bin_maybe_from_env("mainnet-governance-canister", &features) } + +/// Build Wasm for Index canister for the ICP Ledger +pub fn build_mainnet_index_wasm() -> Wasm { + let features = []; + Project::cargo_bin_maybe_from_env("mainnet-index-canister", &features) +} From eb10b92de8bd3f31f0f85d9d3cd185ef04ff8d7d Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Fri, 24 Jan 2025 15:45:13 +0100 Subject: [PATCH 03/12] sns-testing: init Init the new sns-testing project. Currently, this crate provides CLI to bootstrap NNS on the provided PocketIC instance. --- Cargo.lock | 15 +++ Cargo.toml | 1 + rs/sns/testing/BUILD.bazel | 92 +++++++++++++++++ rs/sns/testing/Cargo.toml | 26 +++++ rs/sns/testing/README.md | 8 ++ rs/sns/testing/src/lib.rs | 1 + rs/sns/testing/src/main.rs | 26 +++++ rs/sns/testing/src/pocket_ic.rs | 131 +++++++++++++++++++++++++ rs/sns/testing/tests/sns_testing_ci.rs | 14 +++ 9 files changed, 314 insertions(+) create mode 100644 rs/sns/testing/BUILD.bazel create mode 100644 rs/sns/testing/Cargo.toml create mode 100644 rs/sns/testing/README.md create mode 100644 rs/sns/testing/src/lib.rs create mode 100644 rs/sns/testing/src/main.rs create mode 100644 rs/sns/testing/src/pocket_ic.rs create mode 100644 rs/sns/testing/tests/sns_testing_ci.rs diff --git a/Cargo.lock b/Cargo.lock index 9aa27c4bfe8..d7095a98a97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12601,6 +12601,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "ic-sns-testing" +version = "0.9.0" +dependencies = [ + "candid", + "canister-test", + "clap 4.5.27", + "ic-nervous-system-integration-tests", + "ic-nns-constants", + "ic-nns-test-utils", + "pocket-ic", + "reqwest 0.12.12", + "tokio", +] + [[package]] name = "ic-sns-wasm" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 4d1b0b97846..f1b5b9886de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -346,6 +346,7 @@ members = [ "rs/sns/swap", "rs/sns/swap/proto_library", "rs/sns/test_utils", + "rs/sns/testing", "rs/starter", "rs/state_manager", "rs/state_machine_tests", diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel new file mode 100644 index 00000000000..670e26f18ca --- /dev/null +++ b/rs/sns/testing/BUILD.bazel @@ -0,0 +1,92 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +DEPENDENCIES = [ + "//packages/pocket-ic", + "//rs/nervous_system/integration_tests:nervous_system_integration_tests", + "//rs/nns/test_utils", + "//rs/nns/constants", + "//rs/rust_canisters/canister_test", + "@crate_index//:candid", + "@crate_index//:clap", + "@crate_index//:reqwest", + "@crate_index//:tokio", +] + +MACRO_DEPENDENCIES = [] + +ALIASES = {} + +DEV_DATA = [ + "//rs/ledger_suite/icp/archive:ledger-archive-node-canister-wasm", + "//rs/ledger_suite/icp/index:ic-icp-index-canister", + "//rs/ledger_suite/icp/ledger:ledger-canister-wasm", + "//rs/ledger_suite/icp/ledger:ledger-canister-wasm-notify-method", + "//rs/ledger_suite/icrc1/archive:archive_canister", + "//rs/ledger_suite/icrc1/index-ng:index_ng_canister", + "//rs/ledger_suite/icrc1/ledger:ledger_canister", + "//rs/nns/cmc:cycles-minting-canister", + "//rs/nns/governance:governance-canister", + "//rs/nns/handlers/root/impl:root-canister", + "//rs/nns/sns-wasm:sns-wasm-canister", + "//rs/pocket_ic_server:pocket-ic-server", + "//rs/registry/canister:registry-canister", + "//rs/sns/governance:sns-governance-canister", + "//rs/sns/root:sns-root-canister", + "//rs/sns/swap:sns-swap-canister", + "@nns_dapp_canister//file", + "@sns_aggregator//file", + "@ii_dev_canister//file", +] + +DEV_ENV = { + "CYCLES_MINTING_CANISTER_WASM_PATH": "$(rootpath //rs/nns/cmc:cycles-minting-canister)", + "GOVERNANCE_CANISTER_WASM_PATH": "$(rootpath //rs/nns/governance:governance-canister)", + "REGISTRY_CANISTER_WASM_PATH": "$(rootpath //rs/registry/canister:registry-canister)", + "IC_ICRC1_ARCHIVE_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/archive:archive_canister)", + "IC_ICRC1_INDEX_NG_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/index-ng:index_ng_canister)", + "IC_ICRC1_LEDGER_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/ledger:ledger_canister)", + "IC_ICP_INDEX_WASM_PATH": "$(rootpath //rs/ledger_suite/icp/index:ic-icp-index-canister)", + "LEDGER_CANISTER_WASM_PATH": "$(rootpath //rs/ledger_suite/icp/ledger:ledger-canister-wasm)", + "LEDGER_CANISTER_NOTIFY_METHOD_WASM_PATH": "$(rootpath //rs/ledger_suite/icp/ledger:ledger-canister-wasm-notify-method)", + "LEDGER_ARCHIVE_NODE_CANISTER_WASM_PATH": "$(rootpath //rs/ledger_suite/icp/archive:ledger-archive-node-canister-wasm)", + "SNS_WASM_CANISTER_WASM_PATH": "$(rootpath //rs/nns/sns-wasm:sns-wasm-canister)", + "SNS_GOVERNANCE_CANISTER_WASM_PATH": "$(rootpath //rs/sns/governance:sns-governance-canister)", + "SNS_ROOT_CANISTER_WASM_PATH": "$(rootpath //rs/sns/root:sns-root-canister)", + "SNS_SWAP_CANISTER_WASM_PATH": "$(rootpath //rs/sns/swap:sns-swap-canister)", + "ROOT_CANISTER_WASM_PATH": "$(rootpath //rs/nns/handlers/root/impl:root-canister)", + "POCKET_IC_BIN": "$(rootpath //rs/pocket_ic_server:pocket-ic-server)", + "NNS_DAPP_WASM_PATH": "$(rootpath @nns_dapp_canister//file)", + "SNS_AGGREGATOR_WASM_PATH": "$(rootpath @sns_aggregator//file)", + "INTERNET_IDENTITY_WASM_PATH": "$(rootpath @ii_dev_canister//file)", +} + +rust_binary( + name = "cli", + testonly = True, + srcs = ["src/main.rs"], + env = DEV_ENV, + data = DEV_DATA, + deps = DEPENDENCIES + [ + ":sns_testing", + ], +) + +rust_library( + name = "sns_testing", + testonly = True, + srcs = glob(["src/**/*.rs"]), + crate_name = "ic_sns_testing", + deps = DEPENDENCIES, +) + +rust_test( + name = "sns_testing_ci", + srcs = ["tests/sns_testing_ci.rs"], + data = DEV_DATA, + env = DEV_ENV, + deps = DEPENDENCIES + [ + ":sns_testing", + ], +) diff --git a/rs/sns/testing/Cargo.toml b/rs/sns/testing/Cargo.toml new file mode 100644 index 00000000000..d125f61b784 --- /dev/null +++ b/rs/sns/testing/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ic-sns-testing" +version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +edition.workspace = true + +[[bin]] +name = "sns" +path = "src/main.rs" + +[lib] +path = "src/lib.rs" + +[dependencies] +candid = { workspace = true } +canister-test = { path = "../../rust_canisters/canister_test" } +clap = { workspace = true } +ic-nervous-system-integration-tests = { path = "../../nervous_system/integration_tests" } +ic-nns-constants = { path = "../../nns/constants" } +ic-nns-test-utils = { path = "../../nns/test_utils" } +pocket-ic = { path = "../../../packages/pocket-ic" } +reqwest = { workspace = true } +tokio = { workspace = true } + diff --git a/rs/sns/testing/README.md b/rs/sns/testing/README.md new file mode 100644 index 00000000000..cf3dc708654 --- /dev/null +++ b/rs/sns/testing/README.md @@ -0,0 +1,8 @@ +# SNS testing + +To run the scenario on the local PocketIC instance: +1) Launch PocketIC server: `bazel run //rs/pocket_ic_server:pocket-ic-server -- --ttl 6000 --port 8888` +2) Launch SNS testing scenario on it: `bazel run //rs/sns/testing:cli -- --server-url "http://127.0.0.1:8888"` + +Open local NNS dapp instance: http://qoctq-giaaa-aaaaa-aaaea-cai.localhost:8080/proposals/?u=qoctq-giaaa-aaaaa-aaaea-cai. +You should be able to see executed proposals to add SNS WASM to SNS-W canisters (since currently used NNS dapp is slightly outdated, make sure to clear topic filters). diff --git a/rs/sns/testing/src/lib.rs b/rs/sns/testing/src/lib.rs new file mode 100644 index 00000000000..67949d5605a --- /dev/null +++ b/rs/sns/testing/src/lib.rs @@ -0,0 +1 @@ +pub mod pocket_ic; diff --git a/rs/sns/testing/src/main.rs b/rs/sns/testing/src/main.rs new file mode 100644 index 00000000000..e7ee59cffe8 --- /dev/null +++ b/rs/sns/testing/src/main.rs @@ -0,0 +1,26 @@ +use clap::Parser; +use ic_sns_testing::pocket_ic::bootstrap_nns; +use pocket_ic::PocketIcBuilder; +use reqwest::Url; + +#[derive(Debug, Parser)] +struct SnsTestingOpts { + #[arg(long)] + server_url: Url, +} + +#[tokio::main] +async fn main() { + let opts = SnsTestingOpts::parse(); + let mut pocket_ic = PocketIcBuilder::new() + .with_server_url(opts.server_url) + .with_nns_subnet() + .with_sns_subnet() + .with_ii_subnet() + .with_application_subnet() + .build_async() + .await; + let endpoint = pocket_ic.make_live(Some(8080)).await; + println!("PocketIC endpoint: {}", endpoint); + bootstrap_nns(&pocket_ic).await; +} diff --git a/rs/sns/testing/src/pocket_ic.rs b/rs/sns/testing/src/pocket_ic.rs new file mode 100644 index 00000000000..b166b483293 --- /dev/null +++ b/rs/sns/testing/src/pocket_ic.rs @@ -0,0 +1,131 @@ +use candid::{CandidType, Encode}; +use canister_test::Wasm; +use ic_nervous_system_integration_tests::pocket_ic_helpers::{ + add_wasms_to_sns_wasm, install_canister_with_controllers, NnsInstaller, +}; +use ic_nns_constants::{ + CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID, IDENTITY_CANISTER_ID, LEDGER_CANISTER_ID, + LEDGER_INDEX_CANISTER_ID, NNS_UI_CANISTER_ID, ROOT_CANISTER_ID, SNS_AGGREGATOR_CANISTER_ID, + SNS_WASM_CANISTER_ID, +}; +use pocket_ic::nonblocking::PocketIc; + +pub async fn bootstrap_nns(pocket_ic: &PocketIc) { + // TODO @rvem: at some point in the future we might want to use + // non-default 'initial_balances' as well as 'neurons_fund_hotkeys' to provide + // tokens and neuron hotkeys for user-provided indentities. + let mut nns_installer = NnsInstaller::default(); + nns_installer.with_current_nns_canister_versions(); + nns_installer.with_cycles_minting_canister(); + nns_installer.with_index_canister(); + nns_installer.install(pocket_ic).await; + + install_frontend_nns_canisters(pocket_ic).await; + add_wasms_to_sns_wasm(pocket_ic, false).await.unwrap(); +} + +#[derive(CandidType)] +struct SnsAggregatorPayload { + pub update_interval_ms: u64, + pub fast_interval_ms: u64, +} + +#[derive(CandidType)] +struct NnsDappPayload { + args: Vec<(String, String)>, +} + +async fn install_frontend_nns_canisters(pocket_ic: &PocketIc) { + let features = &[]; + let sns_aggregator_wasm = + Wasm::from_location_specified_by_env_var("sns_aggregator", features).unwrap(); + let nns_dapp_wasm = Wasm::from_location_specified_by_env_var("nns_dapp", features).unwrap(); + let internet_identity_wasm = + Wasm::from_location_specified_by_env_var("internet_identity", features).unwrap(); + + // Refresh every second so that the NNS dapp is as up-to-date as possible + let sns_aggregator_payload = SnsAggregatorPayload { + update_interval_ms: 1000, + fast_interval_ms: 100, + }; + install_canister_with_controllers( + pocket_ic, + "sns_aggregator", + SNS_AGGREGATOR_CANISTER_ID, + Encode!(&sns_aggregator_payload).unwrap(), + sns_aggregator_wasm, + vec![ROOT_CANISTER_ID.get(), SNS_WASM_CANISTER_ID.get()], + ) + .await; + let internet_identity_payload: Option<()> = None; + install_canister_with_controllers( + pocket_ic, + "internet-identity", + IDENTITY_CANISTER_ID, + Encode!(&internet_identity_payload).unwrap(), + internet_identity_wasm, + vec![ROOT_CANISTER_ID.get()], + ) + .await; + // TODO @rvem: perhaps, we may start using configurable endpoint for the IC http interface + // which should be considered in NNS dapp configuration. + let endpoint = "localhost:8080"; + let nns_dapp_payload = NnsDappPayload { + args: vec![ + ("API_HOST".to_string(), format!("http://{}", endpoint)), + ( + "CYCLES_MINTING_CANISTER_ID".to_string(), + CYCLES_MINTING_CANISTER_ID.get().to_string(), + ), + ("DFX_NETWORK".to_string(), "local".to_string()), + ( + "FEATURE_FLAGS".to_string(), + "{\"ENABLE_CKBTC\":false,\"ENABLE_CKTESTBTC\":false}".to_string(), + ), + ("FETCH_ROOT_KEY".to_string(), "true".to_string()), + ( + "GOVERNANCE_CANISTER_ID".to_string(), + GOVERNANCE_CANISTER_ID.get().to_string(), + ), + ("HOST".to_string(), format!("http://{}", endpoint)), + ( + "IDENTITY_SERVICE_URL".to_string(), + format!("http://{}.{}", IDENTITY_CANISTER_ID.get(), endpoint), + ), + ( + "LEDGER_CANISTER_ID".to_string(), + LEDGER_CANISTER_ID.get().to_string(), + ), + ( + "OWN_CANISTER_ID".to_string(), + NNS_UI_CANISTER_ID.get().to_string(), + ), + ( + "ROBOTS".to_string(), + "".to_string(), + ), + ( + "SNS_AGGREGATOR_URL".to_string(), + format!("http://{}.{}", SNS_AGGREGATOR_CANISTER_ID.get(), endpoint), + ), + ("STATIC_HOST".to_string(), format!("http://{}", endpoint)), + ( + "WASM_CANISTER_ID".to_string(), + SNS_WASM_CANISTER_ID.get().to_string(), + ), + ( + "INDEX_CANISTER_ID".to_string(), + LEDGER_INDEX_CANISTER_ID.get().to_string(), + ), + ], + }; + install_canister_with_controllers( + pocket_ic, + "nns-dapp", + NNS_UI_CANISTER_ID, + Encode!(&nns_dapp_payload).unwrap(), + nns_dapp_wasm, + vec![ROOT_CANISTER_ID.get()], + ) + .await; +} diff --git a/rs/sns/testing/tests/sns_testing_ci.rs b/rs/sns/testing/tests/sns_testing_ci.rs new file mode 100644 index 00000000000..5e9a8dee23e --- /dev/null +++ b/rs/sns/testing/tests/sns_testing_ci.rs @@ -0,0 +1,14 @@ +use ic_sns_testing::pocket_ic::bootstrap_nns; +use pocket_ic::PocketIcBuilder; + +#[tokio::test] +async fn test_sns_testing_pocket_ic() { + let pocket_ic = PocketIcBuilder::new() + .with_nns_subnet() + .with_sns_subnet() + .with_ii_subnet() + .with_application_subnet() + .build_async() + .await; + bootstrap_nns(&pocket_ic).await; +} From 09d0c84c0fd8c48462428e307890c954e8ffaa3a Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Wed, 5 Feb 2025 11:00:07 +0100 Subject: [PATCH 04/12] Apply suggestions from code review Co-authored-by: Arshavir Ter-Gabrielyan --- rs/nns/test_utils/src/common.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/nns/test_utils/src/common.rs b/rs/nns/test_utils/src/common.rs index 01dd597bebf..926155ce7a1 100644 --- a/rs/nns/test_utils/src/common.rs +++ b/rs/nns/test_utils/src/common.rs @@ -407,8 +407,8 @@ pub fn build_mainnet_governance_wasm() -> Wasm { Project::cargo_bin_maybe_from_env("mainnet-governance-canister", &features) } -/// Build Wasm for Index canister for the ICP Ledger +/// Build mainnet Wasm for Index canister for the ICP Ledger pub fn build_mainnet_index_wasm() -> Wasm { let features = []; - Project::cargo_bin_maybe_from_env("mainnet-index-canister", &features) + Project::cargo_bin_maybe_from_env("mainnet-ic-icp-index-canister", &features) } From 6b4ffd299dd6e8143ba0bac9b8dc08208889ee17 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Tue, 28 Jan 2025 16:22:22 +0100 Subject: [PATCH 05/12] sns-testing: propose, adopt, and complete swap for the new SNS 1) Add a simple test canister that will be decentralized as a part of newly created SNS 2) Add helpers to install this canister and run it as the new SNS 3) Complete the SNS swap to finalize SNS adoption --- Cargo.lock | 6 +++ rs/sns/testing/BUILD.bazel | 21 +++++++- rs/sns/testing/Cargo.toml | 10 ++++ rs/sns/testing/README.md | 23 +++++++++ rs/sns/testing/canister/canister.rs | 43 +++++++++++++++++ rs/sns/testing/canister/test.did | 3 ++ rs/sns/testing/src/main.rs | 14 +++++- rs/sns/testing/src/pocket_ic.rs | 66 +++++++++++++++++++++++++- rs/sns/testing/tests/sns_testing_ci.rs | 28 ++++++++++- 9 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 rs/sns/testing/canister/canister.rs create mode 100644 rs/sns/testing/canister/test.did diff --git a/Cargo.lock b/Cargo.lock index d7095a98a97..3bbed137f54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12608,11 +12608,17 @@ dependencies = [ "candid", "canister-test", "clap 4.5.27", + "ic-base-types", + "ic-cdk 0.16.0", + "ic-nervous-system-agent", "ic-nervous-system-integration-tests", + "ic-nns-common", "ic-nns-constants", "ic-nns-test-utils", + "ic-sns-swap", "pocket-ic", "reqwest 0.12.12", + "serde", "tokio", ] diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel index 670e26f18ca..74c44d75906 100644 --- a/rs/sns/testing/BUILD.bazel +++ b/rs/sns/testing/BUILD.bazel @@ -1,13 +1,18 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load("//bazel:canisters.bzl", "rust_canister") package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ "//packages/pocket-ic", + "//rs/nervous_system/agent", "//rs/nervous_system/integration_tests:nervous_system_integration_tests", + "//rs/nns/common", "//rs/nns/test_utils", "//rs/nns/constants", + "//rs/sns/swap:swap", "//rs/rust_canisters/canister_test", + "//rs/types/base_types", "@crate_index//:candid", "@crate_index//:clap", "@crate_index//:reqwest", @@ -35,6 +40,7 @@ DEV_DATA = [ "//rs/sns/governance:sns-governance-canister", "//rs/sns/root:sns-root-canister", "//rs/sns/swap:sns-swap-canister", + ":sns_testing_canister", "@nns_dapp_canister//file", "@sns_aggregator//file", "@ii_dev_canister//file", @@ -60,14 +66,15 @@ DEV_ENV = { "NNS_DAPP_WASM_PATH": "$(rootpath @nns_dapp_canister//file)", "SNS_AGGREGATOR_WASM_PATH": "$(rootpath @sns_aggregator//file)", "INTERNET_IDENTITY_WASM_PATH": "$(rootpath @ii_dev_canister//file)", + "SNS_TESTING_CANISTER_WASM_PATH": "$(rootpath :sns_testing_canister)", } rust_binary( name = "cli", testonly = True, srcs = ["src/main.rs"], - env = DEV_ENV, data = DEV_DATA, + env = DEV_ENV, deps = DEPENDENCIES + [ ":sns_testing", ], @@ -90,3 +97,15 @@ rust_test( ":sns_testing", ], ) + +rust_canister( + name = "sns_testing_canister", + testonly = True, + srcs = ["canister/canister.rs"], + service_file = ":canister/test.did", + deps = [ + "@crate_index//:candid", + "@crate_index//:ic-cdk", + "@crate_index//:serde", + ], +) diff --git a/rs/sns/testing/Cargo.toml b/rs/sns/testing/Cargo.toml index d125f61b784..71606c55c89 100644 --- a/rs/sns/testing/Cargo.toml +++ b/rs/sns/testing/Cargo.toml @@ -10,6 +10,10 @@ edition.workspace = true name = "sns" path = "src/main.rs" +[[bin]] +name = "sns-testing-canister" +path = "canister/canister.rs" + [lib] path = "src/lib.rs" @@ -17,10 +21,16 @@ path = "src/lib.rs" candid = { workspace = true } canister-test = { path = "../../rust_canisters/canister_test" } clap = { workspace = true } +ic-base-types = { path = "../../types/base_types" } +ic-cdk = { workspace = true } +ic-nervous-system-agent = { path = "../../nervous_system/agent" } ic-nervous-system-integration-tests = { path = "../../nervous_system/integration_tests" } ic-nns-constants = { path = "../../nns/constants" } +ic-nns-common = { path = "../../nns/common" } ic-nns-test-utils = { path = "../../nns/test_utils" } +ic-sns-swap = { path = "../swap" } pocket-ic = { path = "../../../packages/pocket-ic" } reqwest = { workspace = true } +serde = { workspace = true } tokio = { workspace = true } diff --git a/rs/sns/testing/README.md b/rs/sns/testing/README.md index cf3dc708654..26ef2614a78 100644 --- a/rs/sns/testing/README.md +++ b/rs/sns/testing/README.md @@ -6,3 +6,26 @@ To run the scenario on the local PocketIC instance: Open local NNS dapp instance: http://qoctq-giaaa-aaaaa-aaaea-cai.localhost:8080/proposals/?u=qoctq-giaaa-aaaaa-aaaea-cai. You should be able to see executed proposals to add SNS WASM to SNS-W canisters (since currently used NNS dapp is slightly outdated, make sure to clear topic filters). + +The scenario installs [test canister](./canister/canister.rs) and creates new SNS with it. +You should be able to see in NNS dapp web UI that the proposal to create a new SNS was adopted. + +To interact with the network created by `sns-testing` CLI, you should add the following network config to +`~/.config/dfx/networks.json`: +``` +{ + "sns-testing": { + "bind": "127.0.0.1:8080" + } +} +``` + +Now you can call the testing canister by its id (note that the actual id may vary, make sure to check logs, or NNS proposal info in NNS dapp): +``` +dfx canister --network pocket-ic-system call mxqf3-4h777-77775-qaaaa-cai greet "IC" +``` + +To get the latest NNS proposal info: +``` +./dfx canister --network sns-testing call rrkah-fqaaa-aaaaa-aaaaq-cai list_proposals '(record { include_reward_status = vec {}; include_status = vec {}; exclude_topic = vec {}; limit = 1 })' +``` diff --git a/rs/sns/testing/canister/canister.rs b/rs/sns/testing/canister/canister.rs new file mode 100644 index 00000000000..bebda893f03 --- /dev/null +++ b/rs/sns/testing/canister/canister.rs @@ -0,0 +1,43 @@ +use candid::CandidType; +use serde::Deserialize; +use std::cell::RefCell; + +thread_local! { + static STR: RefCell = RefCell::new("Hoi".to_string()); +} + +#[derive(CandidType, Deserialize)] +pub struct InitArgs { + pub greeting: Option, +} + +fn init_impl(x: Option) { + match x { + None => (), + Some(x) => { + match x.greeting { + None => (), + Some(g) => { + STR.with(|s| *s.borrow_mut() = g); + } + }; + } + } +} + +#[ic_cdk::init] +fn init(x: Option) { + init_impl(x); +} + +#[ic_cdk::post_upgrade] +fn post_upgrade(x: Option) { + init_impl(x); +} + +#[ic_cdk::query] +fn greet(name: String) -> String { + format!("{}, {}!", STR.with(|s| (*s.borrow_mut()).clone()), name) +} + +fn main() {} diff --git a/rs/sns/testing/canister/test.did b/rs/sns/testing/canister/test.did new file mode 100644 index 00000000000..e1fe8ae13be --- /dev/null +++ b/rs/sns/testing/canister/test.did @@ -0,0 +1,3 @@ +service : { + "greet": (text) -> (text); +} diff --git a/rs/sns/testing/src/main.rs b/rs/sns/testing/src/main.rs index e7ee59cffe8..bf9100bc1fc 100644 --- a/rs/sns/testing/src/main.rs +++ b/rs/sns/testing/src/main.rs @@ -1,5 +1,7 @@ use clap::Parser; -use ic_sns_testing::pocket_ic::bootstrap_nns; +use ic_sns_testing::pocket_ic::{ + bootstrap_nns, create_sns, install_test_canister, TestCanisterInitArgs, +}; use pocket_ic::PocketIcBuilder; use reqwest::Url; @@ -23,4 +25,14 @@ async fn main() { let endpoint = pocket_ic.make_live(Some(8080)).await; println!("PocketIC endpoint: {}", endpoint); bootstrap_nns(&pocket_ic).await; + let greeting = "Hello there".to_string(); + let test_canister_id = install_test_canister( + &pocket_ic, + TestCanisterInitArgs { + greeting: Some(greeting), + }, + ) + .await; + println!("Test canister ID: {}", test_canister_id); + let (_, _nns_proposal_id) = create_sns(&pocket_ic, vec![test_canister_id]).await; } diff --git a/rs/sns/testing/src/pocket_ic.rs b/rs/sns/testing/src/pocket_ic.rs index b166b483293..e8d398cac9f 100644 --- a/rs/sns/testing/src/pocket_ic.rs +++ b/rs/sns/testing/src/pocket_ic.rs @@ -1,13 +1,23 @@ use candid::{CandidType, Encode}; use canister_test::Wasm; -use ic_nervous_system_integration_tests::pocket_ic_helpers::{ - add_wasms_to_sns_wasm, install_canister_with_controllers, NnsInstaller, +use ic_base_types::CanisterId; +use ic_nervous_system_agent::sns::Sns; +use ic_nervous_system_integration_tests::{ + create_service_nervous_system_builder::CreateServiceNervousSystemBuilder, + pocket_ic_helpers::{ + add_wasms_to_sns_wasm, install_canister_on_subnet, install_canister_with_controllers, + nns::governance::propose_to_deploy_sns_and_wait, + sns::swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, + NnsInstaller, + }, }; +use ic_nns_common::pb::v1::ProposalId; use ic_nns_constants::{ CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID, IDENTITY_CANISTER_ID, LEDGER_CANISTER_ID, LEDGER_INDEX_CANISTER_ID, NNS_UI_CANISTER_ID, ROOT_CANISTER_ID, SNS_AGGREGATOR_CANISTER_ID, SNS_WASM_CANISTER_ID, }; +use ic_sns_swap::pb::v1::Lifecycle; use pocket_ic::nonblocking::PocketIc; pub async fn bootstrap_nns(pocket_ic: &PocketIc) { @@ -129,3 +139,55 @@ async fn install_frontend_nns_canisters(pocket_ic: &PocketIc) { ) .await; } + +// TODO @rvem: I don't like the fact that this struct definition is copy-pasted from 'canister/canister.rs'. +// We should extract it into a separate crate and reuse in both canister and this crates. +#[derive(CandidType)] +pub struct TestCanisterInitArgs { + pub greeting: Option, +} + +pub async fn install_test_canister(pocket_ic: &PocketIc, args: TestCanisterInitArgs) -> CanisterId { + let topology = pocket_ic.topology().await; + let application_subnet_ids = topology.get_app_subnets(); + let application_subnet_id = application_subnet_ids + .first() + .expect("No Application subnet found"); + let features = &[]; + let test_canister_wasm = + Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); + install_canister_on_subnet( + pocket_ic, + *application_subnet_id, + Encode!(&args).unwrap(), + Some(test_canister_wasm), + vec![ROOT_CANISTER_ID.get()], + ) + .await +} + +pub async fn create_sns( + pocket_ic: &PocketIc, + dapp_canister_ids: Vec, +) -> (Sns, ProposalId) { + let sns_proposal_id = "1"; + let create_service_nervous_system = CreateServiceNervousSystemBuilder::default() + .neurons_fund_participation(true) + .with_dapp_canisters(dapp_canister_ids) + .build(); + let swap_parameters = create_service_nervous_system + .swap_parameters + .clone() + .unwrap(); + let (sns, proposal_id) = + propose_to_deploy_sns_and_wait(pocket_ic, create_service_nervous_system, sns_proposal_id) + .await; + await_swap_lifecycle(pocket_ic, sns.swap.canister_id, Lifecycle::Open) + .await + .expect("Expecting the swap to be open after creation"); + smoke_test_participate_and_finalize(pocket_ic, sns.swap.canister_id, swap_parameters).await; + await_swap_lifecycle(&pocket_ic, sns.swap.canister_id, Lifecycle::Committed) + .await + .expect("Expecting the swap to be commited after creation and swap completion"); + (sns, proposal_id) +} diff --git a/rs/sns/testing/tests/sns_testing_ci.rs b/rs/sns/testing/tests/sns_testing_ci.rs index 5e9a8dee23e..3156ac536b0 100644 --- a/rs/sns/testing/tests/sns_testing_ci.rs +++ b/rs/sns/testing/tests/sns_testing_ci.rs @@ -1,4 +1,7 @@ -use ic_sns_testing::pocket_ic::bootstrap_nns; +use candid::{Decode, Encode, Principal}; +use ic_sns_testing::pocket_ic::{ + bootstrap_nns, create_sns, install_test_canister, TestCanisterInitArgs, +}; use pocket_ic::PocketIcBuilder; #[tokio::test] @@ -11,4 +14,27 @@ async fn test_sns_testing_pocket_ic() { .build_async() .await; bootstrap_nns(&pocket_ic).await; + let greeting = "Hello there".to_string(); + let test_canister_id = install_test_canister( + &pocket_ic, + TestCanisterInitArgs { + greeting: Some(greeting.clone()), + }, + ) + .await; + let test_call_arg = "General Kenobi".to_string(); + let test_canister_response = pocket_ic + .query_call( + test_canister_id.into(), + Principal::anonymous(), + "greet", + Encode!(&test_call_arg).unwrap(), + ) + .await + .expect("Call to a test canister failed"); + assert_eq!( + Decode!(&test_canister_response, String).expect("Failed to decode test canister response"), + format!("{}, {}!", greeting, test_call_arg), + ); + let (sns, _nns_proposal_id) = create_sns(&pocket_ic, vec![test_canister_id]).await; } From 648b88173ad4f38d4be6ba8e5be27e4654647aa6 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 30 Jan 2025 10:18:37 +0100 Subject: [PATCH 06/12] sns-testing: upgrade the test canister Add helpers to upgrade the test canister. Currently, the helper will reuse the same WASM module, but it's possible to provide the new state value as a 'post_upgrade' hook argument. Test scenario as well as CLI now perform canister upgrade. The former also allows to see the SNS voting procedure in the NNS dapp web UI. --- Cargo.lock | 2 + rs/sns/testing/BUILD.bazel | 3 ++ rs/sns/testing/Cargo.toml | 2 + rs/sns/testing/src/main.rs | 18 +++++++- rs/sns/testing/src/pocket_ic.rs | 57 ++++++++++++++++++++++++-- rs/sns/testing/tests/sns_testing_ci.rs | 28 ++++++++++++- 6 files changed, 102 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bbed137f54..e079a98346f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12610,11 +12610,13 @@ dependencies = [ "clap 4.5.27", "ic-base-types", "ic-cdk 0.16.0", + "ic-management-canister-types", "ic-nervous-system-agent", "ic-nervous-system-integration-tests", "ic-nns-common", "ic-nns-constants", "ic-nns-test-utils", + "ic-sns-governance-api", "ic-sns-swap", "pocket-ic", "reqwest 0.12.12", diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel index 74c44d75906..59c70246e87 100644 --- a/rs/sns/testing/BUILD.bazel +++ b/rs/sns/testing/BUILD.bazel @@ -10,9 +10,11 @@ DEPENDENCIES = [ "//rs/nns/common", "//rs/nns/test_utils", "//rs/nns/constants", + "//rs/sns/governance/api", "//rs/sns/swap:swap", "//rs/rust_canisters/canister_test", "//rs/types/base_types", + "//rs/types/management_canister_types", "@crate_index//:candid", "@crate_index//:clap", "@crate_index//:reqwest", @@ -40,6 +42,7 @@ DEV_DATA = [ "//rs/sns/governance:sns-governance-canister", "//rs/sns/root:sns-root-canister", "//rs/sns/swap:sns-swap-canister", + "//rs/types/management_canister_types", ":sns_testing_canister", "@nns_dapp_canister//file", "@sns_aggregator//file", diff --git a/rs/sns/testing/Cargo.toml b/rs/sns/testing/Cargo.toml index 71606c55c89..46809f0eb57 100644 --- a/rs/sns/testing/Cargo.toml +++ b/rs/sns/testing/Cargo.toml @@ -23,11 +23,13 @@ canister-test = { path = "../../rust_canisters/canister_test" } clap = { workspace = true } ic-base-types = { path = "../../types/base_types" } ic-cdk = { workspace = true } +ic-management-canister-types = { path = "../../types/management_canister_types" } ic-nervous-system-agent = { path = "../../nervous_system/agent" } ic-nervous-system-integration-tests = { path = "../../nervous_system/integration_tests" } ic-nns-constants = { path = "../../nns/constants" } ic-nns-common = { path = "../../nns/common" } ic-nns-test-utils = { path = "../../nns/test_utils" } +ic-sns-governance-api = { path = "../../sns/governance/api" } ic-sns-swap = { path = "../swap" } pocket-ic = { path = "../../../packages/pocket-ic" } reqwest = { workspace = true } diff --git a/rs/sns/testing/src/main.rs b/rs/sns/testing/src/main.rs index bf9100bc1fc..ac91e4be5ed 100644 --- a/rs/sns/testing/src/main.rs +++ b/rs/sns/testing/src/main.rs @@ -1,6 +1,7 @@ use clap::Parser; use ic_sns_testing::pocket_ic::{ - bootstrap_nns, create_sns, install_test_canister, TestCanisterInitArgs, + bootstrap_nns, create_sns, install_test_canister, upgrade_sns_controlled_test_canister, + TestCanisterInitArgs, }; use pocket_ic::PocketIcBuilder; use reqwest::Url; @@ -34,5 +35,18 @@ async fn main() { ) .await; println!("Test canister ID: {}", test_canister_id); - let (_, _nns_proposal_id) = create_sns(&pocket_ic, vec![test_canister_id]).await; + println!("Creating SNS..."); + let (sns, _nns_proposal_id) = create_sns(&pocket_ic, vec![test_canister_id]).await; + println!("SNS created"); + println!("Upgrading SNS-controlled test canister..."); + upgrade_sns_controlled_test_canister( + &pocket_ic, + sns, + test_canister_id, + TestCanisterInitArgs { + greeting: Some("Hi".to_string()), + }, + ) + .await; + println!("Test canister upgraded"); } diff --git a/rs/sns/testing/src/pocket_ic.rs b/rs/sns/testing/src/pocket_ic.rs index e8d398cac9f..be7a83b30cb 100644 --- a/rs/sns/testing/src/pocket_ic.rs +++ b/rs/sns/testing/src/pocket_ic.rs @@ -1,13 +1,21 @@ use candid::{CandidType, Encode}; use canister_test::Wasm; use ic_base_types::CanisterId; +use ic_management_canister_types::CanisterInstallMode; use ic_nervous_system_agent::sns::Sns; use ic_nervous_system_integration_tests::{ create_service_nervous_system_builder::CreateServiceNervousSystemBuilder, + pocket_ic_helpers::nns::governance::propose_to_deploy_sns_and_wait, + pocket_ic_helpers::sns::{ + governance::{ + propose_to_upgrade_sns_controlled_canister_and_wait, + EXPECTED_UPGRADE_DURATION_MAX_SECONDS, + }, + swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, + }, pocket_ic_helpers::{ - add_wasms_to_sns_wasm, install_canister_on_subnet, install_canister_with_controllers, - nns::governance::propose_to_deploy_sns_and_wait, - sns::swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, + add_wasms_to_sns_wasm, await_with_timeout, + install_canister_on_subnet, install_canister_with_controllers, NnsInstaller, }, }; @@ -17,8 +25,9 @@ use ic_nns_constants::{ LEDGER_INDEX_CANISTER_ID, NNS_UI_CANISTER_ID, ROOT_CANISTER_ID, SNS_AGGREGATOR_CANISTER_ID, SNS_WASM_CANISTER_ID, }; +use ic_sns_governance_api::pb::v1::UpgradeSnsControlledCanister; use ic_sns_swap::pb::v1::Lifecycle; -use pocket_ic::nonblocking::PocketIc; +use pocket_ic::{management_canister::CanisterStatusResultStatus, nonblocking::PocketIc}; pub async fn bootstrap_nns(pocket_ic: &PocketIc) { // TODO @rvem: at some point in the future we might want to use @@ -191,3 +200,43 @@ pub async fn create_sns( .expect("Expecting the swap to be commited after creation and swap completion"); (sns, proposal_id) } + +pub async fn upgrade_sns_controlled_test_canister( + pocket_ic: &PocketIc, + sns: Sns, + canister_id: CanisterId, + upgrade_arg: TestCanisterInitArgs, +) { + // For now, we're using the same wasm module, but different init arguments used in 'post_upgrade' hook. + let features = &[]; + let test_canister_wasm = + Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); + propose_to_upgrade_sns_controlled_canister_and_wait( + pocket_ic, + sns.governance.canister_id, + UpgradeSnsControlledCanister { + canister_id: Some(canister_id.get()), + new_canister_wasm: test_canister_wasm.bytes(), + canister_upgrade_arg: Some(Encode!(&upgrade_arg).unwrap()), + mode: Some(CanisterInstallMode::Upgrade as i32), + chunked_canister_wasm: None, + }, + ) + .await; + // Wait for the canister to become available + await_with_timeout( + pocket_ic, + 0..EXPECTED_UPGRADE_DURATION_MAX_SECONDS, + |pocket_ic| async { + let canister_status = pocket_ic + .canister_status(canister_id.into(), Some(sns.root.canister_id.into())) + .await; + canister_status + .expect("Canister status is unavailable") + .status as u32 + }, + &(CanisterStatusResultStatus::Running as u32), + ) + .await + .expect("Test canister failed to get into the 'Running' state after upgrade"); +} diff --git a/rs/sns/testing/tests/sns_testing_ci.rs b/rs/sns/testing/tests/sns_testing_ci.rs index 3156ac536b0..0601b6fdf36 100644 --- a/rs/sns/testing/tests/sns_testing_ci.rs +++ b/rs/sns/testing/tests/sns_testing_ci.rs @@ -1,6 +1,7 @@ use candid::{Decode, Encode, Principal}; use ic_sns_testing::pocket_ic::{ - bootstrap_nns, create_sns, install_test_canister, TestCanisterInitArgs, + bootstrap_nns, create_sns, install_test_canister, upgrade_sns_controlled_test_canister, + TestCanisterInitArgs, }; use pocket_ic::PocketIcBuilder; @@ -34,7 +35,30 @@ async fn test_sns_testing_pocket_ic() { .expect("Call to a test canister failed"); assert_eq!( Decode!(&test_canister_response, String).expect("Failed to decode test canister response"), - format!("{}, {}!", greeting, test_call_arg), + format!("{}, {}!", greeting, test_call_arg.clone()), ); let (sns, _nns_proposal_id) = create_sns(&pocket_ic, vec![test_canister_id]).await; + let new_greeting = "Hi".to_string(); + upgrade_sns_controlled_test_canister( + &pocket_ic, + sns, + test_canister_id, + TestCanisterInitArgs { + greeting: Some(new_greeting.clone()), + }, + ) + .await; + let test_canister_response = pocket_ic + .query_call( + test_canister_id.into(), + Principal::anonymous(), + "greet", + Encode!(&test_call_arg).unwrap(), + ) + .await + .expect("Call to a test canister failed"); + assert_eq!( + Decode!(&test_canister_response, String).expect("Failed to decode test canister response"), + format!("{}, {}!", new_greeting, test_call_arg), + ); } From 505b48b6cd7ddc8166bc6462d8400d2a6ec3123e Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Mon, 3 Feb 2025 10:46:21 +0100 Subject: [PATCH 07/12] nervous_system::integration_tests: live mode pocket-ic helpers Problem: Currently, helpers from 'pocket_ic_helpers' module use 'advance_time' pocket-ic method to artificially advance time in order to wait for certain events to happen. However, using 'advance_time' in live mode breaks agent certificates checking since pocket-ic time becomes larger than the real time. Solution: 1) When pocket-ic is running in live mode, run 'std::thread::sleep()' instead of 'advance_time()'. 2) Avoid waiting two days for SNS swap to start in live mode. 3) Allow to use the test version of NNS Governance canister which is capable of starting SNS swap immediately after SNS proposal adoption. --- .../src/pocket_ic_helpers.rs | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs index bfca7165220..d4288026ccf 100644 --- a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs @@ -30,7 +30,8 @@ use ic_nns_test_utils::{ build_lifeline_wasm, build_mainnet_cmc_wasm, build_mainnet_governance_wasm, build_mainnet_index_wasm, build_mainnet_ledger_wasm, build_mainnet_lifeline_wasm, build_mainnet_registry_wasm, build_mainnet_root_wasm, build_mainnet_sns_wasms_wasm, - build_registry_wasm, build_root_wasm, build_sns_wasms_wasm, NnsInitPayloadsBuilder, + build_registry_wasm, build_root_wasm, build_sns_wasms_wasm, build_test_governance_wasm, + NnsInitPayloadsBuilder, }, sns_wasm::{ build_archive_sns_wasm, build_governance_sns_wasm, build_index_ng_sns_wasm, @@ -330,6 +331,7 @@ pub struct NnsInstaller { with_cycles_minting_canister: bool, with_cycles_ledger: bool, with_index_canister: bool, + with_test_governance_canister: bool, } impl NnsInstaller { @@ -395,6 +397,11 @@ impl NnsInstaller { self } + pub fn with_test_governance_canister(&mut self) -> &mut Self { + self.with_test_governance_canister = true; + self + } + /// Installs the NNS canister suite. /// /// Ensures that there is a whale neuron with `TEST_NEURON_1_ID`. @@ -407,6 +414,10 @@ impl NnsInstaller { .mainnet_nns_canister_versions .expect("Please explicitly request either mainnet or tip-of-the-branch NNS version."); + assert!(!(with_mainnet_canister_versions && self.with_test_governance_canister), + "The test version of the governance canister cannot be used with mainnet versions of the NNS canisters" + ); + let topology = pocket_ic.topology().await; let sns_subnet_id = topology.get_sns().expect("No SNS subnet found"); @@ -452,7 +463,11 @@ impl NnsInstaller { ) } else { ( - build_governance_wasm(), + if self.with_test_governance_canister { + build_test_governance_wasm() + } else { + build_governance_wasm() + }, build_ledger_wasm(), build_root_wasm(), build_lifeline_wasm(), @@ -713,10 +728,12 @@ pub mod cycles_ledger { /// Arguments /// 1. `with_mainnet_nns_canister_versions` is a flag indicating whether the mainnet /// (or, therwise, tip-of-this-branch) WASM versions should be installed. -/// 2. `initial_balances` is a `Vec` of `(test_user_icp_ledger_account, +/// 2. `with_test_nns_governance_canister` is a flag indicating whether the test version of +/// the governance canister should be installed. Mutually exclusive with `with_mainnet_nns_canister_versions`. +/// 3. `initial_balances` is a `Vec` of `(test_user_icp_ledger_account, /// test_user_icp_ledger_initial_balance)` pairs, representing some initial ICP balances. /// 3. `custom_registry_mutations` are custom mutations for the inital Registry. These -/// should mutations should comply with Registry invariants, otherwise this function will fail. +/// mutations should comply with Registry invariants, otherwise this function will fail. /// 4. `neurons_fund_hotkeys` - hotkeys of the 1st NNS (Neurons' Fund-participating) neuron. /// /// Returns @@ -1081,19 +1098,14 @@ where assert!(expected_event_interval_seconds.start < expected_event_interval_seconds.end, "expected_event_interval_seconds.start must be less than expected_event_interval_seconds.end"); let timeout_seconds = expected_event_interval_seconds.end - expected_event_interval_seconds.start; - pocket_ic - .advance_time(Duration::from_secs(expected_event_interval_seconds.start)) - .await; + progress_pocket_ic(pocket_ic, expected_event_interval_seconds.start).await; let mut counter = 0; let num_ticks = timeout_seconds.min(500); let seconds_per_tick = (timeout_seconds as f64 / num_ticks as f64).ceil() as u64; loop { - pocket_ic - .advance_time(Duration::from_secs(seconds_per_tick)) - .await; - pocket_ic.tick().await; + progress_pocket_ic(pocket_ic, seconds_per_tick).await; let observed = observe(pocket_ic).await; if observed == *expected { @@ -1109,6 +1121,17 @@ where } } +// Using 'advance_time' in live mode breaks certificate checking, so we have to wait +// for the time to pass naturally. +async fn progress_pocket_ic(pocket_ic: &PocketIc, seconds: u64) { + if pocket_ic.url().is_some() { + std::thread::sleep(Duration::from_secs(seconds)); + } else { + pocket_ic.tick().await; + pocket_ic.advance_time(Duration::from_secs(seconds)).await; + } +} + pub mod nns { use super::*; pub mod governance { @@ -1215,11 +1238,10 @@ pub mod nns { pocket_ic: &PocketIc, proposal_id: u64, ) -> Result { - // We create some blocks until the proposal has finished executing (`pocket_ic.tick()`). + // We progress the blockchain until the proposal has finished executing. let mut last_proposal_info = None; for _attempt_count in 1..=100 { - pocket_ic.tick().await; - pocket_ic.advance_time(Duration::from_secs(1)).await; + progress_pocket_ic(pocket_ic, 1).await; let proposal_info_result = nns_get_proposal_info(pocket_ic, proposal_id, PrincipalId::new_anonymous()) .await; @@ -1688,8 +1710,7 @@ pub mod sns { .await; for _ in 0..20 { - pocket_ic.advance_time(Duration::from_secs(10)).await; - pocket_ic.tick().await; + progress_pocket_ic(pocket_ic, 10).await; } let post_upgrade_version = sns.governance.version(pocket_ic).await; @@ -1859,11 +1880,10 @@ pub mod sns { canister_id: PrincipalId, proposal_id: sns_pb::ProposalId, ) -> Result { - // We create some blocks until the proposal has finished executing (`pocket_ic.tick()`). + // We progress the blockchain until the proposal has finished executing. let mut last_proposal_data = None; for _attempt_count in 1..=50 { - pocket_ic.tick().await; - pocket_ic.advance_time(Duration::from_secs(1)).await; + progress_pocket_ic(pocket_ic, 1).await; let proposal_result = get_proposal( pocket_ic, canister_id, @@ -2660,7 +2680,7 @@ pub mod sns { } } - // Helper function that calls tick on env until either the index canister has synced all + // Helper function that progress the blockchain until either the index canister has synced all // the blocks up to the last one in the ledger or enough attempts passed and therefore it fails. pub async fn wait_until_ledger_and_index_sync_is_completed( pocket_ic: &PocketIc, @@ -2671,8 +2691,7 @@ pub mod sns { let mut num_blocks_synced = u64::MAX; let mut chain_length = u64::MAX; for _i in 0..MAX_ATTEMPTS { - pocket_ic.tick().await; - pocket_ic.advance_time(Duration::from_secs(1)).await; + progress_pocket_ic(pocket_ic, 1).await; num_blocks_synced = index_ng::status(pocket_ic, index_canister_id) .await .num_blocks_synced @@ -2943,13 +2962,17 @@ pub mod sns { expected_lifecycle: Lifecycle, ) -> Result<(), String> { // The swap opens in up to 48 after the proposal for creating this SNS was executed. - pocket_ic - .advance_time(Duration::from_secs(48 * 60 * 60)) - .await; + // Waiting for 48 hours in live mode is not viable, but the live mode is supposed + // to use NNS governance canister with test feature to ensure that swap can be started + // immediately. + if pocket_ic.url().is_none() { + pocket_ic + .advance_time(Duration::from_secs(48 * 60 * 60)) + .await; + } let mut last_lifecycle = None; for _attempt_count in 1..=100 { - pocket_ic.tick().await; - pocket_ic.advance_time(Duration::from_secs(1)).await; + progress_pocket_ic(pocket_ic, 1).await; let response = get_lifecycle(pocket_ic, swap_canister_id).await; let lifecycle = Lifecycle::try_from(response.lifecycle.unwrap()).unwrap(); if lifecycle == expected_lifecycle { @@ -3113,8 +3136,7 @@ pub mod sns { ) -> Result { let mut last_auto_finalization_status = None; for _attempt_count in 1..=1000 { - pocket_ic.tick().await; - pocket_ic.advance_time(Duration::from_secs(1)).await; + progress_pocket_ic(pocket_ic, 1).await; let auto_finalization_status = get_auto_finalization_status(pocket_ic, swap_canister_id).await; match status { From f0c4bedcb0058a6f58d25b12a74b97c22a35c7eb Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Mon, 3 Feb 2025 10:47:31 +0100 Subject: [PATCH 08/12] sns-testing: use NNS Governance test version Problem: We want to be able to run pocket-ic in live mode. However, this is not viable with non-test version of NNS Governance canister because it postpones the SNS swap start. Solution: Use test version of NNS Governance canister. Ensure that swap start_time is 'None' to start the swap immediately after SNS creation proposal adoption. --- rs/sns/testing/BUILD.bazel | 2 ++ rs/sns/testing/README.md | 6 +++++- rs/sns/testing/src/pocket_ic.rs | 10 +++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel index 59c70246e87..feade096c32 100644 --- a/rs/sns/testing/BUILD.bazel +++ b/rs/sns/testing/BUILD.bazel @@ -35,6 +35,7 @@ DEV_DATA = [ "//rs/ledger_suite/icrc1/ledger:ledger_canister", "//rs/nns/cmc:cycles-minting-canister", "//rs/nns/governance:governance-canister", + "//rs/nns/governance:governance-canister-test", "//rs/nns/handlers/root/impl:root-canister", "//rs/nns/sns-wasm:sns-wasm-canister", "//rs/pocket_ic_server:pocket-ic-server", @@ -52,6 +53,7 @@ DEV_DATA = [ DEV_ENV = { "CYCLES_MINTING_CANISTER_WASM_PATH": "$(rootpath //rs/nns/cmc:cycles-minting-canister)", "GOVERNANCE_CANISTER_WASM_PATH": "$(rootpath //rs/nns/governance:governance-canister)", + "GOVERNANCE_CANISTER_TEST_WASM_PATH": "$(rootpath //rs/nns/governance:governance-canister-test)", "REGISTRY_CANISTER_WASM_PATH": "$(rootpath //rs/registry/canister:registry-canister)", "IC_ICRC1_ARCHIVE_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/archive:archive_canister)", "IC_ICRC1_INDEX_NG_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/index-ng:index_ng_canister)", diff --git a/rs/sns/testing/README.md b/rs/sns/testing/README.md index 26ef2614a78..88acb84e644 100644 --- a/rs/sns/testing/README.md +++ b/rs/sns/testing/README.md @@ -8,7 +8,11 @@ Open local NNS dapp instance: http://qoctq-giaaa-aaaaa-aaaea-cai.localhost:8080/ You should be able to see executed proposals to add SNS WASM to SNS-W canisters (since currently used NNS dapp is slightly outdated, make sure to clear topic filters). The scenario installs [test canister](./canister/canister.rs) and creates new SNS with it. -You should be able to see in NNS dapp web UI that the proposal to create a new SNS was adopted. +Once the proposal is adopted, the scenario initiates the SNS swap and closes it by providing sufficient amount of ICP. +Once swap is completed, the test canister is upgraded via SNS voting. + +NNS dapp should show the NNS proposal to create the new SNS as well as proposal in the newly created SNS to upgrade +the controlled canister. To interact with the network created by `sns-testing` CLI, you should add the following network config to `~/.config/dfx/networks.json`: diff --git a/rs/sns/testing/src/pocket_ic.rs b/rs/sns/testing/src/pocket_ic.rs index be7a83b30cb..c4021880985 100644 --- a/rs/sns/testing/src/pocket_ic.rs +++ b/rs/sns/testing/src/pocket_ic.rs @@ -14,9 +14,8 @@ use ic_nervous_system_integration_tests::{ swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, }, pocket_ic_helpers::{ - add_wasms_to_sns_wasm, await_with_timeout, - install_canister_on_subnet, install_canister_with_controllers, - NnsInstaller, + add_wasms_to_sns_wasm, await_with_timeout, install_canister_on_subnet, + install_canister_with_controllers, NnsInstaller, }, }; use ic_nns_common::pb::v1::ProposalId; @@ -35,6 +34,7 @@ pub async fn bootstrap_nns(pocket_ic: &PocketIc) { // tokens and neuron hotkeys for user-provided indentities. let mut nns_installer = NnsInstaller::default(); nns_installer.with_current_nns_canister_versions(); + nns_installer.with_test_governance_canister(); nns_installer.with_cycles_minting_canister(); nns_installer.with_index_canister(); nns_installer.install(pocket_ic).await; @@ -188,6 +188,10 @@ pub async fn create_sns( .swap_parameters .clone() .unwrap(); + assert_eq!( + swap_parameters.start_time, None, + "Expecting the swap start time to be None to start the swap immediately" + ); let (sns, proposal_id) = propose_to_deploy_sns_and_wait(pocket_ic, create_service_nervous_system, sns_proposal_id) .await; From 69992a0aa738b27ceb3fd22bd603d8625cfb3a34 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Wed, 5 Feb 2025 15:27:17 +0100 Subject: [PATCH 09/12] sns-testing: check that NNS canisters exist before deploying Check that PocketIC network has all required subnets and install NNS-related canisters (core and frontend) only when they're missing. At this point, we do not check that canisters were correctly initialized, so this check might not be sufficient to run the scenario on arbitrary network. --- Cargo.lock | 7 +- rs/nervous_system/agent/src/pocketic_impl.rs | 6 +- rs/sns/testing/BUILD.bazel | 1 + rs/sns/testing/Cargo.toml | 3 +- rs/sns/testing/src/lib.rs | 3 +- rs/sns/testing/src/main.rs | 6 +- rs/sns/testing/src/nns_dapp.rs | 192 +++++++++++++++ rs/sns/testing/src/pocket_ic.rs | 246 ------------------- rs/sns/testing/src/sns.rs | 118 +++++++++ rs/sns/testing/tests/sns_testing_ci.rs | 6 +- 10 files changed, 330 insertions(+), 258 deletions(-) create mode 100644 rs/sns/testing/src/nns_dapp.rs delete mode 100644 rs/sns/testing/src/pocket_ic.rs create mode 100644 rs/sns/testing/src/sns.rs diff --git a/Cargo.lock b/Cargo.lock index e079a98346f..eb2843d62a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12607,10 +12607,11 @@ version = "0.9.0" dependencies = [ "candid", "canister-test", - "clap 4.5.27", + "clap 4.5.29", + "futures", "ic-base-types", - "ic-cdk 0.16.0", - "ic-management-canister-types", + "ic-cdk 0.17.1", + "ic-management-canister-types-private", "ic-nervous-system-agent", "ic-nervous-system-integration-tests", "ic-nns-common", diff --git a/rs/nervous_system/agent/src/pocketic_impl.rs b/rs/nervous_system/agent/src/pocketic_impl.rs index 9190c8e3b90..64a22a04290 100644 --- a/rs/nervous_system/agent/src/pocketic_impl.rs +++ b/rs/nervous_system/agent/src/pocketic_impl.rs @@ -148,7 +148,11 @@ impl CallCanisters for PocketIcAgent<'_> { ) -> Result { let canister_id = canister_id.into(); - let controllers = self.pocket_ic.get_controllers(canister_id).await; + let controllers = self + .pocket_ic + .try_get_controllers(canister_id) + .await + .unwrap_or(vec![]); let Some(controller) = controllers.into_iter().last() else { return Err(Self::Error::BlackHole); diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel index feade096c32..6243edc1479 100644 --- a/rs/sns/testing/BUILD.bazel +++ b/rs/sns/testing/BUILD.bazel @@ -17,6 +17,7 @@ DEPENDENCIES = [ "//rs/types/management_canister_types", "@crate_index//:candid", "@crate_index//:clap", + "@crate_index//:futures", "@crate_index//:reqwest", "@crate_index//:tokio", ] diff --git a/rs/sns/testing/Cargo.toml b/rs/sns/testing/Cargo.toml index 46809f0eb57..08df81b2fb9 100644 --- a/rs/sns/testing/Cargo.toml +++ b/rs/sns/testing/Cargo.toml @@ -21,9 +21,10 @@ path = "src/lib.rs" candid = { workspace = true } canister-test = { path = "../../rust_canisters/canister_test" } clap = { workspace = true } +futures = { workspace = true } ic-base-types = { path = "../../types/base_types" } ic-cdk = { workspace = true } -ic-management-canister-types = { path = "../../types/management_canister_types" } +ic-management-canister-types-private = { path = "../../types/management_canister_types" } ic-nervous-system-agent = { path = "../../nervous_system/agent" } ic-nervous-system-integration-tests = { path = "../../nervous_system/integration_tests" } ic-nns-constants = { path = "../../nns/constants" } diff --git a/rs/sns/testing/src/lib.rs b/rs/sns/testing/src/lib.rs index 67949d5605a..bb7109e1c42 100644 --- a/rs/sns/testing/src/lib.rs +++ b/rs/sns/testing/src/lib.rs @@ -1 +1,2 @@ -pub mod pocket_ic; +pub mod nns_dapp; +pub mod sns; diff --git a/rs/sns/testing/src/main.rs b/rs/sns/testing/src/main.rs index ac91e4be5ed..08de1e7dd58 100644 --- a/rs/sns/testing/src/main.rs +++ b/rs/sns/testing/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; -use ic_sns_testing::pocket_ic::{ - bootstrap_nns, create_sns, install_test_canister, upgrade_sns_controlled_test_canister, - TestCanisterInitArgs, +use ic_sns_testing::nns_dapp::bootstrap_nns; +use ic_sns_testing::sns::{ + create_sns, install_test_canister, upgrade_sns_controlled_test_canister, TestCanisterInitArgs, }; use pocket_ic::PocketIcBuilder; use reqwest::Url; diff --git a/rs/sns/testing/src/nns_dapp.rs b/rs/sns/testing/src/nns_dapp.rs new file mode 100644 index 00000000000..ef27e0729a4 --- /dev/null +++ b/rs/sns/testing/src/nns_dapp.rs @@ -0,0 +1,192 @@ +use candid::{CandidType, Encode}; +use canister_test::Wasm; +use futures::future::join_all; +use ic_base_types::CanisterId; +use ic_nervous_system_agent::CallCanisters; +use ic_nervous_system_integration_tests::pocket_ic_helpers::{ + add_wasms_to_sns_wasm, install_canister_with_controllers, NnsInstaller, +}; +use ic_nns_constants::{ + CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID, IDENTITY_CANISTER_ID, LEDGER_CANISTER_ID, + LEDGER_INDEX_CANISTER_ID, LIFELINE_CANISTER_ID, NNS_UI_CANISTER_ID, REGISTRY_CANISTER_ID, + ROOT_CANISTER_ID, SNS_AGGREGATOR_CANISTER_ID, SNS_WASM_CANISTER_ID, +}; +use pocket_ic::nonblocking::PocketIc; + +const ALL_NNS_CANISTER_IDS: [&CanisterId; 8] = [ + &GOVERNANCE_CANISTER_ID, + &LEDGER_CANISTER_ID, + &ROOT_CANISTER_ID, + &LIFELINE_CANISTER_ID, + &SNS_WASM_CANISTER_ID, + ®ISTRY_CANISTER_ID, + &CYCLES_MINTING_CANISTER_ID, + &LEDGER_INDEX_CANISTER_ID, +]; + +async fn validate_subnet_setup(pocket_ic: &PocketIc) { + let topology = pocket_ic.topology().await; + let _nns_subnet_id = topology.get_nns().expect("NNS subnet not found"); + let _sns_subnet_id = topology.get_nns().expect("SNS subnet not found"); + let _ii_subnet_id = topology.get_ii().expect("II subnet not found"); + let app_subnet_ids = topology.get_app_subnets(); + assert!(!app_subnet_ids.is_empty(), "No application subnets found"); +} + +async fn check_canister_exists(pocket_ic: &PocketIc, canister_id: &CanisterId) -> bool { + pocket_ic + .canister_info(*canister_id) + .await + .map(|_| true) + .unwrap_or(false) +} + +pub async fn bootstrap_nns(pocket_ic: &PocketIc) { + // Ensure that all required subnets are present before proceeding to install NNS canisters + // At the moment this check doesn't make a lot of sense since we are always creating the new PocketIC instance + // with all the required subnets. However, in the future, we might want to be able to check externally provided + // networks. + validate_subnet_setup(pocket_ic).await; + + // Check if all NNS canisters are already installed + let canisters_exist = join_all( + ALL_NNS_CANISTER_IDS + .iter() + .map(|canister_id| async { check_canister_exists(pocket_ic, canister_id).await }), + ) + .await; + + if !canisters_exist.iter().any(|exists| *exists) { + // TODO @rvem: at some point in the future we might want to use + // non-default 'initial_balances' as well as 'neurons_fund_hotkeys' to provide + // tokens and neuron hotkeys for user-provided indentities. + let mut nns_installer = NnsInstaller::default(); + nns_installer.with_current_nns_canister_versions(); + nns_installer.with_test_governance_canister(); + nns_installer.with_cycles_minting_canister(); + nns_installer.with_index_canister(); + nns_installer.install(pocket_ic).await; + add_wasms_to_sns_wasm(pocket_ic, false).await.unwrap(); + } else if !canisters_exist.iter().all(|exists| *exists) { + panic!("Some NNS canisters are missing, we cannot fix this automatically at the moment"); + } + + install_frontend_nns_canisters(pocket_ic).await; +} + +#[derive(CandidType)] +struct SnsAggregatorPayload { + pub update_interval_ms: u64, + pub fast_interval_ms: u64, +} + +#[derive(CandidType)] +struct NnsDappPayload { + args: Vec<(String, String)>, +} + +async fn install_frontend_nns_canisters(pocket_ic: &PocketIc) { + let features = &[]; + + let sns_aggregator_wasm = + Wasm::from_location_specified_by_env_var("sns_aggregator", features).unwrap(); + let nns_dapp_wasm = Wasm::from_location_specified_by_env_var("nns_dapp", features).unwrap(); + let internet_identity_wasm = + Wasm::from_location_specified_by_env_var("internet_identity", features).unwrap(); + + if !check_canister_exists(pocket_ic, &SNS_AGGREGATOR_CANISTER_ID).await { + // Refresh every second so that the NNS dapp is as up-to-date as possible + let sns_aggregator_payload = SnsAggregatorPayload { + update_interval_ms: 1000, + fast_interval_ms: 100, + }; + + install_canister_with_controllers( + pocket_ic, + "sns_aggregator", + SNS_AGGREGATOR_CANISTER_ID, + Encode!(&sns_aggregator_payload).unwrap(), + sns_aggregator_wasm, + vec![ROOT_CANISTER_ID.get(), SNS_WASM_CANISTER_ID.get()], + ) + .await; + } + + if !check_canister_exists(pocket_ic, &IDENTITY_CANISTER_ID).await { + let internet_identity_payload: Option<()> = None; + + install_canister_with_controllers( + pocket_ic, + "internet-identity", + IDENTITY_CANISTER_ID, + Encode!(&internet_identity_payload).unwrap(), + internet_identity_wasm, + vec![ROOT_CANISTER_ID.get()], + ) + .await; + } + + if !check_canister_exists(pocket_ic, &NNS_UI_CANISTER_ID).await { + // TODO @rvem: perhaps, we may start using configurable endpoint for the IC http interface + // which should be considered in NNS dapp configuration. + let endpoint = "localhost:8080"; + let nns_dapp_payload = NnsDappPayload { + args: vec![ + ("API_HOST".to_string(), format!("http://{}", endpoint)), + ( + "CYCLES_MINTING_CANISTER_ID".to_string(), + CYCLES_MINTING_CANISTER_ID.get().to_string(), + ), + ("DFX_NETWORK".to_string(), "local".to_string()), + ( + "FEATURE_FLAGS".to_string(), + "{\"ENABLE_CKBTC\":false,\"ENABLE_CKTESTBTC\":false}".to_string(), + ), + ("FETCH_ROOT_KEY".to_string(), "true".to_string()), + ( + "GOVERNANCE_CANISTER_ID".to_string(), + GOVERNANCE_CANISTER_ID.get().to_string(), + ), + ("HOST".to_string(), format!("http://{}", endpoint)), + ( + "IDENTITY_SERVICE_URL".to_string(), + format!("http://{}.{}", IDENTITY_CANISTER_ID.get(), endpoint), + ), + ( + "LEDGER_CANISTER_ID".to_string(), + LEDGER_CANISTER_ID.get().to_string(), + ), + ( + "OWN_CANISTER_ID".to_string(), + NNS_UI_CANISTER_ID.get().to_string(), + ), + ( + "ROBOTS".to_string(), + "".to_string(), + ), + ( + "SNS_AGGREGATOR_URL".to_string(), + format!("http://{}.{}", SNS_AGGREGATOR_CANISTER_ID.get(), endpoint), + ), + ("STATIC_HOST".to_string(), format!("http://{}", endpoint)), + ( + "WASM_CANISTER_ID".to_string(), + SNS_WASM_CANISTER_ID.get().to_string(), + ), + ( + "INDEX_CANISTER_ID".to_string(), + LEDGER_INDEX_CANISTER_ID.get().to_string(), + ), + ], + }; + install_canister_with_controllers( + pocket_ic, + "nns-dapp", + NNS_UI_CANISTER_ID, + Encode!(&nns_dapp_payload).unwrap(), + nns_dapp_wasm, + vec![ROOT_CANISTER_ID.get()], + ) + .await; + }; +} diff --git a/rs/sns/testing/src/pocket_ic.rs b/rs/sns/testing/src/pocket_ic.rs deleted file mode 100644 index c4021880985..00000000000 --- a/rs/sns/testing/src/pocket_ic.rs +++ /dev/null @@ -1,246 +0,0 @@ -use candid::{CandidType, Encode}; -use canister_test::Wasm; -use ic_base_types::CanisterId; -use ic_management_canister_types::CanisterInstallMode; -use ic_nervous_system_agent::sns::Sns; -use ic_nervous_system_integration_tests::{ - create_service_nervous_system_builder::CreateServiceNervousSystemBuilder, - pocket_ic_helpers::nns::governance::propose_to_deploy_sns_and_wait, - pocket_ic_helpers::sns::{ - governance::{ - propose_to_upgrade_sns_controlled_canister_and_wait, - EXPECTED_UPGRADE_DURATION_MAX_SECONDS, - }, - swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, - }, - pocket_ic_helpers::{ - add_wasms_to_sns_wasm, await_with_timeout, install_canister_on_subnet, - install_canister_with_controllers, NnsInstaller, - }, -}; -use ic_nns_common::pb::v1::ProposalId; -use ic_nns_constants::{ - CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID, IDENTITY_CANISTER_ID, LEDGER_CANISTER_ID, - LEDGER_INDEX_CANISTER_ID, NNS_UI_CANISTER_ID, ROOT_CANISTER_ID, SNS_AGGREGATOR_CANISTER_ID, - SNS_WASM_CANISTER_ID, -}; -use ic_sns_governance_api::pb::v1::UpgradeSnsControlledCanister; -use ic_sns_swap::pb::v1::Lifecycle; -use pocket_ic::{management_canister::CanisterStatusResultStatus, nonblocking::PocketIc}; - -pub async fn bootstrap_nns(pocket_ic: &PocketIc) { - // TODO @rvem: at some point in the future we might want to use - // non-default 'initial_balances' as well as 'neurons_fund_hotkeys' to provide - // tokens and neuron hotkeys for user-provided indentities. - let mut nns_installer = NnsInstaller::default(); - nns_installer.with_current_nns_canister_versions(); - nns_installer.with_test_governance_canister(); - nns_installer.with_cycles_minting_canister(); - nns_installer.with_index_canister(); - nns_installer.install(pocket_ic).await; - - install_frontend_nns_canisters(pocket_ic).await; - add_wasms_to_sns_wasm(pocket_ic, false).await.unwrap(); -} - -#[derive(CandidType)] -struct SnsAggregatorPayload { - pub update_interval_ms: u64, - pub fast_interval_ms: u64, -} - -#[derive(CandidType)] -struct NnsDappPayload { - args: Vec<(String, String)>, -} - -async fn install_frontend_nns_canisters(pocket_ic: &PocketIc) { - let features = &[]; - let sns_aggregator_wasm = - Wasm::from_location_specified_by_env_var("sns_aggregator", features).unwrap(); - let nns_dapp_wasm = Wasm::from_location_specified_by_env_var("nns_dapp", features).unwrap(); - let internet_identity_wasm = - Wasm::from_location_specified_by_env_var("internet_identity", features).unwrap(); - - // Refresh every second so that the NNS dapp is as up-to-date as possible - let sns_aggregator_payload = SnsAggregatorPayload { - update_interval_ms: 1000, - fast_interval_ms: 100, - }; - install_canister_with_controllers( - pocket_ic, - "sns_aggregator", - SNS_AGGREGATOR_CANISTER_ID, - Encode!(&sns_aggregator_payload).unwrap(), - sns_aggregator_wasm, - vec![ROOT_CANISTER_ID.get(), SNS_WASM_CANISTER_ID.get()], - ) - .await; - let internet_identity_payload: Option<()> = None; - install_canister_with_controllers( - pocket_ic, - "internet-identity", - IDENTITY_CANISTER_ID, - Encode!(&internet_identity_payload).unwrap(), - internet_identity_wasm, - vec![ROOT_CANISTER_ID.get()], - ) - .await; - // TODO @rvem: perhaps, we may start using configurable endpoint for the IC http interface - // which should be considered in NNS dapp configuration. - let endpoint = "localhost:8080"; - let nns_dapp_payload = NnsDappPayload { - args: vec![ - ("API_HOST".to_string(), format!("http://{}", endpoint)), - ( - "CYCLES_MINTING_CANISTER_ID".to_string(), - CYCLES_MINTING_CANISTER_ID.get().to_string(), - ), - ("DFX_NETWORK".to_string(), "local".to_string()), - ( - "FEATURE_FLAGS".to_string(), - "{\"ENABLE_CKBTC\":false,\"ENABLE_CKTESTBTC\":false}".to_string(), - ), - ("FETCH_ROOT_KEY".to_string(), "true".to_string()), - ( - "GOVERNANCE_CANISTER_ID".to_string(), - GOVERNANCE_CANISTER_ID.get().to_string(), - ), - ("HOST".to_string(), format!("http://{}", endpoint)), - ( - "IDENTITY_SERVICE_URL".to_string(), - format!("http://{}.{}", IDENTITY_CANISTER_ID.get(), endpoint), - ), - ( - "LEDGER_CANISTER_ID".to_string(), - LEDGER_CANISTER_ID.get().to_string(), - ), - ( - "OWN_CANISTER_ID".to_string(), - NNS_UI_CANISTER_ID.get().to_string(), - ), - ( - "ROBOTS".to_string(), - "".to_string(), - ), - ( - "SNS_AGGREGATOR_URL".to_string(), - format!("http://{}.{}", SNS_AGGREGATOR_CANISTER_ID.get(), endpoint), - ), - ("STATIC_HOST".to_string(), format!("http://{}", endpoint)), - ( - "WASM_CANISTER_ID".to_string(), - SNS_WASM_CANISTER_ID.get().to_string(), - ), - ( - "INDEX_CANISTER_ID".to_string(), - LEDGER_INDEX_CANISTER_ID.get().to_string(), - ), - ], - }; - install_canister_with_controllers( - pocket_ic, - "nns-dapp", - NNS_UI_CANISTER_ID, - Encode!(&nns_dapp_payload).unwrap(), - nns_dapp_wasm, - vec![ROOT_CANISTER_ID.get()], - ) - .await; -} - -// TODO @rvem: I don't like the fact that this struct definition is copy-pasted from 'canister/canister.rs'. -// We should extract it into a separate crate and reuse in both canister and this crates. -#[derive(CandidType)] -pub struct TestCanisterInitArgs { - pub greeting: Option, -} - -pub async fn install_test_canister(pocket_ic: &PocketIc, args: TestCanisterInitArgs) -> CanisterId { - let topology = pocket_ic.topology().await; - let application_subnet_ids = topology.get_app_subnets(); - let application_subnet_id = application_subnet_ids - .first() - .expect("No Application subnet found"); - let features = &[]; - let test_canister_wasm = - Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); - install_canister_on_subnet( - pocket_ic, - *application_subnet_id, - Encode!(&args).unwrap(), - Some(test_canister_wasm), - vec![ROOT_CANISTER_ID.get()], - ) - .await -} - -pub async fn create_sns( - pocket_ic: &PocketIc, - dapp_canister_ids: Vec, -) -> (Sns, ProposalId) { - let sns_proposal_id = "1"; - let create_service_nervous_system = CreateServiceNervousSystemBuilder::default() - .neurons_fund_participation(true) - .with_dapp_canisters(dapp_canister_ids) - .build(); - let swap_parameters = create_service_nervous_system - .swap_parameters - .clone() - .unwrap(); - assert_eq!( - swap_parameters.start_time, None, - "Expecting the swap start time to be None to start the swap immediately" - ); - let (sns, proposal_id) = - propose_to_deploy_sns_and_wait(pocket_ic, create_service_nervous_system, sns_proposal_id) - .await; - await_swap_lifecycle(pocket_ic, sns.swap.canister_id, Lifecycle::Open) - .await - .expect("Expecting the swap to be open after creation"); - smoke_test_participate_and_finalize(pocket_ic, sns.swap.canister_id, swap_parameters).await; - await_swap_lifecycle(&pocket_ic, sns.swap.canister_id, Lifecycle::Committed) - .await - .expect("Expecting the swap to be commited after creation and swap completion"); - (sns, proposal_id) -} - -pub async fn upgrade_sns_controlled_test_canister( - pocket_ic: &PocketIc, - sns: Sns, - canister_id: CanisterId, - upgrade_arg: TestCanisterInitArgs, -) { - // For now, we're using the same wasm module, but different init arguments used in 'post_upgrade' hook. - let features = &[]; - let test_canister_wasm = - Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); - propose_to_upgrade_sns_controlled_canister_and_wait( - pocket_ic, - sns.governance.canister_id, - UpgradeSnsControlledCanister { - canister_id: Some(canister_id.get()), - new_canister_wasm: test_canister_wasm.bytes(), - canister_upgrade_arg: Some(Encode!(&upgrade_arg).unwrap()), - mode: Some(CanisterInstallMode::Upgrade as i32), - chunked_canister_wasm: None, - }, - ) - .await; - // Wait for the canister to become available - await_with_timeout( - pocket_ic, - 0..EXPECTED_UPGRADE_DURATION_MAX_SECONDS, - |pocket_ic| async { - let canister_status = pocket_ic - .canister_status(canister_id.into(), Some(sns.root.canister_id.into())) - .await; - canister_status - .expect("Canister status is unavailable") - .status as u32 - }, - &(CanisterStatusResultStatus::Running as u32), - ) - .await - .expect("Test canister failed to get into the 'Running' state after upgrade"); -} diff --git a/rs/sns/testing/src/sns.rs b/rs/sns/testing/src/sns.rs new file mode 100644 index 00000000000..9235c3b3ddc --- /dev/null +++ b/rs/sns/testing/src/sns.rs @@ -0,0 +1,118 @@ +use candid::{CandidType, Encode}; +use canister_test::Wasm; +use ic_base_types::CanisterId; +use ic_management_canister_types_private::CanisterInstallMode; +use ic_nervous_system_agent::sns::Sns; +use ic_nervous_system_integration_tests::{ + create_service_nervous_system_builder::CreateServiceNervousSystemBuilder, + pocket_ic_helpers::nns::governance::propose_to_deploy_sns_and_wait, + pocket_ic_helpers::sns::{ + governance::{ + propose_to_upgrade_sns_controlled_canister_and_wait, + EXPECTED_UPGRADE_DURATION_MAX_SECONDS, + }, + swap::{await_swap_lifecycle, smoke_test_participate_and_finalize}, + }, + pocket_ic_helpers::{await_with_timeout, install_canister_on_subnet}, +}; +use ic_nns_common::pb::v1::ProposalId; +use ic_nns_constants::ROOT_CANISTER_ID; +use ic_sns_governance_api::pb::v1::UpgradeSnsControlledCanister; +use ic_sns_swap::pb::v1::Lifecycle; +use pocket_ic::{management_canister::CanisterStatusResultStatus, nonblocking::PocketIc}; + +// TODO @rvem: I don't like the fact that this struct definition is copy-pasted from 'canister/canister.rs'. +// We should extract it into a separate crate and reuse in both canister and this crates. +#[derive(CandidType)] +pub struct TestCanisterInitArgs { + pub greeting: Option, +} + +pub async fn install_test_canister(pocket_ic: &PocketIc, args: TestCanisterInitArgs) -> CanisterId { + let topology = pocket_ic.topology().await; + let application_subnet_ids = topology.get_app_subnets(); + let application_subnet_id = application_subnet_ids + .first() + .expect("No Application subnet found"); + let features = &[]; + let test_canister_wasm = + Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); + install_canister_on_subnet( + pocket_ic, + *application_subnet_id, + Encode!(&args).unwrap(), + Some(test_canister_wasm), + vec![ROOT_CANISTER_ID.get()], + ) + .await +} + +pub async fn create_sns( + pocket_ic: &PocketIc, + dapp_canister_ids: Vec, +) -> (Sns, ProposalId) { + let sns_proposal_id = "1"; + let create_service_nervous_system = CreateServiceNervousSystemBuilder::default() + .neurons_fund_participation(true) + .with_dapp_canisters(dapp_canister_ids) + .build(); + let swap_parameters = create_service_nervous_system + .swap_parameters + .clone() + .unwrap(); + assert_eq!( + swap_parameters.start_time, None, + "Expecting the swap start time to be None to start the swap immediately" + ); + let (sns, proposal_id) = + propose_to_deploy_sns_and_wait(pocket_ic, create_service_nervous_system, sns_proposal_id) + .await; + await_swap_lifecycle(pocket_ic, sns.swap.canister_id, Lifecycle::Open) + .await + .expect("Expecting the swap to be open after creation"); + smoke_test_participate_and_finalize(pocket_ic, sns.swap.canister_id, swap_parameters).await; + await_swap_lifecycle(pocket_ic, sns.swap.canister_id, Lifecycle::Committed) + .await + .expect("Expecting the swap to be commited after creation and swap completion"); + (sns, proposal_id) +} + +pub async fn upgrade_sns_controlled_test_canister( + pocket_ic: &PocketIc, + sns: Sns, + canister_id: CanisterId, + upgrade_arg: TestCanisterInitArgs, +) { + // For now, we're using the same wasm module, but different init arguments used in 'post_upgrade' hook. + let features = &[]; + let test_canister_wasm = + Wasm::from_location_specified_by_env_var("sns_testing_canister", features).unwrap(); + propose_to_upgrade_sns_controlled_canister_and_wait( + pocket_ic, + sns.governance.canister_id, + UpgradeSnsControlledCanister { + canister_id: Some(canister_id.get()), + new_canister_wasm: test_canister_wasm.bytes(), + canister_upgrade_arg: Some(Encode!(&upgrade_arg).unwrap()), + mode: Some(CanisterInstallMode::Upgrade as i32), + chunked_canister_wasm: None, + }, + ) + .await; + // Wait for the canister to become available + await_with_timeout( + pocket_ic, + 0..EXPECTED_UPGRADE_DURATION_MAX_SECONDS, + |pocket_ic| async { + let canister_status = pocket_ic + .canister_status(canister_id.into(), Some(sns.root.canister_id.into())) + .await; + canister_status + .expect("Canister status is unavailable") + .status as u32 + }, + &(CanisterStatusResultStatus::Running as u32), + ) + .await + .expect("Test canister failed to get into the 'Running' state after upgrade"); +} diff --git a/rs/sns/testing/tests/sns_testing_ci.rs b/rs/sns/testing/tests/sns_testing_ci.rs index 0601b6fdf36..3084aa2b5a4 100644 --- a/rs/sns/testing/tests/sns_testing_ci.rs +++ b/rs/sns/testing/tests/sns_testing_ci.rs @@ -1,7 +1,7 @@ use candid::{Decode, Encode, Principal}; -use ic_sns_testing::pocket_ic::{ - bootstrap_nns, create_sns, install_test_canister, upgrade_sns_controlled_test_canister, - TestCanisterInitArgs, +use ic_sns_testing::nns_dapp::bootstrap_nns; +use ic_sns_testing::sns::{ + create_sns, install_test_canister, upgrade_sns_controlled_test_canister, TestCanisterInitArgs, }; use pocket_ic::PocketIcBuilder; From 2671cf4451baa0efc9f4cedaa13834c66f3695c6 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 6 Feb 2025 13:31:28 +0100 Subject: [PATCH 10/12] pocket-ic: add 'try_get_controllers' 'get_controllers' method panics if the target canister doesn't exist --- packages/pocket-ic/CHANGELOG.md | 2 ++ packages/pocket-ic/src/lib.rs | 9 ++++++++- packages/pocket-ic/src/nonblocking.rs | 15 ++++++++++++--- packages/pocket-ic/tests/tests.rs | 13 +++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/pocket-ic/CHANGELOG.md b/packages/pocket-ic/CHANGELOG.md index e426dc3ef75..5b9879c2210 100644 --- a/packages/pocket-ic/CHANGELOG.md +++ b/packages/pocket-ic/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +- The function `PocketIc::try_get_controllers` which gets the controllers of a canister but doesn't panic if the target canister + doesn't exist. - The function `PocketIcBuilder::with_bitcoind_addrs` to specify multiple addresses and ports at which `bitcoind` processes are listening. - The function `PocketIc::query_call_with_effective_principal` for making generic query calls (including management canister query calls). - The function `PocketIc::ingress_status` to fetch the status of an update call submitted through an ingress message. diff --git a/packages/pocket-ic/src/lib.rs b/packages/pocket-ic/src/lib.rs index b520f804345..2e297de2921 100644 --- a/packages/pocket-ic/src/lib.rs +++ b/packages/pocket-ic/src/lib.rs @@ -70,7 +70,7 @@ use candid::{ Principal, }; use ic_transport_types::SubnetMetrics; -use reqwest::Url; +use reqwest::{StatusCode, Url}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use slog::Level; @@ -649,6 +649,13 @@ impl PocketIc { runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await }) } + /// Get the controllers of a canister. + #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))] + pub fn try_get_controllers(&self, canister_id: CanisterId) -> Result, (StatusCode, String)> { + let runtime = self.runtime.clone(); + runtime.block_on(async { self.pocket_ic.try_get_controllers(canister_id).await }) + } + /// Get the current cycles balance of a canister. #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))] pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 { diff --git a/packages/pocket-ic/src/nonblocking.rs b/packages/pocket-ic/src/nonblocking.rs index e58a8b97536..84d04a1f141 100644 --- a/packages/pocket-ic/src/nonblocking.rs +++ b/packages/pocket-ic/src/nonblocking.rs @@ -519,16 +519,25 @@ impl PocketIc { /// Panics if the canister does not exist. #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))] pub async fn get_controllers(&self, canister_id: CanisterId) -> Vec { + self.try_get_controllers(canister_id).await.unwrap() + } + + /// Get the controllers of a canister. + #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))] + pub async fn try_get_controllers( + &self, + canister_id: CanisterId, + ) -> Result, (StatusCode, String)> { let endpoint = "read/get_controllers"; - let result: Vec = self - .post( + let result: Result, (StatusCode, String)> = self + .try_post( endpoint, RawCanisterId { canister_id: canister_id.as_slice().to_vec(), }, ) .await; - result.into_iter().map(|p| p.into()).collect() + result.map(|v| v.into_iter().map(|p| p.into()).collect()) } /// Get the current cycles balance of a canister. diff --git a/packages/pocket-ic/tests/tests.rs b/packages/pocket-ic/tests/tests.rs index 5eb863b3c4b..8e9874b52e9 100644 --- a/packages/pocket-ic/tests/tests.rs +++ b/packages/pocket-ic/tests/tests.rs @@ -1739,6 +1739,19 @@ fn get_controllers_of_nonexisting_canister() { let _ = pic.get_controllers(canister_id); } +#[test] +fn try_get_controllers_of_nonexisting_canister() { + let pic = PocketIc::new(); + + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 100_000_000_000_000); + pic.stop_canister(canister_id, None).unwrap(); + pic.delete_canister(canister_id, None).unwrap(); + + let res = pic.try_get_controllers(canister_id); + assert!(res.is_err()) +} + #[test] fn test_canister_snapshots() { let pic = PocketIc::new(); From 2c0732a243276286bbc023eea830c9d3caff085d Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Fri, 14 Feb 2025 14:57:05 +0100 Subject: [PATCH 11/12] fixup! pocket-ic: add 'try_get_controllers' --- packages/pocket-ic/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/pocket-ic/src/lib.rs b/packages/pocket-ic/src/lib.rs index 2e297de2921..f84553dc1ab 100644 --- a/packages/pocket-ic/src/lib.rs +++ b/packages/pocket-ic/src/lib.rs @@ -651,7 +651,10 @@ impl PocketIc { /// Get the controllers of a canister. #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))] - pub fn try_get_controllers(&self, canister_id: CanisterId) -> Result, (StatusCode, String)> { + pub fn try_get_controllers( + &self, + canister_id: CanisterId, + ) -> Result, (StatusCode, String)> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.try_get_controllers(canister_id).await }) } From d5235eef4e2f45f3993f99effe1b3729382109e9 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Mon, 17 Feb 2025 09:50:45 +0100 Subject: [PATCH 12/12] fixup! sns-testing: init --- rs/sns/testing/BUILD.bazel | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rs/sns/testing/BUILD.bazel b/rs/sns/testing/BUILD.bazel index 6243edc1479..b63f3efa11c 100644 --- a/rs/sns/testing/BUILD.bazel +++ b/rs/sns/testing/BUILD.bazel @@ -22,10 +22,6 @@ DEPENDENCIES = [ "@crate_index//:tokio", ] -MACRO_DEPENDENCIES = [] - -ALIASES = {} - DEV_DATA = [ "//rs/ledger_suite/icp/archive:ledger-archive-node-canister-wasm", "//rs/ledger_suite/icp/index:ic-icp-index-canister",