1use 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
36const MINIMUM_RETAIN_LENGTH: BlockNumber = 2;
38
39#[derive(Clone)]
43pub struct View {
44 leaves: HashMap<Hash, ActiveLeafPruningInfo>,
45 block_info_storage: HashMap<Hash, BlockInfo>,
46}
47
48impl View {
49 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#[derive(Debug, Clone)]
63struct AllowedRelayParents {
64 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 retain_minimum: BlockNumber,
80}
81
82#[derive(Debug, Clone)]
83struct BlockInfo {
84 block_number: BlockNumber,
85 maybe_allowed_relay_parents: Option<AllowedRelayParents>,
95}
96
97impl View {
98 pub fn leaves(&self) -> impl Iterator<Item = &Hash> {
100 self.leaves.keys()
101 }
102
103 pub fn contains_leaf(&self, leaf_hash: &Hash) -> bool {
105 self.leaves.contains_key(leaf_hash)
106 }
107
108 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 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 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 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 {
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 pub fn all_allowed_relay_parents(&self) -> impl Iterator<Item = &Hash> {
197 self.block_info_storage.keys()
198 }
199
200 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 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 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 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 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#[fatality::fatality]
337pub enum FetchError {
338 #[error("Leaf was already known")]
340 AlreadyKnown,
341
342 #[error("The prospective parachains subsystem was unavailable")]
344 ProspectiveParachainsUnavailable,
345
346 #[error("A block header was unavailable")]
348 BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason),
349
350 #[error("A block header was unavailable due to a chain API error")]
352 ChainApiError(Hash, ChainApiError),
353
354 #[error("The chain API subsystem was unavailable")]
356 ChainApiUnavailable,
357
358 #[error("Runtime API error: {0}")]
360 RuntimeApi(#[from] runtime::Error),
361}
362
363#[derive(Debug)]
365pub enum BlockHeaderUnavailableReason {
366 Unknown,
368 Internal(ChainApiError),
370 SubsystemUnavailable,
372}
373
374struct FetchSummary {
375 minimum_ancestor_number: BlockNumber,
376 leaf_number: BlockNumber,
377}
378
379async 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 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 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 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 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 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 assert_session_index_request(ctx_handle, leaf, session).await;
628
629 assert_scheduling_lookahead_request(ctx_handle, leaf, scheduling_lookahead).await;
631
632 assert_ancestors_request(ctx_handle, leaf, scheduling_lookahead - 1, ancestors.clone())
634 .await;
635
636 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 assert_block_header_requests(ctx_handle, chain, blocks_for_headers).await;
643 };
644 join(fut, overseer_fut).await;
645 }
646
647 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 #[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 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 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 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 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 assert_eq!(view.leaves.len(), 1);
716 assert!(view.leaves.contains_key(leaf));
717
718 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 assert_eq!(
725 view.paths_via_relay_parent(&CHAIN_B[min_idx]),
726 vec![CHAIN_B[min_idx..].to_vec()]
727 );
728 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 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 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 assert_eq!(view.leaves.len(), 2);
758
759 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 #[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 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 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 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 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 assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty());
813 assert_eq!(
815 view.paths_via_relay_parent(&CHAIN_B[min_idx]),
816 vec![CHAIN_B[min_idx..].to_vec()]
817 );
818
819 let leaf = CHAIN_A.last().unwrap();
822 let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
823 let leaf_idx = blocks.len() - 1;
824
825 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 &blocks[1..=leaf_idx],
842 ));
843
844 assert_eq!(view.leaves.len(), 2);
846
847 let expected_ancestry: Vec<Hash> = CHAIN_A[..].iter().rev().copied().collect();
849 assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
850
851 assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty());
853 assert_eq!(view.paths_via_relay_parent(&CHAIN_A[0]), vec![CHAIN_A.to_vec()]);
855 }
856
857 #[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 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 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 &CHAIN_B[leaf_a_number..leaf_b_number],
908 ));
909
910 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 #[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 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 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]], ));
969
970 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 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 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 view.deactivate_leaf(*leaf_a);
988 assert!(view.block_info_storage.is_empty());
989 }
990
991 #[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 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![], vec![], &[GENESIS_HASH],
1018 &[GENESIS_HASH],
1019 ));
1020
1021 assert_matches!(
1023 view.known_allowed_relay_parents_under(&GENESIS_HASH),
1024 Some(hashes) if hashes == &[GENESIS_HASH]
1025 );
1026 }
1027
1028 #[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 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 const SCHEDULING_LOOKAHEAD_B: u32 = 3;
1064 let leaf = CHAIN_B.last().unwrap();
1065 let leaf_idx = CHAIN_B.len() - 1;
1066
1067 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 assert_eq!(view.leaves.len(), 2);
1084
1085 let paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH);
1087 assert_eq!(paths_to_genesis, Vec::<Vec<Hash>>::new());
1088
1089 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 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 assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::<Vec<Hash>>::new());
1101 }
1102
1103 #[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 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]], vec![SESSION; 2],
1135 CHAIN_B,
1136 &CHAIN_B[0..=2], ));
1138
1139 assert_eq!(view.leaves.len(), 1);
1140
1141 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]], vec![SESSION; 2],
1155 CHAIN_B,
1156 &CHAIN_B[3..=5], ));
1158
1159 assert_eq!(view.leaves.len(), 2);
1160
1161 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 let paths = view.paths_via_relay_parent(&CHAIN_B[0]);
1174
1175 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 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 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}