// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // This file is part of Pezkuwi. // Pezkuwi 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. // Pezkuwi 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 Pezkuwi. If not, see . use crate::{ configuration, inclusion, initializer, paras, paras::ParaKind, paras_inherent, scheduler::{ self, common::{Assignment, AssignmentProvider}, }, session_info, shared, }; use alloc::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet, vec_deque::VecDeque}, vec, vec::Vec, }; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use pezframe_support::pezpallet_prelude::*; use pezframe_system::pezpallet_prelude::*; use pezkuwi_primitives::{ ApprovedPeerId, AvailabilityBitfield, BackedCandidate, CandidateCommitments, CandidateDescriptorV2, CandidateHash, ClaimQueueOffset, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CompactStatement, CoreIndex, CoreSelector, DisputeStatement, DisputeStatementSet, GroupIndex, HeadData, Id as ParaId, IndexedVec, InherentData as TeyrchainsInherentData, InvalidDisputeStatementKind, PersistedValidationData, SessionIndex, SigningContext, UMPSignal, UncheckedSigned, ValidDisputeStatementKind, ValidationCode, ValidatorId, ValidatorIndex, ValidityAttestation, UMP_SEPARATOR, }; use pezsp_core::H256; use pezsp_runtime::{ traits::{Header as HeaderT, One, TrailingZeroInput, Zero}, RuntimeAppPublic, }; fn mock_validation_code() -> ValidationCode { ValidationCode(vec![1, 2, 3]) } /// Grab an account, seeded by a name and index. /// /// This is directly from pezframe-benchmarking. Copy/pasted so we can use it when not compiling /// with "features = runtime-benchmarks". fn account(name: &'static str, index: u32, seed: u32) -> AccountId { let entropy = (name, index, seed).using_encoded(pezsp_io::hashing::blake2_256); AccountId::decode(&mut TrailingZeroInput::new(&entropy[..])) .expect("infinite input; no invalid input; qed") } pub fn generate_validator_pairs( validator_count: u32, ) -> Vec<(T::AccountId, ValidatorId)> { (0..validator_count) .map(|i| { let public = ValidatorId::generate_pair(None); // The account Id is not actually used anywhere, just necessary to fulfill the // expected type of the `validators` param of `test_trigger_on_new_session`. let account: T::AccountId = account("validator", i, i); (account, public) }) .collect() } /// Create a 32 byte slice based on the given number. fn byte32_slice_from(n: u32) -> [u8; 32] { let mut slice = [0u8; 32]; slice[31] = (n % (1 << 8)) as u8; slice[30] = ((n >> 8) % (1 << 8)) as u8; slice[29] = ((n >> 16) % (1 << 8)) as u8; slice[28] = ((n >> 24) % (1 << 8)) as u8; slice } /// Paras inherent `enter` benchmark scenario builder. pub(crate) struct BenchBuilder { /// Active validators. Validators should be declared prior to all other setup. validators: Option>, /// Starting block number; we expect it to get incremented on session setup. block_number: BlockNumberFor, /// Starting session; we expect it to get incremented on session setup. session: SessionIndex, /// Session we want the scenario to take place in. We will roll to this session. target_session: u32, /// Optionally set the max validators per core; otherwise uses the configuration value. max_validators_per_core: Option, /// Optionally set the max validators; otherwise uses the configuration value. max_validators: Option, /// Optionally set the number of dispute statements for each candidate. dispute_statements: BTreeMap, /// Session index of for each dispute. Index of slice corresponds to a core, /// which is offset by the number of entries for `backed_and_concluding_paras`. I.E. if /// `backed_and_concluding_paras` has 3 entries, the first index of `dispute_sessions` /// will correspond to core index 3. There must be one entry for each core with a dispute /// statement set. dispute_sessions: Vec, /// Paras here will both be backed in the inherent data and already occupying a core (which is /// freed via bitfields). /// /// Map from para id to number of validity votes. Core indices are generated based on /// `elastic_paras` configuration. Each para id in `elastic_paras` gets the /// specified amount of consecutive cores assigned to it. If a para id is not present /// in `elastic_paras` it get assigned to a single core. backed_and_concluding_paras: BTreeMap, /// Paras which don't yet occupy a core, but will after the inherent has been processed. backed_in_inherent_paras: BTreeMap, /// Map from para id (seed) to number of chained candidates. elastic_paras: BTreeMap, /// Make every candidate include a code upgrade by setting this to `Some` where the interior /// value is the byte length of the new code. code_upgrade: Option, /// Cores which should not be available when being populated with pending candidates. unavailable_cores: Vec, /// Use v2 candidate descriptor. candidate_descriptor_v2: bool, /// Send an approved peer ump signal. Only useful for v2 descriptors approved_peer_signal: Option, /// Apply custom changes to generated candidates candidate_modifier: Option>, _phantom: core::marker::PhantomData, } pub type CandidateModifier = fn(CommittedCandidateReceipt) -> CommittedCandidateReceipt; /// Paras inherent `enter` benchmark scenario. #[cfg(any(feature = "runtime-benchmarks", test))] pub(crate) struct Bench { pub(crate) data: TeyrchainsInherentData>, pub(crate) _session: u32, pub(crate) _block_number: BlockNumberFor, } #[allow(dead_code)] impl BenchBuilder { /// Create a new `BenchBuilder` with some opinionated values that should work with the rest /// of the functions in this implementation. pub(crate) fn new() -> Self { BenchBuilder { validators: None, block_number: Zero::zero(), session: SessionIndex::from(0u32), target_session: 2u32, max_validators_per_core: None, max_validators: None, dispute_statements: BTreeMap::new(), dispute_sessions: Default::default(), backed_and_concluding_paras: Default::default(), backed_in_inherent_paras: Default::default(), elastic_paras: Default::default(), code_upgrade: None, unavailable_cores: vec![], candidate_descriptor_v2: false, approved_peer_signal: None, candidate_modifier: None, _phantom: core::marker::PhantomData::, } } /// Set the session index for each dispute statement set (in other words, set the session the /// the dispute statement set's relay chain block is from). Indexes of `dispute_sessions` /// correspond to a core, which is offset by the number of entries for /// `backed_and_concluding_paras`. I.E. if `backed_and_concluding_paras` cores has 3 entries, /// the first index of `dispute_sessions` will correspond to core index 3. /// /// Note that there must be an entry for each core with a dispute statement set. pub(crate) fn set_dispute_sessions(mut self, dispute_sessions: impl AsRef<[u32]>) -> Self { self.dispute_sessions = dispute_sessions.as_ref().to_vec(); self } /// Set the cores which should not be available when being populated with pending candidates. pub(crate) fn set_unavailable_cores(mut self, unavailable_cores: Vec) -> Self { self.unavailable_cores = unavailable_cores; self } /// Set a map from para id seed to number of validity votes. pub(crate) fn set_backed_and_concluding_paras( mut self, backed_and_concluding_paras: BTreeMap, ) -> Self { self.backed_and_concluding_paras = backed_and_concluding_paras; self } /// Set a map from para id seed to number of validity votes for votes in inherent data. pub(crate) fn set_backed_in_inherent_paras(mut self, backed: BTreeMap) -> Self { self.backed_in_inherent_paras = backed; self } /// Set a map from para id seed to number of cores assigned to it. pub(crate) fn set_elastic_paras(mut self, elastic_paras: BTreeMap) -> Self { self.elastic_paras = elastic_paras; self } /// Set to include a code upgrade for all backed candidates. The value will be the byte length /// of the code. pub(crate) fn set_code_upgrade(mut self, code_upgrade: impl Into>) -> Self { self.code_upgrade = code_upgrade.into(); self } /// Mock header. pub(crate) fn header(block_number: BlockNumberFor) -> HeaderFor { HeaderFor::::new( block_number, // `block_number`, Default::default(), // `extrinsics_root`, Default::default(), // `storage_root`, Default::default(), // `parent_hash`, Default::default(), // digest, ) } /// Number of the relay parent block. fn relay_parent_number(&self) -> u32 { (self.block_number - One::one()) .try_into() .map_err(|_| ()) .expect("self.block_number is u32") } /// Fallback for the maximum number of validators participating in teyrchains consensus (a.k.a. /// active validators). pub(crate) fn fallback_max_validators() -> u32 { configuration::ActiveConfig::::get().max_validators.unwrap_or(1024) } /// Maximum number of validators participating in teyrchains consensus (a.k.a. active /// validators). fn max_validators(&self) -> u32 { self.max_validators.unwrap_or(Self::fallback_max_validators()) } /// Set the maximum number of active validators. #[cfg(not(feature = "runtime-benchmarks"))] pub(crate) fn set_max_validators(mut self, n: u32) -> Self { self.max_validators = Some(n); self } /// Maximum number of validators per core (a.k.a. max validators per group). This value is used /// if none is explicitly set on the builder. pub(crate) fn fallback_max_validators_per_core() -> u32 { configuration::ActiveConfig::::get() .scheduler_params .max_validators_per_core .unwrap_or(5) } /// Specify a mapping of core index/ para id to the number of dispute statements for the /// corresponding dispute statement set. Note that if the number of disputes is not specified /// it fallbacks to having a dispute per every validator. Additionally, an entry is not /// guaranteed to have a dispute - it must line up with the cores marked as disputed as defined /// in `Self::Build`. #[cfg(not(feature = "runtime-benchmarks"))] pub(crate) fn set_dispute_statements(mut self, m: BTreeMap) -> Self { self.dispute_statements = m; self } /// Toggle usage of v2 candidate descriptors. pub(crate) fn set_candidate_descriptor_v2(mut self, enable: bool) -> Self { self.candidate_descriptor_v2 = enable; self } /// Set an approved peer to be sent as a UMP signal. Only used for v2 descriptors pub(crate) fn set_approved_peer_signal(mut self, peer_id: ApprovedPeerId) -> Self { self.approved_peer_signal = Some(peer_id); self } /// Set the candidate modifier. pub(crate) fn set_candidate_modifier( mut self, modifier: Option>, ) -> Self { self.candidate_modifier = modifier; self } /// Get the maximum number of validators per core. fn max_validators_per_core(&self) -> u32 { self.max_validators_per_core.unwrap_or(Self::fallback_max_validators_per_core()) } /// Set maximum number of validators per core. #[cfg(not(feature = "runtime-benchmarks"))] pub(crate) fn set_max_validators_per_core(mut self, n: u32) -> Self { self.max_validators_per_core = Some(n); self } /// Get the maximum number of cores we expect from this configuration. pub(crate) fn max_cores(&self) -> u32 { self.max_validators() / self.max_validators_per_core() } /// Get the minimum number of validity votes in order for a backed candidate to be included. #[cfg(feature = "runtime-benchmarks")] pub(crate) fn fallback_min_backing_votes() -> u32 { 2 } fn mock_head_data() -> HeadData { let max_head_size = configuration::ActiveConfig::::get().max_head_data_size; HeadData(vec![0xFF; max_head_size as usize]) } fn candidate_descriptor_mock(para_id: ParaId) -> CandidateDescriptorV2 { CandidateDescriptorV2::new( para_id, Default::default(), CoreIndex(200), 2, Default::default(), Default::default(), Default::default(), Default::default(), mock_validation_code().hash(), ) } /// Create a mock of `CandidatePendingAvailability`. fn candidate_availability_mock( para_id: ParaId, group_idx: GroupIndex, core_idx: CoreIndex, candidate_hash: CandidateHash, availability_votes: BitVec, commitments: CandidateCommitments, ) -> inclusion::CandidatePendingAvailability> { inclusion::CandidatePendingAvailability::>::new( core_idx, // core candidate_hash, // hash Self::candidate_descriptor_mock(para_id), /* candidate descriptor */ commitments, // commitments availability_votes, /* availability * votes */ Default::default(), // backers Zero::zero(), // relay parent One::one(), /* relay chain block this * was backed in */ group_idx, // backing group ) } /// Add `CandidatePendingAvailability` and `CandidateCommitments` to the relevant storage items. /// /// NOTE: the default `CandidateCommitments` used does not include any data that would lead to /// heavy code paths in `enact_candidate`. But enact_candidates does return a weight which will /// get taken into account. fn add_availability( para_id: ParaId, core_idx: CoreIndex, group_idx: GroupIndex, availability_votes: BitVec, candidate_hash: CandidateHash, ) { let commitments = CandidateCommitments:: { upward_messages: Default::default(), horizontal_messages: Default::default(), new_validation_code: None, head_data: Self::mock_head_data(), processed_downward_messages: 0, hrmp_watermark: 0u32.into(), }; let candidate_availability = Self::candidate_availability_mock( para_id, group_idx, core_idx, candidate_hash, availability_votes, commitments, ); inclusion::PendingAvailability::::mutate(para_id, |maybe_candidates| { if let Some(candidates) = maybe_candidates { candidates.push_back(candidate_availability); } else { *maybe_candidates = Some([candidate_availability].into_iter().collect::>()); } }); } /// Create an `AvailabilityBitfield` where `concluding` is a map where each key is a core index /// that is concluding and `cores` is the total number of cores in the system. fn availability_bitvec(concluding_cores: &BTreeSet, cores: usize) -> AvailabilityBitfield { let mut bitfields = bitvec::bitvec![u8, bitvec::order::Lsb0; 0; 0]; for i in 0..cores { if concluding_cores.contains(&(i as u32)) { bitfields.push(true); } else { bitfields.push(false) } } bitfields.into() } /// Run to block number `to`, calling `initializer` `on_initialize` and `on_finalize` along the /// way. fn run_to_block(to: u32) { let to = to.into(); while pezframe_system::Pezpallet::::block_number() < to { let b = pezframe_system::Pezpallet::::block_number(); initializer::Pezpallet::::on_finalize(b); let b = b + One::one(); pezframe_system::Pezpallet::::set_block_number(b); initializer::Pezpallet::::on_initialize(b); } } /// Register `n_paras` count of teyrchains. /// /// Note that this must be called at least 2 sessions before the target session as there is a /// n+2 session delay for the scheduled actions to take effect. fn setup_para_ids(n_paras: usize) { // make sure teyrchains exist prior to session change. for i in 0..n_paras { let para_id = ParaId::from(i as u32); let validation_code = mock_validation_code(); paras::Pezpallet::::schedule_para_initialize( para_id, paras::ParaGenesisArgs { genesis_head: Self::mock_head_data(), validation_code: validation_code.clone(), para_kind: ParaKind::Teyrchain, }, ) .unwrap(); paras::Pezpallet::::add_trusted_validation_code( pezframe_system::Origin::::Root.into(), validation_code, ) .unwrap(); } } fn signing_context(&self) -> SigningContext { SigningContext { parent_hash: Self::header(self.block_number).hash(), session_index: self.session, } } /// Create a bitvec of `validators` length with all yes votes. fn validator_availability_votes_yes(validators: usize) -> BitVec { // every validator confirms availability. bitvec::bitvec![u8, bitvec::order::Lsb0; 1; validators as usize] } /// Setup session 1 and create `self.validators_map` and `self.validators`. fn setup_session( mut self, target_session: SessionIndex, validators: Vec<(T::AccountId, ValidatorId)>, // Total cores used in the scenario total_cores: usize, // Additional cores for elastic teyrchains extra_cores: usize, ) -> Self { let mut block = 1; for session in 0..target_session { initializer::Pezpallet::::test_trigger_on_new_session( false, session, validators.iter().map(|(a, v)| (a, v.clone())), None, ); block += 1; Self::run_to_block(block); } initializer::Pezpallet::::test_trigger_on_new_session( false, block - 1, validators.iter().map(|(a, v)| (a, v.clone())), None, ); initializer::Pezpallet::::on_finalize(block.into()); let block_number = BlockNumberFor::::from(block + 1); let header = Self::header(block_number); pezframe_system::Pezpallet::::reset_events(); pezframe_system::Pezpallet::::initialize( &header.number(), &header.hash(), header.digest(), ); initializer::Pezpallet::::on_initialize(*header.number()); assert_eq!(shared::CurrentSessionIndex::::get(), target_session); // We need to refetch validators since they have been shuffled. let validators_shuffled = session_info::Sessions::::get(target_session).unwrap().validators.clone(); self.validators = Some(validators_shuffled); self.block_number = block_number; self.session = target_session; assert_eq!(paras::Teyrchains::::get().len(), total_cores - extra_cores); self } /// Create a `UncheckedSigned for each validator where each core in /// `concluding_cores` is fully available. Additionally set up storage such that each /// `concluding_cores`is pending becoming fully available so the generated bitfields will be /// to the cores successfully being freed from the candidates being marked as available. fn create_availability_bitfields( &self, concluding_paras: &BTreeMap, elastic_paras: &BTreeMap, total_cores: usize, ) -> Vec> { let validators = self.validators.as_ref().expect("must have some validators prior to calling"); let mut current_core_idx = 0u32; let mut concluding_cores = BTreeSet::new(); for (seed, _) in concluding_paras.iter() { // make sure the candidates that will be concluding are marked as pending availability. let para_id = ParaId::from(*seed); for _chain_idx in 0..elastic_paras.get(&seed).cloned().unwrap_or(1) { let core_idx = CoreIndex::from(current_core_idx); let group_idx = scheduler::Pezpallet::::group_assigned_to_core(core_idx, self.block_number) .unwrap(); Self::add_availability( para_id, core_idx, group_idx, // No validators have made this candidate available yet. bitvec::bitvec![u8, bitvec::order::Lsb0; 0; validators.len()], CandidateHash(H256::from(byte32_slice_from(current_core_idx))), ); if !self.unavailable_cores.contains(¤t_core_idx) { concluding_cores.insert(current_core_idx); } current_core_idx += 1; } } let availability_bitvec = Self::availability_bitvec(&concluding_cores, total_cores); let bitfields: Vec> = validators .iter() .enumerate() .map(|(i, public)| { let unchecked_signed = UncheckedSigned::::benchmark_sign( public, availability_bitvec.clone(), &self.signing_context(), ValidatorIndex(i as u32), ); unchecked_signed }) .collect(); bitfields } /// Create backed candidates for `cores_with_backed_candidates`. You need these cores to be /// scheduled _within_ paras inherent, which requires marking the available bitfields as fully /// available. /// - `cores_with_backed_candidates` Mapping of `para_id` seed to number of /// validity votes. fn create_backed_candidates( &self, paras_with_backed_candidates: &BTreeMap, elastic_paras: &BTreeMap, includes_code_upgrade: Option, ) -> Vec> { let validators = self.validators.as_ref().expect("must have some validators prior to calling"); let config = configuration::ActiveConfig::::get(); let mut current_core_idx = 0u32; paras_with_backed_candidates .iter() .flat_map(|(seed, num_votes)| { assert!(*num_votes <= validators.len() as u32); let para_id = ParaId::from(*seed); let mut prev_head = None; // How many chained candidates we want to build ? (0..elastic_paras.get(&seed).cloned().unwrap_or(1)) .map(|chain_idx| { let core_idx = CoreIndex::from(current_core_idx); // Advance core index. current_core_idx += 1; let group_idx = scheduler::Pezpallet::::group_assigned_to_core( core_idx, self.block_number, ) .unwrap(); // This generates a pair and adds it to the keystore, returning just the // public. let header = Self::header(self.block_number); let relay_parent = header.hash(); // Set the head data so it can be used while validating the signatures on // the candidate receipt. let mut head_data = Self::mock_head_data(); if chain_idx == 0 { // Only first parahead of the chain needs to be set in storage. paras::Pezpallet::::heads_insert(¶_id, head_data.clone()); } else { // Make each candidate head data unique to avoid cycles. head_data.0[0] = chain_idx; } let persisted_validation_data_hash = PersistedValidationData:: { // To form a chain we set parent head to previous block if any, or // default to what is in storage already setup. parent_head: prev_head.take().unwrap_or(head_data.clone()), relay_parent_number: self.relay_parent_number(), relay_parent_storage_root: Default::default(), max_pov_size: config.max_pov_size, } .hash(); prev_head = Some(head_data.clone()); let pov_hash = Default::default(); let validation_code_hash = mock_validation_code().hash(); let mut past_code_meta = paras::ParaPastCodeMeta::>::default(); past_code_meta.note_replacement(0u32.into(), 0u32.into()); let group_validators = scheduler::Pezpallet::::group_validators(group_idx).unwrap(); let descriptor = CandidateDescriptorV2::new( para_id, relay_parent, core_idx, self.target_session, persisted_validation_data_hash, pov_hash, Default::default(), head_data.hash(), validation_code_hash, ); let mut candidate = CommittedCandidateReceipt:: { descriptor, commitments: CandidateCommitments:: { upward_messages: Default::default(), horizontal_messages: Default::default(), new_validation_code: includes_code_upgrade .map(|v| ValidationCode(vec![42u8; v as usize])), head_data, processed_downward_messages: 0, hrmp_watermark: self.relay_parent_number(), }, }; if self.candidate_descriptor_v2 { // `UMPSignal` separator. candidate.commitments.upward_messages.force_push(UMP_SEPARATOR); // `SelectCore` commitment. // Claim queue offset must be `0` so this candidate is for the very // next block. candidate.commitments.upward_messages.force_push( UMPSignal::SelectCore( CoreSelector(chain_idx as u8), ClaimQueueOffset(0), ) .encode(), ); if let Some(approved_peer_signal) = &self.approved_peer_signal { candidate.commitments.upward_messages.force_push( UMPSignal::ApprovedPeer(approved_peer_signal.clone()).encode(), ); } } // Maybe apply the candidate modifier if let Some(modifier) = self.candidate_modifier { candidate = modifier(candidate); } let candidate_hash = candidate.hash(); let validity_votes: Vec<_> = group_validators .iter() .take(*num_votes as usize) .map(|val_idx| { let public = validators.get(*val_idx).unwrap(); let sig = UncheckedSigned::::benchmark_sign( public, CompactStatement::Valid(candidate_hash), &self.signing_context(), *val_idx, ) .benchmark_signature(); ValidityAttestation::Explicit(sig.clone()) }) .collect(); BackedCandidate::::new( candidate, validity_votes, bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], core_idx, ) }) .collect::>() }) .collect() } /// Fill cores `start..last` with dispute statement sets. The statement sets will have 3/4th of /// votes be valid, and 1/4th of votes be invalid. fn create_disputes( &self, start: u32, last: u32, dispute_sessions: impl AsRef<[u32]>, ) -> Vec { let validators = self.validators.as_ref().expect("must have some validators prior to calling"); let dispute_sessions = dispute_sessions.as_ref(); let mut current_core_idx = start; (start..last) .map(|seed| { let dispute_session_idx = (seed - start) as usize; let session = dispute_sessions .get(dispute_session_idx) .cloned() .unwrap_or(self.target_session); let para_id = ParaId::from(seed); let core_idx = CoreIndex::from(current_core_idx); current_core_idx +=1; let group_idx = scheduler::Pezpallet::::group_assigned_to_core(core_idx, self.block_number) .unwrap(); let candidate_hash = CandidateHash(H256::from(byte32_slice_from(seed))); let relay_parent = H256::from(byte32_slice_from(seed)); Self::add_availability( para_id, core_idx, group_idx, Self::validator_availability_votes_yes(validators.len()), candidate_hash, ); let statements_len = self.dispute_statements.get(&seed).cloned().unwrap_or(validators.len() as u32); let statements = (0..statements_len) .map(|validator_index| { let validator_public = &validators.get(ValidatorIndex::from(validator_index)).expect("Test case is not borked. `ValidatorIndex` out of bounds of `ValidatorId`s."); // We need dispute statements on each side. And we don't want a revert log // so we make sure that we have a super majority with valid statements. let dispute_statement = if validator_index % 4 == 0 { DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) } else if validator_index < 3 { // Set two votes as backing for the dispute set to be accepted DisputeStatement::Valid( ValidDisputeStatementKind::BackingValid(relay_parent) ) } else { DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) }; let data = dispute_statement.payload_data(candidate_hash, session).unwrap(); let statement_sig = validator_public.sign(&data).unwrap(); (dispute_statement, ValidatorIndex(validator_index), statement_sig) }) .collect(); DisputeStatementSet { candidate_hash, session, statements } }) .collect() } /// Build a scenario for testing or benchmarks. /// /// Note that this API only allows building scenarios where the `backed_and_concluding_paras` /// are mutually exclusive with the cores for disputes. So /// `backed_and_concluding_paras.len() + dispute_sessions.len() + backed_in_inherent_paras` must /// be less than the max number of cores. pub(crate) fn build(self) -> Bench { // Make sure relevant storage is cleared. This is just to get the asserts to work when // running tests because it seems the storage is not cleared in between. #[allow(deprecated)] inclusion::PendingAvailability::::remove_all(None); // We don't allow a core to have both disputes and be marked fully available at this block. let max_cores = self.max_cores() as usize; let extra_cores = self .elastic_paras .values() .map(|count| *count as usize) .sum::() .saturating_sub(self.elastic_paras.len() as usize); let used_cores = self.dispute_sessions.len() + self.backed_and_concluding_paras.len() + self.backed_in_inherent_paras.len() + extra_cores; assert!(used_cores <= max_cores); // NOTE: there is an n+2 session delay for these actions to take effect. // We are currently in Session 0, so these changes will take effect in Session 2. Self::setup_para_ids(used_cores - extra_cores); configuration::Pezpallet::::set_coretime_cores_unchecked(used_cores as u32).unwrap(); let validator_ids = generate_validator_pairs::(self.max_validators()); let target_session = SessionIndex::from(self.target_session); let builder = self.setup_session(target_session, validator_ids, used_cores, extra_cores); let bitfields = builder.create_availability_bitfields( &builder.backed_and_concluding_paras, &builder.elastic_paras, scheduler::Pezpallet::::num_availability_cores(), ); let mut backed_in_inherent = BTreeMap::new(); backed_in_inherent.append(&mut builder.backed_and_concluding_paras.clone()); backed_in_inherent.append(&mut builder.backed_in_inherent_paras.clone()); let backed_candidates = builder.create_backed_candidates( &backed_in_inherent, &builder.elastic_paras, builder.code_upgrade, ); let disputes = builder.create_disputes( builder.backed_and_concluding_paras.len() as u32, (used_cores - extra_cores) as u32, builder.dispute_sessions.as_slice(), ); let mut disputed_cores = (builder.backed_and_concluding_paras.len() as u32 ..((used_cores - extra_cores) as u32)) .into_iter() .map(|idx| (idx, 0)) .collect::>(); let mut all_cores = builder.backed_and_concluding_paras.clone(); all_cores.append(&mut disputed_cores); assert_eq!(inclusion::PendingAvailability::::iter().count(), used_cores - extra_cores); // Sanity check that the occupied cores reported by the inclusion module are what we expect // to be. let mut core_idx = 0u32; let elastic_paras = &builder.elastic_paras; let mut occupied_cores = inclusion::Pezpallet::::get_occupied_cores() .map(|(core, candidate)| (core, candidate.candidate_descriptor().para_id())) .collect::>(); occupied_cores.sort_by(|(core_a, _), (core_b, _)| core_a.0.cmp(&core_b.0)); let mut expected_cores = all_cores .iter() .flat_map(|(para_id, _)| { (0..elastic_paras.get(¶_id).cloned().unwrap_or(1)) .map(|_para_local_core_idx| { let old_core_idx = core_idx; core_idx += 1; (CoreIndex(old_core_idx), ParaId::from(*para_id)) }) .collect::>() }) .collect::>(); expected_cores.sort_by(|(core_a, _), (core_b, _)| core_a.0.cmp(&core_b.0)); assert_eq!(expected_cores, occupied_cores); // We need entries in the claim queue for those: all_cores.append(&mut builder.backed_in_inherent_paras.clone()); let mut core_idx = 0u32; let cores = all_cores .keys() .flat_map(|para_id| { (0..elastic_paras.get(¶_id).cloned().unwrap_or(1)) .map(|_para_local_core_idx| { // Load an assignment into provider so that one is present to pop let assignment = ::AssignmentProvider::get_mock_assignment( CoreIndex(core_idx), ParaId::from(*para_id), ); core_idx += 1; (CoreIndex(core_idx - 1), [assignment].into()) }) .collect::)>>() }) .collect::>>(); scheduler::ClaimQueue::::set(cores); Bench:: { data: TeyrchainsInherentData { bitfields, backed_candidates, disputes, parent_header: Self::header(builder.block_number), }, _session: target_session, _block_number: builder.block_number, } } }