use futures::channel::oneshot;
use polkadot_node_subsystem::{
errors::ChainApiError,
messages::{ChainApiMessage, ProspectiveParachainsMessage, RuntimeApiMessage},
SubsystemSender,
};
use polkadot_primitives::{BlockNumber, Hash};
use std::{
collections::{hash_map::Entry, HashMap},
iter,
};
use crate::{
request_session_index_for_child,
runtime::{self, fetch_scheduling_lookahead, recv_runtime},
LOG_TARGET,
};
const MINIMUM_RETAIN_LENGTH: BlockNumber = 2;
#[derive(Clone)]
pub struct View {
leaves: HashMap<Hash, ActiveLeafPruningInfo>,
block_info_storage: HashMap<Hash, BlockInfo>,
}
impl View {
pub fn new() -> Self {
Self { leaves: Default::default(), block_info_storage: Default::default() }
}
}
impl Default for View {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
struct AllowedRelayParents {
allowed_relay_parents_contiguous: Vec<Hash>,
}
impl AllowedRelayParents {
fn allowed_relay_parents(&self) -> &[Hash] {
&self.allowed_relay_parents_contiguous
}
}
#[derive(Debug, Clone)]
struct ActiveLeafPruningInfo {
retain_minimum: BlockNumber,
}
#[derive(Debug, Clone)]
struct BlockInfo {
block_number: BlockNumber,
maybe_allowed_relay_parents: Option<AllowedRelayParents>,
}
impl View {
pub fn leaves(&self) -> impl Iterator<Item = &Hash> {
self.leaves.keys()
}
pub fn contains_leaf(&self, leaf_hash: &Hash) -> bool {
self.leaves.contains_key(leaf_hash)
}
pub fn block_number(&self, leaf_hash: &Hash) -> Option<BlockNumber> {
self.block_info_storage.get(leaf_hash).map(|block_info| block_info.block_number)
}
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 = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).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 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) -> 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())
}
pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec<Vec<Hash>> {
gum::trace!(
target: LOG_TARGET,
?relay_parent,
leaves=?self.leaves,
block_info_storage=?self.block_info_storage,
"Finding paths via relay parent"
);
if !self.block_info_storage.contains_key(relay_parent) {
return vec![];
}
let mut paths = Vec::new();
for (leaf, _) in &self.leaves {
let Some(allowed_rps) = self
.block_info_storage
.get(leaf)
.and_then(|info| info.maybe_allowed_relay_parents.as_ref())
else {
gum::warn!(
target: LOG_TARGET,
?leaf,
"Active leaf missing allowed relay parents",
);
continue;
};
let contiguous = &allowed_rps.allowed_relay_parents_contiguous;
if contiguous.iter().any(|h| h == relay_parent) {
let path: Vec<Hash> = contiguous.iter().rev().copied().collect();
paths.push(path);
}
}
paths
}
async fn fetch_fresh_leaf_and_insert_ancestry<Sender>(
&mut self,
leaf_hash: Hash,
sender: &mut Sender,
) -> Result<FetchSummary, FetchError>
where
Sender: SubsystemSender<ChainApiMessage>
+ SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>,
{
let ancestors = fetch_ancestors(leaf_hash, sender).await?;
let ancestor_len = ancestors.len();
let ancestry: Vec<Hash> = iter::once(leaf_hash).chain(ancestors).collect();
let mut allowed_relay_parents =
Some(AllowedRelayParents { allowed_relay_parents_contiguous: ancestry.clone() });
for block_hash in ancestry {
let block_info_entry = match self.block_info_storage.entry(block_hash) {
Entry::Occupied(_) => continue,
Entry::Vacant(e) => e,
};
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockHeader(block_hash, tx)).await;
let header = match rx.await {
Ok(Ok(Some(header))) => header,
Ok(Ok(None)) => {
return Err(FetchError::BlockHeaderUnavailable(
block_hash,
BlockHeaderUnavailableReason::Unknown,
));
},
Ok(Err(e)) => {
return Err(FetchError::BlockHeaderUnavailable(
block_hash,
BlockHeaderUnavailableReason::Internal(e),
));
},
Err(_) => {
return Err(FetchError::BlockHeaderUnavailable(
block_hash,
BlockHeaderUnavailableReason::SubsystemUnavailable,
));
},
};
block_info_entry.insert(BlockInfo {
block_number: header.number,
maybe_allowed_relay_parents: allowed_relay_parents.take(),
});
}
let leaf_entry = self
.block_info_storage
.get(&leaf_hash)
.expect("We just inserted this entry. qed.");
Ok(FetchSummary {
minimum_ancestor_number: leaf_entry.block_number.saturating_sub(ancestor_len as u32),
leaf_number: leaf_entry.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_ancestors<Sender>(
leaf_hash: Hash,
sender: &mut Sender,
) -> Result<Vec<Hash>, FetchError>
where
Sender: SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>
+ SubsystemSender<ChainApiMessage>,
{
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 (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 mut hashes = rx
.await
.map_err(|_| FetchError::ChainApiUnavailable)?
.map_err(|err| FetchError::ChainApiError(leaf_hash, err))?;
let mut session_change_at = None;
for (i, hash) in hashes.iter().enumerate() {
let session = recv_runtime(request_session_index_for_child(*hash, sender).await).await?;
if session != required_session {
session_change_at = Some(i);
break;
}
}
if let Some(session_change_at) = session_change_at {
hashes.truncate(session_change_at);
}
Ok(hashes)
}
#[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::Header;
use sp_core::testing::TaskExecutor;
use std::time::Duration;
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_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<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();
}
);
}
async fn activate_leaf_with_overseer_requests<Ctx>(
view: &mut View,
ctx: &mut Ctx,
ctx_handle: &mut VirtualOverseer,
leaf: Hash,
session: u32,
scheduling_lookahead: u32,
ancestors: Vec<Hash>,
ancestor_sessions: Vec<u32>,
chain: &[Hash],
blocks_for_headers: &[Hash],
) where
Ctx: SubsystemContext<Message = AllMessages>,
Ctx::Sender: SubsystemSender<ChainApiMessage>
+ SubsystemSender<ProspectiveParachainsMessage>
+ SubsystemSender<RuntimeApiMessage>,
{
let fut = view.activate_leaf(ctx.sender(), leaf).timeout(TIMEOUT).map(|res| {
res.expect("`activate_leaf` timed out").unwrap();
});
let overseer_fut = async {
assert_session_index_request(ctx_handle, leaf, session).await;
assert_scheduling_lookahead_request(ctx_handle, leaf, scheduling_lookahead).await;
assert_ancestors_request(ctx_handle, leaf, scheduling_lookahead - 1, ancestors.clone())
.await;
for (ancestor, ancestor_session) in ancestors.iter().zip(ancestor_sessions.iter()) {
assert_session_index_request(ctx_handle, *ancestor, *ancestor_session).await;
}
assert_block_header_requests(ctx_handle, chain, blocks_for_headers).await;
};
join(fut, overseer_fut).await;
}
fn assert_expected_allowed_relay_parents(view: &View, leaf: &Hash, expected_ancestry: &[Hash]) {
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.allowed_relay_parents_contiguous,
expected_ancestry
);
assert_eq!(view.known_allowed_relay_parents_under(leaf), Some(expected_ancestry));
}
);
}
#[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();
const SESSION: u32 = 2;
const SCHEDULING_LOOKAHEAD: u32 = 3;
let leaf = CHAIN_B.last().unwrap();
let leaf_idx = CHAIN_B.len() - 1;
let min_idx = leaf_idx - (SCHEDULING_LOOKAHEAD as usize - 1);
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
SESSION,
SCHEDULING_LOOKAHEAD,
CHAIN_B[min_idx..leaf_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_idx - min_idx],
CHAIN_B,
&CHAIN_B[min_idx..=leaf_idx],
));
for i in min_idx..(CHAIN_B.len() - 1) {
assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i]).is_none());
}
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_idx..=leaf_idx].iter().rev().copied().collect();
assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
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_idx]),
vec![CHAIN_B[min_idx..].to_vec()]
);
assert_eq!(
view.paths_via_relay_parent(&CHAIN_B[min_idx + 1]),
vec![CHAIN_B[min_idx..].to_vec()]
);
assert_eq!(view.paths_via_relay_parent(&leaf), vec![CHAIN_B[min_idx..].to_vec()]);
const SCHEDULING_LOOKAHEAD_A: u32 = 4;
let leaf = CHAIN_A.last().unwrap();
let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
let leaf_idx = blocks.len() - 1;
let min_idx_a = 1;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
SESSION,
SCHEDULING_LOOKAHEAD_A,
blocks[min_idx_a..leaf_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_idx - min_idx_a],
CHAIN_A,
&blocks[min_idx_a..],
));
assert_eq!(view.leaves.len(), 2);
let expected_ancestry: Vec<Hash> = blocks[min_idx_a..].iter().rev().copied().collect();
assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
}
#[test]
fn construct_fresh_view_with_various_lookaheads() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::new();
const SCHEDULING_LOOKAHEAD: u32 = 5;
const MIN_RELAY_PARENT_NUMBER: u32 = 4;
let current_session = 2;
let leaf = CHAIN_B.last().unwrap();
let leaf_idx = CHAIN_B.len() - 1;
let min_idx = (MIN_RELAY_PARENT_NUMBER - GENESIS_NUMBER - 1) as usize;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
current_session,
SCHEDULING_LOOKAHEAD,
CHAIN_B[min_idx..leaf_idx].iter().rev().copied().collect(),
vec![current_session; leaf_idx - min_idx],
CHAIN_B,
&CHAIN_B[min_idx..=leaf_idx],
));
for i in min_idx..(CHAIN_B.len() - 1) {
assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i]).is_none());
}
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_idx..=leaf_idx].iter().rev().copied().collect();
assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty());
assert_eq!(
view.paths_via_relay_parent(&CHAIN_B[min_idx]),
vec![CHAIN_B[min_idx..].to_vec()]
);
let leaf = CHAIN_A.last().unwrap();
let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
let leaf_idx = blocks.len() - 1;
let mut ancestor_sessions = vec![current_session; leaf_idx - 1];
ancestor_sessions.push(0);
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
current_session,
blocks.len() as u32 + 1,
blocks[..leaf_idx].iter().rev().copied().collect(),
ancestor_sessions,
CHAIN_A,
&blocks[1..=leaf_idx],
));
assert_eq!(view.leaves.len(), 2);
let expected_ancestry: Vec<Hash> = CHAIN_A[..].iter().rev().copied().collect();
assert_expected_allowed_relay_parents(&view, leaf, &expected_ancestry);
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::<AllMessages, _>(pool);
let mut view = View::default();
const SESSION: u32 = 2;
const SCHEDULING_LOOKAHEAD_A: u32 = 3;
let leaf_a_number = 3;
let leaf_a = CHAIN_B[leaf_a_number - 1];
let min_idx = leaf_a_number - (SCHEDULING_LOOKAHEAD_A as usize - 1);
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
leaf_a,
SESSION,
SCHEDULING_LOOKAHEAD_A,
CHAIN_B[min_idx..(leaf_a_number - 1)].iter().rev().copied().collect(),
vec![SESSION; leaf_a_number - 1 - min_idx],
CHAIN_B,
&CHAIN_B[min_idx..leaf_a_number],
));
const SCHEDULING_LOOKAHEAD_B: u32 = 5;
let leaf_b_number = 5;
let leaf_b = CHAIN_B[leaf_b_number - 1];
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
leaf_b,
SESSION,
SCHEDULING_LOOKAHEAD_B,
CHAIN_B[min_idx..(leaf_b_number - 1)].iter().rev().copied().collect(),
vec![SESSION; leaf_b_number - 1 - min_idx],
CHAIN_B,
&CHAIN_B[leaf_a_number..leaf_b_number],
));
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_idx..leaf_a_number].iter().rev().copied().collect();
assert_expected_allowed_relay_parents(&view, &leaf_a, &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 SESSION: u32 = 2;
const SCHEDULING_LOOKAHEAD_A: u32 = 4;
let leaf_a = CHAIN_B.iter().rev().nth(1).unwrap();
let leaf_a_idx = CHAIN_B.len() - 2;
let min_a_idx = leaf_a_idx - (SCHEDULING_LOOKAHEAD_A - 1) as usize;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf_a,
SESSION,
SCHEDULING_LOOKAHEAD_A,
CHAIN_B[min_a_idx..leaf_a_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_a_idx - min_a_idx],
CHAIN_B,
&CHAIN_B[min_a_idx..=leaf_a_idx],
));
const SCHEDULING_LOOKAHEAD_B: u32 = 3;
let leaf_b = CHAIN_B.last().unwrap();
let leaf_b_idx = CHAIN_B.len() - 1;
let min_b_idx = leaf_b_idx - (SCHEDULING_LOOKAHEAD_B - 1) as usize;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf_b,
SESSION,
SCHEDULING_LOOKAHEAD_B,
CHAIN_B[min_b_idx..leaf_b_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_b_idx - min_b_idx],
CHAIN_B,
&[CHAIN_B[leaf_b_idx]], ));
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(min_a_idx) {
assert!(!view.block_info_storage.contains_key(hash));
}
for hash in CHAIN_B.iter().skip(min_a_idx).take(leaf_a_idx - min_a_idx + 1) {
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 SESSION: u32 = 0;
const SCHEDULING_LOOKAHEAD: u32 = 1;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
GENESIS_HASH,
SESSION,
SCHEDULING_LOOKAHEAD,
vec![], vec![], &[GENESIS_HASH],
&[GENESIS_HASH],
));
assert_matches!(
view.known_allowed_relay_parents_under(&GENESIS_HASH),
Some(hashes) if hashes == &[GENESIS_HASH]
);
}
#[test]
fn path_with_fork() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
const SESSION: u32 = 2;
const SCHEDULING_LOOKAHEAD_A: u32 = 4;
let leaf = CHAIN_A.last().unwrap();
let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
let leaf_idx = blocks.len() - 1;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
SESSION,
SCHEDULING_LOOKAHEAD_A,
blocks[1..leaf_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_idx - 1],
CHAIN_A,
&blocks[1..],
));
const SCHEDULING_LOOKAHEAD_B: u32 = 3;
let leaf = CHAIN_B.last().unwrap();
let leaf_idx = CHAIN_B.len() - 1;
let min_b_idx = leaf_idx - (SCHEDULING_LOOKAHEAD_B - 1) as usize;
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
*leaf,
SESSION,
SCHEDULING_LOOKAHEAD_B,
CHAIN_B[min_b_idx..leaf_idx].iter().rev().copied().collect(),
vec![SESSION; leaf_idx - min_b_idx],
CHAIN_B,
&CHAIN_B[min_b_idx..],
));
assert_eq!(view.leaves.len(), 2);
let paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH);
assert_eq!(paths_to_genesis, Vec::<Vec<Hash>>::new());
let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]);
let expected_path_to_leaf_in_a = vec![CHAIN_A.to_vec()];
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![CHAIN_B[3..].to_vec()];
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::<Vec<Hash>>::new());
}
#[test]
fn max_ancesty_len_honored() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
const SESSION: u32 = 2;
const SCHEDULING_LOOKAHEAD: u32 = 3;
let fork_leaf = CHAIN_B[2];
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
fork_leaf,
SESSION,
SCHEDULING_LOOKAHEAD,
vec![CHAIN_B[1], CHAIN_B[0]], vec![SESSION; 2],
CHAIN_B,
&CHAIN_B[0..=2], ));
assert_eq!(view.leaves.len(), 1);
let main_leaf = CHAIN_B[5];
futures::executor::block_on(activate_leaf_with_overseer_requests(
&mut view,
&mut ctx,
&mut ctx_handle,
main_leaf,
SESSION,
SCHEDULING_LOOKAHEAD,
vec![CHAIN_B[4], CHAIN_B[3]], vec![SESSION; 2],
CHAIN_B,
&CHAIN_B[3..=5], ));
assert_eq!(view.leaves.len(), 2);
for hash in &CHAIN_B[..6] {
assert!(
view.block_info_storage.contains_key(hash),
"Block {:?} should be in storage",
hash
);
}
let paths = view.paths_via_relay_parent(&CHAIN_B[0]);
assert_eq!(paths.len(), 1, "B0 should only be reachable via the fork leaf");
let path_via_fork = &paths[0];
assert_eq!(path_via_fork.last(), Some(&fork_leaf));
assert_eq!(path_via_fork.len(), 3);
assert_eq!(path_via_fork, &CHAIN_B[0..=2].to_vec());
let paths = view.paths_via_relay_parent(&CHAIN_B[3]);
assert_eq!(paths.len(), 1, "B3 should only be reachable via the main leaf");
let path_via_main = &paths[0];
assert_eq!(path_via_main.last(), Some(&main_leaf));
assert_eq!(path_via_main.len(), 3);
assert_eq!(path_via_main, &CHAIN_B[3..=5].to_vec());
for hash in &CHAIN_B[..6] {
for path in view.paths_via_relay_parent(hash) {
assert!(
path.len() <= SCHEDULING_LOOKAHEAD as usize,
"Path through {:?} has length {} > lookahead {}",
hash,
path.len(),
SCHEDULING_LOOKAHEAD,
);
}
}
}
}