diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index b53ab0a07c4..ea53f6e45db 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -28,6 +28,7 @@ use ic_replicated_state::{ }; use ic_system_api::InstructionLimits; use ic_types::{ + consensus::ecdsa::QuadrupleId, crypto::canister_threshold_sig::MasterEcdsaPublicKey, ingress::{IngressState, IngressStatus}, messages::{CanisterMessage, Ingress, MessageId, StopCanisterContext}, @@ -50,6 +51,8 @@ mod round_schedule; use crate::util::debug_assert_or_critical_error; pub use round_schedule::RoundSchedule; use round_schedule::*; +mod tecdsa; +use tecdsa::*; /// Only log potentially spammy messages this often (in rounds). With a block /// rate around 1.0, this will result in logging about once every 10 minutes. @@ -1390,6 +1393,7 @@ impl Scheduler for SchedulerImpl { mut state: ReplicatedState, randomness: Randomness, ecdsa_subnet_public_keys: BTreeMap, + ecdsa_quadruple_ids: BTreeMap>, current_round: ExecutionRound, current_round_type: ExecutionRoundType, registry_settings: &RegistryExecutionSettings, @@ -1653,6 +1657,19 @@ impl Scheduler for SchedulerImpl { &ecdsa_subnet_public_keys, ); + // Update [`SignWithEcdsaContext`]s by assigning randomness and matching quadruples. + update_sign_with_ecdsa_contexts( + current_round, + ecdsa_quadruple_ids, + &mut state + .metadata + .subnet_call_context_manager + .sign_with_ecdsa_contexts, + &mut csprng, + registry_settings, + self.metrics.as_ref(), + ); + // Finalization. { let _timer = self.metrics.round_finalization_duration.start_timer(); diff --git a/rs/execution_environment/src/scheduler/scheduler_metrics.rs b/rs/execution_environment/src/scheduler/scheduler_metrics.rs index 63385a60f9e..6a7ff0da889 100644 --- a/rs/execution_environment/src/scheduler/scheduler_metrics.rs +++ b/rs/execution_environment/src/scheduler/scheduler_metrics.rs @@ -69,6 +69,7 @@ pub(super) struct SchedulerMetrics { pub(super) round_postponed_raw_rand_queue: ScopedMetrics, pub(super) round_subnet_queue: ScopedMetrics, pub(super) round_scheduling_duration: Histogram, + pub(super) round_update_sign_with_ecdsa_contexts_duration: Histogram, pub(super) round_inner: ScopedMetrics, pub(super) round_inner_heartbeat_overhead_duration: Histogram, pub(super) round_inner_iteration: ScopedMetrics, @@ -395,6 +396,11 @@ impl SchedulerMetrics { "The duration of execution round scheduling in seconds.", metrics_registry, ), + round_update_sign_with_ecdsa_contexts_duration: duration_histogram( + "execution_round_update_sign_with_ecdsa_contexts_duration_seconds", + "The duration of updating sign with ecdsa contexts in seconds.", + metrics_registry, + ), round_inner: ScopedMetrics { duration: duration_histogram( "execution_round_inner_duration_seconds", diff --git a/rs/execution_environment/src/scheduler/tecdsa.rs b/rs/execution_environment/src/scheduler/tecdsa.rs new file mode 100644 index 00000000000..9f4405d37df --- /dev/null +++ b/rs/execution_environment/src/scheduler/tecdsa.rs @@ -0,0 +1,280 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use ic_crypto_prng::Csprng; +use ic_ic00_types::EcdsaKeyId; +use ic_interfaces::execution_environment::RegistryExecutionSettings; +use ic_replicated_state::metadata_state::subnet_call_context_manager::SignWithEcdsaContext; +use ic_types::{consensus::ecdsa::QuadrupleId, messages::CallbackId, ExecutionRound, Height}; +use rand::RngCore; + +use super::SchedulerMetrics; + +/// Update [`SignWithEcdsaContext`]s by assigning randomness and matching quadruples. +pub(crate) fn update_sign_with_ecdsa_contexts( + current_round: ExecutionRound, + ecdsa_quadruple_ids: BTreeMap>, + contexts: &mut BTreeMap, + csprng: &mut Csprng, + registry_settings: &RegistryExecutionSettings, + metrics: &SchedulerMetrics, +) { + let _timer = metrics + .round_update_sign_with_ecdsa_contexts_duration + .start_timer(); + + // Assign a random nonce to the context in the round immediately subsequent to its successful + // match with a quadruple. + for context in contexts.values_mut() { + if context.nonce.is_none() + && context + .matched_quadruple + .as_ref() + .is_some_and(|(_, height)| height.get() + 1 == current_round.get()) + { + let mut nonce = [0u8; 32]; + csprng.fill_bytes(&mut nonce); + context.nonce = Some(nonce); + } + } + + // Match up to the maximum number of contexts per key ID to delivered quadruples. + let max_ongoing_signatures = registry_settings.quadruples_to_create_in_advance as usize; + for (key_id, quadruple_ids) in ecdsa_quadruple_ids { + match_quadruples_by_key_id( + key_id, + quadruple_ids, + contexts, + max_ongoing_signatures, + Height::from(current_round.get()), + ); + } +} + +/// Match up to `max_ongoing_signatures` quadruple IDs to unmatched sign with ecdsa contexts +/// of the given `key_id`. +fn match_quadruples_by_key_id( + key_id: EcdsaKeyId, + mut quadruple_ids: BTreeSet, + contexts: &mut BTreeMap, + max_ongoing_signatures: usize, + height: Height, +) { + // Remove and count already matched quadruples. + let mut matched = 0; + for (quadruple_id, _) in contexts + .values() + .filter(|context| context.key_id == key_id) + .flat_map(|context| context.matched_quadruple.as_ref()) + { + debug_assert_eq!(Some(&key_id), quadruple_id.key_id()); + quadruple_ids.remove(quadruple_id); + matched += 1; + } + + // Assign quadruples to unmatched contexts until `max_ongoing_signatures` is reached. + for context in contexts + .values_mut() + .filter(|context| context.matched_quadruple.is_none() && context.key_id == key_id) + { + if matched >= max_ongoing_signatures { + break; + } + let Some(quadruple_id) = quadruple_ids.pop_first() else { + break; + }; + debug_assert_eq!(Some(&key_id), quadruple_id.key_id()); + context.matched_quadruple = Some((quadruple_id, height)); + matched += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::scheduler::tests::make_key_id; + use ic_test_utilities::{mock_time, types::messages::RequestBuilder}; + + fn fake_context( + id: u64, + key_id: EcdsaKeyId, + matched_quadruple: Option<(u64, Height)>, + ) -> (CallbackId, SignWithEcdsaContext) { + ( + CallbackId::from(id), + SignWithEcdsaContext { + request: RequestBuilder::new().build(), + key_id: key_id.clone(), + pseudo_random_id: [id as u8; 32], + message_hash: [0; 32], + derivation_path: vec![], + batch_time: mock_time(), + matched_quadruple: matched_quadruple + .map(|(id, h)| (QuadrupleId(id, Some(key_id)), h)), + nonce: None, + }, + ) + } + + fn match_quadruples_basic_test( + key_id: EcdsaKeyId, + quadruple_ids: BTreeSet, + mut contexts: BTreeMap, + max_ongoing_signatures: usize, + height: Height, + cutoff: u64, + ) { + match_quadruples_by_key_id( + key_id, + quadruple_ids, + &mut contexts, + max_ongoing_signatures, + height, + ); + + // All contexts up until the cut-off point should have been matched at the given height, + // the remaining contexts should remain unmatched. + contexts.into_iter().for_each(|(id, context)| { + if id.get() <= cutoff { + assert!(context + .matched_quadruple + .is_some_and(|(qid, h)| qid.id() == id.get() && h == height)) + } else { + assert!(context.matched_quadruple.is_none()) + } + }); + } + + #[test] + fn test_match_quadruples_doesnt_match_other_key_ids() { + let key_id1 = make_key_id(1); + let key_id2 = make_key_id(2); + // 2 quadruples for key 1 + let ids = BTreeSet::from_iter((1..3).map(|i| QuadrupleId(i, Some(key_id1.clone())))); + // 3 contexts for key 2 + let contexts = BTreeMap::from_iter((1..4).map(|i| fake_context(i, key_id2.clone(), None))); + // No contexts should be matched + match_quadruples_basic_test(key_id1, ids, contexts, 5, Height::from(1), 0); + } + + #[test] + fn test_match_quadruples_doesnt_match_more_than_delivered() { + let key_id = make_key_id(1); + // 2 quadruples for key 1 + let ids = BTreeSet::from_iter((1..3).map(|i| QuadrupleId(i, Some(key_id.clone())))); + // 4 contexts for key 1 + let contexts = BTreeMap::from_iter((1..5).map(|i| fake_context(i, key_id.clone(), None))); + // The first 2 contexts should be matched + match_quadruples_basic_test(key_id, ids, contexts, 5, Height::from(1), 2); + } + + #[test] + fn test_match_quadruples_doesnt_match_more_than_requested() { + let key_id = make_key_id(1); + // 3 quadruples for key 1 + let ids = BTreeSet::from_iter((1..4).map(|i| QuadrupleId(i, Some(key_id.clone())))); + // 2 contexts for key 1 + let contexts = BTreeMap::from_iter((1..3).map(|i| fake_context(i, key_id.clone(), None))); + // The first 2 contexts should be matched + match_quadruples_basic_test(key_id, ids, contexts, 5, Height::from(1), 2); + } + + #[test] + fn test_match_quadruples_respects_max() { + let key_id = make_key_id(1); + // 4 quadruples for key 1 + let ids = BTreeSet::from_iter((1..5).map(|i| QuadrupleId(i, Some(key_id.clone())))); + // 4 contexts for key 1 + let contexts = BTreeMap::from_iter((1..5).map(|i| fake_context(i, key_id.clone(), None))); + // The first 3 contexts (up to max_ongoing_signatures) should be matched + match_quadruples_basic_test(key_id, ids, contexts, 3, Height::from(1), 3); + } + + #[test] + fn test_match_quadruples_respects_max_per_key_id() { + let key_id1 = make_key_id(1); + let key_id2 = make_key_id(2); + // 4 quadruples for key 1 + let ids = BTreeSet::from_iter([ + QuadrupleId(1, Some(key_id1.clone())), + QuadrupleId(3, Some(key_id1.clone())), + QuadrupleId(4, Some(key_id1.clone())), + QuadrupleId(5, Some(key_id1.clone())), + ]); + let height = Height::from(1); + // 4 contexts for key 1 and 1 context for key 2 + let contexts = BTreeMap::from_iter([ + fake_context(1, key_id1.clone(), None), + fake_context(2, key_id2, Some((2, height))), + fake_context(3, key_id1.clone(), None), + fake_context(4, key_id1.clone(), None), + fake_context(5, key_id1.clone(), None), + ]); + // With max_ongoing_signatures = 3 per key, the first 4 contexts should be matched in total. + match_quadruples_basic_test(key_id1, ids, contexts, 3, height, 4); + } + + #[test] + fn test_matched_quadruples_arent_matched_again() { + let key_id = make_key_id(1); + // 4 quadruples for key 1 + let ids = BTreeSet::from_iter((1..5).map(|i| QuadrupleId(i, Some(key_id.clone())))); + let height = Height::from(1); + // 5 contexts for key 1, 2 are already matched + let contexts = BTreeMap::from_iter([ + fake_context(1, key_id.clone(), None), + fake_context(2, key_id.clone(), Some((2, height))), + fake_context(3, key_id.clone(), None), + fake_context(4, key_id.clone(), Some((4, height))), + fake_context(5, key_id.clone(), None), + ]); + // The first 4 contexts should be matched + match_quadruples_basic_test(key_id, ids, contexts, 5, height, 4); + } + + #[test] + fn test_matched_quadruples_arent_overwritten() { + let key_id = make_key_id(1); + // 4 quadruples for key 1 + let ids = BTreeSet::from_iter((3..7).map(|i| QuadrupleId(i, Some(key_id.clone())))); + let height = Height::from(2); + // 4 contexts for key 1, the first 3 are already matched + let contexts = BTreeMap::from_iter([ + fake_context(1, key_id.clone(), Some((1, height))), + fake_context(2, key_id.clone(), Some((2, height))), + fake_context(3, key_id.clone(), Some((3, height))), + fake_context(4, key_id.clone(), None), + ]); + // The first 4 contexts should be matched + match_quadruples_basic_test(key_id, ids, contexts, 5, height, 4); + } + + #[test] + fn test_match_quadruples_doesnt_update_height() { + let key_id = make_key_id(1); + // 2 quadruples for key 1 + let ids = BTreeSet::from_iter([ + QuadrupleId(5, Some(key_id.clone())), + QuadrupleId(6, Some(key_id.clone())), + ]); + // 2 contexts for key 1, the first was already matched to the first quadruple + // in the previous round. + let mut contexts = BTreeMap::from_iter([ + fake_context(2, key_id.clone(), Some((5, Height::from(2)))), + fake_context(4, key_id.clone(), None), + ]); + // Match them at height 3 + match_quadruples_by_key_id(key_id.clone(), ids, &mut contexts, 5, Height::from(3)); + + // The first context should still be matched at the height of the previous round (height 2). + let first_context = contexts.pop_first().unwrap().1; + assert!(first_context.matched_quadruple.is_some_and(|(qid, h)| { + qid == QuadrupleId(5, Some(key_id.clone())) && h == Height::from(2) + })); + + // The second context should have been matched to the second quadruple at height 3. + let second_context = contexts.pop_first().unwrap().1; + assert!(second_context.matched_quadruple.is_some_and(|(qid, h)| { + qid == QuadrupleId(6, Some(key_id.clone())) && h == Height::from(3) + })); + } +} diff --git a/rs/execution_environment/src/scheduler/test_utilities.rs b/rs/execution_environment/src/scheduler/test_utilities.rs index 3e44989f56b..ac7a6ced0c6 100644 --- a/rs/execution_environment/src/scheduler/test_utilities.rs +++ b/rs/execution_environment/src/scheduler/test_utilities.rs @@ -49,6 +49,7 @@ use ic_test_utilities::{ }; use ic_test_utilities_execution_environment::{generate_subnets, test_registry_settings}; use ic_types::{ + consensus::ecdsa::QuadrupleId, crypto::{canister_threshold_sig::MasterEcdsaPublicKey, AlgorithmId}, ingress::{IngressState, IngressStatus}, messages::{CallContextId, Ingress, MessageId, Request, RequestOrResponse, Response}, @@ -109,6 +110,8 @@ pub(crate) struct SchedulerTest { metrics_registry: MetricsRegistry, // ECDSA subnet public keys. ecdsa_subnet_public_keys: BTreeMap, + // ECDSA quadruple IDs. + ecdsa_quadruple_ids: BTreeMap>, } impl std::fmt::Debug for SchedulerTest { @@ -470,6 +473,7 @@ impl SchedulerTest { state, Randomness::from([0; 32]), self.ecdsa_subnet_public_keys.clone(), + self.ecdsa_quadruple_ids.clone(), self.round, round_type, self.registry_settings(), @@ -588,6 +592,13 @@ impl SchedulerTest { .cycles_account_manager .memory_cost(bytes, duration, self.subnet_size()) } + + pub(crate) fn deliver_quadruple_ids( + &mut self, + ecdsa_quadruple_ids: BTreeMap>, + ) { + self.ecdsa_quadruple_ids = ecdsa_quadruple_ids; + } } /// A builder for `SchedulerTest`. @@ -605,7 +616,7 @@ pub(crate) struct SchedulerTestBuilder { rate_limiting_of_heap_delta: bool, deterministic_time_slicing: bool, log: ReplicaLogger, - ecdsa_key: Option, + ecdsa_keys: Vec, metrics_registry: MetricsRegistry, } @@ -628,7 +639,7 @@ impl Default for SchedulerTestBuilder { rate_limiting_of_heap_delta: false, deterministic_time_slicing: false, log: no_op_logger(), - ecdsa_key: None, + ecdsa_keys: vec![], metrics_registry: MetricsRegistry::new(), } } @@ -685,11 +696,15 @@ impl SchedulerTestBuilder { pub fn with_ecdsa_key(self, ecdsa_key: EcdsaKeyId) -> Self { Self { - ecdsa_key: Some(ecdsa_key), + ecdsa_keys: vec![ecdsa_key], ..self } } + pub fn with_ecdsa_keys(self, ecdsa_keys: Vec) -> Self { + Self { ecdsa_keys, ..self } + } + pub fn with_batch_time(self, batch_time: Time) -> Self { Self { batch_time, ..self } } @@ -715,7 +730,7 @@ impl SchedulerTestBuilder { state.metadata.batch_time = self.batch_time; let config = SubnetConfig::new(self.subnet_type).cycles_account_manager_config; - if let Some(ecdsa_key) = &self.ecdsa_key { + for ecdsa_key in &self.ecdsa_keys { state .metadata .network_topology @@ -731,7 +746,7 @@ impl SchedulerTestBuilder { .insert(ecdsa_key.clone()); } let ecdsa_subnet_public_keys: BTreeMap = self - .ecdsa_key + .ecdsa_keys .into_iter() .map(|key| { ( @@ -834,6 +849,7 @@ impl SchedulerTestBuilder { registry_settings: self.registry_settings, metrics_registry: self.metrics_registry, ecdsa_subnet_public_keys, + ecdsa_quadruple_ids: BTreeMap::new(), } } } diff --git a/rs/execution_environment/src/scheduler/tests.rs b/rs/execution_environment/src/scheduler/tests.rs index 41e0342f246..55de3064096 100644 --- a/rs/execution_environment/src/scheduler/tests.rs +++ b/rs/execution_environment/src/scheduler/tests.rs @@ -40,12 +40,15 @@ use ic_test_utilities_metrics::{ fetch_counter, fetch_gauge, fetch_gauge_vec, fetch_histogram_stats, fetch_int_gauge, fetch_int_gauge_vec, metric_vec, HistogramStats, }; -use ic_types::messages::{ - CallbackId, Payload, RejectContext, Response, StopCanisterCallId, MAX_RESPONSE_COUNT_BYTES, -}; use ic_types::methods::SystemMethod; use ic_types::methods::WasmMethod; use ic_types::time::expiry_time_from_now; +use ic_types::{ + messages::{ + CallbackId, Payload, RejectContext, Response, StopCanisterCallId, MAX_RESPONSE_COUNT_BYTES, + }, + Height, +}; use ic_types::{time::UNIX_EPOCH, ComputeAllocation, Cycles, NumBytes}; use ic_types_test_utils::ids::user_test_id; use ic_universal_canister::{call_args, wasm, UNIVERSAL_CANISTER_WASM}; @@ -4841,6 +4844,168 @@ fn test_is_next_method_added_to_task_queue() { assert_eq!(heartbeat_and_timer_canister_ids, BTreeSet::from([canister])); } +pub(crate) fn make_key_id(id: u64) -> EcdsaKeyId { + EcdsaKeyId::from_str(&format!("Secp256k1:key_{:?}", id)).unwrap() +} + +fn inject_ecdsa_signing_request(test: &mut SchedulerTest, key_id: &EcdsaKeyId) { + let canister_id = test.create_canister(); + + let payload = Encode!(&SignWithECDSAArgs { + message_hash: [0; 32], + derivation_path: DerivationPath::new(Vec::new()), + key_id: key_id.clone() + }) + .unwrap(); + + test.inject_call_to_ic00( + Method::SignWithECDSA, + payload.clone(), + test.ecdsa_signature_fee(), + canister_id, + InputQueueType::RemoteSubnet, + ); +} + +#[test] +fn test_sign_with_ecdsa_contexts_are_not_updated_without_quadruples() { + let key_id = make_key_id(0); + let mut test = SchedulerTestBuilder::new() + .with_ecdsa_key(key_id.clone()) + .build(); + + inject_ecdsa_signing_request(&mut test, &key_id); + + // Check that nonce isn't set in the following round + for _ in 0..2 { + test.execute_round(ExecutionRoundType::OrdinaryRound); + + let sign_with_ecdsa_context = &test + .state() + .sign_with_ecdsa_contexts() + .values() + .next() + .expect("Context should exist"); + + // Check that quadruple and nonce are none + assert!(sign_with_ecdsa_context.nonce.is_none()); + assert!(sign_with_ecdsa_context.matched_quadruple.is_none()); + } +} + +#[test] +fn test_sign_with_ecdsa_contexts_are_updated_with_quadruples() { + let key_id = make_key_id(0); + let mut test = SchedulerTestBuilder::new() + .with_ecdsa_key(key_id.clone()) + .build(); + let quadruple_id = QuadrupleId(0, Some(key_id.clone())); + let quadruple_ids = BTreeSet::from_iter([quadruple_id.clone()]); + + inject_ecdsa_signing_request(&mut test, &key_id); + test.deliver_quadruple_ids(BTreeMap::from_iter([(key_id, quadruple_ids)])); + + test.execute_round(ExecutionRoundType::OrdinaryRound); + let sign_with_ecdsa_context = &test + .state() + .sign_with_ecdsa_contexts() + .values() + .next() + .expect("Context should exist"); + + let expected_height = Height::from(test.last_round().get()); + + // Check that quadruple was matched + assert_eq!( + sign_with_ecdsa_context.matched_quadruple, + Some((quadruple_id.clone(), expected_height)) + ); + // Check that nonce is still none + assert!(sign_with_ecdsa_context.nonce.is_none()); + + test.execute_round(ExecutionRoundType::OrdinaryRound); + let sign_with_ecdsa_context = &test + .state() + .sign_with_ecdsa_contexts() + .values() + .next() + .expect("Context should exist"); + + // Check that quadruple is still matched + assert_eq!( + sign_with_ecdsa_context.matched_quadruple, + Some((quadruple_id, expected_height)) + ); + // Check that nonce is set + let nonce = sign_with_ecdsa_context.nonce; + assert!(nonce.is_some()); + + test.execute_round(ExecutionRoundType::OrdinaryRound); + let sign_with_ecdsa_context = &test + .state() + .sign_with_ecdsa_contexts() + .values() + .next() + .expect("Context should exist"); + + // Check that nonce wasn't changed + let nonce = sign_with_ecdsa_context.nonce; + assert_eq!(sign_with_ecdsa_context.nonce, nonce); +} + +#[test] +fn test_sign_with_ecdsa_contexts_are_matched_under_multiple_keys() { + let key_ids: Vec<_> = (0..3).map(make_key_id).collect(); + let mut test = SchedulerTestBuilder::new() + .with_ecdsa_keys(key_ids.clone()) + .build(); + + // Deliver 2 quadruples for the first key, 1 for the second, 0 for the third + let quadruple_ids0 = BTreeSet::from_iter([ + QuadrupleId(0, Some(key_ids[0].clone())), + QuadrupleId(1, Some(key_ids[0].clone())), + ]); + let quadruple_ids1 = BTreeSet::from_iter([QuadrupleId(2, Some(key_ids[1].clone()))]); + let quadruple_id_map = BTreeMap::from_iter([ + (key_ids[0].clone(), quadruple_ids0.clone()), + (key_ids[1].clone(), quadruple_ids1.clone()), + ]); + test.deliver_quadruple_ids(quadruple_id_map); + + // Inject 3 contexts requesting the third, second and first key in order + for i in (0..3).rev() { + inject_ecdsa_signing_request(&mut test, &key_ids[i]) + } + + // Execute two rounds + for _ in 0..2 { + test.execute_round(ExecutionRoundType::OrdinaryRound); + } + + let sign_with_ecdsa_contexts = &test.state().sign_with_ecdsa_contexts(); + + // First context (requesting key 3) should be unmatched + let context0 = sign_with_ecdsa_contexts.get(&CallbackId::from(0)).unwrap(); + assert!(context0.nonce.is_none()); + assert!(context0.matched_quadruple.is_none()); + + // Remaining contexts should have been matched + let expected_height = Height::from(test.last_round().get() - 1); + let context1 = sign_with_ecdsa_contexts.get(&CallbackId::from(1)).unwrap(); + assert!(context1.nonce.is_some()); + assert_eq!( + context1.matched_quadruple, + Some((quadruple_ids1.first().unwrap().clone(), expected_height)) + ); + + let context2 = sign_with_ecdsa_contexts.get(&CallbackId::from(2)).unwrap(); + assert!(context2.nonce.is_some()); + assert_eq!( + context2.matched_quadruple, + Some((quadruple_ids0.first().unwrap().clone(), expected_height)) + ); +} + #[test] fn clean_in_progress_raw_rand_request_from_subnet_call_context_manager() { let canister_id = canister_test_id(2); diff --git a/rs/interfaces/src/execution_environment.rs b/rs/interfaces/src/execution_environment.rs index 9f1fb53478c..0cf2ea11e0a 100644 --- a/rs/interfaces/src/execution_environment.rs +++ b/rs/interfaces/src/execution_environment.rs @@ -10,6 +10,7 @@ use ic_registry_provisional_whitelist::ProvisionalWhitelist; use ic_registry_subnet_type::SubnetType; use ic_sys::{PageBytes, PageIndex}; use ic_types::{ + consensus::ecdsa::QuadrupleId, crypto::canister_threshold_sig::MasterEcdsaPublicKey, ingress::{IngressStatus, WasmResult}, messages::{ @@ -19,9 +20,9 @@ use ic_types::{ Cycles, ExecutionRound, Height, NumInstructions, NumPages, Randomness, Time, }; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; use std::sync::Arc; use std::{collections::BTreeMap, ops}; +use std::{collections::BTreeSet, convert::TryFrom}; use std::{convert::Infallible, fmt}; use tower::util::BoxCloneService; @@ -1108,6 +1109,7 @@ pub struct RegistryExecutionSettings { pub max_number_of_canisters: u64, pub provisional_whitelist: ProvisionalWhitelist, pub max_ecdsa_queue_size: u32, + pub quadruples_to_create_in_advance: u32, pub subnet_size: usize, } @@ -1166,6 +1168,7 @@ pub trait Scheduler: Send { state: Self::State, randomness: Randomness, ecdsa_subnet_public_keys: BTreeMap, + ecdsa_quadruple_ids: BTreeMap>, current_round: ExecutionRound, current_round_type: ExecutionRoundType, registry_settings: &RegistryExecutionSettings, diff --git a/rs/messaging/src/message_routing.rs b/rs/messaging/src/message_routing.rs index 591387768c6..12ef379d7a3 100644 --- a/rs/messaging/src/message_routing.rs +++ b/rs/messaging/src/message_routing.rs @@ -701,10 +701,10 @@ impl BatchProcessorImpl { let subnet_features = subnet_record.features.unwrap_or_default().into(); let max_number_of_canisters = subnet_record.max_number_of_canisters; - let max_ecdsa_queue_size = subnet_record + let (max_ecdsa_queue_size, quadruples_to_create_in_advance) = subnet_record .ecdsa_config - .map(|c| c.max_queue_size) - .unwrap_or(0); + .map(|c| (c.max_queue_size, c.quadruples_to_create_in_advance)) + .unwrap_or_default(); let subnet_size = if subnet_record.membership.is_empty() { self.metrics.critical_error_missing_subnet_size.inc(); @@ -729,6 +729,7 @@ impl BatchProcessorImpl { max_number_of_canisters, provisional_whitelist, max_ecdsa_queue_size, + quadruples_to_create_in_advance, subnet_size, }, node_public_keys, diff --git a/rs/messaging/src/message_routing/tests.rs b/rs/messaging/src/message_routing/tests.rs index bd751cf000b..90a9b49475c 100644 --- a/rs/messaging/src/message_routing/tests.rs +++ b/rs/messaging/src/message_routing/tests.rs @@ -492,6 +492,7 @@ fn make_batch_processor( max_number_of_canisters: 0, provisional_whitelist: ProvisionalWhitelist::All, max_ecdsa_queue_size: 0, + quadruples_to_create_in_advance: 0, subnet_size: 0, })); let batch_processor = BatchProcessorImpl { diff --git a/rs/messaging/src/state_machine.rs b/rs/messaging/src/state_machine.rs index 7735bb0014e..6d4ade46430 100644 --- a/rs/messaging/src/state_machine.rs +++ b/rs/messaging/src/state_machine.rs @@ -149,6 +149,7 @@ impl StateMachine for StateMachineImpl { state_with_messages, batch.randomness, batch.ecdsa_subnet_public_keys, + batch.ecdsa_quadruple_ids, ExecutionRound::from(batch.batch_number.get()), execution_round_type, registry_settings, diff --git a/rs/messaging/src/state_machine/tests.rs b/rs/messaging/src/state_machine/tests.rs index 7b17427428e..d868ddea60d 100644 --- a/rs/messaging/src/state_machine/tests.rs +++ b/rs/messaging/src/state_machine/tests.rs @@ -18,6 +18,7 @@ use ic_test_utilities::{ use ic_test_utilities_execution_environment::test_registry_settings; use ic_test_utilities_logger::with_test_replica_logger; use ic_test_utilities_metrics::fetch_int_counter_vec; +use ic_types::consensus::ecdsa::QuadrupleId; use ic_types::messages::SignedIngress; use ic_types::{batch::BatchMessages, crypto::canister_threshold_sig::MasterEcdsaPublicKey}; use ic_types::{Height, PrincipalId, SubnetId, Time}; @@ -34,6 +35,7 @@ mock! { state: ic_replicated_state::ReplicatedState, randomness: ic_types::Randomness, ecdsa_subnet_public_keys: BTreeMap, + ecdsa_quadruple_ids: BTreeMap>, current_round: ExecutionRound, current_round_type: ExecutionRoundType, registry_settings: &RegistryExecutionSettings, @@ -88,11 +90,12 @@ fn test_fixture(provided_batch: &Batch) -> StateMachineTestFixture { always(), eq(provided_batch.randomness), eq(provided_batch.ecdsa_subnet_public_keys.clone()), + eq(provided_batch.ecdsa_quadruple_ids.clone()), eq(round), eq(round_type), eq(test_registry_settings()), ) - .returning(|state, _, _, _, _, _| state); + .returning(|state, _, _, _, _, _, _| state); let mut stream_builder = Box::new(MockStreamBuilder::new()); stream_builder diff --git a/rs/test_utilities/execution_environment/src/lib.rs b/rs/test_utilities/execution_environment/src/lib.rs index 02f02b0094b..f2e76666a35 100644 --- a/rs/test_utilities/execution_environment/src/lib.rs +++ b/rs/test_utilities/execution_environment/src/lib.rs @@ -134,6 +134,7 @@ pub fn test_registry_settings() -> RegistryExecutionSettings { max_number_of_canisters: 0x2000, provisional_whitelist: ProvisionalWhitelist::Set(BTreeSet::new()), max_ecdsa_queue_size: 20, + quadruples_to_create_in_advance: 5, subnet_size: SMALL_APP_SUBNET_MAX_SIZE, } }