From f4738dc78aa9fca0ec0b032fb0836431bdf75ebc Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 26 Jun 2024 09:15:36 +0300 Subject: [PATCH 001/138] Collation fetching fairness --- .../src/validator_side/collation.rs | 125 ++++++++++++++++-- .../src/validator_side/mod.rs | 61 ++++----- 2 files changed, 140 insertions(+), 46 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 96ffe9f13db3..748254e832f7 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -27,7 +27,12 @@ //! ┌──────────────────────────────────────────┐ //! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated -use std::{collections::VecDeque, future::Future, pin::Pin, task::Poll}; +use std::{ + collections::{BTreeMap, VecDeque}, + future::Future, + pin::Pin, + task::Poll, +}; use futures::{future::BoxFuture, FutureExt}; use polkadot_node_network_protocol::{ @@ -48,6 +53,8 @@ use tokio_util::sync::CancellationToken; use crate::{error::SecondingError, LOG_TARGET}; +use super::GroupAssignments; + /// Candidate supplied with a para head it's built on top of. #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] pub struct ProspectiveCandidate { @@ -216,7 +223,6 @@ impl CollationStatus { } /// Information about collations per relay parent. -#[derive(Default)] pub struct Collations { /// What is the current status in regards to a collation for this relay parent? pub status: CollationStatus, @@ -225,18 +231,44 @@ pub struct Collations { /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` /// yet. pub fetching_from: Option<(CollatorId, Option)>, - /// Collation that were advertised to us, but we did not yet fetch. - pub waiting_queue: VecDeque<(PendingCollation, CollatorId)>, + /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. + waiting_queue: BTreeMap>, /// How many collations have been seconded. pub seconded_count: usize, + /// What collations were fetched so far for this relay parent. + fetched_per_para: BTreeMap, + // Claims per `ParaId` for the assigned core at the relay parent. This information is obtained + // from the claim queue. + claims_per_para: BTreeMap, } impl Collations { + pub(super) fn new(assignments: &Vec) -> Self { + let mut claims_per_para = BTreeMap::new(); + for para_id in assignments { + *claims_per_para.entry(*para_id).or_default() += 1; + } + + Self { + status: Default::default(), + fetching_from: None, + waiting_queue: Default::default(), + seconded_count: 0, + fetched_per_para: Default::default(), + claims_per_para, + } + } + /// Note a seconded collation for a given para. pub(super) fn note_seconded(&mut self) { self.seconded_count += 1 } + // Note a collation which has been successfully fetched. + pub(super) fn note_fetched(&mut self, para_id: ParaId) { + *self.fetched_per_para.entry(para_id).or_default() += 1 + } + /// Returns the next collation to fetch from the `waiting_queue`. /// /// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. @@ -247,6 +279,7 @@ impl Collations { &mut self, finished_one: &(CollatorId, Option), relay_parent_mode: ProspectiveParachainsMode, + assignments: &GroupAssignments, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. @@ -269,21 +302,20 @@ impl Collations { match self.status { // We don't need to fetch any other collation when we already have seconded one. CollationStatus::Seconded => None, - CollationStatus::Waiting => - if self.is_seconded_limit_reached(relay_parent_mode) { - None - } else { - self.waiting_queue.pop_front() - }, + CollationStatus::Waiting => self.pick_a_collation_to_fetch(&assignments.current), CollationStatus::WaitingOnValidation | CollationStatus::Fetching => unreachable!("We have reset the status above!"), } } - /// Checks the limit of seconded candidates. - pub(super) fn is_seconded_limit_reached( + /// Checks if another collation can be accepted. There are two limits: + /// 1. The number of collations that can be seconded. + /// 2. The number of collations that can be fetched per parachain. This is based on the number + /// of entries in the claim queue. + pub(super) fn is_collations_limit_reached( &self, relay_parent_mode: ProspectiveParachainsMode, + para_id: ParaId, ) -> bool { let seconded_limit = if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } = @@ -293,7 +325,74 @@ impl Collations { } else { 1 }; - self.seconded_count >= seconded_limit + + let respected_per_para_limit = + self.claims_per_para.get(¶_id).copied().unwrap_or_default() >= + self.fetched_per_para.get(¶_id).copied().unwrap_or_default(); + + self.seconded_count >= seconded_limit || !respected_per_para_limit + } + + /// Adds a new collation to the waiting queue for the relay parent. This function doesn't + /// perform any limits check. The caller (`enqueue_collation`) should assure that the collation + /// can be enqueued. + pub(super) fn add_to_waiting_queue(&mut self, collation: (PendingCollation, CollatorId)) { + self.waiting_queue.entry(collation.0.para_id).or_default().push_back(collation); + } + + /// Picks a collation to fetch from the waiting queue. + /// When fetching collations we need to ensure that each parachain has got a fair core time + /// share depending on its assignments in the claim queue. This means that the number of + /// collations fetched per parachain should ideally be equal to (but not bigger than) the number + /// of claims for the particular parachain in the claim queue. + /// + /// To achieve this each parachain with at an entry in the `waiting_queue` has got a score + /// calculated by dividing the number of fetched collations by the number of entries in the + /// claim queue. Lower the score means higher fetching priority. Note that if a parachain hasn't + /// got anything fetched at this relay parent it will have score 0 which means highest priority. + /// + /// If two parachains has got the same score the one which is earlier in the claim queue will be + /// picked. + fn pick_a_collation_to_fetch( + &mut self, + claims: &Vec, + ) -> Option<(PendingCollation, CollatorId)> { + // Find the parachain(s) with the lowest score. + let mut lowest_score = None; + for (para_id, collations) in &mut self.waiting_queue { + let para_score = self + .fetched_per_para + .get(para_id) + .copied() + .unwrap_or_default() + .saturating_div(self.claims_per_para.get(para_id).copied().unwrap_or_default()); + + match lowest_score { + Some((score, _)) if para_score < score => + lowest_score = Some((para_score, vec![(para_id, collations)])), + Some((_, ref mut paras)) => { + paras.push((para_id, collations)); + }, + None => lowest_score = Some((para_score, vec![(para_id, collations)])), + } + } + + if let Some((_, mut lowest_score)) = lowest_score { + for claim in claims { + if let Some((_, collations)) = lowest_score.iter_mut().find(|(id, _)| *id == claim) + { + match collations.pop_front() { + Some(collation) => return Some(collation), + None => { + unreachable!("Collation can't be empty!") + }, + } + } + } + unreachable!("All entries in waiting_queue should also be in claim queue") + } else { + None + } } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index f5c9726f3f6a..53bb665a0d71 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -374,12 +374,9 @@ struct PerRelayParent { } impl PerRelayParent { - fn new(mode: ProspectiveParachainsMode) -> Self { - Self { - prospective_parachains_mode: mode, - assignment: GroupAssignments { current: vec![] }, - collations: Collations::default(), - } + fn new(mode: ProspectiveParachainsMode, assignments: GroupAssignments) -> Self { + let collations = Collations::new(&assignments.current); + Self { prospective_parachains_mode: mode, assignment: assignments, collations } } } @@ -467,12 +464,11 @@ fn is_relay_parent_in_implicit_view( async fn assign_incoming( sender: &mut Sender, - group_assignment: &mut GroupAssignments, current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, relay_parent_mode: ProspectiveParachainsMode, -) -> Result<()> +) -> Result where Sender: CollatorProtocolSenderTrait, { @@ -499,7 +495,7 @@ where rotation_info.core_for_group(group, cores.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); - return Ok(()) + return Ok(GroupAssignments { current: Vec::new() }) }; let paras_now = match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { @@ -532,9 +528,7 @@ where } } - *group_assignment = GroupAssignments { current: paras_now.into_iter().collect() }; - - Ok(()) + Ok(GroupAssignments { current: paras_now.into_iter().collect::>() }) } fn remove_outgoing( @@ -1107,7 +1101,10 @@ where ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent.collations.is_seconded_limit_reached(relay_parent_mode) { + if per_relay_parent + .collations + .is_collations_limit_reached(relay_parent_mode, para_id) + { return Err(AdvertisementError::SecondedLimitReached) } @@ -1199,7 +1196,7 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_seconded_limit_reached(relay_parent_mode) { + if collations.is_collations_limit_reached(relay_parent_mode, para_id) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1222,9 +1219,11 @@ where ?relay_parent, "Added collation to the pending list" ); - collations.waiting_queue.push_back((pending_collation, collator_id)); + collations.add_to_waiting_queue((pending_collation, collator_id)); }, CollationStatus::Waiting => { + // We were waiting for a collation to be advertised to us (we were idle) so we can fetch + // the new collation immediately fetch_collation(sender, state, pending_collation, collator_id).await?; }, CollationStatus::Seconded if relay_parent_mode.is_enabled() => { @@ -1270,19 +1269,11 @@ where state.span_per_relay_parent.insert(*leaf, per_leaf_span); } - let mut per_relay_parent = PerRelayParent::new(mode); - assign_incoming( - sender, - &mut per_relay_parent.assignment, - &mut state.current_assignments, - keystore, - *leaf, - mode, - ) - .await?; + let assignments = + assign_incoming(sender, &mut state.current_assignments, keystore, *leaf, mode).await?; state.active_leaves.insert(*leaf, mode); - state.per_relay_parent.insert(*leaf, per_relay_parent); + state.per_relay_parent.insert(*leaf, PerRelayParent::new(mode, assignments)); if mode.is_enabled() { state @@ -1298,10 +1289,8 @@ where .unwrap_or_default(); for block_hash in allowed_ancestry { if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - let mut per_relay_parent = PerRelayParent::new(mode); - assign_incoming( + let assignments = assign_incoming( sender, - &mut per_relay_parent.assignment, &mut state.current_assignments, keystore, *block_hash, @@ -1309,7 +1298,7 @@ where ) .await?; - entry.insert(per_relay_parent); + entry.insert(PerRelayParent::new(mode, assignments)); } } } @@ -1665,6 +1654,10 @@ async fn run_inner( let CollationEvent {collator_id, pending_collation, .. } = res.collation_event.clone(); + state.per_relay_parent.get_mut(&pending_collation.relay_parent).map(|rp_state| { + rp_state.collations.note_fetched(pending_collation.para_id); + }); + match kick_off_seconding(&mut ctx, &mut state, res).await { Err(err) => { gum::warn!( @@ -1737,9 +1730,11 @@ async fn dequeue_next_collation_and_fetch( previous_fetch: (CollatorId, Option), ) { while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|state| { - state - .collations - .get_next_collation_to_fetch(&previous_fetch, state.prospective_parachains_mode) + state.collations.get_next_collation_to_fetch( + &previous_fetch, + state.prospective_parachains_mode, + &state.assignment, + ) }) { gum::debug!( target: LOG_TARGET, From c7074daac3c0e1c3b3ebc1616a7941b50ad03955 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 26 Jun 2024 10:19:56 +0300 Subject: [PATCH 002/138] Comments --- .../node/network/collator-protocol/src/validator_side/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 53bb665a0d71..f260d31319ea 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1227,8 +1227,8 @@ where fetch_collation(sender, state, pending_collation, collator_id).await?; }, CollationStatus::Seconded if relay_parent_mode.is_enabled() => { - // Limit is not reached, it's allowed to second another - // collation. + // Limit is not reached (checked with `is_collations_limit_reached` before the match + // expression), it's allowed to second another collation. fetch_collation(sender, state, pending_collation, collator_id).await?; }, CollationStatus::Seconded => { From 73eee8702c783afd28f5502881f09e506f041942 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 26 Jun 2024 16:39:15 +0300 Subject: [PATCH 003/138] Fix tests and add some logs --- .../src/validator_side/collation.rs | 27 +++++- .../src/validator_side/tests/mod.rs | 24 +++++- .../tests/prospective_parachains.rs | 86 +++++++++++++++---- 3 files changed, 116 insertions(+), 21 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 748254e832f7..bf7df343be27 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -330,7 +330,20 @@ impl Collations { self.claims_per_para.get(¶_id).copied().unwrap_or_default() >= self.fetched_per_para.get(¶_id).copied().unwrap_or_default(); - self.seconded_count >= seconded_limit || !respected_per_para_limit + let respected_seconding_limit = self.seconded_count < seconded_limit; + + gum::trace!( + target: LOG_TARGET, + ?para_id, + claims_per_para=?self.claims_per_para, + fetched_per_para=?self.fetched_per_para, + ?seconded_limit, + ?respected_per_para_limit, + ?respected_seconding_limit, + "is_collations_limit_reached" + ); + + !(respected_seconding_limit && respected_per_para_limit) } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't @@ -357,6 +370,13 @@ impl Collations { &mut self, claims: &Vec, ) -> Option<(PendingCollation, CollatorId)> { + gum::trace!( + target: LOG_TARGET, + waiting_queue = ?self.waiting_queue, + claim_queue = ?claims, + "Pick a collation to fetch." + ); + // Find the parachain(s) with the lowest score. let mut lowest_score = None; for (para_id, collations) in &mut self.waiting_queue { @@ -367,6 +387,11 @@ impl Collations { .unwrap_or_default() .saturating_div(self.claims_per_para.get(para_id).copied().unwrap_or_default()); + // skip empty queues + if collations.is_empty() { + continue + } + match lowest_score { Some((score, _)) if para_score < score => lowest_score = Some((para_score, vec![(para_id, collations)])), diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 44e25efd4dfc..4b59c9a507be 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -42,8 +42,9 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ - CandidateReceipt, CollatorPair, CoreIndex, CoreState, GroupIndex, GroupRotationInfo, HeadData, - OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, + AsyncBackingParams, CandidateReceipt, CollatorPair, CoreIndex, CoreState, GroupIndex, + GroupRotationInfo, HeadData, OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, + ValidatorIndex, }; use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, @@ -77,6 +78,7 @@ struct TestState { group_rotation_info: GroupRotationInfo, cores: Vec, claim_queue: BTreeMap>, + async_backing_params: AsyncBackingParams, } impl Default for TestState { @@ -126,10 +128,23 @@ impl Default for TestState { }), ]; + let async_backing_params = + AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let mut claim_queue = BTreeMap::new(); - claim_queue.insert(CoreIndex(0), [chain_ids[0]].into_iter().collect()); + claim_queue.insert( + CoreIndex(0), + iter::repeat(chain_ids[0]) + .take(async_backing_params.max_candidate_depth as usize + 1) + .collect(), + ); claim_queue.insert(CoreIndex(1), VecDeque::new()); - claim_queue.insert(CoreIndex(2), [chain_ids[1]].into_iter().collect()); + claim_queue.insert( + CoreIndex(2), + iter::repeat(chain_ids[1]) + .take(async_backing_params.max_candidate_depth as usize + 1) + .collect(), + ); Self { chain_ids, @@ -140,6 +155,7 @@ impl Default for TestState { group_rotation_info, cores, claim_queue, + async_backing_params, } } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 472731b506ab..6e716a82b015 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -25,9 +25,6 @@ use polkadot_primitives::{ }; use rstest::rstest; -const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - fn get_parent_hash(hash: Hash) -> Hash { Hash::from_low_u64_be(hash.to_low_u64_be() + 1) } @@ -98,8 +95,9 @@ async fn assert_assign_incoming( pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, test_state: &TestState, - new_view: Vec<(Hash, u32)>, // Hash and block number. - activated: u8, // How many new heads does this update contain? + new_view: Vec<(Hash, u32)>, // Hash and block number. + activated: u8, // How many new heads does this update contain? + async_backing_params: &AsyncBackingParams, // returned via the runtime api ) -> Option { let new_view: HashMap = HashMap::from_iter(new_view); @@ -120,7 +118,7 @@ pub(super) async fn update_view( parent, RuntimeApiRequest::AsyncBackingParams(tx), )) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); + tx.send(Ok(async_backing_params.clone())).unwrap(); (parent, new_view.get(&parent).copied().expect("Unknown parent requested")) } ); @@ -134,7 +132,7 @@ pub(super) async fn update_view( ) .await; - let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); + let min_number = leaf_number.saturating_sub(async_backing_params.allowed_ancestry_len); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) @@ -339,7 +337,14 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); @@ -418,7 +423,14 @@ fn v1_advertisement_rejected_on_non_active_leave() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); @@ -468,7 +480,14 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -570,7 +589,14 @@ fn second_multiple_candidates_per_relay_parent() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); @@ -583,7 +609,7 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; - for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) { + for i in 0..(test_state.async_backing_params.max_candidate_depth + 1) { let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); candidate.descriptor.para_id = test_state.chain_ids[0]; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); @@ -727,7 +753,14 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); @@ -831,7 +864,14 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_c, head_c_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); @@ -954,7 +994,14 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1032,7 +1079,14 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; let peer_a = PeerId::random(); From fa321cef58fb9eec9c5eead030c0a92459c5c5e1 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 27 Jun 2024 13:38:40 +0300 Subject: [PATCH 004/138] Fix per para limit calculation in `is_collations_limit_reached` --- .../src/validator_side/collation.rs | 23 ++++++++++++++++--- .../src/validator_side/mod.rs | 11 +++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index bf7df343be27..cbb926939d99 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -28,7 +28,7 @@ //! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated use std::{ - collections::{BTreeMap, VecDeque}, + collections::{BTreeMap, HashMap, HashSet, VecDeque}, future::Future, pin::Pin, task::Poll, @@ -53,7 +53,7 @@ use tokio_util::sync::CancellationToken; use crate::{error::SecondingError, LOG_TARGET}; -use super::GroupAssignments; +use super::{GroupAssignments, PeerData}; /// Candidate supplied with a para head it's built on top of. #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] @@ -316,6 +316,7 @@ impl Collations { &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, + peer_data: &HashMap, ) -> bool { let seconded_limit = if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } = @@ -326,9 +327,24 @@ impl Collations { 1 }; + // All collators in `Collating` state for `para_id` we know about + let collators_for_para = peer_data + .iter() + .filter(|(_, data)| data.collating_para() == Some(para_id)) + .filter_map(|(_, data)| data.collator_id()) + .collect::>(); + + // If there is a pending fetch - count it + let pending_fetch = self + .fetching_from + .as_ref() + .map(|(collator_id, _)| if collators_for_para.contains(&collator_id) { 1 } else { 0 }) + .unwrap_or(0); + + // Successful fetches + a pending fetch < claim queue entries for `para_id` let respected_per_para_limit = self.claims_per_para.get(¶_id).copied().unwrap_or_default() >= - self.fetched_per_para.get(¶_id).copied().unwrap_or_default(); + self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + pending_fetch; let respected_seconding_limit = self.seconded_count < seconded_limit; @@ -337,6 +353,7 @@ impl Collations { ?para_id, claims_per_para=?self.claims_per_para, fetched_per_para=?self.fetched_per_para, + ?pending_fetch, ?seconded_limit, ?respected_per_para_limit, ?respected_seconding_limit, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index f260d31319ea..823e21ff1377 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1101,10 +1101,11 @@ where ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent - .collations - .is_collations_limit_reached(relay_parent_mode, para_id) - { + if per_relay_parent.collations.is_collations_limit_reached( + relay_parent_mode, + para_id, + &state.peer_data, + ) { return Err(AdvertisementError::SecondedLimitReached) } @@ -1196,7 +1197,7 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_collations_limit_reached(relay_parent_mode, para_id) { + if collations.is_collations_limit_reached(relay_parent_mode, para_id, &state.peer_data) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, From 96392a574b32f60e220f1650e7f696434f5e1090 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 27 Jun 2024 13:39:30 +0300 Subject: [PATCH 005/138] Fix default `TestState` initialization: claim queue len should be equal to `allowed_ancestry_len` --- .../network/collator-protocol/src/validator_side/tests/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 4b59c9a507be..1da035ebc837 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -135,14 +135,14 @@ impl Default for TestState { claim_queue.insert( CoreIndex(0), iter::repeat(chain_ids[0]) - .take(async_backing_params.max_candidate_depth as usize + 1) + .take(async_backing_params.allowed_ancestry_len as usize) .collect(), ); claim_queue.insert(CoreIndex(1), VecDeque::new()); claim_queue.insert( CoreIndex(2), iter::repeat(chain_ids[1]) - .take(async_backing_params.max_candidate_depth as usize + 1) + .take(async_backing_params.allowed_ancestry_len as usize) .collect(), ); From 0f28aa8426037e0cf3532607982cc213d0849529 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 27 Jun 2024 16:21:13 +0300 Subject: [PATCH 006/138] clippy --- .../src/validator_side/tests/prospective_parachains.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 6e716a82b015..f5101ca98c1a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -118,7 +118,7 @@ pub(super) async fn update_view( parent, RuntimeApiRequest::AsyncBackingParams(tx), )) => { - tx.send(Ok(async_backing_params.clone())).unwrap(); + tx.send(Ok(*async_backing_params)).unwrap(); (parent, new_view.get(&parent).copied().expect("Unknown parent requested")) } ); From e5ea548e944d912812bc5f7b169549a9a84799a3 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 28 Jun 2024 12:25:27 +0300 Subject: [PATCH 007/138] Update `is_collations_limit_reached` - remove seconded limit --- .../src/validator_side/collation.rs | 27 +++++++------------ .../src/validator_side/mod.rs | 9 ++----- .../tests/prospective_parachains.rs | 15 +++-------- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index cbb926939d99..ff27991fc758 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -308,24 +308,21 @@ impl Collations { } } - /// Checks if another collation can be accepted. There are two limits: - /// 1. The number of collations that can be seconded. - /// 2. The number of collations that can be fetched per parachain. This is based on the number - /// of entries in the claim queue. + /// Checks if another collation can be accepted. The number of collations that can be fetched + /// per parachain is limited by the entries in the claim queue for the `ParaId` in question. + /// + /// If prospective parachains mode is not enabled then we fall back to synchronous backing. In + /// this case there is a limit of 1 collation per relay parent. pub(super) fn is_collations_limit_reached( &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, peer_data: &HashMap, ) -> bool { - let seconded_limit = - if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } = - relay_parent_mode - { - max_candidate_depth + 1 - } else { - 1 - }; + if let ProspectiveParachainsMode::Disabled = relay_parent_mode { + // fallback to synchronous backing + return self.seconded_count >= 1 + } // All collators in `Collating` state for `para_id` we know about let collators_for_para = peer_data @@ -346,21 +343,17 @@ impl Collations { self.claims_per_para.get(¶_id).copied().unwrap_or_default() >= self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + pending_fetch; - let respected_seconding_limit = self.seconded_count < seconded_limit; - gum::trace!( target: LOG_TARGET, ?para_id, claims_per_para=?self.claims_per_para, fetched_per_para=?self.fetched_per_para, ?pending_fetch, - ?seconded_limit, ?respected_per_para_limit, - ?respected_seconding_limit, "is_collations_limit_reached" ); - !(respected_seconding_limit && respected_per_para_limit) + !respected_per_para_limit } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 823e21ff1377..8da0faefe1bd 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -146,6 +146,7 @@ enum InsertAdvertisementError { /// No prior declare message received. UndeclaredCollator, /// A limit for announcements per peer is reached. + #[allow(dead_code)] PeerLimitReached, } @@ -250,10 +251,7 @@ impl PeerData { .advertisements .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); }, - ( - ProspectiveParachainsMode::Enabled { max_candidate_depth, .. }, - candidate_hash, - ) => { + (ProspectiveParachainsMode::Enabled { .. }, candidate_hash) => { if let Some(candidate_hash) = candidate_hash { if state .advertisements @@ -266,9 +264,6 @@ impl PeerData { let candidates = state.advertisements.entry(on_relay_parent).or_default(); - if candidates.len() > max_candidate_depth { - return Err(InsertAdvertisementError::PeerLimitReached) - } candidates.insert(candidate_hash); } else { if self.version != CollationVersion::V1 { diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index f5101ca98c1a..cd3b4ec8d054 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -609,7 +609,7 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; - for i in 0..(test_state.async_backing_params.max_candidate_depth + 1) { + for i in 0..(test_state.async_backing_params.allowed_ancestry_len) { let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); candidate.descriptor.para_id = test_state.chain_ids[0]; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); @@ -694,16 +694,9 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; - // Reported because reached the limit of advertisements per relay parent. - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)), - ) => { - assert_eq!(peer_a, peer_id); - assert_eq!(rep.value, COST_UNEXPECTED_MESSAGE.cost_or_benefit()); - } - ); + // Rejected but not reported because reached the limit of advertisements for the para_id + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); // By different peer too (not reported). let pair_b = CollatorPair::generate().0; From 9abc898194f4242b7e671b4e92f52a8f4f56869e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 1 Jul 2024 13:59:37 +0300 Subject: [PATCH 008/138] Fix pending fetches and more tests --- .../src/validator_side/collation.rs | 37 +- .../src/validator_side/mod.rs | 22 +- .../src/validator_side/tests/collation.rs | 61 +++ .../src/validator_side/tests/mod.rs | 73 +++- .../tests/prospective_parachains.rs | 365 ++++++++++++++---- 5 files changed, 450 insertions(+), 108 deletions(-) create mode 100644 polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index ff27991fc758..629a4eeb1305 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -28,7 +28,7 @@ //! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated use std::{ - collections::{BTreeMap, HashMap, HashSet, VecDeque}, + collections::{BTreeMap, VecDeque}, future::Future, pin::Pin, task::Poll, @@ -53,7 +53,7 @@ use tokio_util::sync::CancellationToken; use crate::{error::SecondingError, LOG_TARGET}; -use super::{GroupAssignments, PeerData}; +use super::GroupAssignments; /// Candidate supplied with a para head it's built on top of. #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] @@ -228,8 +228,14 @@ pub struct Collations { pub status: CollationStatus, /// Collator we're fetching from, optionally which candidate was requested. /// - /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` - /// yet. + /// This is the last fetch for the relay parent. The value is used in + /// `get_next_collation_to_fetch` (called from `dequeue_next_collation_and_fetch`) to determine + /// if the last fetched collation is the same as the one which just finished. If yes - another + /// collation should be fetched. If not - another fetch was already initiated and + /// `get_next_collation_to_fetch` will do nothing. + /// + /// For the reasons above this value is not set to `None` when the fetch is done! Don't use it + /// to check if there is a pending fetch. pub fetching_from: Option<(CollatorId, Option)>, /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. waiting_queue: BTreeMap>, @@ -317,38 +323,25 @@ impl Collations { &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, - peer_data: &HashMap, + num_pending_fetches: usize, ) -> bool { if let ProspectiveParachainsMode::Disabled = relay_parent_mode { // fallback to synchronous backing return self.seconded_count >= 1 } - // All collators in `Collating` state for `para_id` we know about - let collators_for_para = peer_data - .iter() - .filter(|(_, data)| data.collating_para() == Some(para_id)) - .filter_map(|(_, data)| data.collator_id()) - .collect::>(); - - // If there is a pending fetch - count it - let pending_fetch = self - .fetching_from - .as_ref() - .map(|(collator_id, _)| if collators_for_para.contains(&collator_id) { 1 } else { 0 }) - .unwrap_or(0); - // Successful fetches + a pending fetch < claim queue entries for `para_id` let respected_per_para_limit = - self.claims_per_para.get(¶_id).copied().unwrap_or_default() >= - self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + pending_fetch; + self.claims_per_para.get(¶_id).copied().unwrap_or_default() > + self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + + num_pending_fetches; gum::trace!( target: LOG_TARGET, ?para_id, claims_per_para=?self.claims_per_para, fetched_per_para=?self.fetched_per_para, - ?pending_fetch, + ?num_pending_fetches, ?respected_per_para_limit, "is_collations_limit_reached" ); diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 8da0faefe1bd..35d8c57ff5c1 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1099,7 +1099,7 @@ where if per_relay_parent.collations.is_collations_limit_reached( relay_parent_mode, para_id, - &state.peer_data, + num_pending_collations_for_para_at_relay_parent(&state, para_id, relay_parent), ) { return Err(AdvertisementError::SecondedLimitReached) } @@ -1169,6 +1169,9 @@ where ?relay_parent, "Received advertise collation", ); + + let num_pending_fetches = + num_pending_collations_for_para_at_relay_parent(&state, para_id, relay_parent); let per_relay_parent = match state.per_relay_parent.get_mut(&relay_parent) { Some(rp_state) => rp_state, None => { @@ -1192,7 +1195,7 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_collations_limit_reached(relay_parent_mode, para_id, &state.peer_data) { + if collations.is_collations_limit_reached(relay_parent_mode, para_id, num_pending_fetches) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -2103,3 +2106,18 @@ async fn handle_collation_fetch_response( state.metrics.on_request(metrics_result); result } + +// Returns the number of pending fetches for `ParaId` at a specific relay parent. +fn num_pending_collations_for_para_at_relay_parent( + state: &State, + para_id: ParaId, + relay_parent: Hash, +) -> usize { + state + .collation_requests_cancel_handles + .iter() + .filter(|(pending_collation, _)| { + pending_collation.para_id == para_id && pending_collation.relay_parent == relay_parent + }) + .count() +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs new file mode 100644 index 000000000000..af4913f2b946 --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -0,0 +1,61 @@ +use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; +use polkadot_primitives::{CollatorId, Id as ParaId}; + +use sp_core::sr25519; + +use super::Collations; + +#[test] +fn cant_add_more_than_claim_queue() { + let para_a = ParaId::from(1); + let para_b = ParaId::from(2); + let assignments = vec![para_a.clone(), para_b.clone(), para_a.clone()]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + let mut collations = Collations::new(&assignments); + + // first collation for `para_a` is in the limit + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); + collations.note_fetched(para_a.clone()); + // and `para_b` is not affected + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + + // second collation for `para_a` is also in the limit + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); + collations.note_fetched(para_a.clone()); + + // `para_b`` is still not affected + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + + // third collation for `para_a`` will be above the limit + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); + + // one fetch for b + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + collations.note_fetched(para_b.clone()); + + // and now both paras are over limit + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); +} + +#[test] +fn pending_fetches_are_counted() { + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let para_b = ParaId::from(2); + let assignments = vec![para_a.clone(), para_b.clone(), para_a.clone()]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + let mut collations = Collations::new(&assignments); + collations.fetching_from = Some((collator_id_a, None)); + + // first collation for `para_a` is in the limit + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 1)); + collations.note_fetched(para_a.clone()); + + // second collation for `para_a`` is not in the limit due to the pending fetch + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 1)); +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 1da035ebc837..edb17d697d78 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -50,6 +50,7 @@ use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, }; +mod collation; mod prospective_parachains; const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); @@ -83,10 +84,6 @@ struct TestState { impl Default for TestState { fn default() -> Self { - let chain_a = ParaId::from(1); - let chain_b = ParaId::from(2); - - let chain_ids = vec![chain_a, chain_b]; let relay_parent = Hash::repeat_byte(0x05); let collators = iter::repeat(()).map(|_| CollatorPair::generate().0).take(5).collect(); @@ -109,10 +106,16 @@ impl Default for TestState { GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 1, now: 0 }; let cores = vec![ - CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }), + CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(Self::CHAIN_IDS[0]), + collator: None, + }), CoreState::Free, CoreState::Occupied(OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id: chain_ids[1], collator: None }), + next_up_on_available: Some(ScheduledCore { + para_id: ParaId::from(Self::CHAIN_IDS[1]), + collator: None, + }), occupied_since: 0, time_out_at: 1, next_up_on_time_out: None, @@ -121,33 +124,30 @@ impl Default for TestState { candidate_hash: Default::default(), candidate_descriptor: { let mut d = dummy_candidate_descriptor(dummy_hash()); - d.para_id = chain_ids[1]; + d.para_id = ParaId::from(Self::CHAIN_IDS[1]); d }, }), ]; - let async_backing_params = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - let mut claim_queue = BTreeMap::new(); claim_queue.insert( CoreIndex(0), - iter::repeat(chain_ids[0]) - .take(async_backing_params.allowed_ancestry_len as usize) + iter::repeat(ParaId::from(Self::CHAIN_IDS[0])) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) .collect(), ); claim_queue.insert(CoreIndex(1), VecDeque::new()); claim_queue.insert( CoreIndex(2), - iter::repeat(chain_ids[1]) - .take(async_backing_params.allowed_ancestry_len as usize) + iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) .collect(), ); Self { - chain_ids, + chain_ids: Self::CHAIN_IDS.map(|id| ParaId::from(id)).to_vec(), relay_parent, collators, validator_public, @@ -155,11 +155,52 @@ impl Default for TestState { group_rotation_info, cores, claim_queue, - async_backing_params, + async_backing_params: Self::ASYNC_BACKING_PARAMS, } } } +impl TestState { + const CHAIN_IDS: [u32; 2] = [1, 2]; + const ASYNC_BACKING_PARAMS: AsyncBackingParams = + AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + fn with_shared_core() -> Self { + let mut state = Self::default(); + + let cores = vec![ + CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(Self::CHAIN_IDS[0]), + collator: None, + }), + CoreState::Free, + ]; + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[1]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + ); + + state.cores = cores; + state.claim_queue = claim_queue; + + state + } +} + type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index cd3b4ec8d054..c92013669987 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -325,6 +325,83 @@ async fn assert_persisted_validation_data( } } +async fn submit_second_and_assert( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + para_id: ParaId, + relay_parent: Hash, + collator: PeerId, + candidate_head_data: HeadData, +) { + let mut candidate = dummy_candidate_receipt_bad_sig(relay_parent, Some(Default::default())); + candidate.descriptor.para_id = para_id; + candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); + let commitments = CandidateCommitments { + head_data: candidate_head_data, + horizontal_messages: Default::default(), + upward_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + candidate.commitments_hash = commitments.hash(); + + let candidate_hash = candidate.hash(); + let parent_head_data_hash = Hash::zero(); + + advertise_collation( + virtual_overseer, + collator, + relay_parent, + Some((candidate_hash, parent_head_data_hash)), + ) + .await; + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::CandidateBacking( + CandidateBackingMessage::CanSecond(request, tx), + ) => { + assert_eq!(request.candidate_hash, candidate_hash); + assert_eq!(request.candidate_para_id, para_id); + assert_eq!(request.parent_head_data_hash, parent_head_data_hash); + tx.send(true).expect("receiving side should be alive"); + } + ); + + let response_channel = assert_fetch_collation_request( + virtual_overseer, + relay_parent, + para_id, + Some(candidate_hash), + ) + .await; + + let pov = PoV { block_data: BlockData(vec![1]) }; + + response_channel + .send(Ok(( + request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) + .expect("Sending response should succeed"); + + assert_candidate_backing_second( + virtual_overseer, + relay_parent, + para_id, + &pov, + CollationVersion::V2, + ) + .await; + + let candidate = CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; + + send_seconded_statement(virtual_overseer, keystore.clone(), &candidate).await; + + assert_collation_seconded(virtual_overseer, relay_parent, collator, CollationVersion::V2).await; +} + #[test] fn v1_advertisement_accepted_and_seconded() { let test_state = TestState::default(); @@ -609,79 +686,17 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; + // `allowed_ancestry_len` equals the size of the claim queue for i in 0..(test_state.async_backing_params.allowed_ancestry_len) { - let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); - candidate.descriptor.para_id = test_state.chain_ids[0]; - candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - let commitments = CandidateCommitments { - head_data: HeadData(vec![i as u8]), - horizontal_messages: Default::default(), - upward_messages: Default::default(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - candidate.commitments_hash = commitments.hash(); - - let candidate_hash = candidate.hash(); - let parent_head_data_hash = Hash::zero(); - - advertise_collation( - &mut virtual_overseer, - peer_a, - head_c, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::CandidateBacking( - CandidateBackingMessage::CanSecond(request, tx), - ) => { - assert_eq!(request.candidate_hash, candidate_hash); - assert_eq!(request.candidate_para_id, test_state.chain_ids[0]); - assert_eq!(request.parent_head_data_hash, parent_head_data_hash); - tx.send(true).expect("receiving side should be alive"); - } - ); - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - head_c, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - let pov = PoV { block_data: BlockData(vec![1]) }; - - response_channel - .send(Ok(( - request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( + submit_second_and_assert( &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), head_c, - test_state.chain_ids[0], - &pov, - CollationVersion::V2, + peer_a, + HeadData(vec![i as u8]), ) .await; - - let candidate = - CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; - - send_seconded_statement(&mut virtual_overseer, keystore.clone(), &candidate).await; - - assert_collation_seconded(&mut virtual_overseer, head_c, peer_a, CollationVersion::V2) - .await; } // No more advertisements can be made for this relay parent. @@ -1358,3 +1373,217 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { virtual_overseer }); } + +#[test] +fn collations_outside_limits_are_not_fetched() { + let test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + // Grandparent of head `a`. + let head_b = Hash::from_low_u64_be(128); + let head_b_num: u32 = 2; + + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; + + let peer_a = PeerId::random(); + let pair_a = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair_a.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let peer_b = PeerId::random(); + let pair_b = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_b, + pair_b.clone(), + test_state.chain_ids[1], + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), + head_b, + peer_a, + HeadData(vec![1 as u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[1]), + head_b, + peer_b, + HeadData(vec![2 as u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), + head_b, + peer_a, + HeadData(vec![3 as u8]), + ) + .await; + + // No more advertisements can be made for this relay parent. + + // verify for peer_a + let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // verify for peer_b + let candidate_hash = CandidateHash(Hash::repeat_byte(0xBB)); + advertise_collation( + &mut virtual_overseer, + peer_b, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn fair_collation_fetches() { + let test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + // Grandparent of head `a`. + let head_b = Hash::from_low_u64_be(128); + let head_b_num: u32 = 2; + + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; + + let peer_a = PeerId::random(); + let pair_a = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair_a.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let peer_b = PeerId::random(); + let pair_b = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_b, + pair_b.clone(), + test_state.chain_ids[1], + CollationVersion::V2, + ) + .await; + + // `peer_a` sends two advertisements (its claim queue limit) + for i in 0..2u8 { + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), + head_b, + peer_a, + HeadData(vec![i]), + ) + .await; + } + + // `peer_a` sends another advertisement and it is ignored + let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // `peer_b` should still be able to advertise its collation + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[1]), + head_b, + peer_b, + HeadData(vec![0 as u8]), + ) + .await; + + // And no more advertisements can be made for this relay parent. + + // verify for peer_a + let candidate_hash = CandidateHash(Hash::repeat_byte(0xBB)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // verify for peer_b + let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC)); + advertise_collation( + &mut virtual_overseer, + peer_b, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} From c07890b0bef348188ab58c7022c1f0109157a40b Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 1 Jul 2024 14:54:07 +0300 Subject: [PATCH 009/138] Remove unnecessary clone --- .../src/validator_side/tests/collation.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index af4913f2b946..256aeea3e819 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -9,35 +9,35 @@ use super::Collations; fn cant_add_more_than_claim_queue() { let para_a = ParaId::from(1); let para_b = ParaId::from(2); - let assignments = vec![para_a.clone(), para_b.clone(), para_a.clone()]; + let assignments = vec![para_a, para_b, para_a]; let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let mut collations = Collations::new(&assignments); // first collation for `para_a` is in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); - collations.note_fetched(para_a.clone()); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + collations.note_fetched(para_a); // and `para_b` is not affected - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); // second collation for `para_a` is also in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); - collations.note_fetched(para_a.clone()); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + collations.note_fetched(para_a); // `para_b`` is still not affected - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); // third collation for `para_a`` will be above the limit - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); // one fetch for b - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); - collations.note_fetched(para_b.clone()); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); + collations.note_fetched(para_b); // and now both paras are over limit - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 0)); - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_b.clone(), 0)); + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); } #[test] @@ -45,7 +45,7 @@ fn pending_fetches_are_counted() { let para_a = ParaId::from(1); let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let para_b = ParaId::from(2); - let assignments = vec![para_a.clone(), para_b.clone(), para_a.clone()]; + let assignments = vec![para_a, para_b, para_a]; let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; @@ -53,9 +53,9 @@ fn pending_fetches_are_counted() { collations.fetching_from = Some((collator_id_a, None)); // first collation for `para_a` is in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 1)); - collations.note_fetched(para_a.clone()); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 1)); + collations.note_fetched(para_a); // second collation for `para_a`` is not in the limit due to the pending fetch - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a.clone(), 1)); + assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 1)); } From e50440e11430baae0ff6fe605f1e998cdbbca55a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 1 Jul 2024 15:20:23 +0300 Subject: [PATCH 010/138] Comments --- .../collator-protocol/src/validator_side/collation.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 629a4eeb1305..012afd43df24 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -306,7 +306,9 @@ impl Collations { self.status.back_to_waiting(relay_parent_mode); match self.status { - // We don't need to fetch any other collation when we already have seconded one. + // If async backing is enabled `back_to_waiting` will change `Seconded` state to + // `Waiting` so that we can fetch more collations. If async backing is disabled we can't + // fetch more than one collation per relay parent so `None` is returned. CollationStatus::Seconded => None, CollationStatus::Waiting => self.pick_a_collation_to_fetch(&assignments.current), CollationStatus::WaitingOnValidation | CollationStatus::Fetching => @@ -330,7 +332,7 @@ impl Collations { return self.seconded_count >= 1 } - // Successful fetches + a pending fetch < claim queue entries for `para_id` + // Successful fetches + pending fetches < claim queue entries for `para_id` let respected_per_para_limit = self.claims_per_para.get(¶_id).copied().unwrap_or_default() > self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + @@ -412,7 +414,7 @@ impl Collations { match collations.pop_front() { Some(collation) => return Some(collation), None => { - unreachable!("Collation can't be empty!") + unreachable!("Collation can't be empty because empty ones are skipped at the beginning of the loop.") }, } } From 42b05c73c3f560b7e28aa4a186f461f593202350 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 1 Jul 2024 17:59:22 +0300 Subject: [PATCH 011/138] Better var names --- .../src/validator_side/collation.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 012afd43df24..fd1c8e4cab72 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -53,8 +53,6 @@ use tokio_util::sync::CancellationToken; use crate::{error::SecondingError, LOG_TARGET}; -use super::GroupAssignments; - /// Candidate supplied with a para head it's built on top of. #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] pub struct ProspectiveCandidate { @@ -249,9 +247,9 @@ pub struct Collations { } impl Collations { - pub(super) fn new(assignments: &Vec) -> Self { + pub(super) fn new(claim_queue: &Vec) -> Self { let mut claims_per_para = BTreeMap::new(); - for para_id in assignments { + for para_id in claim_queue { *claims_per_para.entry(*para_id).or_default() += 1; } @@ -285,7 +283,7 @@ impl Collations { &mut self, finished_one: &(CollatorId, Option), relay_parent_mode: ProspectiveParachainsMode, - assignments: &GroupAssignments, + claim_queue: &Vec, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. @@ -310,7 +308,7 @@ impl Collations { // `Waiting` so that we can fetch more collations. If async backing is disabled we can't // fetch more than one collation per relay parent so `None` is returned. CollationStatus::Seconded => None, - CollationStatus::Waiting => self.pick_a_collation_to_fetch(&assignments.current), + CollationStatus::Waiting => self.pick_a_collation_to_fetch(&claim_queue), CollationStatus::WaitingOnValidation | CollationStatus::Fetching => unreachable!("We have reset the status above!"), } @@ -373,12 +371,12 @@ impl Collations { /// picked. fn pick_a_collation_to_fetch( &mut self, - claims: &Vec, + claim_queue: &Vec, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, waiting_queue = ?self.waiting_queue, - claim_queue = ?claims, + ?claim_queue, "Pick a collation to fetch." ); @@ -408,7 +406,7 @@ impl Collations { } if let Some((_, mut lowest_score)) = lowest_score { - for claim in claims { + for claim in claim_queue { if let Some((_, collations)) = lowest_score.iter_mut().find(|(id, _)| *id == claim) { match collations.pop_front() { From 2f5a466b7ba1a6820e7c63089b090e95838f4e67 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 2 Jul 2024 10:39:00 +0300 Subject: [PATCH 012/138] Fix `pick_a_collation_to_fetch` and add more tests --- .../src/validator_side/collation.rs | 11 ++- .../src/validator_side/mod.rs | 2 +- .../src/validator_side/tests/collation.rs | 98 +++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index fd1c8e4cab72..26641a30671c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -375,7 +375,9 @@ impl Collations { ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, - waiting_queue = ?self.waiting_queue, + waiting_queue=?self.waiting_queue, + fetched_per_para=?self.fetched_per_para, + claims_per_para=?self.claims_per_para, ?claim_queue, "Pick a collation to fetch." ); @@ -387,8 +389,11 @@ impl Collations { .fetched_per_para .get(para_id) .copied() - .unwrap_or_default() - .saturating_div(self.claims_per_para.get(para_id).copied().unwrap_or_default()); + .map(|v| { + (v as f64) / + (self.claims_per_para.get(para_id).copied().unwrap_or_default() as f64) + }) + .unwrap_or_default(); // skip empty queues if collations.is_empty() { diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 35d8c57ff5c1..437825033dc8 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1732,7 +1732,7 @@ async fn dequeue_next_collation_and_fetch( state.collations.get_next_collation_to_fetch( &previous_fetch, state.prospective_parachains_mode, - &state.assignment, + &state.assignment.current, ) }) { gum::debug!( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 256aeea3e819..2bd0e587229e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -59,3 +59,101 @@ fn pending_fetches_are_counted() { // second collation for `para_a`` is not in the limit due to the pending fetch assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 1)); } + +#[test] +fn collation_fetching_respects_claim_queue() { + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let peer_a = PeerId::random(); + + let para_b = ParaId::from(2); + let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); + let peer_b = PeerId::random(); + + let assignments = vec![para_a, para_b, para_a]; + let mut collations = Collations::new(&assignments); + collations.fetching_from = None; + + let relay_parent = Hash::repeat_byte(0x01); + + let collation_a1 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(1)), + parent_head_data_hash: Hash::repeat_byte(1), + }), + ), + collator_id_a.clone(), + ); + + let collation_a2 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(2)), + parent_head_data_hash: Hash::repeat_byte(2), + }), + ), + collator_id_a.clone(), + ); + + let collation_b1 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), + }), + ), + collator_id_b.clone(), + ); + + collations.add_to_waiting_queue(collation_a1.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + collations.add_to_waiting_queue(collation_b1.clone()); + + let claim_queue = vec![para_a, para_b, para_a]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + assert_eq!( + Some(collation_a1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a1.0.para_id); + + assert_eq!( + Some(collation_b1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b1.0.para_id); + + assert_eq!( + Some(collation_a2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a2.0.para_id); +} + From ff96ef9651327f1bf3c287ffbe65bec04d422573 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 2 Jul 2024 13:46:22 +0300 Subject: [PATCH 013/138] Fix test: `collation_fetching_respects_claim_queue` --- .../src/validator_side/tests/collation.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 2bd0e587229e..e34617b8d4ee 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -1,9 +1,10 @@ use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; -use polkadot_primitives::{CollatorId, Id as ParaId}; +use polkadot_primitives::{CandidateHash, CollatorId, Hash, Id as ParaId}; +use sc_network::PeerId; use sp_core::sr25519; -use super::Collations; +use super::{Collations, PendingCollation, ProspectiveCandidate}; #[test] fn cant_add_more_than_claim_queue() { @@ -70,8 +71,11 @@ fn collation_fetching_respects_claim_queue() { let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); let peer_b = PeerId::random(); - let assignments = vec![para_a, para_b, para_a]; - let mut collations = Collations::new(&assignments); + let claim_queue = vec![para_a, para_b, para_a]; + let mut collations = Collations::new(&claim_queue); + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + collations.fetching_from = None; let relay_parent = Hash::repeat_byte(0x01); @@ -119,10 +123,6 @@ fn collation_fetching_respects_claim_queue() { collations.add_to_waiting_queue(collation_a2.clone()); collations.add_to_waiting_queue(collation_b1.clone()); - let claim_queue = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - assert_eq!( Some(collation_a1.clone()), collations.get_next_collation_to_fetch( @@ -156,4 +156,3 @@ fn collation_fetching_respects_claim_queue() { ); collations.note_fetched(collation_a2.0.para_id); } - From e837689fcfd168a2f20de4eda7ca6f863981bb66 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 2 Jul 2024 14:33:41 +0300 Subject: [PATCH 014/138] Add `collation_fetching_fallback_works` test + comments --- Cargo.lock | 1 + .../node/network/collator-protocol/Cargo.toml | 1 + .../src/validator_side/collation.rs | 26 ++++--- .../src/validator_side/tests/collation.rs | 75 +++++++++++++++++++ 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbb785a618a8..30343e645deb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12818,6 +12818,7 @@ dependencies = [ "sp-keyring", "sp-keystore", "sp-runtime", + "sp-tracing 16.0.0", "thiserror", "tokio-util", "tracing-gum", diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index a56c1c7dfe98..c43b4dac2d5e 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -39,6 +39,7 @@ sp-keyring = { path = "../../../../substrate/primitives/keyring" } sc-keystore = { path = "../../../../substrate/client/keystore" } sc-network = { path = "../../../../substrate/client/network" } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["std"] } +sp-tracing = { path = "../../../../substrate/primitives/tracing"} polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 26641a30671c..a577eda18e1b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -242,14 +242,22 @@ pub struct Collations { /// What collations were fetched so far for this relay parent. fetched_per_para: BTreeMap, // Claims per `ParaId` for the assigned core at the relay parent. This information is obtained - // from the claim queue. + // from `GroupAssignments` which contains either the claim queue for the core or the `ParaId` + // of the parachain assigned to the core. claims_per_para: BTreeMap, } impl Collations { - pub(super) fn new(claim_queue: &Vec) -> Self { + /// `Collations` should work with and without claim queue support. To make this happen without + /// creating two parallel implementations instead of working with the claim queue directly it + /// uses `GroupAssignments`. If the runtime supports claim queue `GroupAssignments` contains the + /// claim queue for the core assigned to the (backing group of the) validator. If the runtime + /// doesn't support claim queue `GroupAssignments` contains only one entry - the `ParaId` of the + /// parachain assigned to the core. This way we can handle both cases with a single + /// implementation and avoid code duplication. + pub(super) fn new(group_assignments: &Vec) -> Self { let mut claims_per_para = BTreeMap::new(); - for para_id in claim_queue { + for para_id in group_assignments { *claims_per_para.entry(*para_id).or_default() += 1; } @@ -283,7 +291,7 @@ impl Collations { &mut self, finished_one: &(CollatorId, Option), relay_parent_mode: ProspectiveParachainsMode, - claim_queue: &Vec, + group_assignments: &Vec, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. @@ -308,14 +316,14 @@ impl Collations { // `Waiting` so that we can fetch more collations. If async backing is disabled we can't // fetch more than one collation per relay parent so `None` is returned. CollationStatus::Seconded => None, - CollationStatus::Waiting => self.pick_a_collation_to_fetch(&claim_queue), + CollationStatus::Waiting => self.pick_a_collation_to_fetch(&group_assignments), CollationStatus::WaitingOnValidation | CollationStatus::Fetching => unreachable!("We have reset the status above!"), } } /// Checks if another collation can be accepted. The number of collations that can be fetched - /// per parachain is limited by the entries in the claim queue for the `ParaId` in question. + /// per parachain is limited by the entries in claim queue for the `ParaId` in question. /// /// If prospective parachains mode is not enabled then we fall back to synchronous backing. In /// this case there is a limit of 1 collation per relay parent. @@ -371,14 +379,14 @@ impl Collations { /// picked. fn pick_a_collation_to_fetch( &mut self, - claim_queue: &Vec, + group_assignments: &Vec, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, waiting_queue=?self.waiting_queue, fetched_per_para=?self.fetched_per_para, claims_per_para=?self.claims_per_para, - ?claim_queue, + ?group_assignments, "Pick a collation to fetch." ); @@ -411,7 +419,7 @@ impl Collations { } if let Some((_, mut lowest_score)) = lowest_score { - for claim in claim_queue { + for claim in group_assignments { if let Some((_, collations)) = lowest_score.iter_mut().find(|(id, _)| *id == claim) { match collations.pop_front() { diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index e34617b8d4ee..6ca2d2075065 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -8,6 +8,8 @@ use super::{Collations, PendingCollation, ProspectiveCandidate}; #[test] fn cant_add_more_than_claim_queue() { + sp_tracing::init_for_tests(); + let para_a = ParaId::from(1); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; @@ -43,6 +45,8 @@ fn cant_add_more_than_claim_queue() { #[test] fn pending_fetches_are_counted() { + sp_tracing::init_for_tests(); + let para_a = ParaId::from(1); let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let para_b = ParaId::from(2); @@ -63,6 +67,8 @@ fn pending_fetches_are_counted() { #[test] fn collation_fetching_respects_claim_queue() { + sp_tracing::init_for_tests(); + let para_a = ParaId::from(1); let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let peer_a = PeerId::random(); @@ -156,3 +162,72 @@ fn collation_fetching_respects_claim_queue() { ); collations.note_fetched(collation_a2.0.para_id); } + +#[test] +fn collation_fetching_fallback_works() { + sp_tracing::init_for_tests(); + + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let peer_a = PeerId::random(); + + let claim_queue = vec![para_a]; + let mut collations = Collations::new(&claim_queue); + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + collations.fetching_from = None; + + let relay_parent = Hash::repeat_byte(0x01); + + let collation_a1 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(1)), + parent_head_data_hash: Hash::repeat_byte(1), + }), + ), + collator_id_a.clone(), + ); + + let collation_a2 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(2)), + parent_head_data_hash: Hash::repeat_byte(2), + }), + ), + collator_id_a.clone(), + ); + + collations.add_to_waiting_queue(collation_a1.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + + assert_eq!( + Some(collation_a1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a1.0.para_id); + + assert_eq!( + Some(collation_a2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a2.0.para_id); +} From 91cdd134532d8ddbd190506c5a6f1907f75f5c2c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 11:08:17 +0300 Subject: [PATCH 015/138] More tests --- .../src/validator_side/tests/collation.rs | 124 ++++++++++++++++++ .../src/validator_side/tests/mod.rs | 31 ++++- .../tests/prospective_parachains.rs | 120 +++++++++++++++-- 3 files changed, 261 insertions(+), 14 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 6ca2d2075065..cdc1cda070f2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -231,3 +231,127 @@ fn collation_fetching_fallback_works() { ); collations.note_fetched(collation_a2.0.para_id); } + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue() { + sp_tracing::init_for_tests(); + + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let peer_a = PeerId::random(); + + let para_b = ParaId::from(2); + let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); + let peer_b = PeerId::random(); + + let claim_queue = vec![para_a, para_b, para_a, para_b]; + let mut collations = Collations::new(&claim_queue); + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; + + collations.fetching_from = None; + + let relay_parent = Hash::repeat_byte(0x01); + + let collation_a1 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(1)), + parent_head_data_hash: Hash::repeat_byte(1), + }), + ), + collator_id_a.clone(), + ); + + let collation_a2 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(2)), + parent_head_data_hash: Hash::repeat_byte(2), + }), + ), + collator_id_a.clone(), + ); + + let collation_b1 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), + }), + ), + collator_id_b.clone(), + ); + + let collation_b2 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(4)), + parent_head_data_hash: Hash::repeat_byte(4), + }), + ), + collator_id_b.clone(), + ); + + // Despite the order here the fetches should be a1, b1, a2, b2 + collations.add_to_waiting_queue(collation_b1.clone()); + collations.add_to_waiting_queue(collation_b2.clone()); + collations.add_to_waiting_queue(collation_a1.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + + assert_eq!( + Some(collation_a1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a1.0.para_id); + + assert_eq!( + Some(collation_b1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b1.0.para_id); + + assert_eq!( + Some(collation_a2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a2.0.para_id); + + assert_eq!( + Some(collation_b2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b2.0.para_id); +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index edb17d697d78..7243af9fe4ca 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -78,7 +78,7 @@ struct TestState { validator_groups: Vec>, group_rotation_info: GroupRotationInfo, cores: Vec, - claim_queue: BTreeMap>, + claim_queue: Option>>, async_backing_params: AsyncBackingParams, } @@ -154,7 +154,7 @@ impl Default for TestState { validator_groups, group_rotation_info, cores, - claim_queue, + claim_queue: Some(claim_queue), async_backing_params: Self::ASYNC_BACKING_PARAMS, } } @@ -195,7 +195,21 @@ impl TestState { ); state.cores = cores; - state.claim_queue = claim_queue; + state.claim_queue = Some(claim_queue); + + state + } + + fn without_claim_queue() -> Self { + let mut state = Self::default(); + state.claim_queue = None; + state.cores = vec![ + CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(Self::CHAIN_IDS[0]), + collator: None, + }), + CoreState::Free, + ]; state } @@ -350,7 +364,16 @@ async fn respond_to_core_info_queries( _, RuntimeApiRequest::ClaimQueue(tx), )) => { - let _ = tx.send(Ok(test_state.claim_queue.clone())); + match test_state.claim_queue { + Some(ref claim_queue) => { + let _ = tx.send(Ok(claim_queue.clone())); + }, + None => { + let _ = tx.send(Err(RuntimeApiError::NotSupported { runtime_api_name: "doesnt_matter" })); + } + + } + } ); } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index c92013669987..70d189334a95 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -76,19 +76,28 @@ async fn assert_assign_incoming( parent, RuntimeApiRequest::Version(tx), )) if parent == hash => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + match test_state.claim_queue { + Some(_) => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + }, + None => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)); + } + } } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - parent, - RuntimeApiRequest::ClaimQueue(tx), - )) if parent == hash => { - let _ = tx.send(Ok(test_state.claim_queue.clone())); - } - ); + if let Some(claim_queue) = &test_state.claim_queue { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ClaimQueue(tx), + )) if parent == hash => { + let _ = tx.send(Ok(claim_queue.clone())); + } + ); + } } /// Handle a view update. @@ -1587,3 +1596,94 @@ fn fair_collation_fetches() { virtual_overseer }); } + +#[test] +fn collation_fetches_without_claimqueue() { + let test_state = TestState::without_claim_queue(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + // Grandparent of head `a`. + let head_b = Hash::from_low_u64_be(128); + let head_b_num: u32 = 2; + + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_b, head_b_num)], + 1, + &test_state.async_backing_params, + ) + .await; + + let peer_a = PeerId::random(); + let pair_a = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair_a.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let peer_b = PeerId::random(); + let pair_b = CollatorPair::generate().0; + + // connect an unneeded collator + connect_and_declare_collator( + &mut virtual_overseer, + peer_b, + pair_b.clone(), + test_state.chain_ids[1], + CollationVersion::V2, + ) + .await; + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridgeTx( + NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, _)), + ) => { + assert_eq!(peer_id, peer_b); + } + ); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridgeTx( + NetworkBridgeTxMessage::DisconnectPeer(peer_id, peer_set) + ) => { + assert_eq!(peer_id, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + + // in fallback mode up to `max_candidate_depth` collations are accepted + for i in 0..test_state.async_backing_params.max_candidate_depth { + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), + head_b, + peer_a, + HeadData(vec![i as u8]), + ) + .await; + } + + // `peer_a` sends another advertisement and it is ignored + let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} From 9f2d59be85f8af3b897c0d1c5dc18206717f5c1f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 11:57:06 +0300 Subject: [PATCH 016/138] Fix collation limit fallback --- .../tests/prospective_parachains.rs | 20 +++++++ .../src/validator_side/collation.rs | 60 ++++++++++++------- .../src/validator_side/tests/collation.rs | 36 +++++++---- .../tests/prospective_parachains.rs | 19 +++++- .../node/subsystem-util/src/runtime/mod.rs | 28 +++++++-- 5 files changed, 127 insertions(+), 36 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index ea8fdb0e04fb..9e68247d5b39 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -58,6 +58,16 @@ async fn update_view( } ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::Version(tx), + )) => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + } + ); + let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); let ancestry_len = leaf_number + 1 - min_number; @@ -96,6 +106,16 @@ async fn update_view( } ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::Version(tx), + )) => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + } + ); + assert_matches!( overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(50)).await.unwrap(), AllMessages::RuntimeApi( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index a577eda18e1b..6c55c92681eb 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -327,34 +327,50 @@ impl Collations { /// /// If prospective parachains mode is not enabled then we fall back to synchronous backing. In /// this case there is a limit of 1 collation per relay parent. + /// + /// If prospective parachains mode is enabled but claim queue is not supported then up to + /// `max_candidate_depth + 1` seconded collations are supported. In theory in this case if two + /// parachains are sharing a core no fairness is guaranteed between them and the faster one can + /// starve the slower one by exhausting the limit with its own advertisements. In practice this + /// should not happen because core sharing implies core time support which implies the claim + /// queue is available. pub(super) fn is_collations_limit_reached( &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, num_pending_fetches: usize, ) -> bool { - if let ProspectiveParachainsMode::Disabled = relay_parent_mode { - // fallback to synchronous backing - return self.seconded_count >= 1 - } + match relay_parent_mode { + ProspectiveParachainsMode::Disabled => return self.seconded_count >= 1, + ProspectiveParachainsMode::Enabled { + max_candidate_depth, + allowed_ancestry_len: _, + claim_queue_support, + } if !claim_queue_support => self.seconded_count >= max_candidate_depth + 1, + ProspectiveParachainsMode::Enabled { + max_candidate_depth: _, + allowed_ancestry_len: _, + claim_queue_support: _, + } => { + // Successful fetches + pending fetches < claim queue entries for `para_id` + let respected_per_para_limit = + self.claims_per_para.get(¶_id).copied().unwrap_or_default() > + self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + + num_pending_fetches; - // Successful fetches + pending fetches < claim queue entries for `para_id` - let respected_per_para_limit = - self.claims_per_para.get(¶_id).copied().unwrap_or_default() > - self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + - num_pending_fetches; - - gum::trace!( - target: LOG_TARGET, - ?para_id, - claims_per_para=?self.claims_per_para, - fetched_per_para=?self.fetched_per_para, - ?num_pending_fetches, - ?respected_per_para_limit, - "is_collations_limit_reached" - ); + gum::trace!( + target: LOG_TARGET, + ?para_id, + claims_per_para=?self.claims_per_para, + fetched_per_para=?self.fetched_per_para, + ?num_pending_fetches, + ?respected_per_para_limit, + "is_collations_limit_reached" + ); - !respected_per_para_limit + !respected_per_para_limit + }, + } } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't @@ -377,6 +393,10 @@ impl Collations { /// /// If two parachains has got the same score the one which is earlier in the claim queue will be /// picked. + /// + /// If claim queue is not supported then `group_assignment` should contain just one element and + /// the score won't matter. In this case collations will be fetched in the order they were + /// received. fn pick_a_collation_to_fetch( &mut self, group_assignments: &Vec, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index cdc1cda070f2..11b12a52efdf 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -13,8 +13,11 @@ fn cant_add_more_than_claim_queue() { let para_a = ParaId::from(1); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let relay_parent_mode = ProspectiveParachainsMode::Enabled { + max_candidate_depth: 4, + allowed_ancestry_len: 3, + claim_queue_support: true, + }; let mut collations = Collations::new(&assignments); @@ -51,8 +54,11 @@ fn pending_fetches_are_counted() { let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let relay_parent_mode = ProspectiveParachainsMode::Enabled { + max_candidate_depth: 4, + allowed_ancestry_len: 3, + claim_queue_support: true, + }; let mut collations = Collations::new(&assignments); collations.fetching_from = Some((collator_id_a, None)); @@ -79,8 +85,11 @@ fn collation_fetching_respects_claim_queue() { let claim_queue = vec![para_a, para_b, para_a]; let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let relay_parent_mode = ProspectiveParachainsMode::Enabled { + max_candidate_depth: 4, + allowed_ancestry_len: 3, + claim_queue_support: true, + }; collations.fetching_from = None; @@ -173,8 +182,11 @@ fn collation_fetching_fallback_works() { let claim_queue = vec![para_a]; let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let relay_parent_mode = ProspectiveParachainsMode::Enabled { + max_candidate_depth: 4, + allowed_ancestry_len: 3, + claim_queue_support: false, + }; collations.fetching_from = None; @@ -206,6 +218,7 @@ fn collation_fetching_fallback_works() { collator_id_a.clone(), ); + // Collations will be fetched in the order they were added collations.add_to_waiting_queue(collation_a1.clone()); collations.add_to_waiting_queue(collation_a2.clone()); @@ -246,8 +259,11 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let claim_queue = vec![para_a, para_b, para_a, para_b]; let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; + let relay_parent_mode = ProspectiveParachainsMode::Enabled { + max_candidate_depth: 5, + allowed_ancestry_len: 4, + claim_queue_support: true, + }; collations.fetching_from = None; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 70d189334a95..91208176747c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -132,6 +132,23 @@ pub(super) async fn update_view( } ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::Version(tx), + )) => { + match test_state.claim_queue { + Some(_) => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + }, + None => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)); + } + } + } + ); + assert_assign_incoming( virtual_overseer, test_state, @@ -1660,7 +1677,7 @@ fn collation_fetches_without_claimqueue() { ); // in fallback mode up to `max_candidate_depth` collations are accepted - for i in 0..test_state.async_backing_params.max_candidate_depth { + for i in 0..test_state.async_backing_params.max_candidate_depth + 1 { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index 2c9ec8db3778..7902848b4937 100644 --- a/polkadot/node/subsystem-util/src/runtime/mod.rs +++ b/polkadot/node/subsystem-util/src/runtime/mod.rs @@ -38,11 +38,12 @@ use polkadot_primitives::{ }; use crate::{ - request_async_backing_params, request_availability_cores, request_candidate_events, - request_from_runtime, request_key_ownership_proof, request_on_chain_votes, - request_session_executor_params, request_session_index_for_child, request_session_info, - request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, - request_validator_groups, vstaging::get_disabled_validators_with_fallback, + has_required_runtime, request_async_backing_params, request_availability_cores, + request_candidate_events, request_from_runtime, request_key_ownership_proof, + request_on_chain_votes, request_session_executor_params, request_session_index_for_child, + request_session_info, request_submit_report_dispute_lost, request_unapplied_slashes, + request_validation_code_by_hash, request_validator_groups, + vstaging::get_disabled_validators_with_fallback, }; /// Errors that can happen on runtime fetches. @@ -481,6 +482,8 @@ pub enum ProspectiveParachainsMode { /// How many ancestors of a relay parent are allowed to build candidates on top /// of. allowed_ancestry_len: usize, + /// Whether or not the runtime supports claim queue. + claim_queue_support: bool, }, } @@ -489,6 +492,14 @@ impl ProspectiveParachainsMode { pub fn is_enabled(&self) -> bool { matches!(self, ProspectiveParachainsMode::Enabled { .. }) } + + /// Returns true if ProspectiveParachainsMode is enabled and has got claim queue support + pub fn has_claim_queue_support(&self) -> bool { + match self { + ProspectiveParachainsMode::Enabled { claim_queue_support, .. } => *claim_queue_support, + ProspectiveParachainsMode::Disabled => false, + } + } } /// Requests prospective parachains mode for a given relay parent based on @@ -515,9 +526,16 @@ where Ok(ProspectiveParachainsMode::Disabled) } else { let AsyncBackingParams { max_candidate_depth, allowed_ancestry_len } = result?; + let claim_queue_support = has_required_runtime( + sender, + relay_parent, + RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT, + ) + .await; Ok(ProspectiveParachainsMode::Enabled { max_candidate_depth: max_candidate_depth as _, allowed_ancestry_len: allowed_ancestry_len as _, + claim_queue_support, }) } } From a10c86d5eca005ffcfbdfe4c468c84c61bf616a4 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 14:49:32 +0300 Subject: [PATCH 017/138] Separate `claim_queue_support` from `ProspectiveParachainsMode` --- polkadot/node/core/backing/src/lib.rs | 5 +- .../tests/prospective_parachains.rs | 20 ------- .../src/validator_side/collation.rs | 14 ++--- .../src/validator_side/mod.rs | 20 +++++-- .../src/validator_side/tests/collation.rs | 54 ++++++++----------- .../src/validator_side/tests/mod.rs | 9 ++++ .../node/subsystem-util/src/runtime/mod.rs | 28 ++-------- 7 files changed, 63 insertions(+), 87 deletions(-) diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 1bda81c5197e..9498e0d657e0 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -281,7 +281,10 @@ impl From<&ActiveLeafState> for ProspectiveParachainsMode { ActiveLeafState::ProspectiveParachainsEnabled { max_candidate_depth, allowed_ancestry_len, - } => ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len }, + } => ProspectiveParachainsMode::Enabled { + max_candidate_depth, + allowed_ancestry_len, + }, } } } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index 9e68247d5b39..ea8fdb0e04fb 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -58,16 +58,6 @@ async fn update_view( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Version(tx), - )) => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); - } - ); - let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); let ancestry_len = leaf_number + 1 - min_number; @@ -106,16 +96,6 @@ async fn update_view( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Version(tx), - )) => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); - } - ); - assert_matches!( overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(50)).await.unwrap(), AllMessages::RuntimeApi( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 6c55c92681eb..6ec18a2946f3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -245,6 +245,8 @@ pub struct Collations { // from `GroupAssignments` which contains either the claim queue for the core or the `ParaId` // of the parachain assigned to the core. claims_per_para: BTreeMap, + // Whether the runtime supports claim queue runtime api. + has_claim_queue: bool, } impl Collations { @@ -255,7 +257,7 @@ impl Collations { /// doesn't support claim queue `GroupAssignments` contains only one entry - the `ParaId` of the /// parachain assigned to the core. This way we can handle both cases with a single /// implementation and avoid code duplication. - pub(super) fn new(group_assignments: &Vec) -> Self { + pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { let mut claims_per_para = BTreeMap::new(); for para_id in group_assignments { *claims_per_para.entry(*para_id).or_default() += 1; @@ -268,6 +270,7 @@ impl Collations { seconded_count: 0, fetched_per_para: Default::default(), claims_per_para, + has_claim_queue, } } @@ -342,15 +345,12 @@ impl Collations { ) -> bool { match relay_parent_mode { ProspectiveParachainsMode::Disabled => return self.seconded_count >= 1, - ProspectiveParachainsMode::Enabled { - max_candidate_depth, - allowed_ancestry_len: _, - claim_queue_support, - } if !claim_queue_support => self.seconded_count >= max_candidate_depth + 1, + ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len: _ } + if !self.has_claim_queue => + self.seconded_count >= max_candidate_depth + 1, ProspectiveParachainsMode::Enabled { max_candidate_depth: _, allowed_ancestry_len: _, - claim_queue_support: _, } => { // Successful fetches + pending fetches < claim queue entries for `para_id` let respected_per_para_limit = diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 437825033dc8..fc57f7795cb5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -43,12 +43,13 @@ use polkadot_node_subsystem::{ messages::{ CanSecondRequest, CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, ProspectiveParachainsMessage, - ProspectiveValidationDataRequest, + ProspectiveValidationDataRequest, RuntimeApiRequest, }, overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, PerLeafSpan, }; use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, + has_required_runtime, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, vstaging::fetch_claim_queue, @@ -369,8 +370,12 @@ struct PerRelayParent { } impl PerRelayParent { - fn new(mode: ProspectiveParachainsMode, assignments: GroupAssignments) -> Self { - let collations = Collations::new(&assignments.current); + fn new( + mode: ProspectiveParachainsMode, + assignments: GroupAssignments, + has_claim_queue: bool, + ) -> Self { + let collations = Collations::new(&assignments.current, has_claim_queue); Self { prospective_parachains_mode: mode, assignment: assignments, collations } } } @@ -1262,6 +1267,9 @@ where for leaf in added { let mode = prospective_parachains_mode(sender, *leaf).await?; + let has_claim_queue_support = + has_required_runtime(sender, *leaf, RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT) + .await; if let Some(span) = view.span_per_head().get(leaf).cloned() { let per_leaf_span = PerLeafSpan::new(span, "validator-side"); @@ -1272,7 +1280,9 @@ where assign_incoming(sender, &mut state.current_assignments, keystore, *leaf, mode).await?; state.active_leaves.insert(*leaf, mode); - state.per_relay_parent.insert(*leaf, PerRelayParent::new(mode, assignments)); + state + .per_relay_parent + .insert(*leaf, PerRelayParent::new(mode, assignments, has_claim_queue_support)); if mode.is_enabled() { state @@ -1297,7 +1307,7 @@ where ) .await?; - entry.insert(PerRelayParent::new(mode, assignments)); + entry.insert(PerRelayParent::new(mode, assignments, has_claim_queue_support)); } } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 11b12a52efdf..b0a252d9ef0b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -13,16 +13,14 @@ fn cant_add_more_than_claim_queue() { let para_a = ParaId::from(1); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = ProspectiveParachainsMode::Enabled { - max_candidate_depth: 4, - allowed_ancestry_len: 3, - claim_queue_support: true, - }; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let claim_queue_support = true; - let mut collations = Collations::new(&assignments); + let mut collations = Collations::new(&assignments, claim_queue_support); // first collation for `para_a` is in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0,)); collations.note_fetched(para_a); // and `para_b` is not affected assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); @@ -54,13 +52,11 @@ fn pending_fetches_are_counted() { let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = ProspectiveParachainsMode::Enabled { - max_candidate_depth: 4, - allowed_ancestry_len: 3, - claim_queue_support: true, - }; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let claim_queue_support = true; - let mut collations = Collations::new(&assignments); + let mut collations = Collations::new(&assignments, claim_queue_support); collations.fetching_from = Some((collator_id_a, None)); // first collation for `para_a` is in the limit @@ -84,12 +80,11 @@ fn collation_fetching_respects_claim_queue() { let peer_b = PeerId::random(); let claim_queue = vec![para_a, para_b, para_a]; - let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = ProspectiveParachainsMode::Enabled { - max_candidate_depth: 4, - allowed_ancestry_len: 3, - claim_queue_support: true, - }; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let claim_queue_support = true; + + let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; @@ -181,12 +176,11 @@ fn collation_fetching_fallback_works() { let peer_a = PeerId::random(); let claim_queue = vec![para_a]; - let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = ProspectiveParachainsMode::Enabled { - max_candidate_depth: 4, - allowed_ancestry_len: 3, - claim_queue_support: false, - }; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let claim_queue_support = false; + + let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; @@ -258,13 +252,11 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let peer_b = PeerId::random(); let claim_queue = vec![para_a, para_b, para_a, para_b]; - let mut collations = Collations::new(&claim_queue); - let relay_parent_mode = ProspectiveParachainsMode::Enabled { - max_candidate_depth: 5, - allowed_ancestry_len: 4, - claim_queue_support: true, - }; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; + let claim_queue_support = true; + let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; let relay_parent = Hash::repeat_byte(0x01); diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 7243af9fe4ca..162bde1a41e4 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -564,6 +564,15 @@ async fn assert_async_backing_params_request(virtual_overseer: &mut VirtualOvers tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); } ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::Version(tx), + )) => { + let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + } + ); } // As we receive a relevant advertisement act on it and issue a collation request. diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index 7902848b4937..2c9ec8db3778 100644 --- a/polkadot/node/subsystem-util/src/runtime/mod.rs +++ b/polkadot/node/subsystem-util/src/runtime/mod.rs @@ -38,12 +38,11 @@ use polkadot_primitives::{ }; use crate::{ - has_required_runtime, request_async_backing_params, request_availability_cores, - request_candidate_events, request_from_runtime, request_key_ownership_proof, - request_on_chain_votes, request_session_executor_params, request_session_index_for_child, - request_session_info, request_submit_report_dispute_lost, request_unapplied_slashes, - request_validation_code_by_hash, request_validator_groups, - vstaging::get_disabled_validators_with_fallback, + request_async_backing_params, request_availability_cores, request_candidate_events, + request_from_runtime, request_key_ownership_proof, request_on_chain_votes, + request_session_executor_params, request_session_index_for_child, request_session_info, + request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, + request_validator_groups, vstaging::get_disabled_validators_with_fallback, }; /// Errors that can happen on runtime fetches. @@ -482,8 +481,6 @@ pub enum ProspectiveParachainsMode { /// How many ancestors of a relay parent are allowed to build candidates on top /// of. allowed_ancestry_len: usize, - /// Whether or not the runtime supports claim queue. - claim_queue_support: bool, }, } @@ -492,14 +489,6 @@ impl ProspectiveParachainsMode { pub fn is_enabled(&self) -> bool { matches!(self, ProspectiveParachainsMode::Enabled { .. }) } - - /// Returns true if ProspectiveParachainsMode is enabled and has got claim queue support - pub fn has_claim_queue_support(&self) -> bool { - match self { - ProspectiveParachainsMode::Enabled { claim_queue_support, .. } => *claim_queue_support, - ProspectiveParachainsMode::Disabled => false, - } - } } /// Requests prospective parachains mode for a given relay parent based on @@ -526,16 +515,9 @@ where Ok(ProspectiveParachainsMode::Disabled) } else { let AsyncBackingParams { max_candidate_depth, allowed_ancestry_len } = result?; - let claim_queue_support = has_required_runtime( - sender, - relay_parent, - RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT, - ) - .await; Ok(ProspectiveParachainsMode::Enabled { max_candidate_depth: max_candidate_depth as _, allowed_ancestry_len: allowed_ancestry_len as _, - claim_queue_support, }) } } From b39858aaafb6fed18767eb8567e5a26b03246013 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 15:01:00 +0300 Subject: [PATCH 018/138] Fix comments and add logs --- .../src/validator_side/collation.rs | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 6ec18a2946f3..dc803da7506d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -242,8 +242,8 @@ pub struct Collations { /// What collations were fetched so far for this relay parent. fetched_per_para: BTreeMap, // Claims per `ParaId` for the assigned core at the relay parent. This information is obtained - // from `GroupAssignments` which contains either the claim queue for the core or the `ParaId` - // of the parachain assigned to the core. + // from `GroupAssignments` which contains either the claim queue (if runtime supports it) for + // the core or the `ParaId` of the parachain assigned to the core. claims_per_para: BTreeMap, // Whether the runtime supports claim queue runtime api. has_claim_queue: bool, @@ -255,8 +255,7 @@ impl Collations { /// uses `GroupAssignments`. If the runtime supports claim queue `GroupAssignments` contains the /// claim queue for the core assigned to the (backing group of the) validator. If the runtime /// doesn't support claim queue `GroupAssignments` contains only one entry - the `ParaId` of the - /// parachain assigned to the core. This way we can handle both cases with a single - /// implementation and avoid code duplication. + /// parachain assigned to the core. pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { let mut claims_per_para = BTreeMap::new(); for para_id in group_assignments { @@ -332,7 +331,7 @@ impl Collations { /// this case there is a limit of 1 collation per relay parent. /// /// If prospective parachains mode is enabled but claim queue is not supported then up to - /// `max_candidate_depth + 1` seconded collations are supported. In theory in this case if two + /// `max_candidate_depth + 1` seconded collations are accepted. In theory in this case if two /// parachains are sharing a core no fairness is guaranteed between them and the faster one can /// starve the slower one by exhausting the limit with its own advertisements. In practice this /// should not happen because core sharing implies core time support which implies the claim @@ -344,10 +343,29 @@ impl Collations { num_pending_fetches: usize, ) -> bool { match relay_parent_mode { - ProspectiveParachainsMode::Disabled => return self.seconded_count >= 1, + ProspectiveParachainsMode::Disabled => { + gum::trace!( + target: LOG_TARGET, + ?para_id, + seconded_count=self.seconded_count, + "is_collations_limit_reached - ProspectiveParachainsMode::Disabled" + ); + + self.seconded_count >= 1 + }, ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len: _ } if !self.has_claim_queue => - self.seconded_count >= max_candidate_depth + 1, + { + gum::trace!( + target: LOG_TARGET, + ?para_id, + seconded_count=self.seconded_count, + max_candidate_depth, + "is_collations_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" + ); + + self.seconded_count >= max_candidate_depth + 1 + }, ProspectiveParachainsMode::Enabled { max_candidate_depth: _, allowed_ancestry_len: _, @@ -365,7 +383,7 @@ impl Collations { fetched_per_para=?self.fetched_per_para, ?num_pending_fetches, ?respected_per_para_limit, - "is_collations_limit_reached" + "is_collations_limit_reached - ProspectiveParachainsMode::Enabled with claim queue support" ); !respected_per_para_limit @@ -375,7 +393,7 @@ impl Collations { /// Adds a new collation to the waiting queue for the relay parent. This function doesn't /// perform any limits check. The caller (`enqueue_collation`) should assure that the collation - /// can be enqueued. + /// limit is respected. pub(super) fn add_to_waiting_queue(&mut self, collation: (PendingCollation, CollatorId)) { self.waiting_queue.entry(collation.0.para_id).or_default().push_back(collation); } @@ -383,15 +401,14 @@ impl Collations { /// Picks a collation to fetch from the waiting queue. /// When fetching collations we need to ensure that each parachain has got a fair core time /// share depending on its assignments in the claim queue. This means that the number of - /// collations fetched per parachain should ideally be equal to (but not bigger than) the number - /// of claims for the particular parachain in the claim queue. + /// collations fetched per parachain should ideally be equal to the number of claims for the + /// particular parachain in the claim queue. /// /// To achieve this each parachain with at an entry in the `waiting_queue` has got a score /// calculated by dividing the number of fetched collations by the number of entries in the - /// claim queue. Lower the score means higher fetching priority. Note that if a parachain hasn't - /// got anything fetched at this relay parent it will have score 0 which means highest priority. - /// - /// If two parachains has got the same score the one which is earlier in the claim queue will be + /// claim queue. Lower score means higher fetching priority. Note that if a parachain hasn't got + /// anything fetched at this relay parent it will have score 0 which means highest priority. If + /// two parachains has got the same score the one which is earlier in the claim queue will be /// picked. /// /// If claim queue is not supported then `group_assignment` should contain just one element and From b30f3407a508aa67c481066c6e6fde262780924f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 15:11:58 +0300 Subject: [PATCH 019/138] Update test: `collation_fetching_prefer_entries_earlier_in_claim_queue` --- .../src/validator_side/tests/collation.rs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index b0a252d9ef0b..548e3c90a413 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -251,9 +251,13 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); let peer_b = PeerId::random(); - let claim_queue = vec![para_a, para_b, para_a, para_b]; + let para_c = ParaId::from(3); + let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); + let peer_c = PeerId::random(); + + let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; + ProspectiveParachainsMode::Enabled { max_candidate_depth: 6, allowed_ancestry_len: 5 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -313,7 +317,35 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collator_id_b.clone(), ); - // Despite the order here the fetches should be a1, b1, a2, b2 + let collation_c1 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(5)), + parent_head_data_hash: Hash::repeat_byte(5), + }), + ), + collator_id_c.clone(), + ); + + let collation_c2 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(6)), + parent_head_data_hash: Hash::repeat_byte(6), + }), + ), + collator_id_c.clone(), + ); + + // Despite the order here the fetches should be a1, b1, c1, a2, b2, c2 + collations.add_to_waiting_queue(collation_c1.clone()); + collations.add_to_waiting_queue(collation_c2.clone()); collations.add_to_waiting_queue(collation_b1.clone()); collations.add_to_waiting_queue(collation_b2.clone()); collations.add_to_waiting_queue(collation_a1.clone()); @@ -341,6 +373,17 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { ); collations.note_fetched(collation_b1.0.para_id); + assert_eq!( + Some(collation_c1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c1.0.para_id); + assert_eq!( Some(collation_a2.clone()), collations.get_next_collation_to_fetch( @@ -362,4 +405,15 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { ) ); collations.note_fetched(collation_b2.0.para_id); + + assert_eq!( + Some(collation_c2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c2.0.para_id); } From c0f18b99c43537463fc18abbfe894bee27146084 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 3 Jul 2024 15:27:13 +0300 Subject: [PATCH 020/138] Fix `pick_a_collation_to_fetch` and more tests --- .../src/validator_side/collation.rs | 3 +- .../src/validator_side/tests/collation.rs | 458 +++++++++++++++++- 2 files changed, 459 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index dc803da7506d..d2db184d5298 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -448,9 +448,10 @@ impl Collations { match lowest_score { Some((score, _)) if para_score < score => lowest_score = Some((para_score, vec![(para_id, collations)])), - Some((_, ref mut paras)) => { + Some((score, ref mut paras)) if score == para_score => { paras.push((para_id, collations)); }, + Some(_) => continue, None => lowest_score = Some((para_score, vec![(para_id, collations)])), } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 548e3c90a413..c00451742830 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -257,7 +257,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 6, allowed_ancestry_len: 5 }; + ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -417,3 +417,459 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { ); collations.note_fetched(collation_c2.0.para_id); } + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { + sp_tracing::init_for_tests(); + + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let peer_a = PeerId::random(); + + let para_b = ParaId::from(2); + let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); + let peer_b = PeerId::random(); + + let para_c = ParaId::from(3); + let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); + let peer_c = PeerId::random(); + + let claim_queue = vec![para_a, para_b, para_a, para_b, para_a, para_b, para_c, para_c]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 9, allowed_ancestry_len: 8 }; + let claim_queue_support = true; + + let mut collations = Collations::new(&claim_queue, claim_queue_support); + collations.fetching_from = None; + + let relay_parent = Hash::repeat_byte(0x01); + + let collation_a1 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(1)), + parent_head_data_hash: Hash::repeat_byte(1), + }), + ), + collator_id_a.clone(), + ); + + let collation_a2 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(2)), + parent_head_data_hash: Hash::repeat_byte(2), + }), + ), + collator_id_a.clone(), + ); + + let collation_a3 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), + }), + ), + collator_id_a.clone(), + ); + + let collation_b1 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(4)), + parent_head_data_hash: Hash::repeat_byte(4), + }), + ), + collator_id_b.clone(), + ); + + let collation_b2 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(5)), + parent_head_data_hash: Hash::repeat_byte(5), + }), + ), + collator_id_b.clone(), + ); + + let collation_b3 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(6)), + parent_head_data_hash: Hash::repeat_byte(6), + }), + ), + collator_id_b.clone(), + ); + + let collation_c1 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(7)), + parent_head_data_hash: Hash::repeat_byte(7), + }), + ), + collator_id_c.clone(), + ); + + let collation_c2 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(8)), + parent_head_data_hash: Hash::repeat_byte(8), + }), + ), + collator_id_c.clone(), + ); + + collations.add_to_waiting_queue(collation_c1.clone()); + collations.add_to_waiting_queue(collation_c2.clone()); + collations.add_to_waiting_queue(collation_b1.clone()); + collations.add_to_waiting_queue(collation_b2.clone()); + collations.add_to_waiting_queue(collation_b3.clone()); + collations.add_to_waiting_queue(collation_a1.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + collations.add_to_waiting_queue(collation_a3.clone()); + + assert_eq!( + Some(collation_a1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a1.0.para_id); + + assert_eq!( + Some(collation_b1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b1.0.para_id); + + assert_eq!( + Some(collation_c1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c1.0.para_id); + + assert_eq!( + Some(collation_a2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a2.0.para_id); + + assert_eq!( + Some(collation_b2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b2.0.para_id); + + assert_eq!( + Some(collation_c2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c2.0.para_id); + + assert_eq!( + Some(collation_a3.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a3.0.para_id); + + assert_eq!( + Some(collation_b3.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b3.0.para_id); +} + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { + sp_tracing::init_for_tests(); + + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + let peer_a = PeerId::random(); + + let para_b = ParaId::from(2); + let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); + let peer_b = PeerId::random(); + + let para_c = ParaId::from(3); + let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); + let peer_c = PeerId::random(); + + let claim_queue = vec![para_a, para_a, para_a, para_a, para_b, para_b, para_c, para_c]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 9, allowed_ancestry_len: 8 }; + let claim_queue_support = true; + + let mut collations = Collations::new(&claim_queue, claim_queue_support); + collations.fetching_from = None; + + let relay_parent = Hash::repeat_byte(0x01); + + let collation_a1 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(1)), + parent_head_data_hash: Hash::repeat_byte(1), + }), + ), + collator_id_a.clone(), + ); + + let collation_a2 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(2)), + parent_head_data_hash: Hash::repeat_byte(2), + }), + ), + collator_id_a.clone(), + ); + + let collation_a3 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), + }), + ), + collator_id_a.clone(), + ); + + let collation_a4 = ( + PendingCollation::new( + relay_parent, + para_a, + &peer_a, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(4)), + parent_head_data_hash: Hash::repeat_byte(4), + }), + ), + collator_id_a.clone(), + ); + + let collation_b1 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(5)), + parent_head_data_hash: Hash::repeat_byte(5), + }), + ), + collator_id_b.clone(), + ); + + let collation_b2 = ( + PendingCollation::new( + relay_parent, + para_b, + &peer_b, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(6)), + parent_head_data_hash: Hash::repeat_byte(6), + }), + ), + collator_id_b.clone(), + ); + + let collation_c1 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(7)), + parent_head_data_hash: Hash::repeat_byte(7), + }), + ), + collator_id_c.clone(), + ); + + let collation_c2 = ( + PendingCollation::new( + relay_parent, + para_c, + &peer_c, + Some(ProspectiveCandidate { + candidate_hash: CandidateHash(Hash::repeat_byte(8)), + parent_head_data_hash: Hash::repeat_byte(8), + }), + ), + collator_id_c.clone(), + ); + + collations.add_to_waiting_queue(collation_c1.clone()); + collations.add_to_waiting_queue(collation_c2.clone()); + collations.add_to_waiting_queue(collation_b1.clone()); + collations.add_to_waiting_queue(collation_b2.clone()); + collations.add_to_waiting_queue(collation_a1.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + collations.add_to_waiting_queue(collation_a3.clone()); + collations.add_to_waiting_queue(collation_a4.clone()); + + assert_eq!( + Some(collation_a1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a1.0.para_id); + + assert_eq!( + Some(collation_b1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b1.0.para_id); + + assert_eq!( + Some(collation_c1.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c1.0.para_id); + + assert_eq!( + Some(collation_a2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a2.0.para_id); + + assert_eq!( + Some(collation_a3.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a3.0.para_id); + + assert_eq!( + Some(collation_b2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_b2.0.para_id); + + assert_eq!( + Some(collation_c2.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_c2.0.para_id); + + assert_eq!( + Some(collation_a4.clone()), + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode.clone(), + &claim_queue, + ) + ); + collations.note_fetched(collation_a4.0.para_id); +} From fba7ca69abf830d559b6f84805fd67d85ced70db Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 09:54:34 +0300 Subject: [PATCH 021/138] Fix `pick_a_collation_to_fetch` - iter 1 --- .../src/validator_side/collation.rs | 84 +++--- .../src/validator_side/mod.rs | 36 ++- .../src/validator_side/tests/collation.rs | 270 +++++------------- .../src/validator_side/tests/mod.rs | 2 +- 4 files changed, 143 insertions(+), 249 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index d2db184d5298..8c3dba5f959f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -294,6 +294,7 @@ impl Collations { finished_one: &(CollatorId, Option), relay_parent_mode: ProspectiveParachainsMode, group_assignments: &Vec, + pending_fetches: &BTreeMap, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. @@ -318,7 +319,8 @@ impl Collations { // `Waiting` so that we can fetch more collations. If async backing is disabled we can't // fetch more than one collation per relay parent so `None` is returned. CollationStatus::Seconded => None, - CollationStatus::Waiting => self.pick_a_collation_to_fetch(&group_assignments), + CollationStatus::Waiting => + self.pick_a_collation_to_fetch(&group_assignments, pending_fetches), CollationStatus::WaitingOnValidation | CollationStatus::Fetching => unreachable!("We have reset the status above!"), } @@ -417,6 +419,7 @@ impl Collations { fn pick_a_collation_to_fetch( &mut self, group_assignments: &Vec, + pending_fetches: &BTreeMap, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, @@ -427,51 +430,56 @@ impl Collations { "Pick a collation to fetch." ); - // Find the parachain(s) with the lowest score. - let mut lowest_score = None; - for (para_id, collations) in &mut self.waiting_queue { - let para_score = self - .fetched_per_para - .get(para_id) - .copied() - .map(|v| { - (v as f64) / - (self.claims_per_para.get(para_id).copied().unwrap_or_default() as f64) - }) - .unwrap_or_default(); - - // skip empty queues - if collations.is_empty() { - continue + if !self.has_claim_queue { + if let Some(assigned_para_id) = group_assignments.first() { + return self + .waiting_queue + .get_mut(assigned_para_id) + .map(|collations| collations.pop_front()) + .flatten() + } else { + unreachable!("Group assignments should contain at least one element.") } + } - match lowest_score { - Some((score, _)) if para_score < score => - lowest_score = Some((para_score, vec![(para_id, collations)])), - Some((score, ref mut paras)) if score == para_score => { - paras.push((para_id, collations)); - }, - Some(_) => continue, - None => lowest_score = Some((para_score, vec![(para_id, collations)])), + let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); + let mut fetched_per_para = self.fetched_per_para.clone(); + let mut pending_fetches = pending_fetches.clone(); + + for assignment in group_assignments { + if let Some(fetched) = fetched_per_para.get_mut(assignment) { + if *fetched > 0 { + claim_queue_state.push((true, assignment)); + *fetched -= 1; + continue; + } } + + if let Some(pending_fetches) = pending_fetches.get_mut(assignment) { + if *pending_fetches > 0 { + claim_queue_state.push((true, assignment)); + *pending_fetches -= 1; + continue; + } + } + + claim_queue_state.push((false, assignment)); } - if let Some((_, mut lowest_score)) = lowest_score { - for claim in group_assignments { - if let Some((_, collations)) = lowest_score.iter_mut().find(|(id, _)| *id == claim) - { - match collations.pop_front() { - Some(collation) => return Some(collation), - None => { - unreachable!("Collation can't be empty because empty ones are skipped at the beginning of the loop.") - }, - } + for (fulfilled, assignment) in &mut claim_queue_state { + if *fulfilled { + continue + } + + if let Some(collations) = self.waiting_queue.get_mut(assignment) { + if let Some(collation) = collations.pop_front() { + *fulfilled = true; + return Some(collation) } } - unreachable!("All entries in waiting_queue should also be in claim queue") - } else { - None } + + None } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index fc57f7795cb5..63062900a0c3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -19,7 +19,7 @@ use futures::{ }; use futures_timer::Delay; use std::{ - collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, + collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}, future::Future, time::{Duration, Instant}, }; @@ -1738,13 +1738,16 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { - while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|state| { - state.collations.get_next_collation_to_fetch( - &previous_fetch, - state.prospective_parachains_mode, - &state.assignment.current, - ) - }) { + let pending_collations = pending_collations_per_para_at_relay_parent(state, relay_parent); + while let Some((next, id)) = + state.per_relay_parent.get_mut(&relay_parent).and_then(|rp_state| { + rp_state.collations.get_next_collation_to_fetch( + &previous_fetch, + rp_state.prospective_parachains_mode, + &rp_state.assignment.current, + &pending_collations, + ) + }) { gum::debug!( target: LOG_TARGET, ?relay_parent, @@ -2117,7 +2120,7 @@ async fn handle_collation_fetch_response( result } -// Returns the number of pending fetches for `ParaId` at a specific relay parent. +// Returns the number of pending fetches for `ParaId` at the specified relay parent. fn num_pending_collations_for_para_at_relay_parent( state: &State, para_id: ParaId, @@ -2131,3 +2134,18 @@ fn num_pending_collations_for_para_at_relay_parent( }) .count() } + +// Returns the number of pending fetches for each `ParaId` at the specified relay parent. +fn pending_collations_per_para_at_relay_parent( + state: &State, + relay_parent: Hash, +) -> BTreeMap { + state + .collation_requests_cancel_handles + .iter() + .filter(|(col, _)| col.relay_parent == relay_parent) + .fold(BTreeMap::new(), |mut res, (col, _)| { + *res.entry(col.para_id).or_default() += 1; + res + }) +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index c00451742830..143cdf8fed36 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; use polkadot_primitives::{CandidateHash, CollatorId, Hash, Id as ParaId}; @@ -83,6 +85,7 @@ fn collation_fetching_respects_claim_queue() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = true; + let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -140,6 +143,7 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending ) ); collations.note_fetched(collation_a1.0.para_id); @@ -151,6 +155,7 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending ) ); collations.note_fetched(collation_b1.0.para_id); @@ -162,6 +167,7 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending ) ); collations.note_fetched(collation_a2.0.para_id); @@ -179,6 +185,7 @@ fn collation_fetching_fallback_works() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = false; + let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -223,6 +230,7 @@ fn collation_fetching_fallback_works() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_a1.0.para_id); @@ -234,6 +242,7 @@ fn collation_fetching_fallback_works() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_a2.0.para_id); @@ -259,6 +268,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; + let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; @@ -358,6 +368,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_a1.0.para_id); @@ -369,42 +380,46 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_b1.0.para_id); assert_eq!( - Some(collation_c1.clone()), + Some(collation_a2.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_c1.0.para_id); + collations.note_fetched(collation_a2.0.para_id); assert_eq!( - Some(collation_a2.clone()), + Some(collation_b2.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_a2.0.para_id); + collations.note_fetched(collation_b2.0.para_id); assert_eq!( - Some(collation_b2.clone()), + Some(collation_c1.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_b2.0.para_id); + collations.note_fetched(collation_c1.0.para_id); assert_eq!( Some(collation_c2.clone()), @@ -413,13 +428,14 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_c2.0.para_id); } #[test] -fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { +fn collation_fetching_fills_holes_in_claim_queue() { sp_tracing::init_for_tests(); let para_a = ParaId::from(1); @@ -434,10 +450,11 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); let peer_c = PeerId::random(); - let claim_queue = vec![para_a, para_b, para_a, para_b, para_a, para_b, para_c, para_c]; + let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 9, allowed_ancestry_len: 8 }; + ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; + let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; @@ -470,27 +487,14 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { collator_id_a.clone(), ); - let collation_a3 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_a.clone(), - ); - let collation_b1 = ( PendingCollation::new( relay_parent, para_b, &peer_b, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(4)), - parent_head_data_hash: Hash::repeat_byte(4), + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), }), ), collator_id_b.clone(), @@ -502,21 +506,8 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { para_b, &peer_b, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(5)), - parent_head_data_hash: Hash::repeat_byte(5), - }), - ), - collator_id_b.clone(), - ); - - let collation_b3 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(6)), - parent_head_data_hash: Hash::repeat_byte(6), + candidate_hash: CandidateHash(Hash::repeat_byte(4)), + parent_head_data_hash: Hash::repeat_byte(4), }), ), collator_id_b.clone(), @@ -528,8 +519,8 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { para_c, &peer_c, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(7)), - parent_head_data_hash: Hash::repeat_byte(7), + candidate_hash: CandidateHash(Hash::repeat_byte(5)), + parent_head_data_hash: Hash::repeat_byte(5), }), ), collator_id_c.clone(), @@ -541,21 +532,16 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { para_c, &peer_c, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(8)), - parent_head_data_hash: Hash::repeat_byte(8), + candidate_hash: CandidateHash(Hash::repeat_byte(6)), + parent_head_data_hash: Hash::repeat_byte(6), }), ), collator_id_c.clone(), ); + // Despite the order here the fetches should be a1, b1, c1, a2, b2, c2 collations.add_to_waiting_queue(collation_c1.clone()); - collations.add_to_waiting_queue(collation_c2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); - collations.add_to_waiting_queue(collation_b2.clone()); - collations.add_to_waiting_queue(collation_b3.clone()); collations.add_to_waiting_queue(collation_a1.clone()); - collations.add_to_waiting_queue(collation_a2.clone()); - collations.add_to_waiting_queue(collation_a3.clone()); assert_eq!( Some(collation_a1.clone()), @@ -564,21 +550,11 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_a1.0.para_id); - assert_eq!( - Some(collation_b1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_b1.0.para_id); - assert_eq!( Some(collation_c1.clone()), collations.get_next_collation_to_fetch( @@ -586,31 +562,25 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_c1.0.para_id); - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_a2.0.para_id); + collations.add_to_waiting_queue(collation_c2.clone()); + collations.add_to_waiting_queue(collation_b1.clone()); assert_eq!( - Some(collation_b2.clone()), + Some(collation_b1.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_b2.0.para_id); + collations.note_fetched(collation_b1.0.para_id); assert_eq!( Some(collation_c2.clone()), @@ -619,35 +589,41 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue2() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); collations.note_fetched(collation_c2.0.para_id); + collations.add_to_waiting_queue(collation_b2.clone()); + collations.add_to_waiting_queue(collation_a2.clone()); + assert_eq!( - Some(collation_a3.clone()), + Some(collation_a2.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_a3.0.para_id); + collations.note_fetched(collation_a2.0.para_id); assert_eq!( - Some(collation_b3.clone()), + Some(collation_b2.clone()), collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &pending, ) ); - collations.note_fetched(collation_b3.0.para_id); + collations.note_fetched(collation_b2.0.para_id); } #[test] -fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { +fn collation_fetching_takes_in_account_pending_items() { sp_tracing::init_for_tests(); let para_a = ParaId::from(1); @@ -658,13 +634,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); let peer_b = PeerId::random(); - let para_c = ParaId::from(3); - let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); - let peer_c = PeerId::random(); - - let claim_queue = vec![para_a, para_a, para_a, para_a, para_b, para_b, para_c, para_c]; + let claim_queue = vec![para_a, para_b, para_a, para_b]; let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 9, allowed_ancestry_len: 8 }; + ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -698,40 +670,14 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { collator_id_a.clone(), ); - let collation_a3 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_a.clone(), - ); - - let collation_a4 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(4)), - parent_head_data_hash: Hash::repeat_byte(4), - }), - ), - collator_id_a.clone(), - ); - let collation_b1 = ( PendingCollation::new( relay_parent, para_b, &peer_b, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(5)), - parent_head_data_hash: Hash::repeat_byte(5), + candidate_hash: CandidateHash(Hash::repeat_byte(3)), + parent_head_data_hash: Hash::repeat_byte(3), }), ), collator_id_b.clone(), @@ -743,58 +689,16 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { para_b, &peer_b, Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(6)), - parent_head_data_hash: Hash::repeat_byte(6), + candidate_hash: CandidateHash(Hash::repeat_byte(4)), + parent_head_data_hash: Hash::repeat_byte(4), }), ), collator_id_b.clone(), ); - let collation_c1 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(7)), - parent_head_data_hash: Hash::repeat_byte(7), - }), - ), - collator_id_c.clone(), - ); - - let collation_c2 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(8)), - parent_head_data_hash: Hash::repeat_byte(8), - }), - ), - collator_id_c.clone(), - ); - - collations.add_to_waiting_queue(collation_c1.clone()); - collations.add_to_waiting_queue(collation_c2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); - collations.add_to_waiting_queue(collation_b2.clone()); - collations.add_to_waiting_queue(collation_a1.clone()); + // a1 will be pending, a2 and b1 will be in the queue; b1 should be fetched first collations.add_to_waiting_queue(collation_a2.clone()); - collations.add_to_waiting_queue(collation_a3.clone()); - collations.add_to_waiting_queue(collation_a4.clone()); - - assert_eq!( - Some(collation_a1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_a1.0.para_id); + collations.add_to_waiting_queue(collation_b1.clone()); assert_eq!( Some(collation_b1.clone()), @@ -803,20 +707,13 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &BTreeMap::from([(para_a, 1)]), ) ); - collations.note_fetched(collation_b1.0.para_id); + collations.note_fetched(collation_a1.0.para_id); // a1 is no longer pending - assert_eq!( - Some(collation_c1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_c1.0.para_id); + // a1 is fetched, b1 is pending, a2 and b2 are in the queue, a2 should be fetched next + collations.add_to_waiting_queue(collation_b2.clone()); assert_eq!( Some(collation_a2.clone()), @@ -825,21 +722,13 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &BTreeMap::from([(para_b, 1)]), ) ); + collations.note_fetched(collation_b1.0.para_id); collations.note_fetched(collation_a2.0.para_id); - assert_eq!( - Some(collation_a3.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_a3.0.para_id); - + // and finally b2 should be fetched assert_eq!( Some(collation_b2.clone()), collations.get_next_collation_to_fetch( @@ -847,29 +736,8 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue3() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode.clone(), &claim_queue, + &BTreeMap::new(), ) ); collations.note_fetched(collation_b2.0.para_id); - - assert_eq!( - Some(collation_c2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_c2.0.para_id); - - assert_eq!( - Some(collation_a4.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), - &claim_queue, - ) - ); - collations.note_fetched(collation_a4.0.para_id); } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 162bde1a41e4..5c49de54eb6f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -570,7 +570,7 @@ async fn assert_async_backing_params_request(virtual_overseer: &mut VirtualOvers _, RuntimeApiRequest::Version(tx), )) => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); + let _ = tx.send(Ok(RuntimeApiRequest::ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT - 1)); } ); } From d4f4ce2d52c99a347fe57a2c4217ee3f9aa9e58e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 11:02:39 +0300 Subject: [PATCH 022/138] Fix `pick_a_collation_to_fetch` - iter 2 --- .../src/validator_side/collation.rs | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 8c3dba5f959f..88cfc86be657 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -245,8 +245,10 @@ pub struct Collations { // from `GroupAssignments` which contains either the claim queue (if runtime supports it) for // the core or the `ParaId` of the parachain assigned to the core. claims_per_para: BTreeMap, - // Whether the runtime supports claim queue runtime api. - has_claim_queue: bool, + // Represents the claim queue at the relay parent. The `bool` field indicates if a candidate + // was fetched for the `ParaId` at the position in question. In other words - if the claim is + // 'satisfied'. If the claim queue is not avaliable `claim_queue_state` will be `None`. + claim_queue_state: Option>, } impl Collations { @@ -258,10 +260,18 @@ impl Collations { /// parachain assigned to the core. pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { let mut claims_per_para = BTreeMap::new(); + let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); + for para_id in group_assignments { *claims_per_para.entry(*para_id).or_default() += 1; + claim_queue_state.push((false, *para_id)); } + // Not optimal but if the claim queue is not available `group_assignments` will have just + // one element. Can be fixed once claim queue api is released everywhere and the fallback + // code is cleaned up. + let claim_queue_state = if has_claim_queue { Some(claim_queue_state) } else { None }; + Self { status: Default::default(), fetching_from: None, @@ -269,7 +279,7 @@ impl Collations { seconded_count: 0, fetched_per_para: Default::default(), claims_per_para, - has_claim_queue, + claim_queue_state, } } @@ -280,7 +290,22 @@ impl Collations { // Note a collation which has been successfully fetched. pub(super) fn note_fetched(&mut self, para_id: ParaId) { - *self.fetched_per_para.entry(para_id).or_default() += 1 + // update the number of fetched collations for the para_id + *self.fetched_per_para.entry(para_id).or_default() += 1; + + // and the claim queue state + if let Some(claim_queue_state) = self.claim_queue_state.as_mut() { + for (satisfied, assignment) in claim_queue_state { + if *satisfied { + continue + } + + if assignment == ¶_id { + *satisfied = true; + break + } + } + } } /// Returns the next collation to fetch from the `waiting_queue`. @@ -356,7 +381,7 @@ impl Collations { self.seconded_count >= 1 }, ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len: _ } - if !self.has_claim_queue => + if !self.claim_queue_state.is_some() => { gum::trace!( target: LOG_TARGET, @@ -430,50 +455,43 @@ impl Collations { "Pick a collation to fetch." ); - if !self.has_claim_queue { - if let Some(assigned_para_id) = group_assignments.first() { - return self - .waiting_queue - .get_mut(assigned_para_id) - .map(|collations| collations.pop_front()) - .flatten() - } else { - unreachable!("Group assignments should contain at least one element.") - } - } + let claim_queue_state = match self.claim_queue_state.as_mut() { + Some(cqs) => cqs, + // Fallback if claim queue is not avaliable. There is only one assignment in + // `group_assignments` so fetch the first advertisement for it and return. + None => + if let Some(assigned_para_id) = group_assignments.first() { + return self + .waiting_queue + .get_mut(assigned_para_id) + .map(|collations| collations.pop_front()) + .flatten() + } else { + unreachable!("Group assignments should contain at least one element.") + }, + }; - let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); - let mut fetched_per_para = self.fetched_per_para.clone(); let mut pending_fetches = pending_fetches.clone(); - for assignment in group_assignments { - if let Some(fetched) = fetched_per_para.get_mut(assignment) { - if *fetched > 0 { - claim_queue_state.push((true, assignment)); - *fetched -= 1; - continue; - } + for (fulfilled, assignment) in claim_queue_state { + // if this assignment has been already fulfilled - move on + if *fulfilled { + continue } - if let Some(pending_fetches) = pending_fetches.get_mut(assignment) { - if *pending_fetches > 0 { - claim_queue_state.push((true, assignment)); - *pending_fetches -= 1; - continue; + // if there is a pending fetch for this assignment, we should consider it satisfied and + // proceed with the next + if let Some(pending_fetch) = pending_fetches.get_mut(assignment) { + if *pending_fetch > 0 { + *pending_fetch -= 1; + continue } } - claim_queue_state.push((false, assignment)); - } - - for (fulfilled, assignment) in &mut claim_queue_state { - if *fulfilled { - continue - } - + // we have found and unfulfilled assignment - try to fulfill it if let Some(collations) = self.waiting_queue.get_mut(assignment) { if let Some(collation) = collations.pop_front() { - *fulfilled = true; + // we don't mark the entry as fulfilled because it is considered pending return Some(collation) } } From 5f5271219f8501c38a701ccd2cdbca181fda5fcf Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 11:29:55 +0300 Subject: [PATCH 023/138] Remove a redundant runtime version check --- .../src/validator_side/mod.rs | 49 +++++++++++-------- .../src/validator_side/tests/mod.rs | 9 ---- .../tests/prospective_parachains.rs | 17 ------- 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 63062900a0c3..ff6c466b35a3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -43,13 +43,12 @@ use polkadot_node_subsystem::{ messages::{ CanSecondRequest, CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, ProspectiveParachainsMessage, - ProspectiveValidationDataRequest, RuntimeApiRequest, + ProspectiveValidationDataRequest, }, overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, PerLeafSpan, }; use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, - has_required_runtime, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, vstaging::fetch_claim_queue, @@ -462,13 +461,16 @@ fn is_relay_parent_in_implicit_view( } } +// Returns the group assignments for the validator and bool indicating if they are obtained from the +// claim queue or not. The latter is used to handle the fall back case when the claim queue api is +// not available in the runtime. async fn assign_incoming( sender: &mut Sender, current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, relay_parent_mode: ProspectiveParachainsMode, -) -> Result +) -> Result<(GroupAssignments, bool)> where Sender: CollatorProtocolSenderTrait, { @@ -495,25 +497,32 @@ where rotation_info.core_for_group(group, cores.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); - return Ok(GroupAssignments { current: Vec::new() }) + return Ok((GroupAssignments { current: Vec::new() }, false)) }; - let paras_now = match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { + let (paras_now, has_claim_queue) = match fetch_claim_queue(sender, relay_parent) + .await + .map_err(Error::Runtime)? + { // Runtime supports claim queue - use it // // `relay_parent_mode` is not examined here because if the runtime supports claim queue // then it supports async backing params too (`ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT` // < `CLAIM_QUEUE_RUNTIME_REQUIREMENT`). - Some(mut claim_queue) => claim_queue.0.remove(&core_now), + Some(mut claim_queue) => (claim_queue.0.remove(&core_now), true), // Claim queue is not supported by the runtime - use availability cores instead. - None => cores.get(core_now.0 as usize).and_then(|c| match c { - CoreState::Occupied(core) if relay_parent_mode.is_enabled() => - core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), - CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), - CoreState::Occupied(_) | CoreState::Free => None, - }), - } - .unwrap_or_else(|| VecDeque::new()); + None => ( + cores.get(core_now.0 as usize).and_then(|c| match c { + CoreState::Occupied(core) if relay_parent_mode.is_enabled() => + core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), + CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), + CoreState::Occupied(_) | CoreState::Free => None, + }), + false, + ), + }; + + let paras_now = paras_now.unwrap_or_else(|| VecDeque::new()); for para_id in paras_now.iter() { let entry = current_assignments.entry(*para_id).or_default(); @@ -528,7 +537,10 @@ where } } - Ok(GroupAssignments { current: paras_now.into_iter().collect::>() }) + Ok(( + GroupAssignments { current: paras_now.into_iter().collect::>() }, + has_claim_queue, + )) } fn remove_outgoing( @@ -1267,16 +1279,13 @@ where for leaf in added { let mode = prospective_parachains_mode(sender, *leaf).await?; - let has_claim_queue_support = - has_required_runtime(sender, *leaf, RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT) - .await; if let Some(span) = view.span_per_head().get(leaf).cloned() { let per_leaf_span = PerLeafSpan::new(span, "validator-side"); state.span_per_relay_parent.insert(*leaf, per_leaf_span); } - let assignments = + let (assignments, has_claim_queue_support) = assign_incoming(sender, &mut state.current_assignments, keystore, *leaf, mode).await?; state.active_leaves.insert(*leaf, mode); @@ -1298,7 +1307,7 @@ where .unwrap_or_default(); for block_hash in allowed_ancestry { if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - let assignments = assign_incoming( + let (assignments, has_claim_queue_support) = assign_incoming( sender, &mut state.current_assignments, keystore, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 5c49de54eb6f..7243af9fe4ca 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -564,15 +564,6 @@ async fn assert_async_backing_params_request(virtual_overseer: &mut VirtualOvers tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Version(tx), - )) => { - let _ = tx.send(Ok(RuntimeApiRequest::ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT - 1)); - } - ); } // As we receive a relevant advertisement act on it and issue a collation request. diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 91208176747c..64b392dda4f1 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -132,23 +132,6 @@ pub(super) async fn update_view( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Version(tx), - )) => { - match test_state.claim_queue { - Some(_) => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); - }, - None => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)); - } - } - } - ); - assert_assign_incoming( virtual_overseer, test_state, From 6c73e246dbe468b735e745941c7f4d1e98c3352e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 11:54:57 +0300 Subject: [PATCH 024/138] formatting and comments --- polkadot/node/core/backing/src/lib.rs | 5 +--- .../src/validator_side/collation.rs | 26 ++++++++++++------- .../src/validator_side/mod.rs | 1 - 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 9498e0d657e0..1bda81c5197e 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -281,10 +281,7 @@ impl From<&ActiveLeafState> for ProspectiveParachainsMode { ActiveLeafState::ProspectiveParachainsEnabled { max_candidate_depth, allowed_ancestry_len, - } => ProspectiveParachainsMode::Enabled { - max_candidate_depth, - allowed_ancestry_len, - }, + } => ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len }, } } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 88cfc86be657..3b2efc7fb506 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -247,17 +247,21 @@ pub struct Collations { claims_per_para: BTreeMap, // Represents the claim queue at the relay parent. The `bool` field indicates if a candidate // was fetched for the `ParaId` at the position in question. In other words - if the claim is - // 'satisfied'. If the claim queue is not avaliable `claim_queue_state` will be `None`. + // 'satisfied'. If the claim queue is not available `claim_queue_state` will be `None`. claim_queue_state: Option>, } impl Collations { - /// `Collations` should work with and without claim queue support. To make this happen without - /// creating two parallel implementations instead of working with the claim queue directly it - /// uses `GroupAssignments`. If the runtime supports claim queue `GroupAssignments` contains the - /// claim queue for the core assigned to the (backing group of the) validator. If the runtime - /// doesn't support claim queue `GroupAssignments` contains only one entry - the `ParaId` of the - /// parachain assigned to the core. + /// `Collations` should work with and without claim queue support. If the claim queue runtime + /// api is available `GroupAssignments` the claim queue. If not - group assignments will contain + /// just one item (what's scheduled on the core). + /// + /// Some of the logic in `Collations` relies on the claim queue and if it is not available + /// fallbacks to another logic. For this reason `Collations` needs to know if claim queue is + /// available or not. + /// + /// Once claim queue runtime api is released everywhere this logic won't be needed anymore and + /// can be cleaned up. pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { let mut claims_per_para = BTreeMap::new(); let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); @@ -362,7 +366,7 @@ impl Collations { /// parachains are sharing a core no fairness is guaranteed between them and the faster one can /// starve the slower one by exhausting the limit with its own advertisements. In practice this /// should not happen because core sharing implies core time support which implies the claim - /// queue is available. + /// queue being available. pub(super) fn is_collations_limit_reached( &self, relay_parent_mode: ProspectiveParachainsMode, @@ -441,6 +445,10 @@ impl Collations { /// If claim queue is not supported then `group_assignment` should contain just one element and /// the score won't matter. In this case collations will be fetched in the order they were /// received. + /// + /// Note: `group_assignments` is needed just for the fall back logic. It should be removed once + /// claim queue runtime api is released everywhere since it will be redundant - claim queue will + /// already be available in `self.claim_queue_state`. fn pick_a_collation_to_fetch( &mut self, group_assignments: &Vec, @@ -457,7 +465,7 @@ impl Collations { let claim_queue_state = match self.claim_queue_state.as_mut() { Some(cqs) => cqs, - // Fallback if claim queue is not avaliable. There is only one assignment in + // Fallback if claim queue is not available. There is only one assignment in // `group_assignments` so fetch the first advertisement for it and return. None => if let Some(assigned_para_id) = group_assignments.first() { diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index ff6c466b35a3..71bef5c9d7a9 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -521,7 +521,6 @@ where false, ), }; - let paras_now = paras_now.unwrap_or_else(|| VecDeque::new()); for para_id in paras_now.iter() { From 752f3cc4e7c3e4cb5c5ec10479c39b2b52cc10f8 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 13:47:47 +0300 Subject: [PATCH 025/138] pr doc --- prdoc/pr_4880.prdoc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 prdoc/pr_4880.prdoc diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc new file mode 100644 index 000000000000..863de7570b10 --- /dev/null +++ b/prdoc/pr_4880.prdoc @@ -0,0 +1,25 @@ +title: Collation fetching fairness in collator protocol + +doc: + - audience: "Node Dev" + description: | + Implements collation fetching fairness in the validator side of the collator protocol. With + core time if two (or more) parachains share a single core no fairness is guaranteed between + them in terms of collation fetching. The current implementation was accepting up to + `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached + no new collations are accepted. A misbehaving collator can abuse this fact and prevent other + collators/parachains to advertise collations by advertising `max_candidate_depth + 1` + collations of its own. + To address this issue two changes are made: + 1. The validator accepts as much advertisements (and collations fetches) as the number of + entries in the claim queue for the parachain in question. + 2. When new collation should be fetched the validator inspects what was fetched so far, what's + in the claim queue and picks the first slot which hasn't got a collation fetched. If there + is a pending advertisement for it it is fetched. Otherwise the next free slot is picked. + These two changes guarantee that: + 1. Validator doesn't accept more collations than it can actually back. + 2. Each parachain has got a fair share of core time based on its allocations. + +crates: + - name: "polkadot-collator-protocol" + bump: "minor" From f0069f106d468291ec5bdb89d6b6c26889fd6781 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 13:56:33 +0300 Subject: [PATCH 026/138] add license --- .../src/validator_side/tests/collation.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 143cdf8fed36..bf6a2e9ca966 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -1,3 +1,19 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use std::collections::BTreeMap; use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; From 6b9f0b3938870865df02633b7f605135826933dd Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 4 Jul 2024 15:22:49 +0300 Subject: [PATCH 027/138] clippy --- .../src/validator_side/collation.rs | 5 +-- .../src/validator_side/tests/collation.rs | 40 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 3b2efc7fb506..ef83f7622849 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -395,7 +395,7 @@ impl Collations { "is_collations_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" ); - self.seconded_count >= max_candidate_depth + 1 + self.seconded_count > max_candidate_depth }, ProspectiveParachainsMode::Enabled { max_candidate_depth: _, @@ -472,8 +472,7 @@ impl Collations { return self .waiting_queue .get_mut(assigned_para_id) - .map(|collations| collations.pop_front()) - .flatten() + .and_then(|collations| collations.pop_front()) } else { unreachable!("Group assignments should contain at least one element.") }, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index bf6a2e9ca966..128b9d31038d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -157,7 +157,7 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending ) @@ -169,7 +169,7 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending ) @@ -181,7 +181,7 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending ) @@ -244,7 +244,7 @@ fn collation_fetching_fallback_works() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -256,7 +256,7 @@ fn collation_fetching_fallback_works() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -382,7 +382,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -394,7 +394,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -406,7 +406,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -418,7 +418,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -430,7 +430,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -442,7 +442,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -564,7 +564,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -576,7 +576,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -591,7 +591,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -603,7 +603,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -618,7 +618,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -630,7 +630,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &pending, ) @@ -721,7 +721,7 @@ fn collation_fetching_takes_in_account_pending_items() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &BTreeMap::from([(para_a, 1)]), ) @@ -736,7 +736,7 @@ fn collation_fetching_takes_in_account_pending_items() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &BTreeMap::from([(para_b, 1)]), ) @@ -750,7 +750,7 @@ fn collation_fetching_takes_in_account_pending_items() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode.clone(), + relay_parent_mode, &claim_queue, &BTreeMap::new(), ) From b8c1b8593209b2a6474d5e448b1e6cb127a7acc9 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 5 Jul 2024 09:53:00 +0300 Subject: [PATCH 028/138] Update prdoc/pr_4880.prdoc Co-authored-by: Maciej --- prdoc/pr_4880.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index 863de7570b10..2c77caba846c 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -8,7 +8,7 @@ doc: them in terms of collation fetching. The current implementation was accepting up to `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached no new collations are accepted. A misbehaving collator can abuse this fact and prevent other - collators/parachains to advertise collations by advertising `max_candidate_depth + 1` + collators/parachains from advertising collations by advertising `max_candidate_depth + 1` collations of its own. To address this issue two changes are made: 1. The validator accepts as much advertisements (and collations fetches) as the number of From f26362f64ca0eb3909e0cb1b80b384097ecbae9d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Sun, 7 Jul 2024 12:56:27 +0300 Subject: [PATCH 029/138] Limit collations based on seconded count instead of number of fetches --- .../src/validator_side/collation.rs | 88 ++++++++++--------- .../src/validator_side/mod.rs | 66 ++++---------- 2 files changed, 60 insertions(+), 94 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index ef83f7622849..8889afac469e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -192,9 +192,9 @@ pub enum CollationStatus { /// We are waiting for a collation to be advertised to us. Waiting, /// We are currently fetching a collation. - Fetching, + Fetching(ParaId), /// We are waiting that a collation is being validated. - WaitingOnValidation, + WaitingOnValidation(ParaId), /// We have seconded a collation. Seconded, } @@ -237,10 +237,8 @@ pub struct Collations { pub fetching_from: Option<(CollatorId, Option)>, /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. waiting_queue: BTreeMap>, - /// How many collations have been seconded. - pub seconded_count: usize, - /// What collations were fetched so far for this relay parent. - fetched_per_para: BTreeMap, + /// How many collations have been seconded per `ParaId`. + seconded_per_para: BTreeMap, // Claims per `ParaId` for the assigned core at the relay parent. This information is obtained // from `GroupAssignments` which contains either the claim queue (if runtime supports it) for // the core or the `ParaId` of the parachain assigned to the core. @@ -280,22 +278,15 @@ impl Collations { status: Default::default(), fetching_from: None, waiting_queue: Default::default(), - seconded_count: 0, - fetched_per_para: Default::default(), + seconded_per_para: Default::default(), claims_per_para, claim_queue_state, } } /// Note a seconded collation for a given para. - pub(super) fn note_seconded(&mut self) { - self.seconded_count += 1 - } - - // Note a collation which has been successfully fetched. - pub(super) fn note_fetched(&mut self, para_id: ParaId) { - // update the number of fetched collations for the para_id - *self.fetched_per_para.entry(para_id).or_default() += 1; + pub(super) fn note_seconded(&mut self, para_id: ParaId) { + *self.seconded_per_para.entry(para_id).or_default() += 1; // and the claim queue state if let Some(claim_queue_state) = self.claim_queue_state.as_mut() { @@ -323,7 +314,6 @@ impl Collations { finished_one: &(CollatorId, Option), relay_parent_mode: ProspectiveParachainsMode, group_assignments: &Vec, - pending_fetches: &BTreeMap, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. @@ -348,14 +338,13 @@ impl Collations { // `Waiting` so that we can fetch more collations. If async backing is disabled we can't // fetch more than one collation per relay parent so `None` is returned. CollationStatus::Seconded => None, - CollationStatus::Waiting => - self.pick_a_collation_to_fetch(&group_assignments, pending_fetches), - CollationStatus::WaitingOnValidation | CollationStatus::Fetching => + CollationStatus::Waiting => self.pick_a_collation_to_fetch(&group_assignments), + CollationStatus::WaitingOnValidation(_) | CollationStatus::Fetching(_) => unreachable!("We have reset the status above!"), } } - /// Checks if another collation can be accepted. The number of collations that can be fetched + /// Checks if another collation can be accepted. The number of collations that can be seconded /// per parachain is limited by the entries in claim queue for the `ParaId` in question. /// /// If prospective parachains mode is not enabled then we fall back to synchronous backing. In @@ -371,18 +360,20 @@ impl Collations { &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, - num_pending_fetches: usize, ) -> bool { + let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); + let pending_for_para = self.pending_for_para(para_id); + match relay_parent_mode { ProspectiveParachainsMode::Disabled => { gum::trace!( target: LOG_TARGET, ?para_id, - seconded_count=self.seconded_count, + seconded_per_para=?self.seconded_per_para, "is_collations_limit_reached - ProspectiveParachainsMode::Disabled" ); - self.seconded_count >= 1 + seconded_for_para >= 1 }, ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len: _ } if !self.claim_queue_state.is_some() => @@ -390,12 +381,12 @@ impl Collations { gum::trace!( target: LOG_TARGET, ?para_id, - seconded_count=self.seconded_count, + seconded_per_para=?self.seconded_per_para, max_candidate_depth, "is_collations_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" ); - self.seconded_count > max_candidate_depth + seconded_for_para > max_candidate_depth }, ProspectiveParachainsMode::Enabled { max_candidate_depth: _, @@ -404,15 +395,14 @@ impl Collations { // Successful fetches + pending fetches < claim queue entries for `para_id` let respected_per_para_limit = self.claims_per_para.get(¶_id).copied().unwrap_or_default() > - self.fetched_per_para.get(¶_id).copied().unwrap_or_default() + - num_pending_fetches; + seconded_for_para + pending_for_para; gum::trace!( target: LOG_TARGET, ?para_id, claims_per_para=?self.claims_per_para, - fetched_per_para=?self.fetched_per_para, - ?num_pending_fetches, + seconded_per_para=?self.seconded_per_para, + ?pending_for_para, ?respected_per_para_limit, "is_collations_limit_reached - ProspectiveParachainsMode::Enabled with claim queue support" ); @@ -432,15 +422,12 @@ impl Collations { /// Picks a collation to fetch from the waiting queue. /// When fetching collations we need to ensure that each parachain has got a fair core time /// share depending on its assignments in the claim queue. This means that the number of - /// collations fetched per parachain should ideally be equal to the number of claims for the + /// collations seconded per parachain should ideally be equal to the number of claims for the /// particular parachain in the claim queue. /// - /// To achieve this each parachain with at an entry in the `waiting_queue` has got a score - /// calculated by dividing the number of fetched collations by the number of entries in the - /// claim queue. Lower score means higher fetching priority. Note that if a parachain hasn't got - /// anything fetched at this relay parent it will have score 0 which means highest priority. If - /// two parachains has got the same score the one which is earlier in the claim queue will be - /// picked. + /// To achieve this each seconded collation is mapped to an entry from the claim queue. The next + /// fetch is the first unsatisfied entry from the claim queue for which there is an + /// advertisement. /// /// If claim queue is not supported then `group_assignment` should contain just one element and /// the score won't matter. In this case collations will be fetched in the order they were @@ -452,12 +439,10 @@ impl Collations { fn pick_a_collation_to_fetch( &mut self, group_assignments: &Vec, - pending_fetches: &BTreeMap, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, waiting_queue=?self.waiting_queue, - fetched_per_para=?self.fetched_per_para, claims_per_para=?self.claims_per_para, ?group_assignments, "Pick a collation to fetch." @@ -478,7 +463,12 @@ impl Collations { }, }; - let mut pending_fetches = pending_fetches.clone(); + let mut pending_for_para = match self.status { + CollationStatus::Waiting => None, + CollationStatus::Fetching(para_id) => Some(para_id), + CollationStatus::WaitingOnValidation(para_id) => Some(para_id), + CollationStatus::Seconded => None, + }; for (fulfilled, assignment) in claim_queue_state { // if this assignment has been already fulfilled - move on @@ -488,9 +478,10 @@ impl Collations { // if there is a pending fetch for this assignment, we should consider it satisfied and // proceed with the next - if let Some(pending_fetch) = pending_fetches.get_mut(assignment) { - if *pending_fetch > 0 { - *pending_fetch -= 1; + if let Some(pending_for) = pending_for_para { + if pending_for == *assignment { + // the pending item should be used only once + pending_for_para = None; continue } } @@ -506,6 +497,17 @@ impl Collations { None } + + // Returns the number of pending collations for the specified `ParaId`. This function should + // return either 0 or 1. + fn pending_for_para(&self, para_id: ParaId) -> usize { + match self.status { + CollationStatus::Fetching(pending_para_id) if pending_para_id == para_id => 1, + CollationStatus::WaitingOnValidation(pending_para_id) if pending_para_id == para_id => + 1, + _ => 0, + } + } } // Any error that can occur when awaiting a collation fetch response. diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 71bef5c9d7a9..ea5b6ecd8e3c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -19,7 +19,7 @@ use futures::{ }; use futures_timer::Delay; use std::{ - collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}, + collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, future::Future, time::{Duration, Instant}, }; @@ -751,7 +751,7 @@ async fn request_collation( let maybe_candidate_hash = prospective_candidate.as_ref().map(ProspectiveCandidate::candidate_hash); - per_relay_parent.collations.status = CollationStatus::Fetching; + per_relay_parent.collations.status = CollationStatus::Fetching(para_id); per_relay_parent .collations .fetching_from @@ -1112,11 +1112,10 @@ where ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent.collations.is_collations_limit_reached( - relay_parent_mode, - para_id, - num_pending_collations_for_para_at_relay_parent(&state, para_id, relay_parent), - ) { + if per_relay_parent + .collations + .is_collations_limit_reached(relay_parent_mode, para_id) + { return Err(AdvertisementError::SecondedLimitReached) } @@ -1186,8 +1185,6 @@ where "Received advertise collation", ); - let num_pending_fetches = - num_pending_collations_for_para_at_relay_parent(&state, para_id, relay_parent); let per_relay_parent = match state.per_relay_parent.get_mut(&relay_parent) { Some(rp_state) => rp_state, None => { @@ -1211,7 +1208,7 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_collations_limit_reached(relay_parent_mode, para_id, num_pending_fetches) { + if collations.is_collations_limit_reached(relay_parent_mode, para_id) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1226,7 +1223,7 @@ where PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); match collations.status { - CollationStatus::Fetching | CollationStatus::WaitingOnValidation => { + CollationStatus::Fetching(_) | CollationStatus::WaitingOnValidation(_) => { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1501,8 +1498,9 @@ async fn process_msg( if let Some(CollationEvent { collator_id, pending_collation, .. }) = state.fetched_candidates.remove(&fetched_collation) { - let PendingCollation { relay_parent, peer_id, prospective_candidate, .. } = - pending_collation; + let PendingCollation { + relay_parent, peer_id, prospective_candidate, para_id, .. + } = pending_collation; note_good_collation( &mut state.reputation, ctx.sender(), @@ -1523,7 +1521,7 @@ async fn process_msg( if let Some(rp_state) = state.per_relay_parent.get_mut(&parent) { rp_state.collations.status = CollationStatus::Seconded; - rp_state.collations.note_seconded(); + rp_state.collations.note_seconded(para_id); } // See if we've unblocked other collations for seconding. @@ -1671,10 +1669,6 @@ async fn run_inner( let CollationEvent {collator_id, pending_collation, .. } = res.collation_event.clone(); - state.per_relay_parent.get_mut(&pending_collation.relay_parent).map(|rp_state| { - rp_state.collations.note_fetched(pending_collation.para_id); - }); - match kick_off_seconding(&mut ctx, &mut state, res).await { Err(err) => { gum::warn!( @@ -1746,14 +1740,12 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { - let pending_collations = pending_collations_per_para_at_relay_parent(state, relay_parent); while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|rp_state| { rp_state.collations.get_next_collation_to_fetch( &previous_fetch, rp_state.prospective_parachains_mode, &rp_state.assignment.current, - &pending_collations, ) }) { gum::debug!( @@ -1938,6 +1930,8 @@ async fn kick_off_seconding( maybe_parent_head.and_then(|head| maybe_parent_head_hash.map(|hash| (head, hash))), )?; + let para_id = candidate_receipt.descriptor().para_id; + ctx.send_message(CandidateBackingMessage::Second( relay_parent, candidate_receipt, @@ -1947,7 +1941,7 @@ async fn kick_off_seconding( .await; // There's always a single collation being fetched at any moment of time. // In case of a failure, we reset the status back to waiting. - collations.status = CollationStatus::WaitingOnValidation; + collations.status = CollationStatus::WaitingOnValidation(para_id); entry.insert(collation_event); Ok(true) @@ -2127,33 +2121,3 @@ async fn handle_collation_fetch_response( state.metrics.on_request(metrics_result); result } - -// Returns the number of pending fetches for `ParaId` at the specified relay parent. -fn num_pending_collations_for_para_at_relay_parent( - state: &State, - para_id: ParaId, - relay_parent: Hash, -) -> usize { - state - .collation_requests_cancel_handles - .iter() - .filter(|(pending_collation, _)| { - pending_collation.para_id == para_id && pending_collation.relay_parent == relay_parent - }) - .count() -} - -// Returns the number of pending fetches for each `ParaId` at the specified relay parent. -fn pending_collations_per_para_at_relay_parent( - state: &State, - relay_parent: Hash, -) -> BTreeMap { - state - .collation_requests_cancel_handles - .iter() - .filter(|(col, _)| col.relay_parent == relay_parent) - .fold(BTreeMap::new(), |mut res, (col, _)| { - *res.entry(col.para_id).or_default() += 1; - res - }) -} From d6857fc564c8e3e9c5e79bebbb0766d5b9e414e4 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Sun, 7 Jul 2024 13:00:37 +0300 Subject: [PATCH 030/138] Undo rename: is_seconded_limit_reached --- .../src/validator_side/collation.rs | 8 ++++---- .../src/validator_side/mod.rs | 6 +++--- .../src/validator_side/tests/collation.rs | 20 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 8889afac469e..2b30b72e714e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -356,7 +356,7 @@ impl Collations { /// starve the slower one by exhausting the limit with its own advertisements. In practice this /// should not happen because core sharing implies core time support which implies the claim /// queue being available. - pub(super) fn is_collations_limit_reached( + pub(super) fn is_seconded_limit_reached( &self, relay_parent_mode: ProspectiveParachainsMode, para_id: ParaId, @@ -370,7 +370,7 @@ impl Collations { target: LOG_TARGET, ?para_id, seconded_per_para=?self.seconded_per_para, - "is_collations_limit_reached - ProspectiveParachainsMode::Disabled" + "is_seconded_limit_reached - ProspectiveParachainsMode::Disabled" ); seconded_for_para >= 1 @@ -383,7 +383,7 @@ impl Collations { ?para_id, seconded_per_para=?self.seconded_per_para, max_candidate_depth, - "is_collations_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" + "is_seconded_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" ); seconded_for_para > max_candidate_depth @@ -404,7 +404,7 @@ impl Collations { seconded_per_para=?self.seconded_per_para, ?pending_for_para, ?respected_per_para_limit, - "is_collations_limit_reached - ProspectiveParachainsMode::Enabled with claim queue support" + "is_seconded_limit_reached - ProspectiveParachainsMode::Enabled with claim queue support" ); !respected_per_para_limit diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index ea5b6ecd8e3c..a842f54821ce 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1114,7 +1114,7 @@ where if per_relay_parent .collations - .is_collations_limit_reached(relay_parent_mode, para_id) + .is_seconded_limit_reached(relay_parent_mode, para_id) { return Err(AdvertisementError::SecondedLimitReached) } @@ -1208,7 +1208,7 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_collations_limit_reached(relay_parent_mode, para_id) { + if collations.is_seconded_limit_reached(relay_parent_mode, para_id) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1239,7 +1239,7 @@ where fetch_collation(sender, state, pending_collation, collator_id).await?; }, CollationStatus::Seconded if relay_parent_mode.is_enabled() => { - // Limit is not reached (checked with `is_collations_limit_reached` before the match + // Limit is not reached (checked with `is_seconded_limit_reached` before the match // expression), it's allowed to second another collation. fetch_collation(sender, state, pending_collation, collator_id).await?; }, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 128b9d31038d..9de08f856a18 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -38,28 +38,28 @@ fn cant_add_more_than_claim_queue() { let mut collations = Collations::new(&assignments, claim_queue_support); // first collation for `para_a` is in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0,)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0,)); collations.note_fetched(para_a); // and `para_b` is not affected - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); // second collation for `para_a` is also in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); collations.note_fetched(para_a); // `para_b`` is still not affected - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); // third collation for `para_a`` will be above the limit - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); // one fetch for b - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); collations.note_fetched(para_b); // and now both paras are over limit - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 0)); - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_b, 0)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); } #[test] @@ -78,11 +78,11 @@ fn pending_fetches_are_counted() { collations.fetching_from = Some((collator_id_a, None)); // first collation for `para_a` is in the limit - assert!(!collations.is_collations_limit_reached(relay_parent_mode, para_a, 1)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 1)); collations.note_fetched(para_a); // second collation for `para_a`` is not in the limit due to the pending fetch - assert!(collations.is_collations_limit_reached(relay_parent_mode, para_a, 1)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 1)); } #[test] From cde28cd9cb425d5111bc4b1da54360e3719acc70 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 8 Jul 2024 10:00:42 +0300 Subject: [PATCH 031/138] fix collation tests --- .../src/validator_side/tests/collation.rs | 219 ++++-------------- 1 file changed, 42 insertions(+), 177 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 9de08f856a18..cdfdcafcd8b1 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::collections::BTreeMap; - use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; use polkadot_primitives::{CandidateHash, CollatorId, Hash, Id as ParaId}; use sc_network::PeerId; use sp_core::sr25519; +use crate::validator_side::tests::CollationStatus; + use super::{Collations, PendingCollation, ProspectiveCandidate}; #[test] @@ -38,28 +38,28 @@ fn cant_add_more_than_claim_queue() { let mut collations = Collations::new(&assignments, claim_queue_support); // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0,)); - collations.note_fetched(para_a); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + collations.note_seconded(para_a); // and `para_b` is not affected - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); // second collation for `para_a` is also in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); - collations.note_fetched(para_a); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + collations.note_seconded(para_a); // `para_b`` is still not affected - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); // third collation for `para_a`` will be above the limit - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); // one fetch for b - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); - collations.note_fetched(para_b); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + collations.note_seconded(para_b); // and now both paras are over limit - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 0)); - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_b, 0)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_b)); } #[test] @@ -67,7 +67,6 @@ fn pending_fetches_are_counted() { sp_tracing::init_for_tests(); let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; let relay_parent_mode = @@ -75,14 +74,17 @@ fn pending_fetches_are_counted() { let claim_queue_support = true; let mut collations = Collations::new(&assignments, claim_queue_support); - collations.fetching_from = Some((collator_id_a, None)); + collations.status = CollationStatus::Fetching(para_a); //para_a is pending // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a, 1)); - collations.note_fetched(para_a); + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + collations.note_seconded(para_a); + + // second collation for `para_a` is not in the limit due to the pending fetch + assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); - // second collation for `para_a`` is not in the limit due to the pending fetch - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a, 1)); + // a collation for `para_b` is accepted since the pending fetch is for `para_a` + assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); } #[test] @@ -101,11 +103,11 @@ fn collation_fetching_respects_claim_queue() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = true; - let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; + collations.status = CollationStatus::Waiting; //nothing pending let relay_parent = Hash::repeat_byte(0x01); @@ -159,10 +161,9 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending ) ); - collations.note_fetched(collation_a1.0.para_id); + collations.note_seconded(collation_a1.0.para_id); assert_eq!( Some(collation_b1.clone()), @@ -171,10 +172,9 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending ) ); - collations.note_fetched(collation_b1.0.para_id); + collations.note_seconded(collation_b1.0.para_id); assert_eq!( Some(collation_a2.clone()), @@ -183,10 +183,9 @@ fn collation_fetching_respects_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending ) ); - collations.note_fetched(collation_a2.0.para_id); + collations.note_seconded(collation_a2.0.para_id); } #[test] @@ -201,11 +200,11 @@ fn collation_fetching_fallback_works() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = false; - let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; + collations.status = CollationStatus::Waiting; //nothing pending let relay_parent = Hash::repeat_byte(0x01); @@ -246,10 +245,9 @@ fn collation_fetching_fallback_works() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_a1.0.para_id); + collations.note_seconded(collation_a1.0.para_id); assert_eq!( Some(collation_a2.clone()), @@ -258,10 +256,9 @@ fn collation_fetching_fallback_works() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_a2.0.para_id); + collations.note_seconded(collation_a2.0.para_id); } #[test] @@ -284,10 +281,10 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; - let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; + collations.status = CollationStatus::Waiting; //nothing pending let relay_parent = Hash::repeat_byte(0x01); @@ -384,10 +381,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_a1.0.para_id); + collations.note_seconded(collation_a1.0.para_id); assert_eq!( Some(collation_b1.clone()), @@ -396,10 +392,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_b1.0.para_id); + collations.note_seconded(collation_b1.0.para_id); assert_eq!( Some(collation_a2.clone()), @@ -408,10 +403,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_a2.0.para_id); + collations.note_seconded(collation_a2.0.para_id); assert_eq!( Some(collation_b2.clone()), @@ -420,10 +414,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_b2.0.para_id); + collations.note_seconded(collation_b2.0.para_id); assert_eq!( Some(collation_c1.clone()), @@ -432,10 +425,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_c1.0.para_id); + collations.note_seconded(collation_c1.0.para_id); assert_eq!( Some(collation_c2.clone()), @@ -444,10 +436,9 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_c2.0.para_id); + collations.note_seconded(collation_c2.0.para_id); } #[test] @@ -470,10 +461,10 @@ fn collation_fetching_fills_holes_in_claim_queue() { let relay_parent_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; - let pending = BTreeMap::new(); let mut collations = Collations::new(&claim_queue, claim_queue_support); collations.fetching_from = None; + collations.status = CollationStatus::Waiting; //nothing pending let relay_parent = Hash::repeat_byte(0x01); @@ -566,10 +557,9 @@ fn collation_fetching_fills_holes_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_a1.0.para_id); + collations.note_seconded(collation_a1.0.para_id); assert_eq!( Some(collation_c1.clone()), @@ -578,10 +568,9 @@ fn collation_fetching_fills_holes_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_c1.0.para_id); + collations.note_seconded(collation_c1.0.para_id); collations.add_to_waiting_queue(collation_c2.clone()); collations.add_to_waiting_queue(collation_b1.clone()); @@ -593,10 +582,9 @@ fn collation_fetching_fills_holes_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_b1.0.para_id); + collations.note_seconded(collation_b1.0.para_id); assert_eq!( Some(collation_c2.clone()), @@ -605,10 +593,9 @@ fn collation_fetching_fills_holes_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, ) ); - collations.note_fetched(collation_c2.0.para_id); + collations.note_seconded(collation_c2.0.para_id); collations.add_to_waiting_queue(collation_b2.clone()); collations.add_to_waiting_queue(collation_a2.clone()); @@ -620,131 +607,10 @@ fn collation_fetching_fills_holes_in_claim_queue() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &pending, - ) - ); - collations.note_fetched(collation_a2.0.para_id); - - assert_eq!( - Some(collation_b2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, - &claim_queue, - &pending, - ) - ); - collations.note_fetched(collation_b2.0.para_id); -} - -#[test] -fn collation_fetching_takes_in_account_pending_items() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - let peer_a = PeerId::random(); - - let para_b = ParaId::from(2); - let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); - let peer_b = PeerId::random(); - - let claim_queue = vec![para_a, para_b, para_a, para_b]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 5, allowed_ancestry_len: 4 }; - let claim_queue_support = true; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - collations.fetching_from = None; - - let relay_parent = Hash::repeat_byte(0x01); - - let collation_a1 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(1)), - parent_head_data_hash: Hash::repeat_byte(1), - }), - ), - collator_id_a.clone(), - ); - - let collation_a2 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(2)), - parent_head_data_hash: Hash::repeat_byte(2), - }), - ), - collator_id_a.clone(), - ); - - let collation_b1 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_b.clone(), - ); - - let collation_b2 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(4)), - parent_head_data_hash: Hash::repeat_byte(4), - }), - ), - collator_id_b.clone(), - ); - - // a1 will be pending, a2 and b1 will be in the queue; b1 should be fetched first - collations.add_to_waiting_queue(collation_a2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); - - assert_eq!( - Some(collation_b1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, - &claim_queue, - &BTreeMap::from([(para_a, 1)]), - ) - ); - collations.note_fetched(collation_a1.0.para_id); // a1 is no longer pending - - // a1 is fetched, b1 is pending, a2 and b2 are in the queue, a2 should be fetched next - collations.add_to_waiting_queue(collation_b2.clone()); - - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, - &claim_queue, - &BTreeMap::from([(para_b, 1)]), ) ); - collations.note_fetched(collation_b1.0.para_id); - collations.note_fetched(collation_a2.0.para_id); + collations.note_seconded(collation_a2.0.para_id); - // and finally b2 should be fetched assert_eq!( Some(collation_b2.clone()), collations.get_next_collation_to_fetch( @@ -752,8 +618,7 @@ fn collation_fetching_takes_in_account_pending_items() { &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), relay_parent_mode, &claim_queue, - &BTreeMap::new(), ) ); - collations.note_fetched(collation_b2.0.para_id); + collations.note_seconded(collation_b2.0.para_id); } From 4c3db2ad607371bb375ef54dc21620fc4d6c130e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 8 Jul 2024 10:19:40 +0300 Subject: [PATCH 032/138] `collations_fetching_respects_seconded_limit` test --- .../src/validator_side/tests/collation.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index cdfdcafcd8b1..56f950a85ca3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -622,3 +622,35 @@ fn collation_fetching_fills_holes_in_claim_queue() { ); collations.note_seconded(collation_b2.0.para_id); } + +#[test] +fn collations_fetching_respects_seconded_limit() { + sp_tracing::init_for_tests(); + + let para_a = ParaId::from(1); + let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); + + let para_b = ParaId::from(2); + + let claim_queue = vec![para_a, para_b, para_a]; + let relay_parent_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; + let claim_queue_support = true; + + let mut collations = Collations::new(&claim_queue, claim_queue_support); + collations.fetching_from = None; + collations.status = CollationStatus::Fetching(para_a); //para_a is pending + + collations.note_seconded(para_a); + collations.note_seconded(para_a); + + assert_eq!( + None, + collations.get_next_collation_to_fetch( + // doesn't matter since `fetching_from` is `None` + &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), + relay_parent_mode, + &claim_queue, + ) + ); +} From b2bbdfe5352538fbf11377d818efbbe45c86ee63 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 8 Jul 2024 10:44:11 +0300 Subject: [PATCH 033/138] nits --- .../src/validator_side/collation.rs | 6 +++--- .../src/validator_side/tests/collation.rs | 8 +++++--- prdoc/pr_4880.prdoc | 14 ++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 2b30b72e714e..fadba3c03557 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -244,7 +244,7 @@ pub struct Collations { // the core or the `ParaId` of the parachain assigned to the core. claims_per_para: BTreeMap, // Represents the claim queue at the relay parent. The `bool` field indicates if a candidate - // was fetched for the `ParaId` at the position in question. In other words - if the claim is + // was seconded for the `ParaId` at the position in question. In other words - if the claim is // 'satisfied'. If the claim queue is not available `claim_queue_state` will be `None`. claim_queue_state: Option>, } @@ -362,7 +362,6 @@ impl Collations { para_id: ParaId, ) -> bool { let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); - let pending_for_para = self.pending_for_para(para_id); match relay_parent_mode { ProspectiveParachainsMode::Disabled => { @@ -392,7 +391,8 @@ impl Collations { max_candidate_depth: _, allowed_ancestry_len: _, } => { - // Successful fetches + pending fetches < claim queue entries for `para_id` + let pending_for_para = self.pending_for_para(para_id); + let respected_per_para_limit = self.claims_per_para.get(¶_id).copied().unwrap_or_default() > seconded_for_para + pending_for_para; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 56f950a85ca3..6d641ba7bf50 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -150,9 +150,9 @@ fn collation_fetching_respects_claim_queue() { collator_id_b.clone(), ); + collations.add_to_waiting_queue(collation_b1.clone()); collations.add_to_waiting_queue(collation_a1.clone()); collations.add_to_waiting_queue(collation_a2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); assert_eq!( Some(collation_a1.clone()), @@ -366,7 +366,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collator_id_c.clone(), ); - // Despite the order here the fetches should be a1, b1, c1, a2, b2, c2 + // Despite the order here the fetches should follow the claim queue collations.add_to_waiting_queue(collation_c1.clone()); collations.add_to_waiting_queue(collation_c2.clone()); collations.add_to_waiting_queue(collation_b1.clone()); @@ -546,7 +546,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collator_id_c.clone(), ); - // Despite the order here the fetches should be a1, b1, c1, a2, b2, c2 collations.add_to_waiting_queue(collation_c1.clone()); collations.add_to_waiting_queue(collation_a1.clone()); @@ -561,6 +560,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { ); collations.note_seconded(collation_a1.0.para_id); + // fetch c1 since there is nothing better to fetch assert_eq!( Some(collation_c1.clone()), collations.get_next_collation_to_fetch( @@ -572,6 +572,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { ); collations.note_seconded(collation_c1.0.para_id); + // b1 should be prioritized since there is a hole in the claim queue collations.add_to_waiting_queue(collation_c2.clone()); collations.add_to_waiting_queue(collation_b1.clone()); @@ -597,6 +598,7 @@ fn collation_fetching_fills_holes_in_claim_queue() { ); collations.note_seconded(collation_c2.0.para_id); + // same with a2 collations.add_to_waiting_queue(collation_b2.clone()); collations.add_to_waiting_queue(collation_a2.clone()); diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index 2c77caba846c..3b16081f5dd1 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -11,14 +11,16 @@ doc: collators/parachains from advertising collations by advertising `max_candidate_depth + 1` collations of its own. To address this issue two changes are made: - 1. The validator accepts as much advertisements (and collations fetches) as the number of - entries in the claim queue for the parachain in question. - 2. When new collation should be fetched the validator inspects what was fetched so far, what's - in the claim queue and picks the first slot which hasn't got a collation fetched. If there - is a pending advertisement for it it is fetched. Otherwise the next free slot is picked. + 1. For each parachain id the validator accepts advertisements until the number of entries in + the claim queue equals the number of seconded candidates. + 2. When new collation should be fetched the validator inspects what was seconded so far, + what's in the claim queue and picks the first slot which hasn't got a collation seconded + and there is no candidate pending seconding for it. If there is an advertisement in the + waiting queue for it it is fetched. Otherwise the next free slot is picked. These two changes guarantee that: 1. Validator doesn't accept more collations than it can actually back. - 2. Each parachain has got a fair share of core time based on its allocations. + 2. Each parachain has got a fair share of core time based on its allocations in the claim + queue. crates: - name: "polkadot-collator-protocol" From 01d121e997c4e3bbb22c76e20a8b2f7268b8aa5f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 26 Aug 2024 12:51:03 +0300 Subject: [PATCH 034/138] Remove duplicated dependency after merge --- polkadot/node/network/collator-protocol/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 9c3e08b76148..8a7c384dcbe8 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -36,7 +36,6 @@ rstest = { workspace = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } -sp-tracing = { workspace = true } sc-network = { workspace = true, default-features = true } codec = { features = ["std"], workspace = true, default-features = true } From 7b3c002e05da3ad112d6562452cdcd52f366a1db Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 10 Jul 2024 11:07:26 +0300 Subject: [PATCH 035/138] Remove `ProspectiveParachainsMode` from collator-protocol, validator-side --- .../src/validator_side/collation.rs | 88 ++---- .../src/validator_side/mod.rs | 296 +++++++----------- 2 files changed, 146 insertions(+), 238 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index fadba3c03557..27bc1b2e966b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -42,9 +42,7 @@ use polkadot_node_network_protocol::{ }; use polkadot_node_primitives::PoV; use polkadot_node_subsystem::jaeger; -use polkadot_node_subsystem_util::{ - metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode, -}; +use polkadot_node_subsystem_util::metrics::prometheus::prometheus::HistogramTimer; use polkadot_primitives::{ CandidateHash, CandidateReceipt, CollatorId, Hash, HeadData, Id as ParaId, PersistedValidationData, @@ -207,16 +205,9 @@ impl Default for CollationStatus { impl CollationStatus { /// Downgrades to `Waiting`, but only if `self != Seconded`. - fn back_to_waiting(&mut self, relay_parent_mode: ProspectiveParachainsMode) { - match self { - Self::Seconded => - if relay_parent_mode.is_enabled() { - // With async backing enabled it's allowed to - // second more candidates. - *self = Self::Waiting - }, - _ => *self = Self::Waiting, - } + fn back_to_waiting(&mut self) { + // With async backing it's allowed to second more candidates. + *self = Self::Waiting } } @@ -312,7 +303,6 @@ impl Collations { pub(super) fn get_next_collation_to_fetch( &mut self, finished_one: &(CollatorId, Option), - relay_parent_mode: ProspectiveParachainsMode, group_assignments: &Vec, ) -> Option<(PendingCollation, CollatorId)> { // If finished one does not match waiting_collation, then we already dequeued another fetch @@ -331,7 +321,7 @@ impl Collations { return None } } - self.status.back_to_waiting(relay_parent_mode); + self.status.back_to_waiting(); match self.status { // If async backing is enabled `back_to_waiting` will change `Seconded` state to @@ -358,58 +348,40 @@ impl Collations { /// queue being available. pub(super) fn is_seconded_limit_reached( &self, - relay_parent_mode: ProspectiveParachainsMode, + max_candidate_depth: u32, para_id: ParaId, ) -> bool { let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); - match relay_parent_mode { - ProspectiveParachainsMode::Disabled => { - gum::trace!( - target: LOG_TARGET, - ?para_id, - seconded_per_para=?self.seconded_per_para, - "is_seconded_limit_reached - ProspectiveParachainsMode::Disabled" - ); + if !self.claim_queue_state.is_some() { + gum::trace!( + target: LOG_TARGET, + ?para_id, + seconded_per_para=?self.seconded_per_para, + max_candidate_depth, + "is_seconded_limit_reached - no claim queue support" + ); - seconded_for_para >= 1 - }, - ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len: _ } - if !self.claim_queue_state.is_some() => - { - gum::trace!( - target: LOG_TARGET, - ?para_id, - seconded_per_para=?self.seconded_per_para, - max_candidate_depth, - "is_seconded_limit_reached - ProspectiveParachainsMode::Enabled without claim queue support" - ); + return seconded_for_para > max_candidate_depth as usize; + } - seconded_for_para > max_candidate_depth - }, - ProspectiveParachainsMode::Enabled { - max_candidate_depth: _, - allowed_ancestry_len: _, - } => { - let pending_for_para = self.pending_for_para(para_id); + let pending_for_para = self.pending_for_para(para_id); - let respected_per_para_limit = - self.claims_per_para.get(¶_id).copied().unwrap_or_default() > - seconded_for_para + pending_for_para; + let respected_per_para_limit = + self.claims_per_para.get(¶_id).copied().unwrap_or_default() > + seconded_for_para + pending_for_para; - gum::trace!( - target: LOG_TARGET, - ?para_id, - claims_per_para=?self.claims_per_para, - seconded_per_para=?self.seconded_per_para, - ?pending_for_para, - ?respected_per_para_limit, - "is_seconded_limit_reached - ProspectiveParachainsMode::Enabled with claim queue support" - ); + gum::trace!( + target: LOG_TARGET, + ?para_id, + claims_per_para=?self.claims_per_para, + seconded_per_para=?self.seconded_per_para, + ?pending_for_para, + ?respected_per_para_limit, + "is_seconded_limit_reached - with claim queue support" + ); - !respected_per_para_limit - }, - } + !respected_per_para_limit } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index a842f54821ce..5be6004b666d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -50,12 +50,13 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, - runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, + request_async_backing_params, + runtime::recv_runtime, vstaging::fetch_claim_queue, }; use polkadot_primitives::{ - CandidateHash, CollatorId, CoreState, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, - PersistedValidationData, + AsyncBackingParams, CandidateHash, CollatorId, CoreState, Hash, HeadData, Id as ParaId, + OccupiedCoreAssumption, PersistedValidationData, }; use crate::error::{Error, FetchError, Result, SecondingError}; @@ -163,8 +164,7 @@ impl PeerData { fn update_view( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap, - per_relay_parent: &HashMap, + active_leaves: &HashMap, new_view: View, ) { let old_view = std::mem::replace(&mut self.view, new_view); @@ -172,18 +172,12 @@ impl PeerData { for removed in old_view.difference(&self.view) { // Remove relay parent advertisements if it went out // of our (implicit) view. - let keep = per_relay_parent - .get(removed) - .map(|s| { - is_relay_parent_in_implicit_view( - removed, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) - .unwrap_or(false); + let keep = is_relay_parent_in_implicit_view( + removed, + implicit_view, + active_leaves, + peer_state.para_id, + ); if !keep { peer_state.advertisements.remove(&removed); @@ -196,8 +190,7 @@ impl PeerData { fn prune_old_advertisements( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap, - per_relay_parent: &HashMap, + active_leaves: &HashMap, ) { if let PeerState::Collating(ref mut peer_state) = self.state { peer_state.advertisements.retain(|hash, _| { @@ -205,15 +198,12 @@ impl PeerData { // - Relay parent is an active leaf // - It belongs to allowed ancestry under some leaf // Discard otherwise. - per_relay_parent.get(hash).map_or(false, |s| { - is_relay_parent_in_implicit_view( - hash, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) + is_relay_parent_in_implicit_view( + hash, + implicit_view, + active_leaves, + peer_state.para_id, + ) }); } } @@ -224,17 +214,15 @@ impl PeerData { fn insert_advertisement( &mut self, on_relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, candidate_hash: Option, implicit_view: &ImplicitView, - active_leaves: &HashMap, + active_leaves: &HashMap, ) -> std::result::Result<(CollatorId, ParaId), InsertAdvertisementError> { match self.state { PeerState::Connected(_) => Err(InsertAdvertisementError::UndeclaredCollator), PeerState::Collating(ref mut state) => { if !is_relay_parent_in_implicit_view( &on_relay_parent, - relay_parent_mode, implicit_view, active_leaves, state.para_id, @@ -242,47 +230,34 @@ impl PeerData { return Err(InsertAdvertisementError::OutOfOurView) } - match (relay_parent_mode, candidate_hash) { - (ProspectiveParachainsMode::Disabled, candidate_hash) => { - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }, - (ProspectiveParachainsMode::Enabled { .. }, candidate_hash) => { - if let Some(candidate_hash) = candidate_hash { - if state - .advertisements - .get(&on_relay_parent) - .map_or(false, |candidates| candidates.contains(&candidate_hash)) - { - return Err(InsertAdvertisementError::Duplicate) - } - - let candidates = - state.advertisements.entry(on_relay_parent).or_default(); - - candidates.insert(candidate_hash); - } else { - if self.version != CollationVersion::V1 { - gum::error!( - target: LOG_TARGET, - "Programming error, `candidate_hash` can not be `None` \ - for non `V1` networking.", - ); - } - - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }; - }, - } + if let Some(candidate_hash) = candidate_hash { + if state + .advertisements + .get(&on_relay_parent) + .map_or(false, |candidates| candidates.contains(&candidate_hash)) + { + return Err(InsertAdvertisementError::Duplicate) + } + + let candidates = state.advertisements.entry(on_relay_parent).or_default(); + + candidates.insert(candidate_hash); + } else { + if self.version != CollationVersion::V1 { + gum::error!( + target: LOG_TARGET, + "Programming error, `candidate_hash` can not be `None` \ + for non `V1` networking.", + ); + } + + if state.advertisements.contains_key(&on_relay_parent) { + return Err(InsertAdvertisementError::Duplicate) + } + state + .advertisements + .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); + }; state.last_active = Instant::now(); Ok((state.collator_id.clone(), state.para_id)) @@ -363,19 +338,19 @@ struct GroupAssignments { } struct PerRelayParent { - prospective_parachains_mode: ProspectiveParachainsMode, + async_backing_params: AsyncBackingParams, assignment: GroupAssignments, collations: Collations, } impl PerRelayParent { fn new( - mode: ProspectiveParachainsMode, + async_backing_params: AsyncBackingParams, assignments: GroupAssignments, has_claim_queue: bool, ) -> Self { let collations = Collations::new(&assignments.current, has_claim_queue); - Self { prospective_parachains_mode: mode, assignment: assignments, collations } + Self { async_backing_params, assignment: assignments, collations } } } @@ -396,7 +371,7 @@ struct State { /// support prospective parachains. This mapping works as a replacement for /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. - active_leaves: HashMap, + active_leaves: HashMap, /// State tracked per relay parent. per_relay_parent: HashMap, @@ -444,21 +419,16 @@ struct State { fn is_relay_parent_in_implicit_view( relay_parent: &Hash, - relay_parent_mode: ProspectiveParachainsMode, implicit_view: &ImplicitView, - active_leaves: &HashMap, + active_leaves: &HashMap, para_id: ParaId, ) -> bool { - match relay_parent_mode { - ProspectiveParachainsMode::Disabled => active_leaves.contains_key(relay_parent), - ProspectiveParachainsMode::Enabled { .. } => active_leaves.iter().any(|(hash, mode)| { - mode.is_enabled() && - implicit_view - .known_allowed_relay_parents_under(hash, Some(para_id)) - .unwrap_or_default() - .contains(relay_parent) - }), - } + active_leaves.iter().any(|(hash, _)| { + implicit_view + .known_allowed_relay_parents_under(hash, Some(para_id)) + .unwrap_or_default() + .contains(relay_parent) + }) } // Returns the group assignments for the validator and bool indicating if they are obtained from the @@ -469,7 +439,6 @@ async fn assign_incoming( current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, ) -> Result<(GroupAssignments, bool)> where Sender: CollatorProtocolSenderTrait, @@ -505,18 +474,14 @@ where .map_err(Error::Runtime)? { // Runtime supports claim queue - use it - // - // `relay_parent_mode` is not examined here because if the runtime supports claim queue - // then it supports async backing params too (`ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT` - // < `CLAIM_QUEUE_RUNTIME_REQUIREMENT`). Some(mut claim_queue) => (claim_queue.0.remove(&core_now), true), // Claim queue is not supported by the runtime - use availability cores instead. None => ( cores.get(core_now.0 as usize).and_then(|c| match c { - CoreState::Occupied(core) if relay_parent_mode.is_enabled() => + CoreState::Occupied(core) => core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), - CoreState::Occupied(_) | CoreState::Free => None, + CoreState::Free => None, }), false, ), @@ -663,12 +628,7 @@ fn handle_peer_view_change(state: &mut State, peer_id: PeerId, view: View) { None => return, }; - peer_data.update_view( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - view, - ); + peer_data.update_view(&state.implicit_view, &state.active_leaves, view); state.collation_requests_cancel_handles.retain(|pc, handle| { let keep = pc.peer_id != peer_id || peer_data.has_advertised(&pc.relay_parent, None); if !keep { @@ -1089,7 +1049,6 @@ where .get(&relay_parent) .ok_or(AdvertisementError::RelayParentUnknown)?; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let assignment = &per_relay_parent.assignment; let collator_para_id = @@ -1105,17 +1064,16 @@ where let (collator_id, para_id) = peer_data .insert_advertisement( relay_parent, - relay_parent_mode, candidate_hash, &state.implicit_view, &state.active_leaves, ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent - .collations - .is_seconded_limit_reached(relay_parent_mode, para_id) - { + if per_relay_parent.collations.is_seconded_limit_reached( + per_relay_parent.async_backing_params.max_candidate_depth, + para_id, + ) { return Err(AdvertisementError::SecondedLimitReached) } @@ -1123,15 +1081,14 @@ where // Check if backing subsystem allows to second this candidate. // // This is also only important when async backing or elastic scaling is enabled. - let seconding_not_allowed = relay_parent_mode.is_enabled() && - !can_second( - sender, - collator_para_id, - relay_parent, - candidate_hash, - parent_head_data_hash, - ) - .await; + let seconding_not_allowed = !can_second( + sender, + collator_para_id, + relay_parent, + candidate_hash, + parent_head_data_hash, + ) + .await; if seconding_not_allowed { return Err(AdvertisementError::BlockedByBacking) @@ -1200,7 +1157,6 @@ where return Ok(()) }, }; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let prospective_candidate = prospective_candidate.map(|(candidate_hash, parent_head_data_hash)| ProspectiveCandidate { candidate_hash, @@ -1208,7 +1164,10 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_seconded_limit_reached(relay_parent_mode, para_id) { + if collations.is_seconded_limit_reached( + per_relay_parent.async_backing_params.max_candidate_depth, + para_id, + ) { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1238,21 +1197,11 @@ where // the new collation immediately fetch_collation(sender, state, pending_collation, collator_id).await?; }, - CollationStatus::Seconded if relay_parent_mode.is_enabled() => { + CollationStatus::Seconded => { // Limit is not reached (checked with `is_seconded_limit_reached` before the match // expression), it's allowed to second another collation. fetch_collation(sender, state, pending_collation, collator_id).await?; }, - CollationStatus::Seconded => { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - ?relay_parent_mode, - "A collation has already been seconded", - ); - }, } Ok(()) @@ -1274,7 +1223,8 @@ where let added = view.iter().filter(|h| !current_leaves.contains_key(h)); for leaf in added { - let mode = prospective_parachains_mode(sender, *leaf).await?; + let async_backing_params = + recv_runtime(request_async_backing_params(*leaf, sender).await).await?; if let Some(span) = view.span_per_head().get(leaf).cloned() { let per_leaf_span = PerLeafSpan::new(span, "validator-side"); @@ -1282,52 +1232,46 @@ where } let (assignments, has_claim_queue_support) = - assign_incoming(sender, &mut state.current_assignments, keystore, *leaf, mode).await?; + assign_incoming(sender, &mut state.current_assignments, keystore, *leaf).await?; - state.active_leaves.insert(*leaf, mode); - state - .per_relay_parent - .insert(*leaf, PerRelayParent::new(mode, assignments, has_claim_queue_support)); - - if mode.is_enabled() { - state - .implicit_view - .activate_leaf(sender, *leaf) - .await - .map_err(Error::ImplicitViewFetchError)?; - - // Order is always descending. - let allowed_ancestry = state - .implicit_view - .known_allowed_relay_parents_under(leaf, None) - .unwrap_or_default(); - for block_hash in allowed_ancestry { - if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - let (assignments, has_claim_queue_support) = assign_incoming( - sender, - &mut state.current_assignments, - keystore, - *block_hash, - mode, - ) - .await?; + state.active_leaves.insert(*leaf, async_backing_params); + state.per_relay_parent.insert( + *leaf, + PerRelayParent::new(async_backing_params, assignments, has_claim_queue_support), + ); - entry.insert(PerRelayParent::new(mode, assignments, has_claim_queue_support)); - } + state + .implicit_view + .activate_leaf(sender, *leaf) + .await + .map_err(Error::ImplicitViewFetchError)?; + + // Order is always descending. + let allowed_ancestry = state + .implicit_view + .known_allowed_relay_parents_under(leaf, None) + .unwrap_or_default(); + for block_hash in allowed_ancestry { + if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { + let (assignments, has_claim_queue_support) = + assign_incoming(sender, &mut state.current_assignments, keystore, *block_hash) + .await?; + + entry.insert(PerRelayParent::new( + async_backing_params, + assignments, + has_claim_queue_support, + )); } } } - for (removed, mode) in removed { + for (removed, _) in removed { state.active_leaves.remove(removed); // If the leaf is deactivated it still may stay in the view as a part // of implicit ancestry. Only update the state after the hash is actually // pruned from the block info storage. - let pruned = if mode.is_enabled() { - state.implicit_view.deactivate_leaf(*removed) - } else { - vec![*removed] - }; + let pruned = state.implicit_view.deactivate_leaf(*removed); for removed in pruned { if let Some(per_relay_parent) = state.per_relay_parent.remove(&removed) { @@ -1358,11 +1302,7 @@ where }); for (peer_id, peer_data) in state.peer_data.iter_mut() { - peer_data.prune_old_advertisements( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - ); + peer_data.prune_old_advertisements(&state.implicit_view, &state.active_leaves); // Disconnect peers who are not relevant to our current or next para. // @@ -1742,11 +1682,9 @@ async fn dequeue_next_collation_and_fetch( ) { while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|rp_state| { - rp_state.collations.get_next_collation_to_fetch( - &previous_fetch, - rp_state.prospective_parachains_mode, - &rp_state.assignment.current, - ) + rp_state + .collations + .get_next_collation_to_fetch(&previous_fetch, &rp_state.assignment.current) }) { gum::debug!( target: LOG_TARGET, @@ -1852,9 +1790,7 @@ async fn kick_off_seconding( collation_event.collator_protocol_version, collation_event.pending_collation.prospective_candidate, ) { - (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) - if per_relay_parent.prospective_parachains_mode.is_enabled() => - { + (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) => { let pvd = request_prospective_validation_data( ctx.sender(), relay_parent, @@ -1867,7 +1803,7 @@ async fn kick_off_seconding( (pvd, maybe_parent_head_data, Some(parent_head_data_hash)) }, // Support V2 collators without async backing enabled. - (CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => { + (CollationVersion::V1, _) => { let pvd = request_persisted_validation_data( ctx.sender(), candidate_receipt.descriptor().relay_parent, From 5dffddefcacc6573f2e18262d806c8a31741184a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 10 Jul 2024 10:36:41 +0300 Subject: [PATCH 036/138] Fix compilation errors in collation tests --- .../src/validator_side/tests/collation.rs | 57 +++++-------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs index 6d641ba7bf50..24972e66926d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; use polkadot_primitives::{CandidateHash, CollatorId, Hash, Id as ParaId}; use sc_network::PeerId; @@ -31,35 +30,34 @@ fn cant_add_more_than_claim_queue() { let para_a = ParaId::from(1); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let max_candidate_depth = 4; let claim_queue_support = true; let mut collations = Collations::new(&assignments, claim_queue_support); // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); collations.note_seconded(para_a); // and `para_b` is not affected - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); // second collation for `para_a` is also in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); collations.note_seconded(para_a); // `para_b`` is still not affected - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); // third collation for `para_a`` will be above the limit - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); // one fetch for b - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); collations.note_seconded(para_b); // and now both paras are over limit - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); + assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_b)); } #[test] @@ -69,22 +67,21 @@ fn pending_fetches_are_counted() { let para_a = ParaId::from(1); let para_b = ParaId::from(2); let assignments = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + let max_candidate_depth = 4; let claim_queue_support = true; let mut collations = Collations::new(&assignments, claim_queue_support); collations.status = CollationStatus::Fetching(para_a); //para_a is pending // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); collations.note_seconded(para_a); // second collation for `para_a` is not in the limit due to the pending fetch - assert!(collations.is_seconded_limit_reached(relay_parent_mode, para_a)); + assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); // a collation for `para_b` is accepted since the pending fetch is for `para_a` - assert!(!collations.is_seconded_limit_reached(relay_parent_mode, para_b)); + assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); } #[test] @@ -100,8 +97,6 @@ fn collation_fetching_respects_claim_queue() { let peer_b = PeerId::random(); let claim_queue = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -159,7 +154,6 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -170,7 +164,6 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -181,7 +174,6 @@ fn collation_fetching_respects_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -197,8 +189,6 @@ fn collation_fetching_fallback_works() { let peer_a = PeerId::random(); let claim_queue = vec![para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 4, allowed_ancestry_len: 3 }; let claim_queue_support = false; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -243,7 +233,6 @@ fn collation_fetching_fallback_works() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -254,7 +243,6 @@ fn collation_fetching_fallback_works() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -278,8 +266,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let peer_c = PeerId::random(); let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -379,7 +365,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -390,7 +375,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -401,7 +385,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -412,7 +395,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -423,7 +405,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -434,7 +415,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -458,8 +438,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { let peer_c = PeerId::random(); let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -554,7 +532,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -566,7 +543,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -581,7 +557,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -592,7 +567,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -607,7 +581,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -618,7 +591,6 @@ fn collation_fetching_fills_holes_in_claim_queue() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); @@ -635,8 +607,6 @@ fn collations_fetching_respects_seconded_limit() { let para_b = ParaId::from(2); let claim_queue = vec![para_a, para_b, para_a]; - let relay_parent_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 7, allowed_ancestry_len: 6 }; let claim_queue_support = true; let mut collations = Collations::new(&claim_queue, claim_queue_support); @@ -651,7 +621,6 @@ fn collations_fetching_respects_seconded_limit() { collations.get_next_collation_to_fetch( // doesn't matter since `fetching_from` is `None` &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - relay_parent_mode, &claim_queue, ) ); From 1c1744b23207cdcd08b3dfc2b3bb65bc9fb0884a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 11 Jul 2024 13:41:56 +0300 Subject: [PATCH 037/138] `is_seconded_limit_reached` uses the whole view --- .../src/validator_side/collation.rs | 158 ++------------- .../src/validator_side/mod.rs | 185 +++++++++++++----- 2 files changed, 156 insertions(+), 187 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 27bc1b2e966b..ae063b2b6c72 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -193,8 +193,6 @@ pub enum CollationStatus { Fetching(ParaId), /// We are waiting that a collation is being validated. WaitingOnValidation(ParaId), - /// We have seconded a collation. - Seconded, } impl Default for CollationStatus { @@ -203,14 +201,6 @@ impl Default for CollationStatus { } } -impl CollationStatus { - /// Downgrades to `Waiting`, but only if `self != Seconded`. - fn back_to_waiting(&mut self) { - // With async backing it's allowed to second more candidates. - *self = Self::Waiting - } -} - /// Information about collations per relay parent. pub struct Collations { /// What is the current status in regards to a collation for this relay parent? @@ -294,96 +284,6 @@ impl Collations { } } - /// Returns the next collation to fetch from the `waiting_queue`. - /// - /// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. - /// - /// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and - /// the passed in `finished_one` is the currently `waiting_collation`. - pub(super) fn get_next_collation_to_fetch( - &mut self, - finished_one: &(CollatorId, Option), - group_assignments: &Vec, - ) -> Option<(PendingCollation, CollatorId)> { - // If finished one does not match waiting_collation, then we already dequeued another fetch - // to replace it. - if let Some((collator_id, maybe_candidate_hash)) = self.fetching_from.as_ref() { - // If a candidate hash was saved previously, `finished_one` must include this too. - if collator_id != &finished_one.0 && - maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) - { - gum::trace!( - target: LOG_TARGET, - waiting_collation = ?self.fetching_from, - ?finished_one, - "Not proceeding to the next collation - has already been done." - ); - return None - } - } - self.status.back_to_waiting(); - - match self.status { - // If async backing is enabled `back_to_waiting` will change `Seconded` state to - // `Waiting` so that we can fetch more collations. If async backing is disabled we can't - // fetch more than one collation per relay parent so `None` is returned. - CollationStatus::Seconded => None, - CollationStatus::Waiting => self.pick_a_collation_to_fetch(&group_assignments), - CollationStatus::WaitingOnValidation(_) | CollationStatus::Fetching(_) => - unreachable!("We have reset the status above!"), - } - } - - /// Checks if another collation can be accepted. The number of collations that can be seconded - /// per parachain is limited by the entries in claim queue for the `ParaId` in question. - /// - /// If prospective parachains mode is not enabled then we fall back to synchronous backing. In - /// this case there is a limit of 1 collation per relay parent. - /// - /// If prospective parachains mode is enabled but claim queue is not supported then up to - /// `max_candidate_depth + 1` seconded collations are accepted. In theory in this case if two - /// parachains are sharing a core no fairness is guaranteed between them and the faster one can - /// starve the slower one by exhausting the limit with its own advertisements. In practice this - /// should not happen because core sharing implies core time support which implies the claim - /// queue being available. - pub(super) fn is_seconded_limit_reached( - &self, - max_candidate_depth: u32, - para_id: ParaId, - ) -> bool { - let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); - - if !self.claim_queue_state.is_some() { - gum::trace!( - target: LOG_TARGET, - ?para_id, - seconded_per_para=?self.seconded_per_para, - max_candidate_depth, - "is_seconded_limit_reached - no claim queue support" - ); - - return seconded_for_para > max_candidate_depth as usize; - } - - let pending_for_para = self.pending_for_para(para_id); - - let respected_per_para_limit = - self.claims_per_para.get(¶_id).copied().unwrap_or_default() > - seconded_for_para + pending_for_para; - - gum::trace!( - target: LOG_TARGET, - ?para_id, - claims_per_para=?self.claims_per_para, - seconded_per_para=?self.seconded_per_para, - ?pending_for_para, - ?respected_per_para_limit, - "is_seconded_limit_reached - with claim queue support" - ); - - !respected_per_para_limit - } - /// Adds a new collation to the waiting queue for the relay parent. This function doesn't /// perform any limits check. The caller (`enqueue_collation`) should assure that the collation /// limit is respected. @@ -408,58 +308,25 @@ impl Collations { /// Note: `group_assignments` is needed just for the fall back logic. It should be removed once /// claim queue runtime api is released everywhere since it will be redundant - claim queue will /// already be available in `self.claim_queue_state`. - fn pick_a_collation_to_fetch( + pub(super) fn pick_a_collation_to_fetch( &mut self, - group_assignments: &Vec, + claim_queue_state: Vec<(bool, ParaId)>, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, waiting_queue=?self.waiting_queue, claims_per_para=?self.claims_per_para, - ?group_assignments, "Pick a collation to fetch." ); - let claim_queue_state = match self.claim_queue_state.as_mut() { - Some(cqs) => cqs, - // Fallback if claim queue is not available. There is only one assignment in - // `group_assignments` so fetch the first advertisement for it and return. - None => - if let Some(assigned_para_id) = group_assignments.first() { - return self - .waiting_queue - .get_mut(assigned_para_id) - .and_then(|collations| collations.pop_front()) - } else { - unreachable!("Group assignments should contain at least one element.") - }, - }; - - let mut pending_for_para = match self.status { - CollationStatus::Waiting => None, - CollationStatus::Fetching(para_id) => Some(para_id), - CollationStatus::WaitingOnValidation(para_id) => Some(para_id), - CollationStatus::Seconded => None, - }; - for (fulfilled, assignment) in claim_queue_state { // if this assignment has been already fulfilled - move on - if *fulfilled { + if fulfilled { continue } - // if there is a pending fetch for this assignment, we should consider it satisfied and - // proceed with the next - if let Some(pending_for) = pending_for_para { - if pending_for == *assignment { - // the pending item should be used only once - pending_for_para = None; - continue - } - } - // we have found and unfulfilled assignment - try to fulfill it - if let Some(collations) = self.waiting_queue.get_mut(assignment) { + if let Some(collations) = self.waiting_queue.get_mut(&assignment) { if let Some(collation) = collations.pop_front() { // we don't mark the entry as fulfilled because it is considered pending return Some(collation) @@ -472,14 +339,25 @@ impl Collations { // Returns the number of pending collations for the specified `ParaId`. This function should // return either 0 or 1. - fn pending_for_para(&self, para_id: ParaId) -> usize { + fn pending_for_para(&self, para_id: &ParaId) -> usize { match self.status { - CollationStatus::Fetching(pending_para_id) if pending_para_id == para_id => 1, - CollationStatus::WaitingOnValidation(pending_para_id) if pending_para_id == para_id => + CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => 1, + CollationStatus::WaitingOnValidation(pending_para_id) + if pending_para_id == *para_id => 1, _ => 0, } } + + // Returns the number of seconded collations for the specified `ParaId`. + pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { + *self.seconded_per_para.get(¶_id).unwrap_or(&0) + self.pending_for_para(para_id) + } + + // Returns the number of claims in the claim queue for the specified `ParaId`. + pub(super) fn claims_for_para(&self, para_id: &ParaId) -> usize { + self.claims_per_para.get(para_id).copied().unwrap_or_default() + } } // Any error that can occur when awaiting a collation fetch response. diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 5be6004b666d..6438410f149f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -338,19 +338,14 @@ struct GroupAssignments { } struct PerRelayParent { - async_backing_params: AsyncBackingParams, assignment: GroupAssignments, collations: Collations, } impl PerRelayParent { - fn new( - async_backing_params: AsyncBackingParams, - assignments: GroupAssignments, - has_claim_queue: bool, - ) -> Self { + fn new(assignments: GroupAssignments, has_claim_queue: bool) -> Self { let collations = Collations::new(&assignments.current, has_claim_queue); - Self { async_backing_params, assignment: assignments, collations } + Self { assignment: assignments, collations } } } @@ -415,6 +410,9 @@ struct State { /// Aggregated reputation change reputation: ReputationAggregator, + + /// Last known finalized block number + last_finalized_block_num: u32, } fn is_relay_parent_in_implicit_view( @@ -1070,9 +1068,12 @@ where ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent.collations.is_seconded_limit_reached( - per_relay_parent.async_backing_params.max_candidate_depth, - para_id, + if is_seconded_limit_reached( + &state.implicit_view, + &state.per_relay_parent, + relay_parent, + per_relay_parent, + ¶_id, ) { return Err(AdvertisementError::SecondedLimitReached) } @@ -1121,7 +1122,7 @@ where } /// Enqueue collation for fetching. The advertisement is expected to be -/// validated. +/// validated and the seconding limit checked. async fn enqueue_collation( sender: &mut Sender, state: &mut State, @@ -1141,7 +1142,6 @@ where ?relay_parent, "Received advertise collation", ); - let per_relay_parent = match state.per_relay_parent.get_mut(&relay_parent) { Some(rp_state) => rp_state, None => { @@ -1164,20 +1164,6 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_seconded_limit_reached( - per_relay_parent.async_backing_params.max_candidate_depth, - para_id, - ) { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - "Limit of seconded collations reached for valid advertisement", - ); - return Ok(()) - } - let pending_collation = PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); @@ -1197,11 +1183,6 @@ where // the new collation immediately fetch_collation(sender, state, pending_collation, collator_id).await?; }, - CollationStatus::Seconded => { - // Limit is not reached (checked with `is_seconded_limit_reached` before the match - // expression), it's allowed to second another collation. - fetch_collation(sender, state, pending_collation, collator_id).await?; - }, } Ok(()) @@ -1235,10 +1216,9 @@ where assign_incoming(sender, &mut state.current_assignments, keystore, *leaf).await?; state.active_leaves.insert(*leaf, async_backing_params); - state.per_relay_parent.insert( - *leaf, - PerRelayParent::new(async_backing_params, assignments, has_claim_queue_support), - ); + state + .per_relay_parent + .insert(*leaf, PerRelayParent::new(assignments, has_claim_queue_support)); state .implicit_view @@ -1257,11 +1237,7 @@ where assign_incoming(sender, &mut state.current_assignments, keystore, *block_hash) .await?; - entry.insert(PerRelayParent::new( - async_backing_params, - assignments, - has_claim_queue_support, - )); + entry.insert(PerRelayParent::new(assignments, has_claim_queue_support)); } } } @@ -1288,6 +1264,8 @@ where state.fetched_candidates.retain(|k, _| k.relay_parent != removed); state.span_per_relay_parent.remove(&removed); } + + state.last_finalized_block_num = view.finalized_number; } // Remove blocked seconding requests that left the view. @@ -1460,7 +1438,7 @@ async fn process_msg( } if let Some(rp_state) = state.per_relay_parent.get_mut(&parent) { - rp_state.collations.status = CollationStatus::Seconded; + rp_state.collations.status = CollationStatus::Waiting; rp_state.collations.note_seconded(para_id); } @@ -1680,12 +1658,7 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { - while let Some((next, id)) = - state.per_relay_parent.get_mut(&relay_parent).and_then(|rp_state| { - rp_state - .collations - .get_next_collation_to_fetch(&previous_fetch, &rp_state.assignment.current) - }) { + while let Some((next, id)) = get_next_collation_to_fetch(&previous_fetch, relay_parent, state) { gum::debug!( target: LOG_TARGET, ?relay_parent, @@ -2057,3 +2030,121 @@ async fn handle_collation_fetch_response( state.metrics.on_request(metrics_result); result } + +/// Checks if another collation can be accepted. The number of collations that can be seconded +/// per parachain is limited by the entries in claim queue for the `ParaId` in question. Besides the +/// seconded collations at the relay parent of the advertisement any pending or seconded collations +/// at previous relay parents (up to `allowed_ancestry_len` blocks back or last finalized block) are +/// also counted towards the limit. +fn is_seconded_limit_reached( + implicit_view: &ImplicitView, + per_relay_parent: &HashMap, + relay_parent: Hash, + relay_parent_state: &PerRelayParent, + para_id: &ParaId, +) -> bool { + // Get the number of claims for `para_id` at `relay_parent` + // TODO: should be compared against the number of items in the claim queue!? + let claims_for_para = relay_parent_state.collations.claims_for_para(para_id); + + let seconded_and_pending_at_ancestors = seconded_and_pending_for_para_in_view( + implicit_view, + per_relay_parent, + &relay_parent, + para_id, + ); + + claims_for_para >= seconded_and_pending_at_ancestors +} + +fn seconded_and_pending_for_para_in_view( + implicit_view: &ImplicitView, + per_relay_parent: &HashMap, + relay_parent: &Hash, + para_id: &ParaId, +) -> usize { + // `known_allowed_relay_parents_under` returns all leaves within the view for the specified + // block hash including the block hash itself + implicit_view + .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) + .map(|ancestors| { + ancestors.iter().fold(0, |res, anc| { + res + per_relay_parent + .get(&anc) + .map(|rp| rp.collations.seconded_and_pending_for_para(para_id)) + .unwrap_or(0) + }) + }) + .unwrap_or(0) +} + +fn claim_queue_state( + relay_parent: &Hash, + per_relay_parent: &HashMap, + implicit_view: &ImplicitView, +) -> Option> { + let relay_parent_state = per_relay_parent.get(relay_parent)?; + let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); + let mut claims_per_para = scheduled_paras + .into_iter() + .map(|para_id| { + ( + *para_id, + seconded_and_pending_for_para_in_view( + implicit_view, + per_relay_parent, + relay_parent, + para_id, + ), + ) + }) + .collect::>(); + let claim_queue_state = relay_parent_state + .assignment + .current + .iter() + .map(|para_id| match claims_per_para.entry(*para_id) { + Entry::Occupied(mut entry) if *entry.get() > 0 => { + *entry.get_mut() -= 1; + (true, *para_id) + }, + _ => (false, *para_id), + }) + .collect::>(); + Some(claim_queue_state) +} + +/// Returns the next collation to fetch from the `waiting_queue`. +/// +/// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. +/// +/// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and +/// the passed in `finished_one` is the currently `waiting_collation`. +fn get_next_collation_to_fetch( + finished_one: &(CollatorId, Option), + relay_parent: Hash, + state: &mut State, +) -> Option<(PendingCollation, CollatorId)> { + let claim_queue_state = + claim_queue_state(&relay_parent, &state.per_relay_parent, &state.implicit_view)?; + let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; // TODO: this is looked up twice + + // If finished one does not match waiting_collation, then we already dequeued another fetch + // to replace it. + if let Some((collator_id, maybe_candidate_hash)) = rp_state.collations.fetching_from.as_ref() { + // If a candidate hash was saved previously, `finished_one` must include this too. + if collator_id != &finished_one.0 && + maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) + { + gum::trace!( + target: LOG_TARGET, + waiting_collation = ?rp_state.collations.fetching_from, + ?finished_one, + "Not proceeding to the next collation - has already been done." + ); + return None + } + } + rp_state.collations.status = CollationStatus::Waiting; + rp_state.collations.pick_a_collation_to_fetch(claim_queue_state) +} From aaccab1de9905611f5cd902ec00bee5820a66ae7 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 30 Aug 2024 11:54:08 +0300 Subject: [PATCH 038/138] Fix `is_seconded_limit_reached` check --- .../collator-protocol/src/validator_side/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 6438410f149f..1870a85b5fb2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2043,10 +2043,7 @@ fn is_seconded_limit_reached( relay_parent_state: &PerRelayParent, para_id: &ParaId, ) -> bool { - // Get the number of claims for `para_id` at `relay_parent` - // TODO: should be compared against the number of items in the claim queue!? let claims_for_para = relay_parent_state.collations.claims_for_para(para_id); - let seconded_and_pending_at_ancestors = seconded_and_pending_for_para_in_view( implicit_view, per_relay_parent, @@ -2054,7 +2051,16 @@ fn is_seconded_limit_reached( para_id, ); - claims_for_para >= seconded_and_pending_at_ancestors + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + claims_for_para, + seconded_and_pending_at_ancestors, + "Checking if seconded limit is reached" + ); + + seconded_and_pending_at_ancestors >= claims_for_para } fn seconded_and_pending_for_para_in_view( From b1df2e32eb3541b43158aa96b1c8c55a28232e9f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 11 Sep 2024 10:37:19 +0300 Subject: [PATCH 039/138] Trace logs useful for debugging tests --- .../src/validator_side/collation.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index ae063b2b6c72..a31b640668e5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -269,6 +269,8 @@ impl Collations { pub(super) fn note_seconded(&mut self, para_id: ParaId) { *self.seconded_per_para.entry(para_id).or_default() += 1; + gum::trace!(target: LOG_TARGET, ?para_id, new_count=*self.seconded_per_para.entry(para_id).or_default(), "Note seconded."); + // and the claim queue state if let Some(claim_queue_state) = self.claim_queue_state.as_mut() { for (satisfied, assignment) in claim_queue_state { @@ -351,7 +353,18 @@ impl Collations { // Returns the number of seconded collations for the specified `ParaId`. pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { - *self.seconded_per_para.get(¶_id).unwrap_or(&0) + self.pending_for_para(para_id) + let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); + let pending_for_para = self.pending_for_para(para_id); + + gum::trace!( + target: LOG_TARGET, + ?para_id, + seconded_for_para, + pending_for_para, + "Seconded and pending for para." + ); + + seconded_for_para + pending_for_para } // Returns the number of claims in the claim queue for the specified `ParaId`. From ce3a95e470db9a30ddbea4e61b8bdc087d9c8d02 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 11 Sep 2024 10:50:17 +0300 Subject: [PATCH 040/138] Handle unconnected candidates --- .../network/collator-protocol/src/validator_side/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 1870a85b5fb2..a1c403541210 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2070,9 +2070,15 @@ fn seconded_and_pending_for_para_in_view( para_id: &ParaId, ) -> usize { // `known_allowed_relay_parents_under` returns all leaves within the view for the specified - // block hash including the block hash itself + // block hash including the block hash itself. + // If the relay parent is not in the view (unconnected candidate) + // `known_allowed_relay_parents_under` will return `None`. In this case we still we just count + // the candidate at the specified relay parent. + // TODO: what to do when an unconnected candidate becomes connected and potentially we have + // accepted more candidates than the claim queue allows? implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) + .or(Some(&[*relay_parent])) // if the relay parent is not in view we still want to count it .map(|ancestors| { ancestors.iter().fold(0, |res, anc| { res + per_relay_parent From fe3c09d42a2f6996a9e6c36a9e8f4ca12fdcf696 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 11 Sep 2024 17:46:11 +0300 Subject: [PATCH 041/138] Rework pre-prospective parachains tests to work with claim queue --- .../src/validator_side/tests/mod.rs | 447 +++++------------- 1 file changed, 112 insertions(+), 335 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index cc097ba1065c..6da914028351 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use self::prospective_parachains::update_view; + use super::*; use assert_matches::assert_matches; use futures::{executor, future, Future}; @@ -29,15 +31,13 @@ use std::{ }; use polkadot_node_network_protocol::{ - our_view, peer_set::CollationVersion, request_response::{Requests, ResponseSender}, ObservedRole, }; use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem::{ - errors::RuntimeApiError, - messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, +use polkadot_node_subsystem::messages::{ + AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; @@ -50,16 +50,13 @@ use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, }; -mod collation; +// mod collation; mod prospective_parachains; const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = - RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; - fn dummy_pvd() -> PersistedValidationData { PersistedValidationData { parent_head: HeadData(vec![7, 8, 9]), @@ -213,6 +210,41 @@ impl TestState { state } + + fn with_one_scheduled_para() -> Self { + let mut state = Self::default(); + + let cores = vec![CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(Self::CHAIN_IDS[0]), + collator: None, + })]; + + let validator_groups = vec![vec![ValidatorIndex(0), ValidatorIndex(1)]]; + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + ); + + state.cores = cores; + state.validator_groups = validator_groups; + state.claim_queue = Some(claim_queue); + + state + } } type VirtualOverseer = @@ -307,73 +339,6 @@ async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } -async fn respond_to_core_info_queries( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, -) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Validators(tx), - )) => { - let _ = tx.send(Ok(test_state.validator_public.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ValidatorGroups(tx), - )) => { - let _ = tx.send(Ok(( - test_state.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::AvailabilityCores(tx), - )) => { - let _ = tx.send(Ok(test_state.cores.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Version(tx), - )) => { - let _ = tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ClaimQueue(tx), - )) => { - match test_state.claim_queue { - Some(ref claim_queue) => { - let _ = tx.send(Ok(claim_queue.clone())); - }, - None => { - let _ = tx.send(Err(RuntimeApiError::NotSupported { runtime_api_name: "doesnt_matter" })); - } - - } - - } - ); -} - /// Assert that the next message is a `CandidateBacking(Second())`. async fn assert_candidate_backing_second( virtual_overseer: &mut VirtualOverseer, @@ -549,148 +514,6 @@ async fn advertise_collation( .await; } -async fn assert_async_backing_params_request(virtual_overseer: &mut VirtualOverseer, hash: Hash) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(relay_parent, hash); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); -} - -// As we receive a relevant advertisement act on it and issue a collation request. -#[test] -fn act_on_advertisement() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - - assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - None, - ) - .await; - - virtual_overseer - }); -} - -/// Tests that validator side works with v2 network protocol -/// before async backing is enabled. -#[test] -fn act_on_advertisement_v2() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V2, - ) - .await; - - let pov = PoV { block_data: BlockData(vec![]) }; - let mut candidate_a = - dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); - candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; - candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - - let candidate_hash = candidate_a.hash(); - let parent_head_data_hash = Hash::zero(); - // v2 advertisement. - advertise_collation( - &mut virtual_overseer, - peer_b, - test_state.relay_parent, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - response_channel - .send(Ok(( - request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - &pov, - // Async backing isn't enabled and thus it should do it the old way. - CollationVersion::V1, - ) - .await; - - virtual_overseer - }); -} - // Test that other subsystems may modify collators' reputations. #[test] fn collator_reporting_works() { @@ -699,18 +522,18 @@ fn collator_reporting_works() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( + let head = Hash::from_low_u64_be(128); + let head_num: u32 = 0; + + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(head, head_num)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -812,22 +635,15 @@ fn fetch_one_collation_at_a_time() { let TestHarness { mut virtual_overseer, .. } = test_harness; let second = Hash::random(); - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &test_state, + vec![(test_state.relay_parent, 0), (second, 1)], + 2, + &test_state.async_backing_params, ) .await; - // Iter over view since the order may change due to sorted invariant. - for hash in our_view.iter() { - assert_async_backing_params_request(&mut virtual_overseer, *hash).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - } - let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -902,28 +718,22 @@ fn fetch_one_collation_at_a_time() { /// timeout and in case of an error. #[test] fn fetches_next_collation() { - let test_state = TestState::default(); + let test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let second = Hash::random(); - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &test_state, + vec![(test_state.relay_parent, 0), (second, 1)], + 2, + &test_state.async_backing_params, ) .await; - for hash in our_view.iter() { - assert_async_backing_params_request(&mut virtual_overseer, *hash).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - } - let peer_b = PeerId::random(); let peer_c = PeerId::random(); let peer_d = PeerId::random(); @@ -1031,17 +841,15 @@ fn reject_connection_to_next_group() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - let peer_b = PeerId::random(); connect_and_declare_collator( @@ -1080,21 +888,15 @@ fn fetch_next_collation_on_invalid_collation() { let second = Hash::random(); - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &test_state, + vec![(test_state.relay_parent, 0), (second, 1)], + 2, + &test_state.async_backing_params, ) .await; - for hash in our_view.iter() { - assert_async_backing_params_request(&mut virtual_overseer, *hash).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - } - let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -1189,19 +991,15 @@ fn inactive_disconnected() { let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_a], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, hash_a).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - let peer_b = PeerId::random(); connect_and_declare_collator( @@ -1231,7 +1029,7 @@ fn inactive_disconnected() { #[test] fn activity_extends_life() { - let test_state = TestState::default(); + let test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -1242,21 +1040,15 @@ fn activity_extends_life() { let hash_b = Hash::repeat_byte(1); let hash_c = Hash::repeat_byte(2); - let our_view = our_view![hash_a, hash_b, hash_c]; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 1)], + 3, + &test_state.async_backing_params, ) .await; - for hash in our_view.iter() { - assert_async_backing_params_request(&mut virtual_overseer, *hash).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - } - let peer_b = PeerId::random(); connect_and_declare_collator( @@ -1319,17 +1111,15 @@ fn disconnect_if_no_declare() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - let peer_b = PeerId::random(); overseer_send( @@ -1355,22 +1145,18 @@ fn disconnect_if_wrong_declare() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; + let peer_b = PeerId::random(); - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1417,22 +1203,18 @@ fn delay_reputation_change() { test_harness(ReputationAggregator::new(|_| false), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; + let peer_b = PeerId::random(); - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1506,43 +1288,38 @@ fn view_change_clears_old_collators() { let pair = CollatorPair::generate().0; - overseer_send( + let peer = PeerId::random(); + + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), + &test_state, + vec![(test_state.relay_parent, 0)], + 1, + &test_state.async_backing_params, ) .await; - assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - connect_and_declare_collator( &mut virtual_overseer, - peer_b, + peer, pair.clone(), test_state.chain_ids[0], CollationVersion::V1, ) .await; - let hash_b = Hash::repeat_byte(69); + test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_b], - )), + &test_state, + vec![], + 0, + &test_state.async_backing_params, ) .await; - test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - assert_async_backing_params_request(&mut virtual_overseer, hash_b).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - assert_collator_disconnect(&mut virtual_overseer, peer_b).await; + assert_collator_disconnect(&mut virtual_overseer, peer).await; virtual_overseer }) From b9ab5794b9c537eaac5daaa4962f9d4d79fe2b0a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 12 Sep 2024 16:17:53 +0300 Subject: [PATCH 042/138] Fix `collation_fetches_without_claimqueue` --- .../tests/prospective_parachains.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 64b392dda4f1..04b26d0979e0 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1597,6 +1597,8 @@ fn fair_collation_fetches() { }); } +// This should not happen in practice since claim queue is supported on all networks but just in +// case validate that the fallback works as expected #[test] fn collation_fetches_without_claimqueue() { let test_state = TestState::without_claim_queue(); @@ -1659,18 +1661,16 @@ fn collation_fetches_without_claimqueue() { } ); - // in fallback mode up to `max_candidate_depth` collations are accepted - for i in 0..test_state.async_backing_params.max_candidate_depth + 1 { - submit_second_and_assert( - &mut virtual_overseer, - keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), - head_b, - peer_a, - HeadData(vec![i as u8]), - ) - .await; - } + // in fallback mode we only accept what's scheduled on the core + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(TestState::CHAIN_IDS[0]), + head_b, + peer_a, + HeadData(vec![0 as u8]), + ) + .await; // `peer_a` sends another advertisement and it is ignored let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); From fe623bcd6f1d83db9dbf8f11e495f74fc94d4747 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 13 Sep 2024 15:17:55 +0300 Subject: [PATCH 043/138] Test - `collation_fetching_prefer_entries_earlier_in_claim_queue` --- .../tests/prospective_parachains.rs | 285 ++++++++++++++++-- 1 file changed, 261 insertions(+), 24 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 04b26d0979e0..6f4dc61b4742 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -334,6 +334,7 @@ async fn assert_persisted_validation_data( } } +// Combines dummy candidate creation, advertisement and fetching in a single call async fn submit_second_and_assert( virtual_overseer: &mut VirtualOverseer, keystore: KeystorePtr, @@ -342,6 +343,50 @@ async fn submit_second_and_assert( collator: PeerId, candidate_head_data: HeadData, ) { + let (candidate, commitments) = + create_dummy_candidate_and_commitments(para_id, candidate_head_data, relay_parent); + + let candidate_hash = candidate.hash(); + let parent_head_data_hash = Hash::zero(); + + assert_advertise_collation( + virtual_overseer, + collator, + relay_parent, + para_id, + (candidate_hash, parent_head_data_hash), + ) + .await; + + let response_channel = assert_fetch_collation_request( + virtual_overseer, + relay_parent, + para_id, + Some(candidate_hash), + ) + .await; + + let pov = PoV { block_data: BlockData(vec![1]) }; + + send_collation_and_assert_processing( + virtual_overseer, + keystore, + relay_parent, + para_id, + collator, + response_channel, + candidate, + commitments, + pov, + ) + .await; +} + +fn create_dummy_candidate_and_commitments( + para_id: ParaId, + candidate_head_data: HeadData, + relay_parent: Hash, +) -> (CandidateReceipt, CandidateCommitments) { let mut candidate = dummy_candidate_receipt_bad_sig(relay_parent, Some(Default::default())); candidate.descriptor.para_id = para_id; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); @@ -355,38 +400,41 @@ async fn submit_second_and_assert( }; candidate.commitments_hash = commitments.hash(); - let candidate_hash = candidate.hash(); - let parent_head_data_hash = Hash::zero(); + (candidate, commitments) +} - advertise_collation( - virtual_overseer, - collator, - relay_parent, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; +async fn assert_advertise_collation( + virtual_overseer: &mut VirtualOverseer, + peer: PeerId, + relay_parent: Hash, + expected_para_id: ParaId, + candidate: (CandidateHash, Hash), +) { + advertise_collation(virtual_overseer, peer, relay_parent, Some(candidate)).await; assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::CandidateBacking( CandidateBackingMessage::CanSecond(request, tx), ) => { - assert_eq!(request.candidate_hash, candidate_hash); - assert_eq!(request.candidate_para_id, para_id); - assert_eq!(request.parent_head_data_hash, parent_head_data_hash); + assert_eq!(request.candidate_hash, candidate.0); + assert_eq!(request.candidate_para_id, expected_para_id); + assert_eq!(request.parent_head_data_hash, candidate.1); tx.send(true).expect("receiving side should be alive"); } ); +} - let response_channel = assert_fetch_collation_request( - virtual_overseer, - relay_parent, - para_id, - Some(candidate_hash), - ) - .await; - - let pov = PoV { block_data: BlockData(vec![1]) }; - +async fn send_collation_and_assert_processing( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + relay_parent: Hash, + expected_para_id: ParaId, + expected_peer_id: PeerId, + response_channel: ResponseSender, + candidate: CandidateReceipt, + commitments: CandidateCommitments, + pov: PoV, +) { response_channel .send(Ok(( request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) @@ -398,7 +446,7 @@ async fn submit_second_and_assert( assert_candidate_backing_second( virtual_overseer, relay_parent, - para_id, + expected_para_id, &pov, CollationVersion::V2, ) @@ -408,7 +456,13 @@ async fn submit_second_and_assert( send_seconded_statement(virtual_overseer, keystore.clone(), &candidate).await; - assert_collation_seconded(virtual_overseer, relay_parent, collator, CollationVersion::V2).await; + assert_collation_seconded( + virtual_overseer, + relay_parent, + expected_peer_id, + CollationVersion::V2, + ) + .await; } #[test] @@ -1687,3 +1741,186 @@ fn collation_fetches_without_claimqueue() { virtual_overseer }); } + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue() { + let test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let head = Hash::from_low_u64_be(128); + let head_num: u32 = 2; + + update_view( + &mut virtual_overseer, + &test_state, + vec![(head, head_num)], + 1, + &test_state.async_backing_params, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + let (candidate_a1, commitments_a1) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![0 as u8]), head); + let (candidate_b1, commitments_b1) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![1 as u8]), head); + let (candidate_a2, commitments_a2) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2 as u8]), head); + let (candidate_a3, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3 as u8]), head); + let parent_head_data_a1 = HeadData(vec![0 as u8]); + let parent_head_data_b1 = HeadData(vec![1 as u8]); + let parent_head_data_a2 = HeadData(vec![2 as u8]); + let parent_head_data_a3 = HeadData(vec![3 as u8]); + + // advertise a collation for `para_id_a` but don't send the collation. This will be a + // pending fetch. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a1.hash(), parent_head_data_a1.hash()), + ) + .await; + + let response_channel_a1 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a1.hash()), + ) + .await; + // + + // advertise another collation for `para_id_a`. This one should be fetched last. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a2.hash(), parent_head_data_a2.hash()), + ) + .await; + + // There is a pending collation so nothing should be fetched + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertise a collation for `para_id_b`. This should be fetched second + assert_advertise_collation( + &mut virtual_overseer, + collator_b, + head, + para_id_b, + (candidate_b1.hash(), parent_head_data_b1.hash()), + ) + .await; + + // Again - no fetch because of the pending collation + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + //Now send a response for the first fetch and examine the second fetch + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a1, + candidate_a1, + commitments_a1, + PoV { block_data: BlockData(vec![1]) }, + ) + .await; + + // The next fetch should be for `para_id_b` + let response_channel_b = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_b, + Some(candidate_b1.hash()), + ) + .await; + + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_b, + collator_b, + response_channel_b, + candidate_b1, + commitments_b1, + PoV { block_data: BlockData(vec![2]) }, + ) + .await; + + // and the final one for `para_id_a` + let response_channel_a2 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a2.hash()), + ) + .await; + + // Advertise another collation for `para_id_a`. This should be rejected as there is no slot + // in the claim queue for it. One is fetched and one is pending. + advertise_collation( + &mut virtual_overseer, + collator_a, + head, + Some((candidate_a3.hash(), parent_head_data_a3.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Fetch the pending collation + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a2, + candidate_a2, + commitments_a2, + PoV { block_data: BlockData(vec![3]) }, + ) + .await; + + virtual_overseer + }); +} From d21668914f53b1435424e44ad3e73c4b9799592c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 13 Sep 2024 16:24:40 +0300 Subject: [PATCH 044/138] Remove collations test file - all tests are moved in prospective_parachains --- .../src/validator_side/tests/collation.rs | 627 ------------------ .../src/validator_side/tests/mod.rs | 1 - 2 files changed, 628 deletions(-) delete mode 100644 polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs deleted file mode 100644 index 24972e66926d..000000000000 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/collation.rs +++ /dev/null @@ -1,627 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -use polkadot_primitives::{CandidateHash, CollatorId, Hash, Id as ParaId}; - -use sc_network::PeerId; -use sp_core::sr25519; - -use crate::validator_side::tests::CollationStatus; - -use super::{Collations, PendingCollation, ProspectiveCandidate}; - -#[test] -fn cant_add_more_than_claim_queue() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let para_b = ParaId::from(2); - let assignments = vec![para_a, para_b, para_a]; - let max_candidate_depth = 4; - let claim_queue_support = true; - - let mut collations = Collations::new(&assignments, claim_queue_support); - - // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - collations.note_seconded(para_a); - // and `para_b` is not affected - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); - - // second collation for `para_a` is also in the limit - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - collations.note_seconded(para_a); - - // `para_b`` is still not affected - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); - - // third collation for `para_a`` will be above the limit - assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - - // one fetch for b - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); - collations.note_seconded(para_b); - - // and now both paras are over limit - assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_b)); -} - -#[test] -fn pending_fetches_are_counted() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let para_b = ParaId::from(2); - let assignments = vec![para_a, para_b, para_a]; - let max_candidate_depth = 4; - let claim_queue_support = true; - - let mut collations = Collations::new(&assignments, claim_queue_support); - collations.status = CollationStatus::Fetching(para_a); //para_a is pending - - // first collation for `para_a` is in the limit - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - collations.note_seconded(para_a); - - // second collation for `para_a` is not in the limit due to the pending fetch - assert!(collations.is_seconded_limit_reached(max_candidate_depth, para_a)); - - // a collation for `para_b` is accepted since the pending fetch is for `para_a` - assert!(!collations.is_seconded_limit_reached(max_candidate_depth, para_b)); -} - -#[test] -fn collation_fetching_respects_claim_queue() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - let peer_a = PeerId::random(); - - let para_b = ParaId::from(2); - let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); - let peer_b = PeerId::random(); - - let claim_queue = vec![para_a, para_b, para_a]; - let claim_queue_support = true; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - - collations.fetching_from = None; - collations.status = CollationStatus::Waiting; //nothing pending - - let relay_parent = Hash::repeat_byte(0x01); - - let collation_a1 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(1)), - parent_head_data_hash: Hash::repeat_byte(1), - }), - ), - collator_id_a.clone(), - ); - - let collation_a2 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(2)), - parent_head_data_hash: Hash::repeat_byte(2), - }), - ), - collator_id_a.clone(), - ); - - let collation_b1 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_b.clone(), - ); - - collations.add_to_waiting_queue(collation_b1.clone()); - collations.add_to_waiting_queue(collation_a1.clone()); - collations.add_to_waiting_queue(collation_a2.clone()); - - assert_eq!( - Some(collation_a1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a1.0.para_id); - - assert_eq!( - Some(collation_b1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_b1.0.para_id); - - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a2.0.para_id); -} - -#[test] -fn collation_fetching_fallback_works() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - let peer_a = PeerId::random(); - - let claim_queue = vec![para_a]; - let claim_queue_support = false; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - - collations.fetching_from = None; - collations.status = CollationStatus::Waiting; //nothing pending - - let relay_parent = Hash::repeat_byte(0x01); - - let collation_a1 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(1)), - parent_head_data_hash: Hash::repeat_byte(1), - }), - ), - collator_id_a.clone(), - ); - - let collation_a2 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(2)), - parent_head_data_hash: Hash::repeat_byte(2), - }), - ), - collator_id_a.clone(), - ); - - // Collations will be fetched in the order they were added - collations.add_to_waiting_queue(collation_a1.clone()); - collations.add_to_waiting_queue(collation_a2.clone()); - - assert_eq!( - Some(collation_a1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a1.0.para_id); - - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a2.0.para_id); -} - -#[test] -fn collation_fetching_prefer_entries_earlier_in_claim_queue() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - let peer_a = PeerId::random(); - - let para_b = ParaId::from(2); - let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); - let peer_b = PeerId::random(); - - let para_c = ParaId::from(3); - let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); - let peer_c = PeerId::random(); - - let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; - let claim_queue_support = true; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - collations.fetching_from = None; - collations.status = CollationStatus::Waiting; //nothing pending - - let relay_parent = Hash::repeat_byte(0x01); - - let collation_a1 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(1)), - parent_head_data_hash: Hash::repeat_byte(1), - }), - ), - collator_id_a.clone(), - ); - - let collation_a2 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(2)), - parent_head_data_hash: Hash::repeat_byte(2), - }), - ), - collator_id_a.clone(), - ); - - let collation_b1 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_b.clone(), - ); - - let collation_b2 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(4)), - parent_head_data_hash: Hash::repeat_byte(4), - }), - ), - collator_id_b.clone(), - ); - - let collation_c1 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(5)), - parent_head_data_hash: Hash::repeat_byte(5), - }), - ), - collator_id_c.clone(), - ); - - let collation_c2 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(6)), - parent_head_data_hash: Hash::repeat_byte(6), - }), - ), - collator_id_c.clone(), - ); - - // Despite the order here the fetches should follow the claim queue - collations.add_to_waiting_queue(collation_c1.clone()); - collations.add_to_waiting_queue(collation_c2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); - collations.add_to_waiting_queue(collation_b2.clone()); - collations.add_to_waiting_queue(collation_a1.clone()); - collations.add_to_waiting_queue(collation_a2.clone()); - - assert_eq!( - Some(collation_a1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a1.0.para_id); - - assert_eq!( - Some(collation_b1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_b1.0.para_id); - - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a2.0.para_id); - - assert_eq!( - Some(collation_b2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_b2.0.para_id); - - assert_eq!( - Some(collation_c1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_c1.0.para_id); - - assert_eq!( - Some(collation_c2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_c2.0.para_id); -} - -#[test] -fn collation_fetching_fills_holes_in_claim_queue() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - let peer_a = PeerId::random(); - - let para_b = ParaId::from(2); - let collator_id_b = CollatorId::from(sr25519::Public::from_raw([20u8; 32])); - let peer_b = PeerId::random(); - - let para_c = ParaId::from(3); - let collator_id_c = CollatorId::from(sr25519::Public::from_raw([30u8; 32])); - let peer_c = PeerId::random(); - - let claim_queue = vec![para_a, para_b, para_a, para_b, para_c, para_c]; - let claim_queue_support = true; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - collations.fetching_from = None; - collations.status = CollationStatus::Waiting; //nothing pending - - let relay_parent = Hash::repeat_byte(0x01); - - let collation_a1 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(1)), - parent_head_data_hash: Hash::repeat_byte(1), - }), - ), - collator_id_a.clone(), - ); - - let collation_a2 = ( - PendingCollation::new( - relay_parent, - para_a, - &peer_a, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(2)), - parent_head_data_hash: Hash::repeat_byte(2), - }), - ), - collator_id_a.clone(), - ); - - let collation_b1 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(3)), - parent_head_data_hash: Hash::repeat_byte(3), - }), - ), - collator_id_b.clone(), - ); - - let collation_b2 = ( - PendingCollation::new( - relay_parent, - para_b, - &peer_b, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(4)), - parent_head_data_hash: Hash::repeat_byte(4), - }), - ), - collator_id_b.clone(), - ); - - let collation_c1 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(5)), - parent_head_data_hash: Hash::repeat_byte(5), - }), - ), - collator_id_c.clone(), - ); - - let collation_c2 = ( - PendingCollation::new( - relay_parent, - para_c, - &peer_c, - Some(ProspectiveCandidate { - candidate_hash: CandidateHash(Hash::repeat_byte(6)), - parent_head_data_hash: Hash::repeat_byte(6), - }), - ), - collator_id_c.clone(), - ); - - collations.add_to_waiting_queue(collation_c1.clone()); - collations.add_to_waiting_queue(collation_a1.clone()); - - assert_eq!( - Some(collation_a1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a1.0.para_id); - - // fetch c1 since there is nothing better to fetch - assert_eq!( - Some(collation_c1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_c1.0.para_id); - - // b1 should be prioritized since there is a hole in the claim queue - collations.add_to_waiting_queue(collation_c2.clone()); - collations.add_to_waiting_queue(collation_b1.clone()); - - assert_eq!( - Some(collation_b1.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_b1.0.para_id); - - assert_eq!( - Some(collation_c2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_c2.0.para_id); - - // same with a2 - collations.add_to_waiting_queue(collation_b2.clone()); - collations.add_to_waiting_queue(collation_a2.clone()); - - assert_eq!( - Some(collation_a2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_a2.0.para_id); - - assert_eq!( - Some(collation_b2.clone()), - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); - collations.note_seconded(collation_b2.0.para_id); -} - -#[test] -fn collations_fetching_respects_seconded_limit() { - sp_tracing::init_for_tests(); - - let para_a = ParaId::from(1); - let collator_id_a = CollatorId::from(sr25519::Public::from_raw([10u8; 32])); - - let para_b = ParaId::from(2); - - let claim_queue = vec![para_a, para_b, para_a]; - let claim_queue_support = true; - - let mut collations = Collations::new(&claim_queue, claim_queue_support); - collations.fetching_from = None; - collations.status = CollationStatus::Fetching(para_a); //para_a is pending - - collations.note_seconded(para_a); - collations.note_seconded(para_a); - - assert_eq!( - None, - collations.get_next_collation_to_fetch( - // doesn't matter since `fetching_from` is `None` - &(collator_id_a.clone(), Some(CandidateHash(Hash::repeat_byte(0)))), - &claim_queue, - ) - ); -} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 6da914028351..8786b845a6b1 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -50,7 +50,6 @@ use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, }; -// mod collation; mod prospective_parachains; const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); From ea99c7aeded6c8b7b6f5bb4f5b380fd6742ab278 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 16 Sep 2024 10:03:45 +0300 Subject: [PATCH 045/138] fixup - collation_fetching_prefer_entries_earlier_in_claim_queue --- .../src/validator_side/tests/prospective_parachains.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 6f4dc61b4742..1c60a0b15215 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1818,7 +1818,6 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { Some(candidate_a1.hash()), ) .await; - // // advertise another collation for `para_id_a`. This one should be fetched last. assert_advertise_collation( From ee155f54fa64ac38b9ce9310213dda4f086bbee6 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 16 Sep 2024 13:22:50 +0300 Subject: [PATCH 046/138] New test - `collation_fetching_considers_advertisements_from_the_whole_view` --- .../tests/prospective_parachains.rs | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 1c60a0b15215..8bce4e959915 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1923,3 +1923,161 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { virtual_overseer }); } + +#[test] +fn collation_fetching_considers_advertisements_from_the_whole_view() { + let test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(128); + + update_view( + &mut virtual_overseer, + &test_state, + vec![(relay_parent_2, 2)], + 1, + &test_state.async_backing_params, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + // Two advertisements for `para_id_a` at `relay_parent_2` + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0 as u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![1 as u8]), + ) + .await; + + // parent hashes are hardcoded in `get_parent_hash` (called from `update_view`) to be + // `current hash + 1` so we need to craft them carefully (decrement by 2) in order to make + // them fall in the same view. + let relay_parent_4 = Hash::from_low_u64_be(126); + + update_view( + &mut virtual_overseer, + &test_state, + vec![(relay_parent_4, 4)], + 1, + &test_state.async_backing_params, + ) + .await; + + // One advertisement for `para_id_b` at `relay_parent_4` + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_4, + collator_b, + HeadData(vec![3 as u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored + let (candidate_a, _) = create_dummy_candidate_and_commitments( + para_id_a, + HeadData(vec![5 as u8]), + relay_parent_4, + ); + let parent_head_data_a = HeadData(vec![5 as u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_4, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored + let (candidate_b, _) = create_dummy_candidate_and_commitments( + para_id_b, + HeadData(vec![6 as u8]), + relay_parent_4, + ); + let parent_head_data_b = HeadData(vec![6 as u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_4, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one + // can be accepted + let relay_parent_6 = Hash::from_low_u64_be(124); + update_view( + &mut virtual_overseer, + &test_state, + vec![(relay_parent_6, 6)], + 1, + &test_state.async_backing_params, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_6, + collator_a, + HeadData(vec![3 as u8]), + ) + .await; + + virtual_overseer + }); +} From 515a784abefae6ff79349d0824f4dce84b54ab19 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 12:03:03 +0300 Subject: [PATCH 047/138] Update PRdoc and comments --- .../src/validator_side/collation.rs | 22 +++++++++---------- prdoc/pr_4880.prdoc | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index a31b640668e5..305e112f0e3f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -18,14 +18,18 @@ //! //! Usually a path of collations is as follows: //! 1. First, collation must be advertised by collator. +//! 2. The validator inspects the claim queue and decides if the collation should be fetched +//! based on the entries there. A parachain can't have more fetched collations than the +//! entries in the claim queue at a specific relay parent. When calculating this limit the +//! validator counts all advertisements within its view not just at the relay parent. //! 2. If the advertisement was accepted, it's queued for fetch (per relay parent). //! 3. Once it's requested, the collation is said to be Pending. //! 4. Pending collation becomes Fetched once received, we send it to backing for validation. //! 5. If it turns to be invalid or async backing allows seconding another candidate, carry on //! with the next advertisement, otherwise we're done with this relay parent. //! -//! ┌──────────────────────────────────────────┐ -//! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated +//! ┌───────────────────────────────────┐ +//! └─▶Waiting ─▶ Fetching ─▶ WaitingOnValidation use std::{ collections::{BTreeMap, VecDeque}, @@ -189,9 +193,9 @@ pub struct PendingCollationFetch { pub enum CollationStatus { /// We are waiting for a collation to be advertised to us. Waiting, - /// We are currently fetching a collation. + /// We are currently fetching a collation for the specified `ParaId`. Fetching(ParaId), - /// We are waiting that a collation is being validated. + /// We are waiting that a collation is being validated for the specified `ParaId`. WaitingOnValidation(ParaId), } @@ -207,14 +211,8 @@ pub struct Collations { pub status: CollationStatus, /// Collator we're fetching from, optionally which candidate was requested. /// - /// This is the last fetch for the relay parent. The value is used in - /// `get_next_collation_to_fetch` (called from `dequeue_next_collation_and_fetch`) to determine - /// if the last fetched collation is the same as the one which just finished. If yes - another - /// collation should be fetched. If not - another fetch was already initiated and - /// `get_next_collation_to_fetch` will do nothing. - /// - /// For the reasons above this value is not set to `None` when the fetch is done! Don't use it - /// to check if there is a pending fetch. + /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` + /// yet. pub fetching_from: Option<(CollatorId, Option)>, /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. waiting_queue: BTreeMap>, diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index 3b16081f5dd1..4c05dcebb179 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -4,8 +4,8 @@ doc: - audience: "Node Dev" description: | Implements collation fetching fairness in the validator side of the collator protocol. With - core time if two (or more) parachains share a single core no fairness is guaranteed between - them in terms of collation fetching. The current implementation was accepting up to + core time in place if two (or more) parachains share a single core no fairness is guaranteed + between them in terms of collation fetching. The current implementation was accepting up to `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached no new collations are accepted. A misbehaving collator can abuse this fact and prevent other collators/parachains from advertising collations by advertising `max_candidate_depth + 1` From 4ef691962e2d788fb2347e239e5613a3e2010afa Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 13:22:04 +0300 Subject: [PATCH 048/138] Combine `seconded_per_para` and `claims_per_para` from collations in a single struct --- .../src/validator_side/collation.rs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 305e112f0e3f..69d263d59d1d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -205,6 +205,15 @@ impl Default for CollationStatus { } } +/// The number of claims in the claim queue and seconded candidates count for a specific `ParaId`. +#[derive(Default, Debug)] +struct CandidatesStatePerPara { + /// How many collations have been seconded. + pub seconded_per_para: usize, + // Claims in the claim queue for the `ParaId`. + pub claims_per_para: usize, +} + /// Information about collations per relay parent. pub struct Collations { /// What is the current status in regards to a collation for this relay parent? @@ -216,12 +225,8 @@ pub struct Collations { pub fetching_from: Option<(CollatorId, Option)>, /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. waiting_queue: BTreeMap>, - /// How many collations have been seconded per `ParaId`. - seconded_per_para: BTreeMap, - // Claims per `ParaId` for the assigned core at the relay parent. This information is obtained - // from `GroupAssignments` which contains either the claim queue (if runtime supports it) for - // the core or the `ParaId` of the parachain assigned to the core. - claims_per_para: BTreeMap, + /// Number of seconded candidates and claims in the claim queue per `ParaId`. + candidates_state: BTreeMap, // Represents the claim queue at the relay parent. The `bool` field indicates if a candidate // was seconded for the `ParaId` at the position in question. In other words - if the claim is // 'satisfied'. If the claim queue is not available `claim_queue_state` will be `None`. @@ -240,11 +245,11 @@ impl Collations { /// Once claim queue runtime api is released everywhere this logic won't be needed anymore and /// can be cleaned up. pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { - let mut claims_per_para = BTreeMap::new(); + let mut candidates_state = BTreeMap::::new(); let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); for para_id in group_assignments { - *claims_per_para.entry(*para_id).or_default() += 1; + candidates_state.entry(*para_id).or_default().claims_per_para += 1; claim_queue_state.push((false, *para_id)); } @@ -257,17 +262,16 @@ impl Collations { status: Default::default(), fetching_from: None, waiting_queue: Default::default(), - seconded_per_para: Default::default(), - claims_per_para, + candidates_state, claim_queue_state, } } /// Note a seconded collation for a given para. pub(super) fn note_seconded(&mut self, para_id: ParaId) { - *self.seconded_per_para.entry(para_id).or_default() += 1; + self.candidates_state.entry(para_id).or_default().seconded_per_para += 1; - gum::trace!(target: LOG_TARGET, ?para_id, new_count=*self.seconded_per_para.entry(para_id).or_default(), "Note seconded."); + gum::trace!(target: LOG_TARGET, ?para_id, new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, "Note seconded."); // and the claim queue state if let Some(claim_queue_state) = self.claim_queue_state.as_mut() { @@ -315,7 +319,7 @@ impl Collations { gum::trace!( target: LOG_TARGET, waiting_queue=?self.waiting_queue, - claims_per_para=?self.claims_per_para, + candidates_state=?self.candidates_state, "Pick a collation to fetch." ); @@ -351,7 +355,11 @@ impl Collations { // Returns the number of seconded collations for the specified `ParaId`. pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { - let seconded_for_para = *self.seconded_per_para.get(¶_id).unwrap_or(&0); + let seconded_for_para = self + .candidates_state + .get(¶_id) + .map(|state| state.seconded_per_para) + .unwrap_or_default(); let pending_for_para = self.pending_for_para(para_id); gum::trace!( @@ -367,7 +375,10 @@ impl Collations { // Returns the number of claims in the claim queue for the specified `ParaId`. pub(super) fn claims_for_para(&self, para_id: &ParaId) -> usize { - self.claims_per_para.get(para_id).copied().unwrap_or_default() + self.candidates_state + .get(para_id) + .map(|state| state.claims_per_para) + .unwrap_or_default() } } From bd7174f031c1562f6b428cd7763f7d54dd40088e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 14:15:33 +0300 Subject: [PATCH 049/138] No need to handle missing claim queue anymore --- .../src/validator_side/collation.rs | 39 +-------------- .../src/validator_side/mod.rs | 49 +++++++++---------- 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 69d263d59d1d..38a2d21a2a5b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -227,65 +227,28 @@ pub struct Collations { waiting_queue: BTreeMap>, /// Number of seconded candidates and claims in the claim queue per `ParaId`. candidates_state: BTreeMap, - // Represents the claim queue at the relay parent. The `bool` field indicates if a candidate - // was seconded for the `ParaId` at the position in question. In other words - if the claim is - // 'satisfied'. If the claim queue is not available `claim_queue_state` will be `None`. - claim_queue_state: Option>, } impl Collations { - /// `Collations` should work with and without claim queue support. If the claim queue runtime - /// api is available `GroupAssignments` the claim queue. If not - group assignments will contain - /// just one item (what's scheduled on the core). - /// - /// Some of the logic in `Collations` relies on the claim queue and if it is not available - /// fallbacks to another logic. For this reason `Collations` needs to know if claim queue is - /// available or not. - /// - /// Once claim queue runtime api is released everywhere this logic won't be needed anymore and - /// can be cleaned up. - pub(super) fn new(group_assignments: &Vec, has_claim_queue: bool) -> Self { + pub(super) fn new(group_assignments: &Vec) -> Self { let mut candidates_state = BTreeMap::::new(); - let mut claim_queue_state = Vec::with_capacity(group_assignments.len()); for para_id in group_assignments { candidates_state.entry(*para_id).or_default().claims_per_para += 1; - claim_queue_state.push((false, *para_id)); } - // Not optimal but if the claim queue is not available `group_assignments` will have just - // one element. Can be fixed once claim queue api is released everywhere and the fallback - // code is cleaned up. - let claim_queue_state = if has_claim_queue { Some(claim_queue_state) } else { None }; - Self { status: Default::default(), fetching_from: None, waiting_queue: Default::default(), candidates_state, - claim_queue_state, } } /// Note a seconded collation for a given para. pub(super) fn note_seconded(&mut self, para_id: ParaId) { self.candidates_state.entry(para_id).or_default().seconded_per_para += 1; - gum::trace!(target: LOG_TARGET, ?para_id, new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, "Note seconded."); - - // and the claim queue state - if let Some(claim_queue_state) = self.claim_queue_state.as_mut() { - for (satisfied, assignment) in claim_queue_state { - if *satisfied { - continue - } - - if assignment == ¶_id { - *satisfied = true; - break - } - } - } } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 17ad576b7ef6..8d2f5548394d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -342,8 +342,8 @@ struct PerRelayParent { } impl PerRelayParent { - fn new(assignments: GroupAssignments, has_claim_queue: bool) -> Self { - let collations = Collations::new(&assignments.current, has_claim_queue); + fn new(assignments: GroupAssignments) -> Self { + let collations = Collations::new(&assignments.current); Self { assignment: assignments, collations } } } @@ -428,15 +428,13 @@ fn is_relay_parent_in_implicit_view( }) } -// Returns the group assignments for the validator and bool indicating if they are obtained from the -// claim queue or not. The latter is used to handle the fall back case when the claim queue api is -// not available in the runtime. +// Returns the group assignments for the validator based on the information in the claim queue async fn assign_incoming( sender: &mut Sender, current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, -) -> Result<(GroupAssignments, bool)> +) -> Result where Sender: CollatorProtocolSenderTrait, { @@ -463,25 +461,27 @@ where rotation_info.core_for_group(group, cores.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); - return Ok((GroupAssignments { current: Vec::new() }, false)) + return Ok(GroupAssignments { current: Vec::new() }) }; - let (paras_now, has_claim_queue) = match fetch_claim_queue(sender, relay_parent) - .await - .map_err(Error::Runtime)? - { + let paras_now = match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { // Runtime supports claim queue - use it - Some(mut claim_queue) => (claim_queue.0.remove(&core_now), true), - // Claim queue is not supported by the runtime - use availability cores instead. - None => ( + Some(mut claim_queue) => claim_queue.0.remove(&core_now), + // Should never happen since claim queue is released everywhere. + None => { + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + "Claim queue is not available, falling back to availability cores", + ); + cores.get(core_now.0 as usize).and_then(|c| match c { CoreState::Occupied(core) => core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), CoreState::Free => None, - }), - false, - ), + }) + }, }; let paras_now = paras_now.unwrap_or_else(|| VecDeque::new()); @@ -498,10 +498,7 @@ where } } - Ok(( - GroupAssignments { current: paras_now.into_iter().collect::>() }, - has_claim_queue, - )) + Ok(GroupAssignments { current: paras_now.into_iter().collect::>() }) } fn remove_outgoing( @@ -1211,13 +1208,11 @@ where state.span_per_relay_parent.insert(*leaf, per_leaf_span); } - let (assignments, has_claim_queue_support) = + let assignments = assign_incoming(sender, &mut state.current_assignments, keystore, *leaf).await?; state.active_leaves.insert(*leaf, async_backing_params); - state - .per_relay_parent - .insert(*leaf, PerRelayParent::new(assignments, has_claim_queue_support)); + state.per_relay_parent.insert(*leaf, PerRelayParent::new(assignments)); state .implicit_view @@ -1232,11 +1227,11 @@ where .unwrap_or_default(); for block_hash in allowed_ancestry { if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - let (assignments, has_claim_queue_support) = + let assignments = assign_incoming(sender, &mut state.current_assignments, keystore, *block_hash) .await?; - entry.insert(PerRelayParent::new(assignments, has_claim_queue_support)); + entry.insert(PerRelayParent::new(assignments)); } } } From df6165e6aa090a26693303d303e65c88438d0cda Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 14:55:28 +0300 Subject: [PATCH 050/138] Remove dead code and fix some comments --- .../src/validator_side/collation.rs | 25 +++++++------------ .../src/validator_side/mod.rs | 5 ---- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 38a2d21a2a5b..5c7335297e0c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -252,8 +252,7 @@ impl Collations { } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't - /// perform any limits check. The caller (`enqueue_collation`) should assure that the collation - /// limit is respected. + /// perform any limits check. The caller should assure that the collation limit is respected. pub(super) fn add_to_waiting_queue(&mut self, collation: (PendingCollation, CollatorId)) { self.waiting_queue.entry(collation.0.para_id).or_default().push_back(collation); } @@ -268,13 +267,8 @@ impl Collations { /// fetch is the first unsatisfied entry from the claim queue for which there is an /// advertisement. /// - /// If claim queue is not supported then `group_assignment` should contain just one element and - /// the score won't matter. In this case collations will be fetched in the order they were - /// received. - /// - /// Note: `group_assignments` is needed just for the fall back logic. It should be removed once - /// claim queue runtime api is released everywhere since it will be redundant - claim queue will - /// already be available in `self.claim_queue_state`. + /// `claim_queue_state` represents the claim queue and a boolean flag indicating if the claim + /// queue entry is fulfilled or not. pub(super) fn pick_a_collation_to_fetch( &mut self, claim_queue_state: Vec<(bool, ParaId)>, @@ -304,15 +298,14 @@ impl Collations { None } - // Returns the number of pending collations for the specified `ParaId`. This function should - // return either 0 or 1. - fn pending_for_para(&self, para_id: &ParaId) -> usize { + // Returns `true` if there is a pending collation for the specified `ParaId`. + fn pending_for_para(&self, para_id: &ParaId) -> bool { match self.status { - CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => 1, + CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => true, CollationStatus::WaitingOnValidation(pending_para_id) if pending_para_id == *para_id => - 1, - _ => 0, + true, + _ => false, } } @@ -323,7 +316,7 @@ impl Collations { .get(¶_id) .map(|state| state.seconded_per_para) .unwrap_or_default(); - let pending_for_para = self.pending_for_para(para_id); + let pending_for_para = self.pending_for_para(para_id) as usize; gum::trace!( target: LOG_TARGET, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 8d2f5548394d..0d0ee9906616 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -409,9 +409,6 @@ struct State { /// Aggregated reputation change reputation: ReputationAggregator, - - /// Last known finalized block number - last_finalized_block_num: u32, } fn is_relay_parent_in_implicit_view( @@ -1258,8 +1255,6 @@ where state.fetched_candidates.retain(|k, _| k.relay_parent != removed); state.span_per_relay_parent.remove(&removed); } - - state.last_finalized_block_num = view.finalized_number; } // Remove blocked seconding requests that left the view. From 4c5c2711a46203daabf0885b64689dcbbfecb75c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 18:36:59 +0300 Subject: [PATCH 051/138] Remove `is_seconded_limit_reached` and use the code directly due to the weird function params --- .../src/validator_side/mod.rs | 53 ++++++------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 0d0ee9906616..853036738ce2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1061,13 +1061,26 @@ where ) .map_err(AdvertisementError::Invalid)?; - if is_seconded_limit_reached( + let claims_for_para = per_relay_parent.collations.claims_for_para(¶_id); + let seconded_and_pending_at_ancestors = seconded_and_pending_for_para_in_view( &state.implicit_view, &state.per_relay_parent, - relay_parent, - per_relay_parent, + &relay_parent, ¶_id, - ) { + ); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + claims_for_para, + seconded_and_pending_at_ancestors, + "Checking if seconded limit is reached" + ); + + // Checks if another collation can be accepted. The number of collations that can be seconded + // per parachain is limited by the entries in claim queue for the `ParaId` in question. + if seconded_and_pending_at_ancestors >= claims_for_para { return Err(AdvertisementError::SecondedLimitReached) } @@ -2020,38 +2033,6 @@ async fn handle_collation_fetch_response( result } -/// Checks if another collation can be accepted. The number of collations that can be seconded -/// per parachain is limited by the entries in claim queue for the `ParaId` in question. Besides the -/// seconded collations at the relay parent of the advertisement any pending or seconded collations -/// at previous relay parents (up to `allowed_ancestry_len` blocks back or last finalized block) are -/// also counted towards the limit. -fn is_seconded_limit_reached( - implicit_view: &ImplicitView, - per_relay_parent: &HashMap, - relay_parent: Hash, - relay_parent_state: &PerRelayParent, - para_id: &ParaId, -) -> bool { - let claims_for_para = relay_parent_state.collations.claims_for_para(para_id); - let seconded_and_pending_at_ancestors = seconded_and_pending_for_para_in_view( - implicit_view, - per_relay_parent, - &relay_parent, - para_id, - ); - - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - claims_for_para, - seconded_and_pending_at_ancestors, - "Checking if seconded limit is reached" - ); - - seconded_and_pending_at_ancestors >= claims_for_para -} - fn seconded_and_pending_for_para_in_view( implicit_view: &ImplicitView, per_relay_parent: &HashMap, From b0e46272c41bdd63fcbb6c885a085458c24efe78 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 18:50:36 +0300 Subject: [PATCH 052/138] Fix comments --- .../collator-protocol/src/validator_side/mod.rs | 16 +++++++--------- .../tests/prospective_parachains.rs | 2 -- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 853036738ce2..adf122a042d4 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2033,6 +2033,8 @@ async fn handle_collation_fetch_response( result } +// Returns how many seconded candidates and pending fetches are there within the view at a specific +// relay parent fn seconded_and_pending_for_para_in_view( implicit_view: &ImplicitView, per_relay_parent: &HashMap, @@ -2044,8 +2046,6 @@ fn seconded_and_pending_for_para_in_view( // If the relay parent is not in the view (unconnected candidate) // `known_allowed_relay_parents_under` will return `None`. In this case we still we just count // the candidate at the specified relay parent. - // TODO: what to do when an unconnected candidate becomes connected and potentially we have - // accepted more candidates than the claim queue allows? implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) .or(Some(&[*relay_parent])) // if the relay parent is not in view we still want to count it @@ -2060,6 +2060,8 @@ fn seconded_and_pending_for_para_in_view( .unwrap_or(0) } +// Returns the claim queue with a boolean attached to each entry indicating if the position in the +// claim queue has got a corresponding pending fetch or seconded candidate. fn claim_queue_state( relay_parent: &Hash, per_relay_parent: &HashMap, @@ -2096,12 +2098,8 @@ fn claim_queue_state( Some(claim_queue_state) } -/// Returns the next collation to fetch from the `waiting_queue`. -/// -/// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. -/// -/// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and -/// the passed in `finished_one` is the currently `waiting_collation`. +/// Returns the next collation to fetch from the `waiting_queue` and reset the status back to +/// `Waiting`. fn get_next_collation_to_fetch( finished_one: &(CollatorId, Option), relay_parent: Hash, @@ -2109,7 +2107,7 @@ fn get_next_collation_to_fetch( ) -> Option<(PendingCollation, CollatorId)> { let claim_queue_state = claim_queue_state(&relay_parent, &state.per_relay_parent, &state.implicit_view)?; - let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; // TODO: this is looked up twice + let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 8bce4e959915..adab37027cd5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1444,7 +1444,6 @@ fn collations_outside_limits_are_not_fetched() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - // Grandparent of head `a`. let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; @@ -1548,7 +1547,6 @@ fn fair_collation_fetches() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - // Grandparent of head `a`. let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; From d1cf41d5838e694856cdf42a28b53eecfe13d40c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 17 Sep 2024 18:59:25 +0300 Subject: [PATCH 053/138] `pending_for_para` -> `is_pending_for_para` --- .../network/collator-protocol/src/validator_side/collation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 5c7335297e0c..8d2fcb35afec 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -299,7 +299,7 @@ impl Collations { } // Returns `true` if there is a pending collation for the specified `ParaId`. - fn pending_for_para(&self, para_id: &ParaId) -> bool { + fn is_pending_for_para(&self, para_id: &ParaId) -> bool { match self.status { CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => true, CollationStatus::WaitingOnValidation(pending_para_id) @@ -316,7 +316,7 @@ impl Collations { .get(¶_id) .map(|state| state.seconded_per_para) .unwrap_or_default(); - let pending_for_para = self.pending_for_para(para_id) as usize; + let pending_for_para = self.is_pending_for_para(para_id) as usize; gum::trace!( target: LOG_TARGET, From df3a2154812973144955d542960fb508bde46e8c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 19 Sep 2024 17:33:44 +0300 Subject: [PATCH 054/138] Fix `0011-async-backing-6-seconds-rate.toml` - set `lookahead` to 3 otherwise the chain can't progress in time --- .../functional/0011-async-backing-6-seconds-rate.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml index b776622fdce3..e236d6e023c7 100644 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml @@ -14,7 +14,7 @@ chain = "rococo-local" allowed_ancestry_len = 2 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 2 + lookahead = 3 group_rotation_frequency = 4 From b70807b278cfe1df8f7731fac30e8677b04a1cb2 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 20 Sep 2024 10:26:02 +0300 Subject: [PATCH 055/138] Set `lookahead` in polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml --- .../0002-elastic-scaling-doesnt-break-parachains.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml index 9b3576eaa3c2..2705f69b8c0d 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml @@ -8,6 +8,7 @@ bootnode = true [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 2 num_cores = 2 + lookahead = 3 [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" From f047036ec1e7b05872ab9453762c26886a599db6 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 30 Sep 2024 16:14:00 +0300 Subject: [PATCH 056/138] paras_now -> assigned_paras --- .../collator-protocol/src/validator_side/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index adf122a042d4..ed3db2e201b7 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -461,7 +461,10 @@ where return Ok(GroupAssignments { current: Vec::new() }) }; - let paras_now = match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { + let assigned_paras = match fetch_claim_queue(sender, relay_parent) + .await + .map_err(Error::Runtime)? + { // Runtime supports claim queue - use it Some(mut claim_queue) => claim_queue.0.remove(&core_now), // Should never happen since claim queue is released everywhere. @@ -480,9 +483,9 @@ where }) }, }; - let paras_now = paras_now.unwrap_or_else(|| VecDeque::new()); + let assigned_paras = assigned_paras.unwrap_or_else(|| VecDeque::new()); - for para_id in paras_now.iter() { + for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); *entry += 1; if *entry == 1 { @@ -495,7 +498,7 @@ where } } - Ok(GroupAssignments { current: paras_now.into_iter().collect::>() }) + Ok(GroupAssignments { current: assigned_paras.into_iter().collect::>() }) } fn remove_outgoing( From 94e4fc305dba68153ca89ac48741ed1085335c31 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 30 Sep 2024 16:23:37 +0300 Subject: [PATCH 057/138] Remove a duplicated parameter in `update_view` --- .../src/validator_side/tests/mod.rs | 82 ++-------- .../tests/prospective_parachains.rs | 149 +++--------------- 2 files changed, 36 insertions(+), 195 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 8786b845a6b1..bf2556370b44 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -524,14 +524,7 @@ fn collator_reporting_works() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 0; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head, head_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)], 1).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -639,7 +632,6 @@ fn fetch_one_collation_at_a_time() { &test_state, vec![(test_state.relay_parent, 0), (second, 1)], 2, - &test_state.async_backing_params, ) .await; @@ -729,7 +721,6 @@ fn fetches_next_collation() { &test_state, vec![(test_state.relay_parent, 0), (second, 1)], 2, - &test_state.async_backing_params, ) .await; @@ -840,14 +831,8 @@ fn reject_connection_to_next_group() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; let peer_b = PeerId::random(); @@ -892,7 +877,6 @@ fn fetch_next_collation_on_invalid_collation() { &test_state, vec![(test_state.relay_parent, 0), (second, 1)], 2, - &test_state.async_backing_params, ) .await; @@ -990,14 +974,8 @@ fn inactive_disconnected() { let pair = CollatorPair::generate().0; - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; let peer_b = PeerId::random(); @@ -1044,7 +1022,6 @@ fn activity_extends_life() { &test_state, vec![(hash_a, 0), (hash_b, 1), (hash_c, 1)], 3, - &test_state.async_backing_params, ) .await; @@ -1110,14 +1087,8 @@ fn disconnect_if_no_declare() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; let peer_b = PeerId::random(); @@ -1147,14 +1118,8 @@ fn disconnect_if_wrong_declare() { let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; overseer_send( &mut virtual_overseer, @@ -1205,14 +1170,8 @@ fn delay_reputation_change() { let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; overseer_send( &mut virtual_overseer, @@ -1289,14 +1248,8 @@ fn view_change_clears_old_collators() { let peer = PeerId::random(); - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) + .await; connect_and_declare_collator( &mut virtual_overseer, @@ -1309,14 +1262,7 @@ fn view_change_clears_old_collators() { test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - update_view( - &mut virtual_overseer, - &test_state, - vec![], - 0, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![], 0).await; assert_collator_disconnect(&mut virtual_overseer, peer).await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index adab37027cd5..badccaac9769 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -20,8 +20,8 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ - AsyncBackingParams, BlockNumber, CandidateCommitments, CommittedCandidateReceipt, Header, - SigningContext, ValidatorId, + BlockNumber, CandidateCommitments, CommittedCandidateReceipt, Header, SigningContext, + ValidatorId, }; use rstest::rstest; @@ -104,9 +104,8 @@ async fn assert_assign_incoming( pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, test_state: &TestState, - new_view: Vec<(Hash, u32)>, // Hash and block number. - activated: u8, // How many new heads does this update contain? - async_backing_params: &AsyncBackingParams, // returned via the runtime api + new_view: Vec<(Hash, u32)>, // Hash and block number. + activated: u8, // How many new heads does this update contain? ) -> Option { let new_view: HashMap = HashMap::from_iter(new_view); @@ -127,7 +126,7 @@ pub(super) async fn update_view( parent, RuntimeApiRequest::AsyncBackingParams(tx), )) => { - tx.send(Ok(*async_backing_params)).unwrap(); + tx.send(Ok(test_state.async_backing_params)).unwrap(); (parent, new_view.get(&parent).copied().expect("Unknown parent requested")) } ); @@ -141,7 +140,8 @@ pub(super) async fn update_view( ) .await; - let min_number = leaf_number.saturating_sub(async_backing_params.allowed_ancestry_len); + let min_number = + leaf_number.saturating_sub(test_state.async_backing_params.allowed_ancestry_len); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) @@ -477,14 +477,7 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); @@ -563,14 +556,7 @@ fn v1_advertisement_rejected_on_non_active_leave() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); @@ -620,14 +606,7 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -729,14 +708,7 @@ fn second_multiple_candidates_per_relay_parent() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); @@ -824,14 +796,7 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); @@ -935,14 +900,7 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_c, head_c_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; let peer_a = PeerId::random(); @@ -1065,14 +1023,7 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1150,14 +1101,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); @@ -1447,14 +1391,7 @@ fn collations_outside_limits_are_not_fetched() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); let pair_a = CollatorPair::generate().0; @@ -1550,14 +1487,7 @@ fn fair_collation_fetches() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); let pair_a = CollatorPair::generate().0; @@ -1662,14 +1592,7 @@ fn collation_fetches_without_claimqueue() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head_b, head_b_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; let peer_a = PeerId::random(); let pair_a = CollatorPair::generate().0; @@ -1758,14 +1681,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 2; - update_view( - &mut virtual_overseer, - &test_state, - vec![(head, head_num)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)], 1).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1939,14 +1855,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { let relay_parent_2 = Hash::from_low_u64_be(128); - update_view( - &mut virtual_overseer, - &test_state, - vec![(relay_parent_2, 2)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_2, 2)], 1).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1992,14 +1901,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // them fall in the same view. let relay_parent_4 = Hash::from_low_u64_be(126); - update_view( - &mut virtual_overseer, - &test_state, - vec![(relay_parent_4, 4)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_4, 4)], 1).await; // One advertisement for `para_id_b` at `relay_parent_4` submit_second_and_assert( @@ -2057,14 +1959,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one // can be accepted let relay_parent_6 = Hash::from_low_u64_be(124); - update_view( - &mut virtual_overseer, - &test_state, - vec![(relay_parent_6, 6)], - 1, - &test_state.async_backing_params, - ) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_6, 6)], 1).await; submit_second_and_assert( &mut virtual_overseer, From 386488b07adade3c11de19621ff207d95951e5ed Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 30 Sep 2024 16:25:15 +0300 Subject: [PATCH 058/138] Remove an outdated comment --- .../node/network/collator-protocol/src/validator_side/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index ed3db2e201b7..84e2fbffaae2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1780,7 +1780,6 @@ async fn kick_off_seconding( (pvd, maybe_parent_head_data, Some(parent_head_data_hash)) }, - // Support V2 collators without async backing enabled. (CollationVersion::V1, _) => { let pvd = request_persisted_validation_data( ctx.sender(), From ff312c9791d9248d4e70d14665ccff7e0ed77b03 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 2 Oct 2024 15:01:57 +0300 Subject: [PATCH 059/138] Fix `seconded_and_pending_for_para_in_view` --- .../src/validator_side/mod.rs | 4 --- .../tests/prospective_parachains.rs | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 84e2fbffaae2..9ec6c6fd50a5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2045,12 +2045,8 @@ fn seconded_and_pending_for_para_in_view( ) -> usize { // `known_allowed_relay_parents_under` returns all leaves within the view for the specified // block hash including the block hash itself. - // If the relay parent is not in the view (unconnected candidate) - // `known_allowed_relay_parents_under` will return `None`. In this case we still we just count - // the candidate at the specified relay parent. implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) - .or(Some(&[*relay_parent])) // if the relay parent is not in view we still want to count it .map(|ancestors| { ancestors.iter().fold(0, |res, anc| { res + per_relay_parent diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index badccaac9769..421ed95c23df 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -698,17 +698,20 @@ fn second_multiple_candidates_per_relay_parent() { let pair = CollatorPair::generate().0; - // Grandparent of head `a`. + let head_a = Hash::from_low_u64_be(130); + let head_a_num: u32 = 0; + let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - // Grandparent of head `b`. - // Group rotation frequency is 1 by default, at `c` we're assigned - // to the first para. - let head_c = Hash::from_low_u64_be(130); - - // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + // Activated leaf is `a` and `b`.The collation will be based on `b`. + update_view( + &mut virtual_overseer, + &test_state, + vec![(head_a, head_a_num), (head_b, head_b_num)], + 2, + ) + .await; let peer_a = PeerId::random(); @@ -726,8 +729,8 @@ fn second_multiple_candidates_per_relay_parent() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), - head_c, + test_state.chain_ids[0], + head_a, peer_a, HeadData(vec![i as u8]), ) @@ -739,7 +742,7 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_a, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; @@ -765,7 +768,7 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_b, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; From 88d0307b8a511391103cfbfe1e53513b0a787fc8 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 2 Oct 2024 16:09:15 +0300 Subject: [PATCH 060/138] `claim_queue_state` becomes `unfulfilled_claim_queue_entries` - the bool indicating the claim queue entry fulfillment is no longer needed --- .../src/validator_side/collation.rs | 29 +++++++++---------- .../src/validator_side/mod.rs | 24 ++++++++------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 8d2fcb35afec..7d28593e8d7e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -264,14 +264,14 @@ impl Collations { /// particular parachain in the claim queue. /// /// To achieve this each seconded collation is mapped to an entry from the claim queue. The next - /// fetch is the first unsatisfied entry from the claim queue for which there is an + /// fetch is the first unfulfilled entry from the claim queue for which there is an /// advertisement. /// - /// `claim_queue_state` represents the claim queue and a boolean flag indicating if the claim - /// queue entry is fulfilled or not. + /// `unfulfilled_claim_queue_entries` represents all claim queue entries which are still not + /// fulfilled. pub(super) fn pick_a_collation_to_fetch( &mut self, - claim_queue_state: Vec<(bool, ParaId)>, + unfulfilled_claim_queue_entries: Vec, ) -> Option<(PendingCollation, CollatorId)> { gum::trace!( target: LOG_TARGET, @@ -280,18 +280,15 @@ impl Collations { "Pick a collation to fetch." ); - for (fulfilled, assignment) in claim_queue_state { - // if this assignment has been already fulfilled - move on - if fulfilled { - continue - } - - // we have found and unfulfilled assignment - try to fulfill it - if let Some(collations) = self.waiting_queue.get_mut(&assignment) { - if let Some(collation) = collations.pop_front() { - // we don't mark the entry as fulfilled because it is considered pending - return Some(collation) - } + for assignment in unfulfilled_claim_queue_entries { + // if there is an unfulfilled assignment - return it + if let Some(collation) = self + .waiting_queue + .get_mut(&assignment) + .map(|collations| collations.pop_front()) + .flatten() + { + return Some(collation) } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 9ec6c6fd50a5..ff1443cd2b6d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2058,13 +2058,14 @@ fn seconded_and_pending_for_para_in_view( .unwrap_or(0) } -// Returns the claim queue with a boolean attached to each entry indicating if the position in the -// claim queue has got a corresponding pending fetch or seconded candidate. -fn claim_queue_state( +// Returns the claim queue with out fetched or pending advertisement. The resulting `Vec` keeps the +// order in the claim queue so the earlier an element is located in the `Vec` the higher its +// priority is. +fn unfulfilled_claim_queue_entries( relay_parent: &Hash, per_relay_parent: &HashMap, implicit_view: &ImplicitView, -) -> Option> { +) -> Option> { let relay_parent_state = per_relay_parent.get(relay_parent)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); let mut claims_per_para = scheduled_paras @@ -2085,12 +2086,12 @@ fn claim_queue_state( .assignment .current .iter() - .map(|para_id| match claims_per_para.entry(*para_id) { + .filter_map(|para_id| match claims_per_para.entry(*para_id) { Entry::Occupied(mut entry) if *entry.get() > 0 => { *entry.get_mut() -= 1; - (true, *para_id) + None }, - _ => (false, *para_id), + _ => Some(*para_id), }) .collect::>(); Some(claim_queue_state) @@ -2103,8 +2104,11 @@ fn get_next_collation_to_fetch( relay_parent: Hash, state: &mut State, ) -> Option<(PendingCollation, CollatorId)> { - let claim_queue_state = - claim_queue_state(&relay_parent, &state.per_relay_parent, &state.implicit_view)?; + let unfulfilled_entries = unfulfilled_claim_queue_entries( + &relay_parent, + &state.per_relay_parent, + &state.implicit_view, + )?; let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; // If finished one does not match waiting_collation, then we already dequeued another fetch @@ -2124,5 +2128,5 @@ fn get_next_collation_to_fetch( } } rp_state.collations.status = CollationStatus::Waiting; - rp_state.collations.pick_a_collation_to_fetch(claim_queue_state) + rp_state.collations.pick_a_collation_to_fetch(unfulfilled_entries) } From af78352dd242acbea81c106cddb06828db4f6bc9 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 2 Oct 2024 16:12:57 +0300 Subject: [PATCH 061/138] For consistency use `chain_ids` only from `test_state` --- .../validator_side/tests/prospective_parachains.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 421ed95c23df..7bd8b8000454 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1423,7 +1423,7 @@ fn collations_outside_limits_are_not_fetched() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), + ParaId::from(test_state.chain_ids[0]), head_b, peer_a, HeadData(vec![1 as u8]), @@ -1433,7 +1433,7 @@ fn collations_outside_limits_are_not_fetched() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[1]), + ParaId::from(test_state.chain_ids[1]), head_b, peer_b, HeadData(vec![2 as u8]), @@ -1443,7 +1443,7 @@ fn collations_outside_limits_are_not_fetched() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), + ParaId::from(test_state.chain_ids[0]), head_b, peer_a, HeadData(vec![3 as u8]), @@ -1521,7 +1521,7 @@ fn fair_collation_fetches() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), + ParaId::from(test_state.chain_ids[0]), head_b, peer_a, HeadData(vec![i]), @@ -1545,7 +1545,7 @@ fn fair_collation_fetches() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[1]), + ParaId::from(test_state.chain_ids[1]), head_b, peer_b, HeadData(vec![0 as u8]), @@ -1643,7 +1643,7 @@ fn collation_fetches_without_claimqueue() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - ParaId::from(TestState::CHAIN_IDS[0]), + ParaId::from(test_state.chain_ids[0]), head_b, peer_a, HeadData(vec![0 as u8]), From d6360911f4bbd89245cc218e6e57ffd3aa0cb7d8 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 3 Oct 2024 15:20:45 +0300 Subject: [PATCH 062/138] Limit the number of advertisements accepted by each peer for spam protection purposes --- .../network/collator-protocol/src/validator_side/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index ff1443cd2b6d..0132cf92ec5e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -216,6 +216,7 @@ impl PeerData { candidate_hash: Option, implicit_view: &ImplicitView, active_leaves: &HashMap, + per_relay_parent: &PerRelayParent, ) -> std::result::Result<(CollatorId, ParaId), InsertAdvertisementError> { match self.state { PeerState::Connected(_) => Err(InsertAdvertisementError::UndeclaredCollator), @@ -240,6 +241,12 @@ impl PeerData { let candidates = state.advertisements.entry(on_relay_parent).or_default(); + // Current assignments is equal to the length of the claim queue. No honest + // collator should send that much advertisements. + if candidates.len() > per_relay_parent.assignment.current.len() { + return Err(InsertAdvertisementError::PeerLimitReached) + } + candidates.insert(candidate_hash); } else { if self.version != CollationVersion::V1 { @@ -253,6 +260,7 @@ impl PeerData { if state.advertisements.contains_key(&on_relay_parent) { return Err(InsertAdvertisementError::Duplicate) } + state .advertisements .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); @@ -1061,6 +1069,7 @@ where candidate_hash, &state.implicit_view, &state.active_leaves, + &per_relay_parent, ) .map_err(AdvertisementError::Invalid)?; From 2bb82eb1f39b3cf9a2304157fa64b71376a70276 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 7 Oct 2024 12:14:34 +0300 Subject: [PATCH 063/138] Zombienet test --- ...-coretime-collation-fetching-fairness.toml | 44 +++++++++++++++++++ ...coretime-collation-fetching-fairness.zndsl | 18 ++++++++ .../functional/0016-verify-included-events.js | 42 ++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml create mode 100644 polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl create mode 100644 polkadot/zombienet_tests/functional/0016-verify-included-events.js diff --git a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml new file mode 100644 index 000000000000..d5684625572a --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml @@ -0,0 +1,44 @@ +[settings] +timeout = 1000 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] + max_candidate_depth = 3 + allowed_ancestry_len = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + lookahead = 2 + num_cores = 4 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + needed_approvals = 3 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +command = "polkadot" + + [[relaychain.node_groups]] + name = "validator" + args = ["-lparachain=info,parachain::collator-protocol=trace" ] + count = 4 + +{% for id in range(2000,2002) %} +[[parachains]] +id = {{id}} +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-{{id}}" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-{{id}}" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] + +{% endfor %} diff --git a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl new file mode 100644 index 000000000000..136cb507eb2e --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl @@ -0,0 +1,18 @@ +Description: CT shared core test +Network: ./0016-coretime-collation-fetching-fairness.toml +Creds: config + +validator: reports node_roles is 4 + +validator-0: js-script ./0015-force-register-paras.js with "2000,2001" return is 0 within 600 seconds +# core 0 is shared 3:1 between paras +validator-0: js-script ./assign-core.js with "0,2000,43200,2001,14400" return is 0 within 600 seconds + +collator-2000: reports block height is at least 9 within 200 seconds +collator-2001: reports block height is at least 3 within 200 seconds + +# hardcoded check to verify that included onchain events are indeed 3:1 +validator-0: js-script ./0016-verify-included-events.js return is 1 within 120 seconds + +# could be flaky feel free to remove if it's causing problems +validator-2: count of log lines matching regex "Rejected v2 advertisement.*error=SecondedLimitReached" is greater than 1 within 10 seconds diff --git a/polkadot/zombienet_tests/functional/0016-verify-included-events.js b/polkadot/zombienet_tests/functional/0016-verify-included-events.js new file mode 100644 index 000000000000..12f100dc5885 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-verify-included-events.js @@ -0,0 +1,42 @@ +function parse_pjs_int(input) { + return parseInt(input.replace(/,/g, '')); +} + +async function run(nodeName, networkInfo) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + let blocks_per_para = {}; + + await new Promise(async (resolve, _) => { + let block_count = 0; + const unsubscribe = await api.query.system.events((events) => { + block_count++; + + events.forEach((record) => { + const event = record.event; + + if (event.method != 'CandidateIncluded') { + return; + } + + let included_para_id = parse_pjs_int(event.toHuman().data[0].descriptor.paraId); + if (blocks_per_para[included_para_id] == undefined) { + blocks_per_para[included_para_id] = 1; + } else { + blocks_per_para[included_para_id]++; + } + }); + + if (block_count == 12) { + unsubscribe(); + return resolve(); + } + }); + }); + + console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); + return (blocks_per_para[2000] == 6 && blocks_per_para[2001] == 1); +} + +module.exports = { run }; \ No newline at end of file From c782058705cc7aa2331014682526aefa3488c641 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 7 Oct 2024 14:26:26 +0300 Subject: [PATCH 064/138] Rearrange imports --- .../network/collator-protocol/src/validator_side/tests/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index bf2556370b44..70b5a76f4c91 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use self::prospective_parachains::update_view; - use super::*; use assert_matches::assert_matches; use futures::{executor, future, Future}; @@ -30,6 +28,7 @@ use std::{ time::Duration, }; +use self::prospective_parachains::update_view; use polkadot_node_network_protocol::{ peer_set::CollationVersion, request_response::{Requests, ResponseSender}, From 903f7f4a18510ebcbb56e52d7e94922a498731b5 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 7 Oct 2024 14:33:33 +0300 Subject: [PATCH 065/138] Newline and outdated comment --- .../src/validator_side/tests/prospective_parachains.rs | 1 - .../zombienet_tests/functional/0016-verify-included-events.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 7bd8b8000454..a8fd2aee229a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1591,7 +1591,6 @@ fn collation_fetches_without_claimqueue() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - // Grandparent of head `a`. let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; diff --git a/polkadot/zombienet_tests/functional/0016-verify-included-events.js b/polkadot/zombienet_tests/functional/0016-verify-included-events.js index 12f100dc5885..47877b99cf36 100644 --- a/polkadot/zombienet_tests/functional/0016-verify-included-events.js +++ b/polkadot/zombienet_tests/functional/0016-verify-included-events.js @@ -39,4 +39,4 @@ async function run(nodeName, networkInfo) { return (blocks_per_para[2000] == 6 && blocks_per_para[2001] == 1); } -module.exports = { run }; \ No newline at end of file +module.exports = { run }; From cefbce8cf0bf1feb781542a807dc362326a1d18c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 14 Oct 2024 14:06:12 +0300 Subject: [PATCH 066/138] Undo `lookahead = 3` in zombienet tests --- .../0002-elastic-scaling-doesnt-break-parachains.toml | 1 - .../functional/0011-async-backing-6-seconds-rate.toml | 2 +- .../functional/0016-coretime-collation-fetching-fairness.toml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml index 2705f69b8c0d..9b3576eaa3c2 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml @@ -8,7 +8,6 @@ bootnode = true [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 2 num_cores = 2 - lookahead = 3 [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml index e236d6e023c7..b776622fdce3 100644 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml @@ -14,7 +14,7 @@ chain = "rococo-local" allowed_ancestry_len = 2 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 3 + lookahead = 2 group_rotation_frequency = 4 diff --git a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml index d5684625572a..759f98cdc85d 100644 --- a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml @@ -7,7 +7,6 @@ timeout = 1000 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 1 - lookahead = 2 num_cores = 4 [relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] From cb69361d0ad009eed288aa256c8f7699ae9656b1 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 14 Oct 2024 14:22:06 +0300 Subject: [PATCH 067/138] Consider what's scheduled on the core when determining assignments --- .../src/validator_side/mod.rs | 72 +++++++++++++------ .../src/validator_side/tests/mod.rs | 22 ++---- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 0132cf92ec5e..28ff25dda55d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -469,29 +469,52 @@ where return Ok(GroupAssignments { current: Vec::new() }) }; - let assigned_paras = match fetch_claim_queue(sender, relay_parent) - .await - .map_err(Error::Runtime)? - { - // Runtime supports claim queue - use it - Some(mut claim_queue) => claim_queue.0.remove(&core_now), - // Should never happen since claim queue is released everywhere. - None => { - gum::warn!( - target: LOG_TARGET, - ?relay_parent, - "Claim queue is not available, falling back to availability cores", - ); + // Assigned parachains are determined by: + // 1. If there is something scheduled on the core - this is the first assignment. + // 2. Next assigments are whatever is in the claim queue for the core. 2.1 If the claim queue is + // not available and the core is occupied the next assignemnt is taken from + // `next_up_on_available` + // + // Step 1 is needed to unify the assignments with the way backing and prospective parachains + // subsystems handle async backing parameters. Until + // https://github.com/paritytech/polkadot-sdk/issues/5079 is completed the current view consists + // of the latest known relay parent plus its `lookahead` number of ancestors. When #5079 is + // merged everything will be deduced from the claim queue and this logic will be simplified. + let assigned_paras = { + let mut from_core: VecDeque<_> = + if let Some(CoreState::Scheduled(core_state)) = cores.get(core_now.0 as usize) { + vec![core_state.para_id].into_iter().collect() + } else { + vec![].into_iter().collect() + }; - cores.get(core_now.0 as usize).and_then(|c| match c { - CoreState::Occupied(core) => - core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), - CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), - CoreState::Free => None, - }) - }, + let from_claim_queue = + match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { + // Runtime supports claim queue - use it + Some(mut claim_queue) => claim_queue.0.remove(&core_now), + // Should never happen since claim queue is released everywhere. + None => { + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + "Claim queue is not available, assigning from availability cores only", + ); + + if let Some(CoreState::Occupied(core_state)) = cores.get(core_now.0 as usize) { + core_state.next_up_on_available.as_ref().map(|scheduled_core| { + vec![scheduled_core.para_id].into_iter().collect() + }) + } else { + None + } + }, + }; + + let mut from_claim_queue = from_claim_queue.unwrap_or_else(|| VecDeque::new()); + from_core.append(&mut from_claim_queue); + + from_core }; - let assigned_paras = assigned_paras.unwrap_or_else(|| VecDeque::new()); for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); @@ -2057,6 +2080,13 @@ fn seconded_and_pending_for_para_in_view( implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) .map(|ancestors| { + gum::trace!( + target: LOG_TARGET, + ?ancestors, + ?relay_parent, + ?para_id, + "seconded_and_pending_for_para_in_view" + ); ancestors.iter().fold(0, |res, anc| { res + per_relay_parent .get(&anc) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 70b5a76f4c91..a4a77ac38443 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -130,14 +130,14 @@ impl Default for TestState { claim_queue.insert( CoreIndex(0), iter::repeat(ParaId::from(Self::CHAIN_IDS[0])) - .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1) .collect(), ); claim_queue.insert(CoreIndex(1), VecDeque::new()); claim_queue.insert( CoreIndex(2), iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) - .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1) .collect(), ); @@ -175,18 +175,13 @@ impl TestState { claim_queue.insert( CoreIndex(0), VecDeque::from_iter( - [ - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[1]), - ParaId::from(Self::CHAIN_IDS[0]), - ] - .into_iter(), + [ParaId::from(Self::CHAIN_IDS[1]), ParaId::from(Self::CHAIN_IDS[0])].into_iter(), ), ); assert!( claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1 ); state.cores = cores; @@ -223,18 +218,13 @@ impl TestState { claim_queue.insert( CoreIndex(0), VecDeque::from_iter( - [ - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[0]), - ] - .into_iter(), + [ParaId::from(Self::CHAIN_IDS[0]), ParaId::from(Self::CHAIN_IDS[0])].into_iter(), ), ); assert!( claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1 ); state.cores = cores; From 4438349911ed1da71778b842d9d506678a6dd67c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 14 Oct 2024 16:14:02 +0300 Subject: [PATCH 068/138] Fix a clippy warning --- .../network/collator-protocol/src/validator_side/collation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index a15af85f95b6..a919eae4e290 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -284,8 +284,7 @@ impl Collations { if let Some(collation) = self .waiting_queue .get_mut(&assignment) - .map(|collations| collations.pop_front()) - .flatten() + .and_then(|collations| collations.pop_front()) { return Some(collation) } From e82c38657c663c612bf25f2678fab9687b9196d0 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 15 Oct 2024 08:47:04 +0300 Subject: [PATCH 069/138] Update PRdoc --- prdoc/pr_4880.prdoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index 4c05dcebb179..dacf31ca157c 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -25,3 +25,5 @@ doc: crates: - name: "polkadot-collator-protocol" bump: "minor" + - name" "polkadot" + bump: "minor" From 4b2d4c54b645c3bba72655a438a168897bc7966f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 15 Oct 2024 08:52:20 +0300 Subject: [PATCH 070/138] Apply suggestions from code review Co-authored-by: Maciej --- .../src/validator_side/collation.rs | 14 +++++++------- .../collator-protocol/src/validator_side/mod.rs | 10 +++++----- prdoc/pr_4880.prdoc | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index a919eae4e290..756c0a995bfd 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -22,10 +22,10 @@ //! based on the entries there. A parachain can't have more fetched collations than the //! entries in the claim queue at a specific relay parent. When calculating this limit the //! validator counts all advertisements within its view not just at the relay parent. -//! 2. If the advertisement was accepted, it's queued for fetch (per relay parent). -//! 3. Once it's requested, the collation is said to be Pending. -//! 4. Pending collation becomes Fetched once received, we send it to backing for validation. -//! 5. If it turns to be invalid or async backing allows seconding another candidate, carry on +//! 3. If the advertisement was accepted, it's queued for fetch (per relay parent). +//! 4. Once it's requested, the collation is said to be Pending. +//! 5. Pending collation becomes Fetched once received, we send it to backing for validation. +//! 6. If it turns to be invalid or async backing allows seconding another candidate, carry on //! with the next advertisement, otherwise we're done with this relay parent. //! //! ┌───────────────────────────────────┐ @@ -222,7 +222,7 @@ pub struct Collations { /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` /// yet. pub fetching_from: Option<(CollatorId, Option)>, - /// Collation that were advertised to us, but we did not yet fetch. Grouped by `ParaId`. + /// Collation that were advertised to us, but we did not yet request or fetch. Grouped by `ParaId`. waiting_queue: BTreeMap>, /// Number of seconded candidates and claims in the claim queue per `ParaId`. candidates_state: BTreeMap, @@ -293,7 +293,7 @@ impl Collations { None } - // Returns `true` if there is a pending collation for the specified `ParaId`. + // Returns `true` if there is a pending collation for the specified `ParaId`. fn is_pending_for_para(&self, para_id: &ParaId) -> bool { match self.status { CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => true, @@ -304,7 +304,7 @@ impl Collations { } } - // Returns the number of seconded collations for the specified `ParaId`. + // Returns the number of seconded and likely soon to be seconded collations for the specified `ParaId`. pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { let seconded_for_para = self .candidates_state diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 43969188c215..0f9832c5eab5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -145,7 +145,6 @@ enum InsertAdvertisementError { /// No prior declare message received. UndeclaredCollator, /// A limit for announcements per peer is reached. - #[allow(dead_code)] PeerLimitReached, } @@ -467,7 +466,8 @@ where // Assigned parachains are determined by: // 1. If there is something scheduled on the core - this is the first assignment. - // 2. Next assigments are whatever is in the claim queue for the core. 2.1 If the claim queue is + // 2. Next assigments are whatever is in the claim queue for the core. + // 2.1 If the claim queue is // not available and the core is occupied the next assignemnt is taken from // `next_up_on_available` // @@ -1110,7 +1110,7 @@ where // Check if backing subsystem allows to second this candidate. // // This is also only important when async backing or elastic scaling is enabled. - let seconding_not_allowed = !can_second( + let can_second = can_second( sender, collator_para_id, relay_parent, @@ -1119,7 +1119,7 @@ where ) .await; - if seconding_not_allowed { + if !can_second { return Err(AdvertisementError::BlockedByBacking) } } @@ -2072,7 +2072,7 @@ fn seconded_and_pending_for_para_in_view( .unwrap_or(0) } -// Returns the claim queue with out fetched or pending advertisement. The resulting `Vec` keeps the +// Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the // order in the claim queue so the earlier an element is located in the `Vec` the higher its // priority is. fn unfulfilled_claim_queue_entries( diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index dacf31ca157c..cde5ba9a86bf 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -4,7 +4,7 @@ doc: - audience: "Node Dev" description: | Implements collation fetching fairness in the validator side of the collator protocol. With - core time in place if two (or more) parachains share a single core no fairness is guaranteed + core time in place if two (or more) parachains share a single core no fairness was guaranteed between them in terms of collation fetching. The current implementation was accepting up to `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached no new collations are accepted. A misbehaving collator can abuse this fact and prevent other From 1c91371097f0ce22dd2bd3767e28206abe5f48c7 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Tue, 15 Oct 2024 08:46:21 +0000 Subject: [PATCH 071/138] ".git/.scripts/commands/fmt/fmt.sh" --- .../collator-protocol/src/validator_side/collation.rs | 6 ++++-- .../network/collator-protocol/src/validator_side/mod.rs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 756c0a995bfd..48bf3553e594 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -222,7 +222,8 @@ pub struct Collations { /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` /// yet. pub fetching_from: Option<(CollatorId, Option)>, - /// Collation that were advertised to us, but we did not yet request or fetch. Grouped by `ParaId`. + /// Collation that were advertised to us, but we did not yet request or fetch. Grouped by + /// `ParaId`. waiting_queue: BTreeMap>, /// Number of seconded candidates and claims in the claim queue per `ParaId`. candidates_state: BTreeMap, @@ -304,7 +305,8 @@ impl Collations { } } - // Returns the number of seconded and likely soon to be seconded collations for the specified `ParaId`. + // Returns the number of seconded and likely soon to be seconded collations for the specified + // `ParaId`. pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { let seconded_for_para = self .candidates_state diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 0f9832c5eab5..e17beb39a9b0 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -466,8 +466,7 @@ where // Assigned parachains are determined by: // 1. If there is something scheduled on the core - this is the first assignment. - // 2. Next assigments are whatever is in the claim queue for the core. - // 2.1 If the claim queue is + // 2. Next assigments are whatever is in the claim queue for the core. 2.1 If the claim queue is // not available and the core is occupied the next assignemnt is taken from // `next_up_on_available` // From be341326e86fd18d694e22c3533b24bd3cde206d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 15 Oct 2024 12:06:18 +0300 Subject: [PATCH 072/138] Code review feedback --- .../collator-protocol/src/validator_side/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index e17beb39a9b0..432fa35dfada 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -205,9 +205,7 @@ impl PeerData { } } - /// Note an advertisement by the collator. Returns `true` if the advertisement was imported - /// successfully. Fails if the advertisement is duplicate, out of view, or the peer has not - /// declared itself a collator. + /// Performs sanity check for an advertisement and notes it as advertised. fn insert_advertisement( &mut self, on_relay_parent: Hash, @@ -476,7 +474,7 @@ where // of the latest known relay parent plus its `lookahead` number of ancestors. When #5079 is // merged everything will be deduced from the claim queue and this logic will be simplified. let assigned_paras = { - let mut from_core: VecDeque<_> = + let from_core: VecDeque<_> = if let Some(CoreState::Scheduled(core_state)) = cores.get(core_now.0 as usize) { vec![core_state.para_id].into_iter().collect() } else { @@ -505,10 +503,13 @@ where }, }; - let mut from_claim_queue = from_claim_queue.unwrap_or_else(|| VecDeque::new()); - from_core.append(&mut from_claim_queue); + let from_claim_queue = from_claim_queue.unwrap_or_else(|| VecDeque::new()); + // The order is important - first from core, then from claim queue. from_core + .into_iter() + .chain(from_claim_queue.into_iter()) + .collect::>() }; for para_id in assigned_paras.iter() { @@ -681,7 +682,6 @@ async fn request_collation( .get_mut(&relay_parent) .ok_or(FetchError::RelayParentOutOfView)?; - // Relay parent mode is checked in `handle_advertisement`. let (requests, response_recv) = match (peer_protocol_version, prospective_candidate) { (CollationVersion::V1, _) => { let (req, response_recv) = OutgoingRequest::new( From 5c7b2ac3878f17e62f3e9e20009f608991c8eddb Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 15 Oct 2024 12:11:42 +0300 Subject: [PATCH 073/138] Fix a typo in prdoc --- prdoc/pr_4880.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index cde5ba9a86bf..e1042cd4dab2 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -25,5 +25,5 @@ doc: crates: - name: "polkadot-collator-protocol" bump: "minor" - - name" "polkadot" + - name: "polkadot" bump: "minor" From 62c647360944087bf848f12ccfe4ce76a5e94bb2 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 16 Oct 2024 10:49:33 +0300 Subject: [PATCH 074/138] `seconded_and_pending_for_para_in_view` looks up to the len of the claim queue --- .../src/validator_side/mod.rs | 73 +++++++------------ .../src/validator_side/tests/mod.rs | 22 ++++-- 2 files changed, 41 insertions(+), 54 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 432fa35dfada..c911a3fc9cfa 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -462,55 +462,29 @@ where return Ok(GroupAssignments { current: Vec::new() }) }; - // Assigned parachains are determined by: - // 1. If there is something scheduled on the core - this is the first assignment. - // 2. Next assigments are whatever is in the claim queue for the core. 2.1 If the claim queue is - // not available and the core is occupied the next assignemnt is taken from - // `next_up_on_available` - // - // Step 1 is needed to unify the assignments with the way backing and prospective parachains - // subsystems handle async backing parameters. Until - // https://github.com/paritytech/polkadot-sdk/issues/5079 is completed the current view consists - // of the latest known relay parent plus its `lookahead` number of ancestors. When #5079 is - // merged everything will be deduced from the claim queue and this logic will be simplified. - let assigned_paras = { - let from_core: VecDeque<_> = - if let Some(CoreState::Scheduled(core_state)) = cores.get(core_now.0 as usize) { - vec![core_state.para_id].into_iter().collect() - } else { - vec![].into_iter().collect() - }; - - let from_claim_queue = - match fetch_claim_queue(sender, relay_parent).await.map_err(Error::Runtime)? { - // Runtime supports claim queue - use it - Some(mut claim_queue) => claim_queue.0.remove(&core_now), - // Should never happen since claim queue is released everywhere. - None => { - gum::warn!( - target: LOG_TARGET, - ?relay_parent, - "Claim queue is not available, assigning from availability cores only", - ); - - if let Some(CoreState::Occupied(core_state)) = cores.get(core_now.0 as usize) { - core_state.next_up_on_available.as_ref().map(|scheduled_core| { - vec![scheduled_core.para_id].into_iter().collect() - }) - } else { - None - } - }, - }; - - let from_claim_queue = from_claim_queue.unwrap_or_else(|| VecDeque::new()); + let assigned_paras = match fetch_claim_queue(sender, relay_parent) + .await + .map_err(Error::Runtime)? + { + // Runtime supports claim queue - use it + Some(mut claim_queue) => claim_queue.0.remove(&core_now), + // Should never happen since claim queue is released everywhere. + None => { + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + "Claim queue is not available, falling back to availability cores", + ); - // The order is important - first from core, then from claim queue. - from_core - .into_iter() - .chain(from_claim_queue.into_iter()) - .collect::>() + cores.get(core_now.0 as usize).and_then(|c| match c { + CoreState::Occupied(core) => + core.next_up_on_available.as_ref().map(|c| [c.para_id].into_iter().collect()), + CoreState::Scheduled(core) => Some([core.para_id].into_iter().collect()), + CoreState::Free => None, + }) + }, }; + let assigned_paras = assigned_paras.unwrap_or_else(|| VecDeque::new()); for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); @@ -1088,6 +1062,7 @@ where &state.per_relay_parent, &relay_parent, ¶_id, + assignment.current.len(), ); gum::trace!( @@ -2048,6 +2023,7 @@ fn seconded_and_pending_for_para_in_view( per_relay_parent: &HashMap, relay_parent: &Hash, para_id: &ParaId, + claim_queue_len: usize, ) -> usize { // `known_allowed_relay_parents_under` returns all leaves within the view for the specified // block hash including the block hash itself. @@ -2061,7 +2037,7 @@ fn seconded_and_pending_for_para_in_view( ?para_id, "seconded_and_pending_for_para_in_view" ); - ancestors.iter().fold(0, |res, anc| { + ancestors.iter().take(claim_queue_len).fold(0, |res, anc| { res + per_relay_parent .get(&anc) .map(|rp| rp.collations.seconded_and_pending_for_para(para_id)) @@ -2091,6 +2067,7 @@ fn unfulfilled_claim_queue_entries( per_relay_parent, relay_parent, para_id, + relay_parent_state.assignment.current.len(), ), ) }) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index a4a77ac38443..dee1064460b0 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -130,14 +130,14 @@ impl Default for TestState { claim_queue.insert( CoreIndex(0), iter::repeat(ParaId::from(Self::CHAIN_IDS[0])) - .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) .collect(), ); claim_queue.insert(CoreIndex(1), VecDeque::new()); claim_queue.insert( CoreIndex(2), iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) - .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) .collect(), ); @@ -175,13 +175,18 @@ impl TestState { claim_queue.insert( CoreIndex(0), VecDeque::from_iter( - [ParaId::from(Self::CHAIN_IDS[1]), ParaId::from(Self::CHAIN_IDS[0])].into_iter(), + [ + ParaId::from(Self::CHAIN_IDS[1]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), ), ); assert!( claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1 + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize ); state.cores = cores; @@ -218,13 +223,18 @@ impl TestState { claim_queue.insert( CoreIndex(0), VecDeque::from_iter( - [ParaId::from(Self::CHAIN_IDS[0]), ParaId::from(Self::CHAIN_IDS[0])].into_iter(), + [ + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), ), ); assert!( claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - 1 + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize ); state.cores = cores; From a4bc21f68628e5a67ca0ad331b1f9bea5ece68e4 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 16 Oct 2024 15:03:25 +0300 Subject: [PATCH 075/138] rerun CI From 6c103df0ecd2cf7929ccc90d9adbd40ad21f440d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 11:10:34 +0300 Subject: [PATCH 076/138] Fix zombienet test --- .gitlab/pipeline/zombienet/polkadot.yml | 8 ++++++++ .../network/collator-protocol/src/validator_side/mod.rs | 1 + ...oml => 0017-coretime-collation-fetching-fairness.toml} | 7 ++++--- ...sl => 0017-coretime-collation-fetching-fairness.zndsl} | 4 ++-- ...-included-events.js => 0017-verify-included-events.js} | 6 ++++-- 5 files changed, 19 insertions(+), 7 deletions(-) rename polkadot/zombienet_tests/functional/{0016-coretime-collation-fetching-fairness.toml => 0017-coretime-collation-fetching-fairness.toml} (88%) rename polkadot/zombienet_tests/functional/{0016-coretime-collation-fetching-fairness.zndsl => 0017-coretime-collation-fetching-fairness.zndsl} (86%) rename polkadot/zombienet_tests/functional/{0016-verify-included-events.js => 0017-verify-included-events.js} (76%) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index d17380839942..35c3c21b5d13 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -233,6 +233,14 @@ zombienet-polkadot-functional-0016-approval-voting-parallel: --local-dir="${LOCAL_DIR}/functional" --test="0016-approval-voting-parallel.zndsl" +zombienet-polkadot-functional-0017-coretime-collation-fetching-fairness: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0017-coretime-collation-fetching-fairness.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index c911a3fc9cfa..abd74aa438a3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2035,6 +2035,7 @@ fn seconded_and_pending_for_para_in_view( ?ancestors, ?relay_parent, ?para_id, + claim_queue_len, "seconded_and_pending_for_para_in_view" ); ancestors.iter().take(claim_queue_len).fold(0, |res, anc| { diff --git a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml similarity index 88% rename from polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml rename to polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml index 759f98cdc85d..6bc85f29c305 100644 --- a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml @@ -6,8 +6,9 @@ timeout = 1000 allowed_ancestry_len = 2 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 1 - num_cores = 4 + max_validators_per_core = 4 + num_cores = 1 + lookahead = 3 [relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] needed_approvals = 3 @@ -19,7 +20,7 @@ command = "polkadot" [[relaychain.node_groups]] name = "validator" - args = ["-lparachain=info,parachain::collator-protocol=trace" ] + args = ["-lparachain=debug,parachain::collator-protocol=trace" ] count = 4 {% for id in range(2000,2002) %} diff --git a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl similarity index 86% rename from polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl rename to polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl index 136cb507eb2e..822559a46ca7 100644 --- a/polkadot/zombienet_tests/functional/0016-coretime-collation-fetching-fairness.zndsl +++ b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl @@ -1,5 +1,5 @@ Description: CT shared core test -Network: ./0016-coretime-collation-fetching-fairness.toml +Network: ./0017-coretime-collation-fetching-fairness.toml Creds: config validator: reports node_roles is 4 @@ -12,7 +12,7 @@ collator-2000: reports block height is at least 9 within 200 seconds collator-2001: reports block height is at least 3 within 200 seconds # hardcoded check to verify that included onchain events are indeed 3:1 -validator-0: js-script ./0016-verify-included-events.js return is 1 within 120 seconds +validator-0: js-script ./0017-verify-included-events.js return is 1 within 120 seconds # could be flaky feel free to remove if it's causing problems validator-2: count of log lines matching regex "Rejected v2 advertisement.*error=SecondedLimitReached" is greater than 1 within 10 seconds diff --git a/polkadot/zombienet_tests/functional/0016-verify-included-events.js b/polkadot/zombienet_tests/functional/0017-verify-included-events.js similarity index 76% rename from polkadot/zombienet_tests/functional/0016-verify-included-events.js rename to polkadot/zombienet_tests/functional/0017-verify-included-events.js index 47877b99cf36..bcb44fe00807 100644 --- a/polkadot/zombienet_tests/functional/0016-verify-included-events.js +++ b/polkadot/zombienet_tests/functional/0017-verify-included-events.js @@ -10,7 +10,7 @@ async function run(nodeName, networkInfo) { await new Promise(async (resolve, _) => { let block_count = 0; - const unsubscribe = await api.query.system.events((events) => { + const unsubscribe = await api.query.system.events(async (events, block_hash) => { block_count++; events.forEach((record) => { @@ -21,11 +21,13 @@ async function run(nodeName, networkInfo) { } let included_para_id = parse_pjs_int(event.toHuman().data[0].descriptor.paraId); + let relay_parent = event.toHuman().data[0].descriptor.relayParent; if (blocks_per_para[included_para_id] == undefined) { blocks_per_para[included_para_id] = 1; } else { blocks_per_para[included_para_id]++; } + console.log(`CandidateIncluded for ${included_para_id}: block_offset=${block_count} relay_parent=${relay_parent}`); }); if (block_count == 12) { @@ -36,7 +38,7 @@ async function run(nodeName, networkInfo) { }); console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); - return (blocks_per_para[2000] == 6 && blocks_per_para[2001] == 1); + return (blocks_per_para[2000] == 3 * blocks_per_para[2001]); } module.exports = { run }; From d6b35ca5fd5cef3a95da6e47e30b14a9ec7e7558 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 11:46:14 +0300 Subject: [PATCH 077/138] Relax expected block counts for each para --- .../zombienet_tests/functional/0017-verify-included-events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/zombienet_tests/functional/0017-verify-included-events.js b/polkadot/zombienet_tests/functional/0017-verify-included-events.js index bcb44fe00807..372cd5e6ed5b 100644 --- a/polkadot/zombienet_tests/functional/0017-verify-included-events.js +++ b/polkadot/zombienet_tests/functional/0017-verify-included-events.js @@ -38,7 +38,7 @@ async function run(nodeName, networkInfo) { }); console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); - return (blocks_per_para[2000] == 3 * blocks_per_para[2001]); + return (blocks_per_para[2000] > 7) && (blocks_per_para[2001] < 4); } module.exports = { run }; From 586b56b6937017dcc611d017c29139472ec5c538 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 11:51:16 +0300 Subject: [PATCH 078/138] Bump lookahead and decrease timeout --- .../functional/0017-coretime-collation-fetching-fairness.toml | 2 +- .../functional/0017-coretime-collation-fetching-fairness.zndsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml index 6bc85f29c305..63ff487a8694 100644 --- a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml @@ -8,7 +8,7 @@ timeout = 1000 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 4 num_cores = 1 - lookahead = 3 + lookahead = 4 [relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] needed_approvals = 3 diff --git a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl index 822559a46ca7..3789b7f480c5 100644 --- a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl +++ b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl @@ -9,7 +9,7 @@ validator-0: js-script ./0015-force-register-paras.js with "2000,2001" return is validator-0: js-script ./assign-core.js with "0,2000,43200,2001,14400" return is 0 within 600 seconds collator-2000: reports block height is at least 9 within 200 seconds -collator-2001: reports block height is at least 3 within 200 seconds +collator-2001: reports block height is at least 3 within 10 seconds # hardcoded check to verify that included onchain events are indeed 3:1 validator-0: js-script ./0017-verify-included-events.js return is 1 within 120 seconds From a04d480da5bc5644024c9734da3da9172685413a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 17:38:24 +0300 Subject: [PATCH 079/138] Fix ZN pipeline - try 1 --- .gitlab/pipeline/zombienet/polkadot.yml | 5 +++++ .../functional/0015-coretime-shared-core.zndsl | 4 ++-- .../0017-coretime-collation-fetching-fairness.zndsl | 2 +- ...{0015-force-register-paras.js => force-register-paras.js} | 0 4 files changed, 8 insertions(+), 3 deletions(-) rename polkadot/zombienet_tests/functional/{0015-force-register-paras.js => force-register-paras.js} (100%) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 35c3c21b5d13..d956b12c63b6 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -220,6 +220,7 @@ zombienet-polkadot-functional-0015-coretime-shared-core: before_script: - !reference [ .zombienet-polkadot-common, before_script ] - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional + - cp --remove-destination ${LOCAL_DIR}/force-register-paras.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" @@ -236,6 +237,10 @@ zombienet-polkadot-functional-0016-approval-voting-parallel: zombienet-polkadot-functional-0017-coretime-collation-fetching-fairness: extends: - .zombienet-polkadot-common + before_script: + - !reference [ .zombienet-polkadot-common, before_script ] + - cp --remove-destination ${LOCAL_DIR}/force-register-paras.js ${LOCAL_DIR}/functional + - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" diff --git a/polkadot/zombienet_tests/functional/0015-coretime-shared-core.zndsl b/polkadot/zombienet_tests/functional/0015-coretime-shared-core.zndsl index b8b8887df857..8f883dffa5e1 100644 --- a/polkadot/zombienet_tests/functional/0015-coretime-shared-core.zndsl +++ b/polkadot/zombienet_tests/functional/0015-coretime-shared-core.zndsl @@ -5,8 +5,8 @@ Creds: config validator: reports node_roles is 4 # register paras 2 by 2 to speed up the test. registering all at once will exceed the weight limit. -validator-0: js-script ./0015-force-register-paras.js with "2000,2001" return is 0 within 600 seconds -validator-0: js-script ./0015-force-register-paras.js with "2002,2003" return is 0 within 600 seconds +validator-0: js-script ./force-register-paras.js with "2000,2001" return is 0 within 600 seconds +validator-0: js-script ./force-register-paras.js with "2002,2003" return is 0 within 600 seconds # assign core 0 to be shared by all paras. validator-0: js-script ./assign-core.js with "0,2000,14400,2001,14400,2002,14400,2003,14400" return is 0 within 600 seconds diff --git a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl index 3789b7f480c5..0501d64f4927 100644 --- a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl +++ b/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl @@ -4,7 +4,7 @@ Creds: config validator: reports node_roles is 4 -validator-0: js-script ./0015-force-register-paras.js with "2000,2001" return is 0 within 600 seconds +validator-0: js-script ./force-register-paras.js with "2000,2001" return is 0 within 600 seconds # core 0 is shared 3:1 between paras validator-0: js-script ./assign-core.js with "0,2000,43200,2001,14400" return is 0 within 600 seconds diff --git a/polkadot/zombienet_tests/functional/0015-force-register-paras.js b/polkadot/zombienet_tests/functional/force-register-paras.js similarity index 100% rename from polkadot/zombienet_tests/functional/0015-force-register-paras.js rename to polkadot/zombienet_tests/functional/force-register-paras.js From 13d5d154e1070fce7c0581ca44ed72618d0dc884 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 18:10:56 +0300 Subject: [PATCH 080/138] Fix ZN pipeline - try 2 --- .gitlab/pipeline/zombienet/polkadot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index d956b12c63b6..fd730d6ad39e 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -220,7 +220,7 @@ zombienet-polkadot-functional-0015-coretime-shared-core: before_script: - !reference [ .zombienet-polkadot-common, before_script ] - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional - - cp --remove-destination ${LOCAL_DIR}/force-register-paras.js ${LOCAL_DIR}/functional + - cp --remove-destination ${LOCAL_DIR}/functional/force-register-paras.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" From 86870d00299bf22a368ac31719eaf38ebf55ea63 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 19:12:49 +0300 Subject: [PATCH 081/138] Fix ZN pipeline - try 3 --- .gitlab/pipeline/zombienet/polkadot.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index fd730d6ad39e..236644eef20c 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -217,10 +217,6 @@ zombienet-polkadot-functional-0014-chunk-fetching-network-compatibility: zombienet-polkadot-functional-0015-coretime-shared-core: extends: - .zombienet-polkadot-common - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional - - cp --remove-destination ${LOCAL_DIR}/functional/force-register-paras.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" @@ -237,10 +233,6 @@ zombienet-polkadot-functional-0016-approval-voting-parallel: zombienet-polkadot-functional-0017-coretime-collation-fetching-fairness: extends: - .zombienet-polkadot-common - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/force-register-paras.js ${LOCAL_DIR}/functional - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" From 7b822af9c45e59458f08a694322267ccda1eaac5 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 18 Oct 2024 22:36:25 +0300 Subject: [PATCH 082/138] Fix ZN pipeline - try 4 --- .gitlab/pipeline/zombienet/polkadot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 236644eef20c..276ed3c8bcc8 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -217,6 +217,9 @@ zombienet-polkadot-functional-0014-chunk-fetching-network-compatibility: zombienet-polkadot-functional-0015-coretime-shared-core: extends: - .zombienet-polkadot-common + before_script: + - !reference [ .zombienet-polkadot-common, before_script ] + - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" @@ -233,6 +236,9 @@ zombienet-polkadot-functional-0016-approval-voting-parallel: zombienet-polkadot-functional-0017-coretime-collation-fetching-fairness: extends: - .zombienet-polkadot-common + before_script: + - !reference [ .zombienet-polkadot-common, before_script ] + - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" From ade7f9bf8930da9bc10a6c066c8695347bf2ecc3 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 24 Oct 2024 13:44:43 +0300 Subject: [PATCH 083/138] Rename ZN test --- ....toml => 0019-coretime-collation-fetching-fairness.toml} | 0 ...ndsl => 0019-coretime-collation-fetching-fairness.zndsl} | 6 +++--- ...fy-included-events.js => 0019-verify-included-events.js} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename polkadot/zombienet_tests/functional/{0017-coretime-collation-fetching-fairness.toml => 0019-coretime-collation-fetching-fairness.toml} (100%) rename polkadot/zombienet_tests/functional/{0017-coretime-collation-fetching-fairness.zndsl => 0019-coretime-collation-fetching-fairness.zndsl} (81%) rename polkadot/zombienet_tests/functional/{0017-verify-included-events.js => 0019-verify-included-events.js} (100%) diff --git a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml similarity index 100% rename from polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.toml rename to polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml diff --git a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl similarity index 81% rename from polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl rename to polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl index 0501d64f4927..7f3d157be415 100644 --- a/polkadot/zombienet_tests/functional/0017-coretime-collation-fetching-fairness.zndsl +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl @@ -1,5 +1,5 @@ -Description: CT shared core test -Network: ./0017-coretime-collation-fetching-fairness.toml +Description: CT shared core fairness test +Network: ./0019-coretime-collation-fetching-fairness.toml Creds: config validator: reports node_roles is 4 @@ -12,7 +12,7 @@ collator-2000: reports block height is at least 9 within 200 seconds collator-2001: reports block height is at least 3 within 10 seconds # hardcoded check to verify that included onchain events are indeed 3:1 -validator-0: js-script ./0017-verify-included-events.js return is 1 within 120 seconds +validator-0: js-script ./0019-verify-included-events.js return is 1 within 120 seconds # could be flaky feel free to remove if it's causing problems validator-2: count of log lines matching regex "Rejected v2 advertisement.*error=SecondedLimitReached" is greater than 1 within 10 seconds diff --git a/polkadot/zombienet_tests/functional/0017-verify-included-events.js b/polkadot/zombienet_tests/functional/0019-verify-included-events.js similarity index 100% rename from polkadot/zombienet_tests/functional/0017-verify-included-events.js rename to polkadot/zombienet_tests/functional/0019-verify-included-events.js From ab7056743635b624b3d8a1ca636fbfd83aa6f8b3 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 28 Oct 2024 15:25:25 +0200 Subject: [PATCH 084/138] Handle merge conflicts --- .../node/network/collator-protocol/src/validator_side/mod.rs | 4 ++-- .../network/collator-protocol/src/validator_side/tests/mod.rs | 4 ++-- .../src/validator_side/tests/prospective_parachains.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 311ebb99e9be..9706f9cb7384 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -53,7 +53,7 @@ use polkadot_node_subsystem_util::{ runtime::{fetch_claim_queue, recv_runtime}, }; use polkadot_primitives::{ - vstaging::CoreState, AsyncBackingParams, CandidateHash, CollatorId, CoreState, Hash, HeadData, + vstaging::CoreState, AsyncBackingParams, CandidateHash, CollatorId, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, }; @@ -1830,7 +1830,7 @@ async fn kick_off_seconding( maybe_parent_head.and_then(|head| maybe_parent_head_hash.map(|hash| (head, hash))), )?; - let para_id = candidate_receipt.descriptor().para_id; + let para_id = candidate_receipt.descriptor().para_id(); ctx.send_message(CandidateBackingMessage::Second( relay_parent, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index c791fa97c753..04ecca92eddc 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -42,8 +42,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore}, - AsyncBackingParams, CollatorPair, CoreIndex, CoreState, GroupIndex, GroupRotationInfo, - HeadData, OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, + AsyncBackingParams, CollatorPair, CoreIndex, GroupIndex, GroupRotationInfo, HeadData, + PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, }; use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 00407bd0e1cd..64482731d9ca 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -21,7 +21,7 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber, - CandidateCommitments, CommittedCandidateReceipt, Header, SigningContext, ValidatorId, + CandidateCommitments, Header, SigningContext, ValidatorId, }; use rstest::rstest; @@ -399,7 +399,7 @@ fn create_dummy_candidate_and_commitments( }; candidate.commitments_hash = commitments.hash(); - (candidate, commitments) + (candidate.into(), commitments) } async fn assert_advertise_collation( From d24fdc1a0dbaf27d45932e4dd107b3f356f7af18 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 6 Nov 2024 17:49:33 +0200 Subject: [PATCH 085/138] When counting occupied slots from the claim queue consider relay parents below and above the target relay parent --- .../network/collator-protocol/src/error.rs | 3 + .../src/validator_side/mod.rs | 179 ++++++++-- .../src/validator_side/tests/mod.rs | 14 +- .../tests/prospective_parachains.rs | 15 +- .../src/backing_implicit_view.rs | 334 ++++++++++-------- 5 files changed, 361 insertions(+), 184 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index 0f5e0699d85c..9c7173921410 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -63,6 +63,9 @@ pub enum Error { #[error("CollationSeconded contained statement with invalid signature")] InvalidStatementSignature(UncheckedSignedFullStatement), + + #[error(transparent)] + RelayParentError(backing_implicit_view::PathError), } /// An error happened on the validator side of the protocol when attempting diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 9706f9cb7384..a2844aa475b2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1012,6 +1012,80 @@ async fn second_unblocked_collations( } } +fn ensure_seconding_limit_is_respected( + relay_parent: &Hash, + para_id: ParaId, + state: &State, + per_relay_parent: &PerRelayParent, + assignment: &GroupAssignments, +) -> std::result::Result<(), AdvertisementError> { + let claims_for_para = per_relay_parent.collations.claims_for_para(¶_id); + + // Seconding and pending candidates below the relay parent of the candidate. These are + // candidates which might have claimed slots at the current view of the claim queue. + let seconded_and_pending_below = seconded_and_pending_for_para_below( + &state.implicit_view, + &state.per_relay_parent, + &relay_parent, + ¶_id, + assignment.current.len(), + ); + + // Seconding and pending candidates above the relay parent of the candidate. These are + // candidates at a newer relay parent which have already claimed a slot within their view. + let seconded_and_pending_above = seconded_and_pending_for_para_above( + &state.implicit_view, + &state.per_relay_parent, + &relay_parent, + ¶_id, + assignment.current.len(), + ) + .map_err(|_| AdvertisementError::RelayParentUnknown)?; + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + claims_for_para, + seconded_and_pending_below, + ?seconded_and_pending_above, + "Checking if seconded limit is reached" + ); + + // Relay parent is an outer leaf. There are no paths to it. + if seconded_and_pending_above.is_empty() { + if seconded_and_pending_below >= claims_for_para { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + claims_for_para, + "Seconding limit exceeded for an outer leaf" + ); + return Err(AdvertisementError::SecondedLimitReached) + } + } + + // Checks if another collation can be accepted. The number of collations that can be seconded + // per parachain is limited by the entries in claim queue for the `ParaId` in question. + // No op for for outer leaves + for claims_at_path in seconded_and_pending_above { + if claims_at_path + seconded_and_pending_below >= claims_for_para { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + claims_for_para, + claims_at_path, + "Seconding limit exceeded" + ); + return Err(AdvertisementError::SecondedLimitReached) + } + } + + Ok(()) +} + async fn handle_advertisement( sender: &mut Sender, state: &mut State, @@ -1056,29 +1130,13 @@ where ) .map_err(AdvertisementError::Invalid)?; - let claims_for_para = per_relay_parent.collations.claims_for_para(¶_id); - let seconded_and_pending_at_ancestors = seconded_and_pending_for_para_in_view( - &state.implicit_view, - &state.per_relay_parent, + ensure_seconding_limit_is_respected( &relay_parent, - ¶_id, - assignment.current.len(), - ); - - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - claims_for_para, - seconded_and_pending_at_ancestors, - "Checking if seconded limit is reached" - ); - - // Checks if another collation can be accepted. The number of collations that can be seconded - // per parachain is limited by the entries in claim queue for the `ParaId` in question. - if seconded_and_pending_at_ancestors >= claims_for_para { - return Err(AdvertisementError::SecondedLimitReached) - } + para_id, + state, + per_relay_parent, + assignment, + )?; if let Some((candidate_hash, parent_head_data_hash)) = prospective_candidate { // Check if backing subsystem allows to second this candidate. @@ -1206,6 +1264,13 @@ where let added = view.iter().filter(|h| !current_leaves.contains_key(h)); for leaf in added { + gum::trace!( + target: LOG_TARGET, + ?view, + ?leaf, + "handle_our_view_change - added", + ); + let async_backing_params = recv_runtime(request_async_backing_params(*leaf, sender).await).await?; @@ -1226,8 +1291,15 @@ where .implicit_view .known_allowed_relay_parents_under(leaf, None) .unwrap_or_default(); + for block_hash in allowed_ancestry { if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { + gum::trace!( + target: LOG_TARGET, + ?view, + ?block_hash, + "handle_our_view_change - allowed_ancestry", + ); let assignments = assign_incoming(sender, &mut state.current_assignments, keystore, *block_hash) .await?; @@ -1238,6 +1310,13 @@ where } for (removed, _) in removed { + gum::trace!( + target: LOG_TARGET, + ?view, + ?removed, + "handle_our_view_change - removed", + ); + state.active_leaves.remove(removed); // If the leaf is deactivated it still may stay in the view as a part // of implicit ancestry. Only update the state after the hash is actually @@ -2018,7 +2097,7 @@ async fn handle_collation_fetch_response( // Returns how many seconded candidates and pending fetches are there within the view at a specific // relay parent -fn seconded_and_pending_for_para_in_view( +fn seconded_and_pending_for_para_below( implicit_view: &ImplicitView, per_relay_parent: &HashMap, relay_parent: &Hash, @@ -2036,7 +2115,7 @@ fn seconded_and_pending_for_para_in_view( ?relay_parent, ?para_id, claim_queue_len, - "seconded_and_pending_for_para_in_view" + "seconded_and_pending_for_para_below" ); ancestors.iter().take(claim_queue_len).fold(0, |res, anc| { res + per_relay_parent @@ -2048,6 +2127,29 @@ fn seconded_and_pending_for_para_in_view( .unwrap_or(0) } +fn seconded_and_pending_for_para_above( + implicit_view: &ImplicitView, + per_relay_parent: &HashMap, + relay_parent: &Hash, + para_id: &ParaId, + claim_queue_len: usize, +) -> Result> { + let outer_paths = implicit_view + .paths_to_relay_parent(relay_parent) + .map_err(Error::RelayParentError)?; + let mut result = vec![]; + for path in outer_paths { + let r = path.iter().take(claim_queue_len).fold(0, |res, anc| { + res + per_relay_parent + .get(&anc) + .map(|rp| rp.collations.seconded_and_pending_for_para(para_id)) + .unwrap_or(0) + }); + result.push(r); + } + + Ok(result) +} // Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the // order in the claim queue so the earlier an element is located in the `Vec` the higher its // priority is. @@ -2061,16 +2163,27 @@ fn unfulfilled_claim_queue_entries( let mut claims_per_para = scheduled_paras .into_iter() .map(|para_id| { - ( - *para_id, - seconded_and_pending_for_para_in_view( - implicit_view, - per_relay_parent, - relay_parent, - para_id, - relay_parent_state.assignment.current.len(), - ), + let below = seconded_and_pending_for_para_below( + implicit_view, + per_relay_parent, + relay_parent, + para_id, + relay_parent_state.assignment.current.len(), + ); + let above = seconded_and_pending_for_para_above( + implicit_view, + per_relay_parent, + relay_parent, + para_id, + relay_parent_state.assignment.current.len(), ) + .unwrap_or_default() //todo + .iter() + .max() + .copied() + .unwrap_or(0); + + (*para_id, below + above) }) .collect::>(); let claim_queue_state = relay_parent_state diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 04ecca92eddc..27bbc0cba23c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -79,7 +79,7 @@ struct TestState { impl Default for TestState { fn default() -> Self { - let relay_parent = Hash::repeat_byte(0x05); + let relay_parent = Hash::from_low_u64_be(0x05); let collators = iter::repeat(()).map(|_| CollatorPair::generate().0).take(5).collect(); let validators = vec![ @@ -624,7 +624,7 @@ fn fetch_one_collation_at_a_time() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); + let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); update_view( &mut virtual_overseer, @@ -878,7 +878,7 @@ fn fetch_next_collation_on_invalid_collation() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); + let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); update_view( &mut virtual_overseer, @@ -1024,14 +1024,14 @@ fn activity_extends_life() { let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - let hash_b = Hash::repeat_byte(1); - let hash_c = Hash::repeat_byte(2); + let hash_a = Hash::from_low_u64_be(12); + let hash_b = Hash::from_low_u64_be(11); + let hash_c = Hash::from_low_u64_be(10); update_view( &mut virtual_overseer, &test_state, - vec![(hash_a, 0), (hash_b, 1), (hash_c, 1)], + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], 3, ) .await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 64482731d9ca..c99ec6f0f4bb 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -119,8 +119,13 @@ pub(super) async fn update_view( let mut next_overseer_message = None; for _ in 0..activated { + let msg = match next_overseer_message.take() { + Some(msg) => msg, + None => overseer_recv(virtual_overseer).await, + }; + let (leaf_hash, leaf_number) = assert_matches!( - overseer_recv(virtual_overseer).await, + msg, AllMessages::RuntimeApi(RuntimeApiMessage::Request( parent, RuntimeApiRequest::AsyncBackingParams(tx), @@ -1593,7 +1598,7 @@ fn collation_fetches_without_claimqueue() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - let head_b = Hash::from_low_u64_be(128); + let head_b = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); let head_b_num: u32 = 2; update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; @@ -1857,7 +1862,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { let collator_b = PeerId::random(); let para_id_b = test_state.chain_ids[1]; - let relay_parent_2 = Hash::from_low_u64_be(128); + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_2, 2)], 1).await; @@ -1903,7 +1908,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // parent hashes are hardcoded in `get_parent_hash` (called from `update_view`) to be // `current hash + 1` so we need to craft them carefully (decrement by 2) in order to make // them fall in the same view. - let relay_parent_4 = Hash::from_low_u64_be(126); + let relay_parent_4 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 2); update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_4, 4)], 1).await; @@ -1962,7 +1967,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one // can be accepted - let relay_parent_6 = Hash::from_low_u64_be(124); + let relay_parent_6 = Hash::from_low_u64_be(relay_parent_4.to_low_u64_be() - 2); update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_6, 6)], 1).await; submit_second_and_assert( diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index a805ef8165e5..e094b3ec1e18 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -22,7 +22,7 @@ use polkadot_node_subsystem::{ }; use polkadot_primitives::{BlockNumber, Hash, Id as ParaId}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap, HashSet}; use crate::{ inclusion_emulator::RelayChainBlockInfo, @@ -142,6 +142,17 @@ impl From for RelayChainBlockInfo { } } +/// Error in constructing a path from an outer relay parent to a target relay parent. +#[fatality::fatality] +pub enum PathError { + /// The relay parent is not present in the view. + #[error("The relay parent is not present in the view")] + UnknownRelayParent(Hash), + /// There are no relay parents to build on in the view. + #[error("The view is empty")] + NoRelayParentsToBuildOn, +} + impl View { /// Get an iterator over active leaves in the view. pub fn leaves(&self) -> impl Iterator { @@ -173,13 +184,7 @@ impl View { return Err(FetchError::AlreadyKnown) } - let res = fetch_fresh_leaf_and_insert_ancestry( - leaf_hash, - &mut self.block_info_storage, - &mut *sender, - self.collating_for, - ) - .await; + let res = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).await; match res { Ok(fetched) => { @@ -323,6 +328,188 @@ impl View { .as_ref() .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } + + /// Returns all relay parents grouped by block number + fn relay_parents_by_block_number(&self) -> BTreeMap> { + self.block_info_storage.iter().fold(BTreeMap::new(), |mut acc, (hash, info)| { + acc.entry(info.block_number).or_insert_with(HashSet::new).insert(*hash); + acc + }) + } + + /// Returns all paths from each outer leaf to `relay_parent`. If no paths exist the function + /// will return an empty `Vec`. + /// + /// The input is not included in the path so if `relay_parent` happens to be an outer leaf, no + /// paths will be returned. + pub fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Result>, PathError> { + let relay_parents_by_block_number = self.relay_parents_by_block_number(); + let outer_leaves = relay_parents_by_block_number + .last_key_value() + .ok_or(PathError::NoRelayParentsToBuildOn)? + .1; + + // handle the case where the relay parent is an outer leaf + if outer_leaves.contains(&relay_parent) { + return Ok(vec![]); + } + + let mut paths = Vec::new(); + for leaf in outer_leaves { + let mut path = Vec::new(); + let mut current_block = *leaf; + + while current_block != *relay_parent { + path.push(current_block); + current_block = self + .block_info_storage + .get(¤t_block) + .ok_or(PathError::UnknownRelayParent(current_block))? + .parent_hash; + } + + // Don't add empty paths to the result + if path.is_empty() { + continue + } + + paths.push(path); + } + + Ok(paths) + } + + async fn fetch_fresh_leaf_and_insert_ancestry( + &mut self, + leaf_hash: Hash, + sender: &mut Sender, + ) -> Result + where + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, + { + let leaf_header = { + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; + + match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + } + }; + + // If the node is a collator, bypass prospective-parachains. We're only interested in the + // one paraid and the subsystem is not present. + let min_relay_parents = if let Some(para_id) = self.collating_for { + fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) + .await? + .map(|x| vec![(para_id, x)]) + .unwrap_or_default() + } else { + fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? + }; + + let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); + let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; + + let ancestry = if leaf_header.number > 0 { + let mut next_ancestor_number = leaf_header.number - 1; + let mut next_ancestor_hash = leaf_header.parent_hash; + + let mut ancestry = Vec::with_capacity(expected_ancestry_len); + ancestry.push(leaf_hash); + + // Ensure all ancestors up to and including `min_min` are in the + // block storage. When views advance incrementally, everything + // should already be present. + while next_ancestor_number >= min_min { + let parent_hash = if let Some(info) = + self.block_info_storage.get(&next_ancestor_hash) + { + info.parent_hash + } else { + // load the header and insert into block storage. + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; + + let header = match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + }; + + self.block_info_storage.insert( + next_ancestor_hash, + BlockInfo { + block_number: next_ancestor_number, + parent_hash: header.parent_hash, + maybe_allowed_relay_parents: None, + }, + ); + + header.parent_hash + }; + + ancestry.push(next_ancestor_hash); + if next_ancestor_number == 0 { + break + } + + next_ancestor_number -= 1; + next_ancestor_hash = parent_hash; + } + + ancestry + } else { + vec![leaf_hash] + }; + + let fetched_ancestry = + FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; + + let allowed_relay_parents = AllowedRelayParents { + minimum_relay_parents: min_relay_parents.into_iter().collect(), + allowed_relay_parents_contiguous: ancestry, + }; + + let leaf_block_info = BlockInfo { + parent_hash: leaf_header.parent_hash, + block_number: leaf_header.number, + maybe_allowed_relay_parents: Some(allowed_relay_parents), + }; + + self.block_info_storage.insert(leaf_hash, leaf_block_info); + + Ok(fetched_ancestry) + } } /// Errors when fetching a leaf and associated ancestry. @@ -442,137 +629,6 @@ where Ok(Some(min)) } -async fn fetch_fresh_leaf_and_insert_ancestry( - leaf_hash: Hash, - block_info_storage: &mut HashMap, - sender: &mut Sender, - collating_for: Option, -) -> Result -where - Sender: SubsystemSender - + SubsystemSender - + SubsystemSender, -{ - let leaf_header = { - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; - - match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - } - }; - - // If the node is a collator, bypass prospective-parachains. We're only interested in the one - // paraid and the subsystem is not present. - let min_relay_parents = if let Some(para_id) = collating_for { - fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) - .await? - .map(|x| vec![(para_id, x)]) - .unwrap_or_default() - } else { - fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? - }; - - let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); - let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; - - let ancestry = if leaf_header.number > 0 { - let mut next_ancestor_number = leaf_header.number - 1; - let mut next_ancestor_hash = leaf_header.parent_hash; - - let mut ancestry = Vec::with_capacity(expected_ancestry_len); - ancestry.push(leaf_hash); - - // Ensure all ancestors up to and including `min_min` are in the - // block storage. When views advance incrementally, everything - // should already be present. - while next_ancestor_number >= min_min { - let parent_hash = if let Some(info) = block_info_storage.get(&next_ancestor_hash) { - info.parent_hash - } else { - // load the header and insert into block storage. - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; - - let header = match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - }; - - block_info_storage.insert( - next_ancestor_hash, - BlockInfo { - block_number: next_ancestor_number, - parent_hash: header.parent_hash, - maybe_allowed_relay_parents: None, - }, - ); - - header.parent_hash - }; - - ancestry.push(next_ancestor_hash); - if next_ancestor_number == 0 { - break - } - - next_ancestor_number -= 1; - next_ancestor_hash = parent_hash; - } - - ancestry - } else { - vec![leaf_hash] - }; - - let fetched_ancestry = - FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; - - let allowed_relay_parents = AllowedRelayParents { - minimum_relay_parents: min_relay_parents.into_iter().collect(), - allowed_relay_parents_contiguous: ancestry, - }; - - let leaf_block_info = BlockInfo { - parent_hash: leaf_header.parent_hash, - block_number: leaf_header.number, - maybe_allowed_relay_parents: Some(allowed_relay_parents), - }; - - block_info_storage.insert(leaf_hash, leaf_block_info); - - Ok(fetched_ancestry) -} - #[cfg(test)] mod tests { use super::*; From f55390ec71dd0181f3a5befabbb434e1454b2cbc Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 7 Nov 2024 10:58:34 +0200 Subject: [PATCH 086/138] Add a test --- .../tests/prospective_parachains.rs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index c99ec6f0f4bb..403894008654 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -1983,3 +1983,91 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { virtual_overseer }); } + +#[test] +fn claim_queue_spot_claimed_at_next_relay_parent() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = Some(claim_queue); + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(12); + let hash_b = Hash::from_low_u64_be(11); + let hash_c = Hash::from_low_u64_be(10); + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view( + &mut virtual_overseer, + &test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], + 3, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims the spot at hash_a + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0 as u8]), + ) + .await; + + // Another collation at hash_a claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![1 as u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![0 as u8]), + ) + .await; + + // Collation at hash_b should be ignored because the claim queue is satisfied + advertise_collation(&mut virtual_overseer, collator_a, hash_b, None).await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} From a2093ee7a964614f7bc4937ca81c44387482530c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 7 Nov 2024 11:03:42 +0200 Subject: [PATCH 087/138] Small style fixes in tests --- .../tests/prospective_parachains.rs | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 403894008654..5fd85743781a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -509,7 +509,7 @@ fn v1_advertisement_accepted_and_seconded() { candidate.descriptor.para_id = test_state.chain_ids[0]; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); let commitments = CandidateCommitments { - head_data: HeadData(vec![1 as u8]), + head_data: HeadData(vec![1u8]), horizontal_messages: Default::default(), upward_messages: Default::default(), new_validation_code: None, @@ -729,7 +729,7 @@ fn second_multiple_candidates_per_relay_parent() { .await; // `allowed_ancestry_len` equals the size of the claim queue - for i in 0..(test_state.async_backing_params.allowed_ancestry_len) { + for i in 0..test_state.async_backing_params.allowed_ancestry_len { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), @@ -1433,7 +1433,7 @@ fn collations_outside_limits_are_not_fetched() { ParaId::from(test_state.chain_ids[0]), head_b, peer_a, - HeadData(vec![1 as u8]), + HeadData(vec![1u8]), ) .await; @@ -1443,7 +1443,7 @@ fn collations_outside_limits_are_not_fetched() { ParaId::from(test_state.chain_ids[1]), head_b, peer_b, - HeadData(vec![2 as u8]), + HeadData(vec![2u8]), ) .await; @@ -1453,7 +1453,7 @@ fn collations_outside_limits_are_not_fetched() { ParaId::from(test_state.chain_ids[0]), head_b, peer_a, - HeadData(vec![3 as u8]), + HeadData(vec![3u8]), ) .await; @@ -1555,7 +1555,7 @@ fn fair_collation_fetches() { ParaId::from(test_state.chain_ids[1]), head_b, peer_b, - HeadData(vec![0 as u8]), + HeadData(vec![0u8]), ) .await; @@ -1592,7 +1592,7 @@ fn fair_collation_fetches() { // This should not happen in practice since claim queue is supported on all networks but just in // case validate that the fallback works as expected #[test] -fn collation_fetches_without_claimqueue() { +fn collation_fetches_without_claim_queue() { let test_state = TestState::without_claim_queue(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { @@ -1652,7 +1652,7 @@ fn collation_fetches_without_claimqueue() { ParaId::from(test_state.chain_ids[0]), head_b, peer_a, - HeadData(vec![0 as u8]), + HeadData(vec![0u8]), ) .await; @@ -1711,17 +1711,17 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { .await; let (candidate_a1, commitments_a1) = - create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![0 as u8]), head); + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![0u8]), head); let (candidate_b1, commitments_b1) = - create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![1 as u8]), head); + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![1u8]), head); let (candidate_a2, commitments_a2) = - create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2 as u8]), head); + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), head); let (candidate_a3, _) = - create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3 as u8]), head); - let parent_head_data_a1 = HeadData(vec![0 as u8]); - let parent_head_data_b1 = HeadData(vec![1 as u8]); - let parent_head_data_a2 = HeadData(vec![2 as u8]); - let parent_head_data_a3 = HeadData(vec![3 as u8]); + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), head); + let parent_head_data_a1 = HeadData(vec![0u8]); + let parent_head_data_b1 = HeadData(vec![1u8]); + let parent_head_data_a2 = HeadData(vec![2u8]); + let parent_head_data_a3 = HeadData(vec![3u8]); // advertise a collation for `para_id_a` but don't send the collation. This will be a // pending fetch. @@ -1891,7 +1891,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { para_id_a, relay_parent_2, collator_a, - HeadData(vec![0 as u8]), + HeadData(vec![0u8]), ) .await; @@ -1901,7 +1901,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { para_id_a, relay_parent_2, collator_a, - HeadData(vec![1 as u8]), + HeadData(vec![1u8]), ) .await; @@ -1919,7 +1919,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { para_id_b, relay_parent_4, collator_b, - HeadData(vec![3 as u8]), + HeadData(vec![3u8]), ) .await; @@ -1927,12 +1927,9 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // must be ignored // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored - let (candidate_a, _) = create_dummy_candidate_and_commitments( - para_id_a, - HeadData(vec![5 as u8]), - relay_parent_4, - ); - let parent_head_data_a = HeadData(vec![5 as u8]); + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_4); + let parent_head_data_a = HeadData(vec![5u8]); advertise_collation( &mut virtual_overseer, @@ -1946,12 +1943,9 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { assert_matches!(virtual_overseer.recv().now_or_never(), None); // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored - let (candidate_b, _) = create_dummy_candidate_and_commitments( - para_id_b, - HeadData(vec![6 as u8]), - relay_parent_4, - ); - let parent_head_data_b = HeadData(vec![6 as u8]); + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_4); + let parent_head_data_b = HeadData(vec![6u8]); advertise_collation( &mut virtual_overseer, @@ -1976,7 +1970,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { para_id_a, relay_parent_6, collator_a, - HeadData(vec![3 as u8]), + HeadData(vec![3u8]), ) .await; @@ -2036,7 +2030,7 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { ParaId::from(test_state.chain_ids[0]), hash_a, collator_a, - HeadData(vec![0 as u8]), + HeadData(vec![0u8]), ) .await; @@ -2047,7 +2041,7 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { ParaId::from(test_state.chain_ids[0]), hash_a, collator_a, - HeadData(vec![1 as u8]), + HeadData(vec![1u8]), ) .await; @@ -2058,7 +2052,7 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { ParaId::from(test_state.chain_ids[0]), hash_c, collator_a, - HeadData(vec![0 as u8]), + HeadData(vec![0u8]), ) .await; From ded6fb586398dc020f2fabdcc29d86b52491198d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 7 Nov 2024 11:39:36 +0200 Subject: [PATCH 088/138] Fix a todo --- .../network/collator-protocol/src/error.rs | 7 +- .../src/validator_side/mod.rs | 72 +++++++++++-------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index 9c7173921410..578ed105c059 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -64,8 +64,11 @@ pub enum Error { #[error("CollationSeconded contained statement with invalid signature")] InvalidStatementSignature(UncheckedSignedFullStatement), - #[error(transparent)] - RelayParentError(backing_implicit_view::PathError), + #[error("Relay parent not found during path traversal")] + PathRelayParentError(backing_implicit_view::PathError), + + #[error("No state for the relay parent")] + RelayParentStateNotFound, } /// An error happened on the validator side of the protocol when attempting diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index a2844aa475b2..bdc323060681 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2136,7 +2136,7 @@ fn seconded_and_pending_for_para_above( ) -> Result> { let outer_paths = implicit_view .paths_to_relay_parent(relay_parent) - .map_err(Error::RelayParentError)?; + .map_err(Error::PathRelayParentError)?; let mut result = vec![]; for path in outer_paths { let r = path.iter().take(claim_queue_len).fold(0, |res, anc| { @@ -2157,35 +2157,33 @@ fn unfulfilled_claim_queue_entries( relay_parent: &Hash, per_relay_parent: &HashMap, implicit_view: &ImplicitView, -) -> Option> { - let relay_parent_state = per_relay_parent.get(relay_parent)?; +) -> Result> { + let relay_parent_state = + per_relay_parent.get(relay_parent).ok_or(Error::RelayParentStateNotFound)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); - let mut claims_per_para = scheduled_paras + let mut claims_per_para = HashMap::new(); + for para_id in scheduled_paras { + let below = seconded_and_pending_for_para_below( + implicit_view, + per_relay_parent, + relay_parent, + para_id, + relay_parent_state.assignment.current.len(), + ); + let above = seconded_and_pending_for_para_above( + implicit_view, + per_relay_parent, + relay_parent, + para_id, + relay_parent_state.assignment.current.len(), + )? .into_iter() - .map(|para_id| { - let below = seconded_and_pending_for_para_below( - implicit_view, - per_relay_parent, - relay_parent, - para_id, - relay_parent_state.assignment.current.len(), - ); - let above = seconded_and_pending_for_para_above( - implicit_view, - per_relay_parent, - relay_parent, - para_id, - relay_parent_state.assignment.current.len(), - ) - .unwrap_or_default() //todo - .iter() - .max() - .copied() - .unwrap_or(0); + .max() + .unwrap_or(0); + + claims_per_para.insert(*para_id, below + above); + } - (*para_id, below + above) - }) - .collect::>(); let claim_queue_state = relay_parent_state .assignment .current @@ -2198,7 +2196,7 @@ fn unfulfilled_claim_queue_entries( _ => Some(*para_id), }) .collect::>(); - Some(claim_queue_state) + Ok(claim_queue_state) } /// Returns the next collation to fetch from the `waiting_queue` and reset the status back to @@ -2208,11 +2206,25 @@ fn get_next_collation_to_fetch( relay_parent: Hash, state: &mut State, ) -> Option<(PendingCollation, CollatorId)> { - let unfulfilled_entries = unfulfilled_claim_queue_entries( + let maybe_unfulfilled_entries = unfulfilled_claim_queue_entries( &relay_parent, &state.per_relay_parent, &state.implicit_view, - )?; + ); + + let unfulfilled_entries = match maybe_unfulfilled_entries { + Ok(entries) => entries, + Err(err) => { + // This should never happen + gum::error!( + target: LOG_TARGET, + ?relay_parent, + ?err, + "Failed to get unfulfilled claim queue entries" + ); + return None + }, + }; let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; // If finished one does not match waiting_collation, then we already dequeued another fetch From cda9330e91856457b60765f6b7782fa282703250 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 7 Nov 2024 22:14:23 +0200 Subject: [PATCH 089/138] Fix `paths_to_relay_parent` --- .../network/collator-protocol/src/error.rs | 3 - .../src/validator_side/mod.rs | 4 +- .../src/backing_implicit_view.rs | 113 ++++++++++++------ 3 files changed, 77 insertions(+), 43 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index 578ed105c059..7ff04e8e7d4e 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -64,9 +64,6 @@ pub enum Error { #[error("CollationSeconded contained statement with invalid signature")] InvalidStatementSignature(UncheckedSignedFullStatement), - #[error("Relay parent not found during path traversal")] - PathRelayParentError(backing_implicit_view::PathError), - #[error("No state for the relay parent")] RelayParentStateNotFound, } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index bdc323060681..b2a670f543c8 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2134,9 +2134,7 @@ fn seconded_and_pending_for_para_above( para_id: &ParaId, claim_queue_len: usize, ) -> Result> { - let outer_paths = implicit_view - .paths_to_relay_parent(relay_parent) - .map_err(Error::PathRelayParentError)?; + let outer_paths = implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; for path in outer_paths { let r = path.iter().take(claim_queue_len).fold(0, |res, anc| { diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index e094b3ec1e18..0512329d8f9b 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -22,7 +22,7 @@ use polkadot_node_subsystem::{ }; use polkadot_primitives::{BlockNumber, Hash, Id as ParaId}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; use crate::{ inclusion_emulator::RelayChainBlockInfo, @@ -41,6 +41,7 @@ pub struct View { leaves: HashMap, block_info_storage: HashMap, collating_for: Option, + outer_leaves: HashSet, } impl View { @@ -49,7 +50,12 @@ impl View { /// relay parents of a single paraid. When this is true, prospective-parachains is no longer /// queried. pub fn new(collating_for: Option) -> Self { - Self { leaves: Default::default(), block_info_storage: Default::default(), collating_for } + Self { + leaves: Default::default(), + block_info_storage: Default::default(), + collating_for, + outer_leaves: Default::default(), + } } } @@ -257,6 +263,8 @@ impl View { parent_hash: leaf.parent_hash, }, ); + self.outer_leaves.remove(&leaf.parent_hash); // the parent is no longer an outer leaf + self.outer_leaves.insert(leaf.hash); // the new leaf is an outer leaf } /// Deactivate a leaf in the view. This prunes any outdated implicit ancestors as well. @@ -285,6 +293,10 @@ impl View { keep }); + for r in &removed { + self.outer_leaves.remove(r); + } + removed } } @@ -329,54 +341,60 @@ impl View { .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } - /// Returns all relay parents grouped by block number - fn relay_parents_by_block_number(&self) -> BTreeMap> { - self.block_info_storage.iter().fold(BTreeMap::new(), |mut acc, (hash, info)| { - acc.entry(info.block_number).or_insert_with(HashSet::new).insert(*hash); - acc - }) - } - /// Returns all paths from each outer leaf to `relay_parent`. If no paths exist the function /// will return an empty `Vec`. /// /// The input is not included in the path so if `relay_parent` happens to be an outer leaf, no /// paths will be returned. - pub fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Result>, PathError> { - let relay_parents_by_block_number = self.relay_parents_by_block_number(); - let outer_leaves = relay_parents_by_block_number - .last_key_value() - .ok_or(PathError::NoRelayParentsToBuildOn)? - .1; - - // handle the case where the relay parent is an outer leaf - if outer_leaves.contains(&relay_parent) { - return Ok(vec![]); + pub fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Vec> { + if self.outer_leaves.is_empty() { + // No outer leaves so the view should be empty. Don't return any paths. + return vec![] + }; + + if !self.block_info_storage.contains_key(relay_parent) { + // `relay_parent` is not in the view - don't return any paths + return vec![] } + // Find all paths from each outer leaf to `relay_parent`. let mut paths = Vec::new(); - for leaf in outer_leaves { + for outer_leaf in &self.outer_leaves { let mut path = Vec::new(); - let mut current_block = *leaf; - - while current_block != *relay_parent { - path.push(current_block); - current_block = self - .block_info_storage - .get(¤t_block) - .ok_or(PathError::UnknownRelayParent(current_block))? - .parent_hash; - } + let mut current_leaf = *outer_leaf; + let mut visited = HashSet::new(); + + loop { + // If `relay_parent` is an outer leaf we'll immediately return an empty path (which + // is the desired behaviour). + if current_leaf == *relay_parent { + // path is complete + paths.push(path); + break + } - // Don't add empty paths to the result - if path.is_empty() { - continue - } + path.push(current_leaf); - paths.push(path); + current_leaf = match self.block_info_storage.get(¤t_leaf) { + Some(info) => { + let r = info.parent_hash; + if visited.contains(&r) { + // There is a cycle so this is not a path to `relay_parent` + break + } + visited.insert(r); + r + }, + None => { + // Parent is not found there for it should be outside the view. Abandon this + // path. + break + }, + } + } } - Ok(paths) + paths } async fn fetch_fresh_leaf_and_insert_ancestry( @@ -507,6 +525,8 @@ impl View { }; self.block_info_storage.insert(leaf_hash, leaf_block_info); + self.outer_leaves.remove(&leaf_header.parent_hash); // the parent is no longer an outer leaf + self.outer_leaves.insert(leaf_hash); // the new leaf is an outer leaf Ok(fetched_ancestry) } @@ -859,6 +879,14 @@ mod tests { assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..(PARA_A_MIN_PARENT - 1) as usize])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert_eq!(view.outer_leaves.len(), 1); + assert!(view.outer_leaves.contains(leaf)); + assert!(view.paths_to_relay_parent(&CHAIN_B[0]).is_empty()); + assert_eq!( + view.paths_to_relay_parent(&CHAIN_B[min_min_idx]), + vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + ); } ); @@ -979,6 +1007,11 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert_eq!( + view.paths_to_relay_parent(&CHAIN_B[min_min_idx]), + vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + ); } ); @@ -1047,6 +1080,12 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert!(view.paths_to_relay_parent(&GENESIS_HASH).is_empty()); + assert_eq!( + view.paths_to_relay_parent(&CHAIN_A[0]), + vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + ); } ); } From 505eb24d4d986d8bfd02eb0cbed0214cd963b0eb Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 09:45:54 +0200 Subject: [PATCH 090/138] Additional test for `paths_to_relay_parent` --- .../src/backing_implicit_view.rs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 0512329d8f9b..f13ae866b82c 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -1260,4 +1260,69 @@ mod tests { Some(hashes) if hashes == &[GENESIS_HASH] ); } + + #[test] + fn path_with_fork() { + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); + + let mut view = View::default(); + + assert_eq!(view.collating_for, None); + + // Chain A + let prospective_response = vec![(PARA_A, 0)]; // was PARA_A_MIN_PARENT + let leaf = CHAIN_A.last().unwrap(); + let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); + let leaf_idx = blocks.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + // Chain B + let prospective_response = vec![(PARA_A, 1)]; + + let leaf = CHAIN_B.last().unwrap(); + let leaf_idx = CHAIN_B.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[0..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + assert_eq!(view.leaves.len(), 2); + + let mut paths_to_genesis = view.paths_to_relay_parent(&GENESIS_HASH); + paths_to_genesis.sort(); + let mut expected_paths_to_genesis = vec![ + CHAIN_A.iter().rev().copied().collect::>(), + CHAIN_B.iter().rev().copied().collect::>(), + ]; + expected_paths_to_genesis.sort(); + assert_eq!(paths_to_genesis, expected_paths_to_genesis); + + let path_to_leaf_in_a = view.paths_to_relay_parent(&CHAIN_A[1]); + let expected_path_to_leaf_in_a = + vec![CHAIN_A[2..].iter().rev().copied().collect::>()]; + assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a); + + let path_to_leaf_in_b = view.paths_to_relay_parent(&CHAIN_B[4]); + let expected_path_to_leaf_in_b = + vec![CHAIN_B[5..].iter().rev().copied().collect::>()]; + assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b); + + assert_eq!(view.paths_to_relay_parent(&Hash::repeat_byte(0x0A)), Vec::>::new()); + } } From 94f573a4a392818d7bcce26ae230d2738196fa5e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 10:42:45 +0200 Subject: [PATCH 091/138] Simplifications --- .../src/validator_side/mod.rs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index b2a670f543c8..98b4c8b17a60 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -167,8 +167,7 @@ impl PeerData { let old_view = std::mem::replace(&mut self.view, new_view); if let PeerState::Collating(ref mut peer_state) = self.state { for removed in old_view.difference(&self.view) { - // Remove relay parent advertisements if it went out - // of our (implicit) view. + // Remove relay parent advertisements if it went out of our (implicit) view. let keep = is_relay_parent_in_implicit_view( removed, implicit_view, @@ -1039,8 +1038,7 @@ fn ensure_seconding_limit_is_respected( &relay_parent, ¶_id, assignment.current.len(), - ) - .map_err(|_| AdvertisementError::RelayParentUnknown)?; + ); gum::trace!( target: LOG_TARGET, @@ -2133,7 +2131,7 @@ fn seconded_and_pending_for_para_above( relay_parent: &Hash, para_id: &ParaId, claim_queue_len: usize, -) -> Result> { +) -> Vec { let outer_paths = implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; for path in outer_paths { @@ -2146,7 +2144,7 @@ fn seconded_and_pending_for_para_above( result.push(r); } - Ok(result) + result } // Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the // order in the claim queue so the earlier an element is located in the `Vec` the higher its @@ -2174,7 +2172,7 @@ fn unfulfilled_claim_queue_entries( relay_parent, para_id, relay_parent_state.assignment.current.len(), - )? + ) .into_iter() .max() .unwrap_or(0); @@ -2204,16 +2202,13 @@ fn get_next_collation_to_fetch( relay_parent: Hash, state: &mut State, ) -> Option<(PendingCollation, CollatorId)> { - let maybe_unfulfilled_entries = unfulfilled_claim_queue_entries( + let unfulfilled_entries = match unfulfilled_claim_queue_entries( &relay_parent, &state.per_relay_parent, &state.implicit_view, - ); - - let unfulfilled_entries = match maybe_unfulfilled_entries { + ) { Ok(entries) => entries, Err(err) => { - // This should never happen gum::error!( target: LOG_TARGET, ?relay_parent, @@ -2223,7 +2218,17 @@ fn get_next_collation_to_fetch( return None }, }; - let rp_state = state.per_relay_parent.get_mut(&relay_parent)?; + let rp_state = match state.per_relay_parent.get_mut(&relay_parent) { + Some(rp_state) => rp_state, + None => { + gum::error!( + target: LOG_TARGET, + ?relay_parent, + "Failed to get relay parent state" + ); + return None + }, + }; // If finished one does not match waiting_collation, then we already dequeued another fetch // to replace it. From 55e7fb2916ceb62b269888eaf45c23000b22a55c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 13:27:34 +0200 Subject: [PATCH 092/138] Resolve merge conflicts --- .../src/validator_side/mod.rs | 85 +++----- .../src/validator_side/tests/mod.rs | 188 +----------------- .../tests/prospective_parachains.rs | 89 +-------- 3 files changed, 46 insertions(+), 316 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 120c650d653e..fcb326de10f7 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -49,14 +49,14 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, - request_async_backing_params, request_claim_queue, request_session_index_for_child - runtime::{fetch_claim_queue, recv_runtime, request_node_features}, + request_async_backing_params, request_claim_queue, request_session_index_for_child, + runtime::{recv_runtime, request_node_features}, }; use polkadot_primitives::{ node_features, - vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion, CoreState}, - CandidateHash, CollatorId, CoreIndex, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, - PersistedValidationData, SessionIndex, AsyncBackingParams + vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion}, + AsyncBackingParams, CandidateHash, CollatorId, CoreIndex, Hash, HeadData, Id as ParaId, + OccupiedCoreAssumption, PersistedValidationData, SessionIndex, }; use crate::error::{Error, FetchError, Result, SecondingError}; @@ -349,13 +349,6 @@ struct PerRelayParent { session_index: SessionIndex, } -impl PerRelayParent { - fn new(assignments: GroupAssignments) -> Self { - let collations = Collations::new(&assignments.current); - Self { assignment: assignments, collations, ..Default::default() } - } -} - /// All state relevant for the validator side of the protocol lives here. #[derive(Default)] struct State { @@ -435,7 +428,6 @@ async fn construct_per_relay_parent( current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, v2_receipts: bool, session_index: SessionIndex, ) -> Result> @@ -468,22 +460,12 @@ where return Ok(None) }; - let claim_queue = request_claim_queue(relay_parent, sender) + let mut claim_queue = request_claim_queue(relay_parent, sender) .await .await .map_err(Error::CancelledClaimQueue)??; - let paras_now = cores - .get(core_now.0 as usize) - .and_then(|c| match (c, relay_parent_mode) { - (CoreState::Occupied(_), ProspectiveParachainsMode::Disabled) => None, - ( - CoreState::Occupied(_), - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, .. }, - ) => None, - _ => claim_queue.get(&core_now).cloned(), - }) - .unwrap_or_else(|| VecDeque::new()); + let assigned_paras = claim_queue.remove(&core_now).unwrap_or_else(|| VecDeque::new()); for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); @@ -498,10 +480,12 @@ where } } + let assignment = GroupAssignments { current: assigned_paras.into_iter().collect() }; + let collations = Collations::new(&assignment.current); + Ok(Some(PerRelayParent { - prospective_parachains_mode: relay_parent_mode, - assignment: GroupAssignments { current: paras_now.into_iter().collect() }, - collations: Collations::default(), + assignment, + collations, v2_receipts, session_index, current_core: core_now, @@ -1289,7 +1273,6 @@ where &mut state.current_assignments, keystore, *leaf, - mode, v2_receipts, session_index, ) @@ -1299,7 +1282,7 @@ where }; state.active_leaves.insert(*leaf, async_backing_params); - state.per_relay_parent.insert(*leaf, PerRelayParent::new(assignments)); + state.per_relay_parent.insert(*leaf, per_relay_parent); state .implicit_view @@ -1307,28 +1290,26 @@ where .await .map_err(Error::ImplicitViewFetchError)?; - // Order is always descending. - let allowed_ancestry = state - .implicit_view - .known_allowed_relay_parents_under(leaf, None) - .unwrap_or_default(); - for block_hash in allowed_ancestry { - if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - // Safe to use the same v2 receipts config for the allowed relay parents as well - // as the same session index since they must be in the same session. - if let Some(per_relay_parent) = construct_per_relay_parent( - sender, - &mut state.current_assignments, - keystore, - *block_hash, - mode, - v2_receipts, - session_index, - ) - .await? - { - entry.insert(per_relay_parent); - } + // Order is always descending. + let allowed_ancestry = state + .implicit_view + .known_allowed_relay_parents_under(leaf, None) + .unwrap_or_default(); + for block_hash in allowed_ancestry { + if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { + // Safe to use the same v2 receipts config for the allowed relay parents as well + // as the same session index since they must be in the same session. + if let Some(per_relay_parent) = construct_per_relay_parent( + sender, + &mut state.current_assignments, + keystore, + *block_hash, + v2_receipts, + session_index, + ) + .await? + { + entry.insert(per_relay_parent); } } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 47ab8c095c61..fcae2afa869c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -74,7 +74,8 @@ struct TestState { validator_groups: Vec>, group_rotation_info: GroupRotationInfo, cores: Vec, - claim_queue: BTreeMap>, + claim_queue: Option>>, + async_backing_params: AsyncBackingParams, node_features: NodeFeatures, session_index: SessionIndex, } @@ -136,7 +137,12 @@ impl Default for TestState { .collect(), ); claim_queue.insert(CoreIndex(1), VecDeque::new()); - claim_queue.insert(CoreIndex(2), [chain_ids[1]].into_iter().collect()); + claim_queue.insert( + CoreIndex(2), + iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) + .collect(), + ); let mut node_features = NodeFeatures::EMPTY; node_features.resize(node_features::FeatureIndex::CandidateReceiptV2 as usize + 1, false); @@ -150,7 +156,8 @@ impl Default for TestState { validator_groups, group_rotation_info, cores, - claim_queue, + claim_queue: Some(claim_queue), + async_backing_params: Self::ASYNC_BACKING_PARAMS, node_features, session_index: 1, } @@ -247,96 +254,6 @@ impl TestState { } } -impl TestState { - const CHAIN_IDS: [u32; 2] = [1, 2]; - const ASYNC_BACKING_PARAMS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - - fn with_shared_core() -> Self { - let mut state = Self::default(); - - let cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - }), - CoreState::Free, - ]; - - let mut claim_queue = BTreeMap::new(); - claim_queue.insert( - CoreIndex(0), - VecDeque::from_iter( - [ - ParaId::from(Self::CHAIN_IDS[1]), - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[0]), - ] - .into_iter(), - ), - ); - - assert!( - claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - ); - - state.cores = cores; - state.claim_queue = Some(claim_queue); - - state - } - - fn without_claim_queue() -> Self { - let mut state = Self::default(); - state.claim_queue = None; - state.cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - }), - CoreState::Free, - ]; - - state - } - - fn with_one_scheduled_para() -> Self { - let mut state = Self::default(); - - let cores = vec![CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - })]; - - let validator_groups = vec![vec![ValidatorIndex(0), ValidatorIndex(1)]]; - - let mut claim_queue = BTreeMap::new(); - claim_queue.insert( - CoreIndex(0), - VecDeque::from_iter( - [ - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[0]), - ParaId::from(Self::CHAIN_IDS[0]), - ] - .into_iter(), - ), - ); - - assert!( - claim_queue.get(&CoreIndex(0)).unwrap().len() == - Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize - ); - - state.cores = cores; - state.validator_groups = validator_groups; - state.claim_queue = Some(claim_queue); - - state - } -} - type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; @@ -429,91 +346,6 @@ async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } -async fn respond_to_runtime_api_queries( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - hash: Hash, -) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::SessionIndexForChild(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.session_index)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::NodeFeatures(_, tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.node_features.clone())).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Validators(tx), - )) => { - let _ = tx.send(Ok(test_state.validator_public.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidatorGroups(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(( - test_state.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AvailabilityCores(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.cores.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ClaimQueue(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.claim_queue.clone())); - } - ); -} - /// Assert that the next message is a `CandidateBacking(Second())`. async fn assert_candidate_backing_second( virtual_overseer: &mut VirtualOverseer, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 69e24f99ada7..ba1f21182cd9 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -21,7 +21,7 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2}, - AsyncBackingParams, BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, + BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, }; use polkadot_primitives_test_helpers::dummy_committed_candidate_receipt_v2; use rstest::rstest; @@ -77,7 +77,7 @@ async fn assert_construct_per_relay_parent( parent, RuntimeApiRequest::ClaimQueue(tx), )) if parent == hash => { - let _ = tx.send(Ok(test_state.claim_queue.clone())); + let _ = tx.send(Ok(test_state.claim_queue.clone().unwrap())); //todo } ); } @@ -123,7 +123,7 @@ pub(super) async fn update_view( _, RuntimeApiRequest::AsyncBackingParams(tx), )) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); + tx.send(Ok(test_state.async_backing_params)).unwrap(); } ); @@ -1811,89 +1811,6 @@ fn fair_collation_fetches() { }); } -// This should not happen in practice since claim queue is supported on all networks but just in -// case validate that the fallback works as expected -#[test] -fn collation_fetches_without_claim_queue() { - let test_state = TestState::without_claim_queue(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, keystore } = test_harness; - - let head_b = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - let head_b_num: u32 = 2; - - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; - - let peer_a = PeerId::random(); - let pair_a = CollatorPair::generate().0; - - connect_and_declare_collator( - &mut virtual_overseer, - peer_a, - pair_a.clone(), - test_state.chain_ids[0], - CollationVersion::V2, - ) - .await; - - let peer_b = PeerId::random(); - let pair_b = CollatorPair::generate().0; - - // connect an unneeded collator - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair_b.clone(), - test_state.chain_ids[1], - CollationVersion::V2, - ) - .await; - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, _)), - ) => { - assert_eq!(peer_id, peer_b); - } - ); - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::DisconnectPeer(peer_id, peer_set) - ) => { - assert_eq!(peer_id, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - - // in fallback mode we only accept what's scheduled on the core - submit_second_and_assert( - &mut virtual_overseer, - keystore.clone(), - ParaId::from(test_state.chain_ids[0]), - head_b, - peer_a, - HeadData(vec![0u8]), - ) - .await; - - // `peer_a` sends another advertisement and it is ignored - let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); - advertise_collation( - &mut virtual_overseer, - peer_a, - head_b, - Some((candidate_hash, Hash::zero())), - ) - .await; - test_helpers::Yield::new().await; - assert_matches!(virtual_overseer.recv().now_or_never(), None); - - virtual_overseer - }); -} - #[test] fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let test_state = TestState::with_shared_core(); From e27ddd42513e320ef85c6e6cf69939f4cb0bd636 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 13:32:26 +0200 Subject: [PATCH 093/138] Fix todos --- .../src/validator_side/tests/mod.rs | 22 ++++--------------- .../tests/prospective_parachains.rs | 4 ++-- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index fcae2afa869c..3063758010d6 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -74,7 +74,7 @@ struct TestState { validator_groups: Vec>, group_rotation_info: GroupRotationInfo, cores: Vec, - claim_queue: Option>>, + claim_queue: BTreeMap>, async_backing_params: AsyncBackingParams, node_features: NodeFeatures, session_index: SessionIndex, @@ -156,7 +156,7 @@ impl Default for TestState { validator_groups, group_rotation_info, cores, - claim_queue: Some(claim_queue), + claim_queue, async_backing_params: Self::ASYNC_BACKING_PARAMS, node_features, session_index: 1, @@ -199,21 +199,7 @@ impl TestState { ); state.cores = cores; - state.claim_queue = Some(claim_queue); - - state - } - - fn without_claim_queue() -> Self { - let mut state = Self::default(); - state.claim_queue = None; - state.cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - }), - CoreState::Free, - ]; + state.claim_queue = claim_queue; state } @@ -248,7 +234,7 @@ impl TestState { state.cores = cores; state.validator_groups = validator_groups; - state.claim_queue = Some(claim_queue); + state.claim_queue = claim_queue; state } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index ba1f21182cd9..7057b075aa2a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -77,7 +77,7 @@ async fn assert_construct_per_relay_parent( parent, RuntimeApiRequest::ClaimQueue(tx), )) if parent == hash => { - let _ = tx.send(Ok(test_state.claim_queue.clone().unwrap())); //todo + let _ = tx.send(Ok(test_state.claim_queue.clone())); } ); } @@ -2130,7 +2130,7 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { .into_iter(), ), ); - test_state.claim_queue = Some(claim_queue); + test_state.claim_queue = claim_queue; test_state.async_backing_params.max_candidate_depth = 3; test_state.async_backing_params.allowed_ancestry_len = 2; From a10c0c15cd3d2f7eabd8df8ae25aa337a01521eb Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 13:53:04 +0200 Subject: [PATCH 094/138] Comment --- .../node/network/collator-protocol/src/validator_side/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index fcb326de10f7..f177521b26fd 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1170,8 +1170,8 @@ where Ok(()) } -/// Enqueue collation for fetching. The advertisement is expected to be -/// validated and the seconding limit checked. +/// Enqueue collation for fetching. The advertisement is expected to be validated and the seconding +/// limit checked. async fn enqueue_collation( sender: &mut Sender, state: &mut State, From ee11c6ae3a05446650283cf36f501f938316f5f4 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 8 Nov 2024 18:43:06 +0200 Subject: [PATCH 095/138] Remove unneeded log line --- .../network/collator-protocol/src/validator_side/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index f177521b26fd..4b2cc850cf22 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2120,14 +2120,6 @@ fn seconded_and_pending_for_para_below( implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) .map(|ancestors| { - gum::trace!( - target: LOG_TARGET, - ?ancestors, - ?relay_parent, - ?para_id, - claim_queue_len, - "seconded_and_pending_for_para_below" - ); ancestors.iter().take(claim_queue_len).fold(0, |res, anc| { res + per_relay_parent .get(&anc) From f8e3f527b7fdcc41cd1b51aa4419cf177f732195 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 12 Nov 2024 10:22:36 +0200 Subject: [PATCH 096/138] Update ZN tests and add some additional logs --- .../node/network/collator-protocol/src/validator_side/mod.rs | 2 ++ .../0002-elastic-scaling-doesnt-break-parachains.toml | 2 +- .../0002-elastic-scaling-doesnt-break-parachains.zndsl | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 4b2cc850cf22..b3c6a24e8d11 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1038,6 +1038,7 @@ fn ensure_seconding_limit_is_respected( claims_for_para, seconded_and_pending_below, ?seconded_and_pending_above, + claim_queue = ?assignment.current, "Checking if seconded limit is reached" ); @@ -1065,6 +1066,7 @@ fn ensure_seconding_limit_is_respected( ?relay_parent, ?para_id, claims_for_para, + seconded_and_pending_below, claims_at_path, "Seconding limit exceeded" ); diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml index 9b3576eaa3c2..046d707cc1e8 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml @@ -37,4 +37,4 @@ onboard_as_parachain = false [parachains.collator] name = "collator2000" command = "polkadot-parachain" - args = [ "-lparachain=debug" ] + args = [ "-lparachain=debug", "--experimental-use-slot-based" ] diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl index 7ba896e1c903..0cfc29f532d1 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl @@ -12,7 +12,7 @@ validator: parachain 2000 block height is at least 10 within 200 seconds # Register the second core assigned to this parachain. alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds +alice: js-script ./assign-core.js with "1,2000,57600" return is 0 within 600 seconds validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds From 1c0f93a4a8eae4435bdc6afc8aed3590ffc80b45 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 12 Nov 2024 13:54:08 +0200 Subject: [PATCH 097/138] Remove `CollationStatus::WaitingOnValidation` and fix pending collations counting --- .../src/validator_side/collation.rs | 33 +---- .../src/validator_side/mod.rs | 132 ++++++++++++------ 2 files changed, 93 insertions(+), 72 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index e9fc367deb9b..f6eda463205f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -196,8 +196,6 @@ pub enum CollationStatus { Waiting, /// We are currently fetching a collation for the specified `ParaId`. Fetching(ParaId), - /// We are waiting that a collation is being validated for the specified `ParaId`. - WaitingOnValidation(ParaId), } impl Default for CollationStatus { @@ -296,36 +294,11 @@ impl Collations { None } - // Returns `true` if there is a pending collation for the specified `ParaId`. - fn is_pending_for_para(&self, para_id: &ParaId) -> bool { - match self.status { - CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => true, - CollationStatus::WaitingOnValidation(pending_para_id) - if pending_para_id == *para_id => - true, - _ => false, - } - } - - // Returns the number of seconded and likely soon to be seconded collations for the specified - // `ParaId`. - pub(super) fn seconded_and_pending_for_para(&self, para_id: &ParaId) -> usize { - let seconded_for_para = self - .candidates_state + pub(super) fn seconded_for_para(&self, para_id: &ParaId) -> usize { + self.candidates_state .get(¶_id) .map(|state| state.seconded_per_para) - .unwrap_or_default(); - let pending_for_para = self.is_pending_for_para(para_id) as usize; - - gum::trace!( - target: LOG_TARGET, - ?para_id, - seconded_for_para, - pending_for_para, - "Seconded and pending for para." - ); - - seconded_for_para + pending_for_para + .unwrap_or_default() } // Returns the number of claims in the claim queue for the specified `ParaId`. diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index b3c6a24e8d11..fbcea18b779e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -409,6 +409,57 @@ struct State { reputation: ReputationAggregator, } +impl State { + // Returns the number of seconded and pending collations for a specific `ParaId`. Pending + // collations are: + // 1. Collations being fetched from a collator. + // 2. Collations waiting for validation from backing subsystem. + // 3. Collations blocked from seconding due to parent not being known by backing subsystem. + fn seconded_and_pending_for_para(&self, relay_parent: &Hash, para_id: &ParaId) -> usize { + let seconded = self + .per_relay_parent + .get(relay_parent) + .map_or(0, |per_relay_parent| per_relay_parent.collations.seconded_for_para(para_id)); + + let pending_fetch = self.per_relay_parent.get(relay_parent).map_or(0, |rp_state| { + match rp_state.collations.status { + CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => 1, + _ => 0, + } + }); + + let waiting_for_validation = self + .fetched_candidates + .keys() + .filter(|fc| fc.relay_parent == *relay_parent && fc.para_id == *para_id) + .count(); + + let blocked_from_seconding = + self.blocked_from_seconding.values().fold(0, |acc, blocked_collations| { + acc + blocked_collations + .iter() + .filter(|pc| { + pc.candidate_receipt.descriptor.para_id() == *para_id && + pc.candidate_receipt.descriptor.relay_parent() == *relay_parent + }) + .count() + }); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + seconded, + pending_fetch, + waiting_for_validation, + blocked_from_seconding, + "Seconded and pending collations for para", + ); + + seconded + pending_fetch + waiting_for_validation + blocked_from_seconding + } +} + fn is_relay_parent_in_implicit_view( relay_parent: &Hash, implicit_view: &ImplicitView, @@ -1014,8 +1065,7 @@ fn ensure_seconding_limit_is_respected( // Seconding and pending candidates below the relay parent of the candidate. These are // candidates which might have claimed slots at the current view of the claim queue. let seconded_and_pending_below = seconded_and_pending_for_para_below( - &state.implicit_view, - &state.per_relay_parent, + &state, &relay_parent, ¶_id, assignment.current.len(), @@ -1024,8 +1074,7 @@ fn ensure_seconding_limit_is_respected( // Seconding and pending candidates above the relay parent of the candidate. These are // candidates at a newer relay parent which have already claimed a slot within their view. let seconded_and_pending_above = seconded_and_pending_for_para_above( - &state.implicit_view, - &state.per_relay_parent, + &state, &relay_parent, ¶_id, assignment.current.len(), @@ -1219,7 +1268,7 @@ where PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); match collations.status { - CollationStatus::Fetching(_) | CollationStatus::WaitingOnValidation(_) => { + CollationStatus::Fetching(_) => { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1647,6 +1696,7 @@ async fn run_inner( disconnect_inactive_peers(ctx.sender(), &eviction_policy, &state.peer_data).await; }, resp = state.collation_requests.select_next_some() => { + let relay_parent = resp.0.pending_collation.relay_parent; let res = match handle_collation_fetch_response( &mut state, resp, @@ -1655,9 +1705,17 @@ async fn run_inner( ).await { Err(Some((peer_id, rep))) => { modify_reputation(&mut state.reputation, ctx.sender(), peer_id, rep).await; + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status = CollationStatus::Waiting; + }); continue }, Err(None) => { + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status = CollationStatus::Waiting; + }); continue }, Ok(res) => res @@ -1736,6 +1794,11 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { + // Reset the status before trying to fetch another collation + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status = CollationStatus::Waiting; + }); + while let Some((next, id)) = get_next_collation_to_fetch(&previous_fetch, relay_parent, state) { gum::debug!( target: LOG_TARGET, @@ -1920,8 +1983,6 @@ async fn kick_off_seconding( maybe_parent_head.and_then(|head| maybe_parent_head_hash.map(|hash| (head, hash))), )?; - let para_id = candidate_receipt.descriptor().para_id(); - ctx.send_message(CandidateBackingMessage::Second( relay_parent, candidate_receipt, @@ -1931,7 +1992,7 @@ async fn kick_off_seconding( .await; // There's always a single collation being fetched at any moment of time. // In case of a failure, we reset the status back to waiting. - collations.status = CollationStatus::WaitingOnValidation(para_id); + collations.status = CollationStatus::Waiting; entry.insert(collation_event); Ok(true) @@ -2111,43 +2172,38 @@ async fn handle_collation_fetch_response( // Returns how many seconded candidates and pending fetches are there within the view at a specific // relay parent fn seconded_and_pending_for_para_below( - implicit_view: &ImplicitView, - per_relay_parent: &HashMap, + state: &State, relay_parent: &Hash, para_id: &ParaId, claim_queue_len: usize, ) -> usize { // `known_allowed_relay_parents_under` returns all leaves within the view for the specified // block hash including the block hash itself. - implicit_view + state + .implicit_view .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) .map(|ancestors| { - ancestors.iter().take(claim_queue_len).fold(0, |res, anc| { - res + per_relay_parent - .get(&anc) - .map(|rp| rp.collations.seconded_and_pending_for_para(para_id)) - .unwrap_or(0) - }) + ancestors + .iter() + .take(claim_queue_len) + .fold(0, |res, anc| res + state.seconded_and_pending_for_para(anc, para_id)) }) .unwrap_or(0) } fn seconded_and_pending_for_para_above( - implicit_view: &ImplicitView, - per_relay_parent: &HashMap, + state: &State, relay_parent: &Hash, para_id: &ParaId, claim_queue_len: usize, ) -> Vec { - let outer_paths = implicit_view.paths_to_relay_parent(relay_parent); + let outer_paths = state.implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; for path in outer_paths { - let r = path.iter().take(claim_queue_len).fold(0, |res, anc| { - res + per_relay_parent - .get(&anc) - .map(|rp| rp.collations.seconded_and_pending_for_para(para_id)) - .unwrap_or(0) - }); + let r = path + .iter() + .take(claim_queue_len) + .fold(0, |res, anc| res + state.seconded_and_pending_for_para(&anc, para_id)); result.push(r); } @@ -2156,26 +2212,22 @@ fn seconded_and_pending_for_para_above( // Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the // order in the claim queue so the earlier an element is located in the `Vec` the higher its // priority is. -fn unfulfilled_claim_queue_entries( - relay_parent: &Hash, - per_relay_parent: &HashMap, - implicit_view: &ImplicitView, -) -> Result> { - let relay_parent_state = - per_relay_parent.get(relay_parent).ok_or(Error::RelayParentStateNotFound)?; +fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result> { + let relay_parent_state = state + .per_relay_parent + .get(relay_parent) + .ok_or(Error::RelayParentStateNotFound)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); let mut claims_per_para = HashMap::new(); for para_id in scheduled_paras { let below = seconded_and_pending_for_para_below( - implicit_view, - per_relay_parent, + state, relay_parent, para_id, relay_parent_state.assignment.current.len(), ); let above = seconded_and_pending_for_para_above( - implicit_view, - per_relay_parent, + state, relay_parent, para_id, relay_parent_state.assignment.current.len(), @@ -2209,11 +2261,7 @@ fn get_next_collation_to_fetch( relay_parent: Hash, state: &mut State, ) -> Option<(PendingCollation, CollatorId)> { - let unfulfilled_entries = match unfulfilled_claim_queue_entries( - &relay_parent, - &state.per_relay_parent, - &state.implicit_view, - ) { + let unfulfilled_entries = match unfulfilled_claim_queue_entries(&relay_parent, &state) { Ok(entries) => entries, Err(err) => { gum::error!( From 67321cb7661c097006385857a1c16e9775d7528a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 12 Nov 2024 14:01:35 +0200 Subject: [PATCH 098/138] Use slot based collator for 0018-shared-core-idle-parachain --- .../functional/0018-shared-core-idle-parachain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml index 745c4f9e24b1..d3ff00002242 100644 --- a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml +++ b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml @@ -36,4 +36,4 @@ chain = "glutton-westend-local-2000" name = "collator-2000" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug"] + args = ["-lparachain=debug", "--experimental-use-slot-based"] From 40cbf2fc6dae7dbe8d2c3f2475f94d86c92455fb Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 12 Nov 2024 15:19:51 +0200 Subject: [PATCH 099/138] Use slot based collator for 0019-coretime-collation-fetching fairness --- .../functional/0019-coretime-collation-fetching-fairness.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml index 63ff487a8694..faabacabd6ac 100644 --- a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -39,6 +39,6 @@ chain = "glutton-westend-local-{{id}}" name = "collator-{{id}}" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug"] + args = ["-lparachain=debug", "--experimental-use-slot-based"] {% endfor %} From b30c037a65dbf0234d6707beb0defcada8da5d94 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 12 Nov 2024 16:30:48 +0200 Subject: [PATCH 100/138] Remove log line assert from coretime-collation-fetching-fairness test --- .../functional/0019-coretime-collation-fetching-fairness.zndsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl index 7f3d157be415..8892b03ac29c 100644 --- a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl @@ -14,5 +14,3 @@ collator-2001: reports block height is at least 3 within 10 seconds # hardcoded check to verify that included onchain events are indeed 3:1 validator-0: js-script ./0019-verify-included-events.js return is 1 within 120 seconds -# could be flaky feel free to remove if it's causing problems -validator-2: count of log lines matching regex "Rejected v2 advertisement.*error=SecondedLimitReached" is greater than 1 within 10 seconds From 9fbed03ccad82776a2072f9a17a42b7e30a95733 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 14 Nov 2024 17:39:44 +0200 Subject: [PATCH 101/138] Update coretime-collation-fetching-fairness ZN test to simulate a collator disrespecting the claim queue limits --- ...-coretime-collation-fetching-fairness.toml | 24 +++++++++++++++---- .../functional/0019-verify-included-events.js | 4 +++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml index faabacabd6ac..59f567ca077e 100644 --- a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -23,22 +23,36 @@ command = "polkadot" args = ["-lparachain=debug,parachain::collator-protocol=trace" ] count = 4 -{% for id in range(2000,2002) %} [[parachains]] -id = {{id}} +id = 2000 register_para = false onboard_as_parachain = false add_to_genesis = false -chain = "glutton-westend-local-{{id}}" +chain = "glutton-westend-local-2000" [parachains.genesis.runtimeGenesis.patch.glutton] compute = "50000000" storage = "2500000000" trashDataCount = 5120 [parachains.collator] - name = "collator-{{id}}" + name = "collator-2000" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" args = ["-lparachain=debug", "--experimental-use-slot-based"] -{% endfor %} +[[parachains]] +id = 2001 +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-2001" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-2001" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0019-verify-included-events.js b/polkadot/zombienet_tests/functional/0019-verify-included-events.js index 372cd5e6ed5b..ad12ba47b03d 100644 --- a/polkadot/zombienet_tests/functional/0019-verify-included-events.js +++ b/polkadot/zombienet_tests/functional/0019-verify-included-events.js @@ -38,7 +38,9 @@ async function run(nodeName, networkInfo) { }); console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); - return (blocks_per_para[2000] > 7) && (blocks_per_para[2001] < 4); + // This check assumes that para 2000 runs slot based collator which respects its claim queue + // and para 2001 runs lookahead which generates blocks for each relay parent. + return (blocks_per_para[2000] >= 7) && (blocks_per_para[2001] <= 4); } module.exports = { run }; From b032c6696f0f4eadb2e49042972d9708e2c72a2b Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 14 Nov 2024 17:56:37 +0200 Subject: [PATCH 102/138] Remove `outer_leaves` from backing implicit view --- .../src/backing_implicit_view.rs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index f13ae866b82c..27ad4a253984 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -41,7 +41,6 @@ pub struct View { leaves: HashMap, block_info_storage: HashMap, collating_for: Option, - outer_leaves: HashSet, } impl View { @@ -50,12 +49,7 @@ impl View { /// relay parents of a single paraid. When this is true, prospective-parachains is no longer /// queried. pub fn new(collating_for: Option) -> Self { - Self { - leaves: Default::default(), - block_info_storage: Default::default(), - collating_for, - outer_leaves: Default::default(), - } + Self { leaves: Default::default(), block_info_storage: Default::default(), collating_for } } } @@ -263,8 +257,6 @@ impl View { parent_hash: leaf.parent_hash, }, ); - self.outer_leaves.remove(&leaf.parent_hash); // the parent is no longer an outer leaf - self.outer_leaves.insert(leaf.hash); // the new leaf is an outer leaf } /// Deactivate a leaf in the view. This prunes any outdated implicit ancestors as well. @@ -293,10 +285,6 @@ impl View { keep }); - for r in &removed { - self.outer_leaves.remove(r); - } - removed } } @@ -341,13 +329,13 @@ impl View { .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } - /// Returns all paths from each outer leaf to `relay_parent`. If no paths exist the function + /// Returns all paths from a leaf to `relay_parent`. If no paths exist the function /// will return an empty `Vec`. /// /// The input is not included in the path so if `relay_parent` happens to be an outer leaf, no /// paths will be returned. pub fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Vec> { - if self.outer_leaves.is_empty() { + if self.leaves.is_empty() { // No outer leaves so the view should be empty. Don't return any paths. return vec![] }; @@ -359,9 +347,9 @@ impl View { // Find all paths from each outer leaf to `relay_parent`. let mut paths = Vec::new(); - for outer_leaf in &self.outer_leaves { + for (leaf, _) in &self.leaves { let mut path = Vec::new(); - let mut current_leaf = *outer_leaf; + let mut current_leaf = *leaf; let mut visited = HashSet::new(); loop { @@ -525,8 +513,6 @@ impl View { }; self.block_info_storage.insert(leaf_hash, leaf_block_info); - self.outer_leaves.remove(&leaf_header.parent_hash); // the parent is no longer an outer leaf - self.outer_leaves.insert(leaf_hash); // the new leaf is an outer leaf Ok(fetched_ancestry) } @@ -880,8 +866,8 @@ mod tests { assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); - assert_eq!(view.outer_leaves.len(), 1); - assert!(view.outer_leaves.contains(leaf)); + assert_eq!(view.leaves.len(), 1); + assert!(view.leaves.contains_key(leaf)); assert!(view.paths_to_relay_parent(&CHAIN_B[0]).is_empty()); assert_eq!( view.paths_to_relay_parent(&CHAIN_B[min_min_idx]), From eb0ca120671fe7af4d50e863f7d4ac0f553a459d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 14 Nov 2024 18:24:11 +0200 Subject: [PATCH 103/138] Restore `WaitingOnValidation` and `back_to_waiting` --- .../src/validator_side/collation.rs | 17 ++++++++++++++++- .../collator-protocol/src/validator_side/mod.rs | 16 +++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index f6eda463205f..8fe27100abf5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -196,6 +196,8 @@ pub enum CollationStatus { Waiting, /// We are currently fetching a collation for the specified `ParaId`. Fetching(ParaId), + /// We are waiting that a collation is being validated. + WaitingOnValidation, } impl Default for CollationStatus { @@ -204,6 +206,13 @@ impl Default for CollationStatus { } } +impl CollationStatus { + /// Downgrades to `Waiting` + pub fn back_to_waiting(&mut self) { + *self = Self::Waiting + } +} + /// The number of claims in the claim queue and seconded candidates count for a specific `ParaId`. #[derive(Default, Debug)] struct CandidatesStatePerPara { @@ -248,7 +257,13 @@ impl Collations { /// Note a seconded collation for a given para. pub(super) fn note_seconded(&mut self, para_id: ParaId) { self.candidates_state.entry(para_id).or_default().seconded_per_para += 1; - gum::trace!(target: LOG_TARGET, ?para_id, new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, "Note seconded."); + gum::trace!( + target: LOG_TARGET, + ?para_id, + new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, + "Note seconded." + ); + self.status.back_to_waiting(); } /// Adds a new collation to the waiting queue for the relay parent. This function doesn't diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index fbcea18b779e..3eebb2241101 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1268,7 +1268,7 @@ where PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); match collations.status { - CollationStatus::Fetching(_) => { + CollationStatus::Fetching(_) | CollationStatus::WaitingOnValidation => { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1566,7 +1566,6 @@ async fn process_msg( } if let Some(rp_state) = state.per_relay_parent.get_mut(&parent) { - rp_state.collations.status = CollationStatus::Waiting; rp_state.collations.note_seconded(para_id); } @@ -1707,14 +1706,14 @@ async fn run_inner( modify_reputation(&mut state.reputation, ctx.sender(), peer_id, rep).await; // Reset the status for the relay parent state.per_relay_parent.get_mut(&relay_parent).map(|rp| { - rp.collations.status = CollationStatus::Waiting; + rp.collations.status.back_to_waiting(); }); continue }, Err(None) => { // Reset the status for the relay parent state.per_relay_parent.get_mut(&relay_parent).map(|rp| { - rp.collations.status = CollationStatus::Waiting; + rp.collations.status.back_to_waiting(); }); continue }, @@ -1794,11 +1793,6 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { - // Reset the status before trying to fetch another collation - state.per_relay_parent.get_mut(&relay_parent).map(|rp| { - rp.collations.status = CollationStatus::Waiting; - }); - while let Some((next, id)) = get_next_collation_to_fetch(&previous_fetch, relay_parent, state) { gum::debug!( target: LOG_TARGET, @@ -1992,7 +1986,7 @@ async fn kick_off_seconding( .await; // There's always a single collation being fetched at any moment of time. // In case of a failure, we reset the status back to waiting. - collations.status = CollationStatus::Waiting; + collations.status = CollationStatus::WaitingOnValidation; entry.insert(collation_event); Ok(true) @@ -2301,7 +2295,7 @@ fn get_next_collation_to_fetch( return None } } - rp_state.collations.status = CollationStatus::Waiting; + rp_state.collations.status.back_to_waiting(); rp_state.collations.pick_a_collation_to_fetch(unfulfilled_entries) } From 00fafd400bb066e2a7ce2280d34a568c6c3ee154 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 09:33:51 +0200 Subject: [PATCH 104/138] Update polkadot/node/network/collator-protocol/src/validator_side/mod.rs Co-authored-by: Alin Dima --- .../node/network/collator-protocol/src/validator_side/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 3eebb2241101..c1247ef76d12 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -239,7 +239,7 @@ impl PeerData { let candidates = state.advertisements.entry(on_relay_parent).or_default(); // Current assignments is equal to the length of the claim queue. No honest - // collator should send that much advertisements. + // collator should send that many advertisements. if candidates.len() > per_relay_parent.assignment.current.len() { return Err(InsertAdvertisementError::PeerLimitReached) } From 61e3a80e2d3bf8b04a71b9883dd46f56f56dba2b Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 09:22:43 +0200 Subject: [PATCH 105/138] Don't fetch availability cores --- .../src/validator_side/mod.rs | 7 +- .../src/validator_side/tests/mod.rs | 1 + .../tests/prospective_parachains.rs | 105 ------------------ 3 files changed, 2 insertions(+), 111 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index c1247ef76d12..009c06b97031 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -496,16 +496,11 @@ where .await .map_err(Error::CancelledValidatorGroups)??; - let cores = polkadot_node_subsystem_util::request_availability_cores(relay_parent, sender) - .await - .await - .map_err(Error::CancelledAvailabilityCores)??; - let core_now = if let Some(group) = polkadot_node_subsystem_util::signing_key_and_index(&validators, keystore).and_then( |(_, index)| polkadot_node_subsystem_util::find_validator_group(&groups, index), ) { - rotation_info.core_for_group(group, cores.len()) + rotation_info.core_for_group(group, groups.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); return Ok(None) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 3063758010d6..c6897e7f574d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -192,6 +192,7 @@ impl TestState { .into_iter(), ), ); + state.validator_groups.truncate(1); assert!( claim_queue.get(&CoreIndex(0)).unwrap().len() == diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 7057b075aa2a..68621c2fa25a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -62,15 +62,6 @@ async fn assert_construct_per_relay_parent( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == hash => { - tx.send(Ok(test_state.cores.clone())).unwrap(); - } - ); - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( @@ -1613,102 +1604,6 @@ fn invalid_v2_descriptor() { }); } -#[test] -fn collations_outside_limits_are_not_fetched() { - let test_state = TestState::with_shared_core(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, keystore } = test_harness; - - let head_b = Hash::from_low_u64_be(128); - let head_b_num: u32 = 2; - - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; - - let peer_a = PeerId::random(); - let pair_a = CollatorPair::generate().0; - - connect_and_declare_collator( - &mut virtual_overseer, - peer_a, - pair_a.clone(), - test_state.chain_ids[0], - CollationVersion::V2, - ) - .await; - - let peer_b = PeerId::random(); - let pair_b = CollatorPair::generate().0; - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair_b.clone(), - test_state.chain_ids[1], - CollationVersion::V2, - ) - .await; - - submit_second_and_assert( - &mut virtual_overseer, - keystore.clone(), - ParaId::from(test_state.chain_ids[0]), - head_b, - peer_a, - HeadData(vec![1u8]), - ) - .await; - - submit_second_and_assert( - &mut virtual_overseer, - keystore.clone(), - ParaId::from(test_state.chain_ids[1]), - head_b, - peer_b, - HeadData(vec![2u8]), - ) - .await; - - submit_second_and_assert( - &mut virtual_overseer, - keystore.clone(), - ParaId::from(test_state.chain_ids[0]), - head_b, - peer_a, - HeadData(vec![3u8]), - ) - .await; - - // No more advertisements can be made for this relay parent. - - // verify for peer_a - let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); - advertise_collation( - &mut virtual_overseer, - peer_a, - head_b, - Some((candidate_hash, Hash::zero())), - ) - .await; - test_helpers::Yield::new().await; - assert_matches!(virtual_overseer.recv().now_or_never(), None); - - // verify for peer_b - let candidate_hash = CandidateHash(Hash::repeat_byte(0xBB)); - advertise_collation( - &mut virtual_overseer, - peer_b, - head_b, - Some((candidate_hash, Hash::zero())), - ) - .await; - test_helpers::Yield::new().await; - assert_matches!(virtual_overseer.recv().now_or_never(), None); - - virtual_overseer - }); -} - #[test] fn fair_collation_fetches() { let test_state = TestState::with_shared_core(); From 5b741d2ac4e75b6e43d9119b5633319779273dfa Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 09:33:05 +0200 Subject: [PATCH 106/138] Fix a log message and remove 'outer' from comments/variable names. --- .../collator-protocol/src/validator_side/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 009c06b97031..d36ba624c230 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1086,7 +1086,7 @@ fn ensure_seconding_limit_is_respected( "Checking if seconded limit is reached" ); - // Relay parent is an outer leaf. There are no paths to it. + // Relay parent is a leaf. There are no paths to it. if seconded_and_pending_above.is_empty() { if seconded_and_pending_below >= claims_for_para { gum::trace!( @@ -1094,7 +1094,8 @@ fn ensure_seconding_limit_is_respected( ?relay_parent, ?para_id, claims_for_para, - "Seconding limit exceeded for an outer leaf" + ?seconded_and_pending_above, + "Seconding limit exceeded for a leaf" ); return Err(AdvertisementError::SecondedLimitReached) } @@ -1102,7 +1103,7 @@ fn ensure_seconding_limit_is_respected( // Checks if another collation can be accepted. The number of collations that can be seconded // per parachain is limited by the entries in claim queue for the `ParaId` in question. - // No op for for outer leaves + // No op for for leaves for claims_at_path in seconded_and_pending_above { if claims_at_path + seconded_and_pending_below >= claims_for_para { gum::trace!( @@ -2186,9 +2187,9 @@ fn seconded_and_pending_for_para_above( para_id: &ParaId, claim_queue_len: usize, ) -> Vec { - let outer_paths = state.implicit_view.paths_to_relay_parent(relay_parent); + let leaf_paths = state.implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; - for path in outer_paths { + for path in leaf_paths { let r = path .iter() .take(claim_queue_len) From 56eac5d99d8b555dcaab05c26af48bc7bb09f8c3 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 10:04:27 +0200 Subject: [PATCH 107/138] Small refactor in `seconded_and_pending_for_para_below` and `seconded_and_pending_for_para_above` + comments --- .../network/collator-protocol/src/validator_side/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index d36ba624c230..0e3cf6e3296b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -362,8 +362,7 @@ struct State { /// ancestry of some active leaf, then it does support prospective parachains. implicit_view: ImplicitView, - /// All active leaves observed by us, including both that do and do not - /// support prospective parachains. This mapping works as a replacement for + /// All active leaves observed by us. This mapping works as a replacement for /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. active_leaves: HashMap, @@ -2176,7 +2175,8 @@ fn seconded_and_pending_for_para_below( ancestors .iter() .take(claim_queue_len) - .fold(0, |res, anc| res + state.seconded_and_pending_for_para(anc, para_id)) + .map(|anc| state.seconded_and_pending_for_para(anc, para_id)) + .sum() }) .unwrap_or(0) } @@ -2193,7 +2193,8 @@ fn seconded_and_pending_for_para_above( let r = path .iter() .take(claim_queue_len) - .fold(0, |res, anc| res + state.seconded_and_pending_for_para(&anc, para_id)); + .map(|anc| state.seconded_and_pending_for_para(anc, para_id)) + .sum(); result.push(r); } From 9607e1cce2f0dc3f37e912984e315ed0f045e896 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 10:13:06 +0200 Subject: [PATCH 108/138] Comments + test name --- .../collator-protocol/src/validator_side/tests/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index c6897e7f574d..70708f3d2eb2 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -611,11 +611,10 @@ fn collator_authentication_verification_works() { }); } -/// Tests that a validator fetches only one collation at any moment of time -/// per relay parent and ignores other advertisements once a candidate gets -/// seconded. +/// Tests that on a V1 Advertisement a validator fetches only one collation at any moment of time +/// per relay parent and ignores other V1 advertisements once a candidate gets seconded. #[test] -fn fetch_one_collation_at_a_time() { +fn fetch_one_collation_at_a_time_for_v1_advertisement() { let test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { From 02ed2ae21e455204e9d82a92002a2869a9b5f4af Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 10:54:55 +0200 Subject: [PATCH 109/138] Remove `activated` from `update_view` in tests --- .../src/validator_side/tests/mod.rs | 26 +++++---------- .../tests/prospective_parachains.rs | 33 +++++++++---------- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 70708f3d2eb2..0ad7aa36ef24 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -519,7 +519,7 @@ fn collator_reporting_works() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -625,7 +625,6 @@ fn fetch_one_collation_at_a_time_for_v1_advertisement() { &mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0), (second, 1)], - 2, ) .await; @@ -717,7 +716,6 @@ fn fetches_next_collation() { &mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0), (second, 1)], - 2, ) .await; @@ -834,8 +832,7 @@ fn reject_connection_to_next_group() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -879,7 +876,6 @@ fn fetch_next_collation_on_invalid_collation() { &mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0), (second, 1)], - 2, ) .await; @@ -980,8 +976,7 @@ fn inactive_disconnected() { let pair = CollatorPair::generate().0; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1027,7 +1022,6 @@ fn activity_extends_life() { &mut virtual_overseer, &test_state, vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], - 3, ) .await; @@ -1093,8 +1087,7 @@ fn disconnect_if_no_declare() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1124,8 +1117,7 @@ fn disconnect_if_wrong_declare() { let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; overseer_send( &mut virtual_overseer, @@ -1176,8 +1168,7 @@ fn delay_reputation_change() { let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; overseer_send( &mut virtual_overseer, @@ -1254,8 +1245,7 @@ fn view_change_clears_old_collators() { let peer = PeerId::random(); - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)], 1) - .await; + update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1268,7 +1258,7 @@ fn view_change_clears_old_collators() { test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - update_view(&mut virtual_overseer, &test_state, vec![], 0).await; + update_view(&mut virtual_overseer, &test_state, vec![]).await; assert_collator_disconnect(&mut virtual_overseer, peer).await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 68621c2fa25a..d051090dda1f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -78,7 +78,6 @@ pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, test_state: &TestState, new_view: Vec<(Hash, u32)>, // Hash and block number. - activated: u8, // How many new heads does this update contain? ) -> Option { let new_view: HashMap = HashMap::from_iter(new_view); @@ -91,7 +90,7 @@ pub(super) async fn update_view( .await; let mut next_overseer_message = None; - for _ in 0..activated { + for _ in 0..new_view.len() { let msg = match next_overseer_message.take() { Some(msg) => msg, None => overseer_recv(virtual_overseer).await, @@ -474,7 +473,7 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -553,7 +552,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -603,7 +602,7 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -706,7 +705,6 @@ fn second_multiple_candidates_per_relay_parent() { &mut virtual_overseer, &test_state, vec![(head_a, head_a_num), (head_b, head_b_num)], - 2, ) .await; @@ -796,7 +794,7 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -900,7 +898,7 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)]).await; let peer_a = PeerId::random(); @@ -1023,7 +1021,7 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1101,7 +1099,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1402,7 +1400,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1510,7 +1508,7 @@ fn invalid_v2_descriptor() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1614,7 +1612,7 @@ fn fair_collation_fetches() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let pair_a = CollatorPair::generate().0; @@ -1724,7 +1722,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 2; - update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1898,7 +1896,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_2, 2)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_2, 2)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1944,7 +1942,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // them fall in the same view. let relay_parent_4 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 2); - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_4, 4)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_4, 4)]).await; // One advertisement for `para_id_b` at `relay_parent_4` submit_second_and_assert( @@ -1996,7 +1994,7 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one // can be accepted let relay_parent_6 = Hash::from_low_u64_be(relay_parent_4.to_low_u64_be() - 2); - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_6, 6)], 1).await; + update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_6, 6)]).await; submit_second_and_assert( &mut virtual_overseer, @@ -2044,7 +2042,6 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { &mut virtual_overseer, &test_state, vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], - 3, ) .await; From fe7f9448ed0dbf9534192a7a7d870bb0255aa45c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 15 Nov 2024 15:11:44 +0200 Subject: [PATCH 110/138] Remove `cores` from `TestState` --- .../src/validator_side/tests/mod.rs | 54 ++----------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 0ad7aa36ef24..b0e531993142 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -41,14 +41,11 @@ use polkadot_node_subsystem::messages::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ - node_features, - vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore}, - AsyncBackingParams, CollatorPair, CoreIndex, GroupIndex, GroupRotationInfo, HeadData, - NodeFeatures, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, -}; -use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, + node_features, vstaging::CandidateReceiptV2 as CandidateReceipt, AsyncBackingParams, + CollatorPair, CoreIndex, GroupRotationInfo, HeadData, NodeFeatures, PersistedValidationData, + ValidatorId, ValidatorIndex, }; +use polkadot_primitives_test_helpers::{dummy_candidate_receipt_bad_sig, dummy_hash}; mod prospective_parachains; @@ -73,7 +70,6 @@ struct TestState { validator_public: Vec, validator_groups: Vec>, group_rotation_info: GroupRotationInfo, - cores: Vec, claim_queue: BTreeMap>, async_backing_params: AsyncBackingParams, node_features: NodeFeatures, @@ -103,32 +99,6 @@ impl Default for TestState { let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 1, now: 0 }; - let cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - }), - CoreState::Free, - CoreState::Occupied(OccupiedCore { - next_up_on_available: Some(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[1]), - collator: None, - }), - occupied_since: 0, - time_out_at: 1, - next_up_on_time_out: None, - availability: Default::default(), - group_responsible: GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: { - let mut d = dummy_candidate_descriptor(dummy_hash()); - d.para_id = ParaId::from(Self::CHAIN_IDS[1]); - - d.into() - }, - }), - ]; - let mut claim_queue = BTreeMap::new(); claim_queue.insert( CoreIndex(0), @@ -155,7 +125,6 @@ impl Default for TestState { validator_public, validator_groups, group_rotation_info, - cores, claim_queue, async_backing_params: Self::ASYNC_BACKING_PARAMS, node_features, @@ -172,14 +141,6 @@ impl TestState { fn with_shared_core() -> Self { let mut state = Self::default(); - let cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - }), - CoreState::Free, - ]; - let mut claim_queue = BTreeMap::new(); claim_queue.insert( CoreIndex(0), @@ -199,7 +160,6 @@ impl TestState { Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize ); - state.cores = cores; state.claim_queue = claim_queue; state @@ -208,11 +168,6 @@ impl TestState { fn with_one_scheduled_para() -> Self { let mut state = Self::default(); - let cores = vec![CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(Self::CHAIN_IDS[0]), - collator: None, - })]; - let validator_groups = vec![vec![ValidatorIndex(0), ValidatorIndex(1)]]; let mut claim_queue = BTreeMap::new(); @@ -233,7 +188,6 @@ impl TestState { Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize ); - state.cores = cores; state.validator_groups = validator_groups; state.claim_queue = claim_queue; From a31bf6f4d227f4fbce27a29bed9f9ef91c811582 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Sun, 17 Nov 2024 13:00:46 +0200 Subject: [PATCH 111/138] Fix `ensure_seconding_limit_is_respected` --- .../src/validator_side/mod.rs | 103 ++++++- .../src/validator_side/tests/mod.rs | 110 ++++---- .../tests/prospective_parachains.rs | 262 ++++++++++++++---- ...-coretime-collation-fetching-fairness.toml | 4 +- .../functional/0019-verify-included-events.js | 7 +- 5 files changed, 360 insertions(+), 126 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 0e3cf6e3296b..6ac63e531a1a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1054,16 +1054,13 @@ fn ensure_seconding_limit_is_respected( per_relay_parent: &PerRelayParent, assignment: &GroupAssignments, ) -> std::result::Result<(), AdvertisementError> { + // All claims we can accept for `para_id` at `relay_parent`. Some of this claims might already + // be claimed below or above `relay_parent`. let claims_for_para = per_relay_parent.collations.claims_for_para(¶_id); - // Seconding and pending candidates below the relay parent of the candidate. These are - // candidates which might have claimed slots at the current view of the claim queue. - let seconded_and_pending_below = seconded_and_pending_for_para_below( - &state, - &relay_parent, - ¶_id, - assignment.current.len(), - ); + // Claims from our view which are already claimed at previous relay parents. + let claimed_from_our_view = + claimed_within_view(&state, &relay_parent, ¶_id, assignment.current.len()); // Seconding and pending candidates above the relay parent of the candidate. These are // candidates at a newer relay parent which have already claimed a slot within their view. @@ -1079,7 +1076,7 @@ fn ensure_seconding_limit_is_respected( ?relay_parent, ?para_id, claims_for_para, - seconded_and_pending_below, + claimed_from_our_view, ?seconded_and_pending_above, claim_queue = ?assignment.current, "Checking if seconded limit is reached" @@ -1087,7 +1084,7 @@ fn ensure_seconding_limit_is_respected( // Relay parent is a leaf. There are no paths to it. if seconded_and_pending_above.is_empty() { - if seconded_and_pending_below >= claims_for_para { + if claimed_from_our_view >= claims_for_para { gum::trace!( target: LOG_TARGET, ?relay_parent, @@ -1104,14 +1101,14 @@ fn ensure_seconding_limit_is_respected( // per parachain is limited by the entries in claim queue for the `ParaId` in question. // No op for for leaves for claims_at_path in seconded_and_pending_above { - if claims_at_path + seconded_and_pending_below >= claims_for_para { + if claims_at_path + claimed_from_our_view >= claims_for_para { gum::trace!( target: LOG_TARGET, ?relay_parent, ?para_id, - claims_for_para, - seconded_and_pending_below, claims_at_path, + claimed_from_our_view, + claims_for_para, "Seconding limit exceeded" ); return Err(AdvertisementError::SecondedLimitReached) @@ -2181,6 +2178,86 @@ fn seconded_and_pending_for_para_below( .unwrap_or(0) } +// Iterates all relay parents under `relay_parent` within the view up to `claim_queue_len` and +// returns the number of claims for `para_id` which are affecting `relay_parent`. Affecting in this +// context means that a relay parent before `relay_parent` have claimed a slot from `relay_parent`'s +// view. +fn claimed_within_view( + state: &State, + relay_parent: &Hash, + para_id: &ParaId, + claim_queue_len: usize, +) -> usize { + // First we take all the ancestors of `relay_parent`. The order is `relay_parent` -> ancestor... + let ancestors = if let Some(anc) = state + .implicit_view + .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) + { + anc + } else { + return 0; + }; + + // We want to take the ancestors which fall in the claim queue view (hence the `take()`) and + // start counting claims from the oldest relay parent (hence the `rev()`). We start from oldest + // so that we can track claims from the ancestors. + // Example: + // CQ: [A, B, A] + // Relay parents: 1, 2, 3. + // We want to second a candidate for para A at relay parent 3. + // At relay parent 1 we have got two advertisements for A and one for B. + // At relay parent 2 we have got no advertisements. + // At relay parent 3 we CHECK if we can accept an advertisement for A. + // Visually: + // + // CQ: [A B A] CQ: [B A B] CQ: [A B A] + // + // ┌───────┐ ┌───────┐ ┌───────┐ + // │ RP1 ┼──────► RP2 ┼──────► RP3 │ + // └───────┘ └───────┘ └───────┘ + // A1 + // B1────────────► + // A2───────────────────────────► + // + // Since we have got three advertisements at RP1, A1 claims the slot at RP1, B1 - at RP2 and A2 + // - at RP3. This means that at RP2 we still can accept one more advertisement for B and one for + // A. + // How the counting must work for para id A. ACC here represents the number of claims + // transferred to the next relay parent which finally are transferred to the target relay + // parent: + // - at RP1 we have got two claims and para A is in the first position in the claim queue. ACC = + // 2 - 1 = 1. + // - at RP2 we have got no claims advertised. Para A is not on the first position of the claim + // queue, so A2 can't be scheduled here. ACC is still 1. + // - at RP 3 we reach our target relay parent with ACC = 1. We want to advertise at RP 3 so we + // add 1 to ACC and it becomes 2. IMPORTANT: since RP3 is our target relay parent we DON'T + // subtract 1 since this is the position the new advertisement will occupy. + // - ACC = 2 is the final result which represents the number of claims for para A at RP3. + // + // It's outside the scope of this function but in this case we CAN add an advertisement for para + // A at RP3 since the number of claims equals the number of entries for A in the claim queue. + + // We take all ancestors returned from `known_allowed_relay_parents_under` (remember they start + // with `relay_parent`), take the number of elements we are interested in (claim_queue_len) and + // then reverse them so that we start from the oldest one. + // At each ancestor we take the number of seconded and pending candidates at it, add it to the + // total number of claims and subtract 1 if `para_id` is at the first position of the claim + // queue for that ancestor. + ancestors.iter().take(claim_queue_len).rev().fold(0, |acc, ancestors| { + let seconded_and_pending = state.seconded_and_pending_for_para(ancestors, para_id); + let first_assignment = state + .per_relay_parent + .get(ancestors) + .map(|per_relay_parent| per_relay_parent.assignment.current.first()) + .flatten(); + match first_assignment { + Some(first) if first == para_id && ancestors != relay_parent => + (acc + seconded_and_pending).saturating_sub(1), + _ => acc + seconded_and_pending, + } + }) +} + fn seconded_and_pending_for_para_above( state: &State, relay_parent: &Hash, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index b0e531993142..2c87dcc236db 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -74,6 +74,8 @@ struct TestState { async_backing_params: AsyncBackingParams, node_features: NodeFeatures, session_index: SessionIndex, + // Used by `update_view` to keep track of latest requested ancestor + last_known_block: Option, } impl Default for TestState { @@ -129,6 +131,7 @@ impl Default for TestState { async_backing_params: Self::ASYNC_BACKING_PARAMS, node_features, session_index: 1, + last_known_block: None, } } } @@ -465,7 +468,7 @@ async fn advertise_collation( // Test that other subsystems may modify collators' reputations. #[test] fn collator_reporting_works() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -473,7 +476,7 @@ fn collator_reporting_works() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head, head_num)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -569,18 +572,14 @@ fn collator_authentication_verification_works() { /// per relay parent and ignores other V1 advertisements once a candidate gets seconded. #[test] fn fetch_one_collation_at_a_time_for_v1_advertisement() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0), (second, 1)], - ) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0), (second, 1)]) + .await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -603,8 +602,8 @@ fn fetch_one_collation_at_a_time_for_v1_advertisement() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, @@ -659,19 +658,14 @@ fn fetch_one_collation_at_a_time_for_v1_advertisement() { /// timeout and in case of an error. #[test] fn fetches_next_collation() { - let test_state = TestState::with_one_scheduled_para(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; + let first = test_state.relay_parent; let second = Hash::random(); - - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0), (second, 1)], - ) - .await; + update_view(&mut virtual_overseer, &mut test_state, vec![(first, 0), (second, 1)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -781,12 +775,13 @@ fn fetches_next_collation() { #[test] fn reject_connection_to_next_group() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -819,19 +814,14 @@ fn reject_connection_to_next_group() { // invalid. #[test] fn fetch_next_collation_on_invalid_collation() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; + let first = test_state.relay_parent; let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - - update_view( - &mut virtual_overseer, - &test_state, - vec![(test_state.relay_parent, 0), (second, 1)], - ) - .await; + update_view(&mut virtual_overseer, &mut test_state, vec![(first, 0), (second, 1)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -854,12 +844,12 @@ fn fetch_next_collation_on_invalid_collation() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, first, None).await; + advertise_collation(&mut virtual_overseer, peer_c, first, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + first, test_state.chain_ids[0], None, ) @@ -869,7 +859,7 @@ fn fetch_next_collation_on_invalid_collation() { let mut candidate_a = dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; + candidate_a.descriptor.relay_parent = first; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel .send(Ok(( @@ -884,7 +874,7 @@ fn fetch_next_collation_on_invalid_collation() { let receipt = assert_candidate_backing_second( &mut virtual_overseer, - test_state.relay_parent, + first, test_state.chain_ids[0], &pov, CollationVersion::V1, @@ -892,11 +882,8 @@ fn fetch_next_collation_on_invalid_collation() { .await; // Inform that the candidate was invalid. - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::Invalid(test_state.relay_parent, receipt), - ) - .await; + overseer_send(&mut virtual_overseer, CollatorProtocolMessage::Invalid(first, receipt)) + .await; assert_matches!( overseer_recv(&mut virtual_overseer).await, @@ -909,13 +896,8 @@ fn fetch_next_collation_on_invalid_collation() { ); // We should see a request for another collation. - assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - None, - ) - .await; + assert_fetch_collation_request(&mut virtual_overseer, first, test_state.chain_ids[0], None) + .await; virtual_overseer }); @@ -923,14 +905,15 @@ fn fetch_next_collation_on_invalid_collation() { #[test] fn inactive_disconnected() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -942,11 +925,11 @@ fn inactive_disconnected() { CollationVersion::V1, ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -961,7 +944,7 @@ fn inactive_disconnected() { #[test] fn activity_extends_life() { - let test_state = TestState::with_one_scheduled_para(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -974,7 +957,7 @@ fn activity_extends_life() { update_view( &mut virtual_overseer, - &test_state, + &mut test_state, vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], ) .await; @@ -1036,12 +1019,13 @@ fn activity_extends_life() { #[test] fn disconnect_if_no_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1064,14 +1048,15 @@ fn disconnect_if_no_declare() { #[test] fn disconnect_if_wrong_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; overseer_send( &mut virtual_overseer, @@ -1115,14 +1100,15 @@ fn disconnect_if_wrong_declare() { #[test] fn delay_reputation_change() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| false), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; let peer_b = PeerId::random(); - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; overseer_send( &mut virtual_overseer, @@ -1198,8 +1184,8 @@ fn view_change_clears_old_collators() { let pair = CollatorPair::generate().0; let peer = PeerId::random(); - - update_view(&mut virtual_overseer, &test_state, vec![(test_state.relay_parent, 0)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1212,7 +1198,7 @@ fn view_change_clears_old_collators() { test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - update_view(&mut virtual_overseer, &test_state, vec![]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![]).await; assert_collator_disconnect(&mut virtual_overseer, peer).await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index d051090dda1f..416e3e7edb9c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -45,7 +45,8 @@ async fn assert_construct_per_relay_parent( msg, AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) - ) if parent == hash => { + ) => { + assert_eq!(parent, hash); tx.send(Ok(test_state.validator_public.clone())).unwrap(); } ); @@ -68,6 +69,7 @@ async fn assert_construct_per_relay_parent( parent, RuntimeApiRequest::ClaimQueue(tx), )) if parent == hash => { + // println!("CECO: Claim queue requested for block hash: {:?}", hash); let _ = tx.send(Ok(test_state.claim_queue.clone())); } ); @@ -76,11 +78,11 @@ async fn assert_construct_per_relay_parent( /// Handle a view update. pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, + test_state: &mut TestState, new_view: Vec<(Hash, u32)>, // Hash and block number. ) -> Option { + let last_block_from_view = new_view.last().map(|t| t.1); let new_view: HashMap = HashMap::from_iter(new_view); - let our_view = OurView::new(new_view.keys().map(|hash| *hash), 0); overseer_send( @@ -150,6 +152,10 @@ pub(super) async fn update_view( { let mut ancestry_iter = ancestry_iter.clone(); while let Some((hash, number)) = ancestry_iter.next() { + if Some(number) == test_state.last_known_block { + break; + } + // May be `None` for the last element. let parent_hash = ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash)); @@ -197,6 +203,9 @@ pub(super) async fn update_view( // Skip the leaf. for (hash, number) in ancestry_iter.skip(1).take(requested_len.saturating_sub(1)) { + if Some(number) == test_state.last_known_block { + break; + } assert_construct_per_relay_parent( virtual_overseer, test_state, @@ -207,6 +216,9 @@ pub(super) async fn update_view( .await; } } + + test_state.last_known_block = last_block_from_view; + next_overseer_message } @@ -463,7 +475,7 @@ async fn send_collation_and_assert_processing( #[test] fn v1_advertisement_accepted_and_seconded() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -473,7 +485,7 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -542,7 +554,7 @@ fn v1_advertisement_accepted_and_seconded() { #[test] fn v1_advertisement_rejected_on_non_active_leaf() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -552,7 +564,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -584,7 +596,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { #[test] fn accept_advertisements_from_implicit_view() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -602,7 +614,7 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -687,7 +699,7 @@ fn accept_advertisements_from_implicit_view() { #[test] fn second_multiple_candidates_per_relay_parent() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -703,7 +715,7 @@ fn second_multiple_candidates_per_relay_parent() { // Activated leaf is `a` and `b`.The collation will be based on `b`. update_view( &mut virtual_overseer, - &test_state, + &mut test_state, vec![(head_a, head_a_num), (head_b, head_b_num)], ) .await; @@ -777,7 +789,7 @@ fn second_multiple_candidates_per_relay_parent() { #[test] fn fetched_collation_sanity_check() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -794,7 +806,7 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -888,7 +900,7 @@ fn fetched_collation_sanity_check() { #[test] fn sanity_check_invalid_parent_head_data() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -898,7 +910,7 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_c, head_c_num)]).await; let peer_a = PeerId::random(); @@ -1008,7 +1020,7 @@ fn sanity_check_invalid_parent_head_data() { #[test] fn advertisement_spam_protection() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -1021,7 +1033,7 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1082,7 +1094,7 @@ fn advertisement_spam_protection() { #[case(true)] #[case(false)] fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1099,7 +1111,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1400,7 +1412,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1498,7 +1510,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { #[test] fn invalid_v2_descriptor() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -1508,7 +1520,7 @@ fn invalid_v2_descriptor() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1604,7 +1616,7 @@ fn invalid_v2_descriptor() { #[test] fn fair_collation_fetches() { - let test_state = TestState::with_shared_core(); + let mut test_state = TestState::with_shared_core(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1612,7 +1624,7 @@ fn fair_collation_fetches() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let pair_a = CollatorPair::generate().0; @@ -1706,7 +1718,7 @@ fn fair_collation_fetches() { #[test] fn collation_fetching_prefer_entries_earlier_in_claim_queue() { - let test_state = TestState::with_shared_core(); + let mut test_state = TestState::with_shared_core(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1722,7 +1734,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { let head = Hash::from_low_u64_be(128); let head_num: u32 = 2; - update_view(&mut virtual_overseer, &test_state, vec![(head, head_num)]).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head, head_num)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1881,7 +1893,7 @@ fn collation_fetching_prefer_entries_earlier_in_claim_queue() { #[test] fn collation_fetching_considers_advertisements_from_the_whole_view() { - let test_state = TestState::with_shared_core(); + let mut test_state = TestState::with_shared_core(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1896,7 +1908,12 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_2, 2)]).await; + assert_eq!( + *test_state.claim_queue.get(&CoreIndex(0)).unwrap(), + VecDeque::from([para_id_b, para_id_a, para_id_a]) + ); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; connect_and_declare_collator( &mut virtual_overseer, @@ -1916,7 +1933,6 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { ) .await; - // Two advertisements for `para_id_a` at `relay_parent_2` submit_second_and_assert( &mut virtual_overseer, keystore.clone(), @@ -1930,43 +1946,48 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { submit_second_and_assert( &mut virtual_overseer, keystore.clone(), - para_id_a, + para_id_b, relay_parent_2, - collator_a, + collator_b, HeadData(vec![1u8]), ) .await; - // parent hashes are hardcoded in `get_parent_hash` (called from `update_view`) to be - // `current hash + 1` so we need to craft them carefully (decrement by 2) in order to make - // them fall in the same view. - let relay_parent_4 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 2); - - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_4, 4)]).await; + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; - // One advertisement for `para_id_b` at `relay_parent_4` submit_second_and_assert( &mut virtual_overseer, keystore.clone(), para_id_b, - relay_parent_4, + relay_parent_3, collator_b, HeadData(vec![3u8]), ) .await; + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_3, + collator_a, + HeadData(vec![3u8]), + ) + .await; // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` // must be ignored - // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored let (candidate_a, _) = - create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_4); + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_3); let parent_head_data_a = HeadData(vec![5u8]); advertise_collation( &mut virtual_overseer, collator_a, - relay_parent_4, + relay_parent_3, Some((candidate_a.hash(), parent_head_data_a.hash())), ) .await; @@ -1974,15 +1995,14 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { test_helpers::Yield::new().await; assert_matches!(virtual_overseer.recv().now_or_never(), None); - // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored let (candidate_b, _) = - create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_4); + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_3); let parent_head_data_b = HeadData(vec![6u8]); advertise_collation( &mut virtual_overseer, collator_b, - relay_parent_4, + relay_parent_3, Some((candidate_b.hash(), parent_head_data_b.hash())), ) .await; @@ -1993,8 +2013,8 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one // can be accepted - let relay_parent_6 = Hash::from_low_u64_be(relay_parent_4.to_low_u64_be() - 2); - update_view(&mut virtual_overseer, &test_state, vec![(relay_parent_6, 6)]).await; + let relay_parent_6 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 2); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_6, 6)]).await; submit_second_and_assert( &mut virtual_overseer, @@ -2010,6 +2030,152 @@ fn collation_fetching_considers_advertisements_from_the_whole_view() { }); } +#[test] +fn collation_fetching_fairness_handles_old_claims() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_2, + collator_b, + HeadData(vec![1u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_b, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; + + // nothing is advertised here + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + let relay_parent_4 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_4, 4)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_4, + collator_b, + HeadData(vec![3u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_4, + collator_a, + HeadData(vec![4u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_4); + let parent_head_data_a = HeadData(vec![5u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_4, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_4); + let parent_head_data_b = HeadData(vec![6u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_4, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + #[test] fn claim_queue_spot_claimed_at_next_relay_parent() { let mut test_state = TestState::with_one_scheduled_para(); @@ -2040,7 +2206,7 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { update_view( &mut virtual_overseer, - &test_state, + &mut test_state, vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], ) .await; diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml index 59f567ca077e..cdc932e047eb 100644 --- a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -8,7 +8,7 @@ timeout = 1000 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 4 num_cores = 1 - lookahead = 4 + lookahead = 2 [relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] needed_approvals = 3 @@ -38,7 +38,7 @@ chain = "glutton-westend-local-2000" name = "collator-2000" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug", "--experimental-use-slot-based"] + args = ["-lparachain=debug,parachain::collator-protocol=trace", "--experimental-use-slot-based"] [[parachains]] id = 2001 diff --git a/polkadot/zombienet_tests/functional/0019-verify-included-events.js b/polkadot/zombienet_tests/functional/0019-verify-included-events.js index ad12ba47b03d..6557a5a80e6b 100644 --- a/polkadot/zombienet_tests/functional/0019-verify-included-events.js +++ b/polkadot/zombienet_tests/functional/0019-verify-included-events.js @@ -40,7 +40,12 @@ async function run(nodeName, networkInfo) { console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); // This check assumes that para 2000 runs slot based collator which respects its claim queue // and para 2001 runs lookahead which generates blocks for each relay parent. - return (blocks_per_para[2000] >= 7) && (blocks_per_para[2001] <= 4); + // + // For 12 blocks there will be one session change. One block won't have anything backed/included. + // In the next there will be one backed so for 12 blocks we should expect 10 included events - no + // more than 4 for para 2001 and at least 6 for para 2000. This should also cover the unlucky + // case when we observe two session changes during the 12 block period. + return (blocks_per_para[2000] >= 6) && (blocks_per_para[2001] <= 4); } module.exports = { run }; From d251a1410d6be9e09ccc870e1fd894540c93d878 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 18 Nov 2024 15:24:38 +0200 Subject: [PATCH 112/138] newline --- .../functional/0019-coretime-collation-fetching-fairness.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml index cdc932e047eb..43f3ef8f9e55 100644 --- a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -55,4 +55,4 @@ chain = "glutton-westend-local-2001" name = "collator-2001" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug"] \ No newline at end of file + args = ["-lparachain=debug"] From 7c39070bd88d72f59cb6d3728cb26321fe8265d5 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 18 Nov 2024 17:48:55 +0200 Subject: [PATCH 113/138] clippy --- .../node/network/collator-protocol/src/validator_side/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 6ac63e531a1a..8fbff4b5b0f3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2248,8 +2248,7 @@ fn claimed_within_view( let first_assignment = state .per_relay_parent .get(ancestors) - .map(|per_relay_parent| per_relay_parent.assignment.current.first()) - .flatten(); + .and_then(|per_relay_parent| per_relay_parent.assignment.current.first()); match first_assignment { Some(first) if first == para_id && ancestors != relay_parent => (acc + seconded_and_pending).saturating_sub(1), From 5c3e52ae2c7e6705696d0c3dc337fc0cbb907223 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 18 Nov 2024 18:02:25 +0200 Subject: [PATCH 114/138] fmt --- polkadot/statement-table/src/generic.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index e3c470fcdeec..1e90338a0f18 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -245,8 +245,7 @@ impl CandidateData { pub fn attested( &self, validity_threshold: usize, - ) -> Option> - { + ) -> Option> { let valid_votes = self.validity_votes.len(); if valid_votes < validity_threshold { return None @@ -322,8 +321,7 @@ impl Table { digest: &Ctx::Digest, context: &Ctx, minimum_backing_votes: u32, - ) -> Option> - { + ) -> Option> { self.candidate_votes.get(digest).and_then(|data| { let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| { effective_minimum_backing_votes(len, minimum_backing_votes) From 2c9721ebc33b1af0a1f167c5c0402dd95eeb180e Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Mon, 18 Nov 2024 16:15:50 +0000 Subject: [PATCH 115/138] ".git/.scripts/commands/fmt/fmt.sh" --- polkadot/statement-table/src/generic.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 1e90338a0f18..e3c470fcdeec 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -245,7 +245,8 @@ impl CandidateData { pub fn attested( &self, validity_threshold: usize, - ) -> Option> { + ) -> Option> + { let valid_votes = self.validity_votes.len(); if valid_votes < validity_threshold { return None @@ -321,7 +322,8 @@ impl Table { digest: &Ctx::Digest, context: &Ctx, minimum_backing_votes: u32, - ) -> Option> { + ) -> Option> + { self.candidate_votes.get(digest).and_then(|data| { let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| { effective_minimum_backing_votes(len, minimum_backing_votes) From 46b4d9fd8ea2aac7eba85a5f583a55f62369f701 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 19 Nov 2024 10:28:36 +0200 Subject: [PATCH 116/138] Fix PRdoc --- prdoc/pr_4880.prdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc index e1042cd4dab2..8c3e520ee950 100644 --- a/prdoc/pr_4880.prdoc +++ b/prdoc/pr_4880.prdoc @@ -24,6 +24,6 @@ doc: crates: - name: "polkadot-collator-protocol" - bump: "minor" + bump: "patch" - name: "polkadot" - bump: "minor" + bump: "patch" From bf927d25294485a6a3675ef5ba5f5724dedfddb8 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 20 Nov 2024 12:08:27 +0200 Subject: [PATCH 117/138] Remove `seconded_and_pending_for_para_below` and fix `seconded_and_pending_for_para_above` --- .../src/validator_side/mod.rs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 8fbff4b5b0f3..d67c52e1824c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2155,29 +2155,6 @@ async fn handle_collation_fetch_response( result } -// Returns how many seconded candidates and pending fetches are there within the view at a specific -// relay parent -fn seconded_and_pending_for_para_below( - state: &State, - relay_parent: &Hash, - para_id: &ParaId, - claim_queue_len: usize, -) -> usize { - // `known_allowed_relay_parents_under` returns all leaves within the view for the specified - // block hash including the block hash itself. - state - .implicit_view - .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) - .map(|ancestors| { - ancestors - .iter() - .take(claim_queue_len) - .map(|anc| state.seconded_and_pending_for_para(anc, para_id)) - .sum() - }) - .unwrap_or(0) -} - // Iterates all relay parents under `relay_parent` within the view up to `claim_queue_len` and // returns the number of claims for `para_id` which are affecting `relay_parent`. Affecting in this // context means that a relay parent before `relay_parent` have claimed a slot from `relay_parent`'s @@ -2243,14 +2220,14 @@ fn claimed_within_view( // At each ancestor we take the number of seconded and pending candidates at it, add it to the // total number of claims and subtract 1 if `para_id` is at the first position of the claim // queue for that ancestor. - ancestors.iter().take(claim_queue_len).rev().fold(0, |acc, ancestors| { - let seconded_and_pending = state.seconded_and_pending_for_para(ancestors, para_id); + ancestors.iter().take(claim_queue_len).rev().fold(0, |acc, anc| { + let seconded_and_pending = state.seconded_and_pending_for_para(anc, para_id); let first_assignment = state .per_relay_parent - .get(ancestors) + .get(anc) .and_then(|per_relay_parent| per_relay_parent.assignment.current.first()); match first_assignment { - Some(first) if first == para_id && ancestors != relay_parent => + Some(first) if first == para_id && anc != relay_parent => (acc + seconded_and_pending).saturating_sub(1), _ => acc + seconded_and_pending, } @@ -2266,12 +2243,35 @@ fn seconded_and_pending_for_para_above( let leaf_paths = state.implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; for path in leaf_paths { - let r = path - .iter() - .take(claim_queue_len) - .map(|anc| state.seconded_and_pending_for_para(anc, para_id)) - .sum(); - result.push(r); + // Here we track how many first slots from the claim queue at the ancestors are actually + // claimed. This is the end result we care about. + let mut claimed_slots = 0; + // Here we track how many claims are transferred to the next ancestors in the path. We need + // this to decide if a next first slot is claimed or not. + // For example we have got a path rp1->rp2. Claim queue is [A,A] at each ancestor.At rp1 we + // have got 2 claims, at rp2 = 0. In this case we have got both first slots claimed (at rp1 + // and rp2) since the 2nd claim from rp1 is transferred to rp2. + let mut unfulfilled_claims = 0; + + for anc in path.iter().take(claim_queue_len).rev() { + // Anything seconded for `para_id` at the ancestor is added up to the claims. + unfulfilled_claims += state.seconded_and_pending_for_para(anc, para_id); + + let first_assignment_in_cq = state + .per_relay_parent + .get(anc) + .and_then(|per_relay_parent| per_relay_parent.assignment.current.first()); + + // If we have got unfulfilled claims and the first assignment in the claim queue is for + // `para_id` then we can assign a claim to the current ancestor. Mark the slot as + // claimed and decrease the number of unfulfilled claims. + if unfulfilled_claims > 0 && first_assignment_in_cq == Some(para_id) { + claimed_slots += 1; + unfulfilled_claims -= 1; + } + } + + result.push(claimed_slots); } result @@ -2287,7 +2287,7 @@ fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); let mut claims_per_para = HashMap::new(); for para_id in scheduled_paras { - let below = seconded_and_pending_for_para_below( + let below = claimed_within_view( state, relay_parent, para_id, From d05777b65da1bd06662a0f10623d5b6fe71e937a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 20 Nov 2024 12:15:15 +0200 Subject: [PATCH 118/138] Update comment --- .../src/validator_side/mod.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index d67c52e1824c..ff77749220bd 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2197,22 +2197,24 @@ fn claimed_within_view( // A2───────────────────────────► // // Since we have got three advertisements at RP1, A1 claims the slot at RP1, B1 - at RP2 and A2 - // - at RP3. This means that at RP2 we still can accept one more advertisement for B and one for - // A. - // How the counting must work for para id A. ACC here represents the number of claims - // transferred to the next relay parent which finally are transferred to the target relay - // parent: + // - at RP3. This means that at RP2 we still can accept one more advertisement for B and one + // more for A. + // + // How the counting must work for para id A. Let's say we want to second something for A at RP3 + // and we want to know how many slots are claimed by older relay parents. ACC represents the + // number of claims transferred to the next relay parent which finally are transferred to + // the target relay parent. We start from the oldest relay parent and go to the newest one: // - at RP1 we have got two claims and para A is in the first position in the claim queue. ACC = // 2 - 1 = 1. // - at RP2 we have got no claims advertised. Para A is not on the first position of the claim // queue, so A2 can't be scheduled here. ACC is still 1. // - at RP 3 we reach our target relay parent with ACC = 1. We want to advertise at RP 3 so we - // add 1 to ACC and it becomes 2. IMPORTANT: since RP3 is our target relay parent we DON'T - // subtract 1 since this is the position the new advertisement will occupy. - // - ACC = 2 is the final result which represents the number of claims for para A at RP3. - // - // It's outside the scope of this function but in this case we CAN add an advertisement for para - // A at RP3 since the number of claims equals the number of entries for A in the claim queue. + // add 1 to ACC and it remains 1. IMPORTANT: since RP3 is our target relay parent we DON'T + // subtract 1 because this is the position the new advertisement will occupy. + // - ACC = 1 is the final result which represents the number of claims for para A at RP3. This + // means that at RP3 the first slot of the claim queue (which is for A) is claimed by an older + // relay parent (RP1). We still can accept one advertisement for A at RP3 since we have got + // one more claim for A (the 3rd one) in th claim queue, // We take all ancestors returned from `known_allowed_relay_parents_under` (remember they start // with `relay_parent`), take the number of elements we are interested in (claim_queue_len) and From f83378363910d402165f0437fa7e83c5f284b6bf Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 20 Nov 2024 14:33:23 +0200 Subject: [PATCH 119/138] new test - `claims_above_are_counted_correctly` --- .../src/validator_side/mod.rs | 4 +- .../tests/prospective_parachains.rs | 134 +++++++++++++++++- 2 files changed, 131 insertions(+), 7 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index ff77749220bd..30e3d67d5f3f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2255,7 +2255,9 @@ fn seconded_and_pending_for_para_above( // and rp2) since the 2nd claim from rp1 is transferred to rp2. let mut unfulfilled_claims = 0; - for anc in path.iter().take(claim_queue_len).rev() { + // `claim_queue_len - 1` because the first element of the claim queue is the 'current' slot. + // Here we are interested only in the 'future' ones + for anc in path.iter().take(claim_queue_len - 1).rev() { // Anything seconded for `para_id` at the ancestor is added up to the claims. unfulfilled_claims += state.seconded_and_pending_for_para(anc, para_id); diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 416e3e7edb9c..bea2f610a704 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -2177,7 +2177,7 @@ fn collation_fetching_fairness_handles_old_claims() { } #[test] -fn claim_queue_spot_claimed_at_next_relay_parent() { +fn claims_below_are_counted_correctly() { let mut test_state = TestState::with_one_scheduled_para(); // Shorten the claim queue to make the test smaller @@ -2196,9 +2196,9 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - let hash_a = Hash::from_low_u64_be(12); - let hash_b = Hash::from_low_u64_be(11); - let hash_c = Hash::from_low_u64_be(10); + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); let pair_a = CollatorPair::generate().0; let collator_a = PeerId::random(); @@ -2249,12 +2249,134 @@ fn claim_queue_spot_claimed_at_next_relay_parent() { ParaId::from(test_state.chain_ids[0]), hash_c, collator_a, - HeadData(vec![0u8]), + HeadData(vec![2u8]), ) .await; // Collation at hash_b should be ignored because the claim queue is satisfied - advertise_collation(&mut virtual_overseer, collator_a, hash_b, None).await; + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claims_above_are_counted_correctly() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + // let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view( + &mut virtual_overseer, + &mut test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_b claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another collation at hash_b claims the spot at hash_c + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![1u8]), + ) + .await; + + // Collation at hash_a claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; test_helpers::Yield::new().await; assert_matches!(virtual_overseer.recv().now_or_never(), None); From 7c807e90b8e591eccc057af42d0850179685225b Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 20 Nov 2024 14:46:13 +0200 Subject: [PATCH 120/138] more tests --- .../tests/prospective_parachains.rs | 113 +++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index bea2f610a704..96ea3871dc0e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -2292,8 +2292,6 @@ fn claims_above_are_counted_correctly() { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; - // let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); @@ -2384,3 +2382,114 @@ fn claims_above_are_counted_correctly() { virtual_overseer }); } + +#[test] +fn claim_fills_last_free_slot() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view( + &mut virtual_overseer, + &mut test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims its spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + // Collation at hash_b claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![4u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} From 8f33ba06f15827bbedd305040edfc573f4a2107a Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 21 Nov 2024 09:47:42 +0200 Subject: [PATCH 121/138] comment --- .../src/validator_side/mod.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 30e3d67d5f3f..75eeea9cffe5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2244,6 +2244,84 @@ fn seconded_and_pending_for_para_above( ) -> Vec { let leaf_paths = state.implicit_view.paths_to_relay_parent(relay_parent); let mut result = vec![]; + + // When counting the number of seconded and pending candidates above the target relay parent we + // have to consider all forks. So first we take all paths from leaves to the target relay parent + // with `paths_to_relay_parent()`. Then for each path we calculate the number of secondend and + // pending candidates and return it as a result. All paths are returned and it's up to the + // caller to decide how to interpret them. + // + // For each path we keep track of two important values: + // - `claimed_slots` - this is the number of the slots in the path which are claimed by a relay + // parent in the path. For simplicity we don't care what's claimed by the target relay parent. + // It's up to the caller to handle this. This value is the end result we save for each path at + // the end of the path iteration. + // - `unfulfilled_claims` - this is the number of advertisements for relay parents in the path + // which we haven't mapped to actual claim queue slots (or relay parents) so far. This value + // is used as an intermediate running sum of the claims and we don't return it as a result. + // + // How these two values work together to count the number of pending and seconded candidates + // above the target relay parent? Let's see an example. Let's say that: + // - our target relay parent is RP0 (to avoid confusion it is not shown in the diagram below). + // - the length of the claim queue (and lookahead) is 3 + // - there are two parachains (A and B) sharing a core. So at each relay parent the claim queue + // is either [ABA] or [BAB]. + // - there are two advertisements for parachain A one for parachain B at RP1. They are A1, A2 + // and B2. + + // We start iterating each path from the oldest relay parent to the leaf. At each ancestor we: + // 1. Get the number of pending and seconded candidates with `seconded_and_pending_for_para()` + // and add it to `unfulfilled_claims`. These are the claims we have to map to the claim queue + // and which we have to transfer to the next ancestor from the path. + // 2. Get the first assignment in the claim queue. If it is for the target `para_id` - we + // subtract one from `unfulfilled_claims` because we have just mapped a claim to the current + // spot. At the same time we add 1 to the `claimed_slots` because the slot at the current + // ancestor is now claimed. + // 3. We repeat these steps until we reach the end leaf and return `claimed_slots`. + + // Now back to our example. We have got this path. At each relay parent we have the state of the + // claim queue and the advertisements: + // + // CQ: [A B A] CQ: [B A B] CQ: [A B A] + // + // ┌───────┐ ┌───────┐ ┌───────┐ + // │ RP1 ┼──────► RP2 ┼──────► RP3 │ + // └───────┘ └───────┘ └───────┘ + // A1 + // B1────────────► + // A2───────────────────────────► + // + // We start with `claimed_slots` and `unfulfilled_claims` both equal to 0. We will look at para + // A and B in parallel but in reality the coed is interested only in one of the paras.At RP1 we + // have got 2 claims for para A and 1 for para B. So `unfulfilled_claims` for para A will be 2 + // and for para B - 1. The first element in the claim queue is for para A so we subtract 1 from + // `unfulfilled_claims` for para A and add 1 to claimed_slots for para A. Bottom line at the end + // of RP1 processing we have got: + // - claimed_slots for para A = 1 + // - claimed_slots for para B = 0 + // - unfulfilled_claims for para A = 1 + // - unfulfilled_claims for para B = 1 + // + // We move on to RP2. Nothing is scheduled here so we don't add anything to `unfulfilled_claims` + // for either A or B. The first element for CQ is B so we subtract 1 from `unfulfilled_claims` + // for B and add 1 to claimed_slots for B. At the end of RP2 processing we have got: + // - claimed_slots for para A = 1 + // - claimed_slots for para B = 1 + // - unfulfilled_claims for para A = 1 + // - unfulfilled_claims for para B = 0 + // + // Now we are at RP3. Again nothing is scheduled here so `unfulfilled_claims` for both A and B + // is unchanged. The first element in the claim queue is A so we subtract 1 from + // `unfulfilled_claims` for A and add 1 to claimed_slots for A. No changes for B. At the end of + // RP3 we have got: + // - claimed_slots for para A = 2 + // - claimed_slots for para B = 1 + // - unfulfilled_claims for para A = 0 + // - unfulfilled_claims for para B = 0 + // Which is our end result too. Note that in this example `unfulfilled_claims` reached 0 for + // both paras but this is not necessary always the case. They might be bigger than zero if there + // are claims which will be fulfilled at future relay parents. But we don't care for this. + for path in leaf_paths { // Here we track how many first slots from the claim queue at the ancestors are actually // claimed. This is the end result we care about. From 1c9db106b131391d1d358d2e46e09ff39c339e4f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 22 Nov 2024 09:15:50 +0200 Subject: [PATCH 122/138] Fix path iteration in `seconded_and_pending_for_para_above` --- .../node/network/collator-protocol/src/validator_side/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 75eeea9cffe5..80466fe32b81 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2335,7 +2335,7 @@ fn seconded_and_pending_for_para_above( // `claim_queue_len - 1` because the first element of the claim queue is the 'current' slot. // Here we are interested only in the 'future' ones - for anc in path.iter().take(claim_queue_len - 1).rev() { + for anc in path.iter().rev().take(claim_queue_len - 1) { // Anything seconded for `para_id` at the ancestor is added up to the claims. unfulfilled_claims += state.seconded_and_pending_for_para(anc, para_id); From 439291af743bd1d47f72d5048891316fe79b6a02 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 25 Nov 2024 14:14:17 +0200 Subject: [PATCH 123/138] Remove a debug println --- .../src/validator_side/tests/prospective_parachains.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 96ea3871dc0e..ee52427e2486 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -69,7 +69,6 @@ async fn assert_construct_per_relay_parent( parent, RuntimeApiRequest::ClaimQueue(tx), )) if parent == hash => { - // println!("CECO: Claim queue requested for block hash: {:?}", hash); let _ = tx.send(Ok(test_state.claim_queue.clone())); } ); From 3f7691a92c6f583293c2764314e4773657620250 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 22 Nov 2024 09:16:51 +0200 Subject: [PATCH 124/138] Refactor claims counting: project claim queue state on top of relay parents --- .../src/validator_side/claim_queue_state.rs | 532 ++++++++++++++++++ .../src/validator_side/mod.rs | 90 +-- .../src/backing_implicit_view.rs | 7 +- 3 files changed, 563 insertions(+), 66 deletions(-) create mode 100644 polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs new file mode 100644 index 000000000000..a846c2eb40de --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -0,0 +1,532 @@ +use std::collections::VecDeque; + +use polkadot_primitives::{Hash, Id as ParaId}; + +#[derive(Debug, PartialEq)] +struct ClaimInfo { + hash: Option, + claim: Option, + claim_queue_len: usize, + claimed: bool, +} + +pub(crate) struct ClaimQueueState { + block_state: VecDeque, + future_blocks: VecDeque, +} + +impl ClaimQueueState { + pub(crate) fn new() -> Self { + Self { block_state: VecDeque::new(), future_blocks: VecDeque::new() } + } + + // Appends a new leaf + pub(crate) fn add_leaf(&mut self, hash: &Hash, claim_queue: &Vec) { + if self.block_state.iter().any(|s| s.hash == Some(*hash)) { + return + } + + // First check if our view for the future blocks is consistent with the one in the claim + // queue of the new block. If not - the claim queue has changed for some reason and we need + // to readjust our view. + for (idx, expected_claim) in claim_queue.iter().enumerate() { + match self.future_blocks.get_mut(idx) { + Some(future_block) => + if future_block.claim.as_ref() != Some(expected_claim) { + // There is an inconsistency. Update our view with the one from the claim + // queue. `claimed` can't be true anymore since the `ParaId` has changed. + future_block.claimed = false; + future_block.claim = Some(*expected_claim); + }, + None => { + self.future_blocks.push_back(ClaimInfo { + hash: None, + claim: Some(*expected_claim), + claim_queue_len: 1, + claimed: false, + }); + }, + } + } + + // Now pop the first future block and add it as a leaf + let claim_info = if let Some(new_leaf) = self.future_blocks.pop_front() { + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: new_leaf.claimed, + } + } else { + // maybe the claim queue was empty but we still need to add a leaf + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: false, + } + }; + + self.block_state.push_back(claim_info); + } + + fn get_window<'a>( + &'a mut self, + relay_parent: &'a Hash, + ) -> impl Iterator + 'a { + let mut window = self + .block_state + .iter_mut() + .skip_while(|b| b.hash != Some(*relay_parent)) + .peekable(); + let cq_len = window.peek().map_or(0, |b| b.claim_queue_len); + window.chain(self.future_blocks.iter_mut()).take(cq_len) + } + + pub(crate) fn claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + let window = self.get_window(relay_parent); + + for w in window { + if w.claimed { + continue + } + + if w.claim == Some(*para_id) { + w.claimed = true; + return true; + } + } + + false + } + + pub(crate) fn can_claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + let window = self.get_window(relay_parent); + + for w in window { + if !w.claimed && w.claim == Some(*para_id) { + return true + } + } + + false + } + + pub(crate) fn unclaimed_at(&mut self, relay_parent: &Hash) -> Vec { + let window = self.get_window(relay_parent); + + window.filter(|b| !b.claimed).filter_map(|b| b.claim.clone()).collect() + } +} + +mod test { + use super::*; + + #[test] + fn sane_initial_state() { + let mut state = ClaimQueueState::new(); + let relay_parent = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + + assert!(!state.can_claim_at(&relay_parent, ¶_id)); + assert!(!state.claim_at(&relay_parent, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent), vec![]); + } + + #[test] + fn add_leaf_works() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // should be no op + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.block_state.len(), 1); + assert_eq!(state.future_blocks.len(), 2); + + // add another leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + } + + #[test] + fn claims_at_separate_relay_parents_work() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let relay_parent_b = Hash::from_low_u64_be(2); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + state.add_leaf(&relay_parent_b, &claim_queue); + + // add one claim for a + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + // and one for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + // a should have one claim since the one for b was claimed + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + // and two more for b + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + } + + #[test] + fn claims_are_transferred_to_next_slot() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + // add two claims, 2nd should be transferred to a new leaf + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // one more + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + } + + #[test] + fn claims_are_transferred_to_new_leaves() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + for _ in 0..3 { + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + } + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // new leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // still no claims for a + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // but can accept for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + } + + #[test] + fn two_paras() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + } +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 80466fe32b81..40c686c281d9 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -63,8 +63,11 @@ use crate::error::{Error, FetchError, Result, SecondingError}; use self::collation::BlockedCollationId; +use self::claim_queue_state::ClaimQueueState; + use super::{modify_reputation, tick_stream, LOG_TARGET}; +mod claim_queue_state; mod collation; mod metrics; @@ -1051,66 +1054,33 @@ fn ensure_seconding_limit_is_respected( relay_parent: &Hash, para_id: ParaId, state: &State, - per_relay_parent: &PerRelayParent, - assignment: &GroupAssignments, ) -> std::result::Result<(), AdvertisementError> { - // All claims we can accept for `para_id` at `relay_parent`. Some of this claims might already - // be claimed below or above `relay_parent`. - let claims_for_para = per_relay_parent.collations.claims_for_para(¶_id); - - // Claims from our view which are already claimed at previous relay parents. - let claimed_from_our_view = - claimed_within_view(&state, &relay_parent, ¶_id, assignment.current.len()); - - // Seconding and pending candidates above the relay parent of the candidate. These are - // candidates at a newer relay parent which have already claimed a slot within their view. - let seconded_and_pending_above = seconded_and_pending_for_para_above( - &state, - &relay_parent, - ¶_id, - assignment.current.len(), - ); - - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - claims_for_para, - claimed_from_our_view, - ?seconded_and_pending_above, - claim_queue = ?assignment.current, - "Checking if seconded limit is reached" - ); - - // Relay parent is a leaf. There are no paths to it. - if seconded_and_pending_above.is_empty() { - if claimed_from_our_view >= claims_for_para { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - claims_for_para, - ?seconded_and_pending_above, - "Seconding limit exceeded for a leaf" + let target_anc = state + .implicit_view + .known_allowed_relay_parents_under(relay_parent, Some(para_id)) + .and_then(|res| res.first()) + .ok_or(AdvertisementError::RelayParentUnknown)?; + let paths = state.implicit_view.paths_to_relay_parent(target_anc); + + for path in paths { + let mut cq_state = ClaimQueueState::new(); + for ancestor in path { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(&ancestor) + .ok_or(AdvertisementError::RelayParentUnknown)? + .assignment + .current, ); - return Err(AdvertisementError::SecondedLimitReached) + for _ in 0..seconded_and_pending { + cq_state.claim_at(&ancestor, ¶_id); + } } - } - // Checks if another collation can be accepted. The number of collations that can be seconded - // per parachain is limited by the entries in claim queue for the `ParaId` in question. - // No op for for leaves - for claims_at_path in seconded_and_pending_above { - if claims_at_path + claimed_from_our_view >= claims_for_para { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - claims_at_path, - claimed_from_our_view, - claims_for_para, - "Seconding limit exceeded" - ); + if !cq_state.can_claim_at(relay_parent, ¶_id) { return Err(AdvertisementError::SecondedLimitReached) } } @@ -1162,13 +1132,7 @@ where ) .map_err(AdvertisementError::Invalid)?; - ensure_seconding_limit_is_respected( - &relay_parent, - para_id, - state, - per_relay_parent, - assignment, - )?; + ensure_seconding_limit_is_respected(&relay_parent, para_id, state)?; if let Some((candidate_hash, parent_head_data_hash)) = prospective_candidate { // Check if backing subsystem allows to second this candidate. diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 31b9eccaa3ae..fc7217281e22 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -345,6 +345,7 @@ impl View { return vec![] } + println!("DEBUG: leaves: {:?}", self.leaves); // Find all paths from each outer leaf to `relay_parent`. let mut paths = Vec::new(); for (leaf, _) in &self.leaves { @@ -353,16 +354,16 @@ impl View { let mut visited = HashSet::new(); loop { + path.push(current_leaf); + // If `relay_parent` is an outer leaf we'll immediately return an empty path (which // is the desired behaviour). - if current_leaf == *relay_parent { + if current_leaf == *relay_parent && !path.is_empty() { // path is complete paths.push(path); break } - path.push(current_leaf); - current_leaf = match self.block_info_storage.get(¤t_leaf) { Some(info) => { let r = info.parent_hash; From b1de6ba503abd86154b11d38dfd399515404c59f Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 25 Nov 2024 09:56:21 +0200 Subject: [PATCH 125/138] Remove unused code --- .../src/validator_side/claim_queue_state.rs | 1 + .../collator-protocol/src/validator_side/collation.rs | 8 -------- polkadot/node/subsystem-util/src/backing_implicit_view.rs | 7 +++---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index a846c2eb40de..5851c55e6b10 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -119,6 +119,7 @@ impl ClaimQueueState { } } +#[cfg(test)] mod test { use super::*; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 8fe27100abf5..a395257b2e27 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -315,14 +315,6 @@ impl Collations { .map(|state| state.seconded_per_para) .unwrap_or_default() } - - // Returns the number of claims in the claim queue for the specified `ParaId`. - pub(super) fn claims_for_para(&self, para_id: &ParaId) -> usize { - self.candidates_state - .get(para_id) - .map(|state| state.claims_per_para) - .unwrap_or_default() - } } // Any error that can occur when awaiting a collation fetch response. diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index fc7217281e22..31b9eccaa3ae 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -345,7 +345,6 @@ impl View { return vec![] } - println!("DEBUG: leaves: {:?}", self.leaves); // Find all paths from each outer leaf to `relay_parent`. let mut paths = Vec::new(); for (leaf, _) in &self.leaves { @@ -354,16 +353,16 @@ impl View { let mut visited = HashSet::new(); loop { - path.push(current_leaf); - // If `relay_parent` is an outer leaf we'll immediately return an empty path (which // is the desired behaviour). - if current_leaf == *relay_parent && !path.is_empty() { + if current_leaf == *relay_parent { // path is complete paths.push(path); break } + path.push(current_leaf); + current_leaf = match self.block_info_storage.get(¤t_leaf) { Some(info) => { let r = info.parent_hash; From 48d3f5cc8dcce8faa0e7ccc01de004ae7cb2a704 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 25 Nov 2024 17:15:48 +0200 Subject: [PATCH 126/138] Trace logs in claim_queue_state --- .../src/validator_side/claim_queue_state.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index 5851c55e6b10..51354843b18f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; +use crate::LOG_TARGET; use polkadot_primitives::{Hash, Id as ParaId}; #[derive(Debug, PartialEq)] @@ -93,17 +94,44 @@ impl ClaimQueueState { if w.claim == Some(*para_id) { w.claimed = true; + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + claim_info=?w, + "Successful claim" + ); return true; } } + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "Unsuccessful claim" + ); false } pub(crate) fn can_claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { let window = self.get_window(relay_parent); + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "can_claim_at" + ); + for w in window { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + claim_info=?w, + "Checking claim" + ); if !w.claimed && w.claim == Some(*para_id) { return true } From 2e0d14269f48f1c86ce6c846b055e777af33489d Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Mon, 25 Nov 2024 17:16:07 +0200 Subject: [PATCH 127/138] Fix path generation in `ensure_seconding_limit_is_respected` --- .../src/validator_side/mod.rs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 40c686c281d9..f5d1caf049d5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1055,14 +1055,32 @@ fn ensure_seconding_limit_is_respected( para_id: ParaId, state: &State, ) -> std::result::Result<(), AdvertisementError> { - let target_anc = state + let ancestors = state .implicit_view .known_allowed_relay_parents_under(relay_parent, Some(para_id)) - .and_then(|res| res.first()) .ok_or(AdvertisementError::RelayParentUnknown)?; - let paths = state.implicit_view.paths_to_relay_parent(target_anc); + let paths_from_leaves_to_target = state.implicit_view.paths_to_relay_parent(relay_parent); - for path in paths { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?ancestors, + ?paths_from_leaves_to_target, + "Checking seconding limit", + ); + + for mut p in paths_from_leaves_to_target { + let mut path = ancestors.to_vec(); + path.append(&mut p); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?path, + "Checking seconding limit for path", + ); let mut cq_state = ClaimQueueState::new(); for ancestor in path { let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); From 33e5c9fb37253af9a98331d3f822815bceb0ed92 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 09:35:53 +0200 Subject: [PATCH 128/138] Rework `unfulfilled_claim_queue_entries` --- .../src/validator_side/mod.rs | 269 +++--------------- 1 file changed, 35 insertions(+), 234 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index f5d1caf049d5..2503c8ca3ff4 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2137,209 +2137,6 @@ async fn handle_collation_fetch_response( result } -// Iterates all relay parents under `relay_parent` within the view up to `claim_queue_len` and -// returns the number of claims for `para_id` which are affecting `relay_parent`. Affecting in this -// context means that a relay parent before `relay_parent` have claimed a slot from `relay_parent`'s -// view. -fn claimed_within_view( - state: &State, - relay_parent: &Hash, - para_id: &ParaId, - claim_queue_len: usize, -) -> usize { - // First we take all the ancestors of `relay_parent`. The order is `relay_parent` -> ancestor... - let ancestors = if let Some(anc) = state - .implicit_view - .known_allowed_relay_parents_under(relay_parent, Some(*para_id)) - { - anc - } else { - return 0; - }; - - // We want to take the ancestors which fall in the claim queue view (hence the `take()`) and - // start counting claims from the oldest relay parent (hence the `rev()`). We start from oldest - // so that we can track claims from the ancestors. - // Example: - // CQ: [A, B, A] - // Relay parents: 1, 2, 3. - // We want to second a candidate for para A at relay parent 3. - // At relay parent 1 we have got two advertisements for A and one for B. - // At relay parent 2 we have got no advertisements. - // At relay parent 3 we CHECK if we can accept an advertisement for A. - // Visually: - // - // CQ: [A B A] CQ: [B A B] CQ: [A B A] - // - // ┌───────┐ ┌───────┐ ┌───────┐ - // │ RP1 ┼──────► RP2 ┼──────► RP3 │ - // └───────┘ └───────┘ └───────┘ - // A1 - // B1────────────► - // A2───────────────────────────► - // - // Since we have got three advertisements at RP1, A1 claims the slot at RP1, B1 - at RP2 and A2 - // - at RP3. This means that at RP2 we still can accept one more advertisement for B and one - // more for A. - // - // How the counting must work for para id A. Let's say we want to second something for A at RP3 - // and we want to know how many slots are claimed by older relay parents. ACC represents the - // number of claims transferred to the next relay parent which finally are transferred to - // the target relay parent. We start from the oldest relay parent and go to the newest one: - // - at RP1 we have got two claims and para A is in the first position in the claim queue. ACC = - // 2 - 1 = 1. - // - at RP2 we have got no claims advertised. Para A is not on the first position of the claim - // queue, so A2 can't be scheduled here. ACC is still 1. - // - at RP 3 we reach our target relay parent with ACC = 1. We want to advertise at RP 3 so we - // add 1 to ACC and it remains 1. IMPORTANT: since RP3 is our target relay parent we DON'T - // subtract 1 because this is the position the new advertisement will occupy. - // - ACC = 1 is the final result which represents the number of claims for para A at RP3. This - // means that at RP3 the first slot of the claim queue (which is for A) is claimed by an older - // relay parent (RP1). We still can accept one advertisement for A at RP3 since we have got - // one more claim for A (the 3rd one) in th claim queue, - - // We take all ancestors returned from `known_allowed_relay_parents_under` (remember they start - // with `relay_parent`), take the number of elements we are interested in (claim_queue_len) and - // then reverse them so that we start from the oldest one. - // At each ancestor we take the number of seconded and pending candidates at it, add it to the - // total number of claims and subtract 1 if `para_id` is at the first position of the claim - // queue for that ancestor. - ancestors.iter().take(claim_queue_len).rev().fold(0, |acc, anc| { - let seconded_and_pending = state.seconded_and_pending_for_para(anc, para_id); - let first_assignment = state - .per_relay_parent - .get(anc) - .and_then(|per_relay_parent| per_relay_parent.assignment.current.first()); - match first_assignment { - Some(first) if first == para_id && anc != relay_parent => - (acc + seconded_and_pending).saturating_sub(1), - _ => acc + seconded_and_pending, - } - }) -} - -fn seconded_and_pending_for_para_above( - state: &State, - relay_parent: &Hash, - para_id: &ParaId, - claim_queue_len: usize, -) -> Vec { - let leaf_paths = state.implicit_view.paths_to_relay_parent(relay_parent); - let mut result = vec![]; - - // When counting the number of seconded and pending candidates above the target relay parent we - // have to consider all forks. So first we take all paths from leaves to the target relay parent - // with `paths_to_relay_parent()`. Then for each path we calculate the number of secondend and - // pending candidates and return it as a result. All paths are returned and it's up to the - // caller to decide how to interpret them. - // - // For each path we keep track of two important values: - // - `claimed_slots` - this is the number of the slots in the path which are claimed by a relay - // parent in the path. For simplicity we don't care what's claimed by the target relay parent. - // It's up to the caller to handle this. This value is the end result we save for each path at - // the end of the path iteration. - // - `unfulfilled_claims` - this is the number of advertisements for relay parents in the path - // which we haven't mapped to actual claim queue slots (or relay parents) so far. This value - // is used as an intermediate running sum of the claims and we don't return it as a result. - // - // How these two values work together to count the number of pending and seconded candidates - // above the target relay parent? Let's see an example. Let's say that: - // - our target relay parent is RP0 (to avoid confusion it is not shown in the diagram below). - // - the length of the claim queue (and lookahead) is 3 - // - there are two parachains (A and B) sharing a core. So at each relay parent the claim queue - // is either [ABA] or [BAB]. - // - there are two advertisements for parachain A one for parachain B at RP1. They are A1, A2 - // and B2. - - // We start iterating each path from the oldest relay parent to the leaf. At each ancestor we: - // 1. Get the number of pending and seconded candidates with `seconded_and_pending_for_para()` - // and add it to `unfulfilled_claims`. These are the claims we have to map to the claim queue - // and which we have to transfer to the next ancestor from the path. - // 2. Get the first assignment in the claim queue. If it is for the target `para_id` - we - // subtract one from `unfulfilled_claims` because we have just mapped a claim to the current - // spot. At the same time we add 1 to the `claimed_slots` because the slot at the current - // ancestor is now claimed. - // 3. We repeat these steps until we reach the end leaf and return `claimed_slots`. - - // Now back to our example. We have got this path. At each relay parent we have the state of the - // claim queue and the advertisements: - // - // CQ: [A B A] CQ: [B A B] CQ: [A B A] - // - // ┌───────┐ ┌───────┐ ┌───────┐ - // │ RP1 ┼──────► RP2 ┼──────► RP3 │ - // └───────┘ └───────┘ └───────┘ - // A1 - // B1────────────► - // A2───────────────────────────► - // - // We start with `claimed_slots` and `unfulfilled_claims` both equal to 0. We will look at para - // A and B in parallel but in reality the coed is interested only in one of the paras.At RP1 we - // have got 2 claims for para A and 1 for para B. So `unfulfilled_claims` for para A will be 2 - // and for para B - 1. The first element in the claim queue is for para A so we subtract 1 from - // `unfulfilled_claims` for para A and add 1 to claimed_slots for para A. Bottom line at the end - // of RP1 processing we have got: - // - claimed_slots for para A = 1 - // - claimed_slots for para B = 0 - // - unfulfilled_claims for para A = 1 - // - unfulfilled_claims for para B = 1 - // - // We move on to RP2. Nothing is scheduled here so we don't add anything to `unfulfilled_claims` - // for either A or B. The first element for CQ is B so we subtract 1 from `unfulfilled_claims` - // for B and add 1 to claimed_slots for B. At the end of RP2 processing we have got: - // - claimed_slots for para A = 1 - // - claimed_slots for para B = 1 - // - unfulfilled_claims for para A = 1 - // - unfulfilled_claims for para B = 0 - // - // Now we are at RP3. Again nothing is scheduled here so `unfulfilled_claims` for both A and B - // is unchanged. The first element in the claim queue is A so we subtract 1 from - // `unfulfilled_claims` for A and add 1 to claimed_slots for A. No changes for B. At the end of - // RP3 we have got: - // - claimed_slots for para A = 2 - // - claimed_slots for para B = 1 - // - unfulfilled_claims for para A = 0 - // - unfulfilled_claims for para B = 0 - // Which is our end result too. Note that in this example `unfulfilled_claims` reached 0 for - // both paras but this is not necessary always the case. They might be bigger than zero if there - // are claims which will be fulfilled at future relay parents. But we don't care for this. - - for path in leaf_paths { - // Here we track how many first slots from the claim queue at the ancestors are actually - // claimed. This is the end result we care about. - let mut claimed_slots = 0; - // Here we track how many claims are transferred to the next ancestors in the path. We need - // this to decide if a next first slot is claimed or not. - // For example we have got a path rp1->rp2. Claim queue is [A,A] at each ancestor.At rp1 we - // have got 2 claims, at rp2 = 0. In this case we have got both first slots claimed (at rp1 - // and rp2) since the 2nd claim from rp1 is transferred to rp2. - let mut unfulfilled_claims = 0; - - // `claim_queue_len - 1` because the first element of the claim queue is the 'current' slot. - // Here we are interested only in the 'future' ones - for anc in path.iter().rev().take(claim_queue_len - 1) { - // Anything seconded for `para_id` at the ancestor is added up to the claims. - unfulfilled_claims += state.seconded_and_pending_for_para(anc, para_id); - - let first_assignment_in_cq = state - .per_relay_parent - .get(anc) - .and_then(|per_relay_parent| per_relay_parent.assignment.current.first()); - - // If we have got unfulfilled claims and the first assignment in the claim queue is for - // `para_id` then we can assign a claim to the current ancestor. Mark the slot as - // claimed and decrease the number of unfulfilled claims. - if unfulfilled_claims > 0 && first_assignment_in_cq == Some(para_id) { - claimed_slots += 1; - unfulfilled_claims -= 1; - } - } - - result.push(claimed_slots); - } - - result -} // Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the // order in the claim queue so the earlier an element is located in the `Vec` the higher its // priority is. @@ -2349,40 +2146,44 @@ fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result .get(relay_parent) .ok_or(Error::RelayParentStateNotFound)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); - let mut claims_per_para = HashMap::new(); - for para_id in scheduled_paras { - let below = claimed_within_view( - state, - relay_parent, - para_id, - relay_parent_state.assignment.current.len(), - ); - let above = seconded_and_pending_for_para_above( - state, - relay_parent, - para_id, - relay_parent_state.assignment.current.len(), - ) - .into_iter() - .max() - .unwrap_or(0); + let ancestors = state + .implicit_view + .known_allowed_relay_parents_under(relay_parent, None) + .ok_or(Error::RelayParentStateNotFound)?; + let paths_from_leaves_to_target = state.implicit_view.paths_to_relay_parent(relay_parent); - claims_per_para.insert(*para_id, below + above); + let mut cq_states = Vec::new(); + for mut p in paths_from_leaves_to_target { + let mut path = ancestors.to_vec(); + path.append(&mut p); + let mut cq_state = ClaimQueueState::new(); + for para_id in &scheduled_paras { + for ancestor in &path { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(&ancestor) + .ok_or(Error::RelayParentStateNotFound)? + .assignment + .current, + ); + for _ in 0..seconded_and_pending { + cq_state.claim_at(&ancestor, ¶_id); + } + } + } + cq_states.push(cq_state); } - let claim_queue_state = relay_parent_state - .assignment - .current - .iter() - .filter_map(|para_id| match claims_per_para.entry(*para_id) { - Entry::Occupied(mut entry) if *entry.get() > 0 => { - *entry.get_mut() -= 1; - None - }, - _ => Some(*para_id), - }) - .collect::>(); - Ok(claim_queue_state) + let res = cq_states + .iter_mut() + .map(|cq| cq.unclaimed_at(relay_parent)) + .max_by(|a, b| a.len().cmp(&b.len())) + .unwrap_or_default(); + + Ok(res) } /// Returns the next collation to fetch from the `waiting_queue` and reset the status back to From 5bc63de96e5cf1afe85f3900bea244dbe3b2dbaf Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 10:05:20 +0200 Subject: [PATCH 129/138] `paths_from_leaves_via_relay_parent` --- .../src/validator_side/mod.rs | 62 +++++++------------ .../src/backing_implicit_view.rs | 35 ++++++++++- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 2503c8ca3ff4..963b93ed5647 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1055,32 +1055,17 @@ fn ensure_seconding_limit_is_respected( para_id: ParaId, state: &State, ) -> std::result::Result<(), AdvertisementError> { - let ancestors = state - .implicit_view - .known_allowed_relay_parents_under(relay_parent, Some(para_id)) - .ok_or(AdvertisementError::RelayParentUnknown)?; - let paths_from_leaves_to_target = state.implicit_view.paths_to_relay_parent(relay_parent); + let paths = state.implicit_view.paths_from_leaves_via_relay_parent(relay_parent); gum::trace!( target: LOG_TARGET, ?relay_parent, ?para_id, - ?ancestors, - ?paths_from_leaves_to_target, + ?paths, "Checking seconding limit", ); - for mut p in paths_from_leaves_to_target { - let mut path = ancestors.to_vec(); - path.append(&mut p); - - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?para_id, - ?path, - "Checking seconding limit for path", - ); + for path in paths { let mut cq_state = ClaimQueueState::new(); for ancestor in path { let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); @@ -2146,44 +2131,39 @@ fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result .get(relay_parent) .ok_or(Error::RelayParentStateNotFound)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); - let ancestors = state - .implicit_view - .known_allowed_relay_parents_under(relay_parent, None) - .ok_or(Error::RelayParentStateNotFound)?; - let paths_from_leaves_to_target = state.implicit_view.paths_to_relay_parent(relay_parent); + let paths = state.implicit_view.paths_from_leaves_via_relay_parent(relay_parent); - let mut cq_states = Vec::new(); - for mut p in paths_from_leaves_to_target { - let mut path = ancestors.to_vec(); - path.append(&mut p); + let mut claim_queue_states = Vec::new(); + for path in paths { let mut cq_state = ClaimQueueState::new(); - for para_id in &scheduled_paras { - for ancestor in &path { + for ancestor in &path { + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(&ancestor) + .ok_or(Error::RelayParentStateNotFound)? + .assignment + .current, + ); + + for para_id in &scheduled_paras { let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); - cq_state.add_leaf( - &ancestor, - &state - .per_relay_parent - .get(&ancestor) - .ok_or(Error::RelayParentStateNotFound)? - .assignment - .current, - ); for _ in 0..seconded_and_pending { cq_state.claim_at(&ancestor, ¶_id); } } } - cq_states.push(cq_state); + claim_queue_states.push(cq_state); } - let res = cq_states + let unfulfilled_entries = claim_queue_states .iter_mut() .map(|cq| cq.unclaimed_at(relay_parent)) .max_by(|a, b| a.len().cmp(&b.len())) .unwrap_or_default(); - Ok(res) + Ok(unfulfilled_entries) } /// Returns the next collation to fetch from the `waiting_queue` and reset the status back to diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 31b9eccaa3ae..2d20331f3de5 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -28,6 +28,7 @@ use crate::{ inclusion_emulator::RelayChainBlockInfo, request_async_backing_params, request_session_index_for_child, runtime::{self, recv_runtime}, + LOG_TARGET, }; // Always aim to retain 1 block before the active leaves. @@ -334,7 +335,15 @@ impl View { /// /// The input is not included in the path so if `relay_parent` happens to be an outer leaf, no /// paths will be returned. - pub fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Vec> { + fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Vec> { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + leaves=?self.leaves, + block_info_storage=?self.block_info_storage, + "Finding paths via relay parent" + ); + if self.leaves.is_empty() { // No outer leaves so the view should be empty. Don't return any paths. return vec![] @@ -353,8 +362,6 @@ impl View { let mut visited = HashSet::new(); loop { - // If `relay_parent` is an outer leaf we'll immediately return an empty path (which - // is the desired behaviour). if current_leaf == *relay_parent { // path is complete paths.push(path); @@ -385,6 +392,28 @@ impl View { paths } + /// Returns all paths from the leaves via `relay_parent` up to its allowed ancestry + pub fn paths_from_leaves_via_relay_parent(&self, relay_parent: &Hash) -> Vec> { + let mut result = Vec::new(); + + let ancestors = + self.known_allowed_relay_parents_under(relay_parent, None).unwrap_or_default(); + + for p in self.paths_to_relay_parent(relay_parent) { + let full_path = + ancestors.iter().rev().copied().chain(p.into_iter()).collect::>(); + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?ancestors, + ?full_path, + "Found a full path", ); + result.push(full_path); + } + + result + } + async fn fetch_fresh_leaf_and_insert_ancestry( &mut self, leaf_hash: Hash, From 1a195c12d4b89391a83706ae6bc369c620e4d84e Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 11:34:00 +0200 Subject: [PATCH 130/138] Fix `fetch_next_collation_on_invalid_collation` --- .../src/validator_side/tests/mod.rs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 2c87dcc236db..941f41be2f6e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -814,14 +814,13 @@ fn reject_connection_to_next_group() { // invalid. #[test] fn fetch_next_collation_on_invalid_collation() { - let mut test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let first = test_state.relay_parent; - let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); - update_view(&mut virtual_overseer, &mut test_state, vec![(first, 0), (second, 1)]).await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -844,12 +843,12 @@ fn fetch_next_collation_on_invalid_collation() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, first, None).await; - advertise_collation(&mut virtual_overseer, peer_c, first, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, - first, + relay_parent, test_state.chain_ids[0], None, ) @@ -859,7 +858,7 @@ fn fetch_next_collation_on_invalid_collation() { let mut candidate_a = dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = first; + candidate_a.descriptor.relay_parent = relay_parent; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel .send(Ok(( @@ -874,7 +873,7 @@ fn fetch_next_collation_on_invalid_collation() { let receipt = assert_candidate_backing_second( &mut virtual_overseer, - first, + relay_parent, test_state.chain_ids[0], &pov, CollationVersion::V1, @@ -882,8 +881,11 @@ fn fetch_next_collation_on_invalid_collation() { .await; // Inform that the candidate was invalid. - overseer_send(&mut virtual_overseer, CollatorProtocolMessage::Invalid(first, receipt)) - .await; + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::Invalid(relay_parent, receipt), + ) + .await; assert_matches!( overseer_recv(&mut virtual_overseer).await, @@ -896,8 +898,13 @@ fn fetch_next_collation_on_invalid_collation() { ); // We should see a request for another collation. - assert_fetch_collation_request(&mut virtual_overseer, first, test_state.chain_ids[0], None) - .await; + assert_fetch_collation_request( + &mut virtual_overseer, + relay_parent, + test_state.chain_ids[0], + None, + ) + .await; virtual_overseer }); From ac3e1a1df61e0d2a6fa784e181b9737b72085ede Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 14:32:37 +0200 Subject: [PATCH 131/138] `paths_via_relay_parent` uses `block_info` --- .../src/validator_side/mod.rs | 4 +- .../src/backing_implicit_view.rs | 109 ++++++++---------- 2 files changed, 53 insertions(+), 60 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 963b93ed5647..da20c7272b12 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1055,7 +1055,7 @@ fn ensure_seconding_limit_is_respected( para_id: ParaId, state: &State, ) -> std::result::Result<(), AdvertisementError> { - let paths = state.implicit_view.paths_from_leaves_via_relay_parent(relay_parent); + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); gum::trace!( target: LOG_TARGET, @@ -2131,7 +2131,7 @@ fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result .get(relay_parent) .ok_or(Error::RelayParentStateNotFound)?; let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); - let paths = state.implicit_view.paths_from_leaves_via_relay_parent(relay_parent); + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); let mut claim_queue_states = Vec::new(); for path in paths { diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 2d20331f3de5..47674879ef93 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -330,12 +330,9 @@ impl View { .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } - /// Returns all paths from a leaf to `relay_parent`. If no paths exist the function - /// will return an empty `Vec`. - /// - /// The input is not included in the path so if `relay_parent` happens to be an outer leaf, no - /// paths will be returned. - fn paths_to_relay_parent(&self, relay_parent: &Hash) -> Vec> { + /// Returns all paths from a leaf to the last block in state containing `relay_parent`. If no + /// paths exist the function will return an empty `Vec`. + pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec> { gum::trace!( target: LOG_TARGET, ?relay_parent, @@ -360,60 +357,46 @@ impl View { let mut path = Vec::new(); let mut current_leaf = *leaf; let mut visited = HashSet::new(); + let mut path_contains_target = false; + // Start from the leaf and traverse all known blocks loop { - if current_leaf == *relay_parent { - // path is complete - paths.push(path); + if visited.contains(¤t_leaf) { + // There is a cycle - abandon this path break } - path.push(current_leaf); - current_leaf = match self.block_info_storage.get(¤t_leaf) { Some(info) => { - let r = info.parent_hash; - if visited.contains(&r) { - // There is a cycle so this is not a path to `relay_parent` - break + // `current_leaf` is a known block - add it to the path and mark it as + // visited + path.push(current_leaf); + visited.insert(current_leaf); + + // `current_leaf` is the target `relay_parent`. Mark the path so that it's + // included in the result + if current_leaf == *relay_parent { + path_contains_target = true; } - visited.insert(r); - r + + // update `current_leaf` with the parent + info.parent_hash }, None => { - // Parent is not found there for it should be outside the view. Abandon this - // path. + // path is complete + if path_contains_target { + // we want the path ordered from oldest to newest so reverse it + paths.push(path.into_iter().rev().collect()); + } break }, - } + }; } } paths } - /// Returns all paths from the leaves via `relay_parent` up to its allowed ancestry - pub fn paths_from_leaves_via_relay_parent(&self, relay_parent: &Hash) -> Vec> { - let mut result = Vec::new(); - - let ancestors = - self.known_allowed_relay_parents_under(relay_parent, None).unwrap_or_default(); - - for p in self.paths_to_relay_parent(relay_parent) { - let full_path = - ancestors.iter().rev().copied().chain(p.into_iter()).collect::>(); - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?ancestors, - ?full_path, - "Found a full path", ); - result.push(full_path); - } - - result - } - async fn fetch_fresh_leaf_and_insert_ancestry( &mut self, leaf_hash: Hash, @@ -892,10 +875,19 @@ mod tests { assert_eq!(view.leaves.len(), 1); assert!(view.leaves.contains_key(leaf)); - assert!(view.paths_to_relay_parent(&CHAIN_B[0]).is_empty()); + assert!(view.paths_via_relay_parent(&CHAIN_B[0]).is_empty()); + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx + 1]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); assert_eq!( - view.paths_to_relay_parent(&CHAIN_B[min_min_idx]), - vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + view.paths_via_relay_parent(&leaf), + vec![CHAIN_B[min_min_idx..].to_vec()] ); } ); @@ -1018,9 +1010,10 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); assert_eq!( - view.paths_to_relay_parent(&CHAIN_B[min_min_idx]), - vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] ); } ); @@ -1091,10 +1084,10 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); - assert!(view.paths_to_relay_parent(&GENESIS_HASH).is_empty()); + assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty()); assert_eq!( - view.paths_to_relay_parent(&CHAIN_A[0]), - vec![expected_ancestry.iter().take(expected_ancestry.len() - 1).copied().collect::>()] + view.paths_via_relay_parent(&CHAIN_A[0]), + vec![CHAIN_A.to_vec()] ); } ); @@ -1314,25 +1307,25 @@ mod tests { assert_eq!(view.leaves.len(), 2); - let mut paths_to_genesis = view.paths_to_relay_parent(&GENESIS_HASH); + let mut paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH); paths_to_genesis.sort(); let mut expected_paths_to_genesis = vec![ - CHAIN_A.iter().rev().copied().collect::>(), - CHAIN_B.iter().rev().copied().collect::>(), + [GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>(), + [GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>(), ]; expected_paths_to_genesis.sort(); assert_eq!(paths_to_genesis, expected_paths_to_genesis); - let path_to_leaf_in_a = view.paths_to_relay_parent(&CHAIN_A[1]); + let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]); let expected_path_to_leaf_in_a = - vec![CHAIN_A[2..].iter().rev().copied().collect::>()]; + vec![[GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>()]; assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a); - let path_to_leaf_in_b = view.paths_to_relay_parent(&CHAIN_B[4]); + let path_to_leaf_in_b = view.paths_via_relay_parent(&CHAIN_B[4]); let expected_path_to_leaf_in_b = - vec![CHAIN_B[5..].iter().rev().copied().collect::>()]; + vec![[GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>()]; assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b); - assert_eq!(view.paths_to_relay_parent(&Hash::repeat_byte(0x0A)), Vec::>::new()); + assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::>::new()); } } From 31c6cdb2b35feebb91d4a95d29750df92f6b10ef Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 14:46:02 +0200 Subject: [PATCH 132/138] Fix tests --- .../validator_side/tests/prospective_parachains.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index ee52427e2486..480c104a32b7 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -698,7 +698,7 @@ fn accept_advertisements_from_implicit_view() { #[test] fn second_multiple_candidates_per_relay_parent() { - let mut test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1093,7 +1093,7 @@ fn advertisement_spam_protection() { #[case(true)] #[case(false)] fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { - let mut test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -2203,12 +2203,7 @@ fn claims_below_are_counted_correctly() { let collator_a = PeerId::random(); let para_id_a = test_state.chain_ids[0]; - update_view( - &mut virtual_overseer, - &mut test_state, - vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], - ) - .await; + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; connect_and_declare_collator( &mut virtual_overseer, From 3794e6952579abcb434b9b8bf23329453e3d5630 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 15:19:16 +0200 Subject: [PATCH 133/138] Comments --- .../src/validator_side/claim_queue_state.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index 51354843b18f..9c5a115c8d86 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -3,6 +3,13 @@ use std::collections::VecDeque; use crate::LOG_TARGET; use polkadot_primitives::{Hash, Id as ParaId}; +/// Represents a single claim from the claim queue, mapped to a relay block. +/// - `hash` is `Option` since the claim might be for a future block. +/// - `claim` represents the `ParaId` scheduled for the block. Can be `None` if nothing is scheduled +/// at this block. +/// - `claim_queue_len` is the length of the claim queue at the block. It is used to determine the +/// 'block window' where a claim can be made. +/// - `claimed` is a flag that indicates if the slot is claimed or not. #[derive(Debug, PartialEq)] struct ClaimInfo { hash: Option, @@ -11,6 +18,50 @@ struct ClaimInfo { claimed: bool, } +/// Tracks the state of the claim queue over a set of relay blocks. +/// +/// Generally the claim queue represents the `ParaId` that should be scheduled at the current block +/// (the first element of the claim queue) and N other `ParaId`s which are supposed to be scheduled +/// on the next relay blocks. In other words the claim queue is a rolling window giving a hint what +/// should be built/fetched/accepted (depending on the context) at each block. +/// +/// Since the claim queue peeks into the future blocks there is a relation between the claim queue +/// state between the current block and the future blocks. Let's see an example: +/// - relay parent 1; Claim queue: [A, B, A] +/// - relay parent 2; Claim queue: [B, A, B] +/// - relay parent 3; Claim queue: [A, B, A] +/// - and so on +/// +/// Note that at rp1 the second element in the claim queue is equal to the first one in rp2. Also +/// the third element of the claim queue at rp1 is equal to the second one in rp2 and the first one +/// in rp3. +/// +/// So if we want to claim the third slot at rp 1 we are also claiming the second at rp2 and first +/// at rp3. To track this in a simple way we can project the claim queue onto the relay blocks like +/// this: +/// [A] [B] [A] -> this is the claim queue at rp3 +/// [B] [A] [B] -> this is the claim queue at rp2 +/// [A] [B] [A] -> this is the claim queue at rp1 +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Note that the claims at each column are the same so we can simplify this by just projecting a +/// single claim over a block: +/// [A] [B] [A] [B] [A] -> claims effectively are the same +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Basically this is how `ClaimQueueState` works. It keeps track of claims at each block by mapping +/// claims to relay blocks. +/// +/// How making a claim works? +/// At each relay block we keep track how long is the claim queue. This is a 'window' where we can +/// make a claim. So adding a claim just looks for a free spot at this window and claims it. +/// +/// Note on adding a new leaf. +/// When a new leaf is added we check if the first element in its claim queue matches with the +/// projection on the first element in 'future blocks'. If yes - the new relay block inherits this +/// claim. If not - this means that the claim queue changed for some reason so the claim can't be +/// inherited. This should not happen under normal circumstances. But if it happens it means that we +/// have got one claim which won't be satisfied in the worst case scenario. pub(crate) struct ClaimQueueState { block_state: VecDeque, future_blocks: VecDeque, From 3e2acd4c78e25e27fa55315f2d8d2ee31772d753 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Tue, 26 Nov 2024 15:49:00 +0200 Subject: [PATCH 134/138] File header and tests for edge cases in claim_queue_state --- .../src/validator_side/claim_queue_state.rs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index 9c5a115c8d86..2f19d4964b8a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -1,3 +1,22 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! `ClaimQueueState` tracks the state of the claim queue over a set of relay blocks. Refer to +//! [`ClaimQueueState`] for more details. + use std::collections::VecDeque; use crate::LOG_TARGET; @@ -609,4 +628,155 @@ mod test { ]) ); } + + #[test] + fn claim_queue_changes_unexpectedly() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, a, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost the claim for paraB and have one + // unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + // since the 3rd slot of the claim queue at rp1 is equal to the second one in rp2, this + // claim still exists + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } + + #[test] + fn claim_queue_changes_unexpectedly_with_two_blocks() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_b]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, b, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost both claims for paraB and have + // two unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } } From eac7a737186bfb497378f643f399332e2a326ee1 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 27 Nov 2024 09:50:47 +0200 Subject: [PATCH 135/138] clippy --- .../collator-protocol/src/validator_side/claim_queue_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index 2f19d4964b8a..bde310fa764a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -213,7 +213,7 @@ impl ClaimQueueState { pub(crate) fn unclaimed_at(&mut self, relay_parent: &Hash) -> Vec { let window = self.get_window(relay_parent); - window.filter(|b| !b.claimed).filter_map(|b| b.claim.clone()).collect() + window.filter(|b| !b.claimed).filter_map(|b| b.claim).collect() } } From 3bd85d9cd182deee726e012f1537af7d1fda53df Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Wed, 27 Nov 2024 16:44:53 +0200 Subject: [PATCH 136/138] Apply suggestions from code review Co-authored-by: Alin Dima --- .../collator-protocol/src/validator_side/claim_queue_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index bde310fa764a..0a053e637cb0 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -22,7 +22,7 @@ use std::collections::VecDeque; use crate::LOG_TARGET; use polkadot_primitives::{Hash, Id as ParaId}; -/// Represents a single claim from the claim queue, mapped to a relay block. +/// Represents a single claim from the claim queue, mapped to the relay chain block where it could be backed on-chain. /// - `hash` is `Option` since the claim might be for a future block. /// - `claim` represents the `ParaId` scheduled for the block. Can be `None` if nothing is scheduled /// at this block. From 4b2a67d941e7cd8c6cf89cae329d57232bfed043 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 28 Nov 2024 09:36:26 +0200 Subject: [PATCH 137/138] comment --- .../node/network/collator-protocol/src/validator_side/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 158836e3368b..6a84735868bc 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -2154,6 +2154,11 @@ fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result claim_queue_states.push(cq_state); } + // From the claim queue state for each leaf we have to return a combined single one. Go for a + // simple solution and return the longest one. In theory we always prefer the earliest entries + // in the claim queue so there is a good chance that the longest path is the one with + // unsatisfied entries in the beginning. This is not guaranteed as we might have fetched 2nd or + // 3rd spot from the claim queue but it should be good enough. let unfulfilled_entries = claim_queue_states .iter_mut() .map(|cq| cq.unclaimed_at(relay_parent)) From f3634e1824102ae85b822f40a01a45634166d52b Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Thu, 28 Nov 2024 10:06:11 +0200 Subject: [PATCH 138/138] Small refactoring at claim_queue_state --- .../src/validator_side/claim_queue_state.rs | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs index 0a053e637cb0..caca7c3adfd4 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -22,7 +22,8 @@ use std::collections::VecDeque; use crate::LOG_TARGET; use polkadot_primitives::{Hash, Id as ParaId}; -/// Represents a single claim from the claim queue, mapped to the relay chain block where it could be backed on-chain. +/// Represents a single claim from the claim queue, mapped to the relay chain block where it could +/// be backed on-chain. /// - `hash` is `Option` since the claim might be for a future block. /// - `claim` represents the `ParaId` scheduled for the block. Can be `None` if nothing is scheduled /// at this block. @@ -155,38 +156,16 @@ impl ClaimQueueState { } pub(crate) fn claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { - let window = self.get_window(relay_parent); - - for w in window { - if w.claimed { - continue - } - - if w.claim == Some(*para_id) { - w.claimed = true; - gum::trace!( - target: LOG_TARGET, - ?para_id, - ?relay_parent, - claim_info=?w, - "Successful claim" - ); - return true; - } - } - gum::trace!( target: LOG_TARGET, ?para_id, ?relay_parent, - "Unsuccessful claim" + "claim_at" ); - false + self.find_a_claim(relay_parent, para_id, true) } pub(crate) fn can_claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { - let window = self.get_window(relay_parent); - gum::trace!( target: LOG_TARGET, ?para_id, @@ -194,15 +173,27 @@ impl ClaimQueueState { "can_claim_at" ); + self.find_a_claim(relay_parent, para_id, false) + } + + // Returns `true` if there is a claim within `relay_parent`'s view of the claim queue for + // `para_id`. If `claim_it` is set to `true` the slot is claimed. Otherwise the function just + // reports the availability of the slot. + fn find_a_claim(&mut self, relay_parent: &Hash, para_id: &ParaId, claim_it: bool) -> bool { + let window = self.get_window(relay_parent); + for w in window { gum::trace!( target: LOG_TARGET, ?para_id, ?relay_parent, claim_info=?w, + ?claim_it, "Checking claim" ); + if !w.claimed && w.claim == Some(*para_id) { + w.claimed = claim_it; return true } }