diff --git a/Cargo.lock b/Cargo.lock index a9c5c231ec3d..86ffa24a2fb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15230,6 +15230,7 @@ dependencies = [ "ic_consensus_system_test_utils", "nix 0.24.3", "once_cell", + "rand 0.8.5", "registry-canister", "serde", "slog", diff --git a/rs/tests/testnets/mainnet_nns/BUILD.bazel b/rs/tests/testnets/mainnet_nns/BUILD.bazel index 49023d3cfeac..41ed7a48a299 100644 --- a/rs/tests/testnets/mainnet_nns/BUILD.bazel +++ b/rs/tests/testnets/mainnet_nns/BUILD.bazel @@ -33,6 +33,7 @@ rust_library( "@crate_index//:ic-agent", "@crate_index//:nix", "@crate_index//:once_cell", + "@crate_index//:rand", "@crate_index//:serde", "@crate_index//:slog", "@crate_index//:ssh2", diff --git a/rs/tests/testnets/mainnet_nns/Cargo.toml b/rs/tests/testnets/mainnet_nns/Cargo.toml index 5a4210a09546..1978f913bb04 100644 --- a/rs/tests/testnets/mainnet_nns/Cargo.toml +++ b/rs/tests/testnets/mainnet_nns/Cargo.toml @@ -27,6 +27,7 @@ ic-types = { path = "../../../types/types" } ic_consensus_system_test_utils = { path = "../../consensus/utils" } nix = { workspace = true } once_cell = "1.21" +rand = { workspace = true } registry-canister = { path = "../../../registry/canister" } serde = { workspace = true } slog = { workspace = true } diff --git a/rs/tests/testnets/mainnet_nns/src/lib.rs b/rs/tests/testnets/mainnet_nns/src/lib.rs index b02dce93b0f7..cc0ce8e00fa1 100644 --- a/rs/tests/testnets/mainnet_nns/src/lib.rs +++ b/rs/tests/testnets/mainnet_nns/src/lib.rs @@ -1,5 +1,7 @@ use anyhow::Result; use ic_base_types::PrincipalId; +use ic_canister_client::{Ed25519KeyPair, Sender}; +use ic_canister_client_sender::SigKeys; use ic_consensus_system_test_utils::rw_message::install_nns_and_check_progress; use ic_crypto_utils_threshold_sig_der::public_key_der_to_pem; use ic_limits::DKG_INTERVAL_HEIGHT; @@ -30,9 +32,9 @@ use std::sync::mpsc::{self, Receiver}; use std::{io::Write, process::Command}; use url::Url; -use crate::proposals::NEURON_CONTROLLER; -use crate::proposals::NEURON_SECRET_KEY_PEM; -use crate::proposals::ProposalWithMainnetState; +use crate::proposals::{ + ProposalWithMainnetState, RECOVERED_NNS_DICTATOR_NEURON_IDENTITY, RecoveredNnsDictatorNeuron, +}; pub const MAINNET_NODE_VM_RESOURCE_OVERRIDES: VmResourceOverrides = VmResourceOverrides { boot_image_minimal_size_gibibytes: Some(ImageSizeGiB::new(192)), @@ -197,7 +199,7 @@ fn setup_recovered_nns( patch_subnet_list(&env); // Set up a dictator neuron with a large stake to be able to pass any proposal instantly during // the test - let neuron_id: NeuronId = setup_test_neuron(&env); + let (neuron_id, neuron_secret_key_pem) = setup_test_neuron(&env); // Wait until the aux node is setup and we have fetched ic-recovery before starting the recovery let aux_node = rx_aux_node.recv().unwrap(); @@ -206,7 +208,13 @@ fn setup_recovered_nns( .unwrap_or_else(|e| panic!("Failed to fetch the mainnet ic-recovery because {e:?}")); recover_nns_subnet(&env, &nns_node, &recovered_nns_node, &aux_node); - ProposalWithMainnetState::write_dictator_neuron_id_to_env(&env, neuron_id); + ProposalWithMainnetState::write_dictator_neuron_identity_to_env( + &env, + RecoveredNnsDictatorNeuron { + neuron_id, + neuron_secret_key_pem, + }, + ); test_recovered_nns(&env, &recovered_nns_node); @@ -390,15 +398,16 @@ fn patch_subnet_list(env: &TestEnv) { }); } -fn setup_test_neuron(env: &TestEnv) -> NeuronId { - let neuron_id = with_neuron_for_tests(env); - with_trusted_neurons_following_neuron_for_tests(env, neuron_id); - neuron_id +fn setup_test_neuron(env: &TestEnv) -> (NeuronId, String) { + let neuron_identity = Ed25519KeyPair::generate(&mut rand::thread_rng()); + let neuron_principal = Sender::SigKeys(SigKeys::Ed25519(neuron_identity)).get_principal_id(); + let neuron_id = with_neuron_for_tests(env, neuron_principal); + with_trusted_neurons_following_neuron_for_tests(env, neuron_id, neuron_principal); + (neuron_id, neuron_identity.to_pem()) } -fn with_neuron_for_tests(env: &TestEnv) -> NeuronId { +fn with_neuron_for_tests(env: &TestEnv, controller: PrincipalId) -> NeuronId { let logger: slog::Logger = env.logger(); - let controller = PrincipalId::from_str(NEURON_CONTROLLER).unwrap(); info!(logger, "Create a neuron followed by trusted neurons ..."); // The neuron's stake must be large enough to be eligible to make proposals (> reject cost fee), @@ -430,12 +439,14 @@ fn with_neuron_for_tests(env: &TestEnv) -> NeuronId { neuron_id } -fn with_trusted_neurons_following_neuron_for_tests(env: &TestEnv, neuron_id: NeuronId) { - let NeuronId(id) = neuron_id; - let controller = PrincipalId::from_str(NEURON_CONTROLLER).unwrap(); +fn with_trusted_neurons_following_neuron_for_tests( + env: &TestEnv, + NeuronId(neuron_id): NeuronId, + controller: PrincipalId, +) { ic_replay(env, |cmd| { cmd.arg("with-trusted-neurons-following-neuron-for-tests") - .arg(id.to_string()) + .arg(neuron_id.to_string()) .arg(controller.to_string()); }); } @@ -714,7 +725,7 @@ fn setup_ic(env: TestEnv) { /// This script can be sourced such that we can easily use the legacy /// nns-tools shell scripts in /testnet/tools/nns-tools/ with the dynamic /// testnet deployed by this system-test. -fn write_sh_lib(env: &TestEnv, neuron_id: NeuronId, http_gateway: &Url) { +fn write_sh_lib(env: &TestEnv, NeuronId(neuron_id): NeuronId, http_gateway: &Url) { let logger: slog::Logger = env.logger(); let set_testnet_env_vars_sh_path = env.get_path(PATH_SET_TESTNET_ENV_VARS_SH); let set_testnet_env_vars_sh_str = set_testnet_env_vars_sh_path.display(); @@ -722,16 +733,21 @@ fn write_sh_lib(env: &TestEnv, neuron_id: NeuronId, http_gateway: &Url) { let pem = env.get_path("neuron_secret_key.pem"); let mut pem_file = File::create(&pem).unwrap(); pem_file - .write_all(NEURON_SECRET_KEY_PEM.as_bytes()) + .write_all( + RECOVERED_NNS_DICTATOR_NEURON_IDENTITY + .get() + .expect("'write_dictator_neuron_identity_to_env' should have been called before 'write_sh_lib'") + .1 + .as_bytes(), + ) .unwrap(); - let neuron_id_number = neuron_id.0; fs::write( &set_testnet_env_vars_sh_path, format!( "export IC_ADMIN={ic_admin:?};\n\ export PEM={pem:?};\n\ export NNS_URL=\"{http_gateway}\";\n\ - export NEURON_ID={neuron_id_number:?};\n\ + export NEURON_ID={neuron_id:?};\n\ " ), ) diff --git a/rs/tests/testnets/mainnet_nns/src/proposals.rs b/rs/tests/testnets/mainnet_nns/src/proposals.rs index aed2ef04e9bc..64e4661b3c40 100644 --- a/rs/tests/testnets/mainnet_nns/src/proposals.rs +++ b/rs/tests/testnets/mainnet_nns/src/proposals.rs @@ -35,29 +35,25 @@ use url::Url; * `[name_of_the_proposal]`. * * IMPORTANT: Before making any proposal with this module, you MUST call -* `ProposalWithMainnetState::read_dictator_neuron_id_from_env(&env)` once in your `test` function -* (not in `setup`, as those functions run in different processes). This is necessary, so that the -* neuron ID is initialized from the test environment and used in subsequent proposals. +* `ProposalWithMainnetState::read_dictator_neuron_identity_from_env(&env)` once in your `test` +* function (not in `setup`, as those functions run in different processes). This is necessary, so +* that the neuron identity is initialized from the test environment and used in subsequent +* proposals. */ -// Test neuron secret key and corresponding controller principal -pub(crate) const NEURON_CONTROLLER: &str = - "bc7vk-kulc6-vswcu-ysxhv-lsrxo-vkszu-zxku3-xhzmh-iac7m-lwewm-2ae"; -pub(crate) const NEURON_SECRET_KEY_PEM: &str = "-----BEGIN PRIVATE KEY----- -MFMCAQEwBQYDK2VwBCIEIKohpVANxO4xElQYXElAOXZHwJSVHERLE8feXSfoKwxX -oSMDIQBqgs2z86b+S5X9HvsxtE46UZwfDHtebwmSQWSIcKr2ew== ------END PRIVATE KEY-----"; - -static RECOVERED_NNS_DICTATOR_NEURON_ID: OnceCell = OnceCell::new(); +/// Test neuron ID and secret key of its controller encoded in PEM format. +pub(crate) static RECOVERED_NNS_DICTATOR_NEURON_IDENTITY: OnceCell<(NeuronId, String)> = + OnceCell::new(); #[derive(Deserialize, Serialize)] pub struct RecoveredNnsDictatorNeuron { - recovered_nns_dictator_neuron_id: NeuronId, + pub neuron_id: NeuronId, + pub neuron_secret_key_pem: String, } impl TestEnvAttribute for RecoveredNnsDictatorNeuron { fn attribute_name() -> String { - String::from("recovered_nns_dictator_neuron_id") + String::from("recovered_nns_dictator_neuron_identity") } } @@ -67,50 +63,49 @@ pub struct ProposalWithMainnetState { } impl ProposalWithMainnetState { - /// Initializes a ProposalWithMainnetState instance reading the neuron ID from the static - /// variable, which must have been initialized before via `read_dictator_neuron_id_from_env`. + /// Initializes a [`ProposalWithMainnetState`] instance reading the neuron identity from the + /// static variable, which must have been initialized before via + /// `read_dictator_neuron_identity_from_env`. /// /// This function is not intended to be called externally (thus it is not `pub`), but only called /// at the beginning of each proposal function below. fn new() -> Self { - let neuron_id = *RECOVERED_NNS_DICTATOR_NEURON_ID.get().expect( - "'read_dictator_neuron_id_from_env' must be called before using ProposalWithMainnetState", + let (neuron_id, secret_key_pem) = RECOVERED_NNS_DICTATOR_NEURON_IDENTITY.get().expect( + "'read_dictator_neuron_identity_from_env' must be called before using ProposalWithMainnetState", ); - let sig_keys = - SigKeys::from_pem(NEURON_SECRET_KEY_PEM).expect("Failed to parse secret key"); + let sig_keys = SigKeys::from_pem(secret_key_pem).expect("Failed to parse secret key"); let proposal_sender = Sender::SigKeys(sig_keys); Self { - neuron_id, + neuron_id: *neuron_id, proposal_sender, } } - /// Writes the given dictator neuron ID to the test environment, so that it can be read later on - /// potentially by a different process. Currently used in the `setup` function that creates a - /// testnet with NNS mainnet state. - pub fn write_dictator_neuron_id_to_env(env: &TestEnv, neuron_id: NeuronId) { - RecoveredNnsDictatorNeuron { - recovered_nns_dictator_neuron_id: neuron_id, - } - .write_attribute(env); + /// Writes the given dictator neuron identity to the test environment, so that it can be read + /// later on potentially by a different process. Currently used in the `setup` function that + /// creates a testnet with NNS mainnet state. + pub fn write_dictator_neuron_identity_to_env( + env: &TestEnv, + neuron: RecoveredNnsDictatorNeuron, + ) { + neuron.write_attribute(env); - RECOVERED_NNS_DICTATOR_NEURON_ID - .set(neuron_id) - .expect("'write_dictator_neuron_id_to_env' can only be called once"); + RECOVERED_NNS_DICTATOR_NEURON_IDENTITY + .set((neuron.neuron_id, neuron.neuron_secret_key_pem)) + .expect("'write_dictator_neuron_identity_to_env' can only be called once"); } - /// Initializes the static variable holding the dictator neuron ID by reading it from the test - /// environment. This function MUST be called once before using any of the proposal functions - /// below. - pub fn read_dictator_neuron_id_from_env(env: &TestEnv) { - let neuron_id = RecoveredNnsDictatorNeuron::try_read_attribute(env) - .expect("'write_dictator_neuron_id_to_env' must be called before reading") - .recovered_nns_dictator_neuron_id; - - RECOVERED_NNS_DICTATOR_NEURON_ID - .set(neuron_id) - .expect("'read_dictator_neuron_id_from_env' can only be called once"); + /// Initializes the static variable holding the dictator neuron identity by reading it from the + /// test environment. This function MUST be called once before using any of the proposal + /// functions below. + pub fn read_dictator_neuron_identity_from_env(env: &TestEnv) { + let neuron = RecoveredNnsDictatorNeuron::try_read_attribute(env) + .expect("'write_dictator_neuron_identity_to_env' must be called before reading"); + + RECOVERED_NNS_DICTATOR_NEURON_IDENTITY + .set((neuron.neuron_id, neuron.neuron_secret_key_pem)) + .expect("'read_dictator_neuron_identity_from_env' can only be called once"); } /// Code duplicate of rs/tests/consensus/utils/src/upgrade.rs:bless_replica_version