diff --git a/Cargo.lock b/Cargo.lock index 067e8b126a1..d2877318f4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9953,6 +9953,7 @@ dependencies = [ "ic-canisters-http-types", "ic-cdk 0.13.5", "ic-cdk-macros 0.9.0", + "ic-cdk-timers", "ic-config", "ic-crypto-getrandom-for-wasm", "ic-crypto-sha2", diff --git a/rs/nns/governance/BUILD.bazel b/rs/nns/governance/BUILD.bazel index 43814dc0c71..5f205ee1c20 100644 --- a/rs/nns/governance/BUILD.bazel +++ b/rs/nns/governance/BUILD.bazel @@ -56,6 +56,7 @@ DEPENDENCIES = [ "@crate_index//:dyn-clone", "@crate_index//:futures", "@crate_index//:ic-cdk", + "@crate_index//:ic-cdk-timers", "@crate_index//:ic-metrics-encoder", "@crate_index//:ic-stable-structures", "@crate_index//:itertools", diff --git a/rs/nns/governance/Cargo.toml b/rs/nns/governance/Cargo.toml index 3fd03050b87..6183dc1c2ee 100644 --- a/rs/nns/governance/Cargo.toml +++ b/rs/nns/governance/Cargo.toml @@ -39,6 +39,7 @@ ic-base-types = { path = "../../types/base_types" } ic-canisters-http-types = { path = "../../rust_canisters/http_types" } ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } +ic-cdk-timers = { workspace = true } ic-crypto-getrandom-for-wasm = { path = "../../crypto/getrandom_for_wasm" } ic-crypto-sha2 = { path = "../../crypto/sha2/" } ic-ledger-core = { path = "../../rosetta-api/ledger_core" } diff --git a/rs/nns/governance/benches/scale.rs b/rs/nns/governance/benches/scale.rs index 71422d36836..ef47e7b163b 100644 --- a/rs/nns/governance/benches/scale.rs +++ b/rs/nns/governance/benches/scale.rs @@ -20,7 +20,7 @@ use ic_base_types::{CanisterId, PrincipalId}; use ic_nervous_system_common::{cmc::FakeCmc, ledger::IcpLedger, NervousSystemError}; use ic_nns_common::pb::v1::NeuronId; use ic_nns_governance::{ - governance::{Environment, Governance, HeapGrowthPotential}, + governance::{Environment, Governance, HeapGrowthPotential, RngError}, pb::v1::{ neuron, proposal, ExecuteNnsFunction, Governance as GovernanceProto, GovernanceError, Motion, NetworkEconomics, Neuron, Proposal, Topic, @@ -48,11 +48,19 @@ impl Environment for MockEnvironment { self.secs } - fn random_u64(&mut self) -> u64 { + fn random_u64(&mut self) -> Result { todo!() } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { + todo!() + } + + fn seed_rng(&mut self, _seed: [u8; 32]) { + todo!() + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { todo!() } diff --git a/rs/nns/governance/canister/canister.rs b/rs/nns/governance/canister/canister.rs index 152acde580c..3599964a24b 100644 --- a/rs/nns/governance/canister/canister.rs +++ b/rs/nns/governance/canister/canister.rs @@ -6,6 +6,7 @@ use ic_cdk::{ api::call::arg_data_raw, caller as ic_cdk_caller, heartbeat, post_upgrade, pre_upgrade, println, query, spawn, update, }; +use ic_management_canister_types::IC_00; use ic_nervous_system_canisters::{cmc::CMCCanister, ledger::IcpLedgerCanister}; use ic_nervous_system_common::{ memory_manager_upgrade_storage::{load_protobuf, store_protobuf}, @@ -20,7 +21,7 @@ use ic_nns_common::{ use ic_nns_constants::LEDGER_CANISTER_ID; use ic_nns_governance::{ decoder_config, encode_metrics, - governance::{Environment, Governance, HeapGrowthPotential, TimeWarp as GovTimeWarp}, + governance::{Environment, Governance, HeapGrowthPotential, RngError, TimeWarp as GovTimeWarp}, neuron_data_validation::NeuronDataValidationSummary, pb::v1::{self as gov_pb, Governance as InternalGovernanceProto}, storage::{grow_upgrades_memory_to, validate_stable_storage, with_upgrades_memory}, @@ -149,8 +150,38 @@ fn set_governance(gov: Governance) { .expect("Error initializing the governance canister."); } +// Seeding interval seeks to find a balance between the need for rng secrecy, and +// avoiding the overhead of frequent reseeding. +const SEEDING_INTERVAL: Duration = Duration::from_secs(3600); +const RETRY_SEEDING_INTERVAL: Duration = Duration::from_secs(30); + +fn schedule_seeding(duration: Duration) { + ic_cdk_timers::set_timer(duration, || { + spawn(async { + let result: Result<([u8; 32],), (i32, String)> = + CdkRuntime::call_with_cleanup(IC_00, "raw_rand", ()).await; + + let seed = match result { + Ok((seed,)) => seed, + Err((code, msg)) => { + println!( + "{}Error seeding RNG. Error Code: {}. Error Message: {}", + LOG_PREFIX, code, msg + ); + schedule_seeding(RETRY_SEEDING_INTERVAL); + return; + } + }; + + () = governance_mut().env.seed_rng(seed); + // Schedule reseeding on a timer with duration SEEDING_INTERVAL + schedule_seeding(SEEDING_INTERVAL); + }) + }); +} + struct CanisterEnv { - rng: ChaCha20Rng, + rng: Option, time_warp: GovTimeWarp, } @@ -174,23 +205,7 @@ fn now_seconds() -> u64 { impl CanisterEnv { fn new() -> Self { CanisterEnv { - // Seed the PRNG with the current time. - // - // This is safe since all replicas are guaranteed to see the same result of time() - // and it isn't easily predictable from the outside. - // - // Using raw_rand from the ic00 api is an asynchronous call so can't really be - // used to generate random numbers for most cases. It could be used to seed - // the PRNG, but that wouldn't help much since after inception the pseudo-random - // numbers could be predicted. - rng: { - let now_nanos = Duration::from_nanos(now_nanoseconds()).as_nanos(); - let mut seed = [0u8; 32]; - seed[..16].copy_from_slice(&now_nanos.to_be_bytes()); - seed[16..32].copy_from_slice(&now_nanos.to_be_bytes()); - ChaCha20Rng::from_seed(seed) - }, - + rng: None, time_warp: GovTimeWarp { delta_s: 0 }, } } @@ -206,14 +221,30 @@ impl Environment for CanisterEnv { self.time_warp = new_time_warp; } - fn random_u64(&mut self) -> u64 { - self.rng.next_u64() + fn random_u64(&mut self) -> Result { + match self.rng.as_mut() { + Some(rand) => Ok(rand.next_u64()), + None => Err(RngError::RngNotInitialized), + } + } + + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { + match self.rng.as_mut() { + Some(rand) => { + let mut bytes = [0u8; 32]; + rand.fill_bytes(&mut bytes); + Ok(bytes) + } + None => Err(RngError::RngNotInitialized), + } } - fn random_byte_array(&mut self) -> [u8; 32] { - let mut bytes = [0u8; 32]; - self.rng.fill_bytes(&mut bytes); - bytes + fn seed_rng(&mut self, seed: [u8; 32]) { + self.rng.replace(ChaCha20Rng::from_seed(seed)); + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + self.rng.as_ref().map(|rng| rng.get_seed()) } fn execute_nns_function( @@ -369,6 +400,7 @@ fn canister_init_(init_payload: ApiGovernanceProto) { init_payload.neurons.len() ); + schedule_seeding(Duration::from_nanos(0)); set_governance(Governance::new( InternalGovernanceProto::from(init_payload), Box::new(CanisterEnv::new()), @@ -411,6 +443,8 @@ fn canister_post_upgrade() { restored_state.neurons.len(), restored_state.xdr_conversion_rate, ); + + schedule_seeding(Duration::from_nanos(0)); set_governance(Governance::new_restored( restored_state, Box::new(CanisterEnv::new()), diff --git a/rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto b/rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto index b0f00357062..f8220f01c20 100644 --- a/rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto +++ b/rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto @@ -2415,6 +2415,10 @@ message Governance { // The summary of restore aging event. optional RestoreAgingSummary restore_aging_summary = 27; + + // Used to initialize an internal pseudorandom number generator. This gets replaced periodically using a secure + // source of randomness (from the platform) + optional bytes rng_seed = 28; } message XdrConversionRate { diff --git a/rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs b/rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs index 06897c2b5cd..cdbac10e5a4 100644 --- a/rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs +++ b/rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs @@ -3261,6 +3261,10 @@ pub struct Governance { /// The summary of restore aging event. #[prost(message, optional, tag = "27")] pub restore_aging_summary: ::core::option::Option, + /// Used to initialize an internal pseudorandom number generator. This gets replaced periodically using a secure + /// source of randomness (from the platform) + #[prost(bytes = "vec", optional, tag = "28")] + pub rng_seed: ::core::option::Option<::prost::alloc::vec::Vec>, } /// Nested message and enum types in `Governance`. pub mod governance { diff --git a/rs/nns/governance/src/governance.rs b/rs/nns/governance/src/governance.rs index 695919c3a6d..f560cb19ca5 100644 --- a/rs/nns/governance/src/governance.rs +++ b/rs/nns/governance/src/governance.rs @@ -105,7 +105,6 @@ use icp_ledger::{ }; use itertools::Itertools; use maplit::hashmap; -use mockall::automock; use registry_canister::{ mutations::do_add_node_operator::AddNodeOperatorPayload, pb::v1::NodeProvidersMonthlyXdrRewards, }; @@ -1451,8 +1450,23 @@ impl fmt::Display for RewardEvent { } } +#[derive(Debug)] +pub enum RngError { + RngNotInitialized, +} + +impl From for GovernanceError { + fn from(e: RngError) -> Self { + match e { + RngError::RngNotInitialized => GovernanceError::new_with_message( + ErrorType::Unavailable, + "Rng not initialized. Try again later.".to_string(), + ), + } + } +} + /// A general trait for the environment in which governance is running. -#[automock] #[async_trait] pub trait Environment: Send + Sync { /// Returns the current time, in seconds since the epoch. @@ -1465,12 +1479,18 @@ pub trait Environment: Send + Sync { /// Returns a random number. /// /// This number is the same in all replicas. - fn random_u64(&mut self) -> u64; + fn random_u64(&mut self) -> Result; /// Returns a random byte array with 32 bytes. /// /// This number is the same in all replicas. - fn random_byte_array(&mut self) -> [u8; 32]; + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError>; + + // Seed the random number generator. + fn seed_rng(&mut self, seed: [u8; 32]); + + // Get the current RNG seed (used in pre-upgrade) + fn get_rng_seed(&self) -> Option<[u8; 32]>; /// Executes a `ExecuteNnsFunction`. The standard implementation is /// expected to call out to another canister and eventually report the @@ -1770,7 +1790,8 @@ impl Governance { // memory, while others are stored in heap. "inactive" Neurons live in stable memory, while // the rest live in heap. - let (neurons, topic_followee_index, heap_governance_proto) = + // Note: We do not carry over the RNG seed in new governance, only in restored governance. + let (neurons, topic_followee_index, heap_governance_proto, _maybe_rng_seed) = split_governance_proto(governance_proto); assert!( @@ -1802,13 +1823,19 @@ impl Governance { /// Restores Governance after an upgrade from its persisted state. pub fn new_restored( governance_proto: GovernanceProto, - env: Box, + mut env: Box, ledger: Box, cmc: Box, ) -> Self { - let (heap_neurons, topic_followee_map, heap_governance_proto) = + let (heap_neurons, topic_followee_map, heap_governance_proto, maybe_rng_seed) = split_governance_proto(governance_proto); + // Carry over the previous rng seed to avoid race conditions in handling queued ingress + // messages that may require a functioning RNG. + if let Some(rng_seed) = maybe_rng_seed { + env.seed_rng(rng_seed); + } + Self { heap_data: heap_governance_proto, neuron_store: NeuronStore::new_restored((heap_neurons, topic_followee_map)), @@ -1829,14 +1856,26 @@ impl Governance { let neuron_store = std::mem::take(&mut self.neuron_store); let (neurons, heap_topic_followee_index) = neuron_store.take(); let heap_governance_proto = std::mem::take(&mut self.heap_data); - reassemble_governance_proto(neurons, heap_topic_followee_index, heap_governance_proto) + let rng_seed = self.env.get_rng_seed(); + reassemble_governance_proto( + neurons, + heap_topic_followee_index, + heap_governance_proto, + rng_seed, + ) } pub fn clone_proto(&self) -> GovernanceProto { let neurons = self.neuron_store.clone_neurons(); let heap_topic_followee_index = self.neuron_store.clone_topic_followee_index(); let heap_governance_proto = self.heap_data.clone(); - reassemble_governance_proto(neurons, heap_topic_followee_index, heap_governance_proto) + let rng_seed = self.env.get_rng_seed(); + reassemble_governance_proto( + neurons, + heap_topic_followee_index, + heap_governance_proto, + rng_seed, + ) } /// Validates that the underlying protobuf is well formed. @@ -2617,11 +2656,11 @@ impl Governance { } let created_timestamp_seconds = self.env.now(); - let child_nid = self.neuron_store.new_neuron_id(&mut *self.env); + let child_nid = self.neuron_store.new_neuron_id(&mut *self.env)?; let from_subaccount = parent_neuron.subaccount(); - let to_subaccount = Subaccount(self.env.random_byte_array()); + let to_subaccount = Subaccount(self.env.random_byte_array()?); // Make sure there isn't already a neuron with the same sub-account. if self.neuron_store.has_neuron_with_subaccount(to_subaccount) { @@ -3024,11 +3063,11 @@ impl Governance { )); } - let child_nid = self.neuron_store.new_neuron_id(&mut *self.env); + let child_nid = self.neuron_store.new_neuron_id(&mut *self.env)?; // use provided sub-account if any, otherwise generate a random one. let to_subaccount = match spawn.nonce { - None => Subaccount(self.env.random_byte_array()), + None => Subaccount(self.env.random_byte_array()?), Some(nonce_val) => { ledger::compute_neuron_staking_subaccount(child_controller, nonce_val) } @@ -3305,7 +3344,7 @@ impl Governance { ) })?; - let child_nid = self.neuron_store.new_neuron_id(&mut *self.env); + let child_nid = self.neuron_store.new_neuron_id(&mut *self.env)?; let from_subaccount = parent_neuron.subaccount(); // The account is derived from the new owner's principal so it can be found by @@ -4089,7 +4128,7 @@ impl Governance { "Reward node provider proposal must have a reward mode.", )), Some(RewardMode::RewardToNeuron(reward_to_neuron)) => { - let to_subaccount = Subaccount(self.env.random_byte_array()); + let to_subaccount = Subaccount(self.env.random_byte_array()?); let _block_height = self .ledger .transfer_funds( @@ -4100,7 +4139,7 @@ impl Governance { now, ) .await?; - let nid = self.neuron_store.new_neuron_id(&mut *self.env); + let nid = self.neuron_store.new_neuron_id(&mut *self.env)?; let dissolve_delay_seconds = std::cmp::min( reward_to_neuron.dissolve_delay_seconds, MAX_DISSOLVE_DELAY_SECONDS, @@ -6112,7 +6151,7 @@ impl Governance { controller: PrincipalId, claim_or_refresh: &ClaimOrRefresh, ) -> Result { - let nid = self.neuron_store.new_neuron_id(&mut *self.env); + let nid = self.neuron_store.new_neuron_id(&mut *self.env)?; let now = self.env.now(); let neuron = NeuronBuilder::new( nid, @@ -7816,7 +7855,9 @@ impl Governance { /// Picks a value at random in [00:00, 23:45] that is a multiple of 15 /// minutes past midnight. pub fn randomly_pick_swap_start(&mut self) -> GlobalTimeOfDay { - let time_of_day_seconds = self.env.random_u64() % ONE_DAY_SECONDS; + // It's not critical that we have perfect randomness here, so we can default to a fixed value + // for the edge case where the RNG is not initialized (which should never happen in practice). + let time_of_day_seconds = self.env.random_u64().unwrap_or(10_000) % ONE_DAY_SECONDS; // Round down to nearest multiple of 15 min. let remainder_seconds = time_of_day_seconds % (15 * 60); diff --git a/rs/nns/governance/src/heap_governance_data.rs b/rs/nns/governance/src/heap_governance_data.rs index c4647b36328..db053bc29ea 100644 --- a/rs/nns/governance/src/heap_governance_data.rs +++ b/rs/nns/governance/src/heap_governance_data.rs @@ -84,16 +84,24 @@ impl From for XdrConversionRatePb { } } +/// Converts a vector of u8s to array of length 32, which is the length needed for our rng seed. +/// If the array is the wrong size, this returns an error. +fn vec_to_array(v: Vec) -> Result<[u8; 32], String> { + <[u8; 32]>::try_from(v).map_err(|v| format!("Expected 32 bytes, got {}", v.len())) +} + /// Splits the governance proto (from UPGRADES_MEMORY) into HeapGovernanceData and neurons, because /// we have a dedicated struct NeuronStore owning the heap neurons. /// Does not guarantee round-trip equivalence between this and /// reassemble_governance_proto if the proto has fields that are None, as they might be filled in by default values. +#[allow(clippy::type_complexity)] pub fn split_governance_proto( governance_proto: GovernanceProto, ) -> ( BTreeMap, HashMap, HeapGovernanceData, + Option<[u8; 32]>, ) { // DO NOT USE THE .. CATCH-ALL SYNTAX HERE. // OTHERWISE, YOU WILL ALMOST CERTAINLY EXPERIENCE @@ -123,6 +131,7 @@ pub fn split_governance_proto( topic_followee_index, xdr_conversion_rate, restore_aging_summary, + rng_seed, } = governance_proto; let neuron_management_voting_period_seconds = @@ -136,6 +145,10 @@ pub fn split_governance_proto( panic!("Deserialization failed for XdrConversionRate: {}", err); }); + let rng_seed = rng_seed + .map(|seed| vec_to_array(seed).ok()) + .and_then(|seed| seed); + ( neurons, topic_followee_index, @@ -161,6 +174,7 @@ pub fn split_governance_proto( xdr_conversion_rate, restore_aging_summary, }, + rng_seed, ) } @@ -170,6 +184,7 @@ pub fn reassemble_governance_proto( neurons: BTreeMap, topic_followee_index: HashMap, heap_governance_proto: HeapGovernanceData, + rng_seed: Option<[u8; 32]>, ) -> GovernanceProto { // DO NOT USE THE .. CATCH-ALL SYNTAX HERE. // OTHERWISE, YOU WILL ALMOST CERTAINLY EXPERIENCE @@ -226,6 +241,7 @@ pub fn reassemble_governance_proto( topic_followee_index, xdr_conversion_rate: Some(xdr_conversion_rate), restore_aging_summary, + rng_seed: rng_seed.map(|seed| seed.to_vec()), } } @@ -278,6 +294,7 @@ mod tests { xdr_permyriad_per_icp: Some(50_000), }), restore_aging_summary: None, + rng_seed: Some(vec![1u8; 32]), } } @@ -285,11 +302,15 @@ mod tests { fn split_and_reassemble_equal() { let governance_proto = simple_governance_proto(); - let (heap_neurons, topic_followee_index, heap_governance_data) = + let (heap_neurons, topic_followee_index, heap_governance_data, rng_seed) = split_governance_proto(governance_proto.clone()); - let reassembled_governance_proto = - reassemble_governance_proto(heap_neurons, topic_followee_index, heap_governance_data); + let reassembled_governance_proto = reassemble_governance_proto( + heap_neurons, + topic_followee_index, + heap_governance_data, + rng_seed, + ); assert_eq!(reassembled_governance_proto, governance_proto); } @@ -301,10 +322,14 @@ mod tests { ..simple_governance_proto() }; - let (heap_neurons, topic_followee_index, heap_governance_data) = + let (heap_neurons, topic_followee_index, heap_governance_data, rng_seed) = split_governance_proto(governance_proto.clone()); - let reassembled_governance_proto = - reassemble_governance_proto(heap_neurons, topic_followee_index, heap_governance_data); + let reassembled_governance_proto = reassemble_governance_proto( + heap_neurons, + topic_followee_index, + heap_governance_data, + rng_seed, + ); assert_eq!( reassembled_governance_proto, @@ -322,7 +347,7 @@ mod tests { ..GovernanceProto::default() }; // split_governance_proto should return a HeapGovernanceData where the neuron_management_voting_period_seconds is 0 when given a default input - let (_, _, heap_governance_proto) = split_governance_proto(governance_proto); + let (_, _, heap_governance_proto, _) = split_governance_proto(governance_proto); assert_eq!( heap_governance_proto.neuron_management_voting_period_seconds, 48 * 60 * 60 diff --git a/rs/nns/governance/src/neuron_store.rs b/rs/nns/governance/src/neuron_store.rs index b0dccbdcd9d..9f586ddc65e 100644 --- a/rs/nns/governance/src/neuron_store.rs +++ b/rs/nns/governance/src/neuron_store.rs @@ -63,6 +63,7 @@ pub enum NeuronStoreError { principal_id: PrincipalId, neuron_id: NeuronId, }, + NeuronIdGenerationUnavailable, } impl NeuronStoreError { @@ -160,6 +161,13 @@ impl Display for NeuronStoreError { principal_id, neuron_id ) } + NeuronStoreError::NeuronIdGenerationUnavailable => { + write!( + f, + "Neuron ID generation is not available currently. \ + Likely due to uninitialized RNG." + ) + } } } } @@ -176,6 +184,7 @@ impl From for GovernanceError { NeuronStoreError::NeuronAlreadyExists(_) => ErrorType::PreconditionFailed, NeuronStoreError::InvalidData { .. } => ErrorType::PreconditionFailed, NeuronStoreError::NotAuthorizedToGetFullNeuron { .. } => ErrorType::NotAuthorized, + NeuronStoreError::NeuronIdGenerationUnavailable => ErrorType::Unavailable, }; GovernanceError::new_with_message(error_type, value.to_string()) } @@ -384,10 +393,11 @@ impl NeuronStore { self.clock.set_time_warp(new_time_warp); } - pub fn new_neuron_id(&self, env: &mut dyn Environment) -> NeuronId { + pub fn new_neuron_id(&self, env: &mut dyn Environment) -> Result { loop { let id = env .random_u64() + .map_err(|_| NeuronStoreError::NeuronIdGenerationUnavailable)? // Let there be no question that id was chosen // intentionally, not just 0 by default. .saturating_add(1); @@ -396,7 +406,7 @@ impl NeuronStore { let is_unique = !self.contains(neuron_id); if is_unique { - return neuron_id; + return Ok(neuron_id); } ic_cdk::println!( diff --git a/rs/nns/governance/src/pb/conversions.rs b/rs/nns/governance/src/pb/conversions.rs index 180b45faf15..aeefb93f420 100644 --- a/rs/nns/governance/src/pb/conversions.rs +++ b/rs/nns/governance/src/pb/conversions.rs @@ -3231,6 +3231,8 @@ impl From for pb::Governance { .collect(), xdr_conversion_rate: item.xdr_conversion_rate.map(|x| x.into()), restore_aging_summary: item.restore_aging_summary.map(|x| x.into()), + // This is not intended to be initialized from outside of canister. + rng_seed: None, } } } diff --git a/rs/nns/governance/src/test_utils.rs b/rs/nns/governance/src/test_utils.rs index 1eecd581c2e..9a258fbf008 100644 --- a/rs/nns/governance/src/test_utils.rs +++ b/rs/nns/governance/src/test_utils.rs @@ -1,5 +1,5 @@ use crate::{ - governance::{Environment, HeapGrowthPotential}, + governance::{Environment, HeapGrowthPotential, RngError}, pb::v1::{ExecuteNnsFunction, GovernanceError, OpenSnsTokenSwap}, }; use async_trait::async_trait; @@ -193,14 +193,20 @@ impl Environment for MockEnvironment { *self.now.lock().unwrap() } - fn random_u64(&mut self) -> u64 { + fn random_u64(&mut self) -> Result { unimplemented!(); } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { unimplemented!(); } + fn seed_rng(&mut self, _seed: [u8; 32]) {} + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + Some([0; 32]) + } + fn execute_nns_function( &self, _proposal_id: u64, diff --git a/rs/nns/governance/tests/degraded_mode.rs b/rs/nns/governance/tests/degraded_mode.rs index 6f0bf6e1e7c..f6b64037594 100644 --- a/rs/nns/governance/tests/degraded_mode.rs +++ b/rs/nns/governance/tests/degraded_mode.rs @@ -9,7 +9,8 @@ use ic_nns_common::pb::v1::NeuronId; use ic_nns_constants::GOVERNANCE_CANISTER_ID; use ic_nns_governance::{ governance::{ - Environment, Governance, HeapGrowthPotential, HEAP_SIZE_SOFT_LIMIT_IN_WASM32_PAGES, + Environment, Governance, HeapGrowthPotential, RngError, + HEAP_SIZE_SOFT_LIMIT_IN_WASM32_PAGES, }, pb::v1::{ governance_error::ErrorType, @@ -35,14 +36,22 @@ impl Environment for DegradedEnv { 111000222 } - fn random_u64(&mut self) -> u64 { - 4 // https://xkcd.com/221 + fn random_u64(&mut self) -> Result { + Ok(4) // https://xkcd.com/221 } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { unimplemented!() } + fn seed_rng(&mut self, _seed: [u8; 32]) { + todo!() + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + todo!() + } + fn execute_nns_function(&self, _: u64, _: &ExecuteNnsFunction) -> Result<(), GovernanceError> { unimplemented!() } diff --git a/rs/nns/governance/tests/fake.rs b/rs/nns/governance/tests/fake.rs index 6cf00fcc738..99c22ecb803 100644 --- a/rs/nns/governance/tests/fake.rs +++ b/rs/nns/governance/tests/fake.rs @@ -14,7 +14,7 @@ use ic_nns_constants::{ SNS_WASM_CANISTER_ID, }; use ic_nns_governance::{ - governance::{Environment, Governance, HeapGrowthPotential}, + governance::{Environment, Governance, HeapGrowthPotential, RngError}, pb::v1::{ manage_neuron, manage_neuron::NeuronIdOrSubaccount, manage_neuron_response, proposal, ExecuteNnsFunction, GovernanceError, ManageNeuron, ManageNeuronResponse, Motion, @@ -312,14 +312,22 @@ impl Environment for FakeDriver { self.state.try_lock().unwrap().now } - fn random_u64(&mut self) -> u64 { - self.state.try_lock().unwrap().rng.next_u64() + fn random_u64(&mut self) -> Result { + Ok(self.state.try_lock().unwrap().rng.next_u64()) } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { let mut bytes = [0u8; 32]; self.state.try_lock().unwrap().rng.fill_bytes(&mut bytes); - bytes + Ok(bytes) + } + + fn seed_rng(&mut self, _seed: [u8; 32]) { + todo!() + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + todo!() } fn execute_nns_function( diff --git a/rs/nns/governance/tests/fixtures/environment_fixture.rs b/rs/nns/governance/tests/fixtures/environment_fixture.rs index e74e947339d..1937722a93f 100644 --- a/rs/nns/governance/tests/fixtures/environment_fixture.rs +++ b/rs/nns/governance/tests/fixtures/environment_fixture.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use candid::{CandidType, Decode, Encode, Error}; use ic_base_types::CanisterId; use ic_nns_governance::{ - governance::{Environment, HeapGrowthPotential}, + governance::{Environment, HeapGrowthPotential, RngError}, pb::v1::{ExecuteNnsFunction, GovernanceError}, }; use ic_sns_root::GetSnsCanistersSummaryRequest; @@ -10,6 +10,7 @@ use ic_sns_swap::pb::v1::GetStateRequest; use ic_sns_wasm::pb::v1::{DeployNewSnsRequest, ListDeployedSnsesRequest}; use proptest::prelude::RngCore; use rand::rngs::StdRng; +use rand_chacha::ChaCha20Rng; use std::{ collections::VecDeque, sync::{Arc, Mutex}, @@ -46,7 +47,7 @@ where /// This state is used to respond to environment calls from Governance in a deterministic way. pub struct EnvironmentFixtureState { pub now: u64, - pub rng: StdRng, + pub rng: Option, pub observed_canister_calls: VecDeque, pub mocked_canister_replies: VecDeque, } @@ -145,18 +146,50 @@ impl Environment for EnvironmentFixture { self.environment_fixture_state.try_lock().unwrap().now } - fn random_u64(&mut self) -> u64 { - self.environment_fixture_state + fn random_u64(&mut self) -> Result { + match self + .environment_fixture_state .try_lock() .unwrap() .rng - .next_u64() + .as_mut() + { + Some(rand) => Ok(rand.next_u64()), + None => Err(RngError::RngNotInitialized), + } } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { + match self + .environment_fixture_state + .try_lock() + .unwrap() + .rng + .as_mut() + { + Some(rand) => { + let mut bytes = [0u8; 32]; + rand.fill_bytes(&mut bytes); + Ok(bytes) + } + // Kick the thing + None => Err(RngError::RngNotInitialized), + } + } + + fn seed_rng(&mut self, _seed: [u8; 32]) { unimplemented!() } + fn get_rng_seed(&self) -> Option<[u8; 32]> { + self.environment_fixture_state + .try_lock() + .unwrap() + .rng + .as_ref() + .map(|r| r.get_seed()) + } + fn execute_nns_function( &self, _proposal_id: u64, diff --git a/rs/nns/governance/tests/fixtures/mod.rs b/rs/nns/governance/tests/fixtures/mod.rs index 254bb67aae3..304a12aade9 100755 --- a/rs/nns/governance/tests/fixtures/mod.rs +++ b/rs/nns/governance/tests/fixtures/mod.rs @@ -20,7 +20,8 @@ use ic_nns_common::{ use ic_nns_constants::LEDGER_CANISTER_ID; use ic_nns_governance::{ governance::{ - governance_minting_account, neuron_subaccount, Environment, Governance, HeapGrowthPotential, + governance_minting_account, neuron_subaccount, Environment, Governance, + HeapGrowthPotential, RngError, }, governance_proto_builder::GovernanceProtoBuilder, pb::v1::{ @@ -37,6 +38,7 @@ use ic_nns_governance::{ }; use icp_ledger::{AccountIdentifier, Subaccount, Tokens}; use rand::{prelude::StdRng, RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; use std::{ collections::{BTreeMap, HashMap, VecDeque}, convert::{TryFrom, TryInto}, @@ -171,7 +173,7 @@ impl Default for EnvironmentBuilder { EnvironmentBuilder { environment_fixture_state: EnvironmentFixtureState { now: 0, - rng: StdRng::seed_from_u64(9539), + rng: Some(ChaCha20Rng::from_seed([1u8; 32])), observed_canister_calls: VecDeque::new(), mocked_canister_replies: VecDeque::new(), }, @@ -485,11 +487,11 @@ impl Environment for NNSFixture { self.nns_state.try_lock().unwrap().environment.now() } - fn random_u64(&mut self) -> u64 { + fn random_u64(&mut self) -> Result { self.nns_state.try_lock().unwrap().environment.random_u64() } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { self.nns_state .try_lock() .unwrap() @@ -497,6 +499,22 @@ impl Environment for NNSFixture { .random_byte_array() } + fn seed_rng(&mut self, seed: [u8; 32]) { + self.nns_state + .try_lock() + .unwrap() + .environment + .seed_rng(seed) + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + self.nns_state + .try_lock() + .unwrap() + .environment + .get_rng_seed() + } + fn execute_nns_function( &self, proposal_id: u64, @@ -706,18 +724,21 @@ impl NNS { self } + // Must be mut because clone_proto must be mut, but should not affect state pub(crate) fn get_state(&self) -> NNSState { + let accounts = self + .fixture + .nns_state + .try_lock() + .unwrap() + .ledger + .accounts + .clone(); + let governance_proto = self.governance.clone_proto(); NNSState { now: self.now(), - accounts: self - .fixture - .nns_state - .try_lock() - .unwrap() - .ledger - .accounts - .clone(), - governance_proto: self.governance.clone_proto(), + accounts, + governance_proto, latest_gc_num_proposals: self.governance.latest_gc_num_proposals, } } @@ -942,14 +963,22 @@ impl Environment for NNS { self.fixture.now() } - fn random_u64(&mut self) -> u64 { + fn random_u64(&mut self) -> Result { self.fixture.random_u64() } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { self.fixture.random_byte_array() } + fn seed_rng(&mut self, seed: [u8; 32]) { + self.fixture.seed_rng(seed) + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + unimplemented!() + } + fn execute_nns_function( &self, proposal_id: u64, diff --git a/rs/nns/governance/tests/governance.rs b/rs/nns/governance/tests/governance.rs index ba90494cab8..72aba3db672 100644 --- a/rs/nns/governance/tests/governance.rs +++ b/rs/nns/governance/tests/governance.rs @@ -46,7 +46,7 @@ use ic_nns_governance::{ test_data::{ CREATE_SERVICE_NERVOUS_SYSTEM, CREATE_SERVICE_NERVOUS_SYSTEM_WITH_MATCHED_FUNDING, }, - Environment, Governance, HeapGrowthPotential, + Environment, Governance, HeapGrowthPotential, RngError, EXECUTE_NNS_FUNCTION_PAYLOAD_LISTING_BYTES_MAX, MAX_DISSOLVE_DELAY_SECONDS, MAX_NEURON_AGE_FOR_AGE_BONUS, MAX_NUMBER_OF_PROPOSALS_WITH_BALLOTS, MIN_DISSOLVE_DELAY_FOR_VOTE_ELIGIBILITY_SECONDS, PROPOSAL_MOTION_TEXT_BYTES_MAX, @@ -465,7 +465,10 @@ fn check_proposal_status_after_voting_and_after_expiration( .map(|(neuron, i)| Neuron { id: Some(NeuronId { id: i }), controller: Some(principal(i)), - account: fake_driver.random_byte_array().to_vec(), + account: fake_driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), ..neuron }) .collect(); @@ -1204,7 +1207,10 @@ fn fixture_for_following() -> GovernanceProto { cached_neuron_stake_e8s: 1_000_000_000, // 10 ICP // One year dissolve_state: Some(neuron::DissolveState::DissolveDelaySeconds(31557600)), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), ..Default::default() }; GovernanceProto { @@ -1977,7 +1983,10 @@ fn fixture_for_manage_neuron() -> GovernanceProto { id: Some(NeuronId { id }), controller: Some(principal(id)), cached_neuron_stake_e8s: 1_000_000_000, // 10 ICP - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -2529,7 +2538,10 @@ fn fixture_two_neurons_second_is_bigger() -> GovernanceProto { id: Some(NeuronId { id: 1 }), controller: Some(principal(1)), cached_neuron_stake_e8s: 23, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), // One year dissolve_state: Some(neuron::DissolveState::DissolveDelaySeconds(31557600)), ..Default::default() @@ -2538,7 +2550,10 @@ fn fixture_two_neurons_second_is_bigger() -> GovernanceProto { id: Some(NeuronId { id: 2 }), controller: Some(principal(2)), cached_neuron_stake_e8s: 951, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), // One year dissolve_state: Some(neuron::DissolveState::DissolveDelaySeconds(31557600)), ..Default::default() @@ -3437,7 +3452,10 @@ fn compute_maturities( controller: Some(principal(i as u64)), cached_neuron_stake_e8s: *stake_e8s, dissolve_state: NOTDISSOLVING_MIN_DISSOLVE_DELAY_TO_VOTE, - account: fake_driver.random_byte_array().to_vec(), + account: fake_driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), ..Default::default() }) .collect(); @@ -3899,7 +3917,10 @@ fn fixture_for_approve_kyc() -> GovernanceProto { id: Some(NeuronId { id: 1 }), controller: Some(principal1), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), kyc_verified: false, dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, @@ -3909,7 +3930,10 @@ fn fixture_for_approve_kyc() -> GovernanceProto { id: Some(NeuronId { id: 2 }), controller: Some(principal2), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), kyc_verified: false, dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, @@ -3919,7 +3943,10 @@ fn fixture_for_approve_kyc() -> GovernanceProto { id: Some(NeuronId { id: 3 }), controller: Some(principal2), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), kyc_verified: false, dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, @@ -3929,7 +3956,10 @@ fn fixture_for_approve_kyc() -> GovernanceProto { id: Some(NeuronId { id: 4 }), controller: Some(principal3), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), kyc_verified: false, dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, @@ -4101,7 +4131,10 @@ fn test_get_neuron_ids_by_principal() { let neuron_a = Neuron { id: Some(NeuronId { id: 1 }), controller: Some(principal1), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -4109,7 +4142,10 @@ fn test_get_neuron_ids_by_principal() { let neuron_b = Neuron { id: Some(NeuronId { id: 2 }), controller: Some(principal2), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -4117,7 +4153,10 @@ fn test_get_neuron_ids_by_principal() { let neuron_c = Neuron { id: Some(NeuronId { id: 3 }), controller: Some(principal2), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -4126,7 +4165,10 @@ fn test_get_neuron_ids_by_principal() { id: Some(NeuronId { id: 4 }), controller: Some(principal2), hot_keys: vec![principal4], - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -5623,7 +5665,7 @@ fn test_neuron_spawn_with_subaccount() { driver.advance_time_by(1); // Nonce used for spawn (given as input). - let nonce_spawn = driver.random_u64(); + let nonce_spawn = driver.random_u64().expect("Could not get random number"); let child_nid = gov .spawn_neuron( @@ -7686,7 +7728,7 @@ fn test_filter_proposals_neuron_visibility() { controller: Some(principal1), hot_keys: vec![principal_hot], cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -7695,7 +7737,7 @@ fn test_filter_proposals_neuron_visibility() { id: Some(NeuronId { id: 2 }), controller: Some(principal2), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -7704,7 +7746,7 @@ fn test_filter_proposals_neuron_visibility() { id: Some(NeuronId { id: 3 }), controller: Some(principal(3)), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), followees: hashmap! { Topic::NeuronManagement as i32 => neuron::Followees { followees: vec![NeuronId { id: 1 }], @@ -7780,7 +7822,7 @@ fn test_filter_proposals_include_all_manage_neuron_ignores_visibility() { id: Some(NeuronId { id: 1 }), controller: Some(principal1), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -7789,7 +7831,7 @@ fn test_filter_proposals_include_all_manage_neuron_ignores_visibility() { id: Some(NeuronId { id: 2 }), controller: Some(principal2), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -7798,7 +7840,7 @@ fn test_filter_proposals_include_all_manage_neuron_ignores_visibility() { id: Some(NeuronId { id: 3 }), controller: Some(principal(3)), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), followees: hashmap! { Topic::NeuronManagement as i32 => neuron::Followees { followees: vec![NeuronId { id: 1 }], @@ -7897,7 +7939,7 @@ fn test_filter_proposals_by_status() { id: Some(NeuronId { id: 1 }), controller: Some(principal1), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -7997,7 +8039,7 @@ fn test_filter_proposals_by_reward_status() { id: Some(NeuronId { id: 1 }), controller: Some(principal1), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -8088,7 +8130,7 @@ fn test_filter_proposals_excluding_topics() { id: Some(NeuronId { id: 1 }), controller: Some(principal1), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -8184,7 +8226,7 @@ fn test_filter_proposal_ballots() { controller: Some(principal1), hot_keys: vec![principal_hot], cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -8194,7 +8236,7 @@ fn test_filter_proposal_ballots() { id: Some(NeuronId { id: 2 }), controller: Some(principal2), cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -8290,7 +8332,7 @@ async fn test_make_proposal_message() { controller: Some(principal1), hot_keys: vec![], cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::DissolveDelaySeconds(MIN_DISSOLVE_DELAY_FOR_VOTE_ELIGIBILITY_SECONDS)), ..Default::default() }, @@ -8373,7 +8415,7 @@ fn test_omit_large_fields() { controller: Some(principal1), hot_keys: vec![], cached_neuron_stake_e8s: 10 * E8, - account: driver.random_byte_array().to_vec(), + account: driver.random_byte_array().expect("Could not get random byte array").to_vec(), dissolve_state: Some(DissolveState::WhenDissolvedTimestampSeconds(0)), aging_since_timestamp_seconds: u64::MAX, ..Default::default() @@ -10857,7 +10899,10 @@ async fn test_known_neurons() { let neurons = vec![ Neuron { id: Some(NeuronId { id: 1 }), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), controller: Some(principal(1)), cached_neuron_stake_e8s: 100_000_000, dissolve_state: Some(DissolveState::DissolveDelaySeconds( @@ -10867,7 +10912,10 @@ async fn test_known_neurons() { }, Neuron { id: Some(NeuronId { id: 2 }), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), controller: Some(principal(2)), cached_neuron_stake_e8s: 100_000_000, dissolve_state: Some(DissolveState::DissolveDelaySeconds( @@ -10877,7 +10925,10 @@ async fn test_known_neurons() { }, Neuron { id: Some(NeuronId { id: 3 }), - account: driver.random_byte_array().to_vec(), + account: driver + .random_byte_array() + .expect("Could not get random byte array") + .to_vec(), controller: Some(principal(3)), cached_neuron_stake_e8s: 100_000_000_000, dissolve_state: Some(DissolveState::DissolveDelaySeconds( @@ -11154,14 +11205,22 @@ impl Environment for MockEnvironment<'_> { DEFAULT_TEST_START_TIMESTAMP_SECONDS } - fn random_u64(&mut self) -> u64 { - RANDOM_U64 + fn random_u64(&mut self) -> Result { + Ok(RANDOM_U64) } - fn random_byte_array(&mut self) -> [u8; 32] { + fn random_byte_array(&mut self) -> Result<[u8; 32], RngError> { panic!("Unexpected call to Environment::random_byte_array"); } + fn seed_rng(&mut self, _seed: [u8; 32]) { + unimplemented!() + } + + fn get_rng_seed(&self) -> Option<[u8; 32]> { + unimplemented!() + } + fn execute_nns_function( &self, _proposal_id: u64, diff --git a/rs/nns/integration_tests/src/node_provider_remuneration.rs b/rs/nns/integration_tests/src/node_provider_remuneration.rs index 33f4d3ae7a7..346efcd6d83 100644 --- a/rs/nns/integration_tests/src/node_provider_remuneration.rs +++ b/rs/nns/integration_tests/src/node_provider_remuneration.rs @@ -186,7 +186,7 @@ fn test_list_node_provider_rewards() { for _ in 0..12 { // Assert that advancing time by a month triggers an automated monthly NP reward event state_machine.advance_time(Duration::from_secs(ONE_MONTH_SECONDS + 1)); - state_machine.advance_time(Duration::from_secs(60)); + state_machine.tick(); state_machine.tick(); let rewards = nns_get_most_recent_monthly_node_provider_rewards(&state_machine).unwrap();