// 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