use futures::channel::oneshot;
use polkadot_node_subsystem::{
errors::ChainApiError,
messages::{ChainApiMessage, ProspectiveParachainsMessage, RuntimeApiMessage},
SubsystemSender,
};
use polkadot_primitives::{BlockNumber, Hash, Id as ParaId};
use std::collections::HashMap;
use crate::{
inclusion_emulator::RelayChainBlockInfo,
request_session_index_for_child,
runtime::{self, prospective_parachains_mode, recv_runtime, ProspectiveParachainsMode},
};
const MINIMUM_RETAIN_LENGTH: BlockNumber = 2;
#[derive(Clone)]
pub struct View {
leaves: HashMap<Hash, ActiveLeafPruningInfo>,
block_info_storage: HashMap<Hash, BlockInfo>,
collating_for: Option<ParaId>,
}
impl View {
pub fn new(collating_for: Option<ParaId>) -> Self {
Self { leaves: Default::default(), block_info_storage: Default::default(), collating_for }
}
}
impl Default for View {
fn default() -> Self {
Self::new(None)
}
}
#[derive(Debug, Clone)]
struct AllowedRelayParents {
minimum_relay_parents: HashMap<ParaId, BlockNumber>,
allowed_relay_parents_contiguous: Vec<Hash>,
}
impl AllowedRelayParents {
fn allowed_relay_parents_for(
&self,
para_id: Option<ParaId>,
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;
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 {
retain_minimum: BlockNumber,
}
#[derive(Debug, Clone)]
struct BlockInfo {
block_number: BlockNumber,
maybe_allowed_relay_parents: Option<AllowedRelayParents>,
parent_hash: Hash,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlockInfoProspectiveParachains {
pub hash: Hash,
pub parent_hash: Hash,
pub number: BlockNumber,
pub storage_root: Hash,
}
impl From<BlockInfoProspectiveParachains> for RelayChainBlockInfo {
fn from(value: BlockInfoProspectiveParachains) -> Self {
Self { hash: value.hash, number: value.number, storage_root: value.storage_root }
}
}
impl View {
pub fn leaves(&self) -> impl Iterator<Item = &Hash> {
self.leaves.keys()
}
pub async fn activate_leaf<Sender>(
&mut self,
sender: &mut Sender,
leaf_hash: Hash,
) -> Result<(), FetchError>
where
Sender: SubsystemSender<ChainApiMessage>
+ SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>,
{
if self.leaves.contains_key(&leaf_hash) {
return Err(FetchError::AlreadyKnown)
}
let res = fetch_fresh_leaf_and_insert_ancestry(
leaf_hash,
&mut self.block_info_storage,
&mut *sender,
self.collating_for,
)
.await;
match res {
Ok(fetched) => {
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),
}
}
pub fn activate_leaf_from_prospective_parachains(
&mut self,
leaf: BlockInfoProspectiveParachains,
ancestors: &[BlockInfoProspectiveParachains],
) {
if self.leaves.contains_key(&leaf.hash) {
return
}
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()),
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,
},
);
}
pub fn deactivate_leaf(&mut self, leaf_hash: Hash) -> Vec<Hash> {
let mut removed = Vec::new();
if self.leaves.remove(&leaf_hash).is_none() {
return removed
}
{
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
}
}
pub fn all_allowed_relay_parents(&self) -> impl Iterator<Item = &Hash> {
self.block_info_storage.keys()
}
pub fn known_allowed_relay_parents_under(
&self,
block_hash: &Hash,
para_id: Option<ParaId>,
) -> 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))
}
}
#[fatality::fatality]
pub enum FetchError {
#[error("Leaf was already known")]
AlreadyKnown,
#[error("The prospective parachains subsystem was unavailable")]
ProspectiveParachainsUnavailable,
#[error("A block header was unavailable")]
BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason),
#[error("A block header was unavailable due to a chain API error")]
ChainApiError(Hash, ChainApiError),
#[error("The chain API subsystem was unavailable")]
ChainApiUnavailable,
#[error("Runtime API error: {0}")]
RuntimeApi(#[from] runtime::Error),
}
#[derive(Debug)]
pub enum BlockHeaderUnavailableReason {
Unknown,
Internal(ChainApiError),
SubsystemUnavailable,
}
struct FetchSummary {
minimum_ancestor_number: BlockNumber,
leaf_number: BlockNumber,
}
async fn fetch_min_relay_parents_from_prospective_parachains<
Sender: SubsystemSender<ProspectiveParachainsMessage>,
>(
leaf_hash: Hash,
sender: &mut Sender,
) -> Result<Vec<(ParaId, BlockNumber)>, FetchError> {
let (tx, rx) = oneshot::channel();
sender
.send_message(ProspectiveParachainsMessage::GetMinimumRelayParents(leaf_hash, tx))
.await;
rx.await.map_err(|_| FetchError::ProspectiveParachainsUnavailable)
}
async fn fetch_min_relay_parents_for_collator<Sender>(
leaf_hash: Hash,
leaf_number: BlockNumber,
sender: &mut Sender,
) -> Result<Option<BlockNumber>, FetchError>
where
Sender: SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>
+ SubsystemSender<ChainApiMessage>,
{
let Ok(ProspectiveParachainsMode::Enabled { allowed_ancestry_len, .. }) =
prospective_parachains_mode(sender, leaf_hash).await
else {
return Ok(None)
};
let required_session =
recv_runtime(request_session_index_for_child(leaf_hash, sender).await).await?;
let mut min = leaf_number;
let (tx, rx) = oneshot::channel();
sender
.send_message(ChainApiMessage::Ancestors {
hash: leaf_hash,
k: allowed_ancestry_len,
response_channel: tx,
})
.await;
let hashes = rx
.await
.map_err(|_| FetchError::ChainApiUnavailable)?
.map_err(|err| FetchError::ChainApiError(leaf_hash, err))?;
for hash in hashes {
let session = recv_runtime(request_session_index_for_child(hash, sender).await).await?;
if session == required_session {
min = min.saturating_sub(1);
} else {
break
}
}
Ok(Some(min))
}
async fn fetch_fresh_leaf_and_insert_ancestry<Sender>(
leaf_hash: Hash,
block_info_storage: &mut HashMap<Hash, BlockInfo>,
sender: &mut Sender,
collating_for: Option<ParaId>,
) -> Result<FetchSummary, FetchError>
where
Sender: SubsystemSender<ChainApiMessage>
+ SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>,
{
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,
)),
}
};
let min_relay_parents = if let Some(para_id) = 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_parachains(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);
while next_ancestor_number >= min_min {
let parent_hash = if let Some(info) = block_info_storage.get(&next_ancestor_hash) {
info.parent_hash
} else {
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,
)),
};
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),
};
block_info_storage.insert(leaf_hash, leaf_block_info);
Ok(fetched_ancestry)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TimeoutExt;
use assert_matches::assert_matches;
use futures::future::{join, FutureExt};
use polkadot_node_subsystem::{messages::RuntimeApiRequest, AllMessages};
use polkadot_node_subsystem_test_helpers::{
make_subsystem_context, TestSubsystemContextHandle,
};
use polkadot_overseer::SubsystemContext;
use polkadot_primitives::{AsyncBackingParams, Header};
use sp_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;
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<AllMessages>;
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<Header> {
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::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(
leaf_hash,
tx
)
) => {
assert_eq!(*leaf, leaf_hash, "received unexpected leaf hash");
tx.send(response).unwrap();
}
);
}
async fn assert_async_backing_params_request(
virtual_overseer: &mut VirtualOverseer,
leaf: Hash,
params: AsyncBackingParams,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
leaf_hash,
RuntimeApiRequest::AsyncBackingParams(
tx
)
)
) => {
assert_eq!(leaf, leaf_hash, "received unexpected leaf hash");
tx.send(Ok(params)).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<Hash>,
) {
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::<AllMessages, _>(pool);
let mut view = View::default();
assert_eq!(view.collating_for, None);
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) {
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<Hash> =
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());
}
);
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<Hash> =
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::<AllMessages, _>(pool);
let mut view = View::new(Some(PARA_A));
assert_eq!(view.collating_for, Some(PARA_A));
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_async_backing_params_request(
&mut ctx_handle,
*leaf,
AsyncBackingParams {
max_candidate_depth: 0,
allowed_ancestry_len: PARA_A_MIN_PARENT,
},
)
.await;
assert_session_index_request(&mut ctx_handle, *leaf, current_session).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) {
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<Hash> =
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());
}
);
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_async_backing_params_request(
&mut ctx_handle,
*leaf,
AsyncBackingParams {
max_candidate_depth: 0,
allowed_ancestry_len: blocks.len() as u32,
},
)
.await;
assert_session_index_request(&mut ctx_handle, *leaf, current_session).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;
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<Hash> =
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());
}
);
}
#[test]
fn reuse_block_info_storage() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(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));
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)], )
.await;
};
futures::executor::block_on(join(fut, overseer_fut));
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<Hash> =
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::<AllMessages, _>(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));
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)];
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));
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());
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));
}
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::<AllMessages, _>(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]
);
}
}