referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/paras_inherent/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Provides glue code over the scheduler and inclusion modules, and accepting
18//! one inherent per block that can include new para candidates and bitfields.
19//!
20//! Unlike other modules in this crate, it does not need to be initialized by the initializer,
21//! as it has no initialization logic and its finalization logic depends only on the details of
22//! this module.
23
24use crate::{
25	configuration, coretime,
26	disputes::DisputesHandler,
27	inclusion::{self, CandidateCheckContext},
28	initializer,
29	metrics::METRICS,
30	paras, scheduler,
31	shared::{self, AllowedSchedulingParentsTracker},
32	ParaId,
33};
34use alloc::{
35	collections::{btree_map::BTreeMap, btree_set::BTreeSet},
36	vec,
37	vec::Vec,
38};
39use bitvec::prelude::BitVec;
40use core::result::Result;
41use frame_support::{
42	defensive,
43	dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
44	inherent::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent},
45	pallet_prelude::*,
46	traits::Randomness,
47};
48
49use frame_system::pallet_prelude::*;
50use pallet_babe::{self, ParentBlockRandomness};
51use polkadot_primitives::{
52	effective_minimum_backing_votes, node_features::FeatureIndex, BackedCandidate,
53	CandidateDescriptorVersion, CandidateHash, CandidateReceiptV2 as CandidateReceipt,
54	CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CoreIndex, DisputeStatementSet,
55	HeadData, InherentData as ParachainsInherentData, MultiDisputeStatementSet,
56	ScrapedOnChainVotes, SessionIndex, SignedAvailabilityBitfields, SigningContext,
57	UncheckedSignedAvailabilityBitfield, UncheckedSignedAvailabilityBitfields, ValidatorId,
58	ValidatorIndex, ValidityAttestation, PARACHAINS_INHERENT_IDENTIFIER,
59};
60use rand::{seq::SliceRandom, SeedableRng};
61use scale_info::TypeInfo;
62use sp_runtime::traits::{Header as HeaderT, One, Saturating};
63
64mod misc;
65mod weights;
66
67use self::weights::checked_multi_dispute_statement_sets_weight;
68pub use self::{
69	misc::{IndexedRetain, IsSortedBy},
70	weights::{
71		backed_candidate_weight, backed_candidates_weight, dispute_statement_set_weight,
72		multi_dispute_statement_sets_weight, paras_inherent_total_weight, signed_bitfield_weight,
73		signed_bitfields_weight, TestWeightInfo, WeightInfo,
74	},
75};
76
77#[cfg(feature = "runtime-benchmarks")]
78mod benchmarking;
79
80#[cfg(test)]
81mod tests;
82
83const LOG_TARGET: &str = "runtime::inclusion-inherent";
84
85/// A bitfield concerning concluded disputes for candidates
86/// associated to the core index equivalent to the bit position.
87#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, Debug, TypeInfo)]
88pub(crate) struct DisputedBitfield(pub(crate) BitVec<u8, bitvec::order::Lsb0>);
89
90impl From<BitVec<u8, bitvec::order::Lsb0>> for DisputedBitfield {
91	fn from(inner: BitVec<u8, bitvec::order::Lsb0>) -> Self {
92		Self(inner)
93	}
94}
95
96#[cfg(test)]
97impl DisputedBitfield {
98	/// Create a new bitfield, where each bit is set to `false`.
99	pub fn zeros(n: usize) -> Self {
100		Self::from(BitVec::<u8, bitvec::order::Lsb0>::repeat(false, n))
101	}
102}
103
104pub use pallet::*;
105
106#[frame_support::pallet]
107pub mod pallet {
108	use super::*;
109
110	#[pallet::pallet]
111	#[pallet::without_storage_info]
112	pub struct Pallet<T>(_);
113
114	#[pallet::config]
115	#[pallet::disable_frame_system_supertrait_check]
116	pub trait Config:
117		inclusion::Config
118		+ scheduler::Config
119		+ initializer::Config
120		+ coretime::Config
121		+ pallet_babe::Config
122	{
123		/// Weight information for extrinsics in this pallet.
124		type WeightInfo: WeightInfo;
125	}
126
127	#[pallet::error]
128	pub enum Error<T> {
129		/// Inclusion inherent called more than once per block.
130		TooManyInclusionInherents,
131		/// The hash of the submitted parent header doesn't correspond to the saved block hash of
132		/// the parent.
133		InvalidParentHeader,
134		/// Inherent data was filtered during execution. This should have only been done
135		/// during creation.
136		InherentDataFilteredDuringExecution,
137		/// Too many candidates supplied.
138		UnscheduledCandidate,
139	}
140
141	/// Whether the paras inherent was included within this block.
142	///
143	/// The `Option<()>` is effectively a `bool`, but it never hits storage in the `None` variant
144	/// due to the guarantees of FRAME's storage APIs.
145	///
146	/// If this is `None` at the end of the block, we panic and render the block invalid.
147	#[pallet::storage]
148	pub(crate) type Included<T> = StorageValue<_, ()>;
149
150	/// Scraped on chain data for extracting resolved disputes as well as backing votes.
151	#[pallet::storage]
152	pub type OnChainVotes<T: Config> = StorageValue<_, ScrapedOnChainVotes<T::Hash>>;
153
154	/// Update the disputes statements set part of the on-chain votes.
155	pub(crate) fn set_scrapable_on_chain_disputes<T: Config>(
156		session: SessionIndex,
157		checked_disputes: CheckedMultiDisputeStatementSet,
158	) {
159		crate::paras_inherent::OnChainVotes::<T>::mutate(move |value| {
160			let disputes =
161				checked_disputes.into_iter().map(DisputeStatementSet::from).collect::<Vec<_>>();
162			let backing_validators_per_candidate = match value.take() {
163				Some(v) => v.backing_validators_per_candidate,
164				None => Vec::new(),
165			};
166			*value = Some(ScrapedOnChainVotes::<T::Hash> {
167				backing_validators_per_candidate,
168				disputes,
169				session,
170			});
171		})
172	}
173
174	/// Update the backing votes including part of the on-chain votes.
175	pub(crate) fn set_scrapable_on_chain_backings<T: Config>(
176		session: SessionIndex,
177		backing_validators_per_candidate: Vec<(
178			CandidateReceipt<T::Hash>,
179			Vec<(ValidatorIndex, ValidityAttestation)>,
180		)>,
181	) {
182		crate::paras_inherent::OnChainVotes::<T>::mutate(move |value| {
183			let disputes = match value.take() {
184				Some(v) => v.disputes,
185				None => MultiDisputeStatementSet::default(),
186			};
187			*value = Some(ScrapedOnChainVotes::<T::Hash> {
188				backing_validators_per_candidate,
189				disputes,
190				session,
191			});
192		})
193	}
194
195	#[pallet::hooks]
196	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
197		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
198			T::DbWeight::get().reads_writes(1, 1) // in `on_finalize`.
199		}
200
201		fn on_finalize(_: BlockNumberFor<T>) {
202			if Included::<T>::take().is_none() {
203				panic!("ParachainInherent was not executed in this block. This is a bug. Please report this at https://github.com/paritytech/polkadot-sdk/issues.");
204			}
205		}
206	}
207
208	#[pallet::inherent]
209	impl<T: Config> ProvideInherent for Pallet<T> {
210		type Call = Call<T>;
211		type Error = MakeFatalError<()>;
212		const INHERENT_IDENTIFIER: InherentIdentifier = PARACHAINS_INHERENT_IDENTIFIER;
213
214		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
215			let inherent_data = Self::create_inherent_inner(data)?;
216
217			Some(Call::enter { data: inherent_data })
218		}
219
220		fn is_inherent(call: &Self::Call) -> bool {
221			matches!(call, Call::enter { .. })
222		}
223	}
224
225	#[pallet::call]
226	impl<T: Config> Pallet<T> {
227		/// Enter the paras inherent. This will process bitfields and backed candidates.
228		#[pallet::call_index(0)]
229		#[pallet::weight((
230			paras_inherent_total_weight::<T>(
231				data.backed_candidates.as_slice(),
232				&data.bitfields,
233				&data.disputes,
234			),
235			DispatchClass::Mandatory,
236		))]
237		pub fn enter(
238			origin: OriginFor<T>,
239			data: ParachainsInherentData<HeaderFor<T>>,
240		) -> DispatchResultWithPostInfo {
241			ensure_none(origin)?;
242
243			ensure!(!Included::<T>::exists(), Error::<T>::TooManyInclusionInherents);
244			Included::<T>::set(Some(()));
245			let initial_data = data.clone();
246
247			Self::process_inherent_data(data).and_then(|(processed, post_info)| {
248				ensure!(initial_data == processed, Error::<T>::InherentDataFilteredDuringExecution);
249				Ok(post_info)
250			})
251		}
252	}
253}
254
255impl<T: Config> Pallet<T> {
256	/// Create the `ParachainsInherentData` that gets passed to [`Self::enter`] in
257	/// [`Self::create_inherent`]. This code is pulled out of [`Self::create_inherent`] so it can be
258	/// unit tested.
259	fn create_inherent_inner(data: &InherentData) -> Option<ParachainsInherentData<HeaderFor<T>>> {
260		let parachains_inherent_data = match data.get_data(&Self::INHERENT_IDENTIFIER) {
261			Ok(Some(d)) => d,
262			Ok(None) => return None,
263			Err(_) => {
264				log::warn!(target: LOG_TARGET, "ParachainsInherentData failed to decode");
265				return None;
266			},
267		};
268		match Self::process_inherent_data(parachains_inherent_data) {
269			Ok((processed, _)) => Some(processed),
270			Err(err) => {
271				log::warn!(target: LOG_TARGET, "Processing inherent data failed: {:?}", err);
272				None
273			},
274		}
275	}
276
277	/// Process inherent data.
278	///
279	/// The given inherent data is processed and state is altered accordingly. If any data could
280	/// not be applied (inconsistencies, weight limit, ...) it is removed.
281	///
282	/// Returns: Result containing processed inherent data and weight, the processed inherent would
283	/// consume.
284	fn process_inherent_data(
285		data: ParachainsInherentData<HeaderFor<T>>,
286	) -> Result<(ParachainsInherentData<HeaderFor<T>>, PostDispatchInfo), DispatchErrorWithPostInfo>
287	{
288		#[cfg(feature = "runtime-metrics")]
289		sp_io::init_tracing();
290
291		let ParachainsInherentData {
292			mut bitfields,
293			mut backed_candidates,
294			parent_header,
295			mut disputes,
296		} = data;
297
298		log::debug!(
299			target: LOG_TARGET,
300			"[process_inherent_data] bitfields.len(): {}, backed_candidates.len(): {}, disputes.len() {}",
301			bitfields.len(),
302			backed_candidates.len(),
303			disputes.len()
304		);
305
306		let parent_hash = frame_system::Pallet::<T>::parent_hash();
307
308		ensure!(
309			parent_header.hash().as_ref() == parent_hash.as_ref(),
310			Error::<T>::InvalidParentHeader,
311		);
312
313		let now = frame_system::Pallet::<T>::block_number();
314		let config = configuration::ActiveConfig::<T>::get();
315
316		let current_session = shared::CurrentSessionIndex::<T>::get();
317
318		// Before anything else, update the allowed scheduling and relay parents.
319		{
320			let parent_number = now.saturating_sub(One::one());
321			let parent_storage_root = *parent_header.state_root();
322
323			shared::Pallet::<T>::new_block(
324				parent_hash,
325				scheduler::Pallet::<T>::claim_queue(),
326				parent_number,
327				config.scheduler_params.lookahead,
328				parent_storage_root,
329				current_session,
330			);
331		}
332
333		let candidates_weight = backed_candidates_weight::<T>(&backed_candidates);
334		let bitfields_weight = signed_bitfields_weight::<T>(&bitfields);
335		let disputes_weight = multi_dispute_statement_sets_weight::<T>(&disputes);
336
337		// Weight before filtering/sanitization except for enacting the candidates
338		let weight_before_filtering = candidates_weight + bitfields_weight + disputes_weight;
339
340		METRICS.on_before_filter(weight_before_filtering.ref_time());
341		log::debug!(target: LOG_TARGET, "Size before filter: {}, candidates + bitfields: {}, disputes: {}", weight_before_filtering.proof_size(), candidates_weight.proof_size() + bitfields_weight.proof_size(), disputes_weight.proof_size());
342		log::debug!(target: LOG_TARGET, "Time weight before filter: {}, candidates + bitfields: {}, disputes: {}", weight_before_filtering.ref_time(), candidates_weight.ref_time() + bitfields_weight.ref_time(), disputes_weight.ref_time());
343
344		let expected_bits = scheduler::Pallet::<T>::num_availability_cores();
345		let validator_public = shared::ActiveValidatorKeys::<T>::get();
346
347		// We are assuming (incorrectly) to have all the weight (for the mandatory class or even
348		// full block) available to us. This can lead to slightly overweight blocks, which still
349		// works as the dispatch class for `enter` is `Mandatory`. By using the `Mandatory`
350		// dispatch class, the upper layers impose no limit on the weight of this inherent, instead
351		// we limit ourselves and make sure to stay within reasonable bounds. It might make sense
352		// to subtract BlockWeights::base_block to reduce chances of becoming overweight.
353		let max_block_weight = {
354			let dispatch_class = DispatchClass::Mandatory;
355			let max_block_weight_full = <T as frame_system::Config>::BlockWeights::get();
356			log::debug!(target: LOG_TARGET, "Max block weight: {}", max_block_weight_full.max_block);
357			// Get max block weight for the mandatory class if defined, otherwise total max weight
358			// of the block.
359			let max_weight = max_block_weight_full
360				.per_class
361				.get(dispatch_class)
362				.max_total
363				.unwrap_or(max_block_weight_full.max_block);
364			log::debug!(target: LOG_TARGET, "Used max block time weight: {}", max_weight);
365
366			let max_block_size_full = <T as frame_system::Config>::BlockLength::get();
367			let max_block_size = max_block_size_full.max.get(dispatch_class);
368			log::debug!(target: LOG_TARGET, "Used max block size: {}", max_block_size);
369
370			// Adjust proof size to max block size as we are tracking tx size.
371			max_weight.set_proof_size(*max_block_size as u64)
372		};
373		log::debug!(target: LOG_TARGET, "Used max block weight: {}", max_block_weight);
374
375		let entropy = compute_entropy::<T>(parent_hash);
376		let mut rng = rand_chacha::ChaChaRng::from_seed(entropy.into());
377
378		// Filter out duplicates and continue.
379		if let Err(()) = T::DisputesHandler::deduplicate_and_sort_dispute_data(&mut disputes) {
380			log::debug!(target: LOG_TARGET, "Found duplicate statement sets, retaining the first");
381		}
382
383		let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period;
384
385		let dispute_statement_set_valid = move |set: DisputeStatementSet| {
386			T::DisputesHandler::filter_dispute_data(set, post_conclusion_acceptance_period)
387		};
388
389		// Limit the disputes first, since the following statements depend on the votes included
390		// here.
391		let (checked_disputes_sets, checked_disputes_sets_consumed_weight) =
392			limit_and_sanitize_disputes::<T, _>(
393				disputes,
394				dispute_statement_set_valid,
395				max_block_weight,
396			);
397
398		let mut all_weight_after = {
399			// Assure the maximum block weight is adhered, by limiting bitfields and backed
400			// candidates. Dispute statement sets were already limited before.
401			let non_disputes_weight = apply_weight_limit::<T>(
402				&mut backed_candidates,
403				&mut bitfields,
404				max_block_weight.saturating_sub(checked_disputes_sets_consumed_weight),
405				&mut rng,
406			);
407
408			let all_weight_after =
409				non_disputes_weight.saturating_add(checked_disputes_sets_consumed_weight);
410
411			METRICS.on_after_filter(all_weight_after.ref_time());
412			log::debug!(
413				target: LOG_TARGET,
414				"[process_inherent_data] after filter: bitfields.len(): {}, backed_candidates.len(): {}, checked_disputes_sets.len() {}",
415				bitfields.len(),
416				backed_candidates.len(),
417				checked_disputes_sets.len()
418			);
419			log::debug!(target: LOG_TARGET, "Size after filter: {}, candidates + bitfields: {}, disputes: {}", all_weight_after.proof_size(), non_disputes_weight.proof_size(), checked_disputes_sets_consumed_weight.proof_size());
420			log::debug!(target: LOG_TARGET, "Time weight after filter: {}, candidates + bitfields: {}, disputes: {}", all_weight_after.ref_time(), non_disputes_weight.ref_time(), checked_disputes_sets_consumed_weight.ref_time());
421
422			if all_weight_after.any_gt(max_block_weight) {
423				log::warn!(target: LOG_TARGET, "Post weight limiting weight is still too large, time: {}, size: {}", all_weight_after.ref_time(), all_weight_after.proof_size());
424			}
425			all_weight_after
426		};
427
428		// Note that `process_checked_multi_dispute_data` will iterate and import each
429		// dispute; so the input here must be reasonably bounded,
430		// which is guaranteed by the checks and weight limitation above.
431		// We don't care about fresh or not disputes
432		// this writes them to storage, so let's query it via those means
433		// if this fails for whatever reason, that's ok.
434		if let Err(e) =
435			T::DisputesHandler::process_checked_multi_dispute_data(&checked_disputes_sets)
436		{
437			log::warn!(target: LOG_TARGET, "MultiDisputesData failed to update: {:?}", e);
438		};
439		METRICS.on_disputes_imported(checked_disputes_sets.len() as u64);
440
441		set_scrapable_on_chain_disputes::<T>(current_session, checked_disputes_sets.clone());
442
443		if T::DisputesHandler::is_frozen() {
444			// Relay chain freeze, at this point we will not include any parachain blocks.
445			METRICS.on_relay_chain_freeze();
446
447			let disputes = checked_disputes_sets
448				.into_iter()
449				.map(|checked| checked.into())
450				.collect::<Vec<_>>();
451			let processed = ParachainsInherentData {
452				bitfields: Vec::new(),
453				backed_candidates: Vec::new(),
454				disputes,
455				parent_header,
456			};
457
458			// The relay chain we are currently on is invalid. Proceed no further on parachains.
459			return Ok((processed, Some(checked_disputes_sets_consumed_weight).into()));
460		}
461
462		// Contains the disputes that are concluded in the current session only,
463		// since these are the only ones that are relevant for the occupied cores
464		// and lightens the load on `free_disputed` significantly.
465		// Cores can't be occupied with candidates of the previous sessions, and only
466		// things with new votes can have just concluded. We only need to collect
467		// cores with disputes that conclude just now, because disputes that
468		// concluded longer ago have already had any corresponding cores cleaned up.
469		let current_concluded_invalid_disputes = checked_disputes_sets
470			.iter()
471			.map(AsRef::as_ref)
472			.filter(|dss| dss.session == current_session)
473			.map(|dss| (dss.session, dss.candidate_hash))
474			.filter(|(session, candidate)| {
475				<T>::DisputesHandler::concluded_invalid(*session, *candidate)
476			})
477			.map(|(_session, candidate)| candidate)
478			.collect::<BTreeSet<CandidateHash>>();
479
480		// Get the cores freed as a result of concluded invalid candidates.
481		let (freed_disputed, concluded_invalid_hashes): (Vec<CoreIndex>, BTreeSet<CandidateHash>) =
482			inclusion::Pallet::<T>::free_disputed(&current_concluded_invalid_disputes)
483				.into_iter()
484				.unzip();
485
486		// Create a bit index from the set of core indices where each index corresponds to
487		// a core index that was freed due to a dispute.
488		//
489		// I.e. 010100 would indicate, the candidates on Core 1 and 3 would be disputed.
490		let disputed_bitfield = create_disputed_bitfield(expected_bits, freed_disputed.iter());
491
492		let bitfields = sanitize_bitfields::<T>(
493			bitfields,
494			disputed_bitfield,
495			expected_bits,
496			parent_hash,
497			current_session,
498			&validator_public[..],
499		);
500		METRICS.on_bitfields_processed(bitfields.len() as u64);
501
502		// Process new availability bitfields, yielding any availability cores whose
503		// work has now concluded.
504		let (enact_weight, freed_concluded) =
505			inclusion::Pallet::<T>::update_pending_availability_and_get_freed_cores(
506				&validator_public[..],
507				bitfields.clone(),
508			);
509		all_weight_after.saturating_accrue(enact_weight);
510		log::debug!(
511			target: LOG_TARGET,
512			"Enacting weight: {}, all weight: {}",
513			enact_weight.ref_time(),
514			all_weight_after.ref_time(),
515		);
516
517		// It's possible that that after the enacting the candidates, the total weight
518		// goes over the limit, however, we can't do anything about it at this point.
519		// By using the `Mandatory` weight, we ensure the block is still accepted,
520		// but no other (user) transactions can be included.
521		if all_weight_after.any_gt(max_block_weight) {
522			log::warn!(
523				target: LOG_TARGET,
524				"Overweight para inherent data after enacting the candidates {:?}: {} > {}",
525				parent_hash,
526				all_weight_after,
527				max_block_weight,
528			);
529		}
530
531		// Inform the disputes module of all included candidates.
532		for (_, candidate_hash) in &freed_concluded {
533			T::DisputesHandler::note_included(current_session, *candidate_hash, now);
534		}
535
536		METRICS.on_candidates_included(freed_concluded.len() as u64);
537
538		// Get the timed out candidates
539		let freed_timeout = if scheduler::Pallet::<T>::availability_timeout_check_required() {
540			inclusion::Pallet::<T>::free_timedout()
541		} else {
542			Vec::new()
543		};
544
545		if !freed_timeout.is_empty() {
546			log::debug!(target: LOG_TARGET, "Evicted timed out cores: {:?}", freed_timeout);
547		}
548
549		// Back candidates.
550		let (candidate_receipt_with_backing_validator_indices, backed_candidates_with_core) =
551			Self::back_candidates(concluded_invalid_hashes, backed_candidates)?;
552
553		set_scrapable_on_chain_backings::<T>(
554			current_session,
555			candidate_receipt_with_backing_validator_indices,
556		);
557
558		let disputes = checked_disputes_sets
559			.into_iter()
560			.map(|checked| checked.into())
561			.collect::<Vec<_>>();
562
563		let bitfields = bitfields.into_iter().map(|v| v.into_unchecked()).collect();
564
565		let count = backed_candidates_with_core.len();
566		let processed = ParachainsInherentData {
567			bitfields,
568			backed_candidates: backed_candidates_with_core.into_iter().fold(
569				Vec::with_capacity(count),
570				|mut acc, (_id, candidates)| {
571					acc.extend(candidates.into_iter().map(|(c, _)| c));
572					acc
573				},
574			),
575			disputes,
576			parent_header,
577		};
578		Ok((processed, Some(all_weight_after).into()))
579	}
580
581	fn back_candidates(
582		concluded_invalid_hashes: BTreeSet<CandidateHash>,
583		backed_candidates: Vec<BackedCandidate<T::Hash>>,
584	) -> Result<
585		(
586			Vec<(CandidateReceipt<T::Hash>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
587			BTreeMap<ParaId, Vec<(BackedCandidate<T::Hash>, CoreIndex)>>,
588		),
589		DispatchErrorWithPostInfo,
590	> {
591		let allowed_scheduling_parents = shared::AllowedSchedulingParents::<T>::get();
592
593		let upcoming_new_session = initializer::Pallet::<T>::upcoming_session_change();
594
595		METRICS.on_candidates_processed_total(backed_candidates.len() as u64);
596
597		let occupied_cores: BTreeSet<_> =
598			inclusion::Pallet::<T>::get_occupied_cores().map(|(core, _)| core).collect();
599
600		let mut eligible: BTreeMap<ParaId, BTreeSet<CoreIndex>> = BTreeMap::new();
601
602		let is_blocked = |core_idx| occupied_cores.contains(&core_idx) || upcoming_new_session;
603		let scheduled = scheduler::Pallet::<T>::advance_claim_queue(is_blocked);
604		let total_eligible_cores = scheduled.len();
605
606		for (core_idx, para_id) in scheduled {
607			eligible.entry(para_id).or_default().insert(core_idx);
608		}
609
610		let node_features = configuration::ActiveConfig::<T>::get().node_features;
611		let v3_enabled = FeatureIndex::CandidateReceiptV3.is_set(&node_features);
612
613		let backed_candidates_with_core = sanitize_backed_candidates::<T>(
614			backed_candidates,
615			&allowed_scheduling_parents,
616			concluded_invalid_hashes,
617			eligible,
618			v3_enabled,
619		);
620		let count = count_backed_candidates(&backed_candidates_with_core);
621
622		ensure!(count <= total_eligible_cores, Error::<T>::UnscheduledCandidate);
623
624		METRICS.on_candidates_sanitized(count as u64);
625
626		// Process backed candidates according to scheduled cores.
627		let candidate_receipt_with_backing_validator_indices =
628			inclusion::Pallet::<T>::process_candidates(
629				&allowed_scheduling_parents,
630				&backed_candidates_with_core,
631				scheduler::Pallet::<T>::group_validators,
632			)?;
633
634		Ok((candidate_receipt_with_backing_validator_indices, backed_candidates_with_core))
635	}
636}
637
638/// Derive a bitfield from dispute
639pub(super) fn create_disputed_bitfield<'a, I>(
640	expected_bits: usize,
641	freed_cores: I,
642) -> DisputedBitfield
643where
644	I: 'a + IntoIterator<Item = &'a CoreIndex>,
645{
646	let mut bitvec = BitVec::repeat(false, expected_bits);
647	for core_idx in freed_cores {
648		let core_idx = core_idx.0 as usize;
649		if core_idx < expected_bits {
650			bitvec.set(core_idx, true);
651		}
652	}
653	DisputedBitfield::from(bitvec)
654}
655
656/// Select a random subset, with preference for certain indices.
657///
658/// Adds random items to the set until all candidates
659/// are tried or the remaining weight is depleted.
660///
661/// Returns the weight of all selected items from `selectables`
662/// as well as their indices in ascending order.
663fn random_sel<X, F: Fn(&X) -> Weight>(
664	rng: &mut rand_chacha::ChaChaRng,
665	selectables: &[X],
666	mut preferred_indices: Vec<usize>,
667	weight_fn: F,
668	weight_limit: Weight,
669) -> (Weight, Vec<usize>) {
670	if selectables.is_empty() {
671		return (Weight::zero(), Vec::new());
672	}
673	// all indices that are not part of the preferred set
674	let mut indices = (0..selectables.len())
675		.into_iter()
676		.filter(|idx| !preferred_indices.contains(idx))
677		.collect::<Vec<_>>();
678	let mut picked_indices = Vec::with_capacity(selectables.len().saturating_sub(1));
679
680	let mut weight_acc = Weight::zero();
681
682	preferred_indices.shuffle(rng);
683	for preferred_idx in preferred_indices {
684		// preferred indices originate from outside
685		if let Some(item) = selectables.get(preferred_idx) {
686			let updated = weight_acc.saturating_add(weight_fn(item));
687			if updated.any_gt(weight_limit) {
688				continue;
689			}
690			weight_acc = updated;
691			picked_indices.push(preferred_idx);
692		}
693	}
694
695	indices.shuffle(rng);
696	for idx in indices {
697		let item = &selectables[idx];
698		let updated = weight_acc.saturating_add(weight_fn(item));
699
700		if updated.any_gt(weight_limit) {
701			continue;
702		}
703		weight_acc = updated;
704
705		picked_indices.push(idx);
706	}
707
708	// sorting indices, so the ordering is retained
709	// unstable sorting is fine, since there are no duplicates in indices
710	// and even if there were, they don't have an identity
711	picked_indices.sort_unstable();
712	(weight_acc, picked_indices)
713}
714
715/// Considers an upper threshold that the inherent data must not exceed.
716///
717/// If there is sufficient space, all bitfields and all candidates
718/// will be included.
719///
720/// Otherwise tries to include all disputes, and then tries to fill the remaining space with
721/// bitfields and then candidates.
722///
723/// The selection process is random. For candidates, there is an exception for code upgrades as they
724/// are preferred. And for disputes, local and older disputes are preferred (see
725/// `limit_and_sanitize_disputes`). for backed candidates, since with a increasing number of
726/// parachains their chances of inclusion become slim. All backed candidates  are checked
727/// beforehand in `fn create_inherent_inner` which guarantees sanity.
728///
729/// Assumes disputes are already filtered by the time this is called.
730///
731/// Returns the total weight consumed by `bitfields` and `candidates`.
732pub(crate) fn apply_weight_limit<T: Config + inclusion::Config>(
733	candidates: &mut Vec<BackedCandidate<<T>::Hash>>,
734	bitfields: &mut UncheckedSignedAvailabilityBitfields,
735	max_consumable_weight: Weight,
736	rng: &mut rand_chacha::ChaChaRng,
737) -> Weight {
738	let total_candidates_weight = backed_candidates_weight::<T>(candidates.as_slice());
739
740	let total_bitfields_weight = signed_bitfields_weight::<T>(&bitfields);
741
742	let total = total_bitfields_weight.saturating_add(total_candidates_weight);
743
744	// candidates + bitfields fit into the block
745	if max_consumable_weight.all_gte(total) {
746		return total;
747	}
748
749	// Invariant: block author provides candidate in the order in which they form a chain
750	// wrt elastic scaling. If the invariant is broken, we'd fail later when filtering candidates
751	// which are unchained.
752
753	let mut chained_candidates: Vec<Vec<_>> = Vec::new();
754	let mut current_para_id = None;
755
756	for candidate in core::mem::take(candidates).into_iter() {
757		let candidate_para_id = candidate.descriptor().para_id();
758		if Some(candidate_para_id) == current_para_id {
759			let chain = chained_candidates
760				.last_mut()
761				.expect("if the current_para_id is Some, then vec is not empty; qed");
762			chain.push(candidate);
763		} else {
764			current_para_id = Some(candidate_para_id);
765			chained_candidates.push(vec![candidate]);
766		}
767	}
768
769	// Elastic scaling: we prefer chains that have a code upgrade among the candidates,
770	// as the candidates containing the upgrade tend to be large and hence stand no chance to
771	// be picked late while maintaining the weight bounds.
772	//
773	// Limitations: For simplicity if total weight of a chain of candidates is larger than
774	// the remaining weight, the chain will still not be included while it could still be possible
775	// to include part of that chain.
776	let preferred_chain_indices = chained_candidates
777		.iter()
778		.enumerate()
779		.filter_map(|(idx, candidates)| {
780			// Check if any of the candidate in chain contains a code upgrade.
781			if candidates
782				.iter()
783				.any(|candidate| candidate.candidate().commitments.new_validation_code.is_some())
784			{
785				Some(idx)
786			} else {
787				None
788			}
789		})
790		.collect::<Vec<usize>>();
791
792	// There is weight remaining to be consumed by a subset of chained candidates
793	// which are going to be picked now.
794	if let Some(max_consumable_by_candidates) =
795		max_consumable_weight.checked_sub(&total_bitfields_weight)
796	{
797		let (acc_candidate_weight, chained_indices) =
798			random_sel::<Vec<BackedCandidate<<T as frame_system::Config>::Hash>>, _>(
799				rng,
800				&chained_candidates,
801				preferred_chain_indices,
802				|candidates| backed_candidates_weight::<T>(&candidates),
803				max_consumable_by_candidates,
804			);
805		log::debug!(target: LOG_TARGET, "Indices Candidates: {:?}, size: {}", chained_indices, candidates.len());
806		chained_candidates
807			.indexed_retain(|idx, _backed_candidates| chained_indices.binary_search(&idx).is_ok());
808		// pick all bitfields, and
809		// fill the remaining space with candidates
810		let total_consumed = acc_candidate_weight.saturating_add(total_bitfields_weight);
811
812		*candidates = chained_candidates.into_iter().flatten().collect::<Vec<_>>();
813
814		return total_consumed;
815	}
816
817	candidates.clear();
818
819	// insufficient space for even the bitfields alone, so only try to fit as many of those
820	// into the block and skip the candidates entirely
821	let (total_consumed, indices) = random_sel::<UncheckedSignedAvailabilityBitfield, _>(
822		rng,
823		&bitfields,
824		vec![],
825		|bitfield| signed_bitfield_weight::<T>(&bitfield),
826		max_consumable_weight,
827	);
828	log::debug!(target: LOG_TARGET, "Indices Bitfields: {:?}, size: {}", indices, bitfields.len());
829
830	bitfields.indexed_retain(|idx, _bitfield| indices.binary_search(&idx).is_ok());
831
832	total_consumed
833}
834
835/// Filter bitfields based on freed core indices, validity, and other sanity checks.
836///
837/// Do sanity checks on the bitfields:
838///
839///  1. no more than one bitfield per validator
840///  2. bitfields are ascending by validator index.
841///  3. each bitfield has exactly `expected_bits`
842///  4. signature is valid
843///  5. remove any disputed core indices
844///
845/// If any of those is not passed, the bitfield is dropped.
846pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
847	unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
848	disputed_bitfield: DisputedBitfield,
849	expected_bits: usize,
850	parent_hash: T::Hash,
851	session_index: SessionIndex,
852	validators: &[ValidatorId],
853) -> SignedAvailabilityBitfields {
854	let mut bitfields = Vec::with_capacity(unchecked_bitfields.len());
855
856	let mut last_index: Option<ValidatorIndex> = None;
857
858	if disputed_bitfield.0.len() != expected_bits {
859		// This is a system logic error that should never occur, but we want to handle it gracefully
860		// so we just drop all bitfields
861		log::error!(target: LOG_TARGET, "BUG: disputed_bitfield != expected_bits");
862		return vec![];
863	}
864
865	let all_zeros = BitVec::<u8, bitvec::order::Lsb0>::repeat(false, expected_bits);
866	let signing_context = SigningContext { parent_hash, session_index };
867	for unchecked_bitfield in unchecked_bitfields {
868		// Find and skip invalid bitfields.
869		if unchecked_bitfield.unchecked_payload().0.len() != expected_bits {
870			log::trace!(
871				target: LOG_TARGET,
872				"bad bitfield length: {} != {:?}",
873				unchecked_bitfield.unchecked_payload().0.len(),
874				expected_bits,
875			);
876			continue;
877		}
878
879		if unchecked_bitfield.unchecked_payload().0.clone() & disputed_bitfield.0.clone() !=
880			all_zeros
881		{
882			log::trace!(
883				target: LOG_TARGET,
884				"bitfield contains disputed cores: {:?}",
885				unchecked_bitfield.unchecked_payload().0.clone() & disputed_bitfield.0.clone()
886			);
887			continue;
888		}
889
890		let validator_index = unchecked_bitfield.unchecked_validator_index();
891
892		if !last_index.map_or(true, |last_index: ValidatorIndex| last_index < validator_index) {
893			log::trace!(
894				target: LOG_TARGET,
895				"bitfield validator index is not greater than last: !({:?} < {})",
896				last_index.as_ref().map(|x| x.0),
897				validator_index.0
898			);
899			continue;
900		}
901
902		if unchecked_bitfield.unchecked_validator_index().0 as usize >= validators.len() {
903			log::trace!(
904				target: LOG_TARGET,
905				"bitfield validator index is out of bounds: {} >= {}",
906				validator_index.0,
907				validators.len(),
908			);
909			continue;
910		}
911
912		let validator_public = &validators[validator_index.0 as usize];
913
914		// Validate bitfield signature.
915		if let Ok(signed_bitfield) =
916			unchecked_bitfield.try_into_checked(&signing_context, validator_public)
917		{
918			bitfields.push(signed_bitfield);
919			METRICS.on_valid_bitfield_signature();
920		} else {
921			log::warn!(target: LOG_TARGET, "Invalid bitfield signature");
922			METRICS.on_invalid_bitfield_signature();
923		};
924
925		last_index = Some(validator_index);
926	}
927	bitfields
928}
929
930/// Perform required checks for given candidate receipt.
931///
932/// Returns `true` if the candidate passes all version and signal checks.
933///
934/// Validate descriptor version, relay/scheduling parent, session, and UMP signals.
935///
936/// This is the first check in the sanitization pipeline. It establishes invariants that
937/// downstream checks (notably `verify_backed_candidate`) rely on.
938///
939/// Returns `false` if:
940/// - the descriptor version is unknown
941/// - version consistency check fails (old/new detection rules disagree unexpectedly)
942/// - version 3 descriptors are present but v3 is not enabled
943/// - the relay parent is not in the allowed relay parents for the relevant session:
944/// - the scheduling parent is not in the allowed scheduling parents
945/// - UMP signal parsing fails
946/// - for V2/V3: scheduling_session != current session
947/// - for V2/V3: the core index in descriptor doesn't match the one computed from the commitments,
948///   or the `SelectCore` signal does not refer to a core at the top of claim queue
949fn check_descriptor_version_and_signals<T: crate::inclusion::Config>(
950	candidate: &BackedCandidate<T::Hash>,
951	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
952	v3_enabled: bool,
953) -> bool {
954	let current_session_index = shared::CurrentSessionIndex::<T>::get();
955	let descriptor_version = candidate.descriptor().version();
956
957	if matches!(descriptor_version, CandidateDescriptorVersion::Unknown(_)) {
958		log::debug!(
959			target: LOG_TARGET,
960			"Candidate with unknown descriptor version. Dropping candidate {:?} for paraid {:?}.",
961			candidate.candidate().hash(),
962			candidate.descriptor().para_id()
963		);
964		return false;
965	}
966
967	// Version consistency + V3 gating (shared logic from primitives).
968	if let Err(reason) = candidate.descriptor().check_version_acceptance(v3_enabled) {
969		log::debug!(
970			target: LOG_TARGET,
971			"{}. Dropping candidate {:?} for paraid {:?}.",
972			reason,
973			candidate.candidate().hash(),
974			candidate.descriptor().para_id()
975		);
976		return false;
977	}
978
979	// Check relay_parent exists in allowed relay parents (execution context).
980	// Needed for all versions to access relay chain state.
981	let relay_parent = candidate.descriptor().relay_parent();
982
983	let session_index = candidate.descriptor().session_index().unwrap_or(current_session_index);
984
985	if shared::Pallet::<T>::get_relay_parent_info(session_index, relay_parent).is_none() {
986		log::debug!(
987			target: LOG_TARGET,
988			"Relay parent {:?} for candidate {:?} is not in the allowed relay parents of session {}.",
989			relay_parent,
990			candidate.candidate().hash(),
991			session_index,
992		);
993		return false;
994	};
995
996	// Check scheduling_parent exists in allowed relay parents (scheduling context).
997	// For V1/V2: scheduling_parent() returns relay_parent (duplicate check, but cheap).
998	// For V3: scheduling_parent() returns the actual scheduling_parent field.
999	//
1000	// Note: we do not check that scheduling_parents advance between candidates. Backwards
1001	// movement of scheduling_parent is primarily a censorship resistance concern, handled
1002	// by the collator protocol's active leaf check. The relay chain only requires validity
1003	// (i.e., the scheduling_parent is in allowed relay parents).
1004	let scheduling_parent = candidate.descriptor().scheduling_parent();
1005	let Some((sp_info, _)) = allowed_scheduling_parents.acquire_info(scheduling_parent) else {
1006		log::debug!(
1007			target: LOG_TARGET,
1008			"Scheduling parent {:?} for candidate {:?} is not in the allowed scheduling parents.",
1009			scheduling_parent,
1010			candidate.candidate().hash(),
1011		);
1012		return false;
1013	};
1014
1015	// UMP signals check uses scheduling parent's claim queue.
1016	// For V1/V2: scheduling_parent == relay_parent, so uses same claim queue as before.
1017	// For V3: uses the claim queue from the scheduling_parent.
1018	if let Err(err) = candidate.candidate().parse_ump_signals(&sp_info.claim_queue) {
1019		log::debug!(
1020			target: LOG_TARGET,
1021			"UMP signal check failed: {:?}. Dropping candidate {:?} for paraid {:?}.",
1022			err,
1023			candidate.candidate().hash(),
1024			candidate.descriptor().para_id()
1025		);
1026		return false;
1027	}
1028
1029	if descriptor_version == CandidateDescriptorVersion::V1 {
1030		// Nothing more to check for v1 descriptors.
1031		return true;
1032	}
1033
1034	// For V2/V3: Check scheduling session matches current session.
1035	// For V2: scheduling_session() returns session_index (relay parent session).
1036	// For V3: scheduling_session() returns scheduling_session_index.
1037	let Some(scheduling_session) = candidate.descriptor().scheduling_session() else {
1038		log::debug!(
1039			target: LOG_TARGET,
1040			"Invalid V2/V3 candidate receipt {:?} for paraid {:?}, missing scheduling session.",
1041			candidate.candidate().hash(),
1042			candidate.descriptor().para_id(),
1043		);
1044		return false;
1045	};
1046
1047	// Check if scheduling session is equal to current session index.
1048	if scheduling_session != current_session_index {
1049		log::debug!(
1050			target: LOG_TARGET,
1051			"Dropping candidate receipt {:?} for paraid {:?}, invalid scheduling session {}, current session {}",
1052			candidate.candidate().hash(),
1053			candidate.descriptor().para_id(),
1054			scheduling_session,
1055			current_session_index
1056		);
1057		return false;
1058	}
1059
1060	true
1061}
1062
1063/// Performs various filtering on the backed candidates inherent data.
1064/// Must maintain the invariant that the returned candidate collection contains the candidates
1065/// sorted in dependency order for each para. When doing any filtering, we must therefore drop any
1066/// subsequent candidates after the filtered one.
1067///
1068/// Filter out:
1069/// 1. any candidates which don't form a chain with the other candidates of the paraid (even if they
1070///    do form a chain but are not in the right order).
1071/// 2. any candidates that have a concluded invalid dispute or who are descendants of a concluded
1072///    invalid candidate.
1073/// 3. any unscheduled candidates, as well as candidates whose paraid has multiple cores assigned
1074///    but have no core index (either injected or in the v2 descriptor).
1075/// 4. all backing votes from disabled validators
1076/// 5. any candidates that end up with less than `effective_minimum_backing_votes` backing votes
1077///
1078/// Returns the scheduled
1079/// backed candidates which passed filtering, mapped by para id and in the right dependency order.
1080///
1081/// ## Candidate validation pipeline
1082///
1083/// Candidate checks are split across two modules. The full pipeline is:
1084///
1085/// **Phase 1: Sanitization** (`paras_inherent`, this module)
1086/// - `check_descriptor_version_and_signals`: version gating, relay/scheduling parent validity,
1087///   session restrictions, UMP signals, core index from signals (V2/V3)
1088/// - `filter_unchained_candidates`: dependency ordering, relay parent bounds, PVD hash, validation
1089///   code hash, para head match (via `verify_backed_candidate`)
1090/// - `map_candidates_to_cores`: core assignment mapping, core index from descriptor/injection
1091/// - `filter_backed_statements_from_disabled_validators`: disabled validator filtering
1092///
1093/// **Phase 2: Processing** (`inclusion::process_candidates`)
1094/// - `verify_backed_candidate`: relay parent lookup (using session from descriptor), PVD hash,
1095///   validation code hash, para head match
1096/// - Scheduling parent lookup for group assignment
1097/// - Backing vote count and signature verification
1098/// - State updates (pending availability, head data, etc.)
1099///
1100/// Note: `verify_backed_candidate` is called in both phases. In phase 1 it's called by
1101/// `filter_unchained_candidates` to validate chaining. In phase 2 it's called by
1102/// `process_candidates` for final validation. The relay parent session check in
1103/// `verify_backed_candidate` relies on `check_descriptor_version_and_signals` having
1104/// already enforced that V1/V2 relay parents are in the current session.
1105fn sanitize_backed_candidates<T: crate::inclusion::Config>(
1106	backed_candidates: Vec<BackedCandidate<T::Hash>>,
1107	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
1108	concluded_invalid_with_descendants: BTreeSet<CandidateHash>,
1109	scheduled: BTreeMap<ParaId, BTreeSet<CoreIndex>>,
1110	v3_enabled: bool,
1111) -> BTreeMap<ParaId, Vec<(BackedCandidate<T::Hash>, CoreIndex)>> {
1112	// Map the candidates to the right paraids, while making sure that the order between candidates
1113	// of the same para is preserved.
1114	let mut candidates_per_para: BTreeMap<ParaId, Vec<_>> = BTreeMap::new();
1115
1116	for candidate in backed_candidates {
1117		if !check_descriptor_version_and_signals::<T>(
1118			&candidate,
1119			allowed_scheduling_parents,
1120			v3_enabled,
1121		) {
1122			continue;
1123		}
1124
1125		candidates_per_para
1126			.entry(candidate.descriptor().para_id())
1127			.or_default()
1128			.push(candidate);
1129	}
1130
1131	// Check that candidates pertaining to the same para form a chain. Drop the ones that
1132	// don't, along with the rest of candidates which follow them in the input vector.
1133	filter_unchained_candidates::<T>(&mut candidates_per_para);
1134
1135	// Remove any candidates that were concluded invalid or who are descendants of concluded invalid
1136	// candidates (along with their descendants).
1137	retain_candidates::<T, _, _>(&mut candidates_per_para, |_, candidate| {
1138		let keep = !concluded_invalid_with_descendants.contains(&candidate.candidate().hash());
1139
1140		if !keep {
1141			log::debug!(
1142				target: LOG_TARGET,
1143				"Found backed candidate {:?} which was concluded invalid or is a descendant of a concluded invalid candidate, for paraid {:?}.",
1144				candidate.candidate().hash(),
1145				candidate.descriptor().para_id()
1146			);
1147		}
1148		keep
1149	});
1150
1151	// Map candidates to scheduled cores. Filter out any unscheduled candidates along with their
1152	// descendants.
1153	let mut backed_candidates_with_core =
1154		map_candidates_to_cores::<T>(&allowed_scheduling_parents, scheduled, candidates_per_para);
1155
1156	// Filter out backing statements from disabled validators. If by that we render a candidate with
1157	// less backing votes than required, filter that candidate also. As all the other filtering
1158	// operations above, we drop the descendants of the dropped candidates also.
1159	filter_backed_statements_from_disabled_validators::<T>(
1160		&mut backed_candidates_with_core,
1161		&allowed_scheduling_parents,
1162	);
1163
1164	backed_candidates_with_core
1165}
1166
1167fn count_backed_candidates<B>(backed_candidates: &BTreeMap<ParaId, Vec<B>>) -> usize {
1168	backed_candidates.values().map(|c| c.len()).sum()
1169}
1170
1171/// Derive entropy from babe provided per block randomness.
1172///
1173/// In the odd case none is available, uses the `parent_hash` and
1174/// a const value, while emitting a warning.
1175fn compute_entropy<T: Config>(parent_hash: T::Hash) -> [u8; 32] {
1176	const CANDIDATE_SEED_SUBJECT: [u8; 32] = *b"candidate-seed-selection-subject";
1177	// NOTE: this is slightly gameable since this randomness was already public
1178	// by the previous block, while for the block author this randomness was
1179	// known 2 epochs ago. it is marginally better than using the parent block
1180	// hash since it's harder to influence the VRF output than the block hash.
1181	let vrf_random = ParentBlockRandomness::<T>::random(&CANDIDATE_SEED_SUBJECT[..]).0;
1182	let mut entropy: [u8; 32] = CANDIDATE_SEED_SUBJECT;
1183	if let Some(vrf_random) = vrf_random {
1184		entropy.as_mut().copy_from_slice(vrf_random.as_ref());
1185	} else {
1186		// in case there is no VRF randomness present, we utilize the relay parent
1187		// as seed, it's better than a static value.
1188		log::warn!(target: LOG_TARGET, "ParentBlockRandomness did not provide entropy");
1189		entropy.as_mut().copy_from_slice(parent_hash.as_ref());
1190	}
1191	entropy
1192}
1193
1194/// Limit disputes in place.
1195///
1196/// Assumes ordering of disputes, retains sorting of the statement.
1197///
1198/// Prime source of overload safety for dispute votes:
1199/// 1. Check accumulated weight does not exceed the maximum block weight.
1200/// 2. If exceeded:
1201///   1. Check validity of all dispute statements sequentially
1202/// 2. If not exceeded:
1203///   1. If weight is exceeded by locals, pick the older ones (lower indices) until the weight limit
1204///      is reached.
1205///
1206/// Returns the consumed weight amount, that is guaranteed to be less than the provided
1207/// `max_consumable_weight`.
1208fn limit_and_sanitize_disputes<
1209	T: Config,
1210	CheckValidityFn: FnMut(DisputeStatementSet) -> Option<CheckedDisputeStatementSet>,
1211>(
1212	disputes: MultiDisputeStatementSet,
1213	mut dispute_statement_set_valid: CheckValidityFn,
1214	max_consumable_weight: Weight,
1215) -> (Vec<CheckedDisputeStatementSet>, Weight) {
1216	// The total weight if all disputes would be included
1217	let disputes_weight = multi_dispute_statement_sets_weight::<T>(&disputes);
1218
1219	if disputes_weight.any_gt(max_consumable_weight) {
1220		log::debug!(target: LOG_TARGET, "Above max consumable weight: {}/{}", disputes_weight, max_consumable_weight);
1221		let mut checked_acc = Vec::<CheckedDisputeStatementSet>::with_capacity(disputes.len());
1222
1223		// Accumulated weight of all disputes picked, that passed the checks.
1224		let mut weight_acc = Weight::zero();
1225
1226		// Select disputes in-order until the remaining weight is attained
1227		disputes.into_iter().for_each(|dss| {
1228			let dispute_weight = dispute_statement_set_weight::<T, &DisputeStatementSet>(&dss);
1229			let updated = weight_acc.saturating_add(dispute_weight);
1230			if max_consumable_weight.all_gte(updated) {
1231				// Always apply the weight. Invalid data cost processing time too:
1232				weight_acc = updated;
1233				if let Some(checked) = dispute_statement_set_valid(dss) {
1234					checked_acc.push(checked);
1235				}
1236			}
1237		});
1238
1239		(checked_acc, weight_acc)
1240	} else {
1241		// Go through all of them, and just apply the filter, they would all fit
1242		let checked = disputes
1243			.into_iter()
1244			.filter_map(|dss| dispute_statement_set_valid(dss))
1245			.collect::<Vec<CheckedDisputeStatementSet>>();
1246		// some might have been filtered out, so re-calc the weight
1247		let checked_disputes_weight = checked_multi_dispute_statement_sets_weight::<T>(&checked);
1248		(checked, checked_disputes_weight)
1249	}
1250}
1251
1252// Helper function for filtering candidates which don't pass the given predicate. When/if the first
1253// candidate which failed the predicate is found, all the other candidates that follow are dropped.
1254fn retain_candidates<
1255	T: inclusion::Config + paras::Config + inclusion::Config,
1256	F: FnMut(ParaId, &mut C) -> bool,
1257	C,
1258>(
1259	candidates_per_para: &mut BTreeMap<ParaId, Vec<C>>,
1260	mut pred: F,
1261) {
1262	for (para_id, candidates) in candidates_per_para.iter_mut() {
1263		let mut latest_valid_idx = None;
1264
1265		for (idx, candidate) in candidates.iter_mut().enumerate() {
1266			if pred(*para_id, candidate) {
1267				// Found a valid candidate.
1268				latest_valid_idx = Some(idx);
1269			} else {
1270				break;
1271			}
1272		}
1273
1274		if let Some(latest_valid_idx) = latest_valid_idx {
1275			candidates.truncate(latest_valid_idx + 1);
1276		} else {
1277			candidates.clear();
1278		}
1279	}
1280
1281	candidates_per_para.retain(|_, c| !c.is_empty());
1282}
1283
1284// Filters statements from disabled validators in `BackedCandidate` and does a few more sanity
1285// checks.
1286fn filter_backed_statements_from_disabled_validators<
1287	T: shared::Config + scheduler::Config + inclusion::Config,
1288>(
1289	backed_candidates_with_core: &mut BTreeMap<
1290		ParaId,
1291		Vec<(BackedCandidate<<T as frame_system::Config>::Hash>, CoreIndex)>,
1292	>,
1293	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
1294) {
1295	let disabled_validators =
1296		BTreeSet::<_>::from_iter(shared::Pallet::<T>::disabled_validators().into_iter());
1297
1298	if disabled_validators.is_empty() {
1299		// No disabled validators - nothing to do
1300		return;
1301	}
1302
1303	let minimum_backing_votes = configuration::ActiveConfig::<T>::get().minimum_backing_votes;
1304
1305	// Process all backed candidates. `validator_indices` in `BackedCandidates` are indices within
1306	// the validator group assigned to the parachain. To obtain this group we need:
1307	// 1. Core index assigned to the parachain which has produced the candidate
1308	// 2. The scheduling parent block number of the candidate
1309	retain_candidates::<T, _, _>(backed_candidates_with_core, |para_id, (bc, core_idx)| {
1310		// `CoreIndex` not used, we just need a copy to write it back later.
1311		let (validator_indices, maybe_injected_core_index) = bc.validator_indices_and_core_index();
1312		let mut validator_indices = BitVec::<_>::from(validator_indices);
1313
1314		// Get scheduling parent block number of the candidate. We need this to get the group index
1315		// assigned to this core at this block number
1316		let scheduling_parent_block_number = match allowed_scheduling_parents
1317			.acquire_info(bc.descriptor().scheduling_parent())
1318		{
1319			Some((_, block_num)) => block_num,
1320			None => {
1321				log::debug!(
1322					target: LOG_TARGET,
1323					"Scheduling parent {:?} for candidate is not in the allowed scheduling parents. Dropping the candidate.",
1324					bc.descriptor().scheduling_parent()
1325				);
1326				return false;
1327			},
1328		};
1329
1330		// Get the group index for the core
1331		let group_idx = match scheduler::Pallet::<T>::group_assigned_to_core(
1332			*core_idx,
1333			scheduling_parent_block_number + One::one(),
1334		) {
1335			Some(group_idx) => group_idx,
1336			None => {
1337				log::debug!(target: LOG_TARGET, "Can't get the group index for core idx {:?}. Dropping the candidate.", core_idx);
1338				return false;
1339			},
1340		};
1341
1342		// And finally get the validator group for this group index
1343		let validator_group = match scheduler::Pallet::<T>::group_validators(group_idx) {
1344			Some(validator_group) => validator_group,
1345			None => {
1346				log::debug!(target: LOG_TARGET, "Can't get the validators from group {:?}. Dropping the candidate.", group_idx);
1347				return false;
1348			},
1349		};
1350
1351		// Bitmask with the disabled indices within the validator group
1352		let disabled_indices = BitVec::<u8, bitvec::order::Lsb0>::from_iter(
1353			validator_group.iter().map(|idx| disabled_validators.contains(idx)),
1354		);
1355		// The indices of statements from disabled validators in `BackedCandidate`. We have to drop
1356		// these.
1357		let indices_to_drop = disabled_indices.clone() & &validator_indices;
1358
1359		// Remove the corresponding votes from `validity_votes`
1360		for idx in indices_to_drop.iter_ones().rev() {
1361			// Map the index in `indices_to_drop` (which is an index into the validator group)
1362			// to the index in the validity votes vector, which might have less number of votes,
1363			// than validators assigned to the group.
1364			//
1365			// For each index `idx` in `indices_to_drop`, the corresponding index in the
1366			// validity votes vector is the number of `1` bits in `validator_indices` before `idx`.
1367			let mapped_idx = validator_indices[..idx].count_ones();
1368			bc.validity_votes_mut().remove(mapped_idx);
1369		}
1370
1371		// Apply the bitmask to drop the disabled validator from `validator_indices`
1372		validator_indices &= !disabled_indices;
1373		// Update the backed candidate
1374		bc.set_validator_indices_and_core_index(validator_indices, maybe_injected_core_index);
1375
1376		// By filtering votes we might render the candidate invalid and cause a failure in
1377		// [`process_candidates`]. To avoid this we have to perform a sanity check here. If there
1378		// are not enough backing votes after filtering we will remove the whole candidate.
1379		if bc.validity_votes().len() <
1380			effective_minimum_backing_votes(validator_group.len(), minimum_backing_votes)
1381		{
1382			log::debug!(
1383				target: LOG_TARGET,
1384				"Dropping candidate {:?} of paraid {:?} because it was left with too few backing votes after votes from disabled validators were filtered.",
1385				bc.candidate().hash(),
1386				para_id
1387			);
1388
1389			return false;
1390		}
1391
1392		true
1393	});
1394}
1395
1396// Check that candidates pertaining to the same para form a chain. Drop the ones that
1397// don't, along with the rest of candidates which follow them in the input vector.
1398// In the process, duplicated candidates will also be dropped (even if they form a valid cycle;
1399// cycles are not allowed if they entail backing duplicated candidates).
1400fn filter_unchained_candidates<T: inclusion::Config + paras::Config + inclusion::Config>(
1401	candidates: &mut BTreeMap<ParaId, Vec<BackedCandidate<T::Hash>>>,
1402) {
1403	let mut para_latest_context: BTreeMap<ParaId, (HeadData, BlockNumberFor<T>)> = BTreeMap::new();
1404	for para_id in candidates.keys() {
1405		let Some(latest_head_data) = inclusion::Pallet::<T>::para_latest_head_data(&para_id) else {
1406			defensive!("Latest included head data for paraid {:?} is None", para_id);
1407			continue;
1408		};
1409		let Some(latest_relay_parent) = inclusion::Pallet::<T>::para_most_recent_context(&para_id)
1410		else {
1411			defensive!("Latest relay parent for paraid {:?} is None", para_id);
1412			continue;
1413		};
1414		para_latest_context.insert(*para_id, (latest_head_data, latest_relay_parent));
1415	}
1416
1417	let mut para_visited_candidates: BTreeMap<ParaId, BTreeSet<CandidateHash>> = BTreeMap::new();
1418
1419	retain_candidates::<T, _, _>(candidates, |para_id, candidate| {
1420		let Some((latest_head_data, latest_relay_parent)) = para_latest_context.get(&para_id)
1421		else {
1422			return false;
1423		};
1424		let candidate_hash = candidate.candidate().hash();
1425
1426		let visited_candidates =
1427			para_visited_candidates.entry(para_id).or_insert_with(|| BTreeSet::new());
1428		if visited_candidates.contains(&candidate_hash) {
1429			log::debug!(
1430				target: LOG_TARGET,
1431				"Found duplicate candidates for paraid {:?}. Dropping the candidates with hash {:?}",
1432				para_id,
1433				candidate_hash
1434			);
1435
1436			// If we got a duplicate candidate, stop.
1437			return false;
1438		} else {
1439			visited_candidates.insert(candidate_hash);
1440		}
1441
1442		let check_ctx = CandidateCheckContext::<T>::new(Some(*latest_relay_parent));
1443
1444		match check_ctx.verify_backed_candidate(candidate.candidate(), latest_head_data.clone()) {
1445			Ok(relay_parent_block_number) => {
1446				para_latest_context.insert(
1447					para_id,
1448					(
1449						candidate.candidate().commitments.head_data.clone(),
1450						relay_parent_block_number,
1451					),
1452				);
1453				true
1454			},
1455			Err(err) => {
1456				log::debug!(
1457					target: LOG_TARGET,
1458					"Backed candidate verification for candidate {:?} of paraid {:?} failed with {:?}",
1459					candidate_hash,
1460					para_id,
1461					err
1462				);
1463				false
1464			},
1465		}
1466	});
1467}
1468
1469/// Map candidates to scheduled cores.
1470/// If the para only has one scheduled core and one candidate supplied, map the candidate to the
1471/// single core. If the para has multiple cores scheduled, only map the candidates with core index.
1472/// Filter out the rest.
1473/// Also returns whether or not we dropped any candidates.
1474/// When dropping a candidate of a para, we must drop all subsequent candidates from that para
1475/// (because they form a chain).
1476fn map_candidates_to_cores<T: configuration::Config + scheduler::Config + inclusion::Config>(
1477	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
1478	mut scheduled: BTreeMap<ParaId, BTreeSet<CoreIndex>>,
1479	candidates: BTreeMap<ParaId, Vec<BackedCandidate<T::Hash>>>,
1480) -> BTreeMap<ParaId, Vec<(BackedCandidate<T::Hash>, CoreIndex)>> {
1481	let mut backed_candidates_with_core = BTreeMap::new();
1482
1483	for (para_id, backed_candidates) in candidates.into_iter() {
1484		if backed_candidates.len() == 0 {
1485			defensive!("Backed candidates for paraid {} is empty.", para_id);
1486			continue;
1487		}
1488
1489		let Some(scheduled_cores) = scheduled.get_mut(&para_id) else {
1490			log::debug!(
1491				target: LOG_TARGET,
1492				"Paraid: {:?} has no entry in scheduled cores but {} candidates were supplied.",
1493				para_id,
1494				backed_candidates.len()
1495			);
1496			continue;
1497		};
1498
1499		// ParaIds without scheduled cores are silently filtered out.
1500		if scheduled_cores.len() == 0 {
1501			log::debug!(
1502				target: LOG_TARGET,
1503				"Paraid: {:?} has no scheduled cores but {} candidates were supplied.",
1504				para_id,
1505				backed_candidates.len()
1506			);
1507			continue;
1508		}
1509
1510		// We must preserve the dependency order given in the input.
1511		let mut temp_backed_candidates = Vec::with_capacity(scheduled_cores.len());
1512
1513		for candidate in backed_candidates {
1514			if scheduled_cores.len() == 0 {
1515				// We've got candidates for all of this para's assigned cores. Move on to
1516				// the next para.
1517				log::debug!(
1518					target: LOG_TARGET,
1519					"Found enough candidates for paraid: {:?}.",
1520					candidate.descriptor().para_id()
1521				);
1522				break;
1523			}
1524
1525			if let Some(core_index) = get_core_index::<T>(allowed_scheduling_parents, &candidate) {
1526				if scheduled_cores.remove(&core_index) {
1527					temp_backed_candidates.push((candidate, core_index));
1528				} else {
1529					// if we got a candidate for a core index which is not scheduled, stop
1530					// the work for this para. the already processed candidate chain in
1531					// temp_backed_candidates is still fine though.
1532					log::debug!(
1533						target: LOG_TARGET,
1534						"Found a backed candidate {:?} with core index {}, which is not scheduled for paraid {:?}.",
1535						candidate.candidate().hash(),
1536						core_index.0,
1537						candidate.descriptor().para_id()
1538					);
1539
1540					break;
1541				}
1542			} else {
1543				// if we got a candidate which does not contain its core index, stop the
1544				// work for this para. the already processed candidate chain in
1545				// temp_backed_candidates is still fine though.
1546
1547				log::debug!(
1548					target: LOG_TARGET,
1549					"Found a backed candidate {:?} without core index information for para {:?}, dropping",
1550					candidate.candidate().hash(),
1551					candidate.descriptor().para_id()
1552				);
1553
1554				break;
1555			}
1556		}
1557
1558		if !temp_backed_candidates.is_empty() {
1559			backed_candidates_with_core
1560				.entry(para_id)
1561				.or_insert_with(|| vec![])
1562				.extend(temp_backed_candidates);
1563		}
1564	}
1565
1566	backed_candidates_with_core
1567}
1568
1569// Must be called only for candidates that have been sanitized already.
1570fn get_core_index<T: configuration::Config + scheduler::Config + inclusion::Config>(
1571	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
1572	candidate: &BackedCandidate<T::Hash>,
1573) -> Option<CoreIndex> {
1574	candidate
1575		.candidate()
1576		.descriptor
1577		.core_index()
1578		.or_else(|| get_injected_core_index::<T>(allowed_scheduling_parents, &candidate))
1579}
1580
1581fn get_injected_core_index<T: configuration::Config + scheduler::Config + inclusion::Config>(
1582	allowed_scheduling_parents: &AllowedSchedulingParentsTracker<T::Hash, BlockNumberFor<T>>,
1583	candidate: &BackedCandidate<T::Hash>,
1584) -> Option<CoreIndex> {
1585	// After stripping the 8 bit extensions, the `validator_indices` field length is expected
1586	// to be equal to backing group size. If these don't match, the `CoreIndex` is badly encoded,
1587	// or not supported.
1588	let (validator_indices, Some(core_idx)) = candidate.validator_indices_and_core_index() else {
1589		return None;
1590	};
1591
1592	let scheduling_parent_block_number =
1593		match allowed_scheduling_parents.acquire_info(candidate.descriptor().scheduling_parent()) {
1594			Some((_, block_num)) => block_num,
1595			None => {
1596				log::debug!(
1597					target: LOG_TARGET,
1598					"Scheduling parent {:?} for candidate {:?} is not in the allowed scheduling parents.",
1599					candidate.descriptor().scheduling_parent(),
1600					candidate.candidate().hash(),
1601				);
1602				return None;
1603			},
1604		};
1605
1606	// Get the backing group of the candidate backed at `core_idx`.
1607	let group_idx = match scheduler::Pallet::<T>::group_assigned_to_core(
1608		core_idx,
1609		scheduling_parent_block_number + One::one(),
1610	) {
1611		Some(group_idx) => group_idx,
1612		None => {
1613			log::debug!(
1614				target: LOG_TARGET,
1615				"Can't get the group index for core idx {:?}.",
1616				core_idx,
1617			);
1618			return None;
1619		},
1620	};
1621
1622	let group_validators = match scheduler::Pallet::<T>::group_validators(group_idx) {
1623		Some(validators) => validators,
1624		None => return None,
1625	};
1626
1627	if group_validators.len() == validator_indices.len() {
1628		Some(core_idx)
1629	} else {
1630		log::debug!(
1631			target: LOG_TARGET,
1632			"Expected validator_indices count different than the real one: {}, {} for candidate {:?}",
1633			group_validators.len(),
1634			validator_indices.len(),
1635			candidate.candidate().hash()
1636		);
1637
1638		None
1639	}
1640}