feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
[package]
|
||||
name = "pezcumulus-client-consensus-common"
|
||||
description = "Pezcumulus specific common consensus implementations"
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codec = { features = ["derive"], workspace = true, default-features = true }
|
||||
dyn-clone = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
tracing = { workspace = true, default-features = true }
|
||||
|
||||
# Bizinikiwi
|
||||
prometheus-endpoint = { workspace = true, default-features = true }
|
||||
pezsc-client-api = { workspace = true, default-features = true }
|
||||
pezsc-consensus = { workspace = true, default-features = true }
|
||||
pezsc-consensus-babe = { workspace = true, default-features = true }
|
||||
pezsc-network = { workspace = true, default-features = true }
|
||||
pezsp-blockchain = { workspace = true, default-features = true }
|
||||
pezsp-consensus = { workspace = true, default-features = true }
|
||||
pezsp-consensus-slots = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
pezsp-timestamp = { workspace = true, default-features = true }
|
||||
pezsp-trie = { workspace = true, default-features = true }
|
||||
pezsp-version = { workspace = true, default-features = true }
|
||||
|
||||
# Pezkuwi
|
||||
pezkuwi-primitives = { workspace = true, default-features = true }
|
||||
|
||||
# Pezcumulus
|
||||
pezcumulus-client-pov-recovery = { workspace = true, default-features = true }
|
||||
pezcumulus-primitives-core = { workspace = true, default-features = true }
|
||||
pezcumulus-relay-chain-interface = { workspace = true, default-features = true }
|
||||
pezcumulus-relay-chain-streams = { workspace = true, default-features = true }
|
||||
schnellru = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-timer = { workspace = true }
|
||||
tokio = { features = ["macros"], workspace = true }
|
||||
|
||||
# Bizinikiwi
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
# Pezcumulus
|
||||
pezcumulus-test-client = { workspace = true }
|
||||
pezcumulus-test-relay-sproof-builder = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezcumulus-client-pov-recovery/runtime-benchmarks",
|
||||
"pezcumulus-primitives-core/runtime-benchmarks",
|
||||
"pezcumulus-relay-chain-interface/runtime-benchmarks",
|
||||
"pezcumulus-relay-chain-streams/runtime-benchmarks",
|
||||
"pezcumulus-test-client/runtime-benchmarks",
|
||||
"pezcumulus-test-relay-sproof-builder/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezsc-client-api/runtime-benchmarks",
|
||||
"pezsc-consensus-babe/runtime-benchmarks",
|
||||
"pezsc-consensus/runtime-benchmarks",
|
||||
"pezsc-network/runtime-benchmarks",
|
||||
"pezsp-blockchain/runtime-benchmarks",
|
||||
"pezsp-consensus-slots/runtime-benchmarks",
|
||||
"pezsp-consensus/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-timestamp/runtime-benchmarks",
|
||||
"pezsp-trie/runtime-benchmarks",
|
||||
"pezsp-version/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! (unstable) Composable utilities for constructing import queues for teyrchains.
|
||||
//!
|
||||
//! Unlike standalone chains, teyrchains have the requirement that all consensus logic
|
||||
//! must be checked within the runtime. This property means that work which is normally
|
||||
//! done in the import queue per-block, such as checking signatures, quorums, and whether
|
||||
//! inherent extrinsics were constructed faithfully do not need to be done, per se.
|
||||
//!
|
||||
//! It may seem that it would be beneficial for the client to do these checks regardless,
|
||||
//! but in practice this means that clients would just reject blocks which are _valid_ according
|
||||
//! to their Teyrchain Validation Function, which is the ultimate source of consensus truth.
|
||||
//!
|
||||
//! However, teyrchain runtimes expose two different access points for executing blocks
|
||||
//! in full nodes versus executing those blocks in the teyrchain validation environment.
|
||||
//! At the time of writing, the inherent and consensus checks in most Pezcumulus runtimes
|
||||
//! are only performed during teyrchain validation, not full node block execution.
|
||||
//!
|
||||
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/91> for details.
|
||||
|
||||
use pezsp_consensus::error::Error as ConsensusError;
|
||||
use pezsp_runtime::traits::Block as BlockT;
|
||||
|
||||
use pezsc_consensus::{
|
||||
block_import::{BlockImport, BlockImportParams},
|
||||
import_queue::{BasicQueue, Verifier},
|
||||
};
|
||||
|
||||
use crate::TeyrchainBlockImportMarker;
|
||||
|
||||
/// A [`Verifier`] for blocks which verifies absolutely nothing.
|
||||
///
|
||||
/// This should only be used when the runtime is responsible for checking block seals and inherents.
|
||||
pub struct VerifyNothing;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<Block: BlockT> Verifier<Block> for VerifyNothing {
|
||||
async fn verify(
|
||||
&self,
|
||||
params: BlockImportParams<Block>,
|
||||
) -> Result<BlockImportParams<Block>, String> {
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
/// An import queue which does no verification.
|
||||
///
|
||||
/// This should only be used when the runtime is responsible for checking block seals and inherents.
|
||||
pub fn verify_nothing_import_queue<Block: BlockT, I>(
|
||||
block_import: I,
|
||||
spawner: &impl pezsp_core::traits::SpawnEssentialNamed,
|
||||
registry: Option<&prometheus_endpoint::Registry>,
|
||||
) -> BasicQueue<Block>
|
||||
where
|
||||
I: BlockImport<Block, Error = ConsensusError>
|
||||
+ TeyrchainBlockImportMarker
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
BasicQueue::new(VerifyNothing, Box::new(block_import), None, spawner, registry)
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use pezsc_client_api::{blockchain::Backend as _, Backend, HeaderBackend as _};
|
||||
use pezsp_blockchain::{HashAndNumber, HeaderMetadata, TreeRoute};
|
||||
use pezsp_runtime::traits::{Block as BlockT, NumberFor, One, Saturating, UniqueSaturatedInto, Zero};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const LOG_TARGET: &str = "level-monitor";
|
||||
|
||||
/// Value good enough to be used with teyrchains using the current backend implementation
|
||||
/// that ships with Bizinikiwi. This value may change in the future.
|
||||
pub const MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT: usize = 32;
|
||||
|
||||
// Counter threshold after which we are going to eventually cleanup our internal data.
|
||||
const CLEANUP_THRESHOLD: u32 = 32;
|
||||
|
||||
/// Upper bound to the number of leaves allowed for each level of the blockchain.
|
||||
///
|
||||
/// If the limit is set and more leaves are detected on block import, then the older ones are
|
||||
/// dropped to make space for the fresh blocks.
|
||||
///
|
||||
/// In environments where blocks confirmations from the relay chain may be "slow", then
|
||||
/// setting an upper bound helps keeping the chain health by dropping old (presumably) stale
|
||||
/// leaves and prevents discarding new blocks because we've reached the backend max value.
|
||||
pub enum LevelLimit {
|
||||
/// Limit set to [`MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT`].
|
||||
Default,
|
||||
/// No explicit limit, however a limit may be implicitly imposed by the backend implementation.
|
||||
None,
|
||||
/// Custom value.
|
||||
Some(usize),
|
||||
}
|
||||
|
||||
/// Support structure to constrain the number of leaves at each level.
|
||||
pub struct LevelMonitor<Block: BlockT, BE> {
|
||||
/// Max number of leaves for each level.
|
||||
level_limit: usize,
|
||||
/// Monotonic counter used to keep track of block freshness.
|
||||
pub(crate) import_counter: NumberFor<Block>,
|
||||
/// Map between blocks hashes and freshness.
|
||||
pub(crate) freshness: HashMap<Block::Hash, NumberFor<Block>>,
|
||||
/// Blockchain levels cache.
|
||||
pub(crate) levels: HashMap<NumberFor<Block>, HashSet<Block::Hash>>,
|
||||
/// Lower level number stored by the levels map.
|
||||
lowest_level: NumberFor<Block>,
|
||||
/// Backend reference to remove blocks on level saturation.
|
||||
backend: Arc<BE>,
|
||||
}
|
||||
|
||||
/// Contains information about the target scheduled for removal.
|
||||
struct TargetInfo<Block: BlockT> {
|
||||
/// Index of freshest leaf in the leaves array.
|
||||
freshest_leaf_idx: usize,
|
||||
/// Route from target to its freshest leaf.
|
||||
freshest_route: TreeRoute<Block>,
|
||||
}
|
||||
|
||||
impl<Block, BE> LevelMonitor<Block, BE>
|
||||
where
|
||||
Block: BlockT,
|
||||
BE: Backend<Block>,
|
||||
{
|
||||
/// Instance a new monitor structure.
|
||||
pub fn new(level_limit: usize, backend: Arc<BE>) -> Self {
|
||||
let mut monitor = LevelMonitor {
|
||||
level_limit,
|
||||
import_counter: Zero::zero(),
|
||||
freshness: HashMap::new(),
|
||||
levels: HashMap::new(),
|
||||
lowest_level: Zero::zero(),
|
||||
backend,
|
||||
};
|
||||
monitor.restore();
|
||||
monitor
|
||||
}
|
||||
|
||||
/// Restore the structure using the backend.
|
||||
///
|
||||
/// Blocks freshness values are inferred from the height and not from the effective import
|
||||
/// moment. This is a not accurate but "good-enough" best effort solution.
|
||||
///
|
||||
/// Level limits are not enforced during this phase.
|
||||
fn restore(&mut self) {
|
||||
let info = self.backend.blockchain().info();
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Restoring chain level monitor from last finalized block: {} {}",
|
||||
info.finalized_number,
|
||||
info.finalized_hash
|
||||
);
|
||||
|
||||
self.lowest_level = info.finalized_number;
|
||||
self.import_counter = info.finalized_number;
|
||||
|
||||
for leaf in self.backend.blockchain().leaves().unwrap_or_default() {
|
||||
let Ok(mut meta) = self.backend.blockchain().header_metadata(leaf) else {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Could not fetch header metadata for leaf: {leaf:?}",
|
||||
);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
self.import_counter = self.import_counter.max(meta.number);
|
||||
|
||||
// Populate the monitor until we don't hit an already imported branch
|
||||
while !self.freshness.contains_key(&meta.hash) {
|
||||
self.freshness.insert(meta.hash, meta.number);
|
||||
self.levels.entry(meta.number).or_default().insert(meta.hash);
|
||||
if meta.number <= self.lowest_level {
|
||||
break;
|
||||
}
|
||||
|
||||
meta = match self.backend.blockchain().header_metadata(meta.parent) {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
// This can happen after we have warp synced a node.
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Could not fetch header metadata for parent: {:?}",
|
||||
meta.parent,
|
||||
);
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Restored chain level monitor up to height {}",
|
||||
self.import_counter
|
||||
);
|
||||
}
|
||||
|
||||
/// Check and enforce the limit bound at the given height.
|
||||
///
|
||||
/// In practice this will enforce the given height in having a number of blocks less than
|
||||
/// the limit passed to the constructor.
|
||||
///
|
||||
/// If the given level is found to have a number of blocks greater than or equal the limit
|
||||
/// then the limit is enforced by choosing one (or more) blocks to remove.
|
||||
///
|
||||
/// The removal strategy is driven by the block freshness.
|
||||
///
|
||||
/// A block freshness is determined by the most recent leaf freshness descending from the block
|
||||
/// itself. In other words its freshness is equal to its more "fresh" descendant.
|
||||
///
|
||||
/// The least "fresh" blocks are eventually removed.
|
||||
pub fn enforce_limit(&mut self, number: NumberFor<Block>) {
|
||||
let level_len = self.levels.get(&number).map(|l| l.len()).unwrap_or_default();
|
||||
if level_len < self.level_limit {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort leaves by freshness only once (less fresh first) and keep track of
|
||||
// leaves that were invalidated on removal.
|
||||
let mut leaves = self.backend.blockchain().leaves().unwrap_or_default();
|
||||
leaves.sort_unstable_by(|a, b| self.freshness.get(a).cmp(&self.freshness.get(b)));
|
||||
let mut invalidated_leaves = HashSet::new();
|
||||
|
||||
// This may not be the most efficient way to remove **multiple** entries, but is the easy
|
||||
// one :-). Should be considered that in "normal" conditions the number of blocks to remove
|
||||
// is 0 or 1, it is not worth to complicate the code too much. One condition that may
|
||||
// trigger multiple removals (2+) is if we restart the node using an existing db and a
|
||||
// smaller limit wrt the one previously used.
|
||||
let remove_count = level_len - self.level_limit + 1;
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Detected leaves overflow at height {number}, removing {remove_count} obsolete blocks",
|
||||
);
|
||||
|
||||
(0..remove_count).all(|_| {
|
||||
self.find_target(number, &leaves, &invalidated_leaves).map_or(false, |target| {
|
||||
self.remove_target(target, number, &leaves, &mut invalidated_leaves);
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to find the best candidate to be removed.
|
||||
//
|
||||
// Given a set of blocks with height equal to `number` (potential candidates)
|
||||
// 1. For each candidate fetch all the leaves that are descending from it.
|
||||
// 2. Set the candidate freshness equal to the fresher of its descending leaves.
|
||||
// 3. The target is set as the candidate that is less fresh.
|
||||
//
|
||||
// Input `leaves` are assumed to be already ordered by "freshness" (less fresh first).
|
||||
//
|
||||
// Returns the index of the target fresher leaf within `leaves` and the route from target to
|
||||
// such leaf.
|
||||
fn find_target(
|
||||
&self,
|
||||
number: NumberFor<Block>,
|
||||
leaves: &[Block::Hash],
|
||||
invalidated_leaves: &HashSet<usize>,
|
||||
) -> Option<TargetInfo<Block>> {
|
||||
let mut target_info: Option<TargetInfo<Block>> = None;
|
||||
let blockchain = self.backend.blockchain();
|
||||
let best_hash = blockchain.info().best_hash;
|
||||
|
||||
// Leaves that where already assigned to some node and thus can be skipped
|
||||
// during the search.
|
||||
let mut assigned_leaves = HashSet::new();
|
||||
|
||||
let level = self.levels.get(&number)?;
|
||||
|
||||
for blk_hash in level.iter().filter(|hash| **hash != best_hash) {
|
||||
// Search for the fresher leaf information for this block
|
||||
let candidate_info = leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(leaf_idx, _)| {
|
||||
!assigned_leaves.contains(leaf_idx) && !invalidated_leaves.contains(leaf_idx)
|
||||
})
|
||||
.rev()
|
||||
.find_map(|(leaf_idx, leaf_hash)| {
|
||||
if blk_hash == leaf_hash {
|
||||
let entry = HashAndNumber { number, hash: *blk_hash };
|
||||
TreeRoute::new(vec![entry], 0).ok().map(|freshest_route| TargetInfo {
|
||||
freshest_leaf_idx: leaf_idx,
|
||||
freshest_route,
|
||||
})
|
||||
} else {
|
||||
match pezsp_blockchain::tree_route(blockchain, *blk_hash, *leaf_hash) {
|
||||
Ok(route) if route.retracted().is_empty() => Some(TargetInfo {
|
||||
freshest_leaf_idx: leaf_idx,
|
||||
freshest_route: route,
|
||||
}),
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"(Lookup) Unable getting route from {:?} to {:?}: {}",
|
||||
blk_hash,
|
||||
leaf_hash,
|
||||
err,
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let candidate_info = match candidate_info {
|
||||
Some(candidate_info) => {
|
||||
assigned_leaves.insert(candidate_info.freshest_leaf_idx);
|
||||
candidate_info
|
||||
},
|
||||
None => {
|
||||
// This should never happen
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Unable getting route to any leaf from {:?} (this is a bug)",
|
||||
blk_hash,
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
// Found fresher leaf for this candidate.
|
||||
// This candidate is set as the new target if:
|
||||
// 1. its fresher leaf is less fresh than the previous target fresher leaf AND
|
||||
// 2. best block is not in its route
|
||||
|
||||
let is_less_fresh = || {
|
||||
target_info
|
||||
.as_ref()
|
||||
.map(|ti| candidate_info.freshest_leaf_idx < ti.freshest_leaf_idx)
|
||||
.unwrap_or(true)
|
||||
};
|
||||
let not_contains_best = || {
|
||||
candidate_info
|
||||
.freshest_route
|
||||
.enacted()
|
||||
.iter()
|
||||
.all(|entry| entry.hash != best_hash)
|
||||
};
|
||||
|
||||
if is_less_fresh() && not_contains_best() {
|
||||
let early_stop = candidate_info.freshest_leaf_idx == 0;
|
||||
target_info = Some(candidate_info);
|
||||
if early_stop {
|
||||
// We will never find a candidate with an worst freshest leaf than this.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target_info
|
||||
}
|
||||
|
||||
// Remove the target block and all its descendants.
|
||||
//
|
||||
// Leaves should have already been ordered by "freshness" (less fresh first).
|
||||
fn remove_target(
|
||||
&mut self,
|
||||
target: TargetInfo<Block>,
|
||||
number: NumberFor<Block>,
|
||||
leaves: &[Block::Hash],
|
||||
invalidated_leaves: &mut HashSet<usize>,
|
||||
) {
|
||||
let mut remove_leaf = |number, hash| {
|
||||
log::debug!(target: LOG_TARGET, "Removing block (@{}) {:?}", number, hash);
|
||||
if let Err(err) = self.backend.remove_leaf_block(hash) {
|
||||
log::debug!(target: LOG_TARGET, "Remove not possible for {}: {}", hash, err);
|
||||
return false;
|
||||
}
|
||||
self.levels.get_mut(&number).map(|level| level.remove(&hash));
|
||||
self.freshness.remove(&hash);
|
||||
true
|
||||
};
|
||||
|
||||
invalidated_leaves.insert(target.freshest_leaf_idx);
|
||||
|
||||
// Takes care of route removal. Starts from the leaf and stops as soon as an error is
|
||||
// encountered. In this case an error is interpreted as the block being not a leaf
|
||||
// and it will be removed while removing another route from the same block but to a
|
||||
// different leaf.
|
||||
let mut remove_route = |route: TreeRoute<Block>| {
|
||||
route.enacted().iter().rev().all(|elem| remove_leaf(elem.number, elem.hash));
|
||||
};
|
||||
|
||||
let target_hash = target.freshest_route.common_block().hash;
|
||||
debug_assert_eq!(
|
||||
target.freshest_route.common_block().number,
|
||||
number,
|
||||
"This is a bug in LevelMonitor::find_target() or the Backend is corrupted"
|
||||
);
|
||||
|
||||
// Remove freshest (cached) route first.
|
||||
remove_route(target.freshest_route);
|
||||
|
||||
// Don't bother trying with leaves we already found to not be our descendants.
|
||||
let to_skip = leaves.len() - target.freshest_leaf_idx;
|
||||
leaves.iter().enumerate().rev().skip(to_skip).for_each(|(leaf_idx, leaf_hash)| {
|
||||
if invalidated_leaves.contains(&leaf_idx) {
|
||||
return;
|
||||
}
|
||||
match pezsp_blockchain::tree_route(self.backend.blockchain(), target_hash, *leaf_hash) {
|
||||
Ok(route) if route.retracted().is_empty() => {
|
||||
invalidated_leaves.insert(leaf_idx);
|
||||
remove_route(route);
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"(Removal) unable getting route from {:?} to {:?}: {}",
|
||||
target_hash,
|
||||
leaf_hash,
|
||||
err,
|
||||
);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
|
||||
remove_leaf(number, target_hash);
|
||||
}
|
||||
|
||||
/// Add a new imported block information to the monitor.
|
||||
pub fn block_imported(&mut self, number: NumberFor<Block>, hash: Block::Hash) {
|
||||
let finalized_num = self.backend.blockchain().info().finalized_number;
|
||||
|
||||
if number > finalized_num {
|
||||
// Only blocks above the last finalized block should be added to the monitor
|
||||
self.import_counter += One::one();
|
||||
self.freshness.insert(hash, self.import_counter);
|
||||
self.levels.entry(number).or_default().insert(hash);
|
||||
}
|
||||
|
||||
let delta: u32 = finalized_num.saturating_sub(self.lowest_level).unique_saturated_into();
|
||||
if delta >= CLEANUP_THRESHOLD {
|
||||
// Do cleanup once in a while, we are allowed to have some obsolete information.
|
||||
for i in 0..delta {
|
||||
let number = self.lowest_level + i.unique_saturated_into();
|
||||
self.levels.remove(&number).map(|level| {
|
||||
level.iter().for_each(|hash| {
|
||||
self.freshness.remove(hash);
|
||||
})
|
||||
});
|
||||
}
|
||||
self.lowest_level = finalized_num;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use codec::Decode;
|
||||
use pezkuwi_primitives::{Block as PBlock, Hash as PHash, Header as PHeader, ValidationCodeHash};
|
||||
|
||||
use cumulus_primitives_core::{relay_chain, AbridgedHostConfiguration};
|
||||
use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface};
|
||||
|
||||
use pezsc_client_api::Backend;
|
||||
use pezsc_consensus::{shared_data::SharedData, BlockImport, ImportResult};
|
||||
use pezsp_consensus_slots::Slot;
|
||||
|
||||
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use pezsp_timestamp::Timestamp;
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
mod level_monitor;
|
||||
mod parent_search;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod teyrchain_consensus;
|
||||
|
||||
pub use parent_search::*;
|
||||
|
||||
pub use cumulus_relay_chain_streams::finalized_heads;
|
||||
pub use teyrchain_consensus::spawn_teyrchain_consensus_tasks;
|
||||
|
||||
use level_monitor::LevelMonitor;
|
||||
pub use level_monitor::{LevelLimit, MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT};
|
||||
|
||||
pub mod import_queue;
|
||||
|
||||
/// Provides the hash of validation code used for authoring/execution of blocks at a given
|
||||
/// hash.
|
||||
pub trait ValidationCodeHashProvider<Hash> {
|
||||
fn code_hash_at(&self, at: Hash) -> Option<ValidationCodeHash>;
|
||||
}
|
||||
|
||||
impl<F, Hash> ValidationCodeHashProvider<Hash> for F
|
||||
where
|
||||
F: Fn(Hash) -> Option<ValidationCodeHash>,
|
||||
{
|
||||
fn code_hash_at(&self, at: Hash) -> Option<ValidationCodeHash> {
|
||||
(self)(at)
|
||||
}
|
||||
}
|
||||
|
||||
/// The result from building a collation.
|
||||
pub struct TeyrchainCandidate<B> {
|
||||
/// The block that was built for this candidate.
|
||||
pub block: B,
|
||||
/// The proof that was recorded while building the block.
|
||||
pub proof: pezsp_trie::StorageProof,
|
||||
}
|
||||
|
||||
/// Teyrchain specific block import.
|
||||
///
|
||||
/// Specialized block import for teyrchains. It supports to delay setting the best block until the
|
||||
/// relay chain has included a candidate in its best block. By default the delayed best block
|
||||
/// setting is disabled. The block import also monitors the imported blocks and prunes by default if
|
||||
/// there are too many blocks at the same height. Too many blocks at the same height can for example
|
||||
/// happen if the relay chain is rejecting the teyrchain blocks in the validation.
|
||||
pub struct TeyrchainBlockImport<Block: BlockT, BI, BE> {
|
||||
inner: BI,
|
||||
monitor: Option<SharedData<LevelMonitor<Block, BE>>>,
|
||||
delayed_best_block: bool,
|
||||
}
|
||||
|
||||
impl<Block: BlockT, BI, BE: Backend<Block>> TeyrchainBlockImport<Block, BI, BE> {
|
||||
/// Create a new instance.
|
||||
///
|
||||
/// The number of leaves per level limit is set to `LevelLimit::Default`.
|
||||
pub fn new(inner: BI, backend: Arc<BE>) -> Self {
|
||||
Self::new_with_limit(inner, backend, LevelLimit::Default)
|
||||
}
|
||||
|
||||
/// Create a new instance with an explicit limit to the number of leaves per level.
|
||||
///
|
||||
/// This function alone doesn't enforce the limit on levels for old imported blocks,
|
||||
/// the limit is eventually enforced only when new blocks are imported.
|
||||
pub fn new_with_limit(inner: BI, backend: Arc<BE>, level_leaves_max: LevelLimit) -> Self {
|
||||
let level_limit = match level_leaves_max {
|
||||
LevelLimit::None => None,
|
||||
LevelLimit::Some(limit) => Some(limit),
|
||||
LevelLimit::Default => Some(MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT),
|
||||
};
|
||||
|
||||
let monitor =
|
||||
level_limit.map(|level_limit| SharedData::new(LevelMonitor::new(level_limit, backend)));
|
||||
|
||||
Self { inner, monitor, delayed_best_block: false }
|
||||
}
|
||||
|
||||
/// Create a new instance which delays setting the best block.
|
||||
///
|
||||
/// The number of leaves per level limit is set to `LevelLimit::Default`.
|
||||
pub fn new_with_delayed_best_block(inner: BI, backend: Arc<BE>) -> Self {
|
||||
Self {
|
||||
delayed_best_block: true,
|
||||
..Self::new_with_limit(inner, backend, LevelLimit::Default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT, I: Clone, BE> Clone for TeyrchainBlockImport<Block, I, BE> {
|
||||
fn clone(&self) -> Self {
|
||||
TeyrchainBlockImport {
|
||||
inner: self.inner.clone(),
|
||||
monitor: self.monitor.clone(),
|
||||
delayed_best_block: self.delayed_best_block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<Block, BI, BE> BlockImport<Block> for TeyrchainBlockImport<Block, BI, BE>
|
||||
where
|
||||
Block: BlockT,
|
||||
BI: BlockImport<Block> + Send + Sync,
|
||||
BE: Backend<Block>,
|
||||
{
|
||||
type Error = BI::Error;
|
||||
|
||||
async fn check_block(
|
||||
&self,
|
||||
block: pezsc_consensus::BlockCheckParams<Block>,
|
||||
) -> Result<pezsc_consensus::ImportResult, Self::Error> {
|
||||
self.inner.check_block(block).await
|
||||
}
|
||||
|
||||
async fn import_block(
|
||||
&self,
|
||||
mut params: pezsc_consensus::BlockImportParams<Block>,
|
||||
) -> Result<pezsc_consensus::ImportResult, Self::Error> {
|
||||
// Blocks are stored within the backend by using POST hash.
|
||||
let hash = params.post_hash();
|
||||
let number = *params.header.number();
|
||||
|
||||
if params.with_state() {
|
||||
// Force imported state finality.
|
||||
// Required for warp sync. We assume that preconditions have been
|
||||
// checked properly and we are importing a finalized block with state.
|
||||
params.finalized = true;
|
||||
}
|
||||
|
||||
if self.delayed_best_block {
|
||||
// Best block is determined by the relay chain, or if we are doing the initial sync
|
||||
// we import all blocks as new best.
|
||||
params.fork_choice = Some(pezsc_consensus::ForkChoiceStrategy::Custom(
|
||||
params.origin == pezsp_consensus::BlockOrigin::NetworkInitialSync,
|
||||
));
|
||||
}
|
||||
|
||||
let maybe_lock = self.monitor.as_ref().map(|monitor_lock| {
|
||||
let mut monitor = monitor_lock.shared_data_locked();
|
||||
monitor.enforce_limit(number);
|
||||
monitor.release_mutex()
|
||||
});
|
||||
|
||||
let res = self.inner.import_block(params).await?;
|
||||
|
||||
if let (Some(mut monitor_lock), ImportResult::Imported(_)) = (maybe_lock, &res) {
|
||||
let mut monitor = monitor_lock.upgrade();
|
||||
monitor.block_imported(number, hash);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait denoting a block import type that fits the teyrchain requirements.
|
||||
pub trait TeyrchainBlockImportMarker {}
|
||||
|
||||
impl<B: BlockT, BI, BE> TeyrchainBlockImportMarker for TeyrchainBlockImport<B, BI, BE> {}
|
||||
|
||||
/// Get the relay-parent slot and timestamp from a header.
|
||||
pub fn relay_slot_and_timestamp(
|
||||
relay_parent_header: &PHeader,
|
||||
relay_chain_slot_duration: Duration,
|
||||
) -> Option<(Slot, Timestamp)> {
|
||||
pezsc_consensus_babe::find_pre_digest::<PBlock>(relay_parent_header)
|
||||
.map(|babe_pre_digest| {
|
||||
let slot = babe_pre_digest.slot();
|
||||
let t = Timestamp::new(relay_chain_slot_duration.as_millis() as u64 * *slot);
|
||||
|
||||
(slot, t)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Reads abridged host configuration from the relay chain storage at the given relay parent.
|
||||
pub async fn load_abridged_host_configuration(
|
||||
relay_parent: PHash,
|
||||
relay_client: &impl RelayChainInterface,
|
||||
) -> Result<Option<AbridgedHostConfiguration>, RelayChainError> {
|
||||
relay_client
|
||||
.get_storage_by_key(relay_parent, relay_chain::well_known_keys::ACTIVE_CONFIG)
|
||||
.await?
|
||||
.map(|bytes| {
|
||||
AbridgedHostConfiguration::decode(&mut &bytes[..])
|
||||
.map_err(RelayChainError::DeserializationError)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use codec::Decode;
|
||||
use pezkuwi_primitives::Hash as RelayHash;
|
||||
|
||||
use cumulus_primitives_core::{
|
||||
relay_chain::{BlockId as RBlockId, OccupiedCoreAssumption},
|
||||
ParaId,
|
||||
};
|
||||
use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface};
|
||||
|
||||
use pezsc_client_api::{Backend, HeaderBackend};
|
||||
|
||||
use pezsp_blockchain::{Backend as BlockchainBackend, TreeRoute};
|
||||
|
||||
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
|
||||
const PARENT_SEARCH_LOG_TARGET: &str = "consensus::common::find_potential_parents";
|
||||
|
||||
/// Parameters when searching for suitable parents to build on top of.
|
||||
#[derive(Debug)]
|
||||
pub struct ParentSearchParams {
|
||||
/// The relay-parent that is intended to be used.
|
||||
pub relay_parent: RelayHash,
|
||||
/// The ID of the teyrchain.
|
||||
pub para_id: ParaId,
|
||||
/// A limitation on the age of relay parents for teyrchain blocks that are being
|
||||
/// considered. This is relative to the `relay_parent` number.
|
||||
pub ancestry_lookback: usize,
|
||||
/// How "deep" parents can be relative to the included teyrchain block at the relay-parent.
|
||||
/// The included block has depth 0.
|
||||
pub max_depth: usize,
|
||||
/// Whether to only ignore "alternative" branches, i.e. branches of the chain
|
||||
/// which do not contain the block pending availability.
|
||||
pub ignore_alternative_branches: bool,
|
||||
}
|
||||
|
||||
/// A potential parent block returned from [`find_potential_parents`]
|
||||
#[derive(PartialEq)]
|
||||
pub struct PotentialParent<B: BlockT> {
|
||||
/// The hash of the block.
|
||||
pub hash: B::Hash,
|
||||
/// The header of the block.
|
||||
pub header: B::Header,
|
||||
/// The depth of the block with respect to the included block.
|
||||
pub depth: usize,
|
||||
/// Whether the block is the included block, is itself pending on-chain, or descends
|
||||
/// from the block pending availability.
|
||||
pub aligned_with_pending: bool,
|
||||
}
|
||||
|
||||
impl<B: BlockT> std::fmt::Debug for PotentialParent<B> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PotentialParent")
|
||||
.field("hash", &self.hash)
|
||||
.field("depth", &self.depth)
|
||||
.field("aligned_with_pending", &self.aligned_with_pending)
|
||||
.field("number", &self.header.number())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a recursive search through blocks to find potential
|
||||
/// parent blocks for a new block.
|
||||
///
|
||||
/// This accepts a relay-chain block to be used as an anchor and a maximum search depth,
|
||||
/// along with some arguments for filtering teyrchain blocks and performs a recursive search
|
||||
/// for teyrchain blocks. The search begins at the last included teyrchain block and returns
|
||||
/// a set of [`PotentialParent`]s which could be potential parents of a new block with this
|
||||
/// relay-parent according to the search parameters.
|
||||
///
|
||||
/// A teyrchain block is a potential parent if it is either the last included teyrchain block, the
|
||||
/// pending teyrchain block (when `max_depth` >= 1), or all of the following hold:
|
||||
/// * its parent is a potential parent
|
||||
/// * its relay-parent is within `ancestry_lookback` of the targeted relay-parent.
|
||||
/// * its relay-parent is within the same session as the targeted relay-parent.
|
||||
/// * the block number is within `max_depth` blocks of the included block
|
||||
pub async fn find_potential_parents<B: BlockT>(
|
||||
params: ParentSearchParams,
|
||||
backend: &impl Backend<B>,
|
||||
relay_client: &impl RelayChainInterface,
|
||||
) -> Result<Vec<PotentialParent<B>>, RelayChainError> {
|
||||
tracing::trace!("Parent search parameters: {params:?}");
|
||||
// Get the included block.
|
||||
let Some((included_header, included_hash)) =
|
||||
fetch_included_from_relay_chain(relay_client, backend, params.para_id, params.relay_parent)
|
||||
.await?
|
||||
else {
|
||||
return Ok(Default::default());
|
||||
};
|
||||
|
||||
let only_included = vec![PotentialParent {
|
||||
hash: included_hash,
|
||||
header: included_header.clone(),
|
||||
depth: 0,
|
||||
aligned_with_pending: true,
|
||||
}];
|
||||
|
||||
if params.max_depth == 0 {
|
||||
return Ok(only_included);
|
||||
};
|
||||
|
||||
// Pending header and hash.
|
||||
let maybe_pending = {
|
||||
// Fetch the most recent pending header from the relay chain. We use
|
||||
// `OccupiedCoreAssumption::Included` so the candidate pending availability gets enacted
|
||||
// before being returned to us.
|
||||
let pending_header = relay_client
|
||||
.persisted_validation_data(
|
||||
params.relay_parent,
|
||||
params.para_id,
|
||||
OccupiedCoreAssumption::Included,
|
||||
)
|
||||
.await?
|
||||
.and_then(|p| B::Header::decode(&mut &p.parent_head.0[..]).ok())
|
||||
.filter(|x| x.hash() != included_hash);
|
||||
|
||||
// If the pending block is not locally known, we can't do anything.
|
||||
if let Some(header) = pending_header {
|
||||
let pending_hash = header.hash();
|
||||
match backend.blockchain().header(pending_hash) {
|
||||
// We are supposed to ignore branches that don't contain the pending block, but we
|
||||
// do not know the pending block locally.
|
||||
Ok(None) | Err(_) if params.ignore_alternative_branches => {
|
||||
tracing::warn!(
|
||||
target: PARENT_SEARCH_LOG_TARGET,
|
||||
%pending_hash,
|
||||
"Failed to get header for pending block.",
|
||||
);
|
||||
return Ok(Default::default());
|
||||
},
|
||||
Ok(Some(_)) => Some((header, pending_hash)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_route_to_last_pending = maybe_pending
|
||||
.as_ref()
|
||||
.map(|(_, pending)| {
|
||||
pezsp_blockchain::tree_route(backend.blockchain(), included_hash, *pending)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// If we want to ignore alternative branches there is no reason to start
|
||||
// the parent search at the included block. We can add the included block and
|
||||
// the path to the pending block to the potential parents directly (limited by max_depth).
|
||||
let (frontier, potential_parents) = match (
|
||||
&maybe_pending,
|
||||
params.ignore_alternative_branches,
|
||||
&maybe_route_to_last_pending,
|
||||
) {
|
||||
(Some((pending_header, pending_hash)), true, Some(ref route_to_pending)) => {
|
||||
let mut potential_parents = only_included;
|
||||
|
||||
// This is a defensive check, should never happen.
|
||||
if !route_to_pending.retracted().is_empty() {
|
||||
tracing::warn!(target: PARENT_SEARCH_LOG_TARGET, "Included block not an ancestor of pending block. This should not happen.");
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Add all items on the path included -> pending - 1 to the potential parents, but
|
||||
// not more than `max_depth`.
|
||||
let num_parents_on_path =
|
||||
route_to_pending.enacted().len().saturating_sub(1).min(params.max_depth);
|
||||
for (num, block) in
|
||||
route_to_pending.enacted().iter().take(num_parents_on_path).enumerate()
|
||||
{
|
||||
let Ok(Some(header)) = backend.blockchain().header(block.hash) else { continue };
|
||||
|
||||
potential_parents.push(PotentialParent {
|
||||
hash: block.hash,
|
||||
header,
|
||||
depth: 1 + num,
|
||||
aligned_with_pending: true,
|
||||
});
|
||||
}
|
||||
|
||||
// The search for additional potential parents should now start at the children of
|
||||
// the pending block.
|
||||
(
|
||||
vec![PotentialParent {
|
||||
hash: *pending_hash,
|
||||
header: pending_header.clone(),
|
||||
depth: route_to_pending.enacted().len(),
|
||||
aligned_with_pending: true,
|
||||
}],
|
||||
potential_parents,
|
||||
)
|
||||
},
|
||||
_ => (only_included, Default::default()),
|
||||
};
|
||||
|
||||
if potential_parents.len() > params.max_depth {
|
||||
return Ok(potential_parents);
|
||||
}
|
||||
|
||||
// Build up the ancestry record of the relay chain to compare against.
|
||||
let rp_ancestry =
|
||||
build_relay_parent_ancestry(params.ancestry_lookback, params.relay_parent, relay_client)
|
||||
.await?;
|
||||
|
||||
Ok(search_child_branches_for_parents(
|
||||
frontier,
|
||||
maybe_route_to_last_pending,
|
||||
included_header,
|
||||
maybe_pending.map(|(_, hash)| hash),
|
||||
backend,
|
||||
params.max_depth,
|
||||
params.ignore_alternative_branches,
|
||||
rp_ancestry,
|
||||
potential_parents,
|
||||
))
|
||||
}
|
||||
|
||||
/// Fetch the included block from the relay chain.
|
||||
async fn fetch_included_from_relay_chain<B: BlockT>(
|
||||
relay_client: &impl RelayChainInterface,
|
||||
backend: &impl Backend<B>,
|
||||
para_id: ParaId,
|
||||
relay_parent: RelayHash,
|
||||
) -> Result<Option<(B::Header, B::Hash)>, RelayChainError> {
|
||||
// Fetch the pending header from the relay chain. We use `OccupiedCoreAssumption::TimedOut`
|
||||
// so that even if there is a pending candidate, we assume it is timed out and we get the
|
||||
// included head.
|
||||
let included_header = relay_client
|
||||
.persisted_validation_data(relay_parent, para_id, OccupiedCoreAssumption::TimedOut)
|
||||
.await?;
|
||||
let included_header = match included_header {
|
||||
Some(pvd) => pvd.parent_head,
|
||||
None => return Ok(None), // this implies the para doesn't exist.
|
||||
};
|
||||
|
||||
let included_header = match B::Header::decode(&mut &included_header.0[..]).ok() {
|
||||
None => return Ok(None),
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
let included_hash = included_header.hash();
|
||||
// If the included block is not locally known, we can't do anything.
|
||||
match backend.blockchain().header(included_hash) {
|
||||
Ok(None) => {
|
||||
tracing::warn!(
|
||||
target: PARENT_SEARCH_LOG_TARGET,
|
||||
%included_hash,
|
||||
"Failed to get header for included block.",
|
||||
);
|
||||
return Ok(None);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
target: PARENT_SEARCH_LOG_TARGET,
|
||||
%included_hash,
|
||||
%e,
|
||||
"Failed to get header for included block.",
|
||||
);
|
||||
return Ok(None);
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
Ok(Some((included_header, included_hash)))
|
||||
}
|
||||
|
||||
/// Build an ancestry of relay parents that are acceptable.
|
||||
///
|
||||
/// An acceptable relay parent is one that is no more than `ancestry_lookback` + 1 blocks below the
|
||||
/// relay parent we want to build on. Teyrchain blocks anchored on relay parents older than that can
|
||||
/// not be considered potential parents for block building. They have no chance of still getting
|
||||
/// included, so our newly build teyrchain block would also not get included.
|
||||
///
|
||||
/// On success, returns a vector of `(header_hash, state_root)` of the relevant relay chain
|
||||
/// ancestry blocks.
|
||||
async fn build_relay_parent_ancestry(
|
||||
ancestry_lookback: usize,
|
||||
relay_parent: RelayHash,
|
||||
relay_client: &impl RelayChainInterface,
|
||||
) -> Result<Vec<(RelayHash, RelayHash)>, RelayChainError> {
|
||||
let mut ancestry = Vec::with_capacity(ancestry_lookback + 1);
|
||||
let mut current_rp = relay_parent;
|
||||
let mut required_session = None;
|
||||
while ancestry.len() <= ancestry_lookback {
|
||||
let Some(header) = relay_client.header(RBlockId::hash(current_rp)).await? else { break };
|
||||
|
||||
let session = relay_client.session_index_for_child(current_rp).await?;
|
||||
if required_session.get_or_insert(session) != &session {
|
||||
// Respect the relay-chain rule not to cross session boundaries.
|
||||
break;
|
||||
}
|
||||
|
||||
ancestry.push((current_rp, *header.state_root()));
|
||||
current_rp = *header.parent_hash();
|
||||
|
||||
// don't iterate back into the genesis block.
|
||||
if header.number == 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(ancestry)
|
||||
}
|
||||
|
||||
/// Start search for child blocks that can be used as parents.
|
||||
pub fn search_child_branches_for_parents<Block: BlockT>(
|
||||
mut frontier: Vec<PotentialParent<Block>>,
|
||||
maybe_route_to_last_pending: Option<TreeRoute<Block>>,
|
||||
included_header: Block::Header,
|
||||
pending_hash: Option<Block::Hash>,
|
||||
backend: &impl Backend<Block>,
|
||||
max_depth: usize,
|
||||
ignore_alternative_branches: bool,
|
||||
rp_ancestry: Vec<(RelayHash, RelayHash)>,
|
||||
mut potential_parents: Vec<PotentialParent<Block>>,
|
||||
) -> Vec<PotentialParent<Block>> {
|
||||
let included_hash = included_header.hash();
|
||||
let is_hash_in_ancestry = |hash| rp_ancestry.iter().any(|x| x.0 == hash);
|
||||
let is_root_in_ancestry = |root| rp_ancestry.iter().any(|x| x.1 == root);
|
||||
|
||||
// The distance between pending and included block. Is later used to check if a child
|
||||
// is aligned with pending when it is between pending and included block.
|
||||
let pending_distance = maybe_route_to_last_pending.as_ref().map(|route| route.enacted().len());
|
||||
|
||||
// If a block is on the path included -> pending, we consider it `aligned_with_pending`.
|
||||
let is_child_pending = |hash| {
|
||||
maybe_route_to_last_pending
|
||||
.as_ref()
|
||||
.map_or(true, |route| route.enacted().iter().any(|x| x.hash == hash))
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
target: PARENT_SEARCH_LOG_TARGET,
|
||||
?included_hash,
|
||||
included_num = ?included_header.number(),
|
||||
?pending_hash ,
|
||||
?rp_ancestry,
|
||||
"Searching relay chain ancestry."
|
||||
);
|
||||
while let Some(entry) = frontier.pop() {
|
||||
let is_pending = pending_hash.as_ref().map_or(false, |h| &entry.hash == h);
|
||||
let is_included = included_hash == entry.hash;
|
||||
|
||||
// note: even if the pending block or included block have a relay parent
|
||||
// outside of the expected part of the relay chain, they are always allowed
|
||||
// because they have already been posted on chain.
|
||||
let is_potential = is_pending || is_included || {
|
||||
let digest = entry.header.digest();
|
||||
let is_hash_in_ancestry_check = cumulus_primitives_core::extract_relay_parent(digest)
|
||||
.map_or(false, is_hash_in_ancestry);
|
||||
let is_root_in_ancestry_check =
|
||||
cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root(digest)
|
||||
.map(|(r, _n)| r)
|
||||
.map_or(false, is_root_in_ancestry);
|
||||
|
||||
is_hash_in_ancestry_check || is_root_in_ancestry_check
|
||||
};
|
||||
|
||||
let parent_aligned_with_pending = entry.aligned_with_pending;
|
||||
let child_depth = entry.depth + 1;
|
||||
let hash = entry.hash;
|
||||
|
||||
tracing::trace!(
|
||||
target: PARENT_SEARCH_LOG_TARGET,
|
||||
?hash,
|
||||
is_potential,
|
||||
is_pending,
|
||||
is_included,
|
||||
"Checking potential parent."
|
||||
);
|
||||
|
||||
if is_potential {
|
||||
potential_parents.push(entry);
|
||||
}
|
||||
|
||||
if !is_potential || child_depth > max_depth {
|
||||
continue;
|
||||
}
|
||||
|
||||
// push children onto search frontier.
|
||||
for child in backend.blockchain().children(hash).ok().into_iter().flatten() {
|
||||
tracing::trace!(target: PARENT_SEARCH_LOG_TARGET, ?child, child_depth, ?pending_distance, "Looking at child.");
|
||||
|
||||
let aligned_with_pending = parent_aligned_with_pending &&
|
||||
(pending_distance.map_or(true, |dist| child_depth > dist) ||
|
||||
is_child_pending(child));
|
||||
|
||||
if ignore_alternative_branches && !aligned_with_pending {
|
||||
tracing::trace!(target: PARENT_SEARCH_LOG_TARGET, ?child, "Child is not aligned with pending block.");
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(Some(header)) = backend.blockchain().header(child) else { continue };
|
||||
|
||||
frontier.push(PotentialParent {
|
||||
hash: child,
|
||||
header,
|
||||
depth: child_depth,
|
||||
aligned_with_pending,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
potential_parents
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,544 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use cumulus_relay_chain_streams::{finalized_heads, new_best_heads};
|
||||
use pezsc_client_api::{
|
||||
Backend, BlockBackend, BlockImportNotification, BlockchainEvents, Finalizer, UsageProvider,
|
||||
};
|
||||
use pezsc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy};
|
||||
use schnellru::{ByLength, LruMap};
|
||||
use pezsp_blockchain::Error as ClientError;
|
||||
use pezsp_consensus::{BlockOrigin, BlockStatus};
|
||||
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
|
||||
use cumulus_client_pov_recovery::{RecoveryKind, RecoveryRequest};
|
||||
use cumulus_relay_chain_interface::RelayChainInterface;
|
||||
|
||||
use pezkuwi_primitives::Id as ParaId;
|
||||
|
||||
use codec::Decode;
|
||||
use futures::{
|
||||
channel::mpsc::{Sender, UnboundedSender},
|
||||
pin_mut, select, FutureExt, SinkExt, Stream, StreamExt,
|
||||
};
|
||||
use pezsp_core::traits::SpawnEssentialNamed;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
const LOG_TARGET: &str = "pezcumulus-consensus";
|
||||
const FINALIZATION_CACHE_SIZE: u32 = 40;
|
||||
|
||||
fn handle_new_finalized_head<P, Block, B>(
|
||||
teyrchain: &Arc<P>,
|
||||
header: Block::Header,
|
||||
last_seen_finalized_hashes: &mut LruMap<Block::Hash, ()>,
|
||||
) where
|
||||
Block: BlockT,
|
||||
B: Backend<Block>,
|
||||
P: Finalizer<Block, B> + UsageProvider<Block> + BlockchainEvents<Block>,
|
||||
{
|
||||
let hash = header.hash();
|
||||
|
||||
last_seen_finalized_hashes.insert(hash, ());
|
||||
|
||||
// Only finalize if we are below the incoming finalized teyrchain head
|
||||
if teyrchain.usage_info().chain.finalized_number < *header.number() {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
"Attempting to finalize header.",
|
||||
);
|
||||
if let Err(e) = teyrchain.finalize_block(hash, None, true) {
|
||||
match e {
|
||||
ClientError::UnknownBlock(_) => tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
"Could not finalize block because it is unknown.",
|
||||
),
|
||||
_ => tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
error = ?e,
|
||||
block_hash = ?hash,
|
||||
"Failed to finalize block",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Streams finalized teyrchain heads from the relay chain.
|
||||
///
|
||||
/// This worker continuously monitors the relay chain for finalized blocks and extracts
|
||||
/// the corresponding teyrchain head data for the given `para_id`. The extracted head
|
||||
/// data is sent through the provided channel for consumption by the consensus system.
|
||||
///
|
||||
/// This is necessary because finalization of blocks can take a long
|
||||
/// time. During this blocking operation, we should not keep references to finality notifications,
|
||||
/// because that prevents the corresponding blocks from getting pruned.
|
||||
pub async fn finalized_head_stream_worker<R: RelayChainInterface + Clone, Block: BlockT>(
|
||||
mut tx: UnboundedSender<Block::Header>,
|
||||
para_id: ParaId,
|
||||
relay_chain: R,
|
||||
) {
|
||||
let finalized_heads = match finalized_heads(relay_chain.clone(), para_id).await {
|
||||
Ok(finalized_heads_stream) => finalized_heads_stream.fuse(),
|
||||
Err(err) => {
|
||||
tracing::error!(target: LOG_TARGET, error = ?err, "Unable to retrieve finalized heads stream.");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
pin_mut!(finalized_heads);
|
||||
loop {
|
||||
if let Some((head_data, _)) = finalized_heads.next().await {
|
||||
let header = match Block::Header::decode(&mut &head_data[..]) {
|
||||
Ok(header) => header,
|
||||
Err(err) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error = ?err,
|
||||
"Could not decode teyrchain header while following finalized heads.",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if let Err(e) = tx.send(header).await {
|
||||
tracing::error!(target: LOG_TARGET, ?e, "Error while sending finalized head.");
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Follow the finalized head of the given teyrchain.
|
||||
///
|
||||
/// For every finalized block of the relay chain, it will get the included teyrchain header
|
||||
/// corresponding to `para_id` and will finalize it in the teyrchain.
|
||||
async fn follow_finalized_head<P, Block, B>(
|
||||
teyrchain: Arc<P>,
|
||||
finalized_head_stream: Box<impl Stream<Item = Block::Header> + Unpin + Send>,
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: Finalizer<Block, B> + UsageProvider<Block> + BlockchainEvents<Block>,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
let mut imported_blocks = teyrchain.import_notification_stream().fuse();
|
||||
let mut finalized_head_stream = finalized_head_stream.fuse();
|
||||
|
||||
// We use this cache to finalize blocks that are imported late.
|
||||
// For example, a block that has been recovered via PoV-Recovery
|
||||
// on a full node can have several minutes delay. With this cache
|
||||
// we have some "memory" of recently finalized blocks.
|
||||
let mut last_seen_finalized_hashes = LruMap::new(ByLength::new(FINALIZATION_CACHE_SIZE));
|
||||
|
||||
loop {
|
||||
select! {
|
||||
fin = finalized_head_stream.next() => {
|
||||
match fin {
|
||||
Some(finalized_head) =>
|
||||
handle_new_finalized_head(&teyrchain, finalized_head, &mut last_seen_finalized_hashes),
|
||||
None => {
|
||||
tracing::debug!(target: LOG_TARGET, "Stopping following finalized head.");
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
imported = imported_blocks.next() => {
|
||||
match imported {
|
||||
Some(imported_block) => {
|
||||
// When we see a block import that is already finalized, we immediately finalize it.
|
||||
if last_seen_finalized_hashes.peek(&imported_block.hash).is_some() {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?imported_block.hash,
|
||||
"Setting newly imported block as finalized.",
|
||||
);
|
||||
|
||||
if let Err(e) = teyrchain.finalize_block(imported_block.hash, None, true) {
|
||||
match e {
|
||||
ClientError::UnknownBlock(_) => tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?imported_block.hash,
|
||||
"Could not finalize block because it is unknown.",
|
||||
),
|
||||
_ => tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
error = ?e,
|
||||
block_hash = ?imported_block.hash,
|
||||
"Failed to finalize block",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Stopping following imported blocks.",
|
||||
);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns the essential finalization tasks for teyrchain consensus.
|
||||
///
|
||||
/// This function creates and spawns two critical background tasks:
|
||||
/// 1. A finalized head stream worker that monitors relay chain finality and extracts included
|
||||
/// headers
|
||||
/// 2. The main teyrchain consensus task that handles finalization and best block updates
|
||||
pub fn spawn_teyrchain_consensus_tasks<P, R, Block, B, S>(
|
||||
para_id: ParaId,
|
||||
teyrchain: Arc<P>,
|
||||
relay_chain: R,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
|
||||
recovery_chan_tx: Option<Sender<RecoveryRequest<Block>>>,
|
||||
spawn_handle: S,
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: Finalizer<Block, B>
|
||||
+ UsageProvider<Block>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ BlockBackend<Block>
|
||||
+ BlockchainEvents<Block>
|
||||
+ 'static,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
R: RelayChainInterface + Clone + 'static,
|
||||
S: SpawnEssentialNamed + 'static,
|
||||
B: Backend<Block> + 'static,
|
||||
{
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
let worker = finalized_head_stream_worker::<_, Block>(tx, para_id, relay_chain.clone());
|
||||
let consensus = run_teyrchain_consensus(
|
||||
para_id,
|
||||
teyrchain,
|
||||
relay_chain,
|
||||
announce_block,
|
||||
Box::new(rx),
|
||||
recovery_chan_tx,
|
||||
);
|
||||
|
||||
spawn_handle.spawn_essential_blocking("pezcumulus-consensus", None, Box::pin(consensus));
|
||||
spawn_handle.spawn_essential_blocking(
|
||||
"pezcumulus-consensus-finality-stream",
|
||||
None,
|
||||
Box::pin(worker),
|
||||
);
|
||||
}
|
||||
|
||||
/// Run the teyrchain consensus.
|
||||
///
|
||||
/// This will follow the given `relay_chain` to act as consensus for the teyrchain that corresponds
|
||||
/// to the given `para_id`. It will set the new best block of the teyrchain as it gets aware of it.
|
||||
/// The same happens for the finalized block.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This will access the backend of the teyrchain and thus, this future should be spawned as
|
||||
/// blocking task.
|
||||
pub async fn run_teyrchain_consensus<P, R, Block, B>(
|
||||
para_id: ParaId,
|
||||
teyrchain: Arc<P>,
|
||||
relay_chain: R,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
|
||||
finalized_head_stream: Box<impl Stream<Item = Block::Header> + Unpin + Send>,
|
||||
recovery_chan_tx: Option<Sender<RecoveryRequest<Block>>>,
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: Finalizer<Block, B>
|
||||
+ UsageProvider<Block>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ BlockBackend<Block>
|
||||
+ BlockchainEvents<Block>,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
R: RelayChainInterface + Clone,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
let follow_new_best = follow_new_best(
|
||||
para_id,
|
||||
teyrchain.clone(),
|
||||
relay_chain.clone(),
|
||||
announce_block,
|
||||
recovery_chan_tx,
|
||||
);
|
||||
let follow_finalized_head = follow_finalized_head(teyrchain, finalized_head_stream);
|
||||
select! {
|
||||
_ = follow_new_best.fuse() => {},
|
||||
_ = follow_finalized_head.fuse() => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Follow the relay chain new best head, to update the Teyrchain new best head.
|
||||
async fn follow_new_best<P, R, Block, B>(
|
||||
para_id: ParaId,
|
||||
teyrchain: Arc<P>,
|
||||
relay_chain: R,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
|
||||
mut recovery_chan_tx: Option<Sender<RecoveryRequest<Block>>>,
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: Finalizer<Block, B>
|
||||
+ UsageProvider<Block>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ BlockBackend<Block>
|
||||
+ BlockchainEvents<Block>,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
R: RelayChainInterface + Clone,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
let new_best_heads = match new_best_heads(relay_chain, para_id).await {
|
||||
Ok(best_heads_stream) => best_heads_stream.fuse(),
|
||||
Err(err) => {
|
||||
tracing::error!(target: LOG_TARGET, error = ?err, "Unable to retrieve best heads stream.");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
pin_mut!(new_best_heads);
|
||||
|
||||
let mut imported_blocks = teyrchain.import_notification_stream().fuse();
|
||||
// The unset best header of the teyrchain. Will be `Some(_)` when we have imported a relay chain
|
||||
// block before the associated teyrchain block. In this case we need to wait for this block to
|
||||
// be imported to set it as new best.
|
||||
let mut unset_best_header = None;
|
||||
|
||||
loop {
|
||||
select! {
|
||||
h = new_best_heads.next() => {
|
||||
match h {
|
||||
Some(h) => handle_new_best_teyrchain_head(
|
||||
h,
|
||||
&*teyrchain,
|
||||
&mut unset_best_header,
|
||||
recovery_chan_tx.as_mut(),
|
||||
).await,
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Stopping following new best.",
|
||||
);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
i = imported_blocks.next() => {
|
||||
match i {
|
||||
Some(i) => handle_new_block_imported(
|
||||
i,
|
||||
&mut unset_best_header,
|
||||
&*teyrchain,
|
||||
&*announce_block,
|
||||
).await,
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Stopping following imported blocks.",
|
||||
);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a new import block of the teyrchain.
|
||||
async fn handle_new_block_imported<Block, P>(
|
||||
notification: BlockImportNotification<Block>,
|
||||
unset_best_header_opt: &mut Option<Block::Header>,
|
||||
teyrchain: &P,
|
||||
announce_block: &(dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync),
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: UsageProvider<Block> + Send + Sync + BlockBackend<Block>,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
{
|
||||
// HACK
|
||||
//
|
||||
// Remove after https://github.com/pezkuwichain/kurdistan-sdk/issues/76 or similar is merged
|
||||
if notification.origin != BlockOrigin::Own {
|
||||
announce_block(notification.hash, None);
|
||||
}
|
||||
|
||||
let unset_best_header = match (notification.is_new_best, &unset_best_header_opt) {
|
||||
// If this is the new best block or we don't have any unset block, we can end it here.
|
||||
(true, _) | (_, None) => return,
|
||||
(false, Some(ref u)) => u,
|
||||
};
|
||||
|
||||
let unset_hash = if notification.header.number() < unset_best_header.number() {
|
||||
return;
|
||||
} else if notification.header.number() == unset_best_header.number() {
|
||||
let unset_hash = unset_best_header.hash();
|
||||
|
||||
if unset_hash != notification.hash {
|
||||
return;
|
||||
} else {
|
||||
unset_hash
|
||||
}
|
||||
} else {
|
||||
unset_best_header.hash()
|
||||
};
|
||||
|
||||
match teyrchain.block_status(unset_hash) {
|
||||
Ok(BlockStatus::InChainWithState) => {
|
||||
let unset_best_header = unset_best_header_opt
|
||||
.take()
|
||||
.expect("We checked above that the value is set; qed");
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?unset_hash,
|
||||
"Importing block as new best for teyrchain.",
|
||||
);
|
||||
import_block_as_new_best(unset_hash, unset_best_header, teyrchain).await;
|
||||
},
|
||||
state => tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?unset_best_header,
|
||||
?notification.header,
|
||||
?state,
|
||||
"Unexpected state for unset best header.",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the new best teyrchain head as extracted from the new best relay chain.
|
||||
async fn handle_new_best_teyrchain_head<Block, P>(
|
||||
head: Vec<u8>,
|
||||
teyrchain: &P,
|
||||
unset_best_header: &mut Option<Block::Header>,
|
||||
mut recovery_chan_tx: Option<&mut Sender<RecoveryRequest<Block>>>,
|
||||
) where
|
||||
Block: BlockT,
|
||||
P: UsageProvider<Block> + Send + Sync + BlockBackend<Block>,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
{
|
||||
let teyrchain_head = match <<Block as BlockT>::Header>::decode(&mut &head[..]) {
|
||||
Ok(header) => header,
|
||||
Err(err) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error = ?err,
|
||||
"Could not decode Teyrchain header while following best heads.",
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let hash = teyrchain_head.hash();
|
||||
|
||||
if teyrchain.usage_info().chain.best_hash == hash {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
"Skipping set new best block, because block is already the best.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the block is already known or otherwise we skip setting new best.
|
||||
match teyrchain.block_status(hash) {
|
||||
Ok(BlockStatus::InChainWithState) => {
|
||||
unset_best_header.take();
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
included = ?hash,
|
||||
"Importing block as new best for teyrchain.",
|
||||
);
|
||||
import_block_as_new_best(hash, teyrchain_head, teyrchain).await;
|
||||
},
|
||||
Ok(BlockStatus::InChainPruned) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
"Trying to set pruned block as new best!",
|
||||
);
|
||||
},
|
||||
Ok(BlockStatus::Unknown) => {
|
||||
*unset_best_header = Some(teyrchain_head);
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
"Teyrchain block not yet imported, waiting for import to enact as best block.",
|
||||
);
|
||||
|
||||
if let Some(ref mut recovery_chan_tx) = recovery_chan_tx {
|
||||
// Best effort channel to actively encourage block recovery.
|
||||
// An error here is not fatal; the relay chain continuously re-announces
|
||||
// the best block, thus we will have other opportunities to retry.
|
||||
let req = RecoveryRequest { hash, kind: RecoveryKind::Full };
|
||||
if let Err(err) = recovery_chan_tx.try_send(req) {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
error = ?err,
|
||||
"Unable to notify block recovery subsystem"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
error = ?e,
|
||||
"Failed to get block status of block.",
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
async fn import_block_as_new_best<Block, P>(hash: Block::Hash, header: Block::Header, teyrchain: &P)
|
||||
where
|
||||
Block: BlockT,
|
||||
P: UsageProvider<Block> + Send + Sync + BlockBackend<Block>,
|
||||
for<'a> &'a P: BlockImport<Block>,
|
||||
{
|
||||
let best_number = teyrchain.usage_info().chain.best_number;
|
||||
if *header.number() < best_number {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
%best_number,
|
||||
block_number = %header.number(),
|
||||
"Skipping importing block as new best block, because there already exists a \
|
||||
best block with an higher number",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make it the new best block
|
||||
let mut block_import_params = BlockImportParams::new(BlockOrigin::ConsensusBroadcast, header);
|
||||
block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(true));
|
||||
block_import_params.import_existing = true;
|
||||
|
||||
if let Err(err) = teyrchain.import_block(block_import_params).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
block_hash = ?hash,
|
||||
error = ?err,
|
||||
"Failed to set new best block.",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user