referrerpolicy=no-referrer-when-downgrade

polkadot_node_subsystem_util/
backing_implicit_view.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
17use futures::channel::oneshot;
18use polkadot_node_subsystem::{
19	errors::ChainApiError,
20	messages::{ChainApiMessage, ProspectiveParachainsMessage, RuntimeApiMessage},
21	SubsystemSender,
22};
23use polkadot_primitives::{BlockNumber, Hash};
24
25use std::{
26	collections::{hash_map::Entry, HashMap},
27	iter,
28};
29
30use crate::{
31	request_session_index_for_child,
32	runtime::{self, fetch_scheduling_lookahead, recv_runtime},
33	LOG_TARGET,
34};
35
36// Always aim to retain 1 block before the active leaves.
37const MINIMUM_RETAIN_LENGTH: BlockNumber = 2;
38
39/// Handles the implicit view of the relay chain derived from the immediate view, which
40/// is composed of active leaves, and the minimum relay-parents allowed for
41/// candidates of various parachains at those leaves.
42#[derive(Clone)]
43pub struct View {
44	leaves: HashMap<Hash, ActiveLeafPruningInfo>,
45	block_info_storage: HashMap<Hash, BlockInfo>,
46}
47
48impl View {
49	/// Create a new empty view.
50	pub fn new() -> Self {
51		Self { leaves: Default::default(), block_info_storage: Default::default() }
52	}
53}
54
55impl Default for View {
56	fn default() -> Self {
57		Self::new()
58	}
59}
60
61// Minimum relay parents implicitly relative to a particular block.
62#[derive(Debug, Clone)]
63struct AllowedRelayParents {
64	// Ancestry, in descending order, starting from the block hash itself down
65	// to and including the minimum of `minimum_relay_parents`.
66	allowed_relay_parents_contiguous: Vec<Hash>,
67}
68
69impl AllowedRelayParents {
70	fn allowed_relay_parents(&self) -> &[Hash] {
71		&self.allowed_relay_parents_contiguous
72	}
73}
74
75#[derive(Debug, Clone)]
76struct ActiveLeafPruningInfo {
77	// The minimum block in the same branch of the relay-chain that should be
78	// preserved.
79	retain_minimum: BlockNumber,
80}
81
82#[derive(Debug, Clone)]
83struct BlockInfo {
84	block_number: BlockNumber,
85	// If this was previously an active leaf, this will be `Some`
86	// and is useful for understanding the views of peers in the network
87	// which may not be in perfect synchrony with our own view.
88	//
89	// If they are ahead of us in getting a new leaf, there's nothing we
90	// can do as it's an unrecognized block hash. But if they're behind us,
91	// it's useful for us to retain some information about previous leaves'
92	// implicit views so we can continue to send relevant messages to them
93	// until they catch up.
94	maybe_allowed_relay_parents: Option<AllowedRelayParents>,
95}
96
97impl View {
98	/// Get an iterator over active leaves in the view.
99	pub fn leaves(&self) -> impl Iterator<Item = &Hash> {
100		self.leaves.keys()
101	}
102
103	/// Check if the given block hash is an active leaf of the current view.
104	pub fn contains_leaf(&self, leaf_hash: &Hash) -> bool {
105		self.leaves.contains_key(leaf_hash)
106	}
107
108	/// Get the block number of a leaf in the current view.
109	/// Returns `None` if leaf is not in the view.
110	pub fn block_number(&self, leaf_hash: &Hash) -> Option<BlockNumber> {
111		self.block_info_storage.get(leaf_hash).map(|block_info| block_info.block_number)
112	}
113
114	/// Activate a leaf in the view.
115	/// This will request the minimum relay parents the leaf and will load headers in the
116	/// ancestry of the leaf as needed. These are the 'implicit ancestors' of the leaf.
117	///
118	/// To maximize reuse of outdated leaves, it's best to activate new leaves before
119	/// deactivating old ones.
120	///
121	/// The allowed relay parents for the relevant paras under this leaf can be
122	/// queried with [`View::known_allowed_relay_parents_under`].
123	///
124	/// No-op for known leaves.
125	pub async fn activate_leaf<Sender>(
126		&mut self,
127		sender: &mut Sender,
128		leaf_hash: Hash,
129	) -> Result<(), FetchError>
130	where
131		Sender: SubsystemSender<ChainApiMessage>
132			+ SubsystemSender<ProspectiveParachainsMessage>
133			+ SubsystemSender<RuntimeApiMessage>,
134	{
135		if self.leaves.contains_key(&leaf_hash) {
136			return Err(FetchError::AlreadyKnown);
137		}
138
139		let res = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).await;
140
141		match res {
142			Ok(fetched) => {
143				// Retain at least `MINIMUM_RETAIN_LENGTH` blocks in storage.
144				// This helps to avoid Chain API calls when activating leaves in the
145				// same chain.
146				let retain_minimum = std::cmp::min(
147					fetched.minimum_ancestor_number,
148					fetched.leaf_number.saturating_sub(MINIMUM_RETAIN_LENGTH),
149				);
150
151				self.leaves.insert(leaf_hash, ActiveLeafPruningInfo { retain_minimum });
152
153				Ok(())
154			},
155			Err(e) => Err(e),
156		}
157	}
158
159	/// Deactivate a leaf in the view. This prunes any outdated implicit ancestors as well.
160	///
161	/// Returns hashes of blocks pruned from storage.
162	pub fn deactivate_leaf(&mut self, leaf_hash: Hash) -> Vec<Hash> {
163		let mut removed = Vec::new();
164
165		if self.leaves.remove(&leaf_hash).is_none() {
166			return removed;
167		}
168
169		// Prune everything before the minimum out of all leaves,
170		// pruning absolutely everything if there are no leaves (empty view)
171		//
172		// Pruning by block number does leave behind orphaned forks slightly longer
173		// but the memory overhead is negligible.
174		{
175			let minimum = self.leaves.values().map(|l| l.retain_minimum).min();
176
177			self.block_info_storage.retain(|hash, i| {
178				let keep = minimum.map_or(false, |m| i.block_number >= m);
179				if !keep {
180					removed.push(*hash);
181				}
182				keep
183			});
184
185			removed
186		}
187	}
188
189	/// Get an iterator over all allowed relay-parents in the view with no particular order.
190	///
191	/// **Important**: not all blocks are guaranteed to be allowed for some leaves, it may
192	/// happen that a block info is only kept in the view storage because of a retaining rule.
193	///
194	/// For getting relay-parents that are valid for parachain candidates use
195	/// [`View::known_allowed_relay_parents_under`].
196	pub fn all_allowed_relay_parents(&self) -> impl Iterator<Item = &Hash> {
197		self.block_info_storage.keys()
198	}
199
200	/// Get the known, allowed relay-parents that are valid for parachain candidates
201	/// which could be backed in a child of a given block.
202	///
203	/// This is expressed as a contiguous slice of relay-chain block hashes which may
204	/// include the provided block hash itself.
205	///
206	/// `None` indicates that the block hash isn't part of the implicit view or that
207	/// there are no known allowed relay parents.
208	///
209	/// This always returns `Some` for active leaves or for blocks that previously
210	/// were active leaves.
211	///
212	/// This can return the empty slice, which indicates that no relay-parents are allowed
213	/// at the given block hash.
214	pub fn known_allowed_relay_parents_under(&self, block_hash: &Hash) -> Option<&[Hash]> {
215		let block_info = self.block_info_storage.get(block_hash)?;
216		block_info
217			.maybe_allowed_relay_parents
218			.as_ref()
219			.map(|mins| mins.allowed_relay_parents())
220	}
221
222	/// Returns all paths from each leaf's oldest allowed ancestor to the leaf, for every
223	/// leaf whose allowed ancestry contains `relay_parent`.
224	///
225	/// If `relay_parent` is not in any leaf's allowed ancestry, returns an empty `Vec`.
226	pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec<Vec<Hash>> {
227		gum::trace!(
228			target: LOG_TARGET,
229			?relay_parent,
230			leaves=?self.leaves,
231			block_info_storage=?self.block_info_storage,
232			"Finding paths via relay parent"
233		);
234
235		if !self.block_info_storage.contains_key(relay_parent) {
236			return vec![];
237		}
238
239		let mut paths = Vec::new();
240		for (leaf, _) in &self.leaves {
241			let Some(allowed_rps) = self
242				.block_info_storage
243				.get(leaf)
244				.and_then(|info| info.maybe_allowed_relay_parents.as_ref())
245			else {
246				gum::warn!(
247					target: LOG_TARGET,
248					?leaf,
249					"Active leaf missing allowed relay parents",
250				);
251				continue;
252			};
253
254			// allowed_relay_parents_contiguous is in descending order: [leaf, parent, grandparent,
255			// ...] Check that relay_parent is in this leaf's allowed ancestry.
256			let contiguous = &allowed_rps.allowed_relay_parents_contiguous;
257			if contiguous.iter().any(|h| h == relay_parent) {
258				let path: Vec<Hash> = contiguous.iter().rev().copied().collect();
259				paths.push(path);
260			}
261		}
262
263		paths
264	}
265
266	async fn fetch_fresh_leaf_and_insert_ancestry<Sender>(
267		&mut self,
268		leaf_hash: Hash,
269		sender: &mut Sender,
270	) -> Result<FetchSummary, FetchError>
271	where
272		Sender: SubsystemSender<ChainApiMessage>
273			+ SubsystemSender<ProspectiveParachainsMessage>
274			+ SubsystemSender<RuntimeApiMessage>,
275	{
276		let ancestors = fetch_ancestors(leaf_hash, sender).await?;
277		let ancestor_len = ancestors.len();
278
279		let ancestry: Vec<Hash> = iter::once(leaf_hash).chain(ancestors).collect();
280
281		let mut allowed_relay_parents =
282			Some(AllowedRelayParents { allowed_relay_parents_contiguous: ancestry.clone() });
283
284		// Ensure all ancestors up to and including `min_relay_parent` are in the
285		// block storage. When views advance incrementally, everything
286		// should already be present.
287		for block_hash in ancestry {
288			let block_info_entry = match self.block_info_storage.entry(block_hash) {
289				Entry::Occupied(_) => continue,
290				Entry::Vacant(e) => e,
291			};
292
293			let (tx, rx) = oneshot::channel();
294			sender.send_message(ChainApiMessage::BlockHeader(block_hash, tx)).await;
295			let header = match rx.await {
296				Ok(Ok(Some(header))) => header,
297				Ok(Ok(None)) => {
298					return Err(FetchError::BlockHeaderUnavailable(
299						block_hash,
300						BlockHeaderUnavailableReason::Unknown,
301					));
302				},
303				Ok(Err(e)) => {
304					return Err(FetchError::BlockHeaderUnavailable(
305						block_hash,
306						BlockHeaderUnavailableReason::Internal(e),
307					));
308				},
309				Err(_) => {
310					return Err(FetchError::BlockHeaderUnavailable(
311						block_hash,
312						BlockHeaderUnavailableReason::SubsystemUnavailable,
313					));
314				},
315			};
316			block_info_entry.insert(BlockInfo {
317				block_number: header.number,
318				// Populate leaf node with Some:
319				maybe_allowed_relay_parents: allowed_relay_parents.take(),
320			});
321		}
322
323		let leaf_entry = self
324			.block_info_storage
325			.get(&leaf_hash)
326			.expect("We just inserted this entry. qed.");
327
328		Ok(FetchSummary {
329			minimum_ancestor_number: leaf_entry.block_number.saturating_sub(ancestor_len as u32),
330			leaf_number: leaf_entry.block_number,
331		})
332	}
333}
334
335/// Errors when fetching a leaf and associated ancestry.
336#[fatality::fatality]
337pub enum FetchError {
338	/// Activated leaf is already present in view.
339	#[error("Leaf was already known")]
340	AlreadyKnown,
341
342	/// Request to the prospective parachains subsystem failed.
343	#[error("The prospective parachains subsystem was unavailable")]
344	ProspectiveParachainsUnavailable,
345
346	/// Failed to fetch the block header.
347	#[error("A block header was unavailable")]
348	BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason),
349
350	/// A block header was unavailable due to a chain API error.
351	#[error("A block header was unavailable due to a chain API error")]
352	ChainApiError(Hash, ChainApiError),
353
354	/// Request to the Chain API subsystem failed.
355	#[error("The chain API subsystem was unavailable")]
356	ChainApiUnavailable,
357
358	/// Request to the runtime API failed.
359	#[error("Runtime API error: {0}")]
360	RuntimeApi(#[from] runtime::Error),
361}
362
363/// Reasons a block header might have been unavailable.
364#[derive(Debug)]
365pub enum BlockHeaderUnavailableReason {
366	/// Block header simply unknown.
367	Unknown,
368	/// Internal Chain API error.
369	Internal(ChainApiError),
370	/// The subsystem was unavailable.
371	SubsystemUnavailable,
372}
373
374struct FetchSummary {
375	minimum_ancestor_number: BlockNumber,
376	leaf_number: BlockNumber,
377}
378
379/// Fetches ancestor block hashes for a given leaf.
380///
381/// Returns up to `scheduling_lookahead - 1` ancestor block hashes in descending order (from most
382/// recent to oldest), stopping early if a session boundary is encountered. This ensures all
383/// returned ancestors are within the same session as the leaf.
384///
385/// # Returns
386///
387/// A vector of ancestor block hashes in descending order (excluding the leaf itself).
388async fn fetch_ancestors<Sender>(
389	leaf_hash: Hash,
390	sender: &mut Sender,
391) -> Result<Vec<Hash>, FetchError>
392where
393	Sender: SubsystemSender<ProspectiveParachainsMessage>
394		+ SubsystemSender<RuntimeApiMessage>
395		+ SubsystemSender<ChainApiMessage>,
396{
397	// Fetch the session of the leaf. We must make sure that we stop at the ancestor which has a
398	// different session index.
399	let required_session =
400		recv_runtime(request_session_index_for_child(leaf_hash, sender).await).await?;
401
402	let scheduling_lookahead =
403		fetch_scheduling_lookahead(leaf_hash, required_session, sender).await?;
404
405	// Fetch the ancestors, up to (scheduling_lookahead - 1).
406	let (tx, rx) = oneshot::channel();
407	sender
408		.send_message(ChainApiMessage::Ancestors {
409			hash: leaf_hash,
410			k: scheduling_lookahead.saturating_sub(1) as usize,
411			response_channel: tx,
412		})
413		.await;
414	let mut hashes = rx
415		.await
416		.map_err(|_| FetchError::ChainApiUnavailable)?
417		.map_err(|err| FetchError::ChainApiError(leaf_hash, err))?;
418
419	let mut session_change_at = None;
420	for (i, hash) in hashes.iter().enumerate() {
421		let session = recv_runtime(request_session_index_for_child(*hash, sender).await).await?;
422		// The relay chain cannot accept blocks backed from previous sessions, with
423		// potentially previous validators. This is a technical limitation we need to
424		// respect here.
425		if session != required_session {
426			session_change_at = Some(i);
427			break;
428		}
429	}
430	if let Some(session_change_at) = session_change_at {
431		hashes.truncate(session_change_at);
432	}
433	Ok(hashes)
434}
435
436#[cfg(test)]
437mod tests {
438	use super::*;
439	use crate::TimeoutExt;
440	use assert_matches::assert_matches;
441	use futures::future::{join, FutureExt};
442	use polkadot_node_subsystem::{messages::RuntimeApiRequest, AllMessages};
443	use polkadot_node_subsystem_test_helpers::{
444		make_subsystem_context, TestSubsystemContextHandle,
445	};
446	use polkadot_overseer::SubsystemContext;
447	use polkadot_primitives::Header;
448	use sp_core::testing::TaskExecutor;
449	use std::time::Duration;
450
451	const GENESIS_HASH: Hash = Hash::repeat_byte(0xFF);
452	const GENESIS_NUMBER: BlockNumber = 0;
453
454	// Chains A and B are forks of genesis.
455
456	const CHAIN_A: &[Hash] =
457		&[Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)];
458
459	const CHAIN_B: &[Hash] = &[
460		Hash::repeat_byte(0x04),
461		Hash::repeat_byte(0x05),
462		Hash::repeat_byte(0x06),
463		Hash::repeat_byte(0x07),
464		Hash::repeat_byte(0x08),
465		Hash::repeat_byte(0x09),
466	];
467
468	type VirtualOverseer = TestSubsystemContextHandle<AllMessages>;
469
470	const TIMEOUT: Duration = Duration::from_secs(2);
471
472	async fn overseer_recv(virtual_overseer: &mut VirtualOverseer) -> AllMessages {
473		virtual_overseer
474			.recv()
475			.timeout(TIMEOUT)
476			.await
477			.expect("overseer `recv` timed out")
478	}
479
480	fn default_header() -> Header {
481		Header {
482			parent_hash: Hash::zero(),
483			number: 0,
484			state_root: Hash::zero(),
485			extrinsics_root: Hash::zero(),
486			digest: Default::default(),
487		}
488	}
489
490	fn get_block_header(chain: &[Hash], hash: &Hash) -> Option<Header> {
491		let idx = chain.iter().position(|h| h == hash)?;
492		let parent_hash = idx.checked_sub(1).map(|i| chain[i]).unwrap_or(GENESIS_HASH);
493		let number =
494			if *hash == GENESIS_HASH { GENESIS_NUMBER } else { GENESIS_NUMBER + idx as u32 + 1 };
495		Some(Header { parent_hash, number, ..default_header() })
496	}
497
498	async fn assert_block_header_requests(
499		virtual_overseer: &mut VirtualOverseer,
500		chain: &[Hash],
501		blocks: &[Hash],
502	) {
503		for block in blocks.iter().rev() {
504			assert_matches!(
505				overseer_recv(virtual_overseer).await,
506				AllMessages::ChainApi(
507					ChainApiMessage::BlockHeader(hash, tx)
508				) => {
509					assert_eq!(*block, hash, "unexpected block header request");
510					let header = if block == &GENESIS_HASH {
511						Header {
512							number: GENESIS_NUMBER,
513							..default_header()
514						}
515					} else {
516						get_block_header(chain, block).expect("unknown block")
517					};
518
519					tx.send(Ok(Some(header))).unwrap();
520				}
521			);
522		}
523	}
524
525	async fn assert_scheduling_lookahead_request(
526		virtual_overseer: &mut VirtualOverseer,
527		leaf: Hash,
528		lookahead: u32,
529	) {
530		assert_matches!(
531			overseer_recv(virtual_overseer).await,
532			AllMessages::RuntimeApi(
533				RuntimeApiMessage::Request(
534					leaf_hash,
535					RuntimeApiRequest::SchedulingLookahead(
536						_,
537						tx
538					)
539				)
540			) => {
541				assert_eq!(leaf, leaf_hash, "received unexpected leaf hash");
542				tx.send(Ok(lookahead)).unwrap();
543			}
544		);
545	}
546
547	async fn assert_session_index_request(
548		virtual_overseer: &mut VirtualOverseer,
549		leaf: Hash,
550		session: u32,
551	) {
552		assert_matches!(
553			overseer_recv(virtual_overseer).await,
554			AllMessages::RuntimeApi(
555				RuntimeApiMessage::Request(
556					leaf_hash,
557					RuntimeApiRequest::SessionIndexForChild(
558						tx
559					)
560				)
561			) => {
562				assert_eq!(leaf, leaf_hash, "received unexpected leaf hash");
563				tx.send(Ok(session)).unwrap();
564			}
565		);
566	}
567
568	async fn assert_ancestors_request(
569		virtual_overseer: &mut VirtualOverseer,
570		leaf: Hash,
571		expected_ancestor_len: u32,
572		response: Vec<Hash>,
573	) {
574		assert_matches!(
575			overseer_recv(virtual_overseer).await,
576			AllMessages::ChainApi(
577				ChainApiMessage::Ancestors {
578					hash: leaf_hash,
579					k,
580					response_channel: tx
581				}
582			) => {
583				assert_eq!(leaf, leaf_hash, "received unexpected leaf hash");
584				assert_eq!(k, expected_ancestor_len as usize);
585
586				tx.send(Ok(response)).unwrap();
587			}
588		);
589	}
590
591	/// Helper function to activate a leaf and handle the expected sequence of overseer requests.
592	/// This encapsulates the common pattern used across multiple tests.
593	///
594	/// # Parameters
595	/// - `view`: The view to activate the leaf in
596	/// - `ctx`: The subsystem context
597	/// - `ctx_handle`: The virtual overseer handle
598	/// - `leaf`: The leaf hash to activate
599	/// - `session`: The session index for the leaf
600	/// - `scheduling_lookahead`: The scheduling lookahead value
601	/// - `ancestors`: The ancestor hashes (in descending order from leaf)
602	/// - `ancestor_sessions`: Session indices for each ancestor (in descending order)
603	/// - `chain`: The chain to use for block header requests
604	/// - `blocks_for_headers`: The blocks to fetch headers for
605	async fn activate_leaf_with_overseer_requests<Ctx>(
606		view: &mut View,
607		ctx: &mut Ctx,
608		ctx_handle: &mut VirtualOverseer,
609		leaf: Hash,
610		session: u32,
611		scheduling_lookahead: u32,
612		ancestors: Vec<Hash>,
613		ancestor_sessions: Vec<u32>,
614		chain: &[Hash],
615		blocks_for_headers: &[Hash],
616	) where
617		Ctx: SubsystemContext<Message = AllMessages>,
618		Ctx::Sender: SubsystemSender<ChainApiMessage>
619			+ SubsystemSender<ProspectiveParachainsMessage>
620			+ SubsystemSender<RuntimeApiMessage>,
621	{
622		let fut = view.activate_leaf(ctx.sender(), leaf).timeout(TIMEOUT).map(|res| {
623			res.expect("`activate_leaf` timed out").unwrap();
624		});
625		let overseer_fut = async {
626			// Session index for leaf
627			assert_session_index_request(ctx_handle, leaf, session).await;
628
629			// Scheduling lookahead
630			assert_scheduling_lookahead_request(ctx_handle, leaf, scheduling_lookahead).await;
631
632			// Ancestors request (returned in descending order)
633			assert_ancestors_request(ctx_handle, leaf, scheduling_lookahead - 1, ancestors.clone())
634				.await;
635
636			// Session index for each ancestor (in descending order)
637			for (ancestor, ancestor_session) in ancestors.iter().zip(ancestor_sessions.iter()) {
638				assert_session_index_request(ctx_handle, *ancestor, *ancestor_session).await;
639			}
640
641			// Block headers for leaf and all ancestors
642			assert_block_header_requests(ctx_handle, chain, blocks_for_headers).await;
643		};
644		join(fut, overseer_fut).await;
645	}
646
647	/// Helper function to assert that allowed relay parents match expectations.
648	///
649	/// # Parameters
650	/// - `view`: The view to check
651	/// - `leaf`: The leaf hash to check allowed relay parents for
652	/// - `expected_ancestry`: The expected allowed relay parents (in descending order)
653	fn assert_expected_allowed_relay_parents(view: &View, leaf: &Hash, expected_ancestry: &[Hash]) {
654		let leaf_info =
655			view.block_info_storage.get(leaf).expect("block must be present in storage");
656		assert_matches!(
657			leaf_info.maybe_allowed_relay_parents,
658			Some(ref allowed_relay_parents) => {
659				assert_eq!(
660					allowed_relay_parents.allowed_relay_parents_contiguous,
661					expected_ancestry
662				);
663				assert_eq!(view.known_allowed_relay_parents_under(leaf), Some(expected_ancestry));
664			}
665		);
666	}
667
668	/// Tests basic view construction by activating two leaves on different chain forks.
669	///
670	/// Verifies that:
671	/// - Allowed relay parents are correctly computed based on scheduling lookahead
672	/// - Only the leaf block stores allowed relay parents, not intermediate ancestors
673	/// - Multiple leaves can coexist in the view
674	/// - Path finding works correctly for blocks within the implicit view
675	#[test]
676	fn construct_fresh_view() {
677		let pool = TaskExecutor::new();
678		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
679
680		let mut view = View::default();
681
682		// Activate first leaf on CHAIN_B with lookahead of 3
683		const SESSION: u32 = 2;
684		const SCHEDULING_LOOKAHEAD: u32 = 3;
685
686		let leaf = CHAIN_B.last().unwrap();
687		let leaf_idx = CHAIN_B.len() - 1;
688		// With lookahead 3, we fetch 2 ancestors (lookahead - 1)
689		let min_idx = leaf_idx - (SCHEDULING_LOOKAHEAD as usize - 1);
690
691		futures::executor::block_on(activate_leaf_with_overseer_requests(
692			&mut view,
693			&mut ctx,
694			&mut ctx_handle,
695			*leaf,
696			SESSION,
697			SCHEDULING_LOOKAHEAD,
698			CHAIN_B[min_idx..leaf_idx].iter().rev().copied().collect(),
699			vec![SESSION; leaf_idx - min_idx],
700			CHAIN_B,
701			&CHAIN_B[min_idx..=leaf_idx],
702		));
703
704		// Only leaf blocks have allowed relay parents, not intermediate ancestors
705		for i in min_idx..(CHAIN_B.len() - 1) {
706			assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i]).is_none());
707		}
708
709		// The leaf should have all blocks from min_idx to leaf as allowed relay parents
710		let expected_ancestry: Vec<Hash> =
711			CHAIN_B[min_idx..=leaf_idx].iter().rev().copied().collect();
712		assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
713
714		// Verify we have exactly one active leaf
715		assert_eq!(view.leaves.len(), 1);
716		assert!(view.leaves.contains_key(leaf));
717
718		// Blocks outside the implicit view return empty paths
719		assert!(view.paths_via_relay_parent(&CHAIN_B[0]).is_empty());
720		assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty());
721
722		// Blocks within the implicit view return the full allowed ancestry path
723		// (oldest allowed ancestor to leaf), bounded by scheduling lookahead.
724		assert_eq!(
725			view.paths_via_relay_parent(&CHAIN_B[min_idx]),
726			vec![CHAIN_B[min_idx..].to_vec()]
727		);
728		// Same full path — relay_parent just needs to be somewhere on it.
729		assert_eq!(
730			view.paths_via_relay_parent(&CHAIN_B[min_idx + 1]),
731			vec![CHAIN_B[min_idx..].to_vec()]
732		);
733		assert_eq!(view.paths_via_relay_parent(&leaf), vec![CHAIN_B[min_idx..].to_vec()]);
734
735		// Activate second leaf on CHAIN_A (a fork of CHAIN_B at genesis)
736		const SCHEDULING_LOOKAHEAD_A: u32 = 4;
737		let leaf = CHAIN_A.last().unwrap();
738		let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
739		let leaf_idx = blocks.len() - 1;
740		// With lookahead 4, we fetch 3 ancestors, starting from CHAIN_A[0]
741		let min_idx_a = 1;
742
743		futures::executor::block_on(activate_leaf_with_overseer_requests(
744			&mut view,
745			&mut ctx,
746			&mut ctx_handle,
747			*leaf,
748			SESSION,
749			SCHEDULING_LOOKAHEAD_A,
750			blocks[min_idx_a..leaf_idx].iter().rev().copied().collect(),
751			vec![SESSION; leaf_idx - min_idx_a],
752			CHAIN_A,
753			&blocks[min_idx_a..],
754		));
755
756		// Now we have two active leaves from different forks
757		assert_eq!(view.leaves.len(), 2);
758
759		// Second leaf has its own set of allowed relay parents
760		let expected_ancestry: Vec<Hash> = blocks[min_idx_a..].iter().rev().copied().collect();
761		assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
762	}
763
764	/// Tests view construction with different scheduling lookahead values and session boundaries.
765	///
766	/// Verifies that:
767	/// - Views can be constructed with various scheduling lookahead values
768	/// - Session boundaries are respected (ancestors in different sessions are excluded)
769	/// - Path finding correctly handles session boundaries
770	#[test]
771	fn construct_fresh_view_with_various_lookaheads() {
772		let pool = TaskExecutor::new();
773		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
774
775		let mut view = View::new();
776
777		// Activate CHAIN_B with a larger lookahead value (5)
778		const SCHEDULING_LOOKAHEAD: u32 = 5;
779		const MIN_RELAY_PARENT_NUMBER: u32 = 4;
780
781		let current_session = 2;
782
783		let leaf = CHAIN_B.last().unwrap();
784		let leaf_idx = CHAIN_B.len() - 1;
785		// Calculate minimum ancestor index based on absolute block number
786		let min_idx = (MIN_RELAY_PARENT_NUMBER - GENESIS_NUMBER - 1) as usize;
787
788		futures::executor::block_on(activate_leaf_with_overseer_requests(
789			&mut view,
790			&mut ctx,
791			&mut ctx_handle,
792			*leaf,
793			current_session,
794			SCHEDULING_LOOKAHEAD,
795			CHAIN_B[min_idx..leaf_idx].iter().rev().copied().collect(),
796			vec![current_session; leaf_idx - min_idx],
797			CHAIN_B,
798			&CHAIN_B[min_idx..=leaf_idx],
799		));
800
801		// Intermediate ancestors don't have allowed relay parents
802		for i in min_idx..(CHAIN_B.len() - 1) {
803			assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i]).is_none());
804		}
805
806		// Leaf has expected allowed relay parents
807		let expected_ancestry: Vec<Hash> =
808			CHAIN_B[min_idx..=leaf_idx].iter().rev().copied().collect();
809		assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
810
811		// Block from different fork returns no paths
812		assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty());
813		// Block within view returns correct path
814		assert_eq!(
815			view.paths_via_relay_parent(&CHAIN_B[min_idx]),
816			vec![CHAIN_B[min_idx..].to_vec()]
817		);
818
819		// Activate CHAIN_A where ancestors extend back to genesis (different session)
820		// This tests that we stop fetching at session boundaries
821		let leaf = CHAIN_A.last().unwrap();
822		let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
823		let leaf_idx = blocks.len() - 1;
824
825		// Ancestors are in current session, but genesis is in session 0
826		// This simulates a session boundary
827		let mut ancestor_sessions = vec![current_session; leaf_idx - 1];
828		ancestor_sessions.push(0);
829
830		futures::executor::block_on(activate_leaf_with_overseer_requests(
831			&mut view,
832			&mut ctx,
833			&mut ctx_handle,
834			*leaf,
835			current_session,
836			blocks.len() as u32 + 1,
837			blocks[..leaf_idx].iter().rev().copied().collect(),
838			ancestor_sessions,
839			CHAIN_A,
840			// Only fetch headers for CHAIN_A blocks; genesis is excluded due to session boundary
841			&blocks[1..=leaf_idx],
842		));
843
844		// Two leaves active (one on each fork)
845		assert_eq!(view.leaves.len(), 2);
846
847		// Allowed relay parents only include CHAIN_A blocks (not genesis due to session boundary)
848		let expected_ancestry: Vec<Hash> = CHAIN_A[..].iter().rev().copied().collect();
849		assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
850
851		// Genesis is not in the view because of the session boundary
852		assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty());
853		// But CHAIN_A blocks are in the view
854		assert_eq!(view.paths_via_relay_parent(&CHAIN_A[0]), vec![CHAIN_A.to_vec()]);
855	}
856
857	/// Tests that block info storage is reused when activating subsequent leaves.
858	///
859	/// Verifies that:
860	/// - Block info for overlapping ancestors is cached and reused
861	/// - Only new blocks fetch headers from the chain API
862	/// - Previously activated leaves retain their allowed relay parents after new leaves are added
863	#[test]
864	fn reuse_block_info_storage() {
865		let pool = TaskExecutor::new();
866		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
867
868		let mut view = View::default();
869
870		// Activate first leaf at block 3 with lookahead 3
871		const SESSION: u32 = 2;
872		const SCHEDULING_LOOKAHEAD_A: u32 = 3;
873		let leaf_a_number = 3;
874		let leaf_a = CHAIN_B[leaf_a_number - 1];
875		let min_idx = leaf_a_number - (SCHEDULING_LOOKAHEAD_A as usize - 1);
876
877		futures::executor::block_on(activate_leaf_with_overseer_requests(
878			&mut view,
879			&mut ctx,
880			&mut ctx_handle,
881			leaf_a,
882			SESSION,
883			SCHEDULING_LOOKAHEAD_A,
884			CHAIN_B[min_idx..(leaf_a_number - 1)].iter().rev().copied().collect(),
885			vec![SESSION; leaf_a_number - 1 - min_idx],
886			CHAIN_B,
887			&CHAIN_B[min_idx..leaf_a_number],
888		));
889
890		// Activate second leaf at block 5 with lookahead 5
891		// This should reuse blocks 1-3 from storage (already fetched for leaf_a)
892		const SCHEDULING_LOOKAHEAD_B: u32 = 5;
893		let leaf_b_number = 5;
894		let leaf_b = CHAIN_B[leaf_b_number - 1];
895
896		futures::executor::block_on(activate_leaf_with_overseer_requests(
897			&mut view,
898			&mut ctx,
899			&mut ctx_handle,
900			leaf_b,
901			SESSION,
902			SCHEDULING_LOOKAHEAD_B,
903			CHAIN_B[min_idx..(leaf_b_number - 1)].iter().rev().copied().collect(),
904			vec![SESSION; leaf_b_number - 1 - min_idx],
905			CHAIN_B,
906			// Only blocks 3-4 need headers; blocks 0-2 were already fetched for leaf_a
907			&CHAIN_B[leaf_a_number..leaf_b_number],
908		));
909
910		// Verify that leaf_a still has its allowed relay parents after activating leaf_b
911		let expected_ancestry: Vec<Hash> =
912			CHAIN_B[min_idx..leaf_a_number].iter().rev().copied().collect();
913		assert_expected_allowed_relay_parents(&view, &leaf_a, &expected_ancestry);
914	}
915
916	/// Tests that outdated blocks are pruned when leaves are deactivated.
917	///
918	/// Verifies that:
919	/// - Deactivating a non-leaf block is a no-op
920	/// - Blocks are pruned when no active leaf requires them
921	/// - The minimum block number across all leaves determines what gets pruned
922	/// - All blocks are pruned when the last leaf is deactivated
923	#[test]
924	fn pruning() {
925		let pool = TaskExecutor::new();
926		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
927
928		let mut view = View::default();
929
930		// Activate leaf_a (second-to-last block) with lookahead 4
931		const SESSION: u32 = 2;
932		const SCHEDULING_LOOKAHEAD_A: u32 = 4;
933		let leaf_a = CHAIN_B.iter().rev().nth(1).unwrap();
934		let leaf_a_idx = CHAIN_B.len() - 2;
935		let min_a_idx = leaf_a_idx - (SCHEDULING_LOOKAHEAD_A - 1) as usize;
936
937		futures::executor::block_on(activate_leaf_with_overseer_requests(
938			&mut view,
939			&mut ctx,
940			&mut ctx_handle,
941			*leaf_a,
942			SESSION,
943			SCHEDULING_LOOKAHEAD_A,
944			CHAIN_B[min_a_idx..leaf_a_idx].iter().rev().copied().collect(),
945			vec![SESSION; leaf_a_idx - min_a_idx],
946			CHAIN_B,
947			&CHAIN_B[min_a_idx..=leaf_a_idx],
948		));
949
950		// Activate leaf_b (last block) with smaller lookahead 3
951		// This has a higher minimum block number than leaf_a
952		const SCHEDULING_LOOKAHEAD_B: u32 = 3;
953		let leaf_b = CHAIN_B.last().unwrap();
954		let leaf_b_idx = CHAIN_B.len() - 1;
955		let min_b_idx = leaf_b_idx - (SCHEDULING_LOOKAHEAD_B - 1) as usize;
956
957		futures::executor::block_on(activate_leaf_with_overseer_requests(
958			&mut view,
959			&mut ctx,
960			&mut ctx_handle,
961			*leaf_b,
962			SESSION,
963			SCHEDULING_LOOKAHEAD_B,
964			CHAIN_B[min_b_idx..leaf_b_idx].iter().rev().copied().collect(),
965			vec![SESSION; leaf_b_idx - min_b_idx],
966			CHAIN_B,
967			&[CHAIN_B[leaf_b_idx]], // Only leaf_b needs fetching; ancestors are cached
968		));
969
970		// Deactivating a non-leaf block should be a no-op
971		let block_info_len = view.block_info_storage.len();
972		view.deactivate_leaf(CHAIN_B[leaf_a_idx - 1]);
973		assert_eq!(block_info_len, view.block_info_storage.len());
974
975		// Deactivate leaf_b. leaf_a requires blocks from min_a_idx onward,
976		// so blocks before min_a_idx should be pruned
977		view.deactivate_leaf(*leaf_b);
978		for hash in CHAIN_B.iter().take(min_a_idx) {
979			assert!(!view.block_info_storage.contains_key(hash));
980		}
981		// Blocks from min_a_idx onward (required by leaf_a) should NOT be pruned
982		for hash in CHAIN_B.iter().skip(min_a_idx).take(leaf_a_idx - min_a_idx + 1) {
983			assert!(view.block_info_storage.contains_key(hash));
984		}
985
986		// Deactivate the last remaining leaf - all blocks should be pruned
987		view.deactivate_leaf(*leaf_a);
988		assert!(view.block_info_storage.is_empty());
989	}
990
991	/// Tests view construction when the leaf is the genesis block.
992	///
993	/// Verifies that:
994	/// - Genesis block can be activated as a leaf
995	/// - No ancestors are fetched (genesis has no parent)
996	/// - Genesis is included in its own allowed relay parents
997	#[test]
998	fn genesis_ancestry() {
999		let pool = TaskExecutor::new();
1000		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
1001
1002		let mut view = View::default();
1003
1004		// Activate genesis as a leaf with minimal lookahead
1005		const SESSION: u32 = 0;
1006		const SCHEDULING_LOOKAHEAD: u32 = 1;
1007
1008		futures::executor::block_on(activate_leaf_with_overseer_requests(
1009			&mut view,
1010			&mut ctx,
1011			&mut ctx_handle,
1012			GENESIS_HASH,
1013			SESSION,
1014			SCHEDULING_LOOKAHEAD,
1015			vec![], // Genesis has no ancestors
1016			vec![], // No ancestor sessions
1017			&[GENESIS_HASH],
1018			&[GENESIS_HASH],
1019		));
1020
1021		// Genesis block should have itself as the only allowed relay parent
1022		assert_matches!(
1023			view.known_allowed_relay_parents_under(&GENESIS_HASH),
1024			Some(hashes) if hashes == &[GENESIS_HASH]
1025		);
1026	}
1027
1028	/// Tests path finding through forked chains.
1029	///
1030	/// Verifies that:
1031	/// - Multiple leaves on different forks can coexist
1032	/// - Path finding returns correct paths for blocks in each fork
1033	/// - Blocks outside the implicit view return empty paths
1034	/// - Genesis (common ancestor) is excluded due to scheduling lookahead
1035	#[test]
1036	fn path_with_fork() {
1037		let pool = TaskExecutor::new();
1038		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
1039
1040		let mut view = View::default();
1041
1042		// Activate leaf on CHAIN_A (forks from genesis)
1043		const SESSION: u32 = 2;
1044		const SCHEDULING_LOOKAHEAD_A: u32 = 4;
1045		let leaf = CHAIN_A.last().unwrap();
1046		let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
1047		let leaf_idx = blocks.len() - 1;
1048
1049		futures::executor::block_on(activate_leaf_with_overseer_requests(
1050			&mut view,
1051			&mut ctx,
1052			&mut ctx_handle,
1053			*leaf,
1054			SESSION,
1055			SCHEDULING_LOOKAHEAD_A,
1056			blocks[1..leaf_idx].iter().rev().copied().collect(),
1057			vec![SESSION; leaf_idx - 1],
1058			CHAIN_A,
1059			&blocks[1..],
1060		));
1061
1062		// Activate leaf on CHAIN_B (also forks from genesis)
1063		const SCHEDULING_LOOKAHEAD_B: u32 = 3;
1064		let leaf = CHAIN_B.last().unwrap();
1065		let leaf_idx = CHAIN_B.len() - 1;
1066
1067		// With lookahead 3, minimum index is at block 3 (leaf_idx=5, so 5-2=3)
1068		let min_b_idx = leaf_idx - (SCHEDULING_LOOKAHEAD_B - 1) as usize;
1069		futures::executor::block_on(activate_leaf_with_overseer_requests(
1070			&mut view,
1071			&mut ctx,
1072			&mut ctx_handle,
1073			*leaf,
1074			SESSION,
1075			SCHEDULING_LOOKAHEAD_B,
1076			CHAIN_B[min_b_idx..leaf_idx].iter().rev().copied().collect(),
1077			vec![SESSION; leaf_idx - min_b_idx],
1078			CHAIN_B,
1079			&CHAIN_B[min_b_idx..],
1080		));
1081
1082		// Both leaves are active
1083		assert_eq!(view.leaves.len(), 2);
1084
1085		// Genesis is not in the view because scheduling lookahead doesn't go back that far
1086		let paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH);
1087		assert_eq!(paths_to_genesis, Vec::<Vec<Hash>>::new());
1088
1089		// CHAIN_A[1] is in the view, path is the full allowed ancestry
1090		let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]);
1091		let expected_path_to_leaf_in_a = vec![CHAIN_A.to_vec()];
1092		assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a);
1093
1094		// CHAIN_B[4] is in the view, path is the full allowed ancestry for this leaf
1095		let path_to_leaf_in_b = view.paths_via_relay_parent(&CHAIN_B[4]);
1096		let expected_path_to_leaf_in_b = vec![CHAIN_B[3..].to_vec()];
1097		assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b);
1098
1099		// Unknown block returns empty paths
1100		assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::<Vec<Hash>>::new());
1101	}
1102
1103	/// When a fork leaf at a lower height coexists with the main chain leaf,
1104	/// `block_info_storage` retains old blocks. `paths_via_relay_parent` walks through all
1105	/// of them, producing paths longer than `scheduling_lookahead`.
1106	///
1107	/// This causes `is_slot_available` in the collator protocol to compute `valid_len = 0` for
1108	/// old-but-valid scheduling parents, unconditionally rejecting advertisements.
1109	///
1110	/// See: https://github.com/paritytech/polkadot-sdk/issues/11625
1111	#[test]
1112	fn max_ancesty_len_honored() {
1113		let pool = TaskExecutor::new();
1114		let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
1115
1116		let mut view = View::default();
1117
1118		const SESSION: u32 = 2;
1119		const SCHEDULING_LOOKAHEAD: u32 = 3;
1120
1121		// Step 1: Activate a "fork leaf" at CHAIN_B[2] (#3) with lookahead=3.
1122		// This represents a fork leaf that stays active while the main chain advances.
1123		// Fetches 2 ancestors: CHAIN_B[1] (#2), CHAIN_B[0] (#1).
1124		// Storage after: {B0, B1, B2}
1125		let fork_leaf = CHAIN_B[2];
1126		futures::executor::block_on(activate_leaf_with_overseer_requests(
1127			&mut view,
1128			&mut ctx,
1129			&mut ctx_handle,
1130			fork_leaf,
1131			SESSION,
1132			SCHEDULING_LOOKAHEAD,
1133			vec![CHAIN_B[1], CHAIN_B[0]], // ancestors in descending order
1134			vec![SESSION; 2],
1135			CHAIN_B,
1136			&CHAIN_B[0..=2], // headers for B0, B1, B2
1137		));
1138
1139		assert_eq!(view.leaves.len(), 1);
1140
1141		// Step 2: Activate the main chain leaf at CHAIN_B[5] (#6) with lookahead=3.
1142		// Fetches 2 ancestors: CHAIN_B[4] (#5), CHAIN_B[3] (#4).
1143		// B0, B1, B2 remain in storage because fork leaf's retain_minimum = #1.
1144		// Storage after: {B0, B1, B2, B3, B4, B5}
1145		let main_leaf = CHAIN_B[5];
1146		futures::executor::block_on(activate_leaf_with_overseer_requests(
1147			&mut view,
1148			&mut ctx,
1149			&mut ctx_handle,
1150			main_leaf,
1151			SESSION,
1152			SCHEDULING_LOOKAHEAD,
1153			vec![CHAIN_B[4], CHAIN_B[3]], // ancestors in descending order
1154			vec![SESSION; 2],
1155			CHAIN_B,
1156			&CHAIN_B[3..=5], // only B3, B4, B5 need headers (B0-B2 already in storage)
1157		));
1158
1159		assert_eq!(view.leaves.len(), 2);
1160
1161		// Verify all 6 blocks are in storage (old blocks retained by fork leaf).
1162		for hash in &CHAIN_B[..6] {
1163			assert!(
1164				view.block_info_storage.contains_key(hash),
1165				"Block {:?} should be in storage",
1166				hash
1167			);
1168		}
1169
1170		// B0 is in the fork leaf's allowed ancestry {B0, B1, B2} but NOT in the
1171		// main leaf's allowed ancestry {B3, B4, B5}. So paths_via_relay_parent(&B0)
1172		// should only return a path via the fork leaf, not the main leaf.
1173		let paths = view.paths_via_relay_parent(&CHAIN_B[0]);
1174
1175		// Expected: only 1 path — via the fork leaf [B0, B1, B2], length 3.
1176		// The main leaf should NOT produce a path through B0 since B0 is outside
1177		// its allowed ancestry.
1178		assert_eq!(paths.len(), 1, "B0 should only be reachable via the fork leaf");
1179
1180		let path_via_fork = &paths[0];
1181		assert_eq!(path_via_fork.last(), Some(&fork_leaf));
1182		assert_eq!(path_via_fork.len(), 3);
1183		assert_eq!(path_via_fork, &CHAIN_B[0..=2].to_vec());
1184
1185		// B3 is in the main leaf's allowed ancestry {B3, B4, B5} but NOT in the
1186		// fork leaf's allowed ancestry {B0, B1, B2}. Should only return one path.
1187		let paths = view.paths_via_relay_parent(&CHAIN_B[3]);
1188		assert_eq!(paths.len(), 1, "B3 should only be reachable via the main leaf");
1189		let path_via_main = &paths[0];
1190		assert_eq!(path_via_main.last(), Some(&main_leaf));
1191		assert_eq!(path_via_main.len(), 3);
1192		assert_eq!(path_via_main, &CHAIN_B[3..=5].to_vec());
1193
1194		// All paths should be bounded by lookahead.
1195		for hash in &CHAIN_B[..6] {
1196			for path in view.paths_via_relay_parent(hash) {
1197				assert!(
1198					path.len() <= SCHEDULING_LOOKAHEAD as usize,
1199					"Path through {:?} has length {} > lookahead {}",
1200					hash,
1201					path.len(),
1202					SCHEDULING_LOOKAHEAD,
1203				);
1204			}
1205		}
1206	}
1207}