referrerpolicy=no-referrer-when-downgrade

polkadot_node_subsystem_util/runtime/
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//! Convenient interface to runtime information.
18
19use polkadot_node_primitives::MAX_FINALITY_LAG;
20use schnellru::{ByLength, LruMap};
21
22use codec::Encode;
23use sp_application_crypto::AppCrypto;
24use sp_core::crypto::ByteArray;
25use sp_keystore::{Keystore, KeystorePtr};
26
27use polkadot_node_subsystem::{
28	errors::RuntimeApiError,
29	messages::{RuntimeApiMessage, RuntimeApiRequest},
30	overseer, SubsystemSender,
31};
32use polkadot_node_subsystem_types::UnpinHandle;
33use polkadot_primitives::{
34	node_features::FeatureIndex, slashing, CandidateEvent, CandidateHash, CoreIndex, CoreState,
35	EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, IndexedVec,
36	NodeFeatures, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
37	SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId,
38	ValidatorIndex, DEFAULT_SCHEDULING_LOOKAHEAD,
39};
40
41use std::collections::{BTreeMap, VecDeque};
42
43use crate::{
44	request_availability_cores, request_candidate_events, request_claim_queue,
45	request_disabled_validators, request_from_runtime, request_key_ownership_proof,
46	request_node_features, request_on_chain_votes, request_session_executor_params,
47	request_session_index_for_child, request_session_info, request_submit_report_dispute_lost,
48	request_unapplied_slashes, request_validation_code_by_hash, request_validator_groups,
49};
50
51/// Errors that can happen on runtime fetches.
52mod error;
53
54use error::Result;
55pub use error::{recv_runtime, Error, FatalError, JfyiError};
56
57const LOG_TARGET: &'static str = "parachain::runtime-info";
58
59/// Configuration for construction a `RuntimeInfo`.
60pub struct Config {
61	/// Needed for retrieval of `ValidatorInfo`
62	///
63	/// Pass `None` if you are not interested.
64	pub keystore: Option<KeystorePtr>,
65
66	/// How many sessions should we keep in the cache?
67	pub session_cache_lru_size: u32,
68}
69
70/// Caching of session info.
71///
72/// It should be ensured that a cached session stays live in the cache as long as we might need it.
73pub struct RuntimeInfo {
74	/// Get the session index for a given relay parent.
75	///
76	/// We query this up to a 100 times per block, so caching it here without roundtrips over the
77	/// overseer seems sensible.
78	session_index_cache: LruMap<Hash, SessionIndex>,
79
80	/// In the happy case, we do not query disabled validators at all. In the worst case, we can
81	/// query it order of `n_cores` times `n_validators` per block, so caching it here seems
82	/// sensible.
83	disabled_validators_cache: LruMap<Hash, Vec<ValidatorIndex>>,
84
85	/// Look up cached sessions by `SessionIndex`.
86	session_info_cache: LruMap<SessionIndex, ExtendedSessionInfo>,
87
88	/// Unpin handle of *some* block in the session.
89	/// Only blocks pinned explicitly by `pin_block` are stored here.
90	pinned_blocks: LruMap<SessionIndex, UnpinHandle>,
91
92	/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
93	keystore: Option<KeystorePtr>,
94}
95
96/// `SessionInfo` with additional useful data for validator nodes.
97pub struct ExtendedSessionInfo {
98	/// Actual session info as fetched from the runtime.
99	pub session_info: SessionInfo,
100	/// Contains useful information about ourselves, in case this node is a validator.
101	pub validator_info: ValidatorInfo,
102	/// Session executor parameters
103	pub executor_params: ExecutorParams,
104	/// Node features
105	pub node_features: NodeFeatures,
106}
107
108/// Information about ourselves, in case we are an `Authority`.
109///
110/// This data is derived from the `SessionInfo` and our key as found in the keystore.
111pub struct ValidatorInfo {
112	/// The index this very validator has in `SessionInfo` vectors, if any.
113	pub our_index: Option<ValidatorIndex>,
114	/// The group we belong to, if any.
115	pub our_group: Option<GroupIndex>,
116}
117
118impl Default for Config {
119	fn default() -> Self {
120		Self {
121			keystore: None,
122			// Usually we need to cache the current and the last session.
123			session_cache_lru_size: 2,
124		}
125	}
126}
127
128impl RuntimeInfo {
129	/// Create a new `RuntimeInfo` for convenient runtime fetches.
130	pub fn new(keystore: Option<KeystorePtr>) -> Self {
131		Self::new_with_config(Config { keystore, ..Default::default() })
132	}
133
134	/// Create with more elaborate configuration options.
135	pub fn new_with_config(cfg: Config) -> Self {
136		Self {
137			// Usually messages are processed for blocks pointing to hashes from last finalized
138			// block to to best, so make this cache large enough to hold at least this amount of
139			// hashes, so that we get the benefit of caching even when finality lag is large.
140			session_index_cache: LruMap::new(ByLength::new(
141				cfg.session_cache_lru_size.max(2 * MAX_FINALITY_LAG),
142			)),
143			session_info_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
144			disabled_validators_cache: LruMap::new(ByLength::new(100)),
145			pinned_blocks: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
146			keystore: cfg.keystore,
147		}
148	}
149
150	/// Returns the session index expected at any child of the `parent` block.
151	/// This does not return the session index for the `parent` block.
152	pub async fn get_session_index_for_child<Sender>(
153		&mut self,
154		sender: &mut Sender,
155		parent: Hash,
156	) -> Result<SessionIndex>
157	where
158		Sender: SubsystemSender<RuntimeApiMessage>,
159	{
160		match self.session_index_cache.get(&parent) {
161			Some(index) => Ok(*index),
162			None => {
163				let index =
164					recv_runtime(request_session_index_for_child(parent, sender).await).await?;
165				self.session_index_cache.insert(parent, index);
166				Ok(index)
167			},
168		}
169	}
170
171	/// Pin a given block in the given session if none are pinned in that session.
172	/// Unpinning will happen automatically when LRU cache grows over the limit.
173	pub fn pin_block(&mut self, session_index: SessionIndex, unpin_handle: UnpinHandle) {
174		self.pinned_blocks.get_or_insert(session_index, || unpin_handle);
175	}
176
177	/// Get the hash of a pinned block for the given session index, if any.
178	pub fn get_block_in_session(&self, session_index: SessionIndex) -> Option<Hash> {
179		self.pinned_blocks.peek(&session_index).map(|h| h.hash())
180	}
181
182	/// Get `ExtendedSessionInfo` by relay parent hash.
183	pub async fn get_session_info<'a, Sender>(
184		&'a mut self,
185		sender: &mut Sender,
186		relay_parent: Hash,
187	) -> Result<&'a ExtendedSessionInfo>
188	where
189		Sender: SubsystemSender<RuntimeApiMessage>,
190	{
191		let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
192
193		self.get_session_info_by_index(sender, relay_parent, session_index).await
194	}
195
196	/// Get the list of disabled validators at the relay parent.
197	pub async fn get_disabled_validators<Sender>(
198		&mut self,
199		sender: &mut Sender,
200		relay_parent: Hash,
201	) -> Result<Vec<ValidatorIndex>>
202	where
203		Sender: SubsystemSender<RuntimeApiMessage>,
204	{
205		match self.disabled_validators_cache.get(&relay_parent).cloned() {
206			Some(result) => Ok(result),
207			None => {
208				let disabled_validators =
209					request_disabled_validators(relay_parent, sender).await.await??;
210				self.disabled_validators_cache.insert(relay_parent, disabled_validators.clone());
211				Ok(disabled_validators)
212			},
213		}
214	}
215
216	/// Get `ExtendedSessionInfo` by session index.
217	///
218	/// `request_session_info` still requires the parent to be passed in, so we take the parent
219	/// in addition to the `SessionIndex`.
220	pub async fn get_session_info_by_index<'a, Sender>(
221		&'a mut self,
222		sender: &mut Sender,
223		parent: Hash,
224		session_index: SessionIndex,
225	) -> Result<&'a ExtendedSessionInfo>
226	where
227		Sender: SubsystemSender<RuntimeApiMessage>,
228	{
229		if self.session_info_cache.get(&session_index).is_none() {
230			let session_info =
231				recv_runtime(request_session_info(parent, session_index, sender).await)
232					.await?
233					.ok_or(JfyiError::NoSuchSession(session_index))?;
234
235			let executor_params =
236				recv_runtime(request_session_executor_params(parent, session_index, sender).await)
237					.await?
238					.ok_or(JfyiError::NoExecutorParams(session_index))?;
239
240			let validator_info = self.get_validator_info(&session_info)?;
241
242			let node_features =
243				request_node_features(parent, session_index, sender).await.await??;
244			let last_set_index = node_features.iter_ones().last().unwrap_or_default();
245			if last_set_index >= FeatureIndex::FirstUnassigned as usize {
246				gum::warn!(target: LOG_TARGET, "Runtime requires feature bit {} that node doesn't support, please upgrade node version", last_set_index);
247			}
248
249			let full_info = ExtendedSessionInfo {
250				session_info,
251				validator_info,
252				executor_params,
253				node_features,
254			};
255
256			self.session_info_cache.insert(session_index, full_info);
257		}
258		Ok(self
259			.session_info_cache
260			.get(&session_index)
261			.expect("We just put the value there. qed."))
262	}
263
264	/// Convenience function for checking the signature of something signed.
265	pub async fn check_signature<Sender, Payload, RealPayload>(
266		&mut self,
267		sender: &mut Sender,
268		relay_parent: Hash,
269		signed: UncheckedSigned<Payload, RealPayload>,
270	) -> Result<
271		std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>,
272	>
273	where
274		Sender: SubsystemSender<RuntimeApiMessage>,
275		Payload: EncodeAs<RealPayload> + Clone,
276		RealPayload: Encode + Clone,
277	{
278		let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
279		let info = self.get_session_info_by_index(sender, relay_parent, session_index).await?;
280		Ok(check_signature(session_index, &info.session_info, relay_parent, signed))
281	}
282
283	/// Build `ValidatorInfo` for the current session.
284	///
285	///
286	/// Returns: `None` if not a parachain validator.
287	fn get_validator_info(&self, session_info: &SessionInfo) -> Result<ValidatorInfo> {
288		if let Some(our_index) = self.get_our_index(&session_info.validators) {
289			// Get our group index:
290			let our_group =
291				session_info.validator_groups.iter().enumerate().find_map(|(i, g)| {
292					g.iter().find_map(|v| {
293						if *v == our_index {
294							Some(GroupIndex(i as u32))
295						} else {
296							None
297						}
298					})
299				});
300			let info = ValidatorInfo { our_index: Some(our_index), our_group };
301			return Ok(info)
302		}
303		return Ok(ValidatorInfo { our_index: None, our_group: None })
304	}
305
306	/// Get our `ValidatorIndex`.
307	///
308	/// Returns: None if we are not a validator.
309	fn get_our_index(
310		&self,
311		validators: &IndexedVec<ValidatorIndex, ValidatorId>,
312	) -> Option<ValidatorIndex> {
313		let keystore = self.keystore.as_ref()?;
314		for (i, v) in validators.iter().enumerate() {
315			if Keystore::has_keys(&**keystore, &[(v.to_raw_vec(), ValidatorId::ID)]) {
316				return Some(ValidatorIndex(i as u32))
317			}
318		}
319		None
320	}
321}
322
323/// Convenience function for quickly checking the signature on signed data.
324pub fn check_signature<Payload, RealPayload>(
325	session_index: SessionIndex,
326	session_info: &SessionInfo,
327	relay_parent: Hash,
328	signed: UncheckedSigned<Payload, RealPayload>,
329) -> std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>
330where
331	Payload: EncodeAs<RealPayload> + Clone,
332	RealPayload: Encode + Clone,
333{
334	let signing_context = SigningContext { session_index, parent_hash: relay_parent };
335
336	session_info
337		.validators
338		.get(signed.unchecked_validator_index())
339		.ok_or_else(|| signed.clone())
340		.and_then(|v| signed.try_into_checked(&signing_context, v))
341}
342
343/// Request availability cores from the runtime.
344pub async fn get_availability_cores<Sender>(
345	sender: &mut Sender,
346	relay_parent: Hash,
347) -> Result<Vec<CoreState>>
348where
349	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
350{
351	recv_runtime(request_availability_cores(relay_parent, sender).await).await
352}
353
354/// Variant of `request_availability_cores` that only returns occupied ones.
355pub async fn get_occupied_cores<Sender>(
356	sender: &mut Sender,
357	relay_parent: Hash,
358) -> Result<Vec<(CoreIndex, OccupiedCore)>>
359where
360	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
361{
362	let cores = get_availability_cores(sender, relay_parent).await?;
363
364	Ok(cores
365		.into_iter()
366		.enumerate()
367		.filter_map(|(core_index, core_state)| {
368			if let CoreState::Occupied(occupied) = core_state {
369				Some((CoreIndex(core_index as u32), occupied))
370			} else {
371				None
372			}
373		})
374		.collect())
375}
376
377/// Get group rotation info based on the given `relay_parent`.
378pub async fn get_group_rotation_info<Sender>(
379	sender: &mut Sender,
380	relay_parent: Hash,
381) -> Result<GroupRotationInfo>
382where
383	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
384{
385	// We drop `groups` here as we don't need them, because of `RuntimeInfo`. Ideally we would not
386	// fetch them in the first place.
387	let (_, info) = recv_runtime(request_validator_groups(relay_parent, sender).await).await?;
388	Ok(info)
389}
390
391/// Get `CandidateEvent`s for the given `relay_parent`.
392pub async fn get_candidate_events<Sender>(
393	sender: &mut Sender,
394	relay_parent: Hash,
395) -> Result<Vec<CandidateEvent>>
396where
397	Sender: SubsystemSender<RuntimeApiMessage>,
398{
399	recv_runtime(request_candidate_events(relay_parent, sender).await).await
400}
401
402/// Get on chain votes.
403pub async fn get_on_chain_votes<Sender>(
404	sender: &mut Sender,
405	relay_parent: Hash,
406) -> Result<Option<ScrapedOnChainVotes>>
407where
408	Sender: SubsystemSender<RuntimeApiMessage>,
409{
410	recv_runtime(request_on_chain_votes(relay_parent, sender).await).await
411}
412
413/// Fetch `ValidationCode` by hash from the runtime.
414pub async fn get_validation_code_by_hash<Sender>(
415	sender: &mut Sender,
416	relay_parent: Hash,
417	validation_code_hash: ValidationCodeHash,
418) -> Result<Option<ValidationCode>>
419where
420	Sender: SubsystemSender<RuntimeApiMessage>,
421{
422	recv_runtime(request_validation_code_by_hash(relay_parent, validation_code_hash, sender).await)
423		.await
424}
425
426/// Fetch a list of `PendingSlashes` from the runtime.
427pub async fn get_unapplied_slashes<Sender>(
428	sender: &mut Sender,
429	relay_parent: Hash,
430) -> Result<Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>>
431where
432	Sender: SubsystemSender<RuntimeApiMessage>,
433{
434	recv_runtime(request_unapplied_slashes(relay_parent, sender).await).await
435}
436
437/// Generate validator key ownership proof.
438///
439/// Note: The choice of `relay_parent` is important here, it needs to match
440/// the desired session index of the validator set in question.
441pub async fn key_ownership_proof<Sender>(
442	sender: &mut Sender,
443	relay_parent: Hash,
444	validator_id: ValidatorId,
445) -> Result<Option<slashing::OpaqueKeyOwnershipProof>>
446where
447	Sender: SubsystemSender<RuntimeApiMessage>,
448{
449	recv_runtime(request_key_ownership_proof(relay_parent, validator_id, sender).await).await
450}
451
452/// Submit a past-session dispute slashing report.
453pub async fn submit_report_dispute_lost<Sender>(
454	sender: &mut Sender,
455	relay_parent: Hash,
456	dispute_proof: slashing::DisputeProof,
457	key_ownership_proof: slashing::OpaqueKeyOwnershipProof,
458) -> Result<Option<()>>
459where
460	Sender: SubsystemSender<RuntimeApiMessage>,
461{
462	recv_runtime(
463		request_submit_report_dispute_lost(
464			relay_parent,
465			dispute_proof,
466			key_ownership_proof,
467			sender,
468		)
469		.await,
470	)
471	.await
472}
473
474/// A snapshot of the runtime claim queue at an arbitrary relay chain block.
475#[derive(Default, Clone, Debug)]
476pub struct ClaimQueueSnapshot(pub BTreeMap<CoreIndex, VecDeque<ParaId>>);
477
478impl From<BTreeMap<CoreIndex, VecDeque<ParaId>>> for ClaimQueueSnapshot {
479	fn from(claim_queue_snapshot: BTreeMap<CoreIndex, VecDeque<ParaId>>) -> Self {
480		ClaimQueueSnapshot(claim_queue_snapshot)
481	}
482}
483
484impl ClaimQueueSnapshot {
485	/// Returns the `ParaId` that has a claim for `core_index` at the specified `depth` in the
486	/// claim queue. A depth of `0` means the very next block.
487	pub fn get_claim_for(&self, core_index: CoreIndex, depth: usize) -> Option<ParaId> {
488		self.0.get(&core_index)?.get(depth).copied()
489	}
490
491	/// Returns an iterator over all claimed cores and the claiming `ParaId` at the specified
492	/// `depth` in the claim queue.
493	pub fn iter_claims_at_depth(
494		&self,
495		depth: usize,
496	) -> impl Iterator<Item = (CoreIndex, ParaId)> + '_ {
497		self.0
498			.iter()
499			.filter_map(move |(core_index, paras)| Some((*core_index, *paras.get(depth)?)))
500	}
501
502	/// Returns an iterator over all claims on the given core.
503	pub fn iter_claims_for_core(
504		&self,
505		core_index: &CoreIndex,
506	) -> impl Iterator<Item = &ParaId> + '_ {
507		self.0.get(core_index).map(|c| c.iter()).into_iter().flatten()
508	}
509
510	/// Returns an iterator over the whole claim queue.
511	pub fn iter_all_claims(&self) -> impl Iterator<Item = (&CoreIndex, &VecDeque<ParaId>)> + '_ {
512		self.0.iter()
513	}
514
515	/// Get all claimed cores for the given `para_id` at the specified depth.
516	pub fn iter_claims_at_depth_for_para(
517		&self,
518		depth: usize,
519		para_id: ParaId,
520	) -> impl Iterator<Item = CoreIndex> + '_ {
521		self.0.iter().filter_map(move |(core_index, ids)| {
522			ids.get(depth).filter(|id| **id == para_id).map(|_| *core_index)
523		})
524	}
525}
526
527/// Fetch the claim queue and wrap it into a helpful `ClaimQueueSnapshot`
528pub async fn fetch_claim_queue(
529	sender: &mut impl SubsystemSender<RuntimeApiMessage>,
530	relay_parent: Hash,
531) -> Result<ClaimQueueSnapshot> {
532	let cq = request_claim_queue(relay_parent, sender)
533		.await
534		.await
535		.map_err(Error::RuntimeRequestCanceled)??;
536
537	Ok(cq.into())
538}
539
540/// Returns the lookahead from the scheduler params if the runtime supports it,
541/// or default value if scheduling lookahead API is not supported by runtime.
542pub async fn fetch_scheduling_lookahead(
543	parent: Hash,
544	session_index: SessionIndex,
545	sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
546) -> Result<u32> {
547	let res = recv_runtime(
548		request_from_runtime(parent, sender, |tx| {
549			RuntimeApiRequest::SchedulingLookahead(session_index, tx)
550		})
551		.await,
552	)
553	.await;
554
555	if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
556		gum::trace!(
557			target: LOG_TARGET,
558			?parent,
559			"Querying the scheduling lookahead from the runtime is not supported by the current Runtime API, falling back to default value of {}",
560			DEFAULT_SCHEDULING_LOOKAHEAD
561		);
562
563		Ok(DEFAULT_SCHEDULING_LOOKAHEAD)
564	} else {
565		res
566	}
567}
568
569/// Fetch the validation code bomb limit from the runtime.
570pub async fn fetch_validation_code_bomb_limit(
571	parent: Hash,
572	session_index: SessionIndex,
573	sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
574) -> Result<u32> {
575	let res = recv_runtime(
576		request_from_runtime(parent, sender, |tx| {
577			RuntimeApiRequest::ValidationCodeBombLimit(session_index, tx)
578		})
579		.await,
580	)
581	.await;
582
583	if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
584		gum::trace!(
585			target: LOG_TARGET,
586			?parent,
587			"Querying the validation code bomb limit from the runtime is not supported by the current Runtime API",
588		);
589
590		// TODO: Remove this once runtime API version 12 is released.
591		#[allow(deprecated)]
592		Ok(polkadot_node_primitives::VALIDATION_CODE_BOMB_LIMIT as u32)
593	} else {
594		res
595	}
596}
597
598#[cfg(test)]
599mod test {
600	use super::*;
601
602	#[test]
603	fn iter_claims_at_depth_for_para_works() {
604		let claim_queue = ClaimQueueSnapshot(BTreeMap::from_iter(
605			[
606				(
607					CoreIndex(0),
608					VecDeque::from_iter([ParaId::from(1), ParaId::from(2), ParaId::from(1)]),
609				),
610				(
611					CoreIndex(1),
612					VecDeque::from_iter([ParaId::from(1), ParaId::from(1), ParaId::from(2)]),
613				),
614				(
615					CoreIndex(2),
616					VecDeque::from_iter([ParaId::from(1), ParaId::from(2), ParaId::from(3)]),
617				),
618				(
619					CoreIndex(3),
620					VecDeque::from_iter([ParaId::from(2), ParaId::from(1), ParaId::from(3)]),
621				),
622			]
623			.into_iter(),
624		));
625
626		// Test getting claims for para_id 1 at depth 0: cores 0, 1, 2
627		let depth_0_cores =
628			claim_queue.iter_claims_at_depth_for_para(0, 1u32.into()).collect::<Vec<_>>();
629		assert_eq!(depth_0_cores.len(), 3);
630		assert_eq!(depth_0_cores, vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]);
631
632		// Test getting claims for para_id 1 at depth 1: cores 1, 3
633		let depth_1_cores =
634			claim_queue.iter_claims_at_depth_for_para(1, 1u32.into()).collect::<Vec<_>>();
635		assert_eq!(depth_1_cores.len(), 2);
636		assert_eq!(depth_1_cores, vec![CoreIndex(1), CoreIndex(3)]);
637
638		// Test getting claims for para_id 1 at depth 2: core 0
639		let depth_2_cores =
640			claim_queue.iter_claims_at_depth_for_para(2, 1u32.into()).collect::<Vec<_>>();
641		assert_eq!(depth_2_cores.len(), 1);
642		assert_eq!(depth_2_cores, vec![CoreIndex(0)]);
643
644		// Test getting claims for para_id 1 at depth 3: no claims
645		let depth_3_cores =
646			claim_queue.iter_claims_at_depth_for_para(3, 1u32.into()).collect::<Vec<_>>();
647		assert!(depth_3_cores.is_empty());
648
649		// Test getting claims for para_id 2 at depth 0: core 3
650		let depth_0_cores =
651			claim_queue.iter_claims_at_depth_for_para(0, 2u32.into()).collect::<Vec<_>>();
652		assert_eq!(depth_0_cores.len(), 1);
653		assert_eq!(depth_0_cores, vec![CoreIndex(3)]);
654
655		// Test getting claims for para_id 2 at depth 1: cores 0, 2
656		let depth_1_cores =
657			claim_queue.iter_claims_at_depth_for_para(1, 2u32.into()).collect::<Vec<_>>();
658		assert_eq!(depth_1_cores.len(), 2);
659		assert_eq!(depth_1_cores, vec![CoreIndex(0), CoreIndex(2)]);
660
661		// Test getting claims for para_id 2 at depth 2: core 1
662		let depth_2_cores =
663			claim_queue.iter_claims_at_depth_for_para(2, 2u32.into()).collect::<Vec<_>>();
664		assert_eq!(depth_2_cores.len(), 1);
665		assert_eq!(depth_2_cores, vec![CoreIndex(1)]);
666
667		// Test getting claims for para_id 3 at depth 0: no claims
668		let depth_0_cores =
669			claim_queue.iter_claims_at_depth_for_para(0, 3u32.into()).collect::<Vec<_>>();
670		assert!(depth_0_cores.is_empty());
671
672		// Test getting claims for para_id 3 at depth 1: no claims
673		let depth_1_cores =
674			claim_queue.iter_claims_at_depth_for_para(1, 3u32.into()).collect::<Vec<_>>();
675		assert!(depth_1_cores.is_empty());
676
677		// Test getting claims for para_id 3 at depth 2: cores 2, 3
678		let depth_2_cores =
679			claim_queue.iter_claims_at_depth_for_para(2, 3u32.into()).collect::<Vec<_>>();
680		assert_eq!(depth_2_cores.len(), 2);
681		assert_eq!(depth_2_cores, vec![CoreIndex(2), CoreIndex(3)]);
682
683		// Test getting claims for non-existent para_id at depth 0: no claims
684		let depth_0_cores =
685			claim_queue.iter_claims_at_depth_for_para(0, 99u32.into()).collect::<Vec<_>>();
686		assert!(depth_0_cores.is_empty());
687	}
688}