// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // This file is part of Pezkuwi. // Pezkuwi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Pezkuwi is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Pezkuwi. If not, see . use futures::channel::oneshot; use pezkuwi_node_subsystem::{ errors::ChainApiError, messages::{ChainApiMessage, ProspectiveTeyrchainsMessage, RuntimeApiMessage}, SubsystemSender, }; use pezkuwi_primitives::{BlockNumber, Hash, Id as ParaId}; use std::collections::{HashMap, HashSet}; use crate::{ inclusion_emulator::RelayChainBlockInfo, request_session_index_for_child, runtime::{self, fetch_scheduling_lookahead, recv_runtime}, LOG_TARGET, }; // Always aim to retain 1 block before the active leaves. const MINIMUM_RETAIN_LENGTH: BlockNumber = 2; /// Handles the implicit view of the relay chain derived from the immediate view, which /// is composed of active leaves, and the minimum relay-parents allowed for /// candidates of various teyrchains at those leaves. #[derive(Clone)] pub struct View { leaves: HashMap, block_info_storage: HashMap, collating_for: Option, } impl View { /// Create a new empty view. /// If `collating_for` is `Some`, the node is a collator and is only interested in the allowed /// relay parents of a single paraid. When this is true, prospective-teyrchains is no longer /// queried. pub fn new(collating_for: Option) -> Self { Self { leaves: Default::default(), block_info_storage: Default::default(), collating_for } } } impl Default for View { fn default() -> Self { Self::new(None) } } // Minimum relay parents implicitly relative to a particular block. #[derive(Debug, Clone)] struct AllowedRelayParents { // minimum relay parents can only be fetched for active leaves, // so this will be empty for all blocks that haven't ever been // witnessed as active leaves. minimum_relay_parents: HashMap, // Ancestry, in descending order, starting from the block hash itself down // to and including the minimum of `minimum_relay_parents`. allowed_relay_parents_contiguous: Vec, } impl AllowedRelayParents { fn allowed_relay_parents_for( &self, para_id: Option, base_number: BlockNumber, ) -> &[Hash] { let para_id = match para_id { None => return &self.allowed_relay_parents_contiguous[..], Some(p) => p, }; let para_min = match self.minimum_relay_parents.get(¶_id) { Some(p) => *p, None => return &[], }; if base_number < para_min { return &[]; } let diff = base_number - para_min; // difference of 0 should lead to slice len of 1 let slice_len = ((diff + 1) as usize).min(self.allowed_relay_parents_contiguous.len()); &self.allowed_relay_parents_contiguous[..slice_len] } } #[derive(Debug, Clone)] struct ActiveLeafPruningInfo { // The minimum block in the same branch of the relay-chain that should be // preserved. retain_minimum: BlockNumber, } #[derive(Debug, Clone)] struct BlockInfo { block_number: BlockNumber, // If this was previously an active leaf, this will be `Some` // and is useful for understanding the views of peers in the network // which may not be in perfect synchrony with our own view. // // If they are ahead of us in getting a new leaf, there's nothing we // can do as it's an unrecognized block hash. But if they're behind us, // it's useful for us to retain some information about previous leaves' // implicit views so we can continue to send relevant messages to them // until they catch up. maybe_allowed_relay_parents: Option, parent_hash: Hash, } /// Information about a relay-chain block, to be used when calling this module from prospective /// teyrchains. #[derive(Debug, Clone, PartialEq)] pub struct BlockInfoProspectiveTeyrchains { /// The hash of the relay-chain block. pub hash: Hash, /// The hash of the parent relay-chain block. pub parent_hash: Hash, /// The number of the relay-chain block. pub number: BlockNumber, /// The storage-root of the relay-chain block. pub storage_root: Hash, } impl From for RelayChainBlockInfo { fn from(value: BlockInfoProspectiveTeyrchains) -> Self { Self { hash: value.hash, number: value.number, storage_root: value.storage_root } } } impl View { /// Get an iterator over active leaves in the view. pub fn leaves(&self) -> impl Iterator { self.leaves.keys() } /// Check if the given block hash is an active leaf of the current view. pub fn contains_leaf(&self, leaf_hash: &Hash) -> bool { self.leaves.contains_key(leaf_hash) } /// Get the block number of a leaf in the current view. /// Returns `None` if leaf is not in the view. pub fn block_number(&self, leaf_hash: &Hash) -> Option { self.block_info_storage.get(leaf_hash).map(|block_info| block_info.block_number) } /// Activate a leaf in the view. /// This will request the minimum relay parents the leaf and will load headers in the /// ancestry of the leaf as needed. These are the 'implicit ancestors' of the leaf. /// /// To maximize reuse of outdated leaves, it's best to activate new leaves before /// deactivating old ones. /// /// The allowed relay parents for the relevant paras under this leaf can be /// queried with [`View::known_allowed_relay_parents_under`]. /// /// No-op for known leaves. pub async fn activate_leaf( &mut self, sender: &mut Sender, leaf_hash: Hash, ) -> Result<(), FetchError> where Sender: SubsystemSender + SubsystemSender + SubsystemSender, { if self.leaves.contains_key(&leaf_hash) { return Err(FetchError::AlreadyKnown); } let res = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).await; match res { Ok(fetched) => { // Retain at least `MINIMUM_RETAIN_LENGTH` blocks in storage. // This helps to avoid Chain API calls when activating leaves in the // same chain. let retain_minimum = std::cmp::min( fetched.minimum_ancestor_number, fetched.leaf_number.saturating_sub(MINIMUM_RETAIN_LENGTH), ); self.leaves.insert(leaf_hash, ActiveLeafPruningInfo { retain_minimum }); Ok(()) }, Err(e) => Err(e), } } /// Activate a leaf in the view. To be used by the prospective teyrchains subsystem. /// /// This will not request any additional data, as prospective teyrchains already provides all /// the required info. /// NOTE: using `activate_leaf` instead of this function will result in a /// deadlock, as it calls prospective-teyrchains under the hood. /// /// No-op for known leaves. pub fn activate_leaf_from_prospective_teyrchains( &mut self, leaf: BlockInfoProspectiveTeyrchains, ancestors: &[BlockInfoProspectiveTeyrchains], ) { if self.leaves.contains_key(&leaf.hash) { return; } // Retain at least `MINIMUM_RETAIN_LENGTH` blocks in storage. // This helps to avoid Chain API calls when activating leaves in the // same chain. let retain_minimum = std::cmp::min( ancestors.last().map(|a| a.number).unwrap_or(0), leaf.number.saturating_sub(MINIMUM_RETAIN_LENGTH), ); self.leaves.insert(leaf.hash, ActiveLeafPruningInfo { retain_minimum }); let mut allowed_relay_parents = AllowedRelayParents { allowed_relay_parents_contiguous: Vec::with_capacity(ancestors.len()), // In this case, initialise this to an empty map, as prospective teyrchains already has // this data and it won't query the implicit view for it. minimum_relay_parents: HashMap::new(), }; for ancestor in ancestors { self.block_info_storage.insert( ancestor.hash, BlockInfo { block_number: ancestor.number, maybe_allowed_relay_parents: None, parent_hash: ancestor.parent_hash, }, ); allowed_relay_parents.allowed_relay_parents_contiguous.push(ancestor.hash); } self.block_info_storage.insert( leaf.hash, BlockInfo { block_number: leaf.number, maybe_allowed_relay_parents: Some(allowed_relay_parents), parent_hash: leaf.parent_hash, }, ); } /// Deactivate a leaf in the view. This prunes any outdated implicit ancestors as well. /// /// Returns hashes of blocks pruned from storage. pub fn deactivate_leaf(&mut self, leaf_hash: Hash) -> Vec { let mut removed = Vec::new(); if self.leaves.remove(&leaf_hash).is_none() { return removed; } // Prune everything before the minimum out of all leaves, // pruning absolutely everything if there are no leaves (empty view) // // Pruning by block number does leave behind orphaned forks slightly longer // but the memory overhead is negligible. { let minimum = self.leaves.values().map(|l| l.retain_minimum).min(); self.block_info_storage.retain(|hash, i| { let keep = minimum.map_or(false, |m| i.block_number >= m); if !keep { removed.push(*hash); } keep }); removed } } /// Get an iterator over all allowed relay-parents in the view with no particular order. /// /// **Important**: not all blocks are guaranteed to be allowed for some leaves, it may /// happen that a block info is only kept in the view storage because of a retaining rule. /// /// For getting relay-parents that are valid for teyrchain candidates use /// [`View::known_allowed_relay_parents_under`]. pub fn all_allowed_relay_parents(&self) -> impl Iterator { self.block_info_storage.keys() } /// Get the known, allowed relay-parents that are valid for teyrchain candidates /// which could be backed in a child of a given block for a given para ID. /// /// This is expressed as a contiguous slice of relay-chain block hashes which may /// include the provided block hash itself. /// /// If `para_id` is `None`, this returns all valid relay-parents across all paras /// for the leaf. /// /// `None` indicates that the block hash isn't part of the implicit view or that /// there are no known allowed relay parents. /// /// This always returns `Some` for active leaves or for blocks that previously /// were active leaves. /// /// This can return the empty slice, which indicates that no relay-parents are allowed /// for the para, e.g. if the para is not scheduled at the given block hash. pub fn known_allowed_relay_parents_under( &self, block_hash: &Hash, para_id: Option, ) -> Option<&[Hash]> { let block_info = self.block_info_storage.get(block_hash)?; block_info .maybe_allowed_relay_parents .as_ref() .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } /// Returns all paths from each leaf to the last block in state containing `relay_parent`. If no /// paths exist the function will return an empty `Vec`. pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec> { gum::trace!( target: LOG_TARGET, ?relay_parent, leaves=?self.leaves, block_info_storage=?self.block_info_storage, "Finding paths via relay parent" ); if self.leaves.is_empty() { // No leaves so the view should be empty. Don't return any paths. return vec![]; }; if !self.block_info_storage.contains_key(relay_parent) { // `relay_parent` is not in the view - don't return any paths return vec![]; } // Find all paths from each leaf to `relay_parent`. let mut paths = Vec::new(); for (leaf, _) in &self.leaves { let mut path = Vec::new(); let mut current_leaf = *leaf; let mut visited = HashSet::new(); let mut path_contains_target = false; // Start from the leaf and traverse all known blocks loop { if visited.contains(¤t_leaf) { // There is a cycle - abandon this path break; } current_leaf = match self.block_info_storage.get(¤t_leaf) { Some(info) => { // `current_leaf` is a known block - add it to the path and mark it as // visited path.push(current_leaf); visited.insert(current_leaf); // `current_leaf` is the target `relay_parent`. Mark the path so that it's // included in the result if current_leaf == *relay_parent { path_contains_target = true; } // update `current_leaf` with the parent info.parent_hash }, None => { // path is complete if path_contains_target { // we want the path ordered from oldest to newest so reverse it paths.push(path.into_iter().rev().collect()); } break; }, }; } } paths } async fn fetch_fresh_leaf_and_insert_ancestry( &mut self, leaf_hash: Hash, sender: &mut Sender, ) -> Result where Sender: SubsystemSender + SubsystemSender + SubsystemSender, { let leaf_header = { let (tx, rx) = oneshot::channel(); sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; match rx.await { Ok(Ok(Some(header))) => header, Ok(Ok(None)) => { return Err(FetchError::BlockHeaderUnavailable( leaf_hash, BlockHeaderUnavailableReason::Unknown, )) }, Ok(Err(e)) => { return Err(FetchError::BlockHeaderUnavailable( leaf_hash, BlockHeaderUnavailableReason::Internal(e), )) }, Err(_) => { return Err(FetchError::BlockHeaderUnavailable( leaf_hash, BlockHeaderUnavailableReason::SubsystemUnavailable, )) }, } }; // If the node is a collator, bypass prospective-teyrchains. We're only interested in the // one paraid and the subsystem is not present. let min_relay_parents = if let Some(para_id) = self.collating_for { fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) .await? .map(|x| vec![(para_id, x)]) .unwrap_or_default() } else { fetch_min_relay_parents_from_prospective_teyrchains(leaf_hash, sender).await? }; let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; let ancestry = if leaf_header.number > 0 { let mut next_ancestor_number = leaf_header.number - 1; let mut next_ancestor_hash = leaf_header.parent_hash; let mut ancestry = Vec::with_capacity(expected_ancestry_len); ancestry.push(leaf_hash); // Ensure all ancestors up to and including `min_min` are in the // block storage. When views advance incrementally, everything // should already be present. while next_ancestor_number >= min_min { let parent_hash = if let Some(info) = self.block_info_storage.get(&next_ancestor_hash) { info.parent_hash } else { // load the header and insert into block storage. let (tx, rx) = oneshot::channel(); sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; let header = match rx.await { Ok(Ok(Some(header))) => header, Ok(Ok(None)) => { return Err(FetchError::BlockHeaderUnavailable( next_ancestor_hash, BlockHeaderUnavailableReason::Unknown, )) }, Ok(Err(e)) => { return Err(FetchError::BlockHeaderUnavailable( next_ancestor_hash, BlockHeaderUnavailableReason::Internal(e), )) }, Err(_) => { return Err(FetchError::BlockHeaderUnavailable( next_ancestor_hash, BlockHeaderUnavailableReason::SubsystemUnavailable, )) }, }; self.block_info_storage.insert( next_ancestor_hash, BlockInfo { block_number: next_ancestor_number, parent_hash: header.parent_hash, maybe_allowed_relay_parents: None, }, ); header.parent_hash }; ancestry.push(next_ancestor_hash); if next_ancestor_number == 0 { break; } next_ancestor_number -= 1; next_ancestor_hash = parent_hash; } ancestry } else { vec![leaf_hash] }; let fetched_ancestry = FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; let allowed_relay_parents = AllowedRelayParents { minimum_relay_parents: min_relay_parents.into_iter().collect(), allowed_relay_parents_contiguous: ancestry, }; let leaf_block_info = BlockInfo { parent_hash: leaf_header.parent_hash, block_number: leaf_header.number, maybe_allowed_relay_parents: Some(allowed_relay_parents), }; self.block_info_storage.insert(leaf_hash, leaf_block_info); Ok(fetched_ancestry) } } /// Errors when fetching a leaf and associated ancestry. #[fatality::fatality] pub enum FetchError { /// Activated leaf is already present in view. #[error("Leaf was already known")] AlreadyKnown, /// Request to the prospective teyrchains subsystem failed. #[error("The prospective teyrchains subsystem was unavailable")] ProspectiveTeyrchainsUnavailable, /// Failed to fetch the block header. #[error("A block header was unavailable")] BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason), /// A block header was unavailable due to a chain API error. #[error("A block header was unavailable due to a chain API error")] ChainApiError(Hash, ChainApiError), /// Request to the Chain API subsystem failed. #[error("The chain API subsystem was unavailable")] ChainApiUnavailable, /// Request to the runtime API failed. #[error("Runtime API error: {0}")] RuntimeApi(#[from] runtime::Error), } /// Reasons a block header might have been unavailable. #[derive(Debug)] pub enum BlockHeaderUnavailableReason { /// Block header simply unknown. Unknown, /// Internal Chain API error. Internal(ChainApiError), /// The subsystem was unavailable. SubsystemUnavailable, } struct FetchSummary { minimum_ancestor_number: BlockNumber, leaf_number: BlockNumber, } // Request the min relay parents from prospective-teyrchains. async fn fetch_min_relay_parents_from_prospective_teyrchains< Sender: SubsystemSender, >( leaf_hash: Hash, sender: &mut Sender, ) -> Result, FetchError> { let (tx, rx) = oneshot::channel(); sender .send_message(ProspectiveTeyrchainsMessage::GetMinimumRelayParents(leaf_hash, tx)) .await; rx.await.map_err(|_| FetchError::ProspectiveTeyrchainsUnavailable) } // Request the min relay parent for the purposes of a collator, directly using ChainApi (where // prospective-teyrchains is not available). async fn fetch_min_relay_parents_for_collator( leaf_hash: Hash, leaf_number: BlockNumber, sender: &mut Sender, ) -> Result, FetchError> where Sender: SubsystemSender + SubsystemSender + SubsystemSender, { // Fetch the session of the leaf. We must make sure that we stop at the ancestor which has a // different session index. let required_session = recv_runtime(request_session_index_for_child(leaf_hash, sender).await).await?; let scheduling_lookahead = fetch_scheduling_lookahead(leaf_hash, required_session, sender).await?; let mut min = leaf_number; // Fetch the ancestors, up to (scheduling_lookahead - 1). let (tx, rx) = oneshot::channel(); sender .send_message(ChainApiMessage::Ancestors { hash: leaf_hash, k: scheduling_lookahead.saturating_sub(1) as usize, response_channel: tx, }) .await; let hashes = rx .await .map_err(|_| FetchError::ChainApiUnavailable)? .map_err(|err| FetchError::ChainApiError(leaf_hash, err))?; for hash in hashes { // The relay chain cannot accept blocks backed from previous sessions, with // potentially previous validators. This is a technical limitation we need to // respect here. let session = recv_runtime(request_session_index_for_child(hash, sender).await).await?; if session == required_session { // We should never underflow here, the ChainAPI stops at genesis block. min = min.saturating_sub(1); } else { break; } } Ok(Some(min)) } #[cfg(test)] mod tests { use super::*; use crate::TimeoutExt; use assert_matches::assert_matches; use futures::future::{join, FutureExt}; use pezkuwi_node_subsystem::{messages::RuntimeApiRequest, AllMessages}; use pezkuwi_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle}; use pezkuwi_overseer::SubsystemContext; use pezkuwi_primitives::Header; use pezsp_core::testing::TaskExecutor; use std::time::Duration; const PARA_A: ParaId = ParaId::new(0); const PARA_B: ParaId = ParaId::new(1); const PARA_C: ParaId = ParaId::new(2); const GENESIS_HASH: Hash = Hash::repeat_byte(0xFF); const GENESIS_NUMBER: BlockNumber = 0; // Chains A and B are forks of genesis. const CHAIN_A: &[Hash] = &[Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)]; const CHAIN_B: &[Hash] = &[ Hash::repeat_byte(0x04), Hash::repeat_byte(0x05), Hash::repeat_byte(0x06), Hash::repeat_byte(0x07), Hash::repeat_byte(0x08), Hash::repeat_byte(0x09), ]; type VirtualOverseer = TestSubsystemContextHandle; const TIMEOUT: Duration = Duration::from_secs(2); async fn overseer_recv(virtual_overseer: &mut VirtualOverseer) -> AllMessages { virtual_overseer .recv() .timeout(TIMEOUT) .await .expect("overseer `recv` timed out") } fn default_header() -> Header { Header { parent_hash: Hash::zero(), number: 0, state_root: Hash::zero(), extrinsics_root: Hash::zero(), digest: Default::default(), } } fn get_block_header(chain: &[Hash], hash: &Hash) -> Option
{ let idx = chain.iter().position(|h| h == hash)?; let parent_hash = idx.checked_sub(1).map(|i| chain[i]).unwrap_or(GENESIS_HASH); let number = if *hash == GENESIS_HASH { GENESIS_NUMBER } else { GENESIS_NUMBER + idx as u32 + 1 }; Some(Header { parent_hash, number, ..default_header() }) } async fn assert_block_header_requests( virtual_overseer: &mut VirtualOverseer, chain: &[Hash], blocks: &[Hash], ) { for block in blocks.iter().rev() { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ChainApi( ChainApiMessage::BlockHeader(hash, tx) ) => { assert_eq!(*block, hash, "unexpected block header request"); let header = if block == &GENESIS_HASH { Header { number: GENESIS_NUMBER, ..default_header() } } else { get_block_header(chain, block).expect("unknown block") }; tx.send(Ok(Some(header))).unwrap(); } ); } } async fn assert_min_relay_parents_request( virtual_overseer: &mut VirtualOverseer, leaf: &Hash, response: Vec<(ParaId, u32)>, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ProspectiveTeyrchains( ProspectiveTeyrchainsMessage::GetMinimumRelayParents( leaf_hash, tx ) ) => { assert_eq!(*leaf, leaf_hash, "received unexpected leaf hash"); tx.send(response).unwrap(); } ); } async fn assert_scheduling_lookahead_request( virtual_overseer: &mut VirtualOverseer, leaf: Hash, lookahead: u32, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi( RuntimeApiMessage::Request( leaf_hash, RuntimeApiRequest::SchedulingLookahead( _, tx ) ) ) => { assert_eq!(leaf, leaf_hash, "received unexpected leaf hash"); tx.send(Ok(lookahead)).unwrap(); } ); } async fn assert_session_index_request( virtual_overseer: &mut VirtualOverseer, leaf: Hash, session: u32, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi( RuntimeApiMessage::Request( leaf_hash, RuntimeApiRequest::SessionIndexForChild( tx ) ) ) => { assert_eq!(leaf, leaf_hash, "received unexpected leaf hash"); tx.send(Ok(session)).unwrap(); } ); } async fn assert_ancestors_request( virtual_overseer: &mut VirtualOverseer, leaf: Hash, expected_ancestor_len: u32, response: Vec, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ChainApi( ChainApiMessage::Ancestors { hash: leaf_hash, k, response_channel: tx } ) => { assert_eq!(leaf, leaf_hash, "received unexpected leaf hash"); assert_eq!(k, expected_ancestor_len as usize); tx.send(Ok(response)).unwrap(); } ); } #[test] fn construct_fresh_view() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::default(); assert_eq!(view.collating_for, None); // Chain B. const PARA_A_MIN_PARENT: u32 = 4; const PARA_B_MIN_PARENT: u32 = 3; let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT), (PARA_B, PARA_B_MIN_PARENT)]; let leaf = CHAIN_B.last().unwrap(); let leaf_idx = CHAIN_B.len() - 1; let min_min_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[min_min_idx..leaf_idx]) .await; }; futures::executor::block_on(join(fut, overseer_fut)); for i in min_min_idx..(CHAIN_B.len() - 1) { // No allowed relay parents constructed for ancestry. assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i], None).is_none()); } let leaf_info = view.block_info_storage.get(leaf).expect("block must be present in storage"); assert_matches!( leaf_info.maybe_allowed_relay_parents, Some(ref allowed_relay_parents) => { assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT); assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_B], PARA_B_MIN_PARENT); let expected_ancestry: Vec = CHAIN_B[min_min_idx..].iter().rev().copied().collect(); assert_eq!( allowed_relay_parents.allowed_relay_parents_contiguous, expected_ancestry ); assert_eq!(view.known_allowed_relay_parents_under(&leaf, None), Some(&expected_ancestry[..])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..(PARA_A_MIN_PARENT - 1) as usize])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); assert_eq!(view.leaves.len(), 1); assert!(view.leaves.contains_key(leaf)); assert!(view.paths_via_relay_parent(&CHAIN_B[0]).is_empty()); assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); assert_eq!( view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), vec![CHAIN_B[min_min_idx..].to_vec()] ); assert_eq!( view.paths_via_relay_parent(&CHAIN_B[min_min_idx + 1]), vec![CHAIN_B[min_min_idx..].to_vec()] ); assert_eq!( view.paths_via_relay_parent(&leaf), vec![CHAIN_B[min_min_idx..].to_vec()] ); } ); // Suppose the whole test chain A is allowed up to genesis for para C. const PARA_C_MIN_PARENT: u32 = 0; let prospective_response = vec![(PARA_C, PARA_C_MIN_PARENT)]; let leaf = CHAIN_A.last().unwrap(); let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); let leaf_idx = blocks.len() - 1; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[..leaf_idx]).await; }; futures::executor::block_on(join(fut, overseer_fut)); assert_eq!(view.leaves.len(), 2); let leaf_info = view.block_info_storage.get(leaf).expect("block must be present in storage"); assert_matches!( leaf_info.maybe_allowed_relay_parents, Some(ref allowed_relay_parents) => { assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_C], GENESIS_NUMBER); let expected_ancestry: Vec = blocks[..].iter().rev().copied().collect(); assert_eq!( allowed_relay_parents.allowed_relay_parents_contiguous, expected_ancestry ); assert_eq!(view.known_allowed_relay_parents_under(&leaf, None), Some(&expected_ancestry[..])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); } ); } #[test] fn construct_fresh_view_single_para() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::new(Some(PARA_A)); assert_eq!(view.collating_for, Some(PARA_A)); // Chain B. const PARA_A_MIN_PARENT: u32 = 4; let current_session = 2; let leaf = CHAIN_B.last().unwrap(); let leaf_idx = CHAIN_B.len() - 1; let min_min_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; assert_session_index_request(&mut ctx_handle, *leaf, current_session).await; assert_scheduling_lookahead_request(&mut ctx_handle, *leaf, PARA_A_MIN_PARENT + 1) .await; assert_ancestors_request( &mut ctx_handle, *leaf, PARA_A_MIN_PARENT, CHAIN_B[min_min_idx..leaf_idx].iter().copied().rev().collect(), ) .await; for hash in CHAIN_B[min_min_idx..leaf_idx].into_iter().rev() { assert_session_index_request(&mut ctx_handle, *hash, current_session).await; } assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[min_min_idx..leaf_idx]) .await; }; futures::executor::block_on(join(fut, overseer_fut)); for i in min_min_idx..(CHAIN_B.len() - 1) { // No allowed relay parents constructed for ancestry. assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i], None).is_none()); } let leaf_info = view.block_info_storage.get(leaf).expect("block must be present in storage"); assert_matches!( leaf_info.maybe_allowed_relay_parents, Some(ref allowed_relay_parents) => { assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT); let expected_ancestry: Vec = CHAIN_B[min_min_idx..].iter().rev().copied().collect(); assert_eq!( allowed_relay_parents.allowed_relay_parents_contiguous, expected_ancestry ); assert_eq!(view.known_allowed_relay_parents_under(&leaf, None), Some(&expected_ancestry[..])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); assert_eq!( view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), vec![CHAIN_B[min_min_idx..].to_vec()] ); } ); // Suppose the whole test chain A is allowed up to genesis for para A, but the genesis block // is in a different session. let leaf = CHAIN_A.last().unwrap(); let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); let leaf_idx = blocks.len() - 1; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; assert_session_index_request(&mut ctx_handle, *leaf, current_session).await; assert_scheduling_lookahead_request(&mut ctx_handle, *leaf, blocks.len() as u32 + 1) .await; assert_ancestors_request( &mut ctx_handle, *leaf, blocks.len() as u32, blocks[..leaf_idx].iter().rev().copied().collect(), ) .await; for hash in blocks[1..leaf_idx].into_iter().rev() { assert_session_index_request(&mut ctx_handle, *hash, current_session).await; } assert_session_index_request(&mut ctx_handle, GENESIS_HASH, 0).await; // We won't request for the genesis block assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[1..leaf_idx]).await; }; futures::executor::block_on(join(fut, overseer_fut)); assert_eq!(view.leaves.len(), 2); let leaf_info = view.block_info_storage.get(leaf).expect("block must be present in storage"); assert_matches!( leaf_info.maybe_allowed_relay_parents, Some(ref allowed_relay_parents) => { assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], 1); let expected_ancestry: Vec = CHAIN_A[..].iter().rev().copied().collect(); assert_eq!( allowed_relay_parents.allowed_relay_parents_contiguous, expected_ancestry ); assert_eq!(view.known_allowed_relay_parents_under(&leaf, None), Some(&expected_ancestry[..])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty()); assert_eq!( view.paths_via_relay_parent(&CHAIN_A[0]), vec![CHAIN_A.to_vec()] ); } ); } #[test] fn reuse_block_info_storage() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::default(); const PARA_A_MIN_PARENT: u32 = 1; let leaf_a_number = 3; let leaf_a = CHAIN_B[leaf_a_number - 1]; let min_min_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), leaf_a).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests( &mut ctx_handle, CHAIN_B, &CHAIN_B[(leaf_a_number - 1)..leaf_a_number], ) .await; assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await; assert_block_header_requests( &mut ctx_handle, CHAIN_B, &CHAIN_B[min_min_idx..(leaf_a_number - 1)], ) .await; }; futures::executor::block_on(join(fut, overseer_fut)); // Blocks up to the 3rd are present in storage. const PARA_B_MIN_PARENT: u32 = 2; let leaf_b_number = 5; let leaf_b = CHAIN_B[leaf_b_number - 1]; let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), leaf_b).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests( &mut ctx_handle, CHAIN_B, &CHAIN_B[(leaf_b_number - 1)..leaf_b_number], ) .await; assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await; assert_block_header_requests( &mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_a_number..(leaf_b_number - 1)], // Note the expected range. ) .await; }; futures::executor::block_on(join(fut, overseer_fut)); // Allowed relay parents for leaf A are preserved. let leaf_a_info = view.block_info_storage.get(&leaf_a).expect("block must be present in storage"); assert_matches!( leaf_a_info.maybe_allowed_relay_parents, Some(ref allowed_relay_parents) => { assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT); let expected_ancestry: Vec = CHAIN_B[min_min_idx..leaf_a_number].iter().rev().copied().collect(); let ancestry = view.known_allowed_relay_parents_under(&leaf_a, Some(PARA_A)).unwrap().to_vec(); assert_eq!(ancestry, expected_ancestry); } ); } #[test] fn pruning() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::default(); const PARA_A_MIN_PARENT: u32 = 3; let leaf_a = CHAIN_B.iter().rev().nth(1).unwrap(); let leaf_a_idx = CHAIN_B.len() - 2; let min_a_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)]; let fut = view .activate_leaf(ctx.sender(), *leaf_a) .timeout(TIMEOUT) .map(|res| res.unwrap().unwrap()); let overseer_fut = async { assert_block_header_requests( &mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_a_idx..(leaf_a_idx + 1)], ) .await; assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[min_a_idx..leaf_a_idx]) .await; }; futures::executor::block_on(join(fut, overseer_fut)); // Also activate a leaf with a lesser minimum relay parent. const PARA_B_MIN_PARENT: u32 = 2; let leaf_b = CHAIN_B.last().unwrap(); let min_b_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)]; // Headers will be requested for the minimum block and the leaf. let blocks = &[CHAIN_B[min_b_idx], *leaf_b]; let fut = view .activate_leaf(ctx.sender(), *leaf_b) .timeout(TIMEOUT) .map(|res| res.expect("`activate_leaf` timed out").unwrap()); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_B, &blocks[(blocks.len() - 1)..]) .await; assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_B, &blocks[..(blocks.len() - 1)]) .await; }; futures::executor::block_on(join(fut, overseer_fut)); // Prune implicit ancestor (no-op). let block_info_len = view.block_info_storage.len(); view.deactivate_leaf(CHAIN_B[leaf_a_idx - 1]); assert_eq!(block_info_len, view.block_info_storage.len()); // Prune a leaf with a greater minimum relay parent. view.deactivate_leaf(*leaf_b); for hash in CHAIN_B.iter().take(PARA_B_MIN_PARENT as usize) { assert!(!view.block_info_storage.contains_key(hash)); } // Prune the last leaf. view.deactivate_leaf(*leaf_a); assert!(view.block_info_storage.is_empty()); } #[test] fn genesis_ancestry() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::default(); const PARA_A_MIN_PARENT: u32 = 0; let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), GENESIS_HASH).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, &[GENESIS_HASH], &[GENESIS_HASH]).await; assert_min_relay_parents_request(&mut ctx_handle, &GENESIS_HASH, prospective_response) .await; }; futures::executor::block_on(join(fut, overseer_fut)); assert_matches!( view.known_allowed_relay_parents_under(&GENESIS_HASH, None), Some(hashes) if hashes == &[GENESIS_HASH] ); } #[test] fn path_with_fork() { let pool = TaskExecutor::new(); let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); let mut view = View::default(); assert_eq!(view.collating_for, None); // Chain A let prospective_response = vec![(PARA_A, 0)]; // was PARA_A_MIN_PARENT let leaf = CHAIN_A.last().unwrap(); let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); let leaf_idx = blocks.len() - 1; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[..leaf_idx]).await; }; futures::executor::block_on(join(fut, overseer_fut)); // Chain B let prospective_response = vec![(PARA_A, 1)]; let leaf = CHAIN_B.last().unwrap(); let leaf_idx = CHAIN_B.len() - 1; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[0..leaf_idx]).await; }; futures::executor::block_on(join(fut, overseer_fut)); assert_eq!(view.leaves.len(), 2); let mut paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH); paths_to_genesis.sort(); let mut expected_paths_to_genesis = vec![ [GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>(), [GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>(), ]; expected_paths_to_genesis.sort(); assert_eq!(paths_to_genesis, expected_paths_to_genesis); let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]); let expected_path_to_leaf_in_a = vec![[GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>()]; assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a); let path_to_leaf_in_b = view.paths_via_relay_parent(&CHAIN_B[4]); let expected_path_to_leaf_in_b = vec![[GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>()]; assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b); assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::>::new()); } }