Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rs/tests/testnets/mainnet_nns/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions rs/tests/testnets/mainnet_nns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
54 changes: 35 additions & 19 deletions rs/tests/testnets/mainnet_nns/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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();
Expand All @@ -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);

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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());
});
}
Expand Down Expand Up @@ -714,24 +725,29 @@ 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();
let ic_admin = fs::canonicalize(get_dependency_path_from_env("IC_ADMIN_PATH")).unwrap();
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\
"
),
)
Expand Down
81 changes: 38 additions & 43 deletions rs/tests/testnets/mainnet_nns/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NeuronId> = 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")
}
}

Expand All @@ -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",
Comment thread
kpop-dfinity marked this conversation as resolved.
);
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
Expand Down
Loading