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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,39 @@
[package]
name = "pezsp-blockchain"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Bizinikiwi blockchain traits and primitives."
documentation = "https://docs.rs/pezsp-blockchain"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
futures = { workspace = true }
parking_lot = { workspace = true, default-features = true }
schnellru = { workspace = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-database = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-state-machine = { workspace = true, default-features = true }
thiserror = { workspace = true }
tracing = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsp-api/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-state-machine/runtime-benchmarks",
]
@@ -0,0 +1,3 @@
Bizinikiwi blockchain traits and primitives.
License: Apache-2.0
@@ -0,0 +1,577 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Bizinikiwi blockchain trait
use codec::{Decode, Encode};
use parking_lot::RwLock;
use pezsp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
Justifications,
};
use std::collections::{btree_set::BTreeSet, HashMap, VecDeque};
use tracing::{debug, warn};
use crate::{
error::{Error, Result},
header_metadata::HeaderMetadata,
tree_route, CachedHeaderMetadata,
};
/// Blockchain database header backend. Does not perform any validation.
pub trait HeaderBackend<Block: BlockT>: Send + Sync {
/// Get block header. Returns `None` if block is not found.
fn header(&self, hash: Block::Hash) -> Result<Option<Block::Header>>;
/// Get blockchain info.
fn info(&self) -> Info<Block>;
/// Get block status.
fn status(&self, hash: Block::Hash) -> Result<BlockStatus>;
/// Get block number by hash. Returns `None` if the header is not in the chain.
fn number(
&self,
hash: Block::Hash,
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>>;
/// Get block hash by number. Returns `None` if the header is not in the chain.
fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;
/// Convert an arbitrary block ID into a block hash.
fn block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Option<Block::Hash>> {
match *id {
BlockId::Hash(h) => Ok(Some(h)),
BlockId::Number(n) => self.hash(n),
}
}
/// Convert an arbitrary block ID into a block hash.
fn block_number_from_id(&self, id: &BlockId<Block>) -> Result<Option<NumberFor<Block>>> {
match *id {
BlockId::Hash(h) => self.number(h),
BlockId::Number(n) => Ok(Some(n)),
}
}
/// Get block header. Returns `UnknownBlock` error if block is not found.
fn expect_header(&self, hash: Block::Hash) -> Result<Block::Header> {
self.header(hash)?
.ok_or_else(|| Error::UnknownBlock(format!("Expect header: {}", hash)))
}
/// Convert an arbitrary block ID into a block number. Returns `UnknownBlock` error if block is
/// not found.
fn expect_block_number_from_id(&self, id: &BlockId<Block>) -> Result<NumberFor<Block>> {
self.block_number_from_id(id).and_then(|n| {
n.ok_or_else(|| Error::UnknownBlock(format!("Expect block number from id: {}", id)))
})
}
/// Convert an arbitrary block ID into a block hash. Returns `UnknownBlock` error if block is
/// not found.
fn expect_block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Block::Hash> {
self.block_hash_from_id(id).and_then(|h| {
h.ok_or_else(|| Error::UnknownBlock(format!("Expect block hash from id: {}", id)))
})
}
}
/// Handles stale forks.
pub trait ForkBackend<Block: BlockT>:
HeaderMetadata<Block> + HeaderBackend<Block> + Send + Sync
{
/// Returns block hashes for provided fork heads. It skips the fork if when blocks are missing
/// (e.g. warp-sync) and internal `tree_route` function fails.
///
/// Example:
/// G --- A1 --- A2 --- A3 --- A4 ( < fork1 )
/// \-----C4 --- C5 ( < fork2 )
/// We finalize A3 and call expand_fork(C5). Result = (C5,C4).
fn expand_forks(
&self,
fork_heads: &[Block::Hash],
) -> std::result::Result<BTreeSet<Block::Hash>, Error> {
let mut expanded_forks = BTreeSet::new();
for fork_head in fork_heads {
match tree_route(self, *fork_head, self.info().finalized_hash) {
Ok(tree_route) => {
for block in tree_route.retracted() {
expanded_forks.insert(block.hash);
}
continue;
},
Err(_) => {
// There are cases when blocks are missing (e.g. warp-sync).
},
}
}
Ok(expanded_forks)
}
}
impl<Block, T> ForkBackend<Block> for T
where
Block: BlockT,
T: HeaderMetadata<Block> + HeaderBackend<Block> + Send + Sync,
{
}
struct MinimalBlockMetadata<Block: BlockT> {
number: NumberFor<Block>,
hash: Block::Hash,
parent: Block::Hash,
}
impl<Block> Clone for MinimalBlockMetadata<Block>
where
Block: BlockT,
{
fn clone(&self) -> Self {
Self { number: self.number, hash: self.hash, parent: self.parent }
}
}
impl<Block> Copy for MinimalBlockMetadata<Block> where Block: BlockT {}
impl<Block> From<&CachedHeaderMetadata<Block>> for MinimalBlockMetadata<Block>
where
Block: BlockT,
{
fn from(value: &CachedHeaderMetadata<Block>) -> Self {
Self { number: value.number, hash: value.hash, parent: value.parent }
}
}
/// Blockchain database backend. Does not perform any validation.
pub trait Backend<Block: BlockT>:
HeaderBackend<Block> + HeaderMetadata<Block, Error = Error>
{
/// Get block body. Returns `None` if block is not found.
fn body(&self, hash: Block::Hash) -> Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
/// Get block justifications. Returns `None` if no justification exists.
fn justifications(&self, hash: Block::Hash) -> Result<Option<Justifications>>;
/// Get last finalized block hash.
fn last_finalized(&self) -> Result<Block::Hash>;
/// Returns hashes of all blocks that are leaves of the block tree.
/// in other words, that have no children, are chain heads.
/// Results must be ordered best (longest, highest) chain first.
fn leaves(&self) -> Result<Vec<Block::Hash>>;
/// Return hashes of all blocks that are children of the block with `parent_hash`.
fn children(&self, parent_hash: Block::Hash) -> Result<Vec<Block::Hash>>;
/// Get the most recent block hash of the longest chain that contains
/// a block with the given `base_hash`.
///
/// The search space is always limited to blocks which are in the finalized
/// chain or descendants of it.
///
/// Returns `Ok(None)` if `base_hash` is not found in search space.
// TODO: document time complexity of this, see [#1444](https://github.com/pezkuwichain/kurdistan-sdk/issues/18)
fn longest_containing(
&self,
base_hash: Block::Hash,
import_lock: &RwLock<()>,
) -> Result<Option<Block::Hash>> {
let Some(base_header) = self.header(base_hash)? else { return Ok(None) };
let leaves = {
// ensure no blocks are imported during this code block.
// an import could trigger a reorg which could change the canonical chain.
// we depend on the canonical chain staying the same during this code block.
let _import_guard = import_lock.read();
let info = self.info();
if info.finalized_number > *base_header.number() {
// `base_header` is on a dead fork.
return Ok(None);
}
self.leaves()?
};
// for each chain. longest chain first. shortest last
for leaf_hash in leaves {
let mut current_hash = leaf_hash;
// go backwards through the chain (via parent links)
loop {
if current_hash == base_hash {
return Ok(Some(leaf_hash));
}
let current_header = self
.header(current_hash)?
.ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?;
// stop search in this chain once we go below the target's block number
if current_header.number() < base_header.number() {
break;
}
current_hash = *current_header.parent_hash();
}
}
// header may be on a dead fork -- the only leaves that are considered are
// those which can still be finalized.
//
// FIXME #1558 only issue this warning when not on a dead fork
warn!(
target: crate::LOG_TARGET,
"Block {:?} exists in chain but not found when following all leaves backwards",
base_hash,
);
Ok(None)
}
/// Get single indexed transaction by content hash. Note that this will only fetch transactions
/// that are indexed by the runtime with `storage_index_transaction`.
fn indexed_transaction(&self, hash: Block::Hash) -> Result<Option<Vec<u8>>>;
/// Check if indexed transaction exists.
fn has_indexed_transaction(&self, hash: Block::Hash) -> Result<bool> {
Ok(self.indexed_transaction(hash)?.is_some())
}
fn block_indexed_body(&self, hash: Block::Hash) -> Result<Option<Vec<Vec<u8>>>>;
/// Returns all leaves that will be displaced after the block finalization.
fn displaced_leaves_after_finalizing(
&self,
finalized_block_hash: Block::Hash,
finalized_block_number: NumberFor<Block>,
finalized_block_parent_hash: Block::Hash,
) -> std::result::Result<DisplacedLeavesAfterFinalization<Block>, Error> {
// There are no forks at genesis.
if finalized_block_number.is_zero() {
return Ok(DisplacedLeavesAfterFinalization::default());
}
let leaves = self.leaves()?;
let now = std::time::Instant::now();
debug!(
target: crate::LOG_TARGET,
?leaves,
?finalized_block_hash,
?finalized_block_number,
"Checking for displaced leaves after finalization."
);
// Store hashes of finalized blocks for quick checking later, the last block is the
// finalized one
let mut finalized_chain = VecDeque::new();
finalized_chain.push_front(MinimalBlockMetadata {
number: finalized_block_number,
hash: finalized_block_hash,
parent: finalized_block_parent_hash,
});
// Local cache is a performance optimization in case of finalized block deep below the
// tip of the chain with a lot of leaves above finalized block
let mut local_cache = HashMap::<Block::Hash, MinimalBlockMetadata<Block>>::new();
let mut result = DisplacedLeavesAfterFinalization {
displaced_leaves: Vec::with_capacity(leaves.len()),
displaced_blocks: Vec::with_capacity(leaves.len()),
};
let mut displaced_blocks_candidates = Vec::new();
let genesis_hash = self.info().genesis_hash;
for leaf_hash in leaves {
let mut current_header_metadata =
MinimalBlockMetadata::from(&self.header_metadata(leaf_hash).map_err(|err| {
debug!(
target: crate::LOG_TARGET,
?leaf_hash,
?err,
elapsed = ?now.elapsed(),
"Failed to fetch leaf header.",
);
err
})?);
let leaf_number = current_header_metadata.number;
// The genesis block is part of the canonical chain.
if leaf_hash == genesis_hash {
result.displaced_leaves.push((leaf_number, leaf_hash));
debug!(
target: crate::LOG_TARGET,
?leaf_hash,
elapsed = ?now.elapsed(),
"Added genesis leaf to displaced leaves."
);
continue;
}
debug!(
target: crate::LOG_TARGET,
?leaf_number,
?leaf_hash,
elapsed = ?now.elapsed(),
"Handle displaced leaf.",
);
// Collect all block hashes until the height of the finalized block
displaced_blocks_candidates.clear();
while current_header_metadata.number > finalized_block_number {
displaced_blocks_candidates.push(current_header_metadata.hash);
let parent_hash = current_header_metadata.parent;
match local_cache.get(&parent_hash) {
Some(metadata_header) => {
current_header_metadata = *metadata_header;
},
None => {
current_header_metadata = MinimalBlockMetadata::from(
&self.header_metadata(parent_hash).map_err(|err| {
debug!(
target: crate::LOG_TARGET,
?err,
?parent_hash,
?leaf_hash,
elapsed = ?now.elapsed(),
"Failed to fetch parent header during leaf tracking.",
);
err
})?,
);
// Cache locally in case more branches above finalized block reference
// the same block hash
local_cache.insert(parent_hash, current_header_metadata);
},
}
}
// If points back to the finalized header then nothing left to do, this leaf will be
// checked again later
if current_header_metadata.hash == finalized_block_hash {
debug!(
target: crate::LOG_TARGET,
?leaf_hash,
elapsed = ?now.elapsed(),
"Leaf points to the finalized header, skipping for now.",
);
continue;
}
// We reuse `displaced_blocks_candidates` to store the current metadata.
// This block is not displaced if there is a gap in the ancestry. We
// check for this gap later.
displaced_blocks_candidates.push(current_header_metadata.hash);
debug!(
target: crate::LOG_TARGET,
current_hash = ?current_header_metadata.hash,
current_num = ?current_header_metadata.number,
?finalized_block_number,
elapsed = ?now.elapsed(),
"Looking for path from finalized block number to current leaf number"
);
// Collect the rest of the displaced blocks of leaf branch
for distance_from_finalized in 1_u32.. {
// Find block at `distance_from_finalized` from finalized block
let (finalized_chain_block_number, finalized_chain_block_hash) =
match finalized_chain.iter().rev().nth(distance_from_finalized as usize) {
Some(header) => (header.number, header.hash),
None => {
let to_fetch = finalized_chain.front().expect("Not empty; qed");
let metadata = match self.header_metadata(to_fetch.parent) {
Ok(metadata) => metadata,
Err(Error::UnknownBlock(_)) => {
debug!(
target: crate::LOG_TARGET,
distance_from_finalized,
hash = ?to_fetch.parent,
number = ?to_fetch.number,
elapsed = ?now.elapsed(),
"Tried to fetch unknown block, block ancestry has gaps."
);
break;
},
Err(err) => {
debug!(
target: crate::LOG_TARGET,
hash = ?to_fetch.parent,
number = ?to_fetch.number,
?err,
elapsed = ?now.elapsed(),
"Failed to fetch header for parent hash.",
);
return Err(err);
},
};
let metadata = MinimalBlockMetadata::from(&metadata);
let result = (metadata.number, metadata.hash);
finalized_chain.push_front(metadata);
result
},
};
if current_header_metadata.hash == finalized_chain_block_hash {
// Found the block on the finalized chain, nothing left to do
result.displaced_leaves.push((leaf_number, leaf_hash));
debug!(
target: crate::LOG_TARGET,
?leaf_hash,
elapsed = ?now.elapsed(),
"Leaf is ancestor of finalized block."
);
break;
}
if current_header_metadata.number <= finalized_chain_block_number {
// Skip more blocks until we get all blocks on finalized chain until the height
// of the parent block
continue;
}
let parent_hash = current_header_metadata.parent;
if finalized_chain_block_hash == parent_hash {
// Reached finalized chain, nothing left to do
result.displaced_blocks.extend(displaced_blocks_candidates.drain(..));
result.displaced_leaves.push((leaf_number, leaf_hash));
debug!(
target: crate::LOG_TARGET,
?leaf_hash,
elapsed = ?now.elapsed(),
"Found displaced leaf."
);
break;
}
// Store displaced block and look deeper for block on finalized chain
debug!(
target: crate::LOG_TARGET,
?parent_hash,
elapsed = ?now.elapsed(),
"Found displaced block. Looking further.",
);
displaced_blocks_candidates.push(parent_hash);
current_header_metadata = MinimalBlockMetadata::from(
&self.header_metadata(parent_hash).map_err(|err| {
debug!(
target: crate::LOG_TARGET,
?err,
?parent_hash,
elapsed = ?now.elapsed(),
"Failed to fetch header for parent during displaced block collection",
);
err
})?,
);
}
}
// There could be duplicates shared by multiple branches, clean them up
result.displaced_blocks.sort_unstable();
result.displaced_blocks.dedup();
debug!(
target: crate::LOG_TARGET,
%finalized_block_hash,
?finalized_block_number,
?result,
elapsed = ?now.elapsed(),
"Finished checking for displaced leaves after finalization.",
);
return Ok(result);
}
}
/// Result of [`Backend::displaced_leaves_after_finalizing`].
#[derive(Clone, Debug)]
pub struct DisplacedLeavesAfterFinalization<Block: BlockT> {
/// A list of hashes and block numbers of displaced leaves.
pub displaced_leaves: Vec<(NumberFor<Block>, Block::Hash)>,
/// A list of hashes displaced blocks from all displaced leaves.
pub displaced_blocks: Vec<Block::Hash>,
}
impl<Block: BlockT> Default for DisplacedLeavesAfterFinalization<Block> {
fn default() -> Self {
Self { displaced_leaves: Vec::new(), displaced_blocks: Vec::new() }
}
}
impl<Block: BlockT> DisplacedLeavesAfterFinalization<Block> {
/// Returns a collection of hashes for the displaced leaves.
pub fn hashes(&self) -> impl Iterator<Item = Block::Hash> + '_ {
self.displaced_leaves.iter().map(|(_, hash)| *hash)
}
}
/// Represents the type of block gaps that may result from either warp sync or fast sync.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)]
pub enum BlockGapType {
/// Both the header and body are missing, as a result of warp sync.
MissingHeaderAndBody,
/// The block body is missing, as a result of fast sync.
MissingBody,
}
/// Represents the block gap resulted by warp sync or fast sync.
///
/// A block gap is a range of blocks where either the bodies, or both headers and bodies are
/// missing.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)]
pub struct BlockGap<N> {
/// The starting block number of the gap (inclusive).
pub start: N,
/// The ending block number of the gap (inclusive).
pub end: N,
/// The type of gap.
pub gap_type: BlockGapType,
}
/// Blockchain info
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Info<Block: BlockT> {
/// Best block hash.
pub best_hash: Block::Hash,
/// Best block number.
pub best_number: <<Block as BlockT>::Header as HeaderT>::Number,
/// Genesis block hash.
pub genesis_hash: Block::Hash,
/// The head of the finalized chain.
pub finalized_hash: Block::Hash,
/// Last finalized block number.
pub finalized_number: <<Block as BlockT>::Header as HeaderT>::Number,
/// Last finalized state.
pub finalized_state: Option<(Block::Hash, <<Block as BlockT>::Header as HeaderT>::Number)>,
/// Number of concurrent leave forks.
pub number_leaves: usize,
/// Missing blocks after warp sync or fast sync.
pub block_gap: Option<BlockGap<NumberFor<Block>>>,
}
/// Block status.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockStatus {
/// Already in the blockchain.
InChain,
/// Not in the queue or the blockchain.
Unknown,
}
@@ -0,0 +1,227 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Bizinikiwi client possible errors.
use codec::Error as CodecError;
use pezsp_api::ApiError;
use pezsp_consensus;
use pezsp_runtime::transaction_validity::TransactionValidityError;
use pezsp_state_machine;
use std::{self, result};
/// Client Result type alias
pub type Result<T> = result::Result<T, Error>;
/// Error when the runtime failed to apply an extrinsic.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum ApplyExtrinsicFailed {
/// The transaction cannot be included into the current block.
///
/// This doesn't necessary mean that the transaction itself is invalid, but it might be just
/// unapplicable onto the current block.
#[error("Extrinsic is not valid: {0:?}")]
Validity(#[from] TransactionValidityError),
#[error("Application specific error")]
Application(#[source] Box<dyn 'static + std::error::Error + Send + Sync>),
}
/// Bizinikiwi Client error
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Error {
#[error("Cancelled oneshot channel {0}")]
OneShotCancelled(#[from] futures::channel::oneshot::Canceled),
#[error(transparent)]
Consensus(#[from] pezsp_consensus::Error),
#[error("Backend error: {0}")]
Backend(String),
#[error("UnknownBlock: {0}")]
UnknownBlock(String),
#[error("UnknownBlocks: {0}")]
UnknownBlocks(String),
#[error(transparent)]
ApplyExtrinsicFailed(#[from] ApplyExtrinsicFailed),
#[error("Child type is invalid")]
InvalidChildType,
#[error("RemoteBodyRequest: invalid extrinsics root expected: {expected} but got {received}")]
ExtrinsicRootInvalid { received: String, expected: String },
// `inner` cannot be made member, since it lacks `std::error::Error` trait bounds.
#[error("Execution failed: {0}")]
Execution(Box<dyn pezsp_state_machine::Error>),
#[error("Blockchain")]
Blockchain(#[source] Box<Error>),
/// A error used by various storage subsystems.
///
/// Eventually this will be replaced.
#[error("{0}")]
StorageChanges(pezsp_state_machine::DefaultError),
#[error("Invalid child storage key")]
InvalidChildStorageKey,
#[error("Current state of blockchain has invalid authorities set")]
InvalidAuthoritiesSet,
#[error("Failed to get runtime version: {0}")]
VersionInvalid(String),
#[error("Provided state is invalid")]
InvalidState,
#[error("error decoding justification for header")]
JustificationDecode,
#[error("bad justification for header: {0}")]
BadJustification(String),
#[error("outdated justification")]
OutdatedJustification,
#[error("This method is not currently available when running in light client mode")]
NotAvailableOnLightClient,
#[error("Remote node has responded with invalid header proof")]
InvalidCHTProof,
#[error("Remote data fetch has been cancelled")]
RemoteFetchCancelled,
#[error("Remote data fetch has been failed")]
RemoteFetchFailed,
#[error("Error decoding call result of {0}")]
CallResultDecode(&'static str, #[source] CodecError),
#[error("Error at calling runtime api: {0}")]
RuntimeApiError(#[from] ApiError),
#[error("Runtime :code missing in storage")]
RuntimeCodeMissing,
#[error("Changes tries are not supported by the runtime")]
ChangesTriesNotSupported,
#[error("Error reading changes tries configuration")]
ErrorReadingChangesTriesConfig,
#[error("Failed to check changes proof: {0}")]
ChangesTrieAccessFailed(String),
#[error("Did not finalize blocks in sequential order.")]
NonSequentialFinalization(String),
#[error("Potential long-range attack: block not in finalized chain.")]
NotInFinalizedChain,
#[error("Failed to get hash of block for building CHT")]
MissingHashRequiredForCHT,
#[error("Calculated state root does not match.")]
InvalidStateRoot,
#[error("Incomplete block import pipeline.")]
IncompletePipeline,
#[error("Transaction pool not ready for block production.")]
TransactionPoolNotReady,
#[error("Database error: {0}")]
DatabaseError(#[from] pezsp_database::error::DatabaseError),
#[error("Failed to get header for hash {0}")]
MissingHeader(String),
#[error("State Database error: {0}")]
StateDatabase(String),
#[error("Statement store error: {0}")]
StatementStore(String),
#[error("Failed to set the chain head to a block that's too old.")]
SetHeadTooOld,
#[error(transparent)]
Application(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
// Should be removed/improved once
// the storage `fn`s returns typed errors.
#[error("Runtime code error: {0}")]
RuntimeCode(&'static str),
// Should be removed/improved once
// the storage `fn`s returns typed errors.
#[error("Storage error: {0}")]
Storage(String),
}
impl From<Box<dyn pezsp_state_machine::Error + Send + Sync + 'static>> for Error {
fn from(e: Box<dyn pezsp_state_machine::Error + Send + Sync + 'static>) -> Self {
Self::from_state(e)
}
}
impl From<Box<dyn pezsp_state_machine::Error>> for Error {
fn from(e: Box<dyn pezsp_state_machine::Error>) -> Self {
Self::from_state(e)
}
}
impl From<Error> for ApiError {
fn from(err: Error) -> ApiError {
match err {
Error::UnknownBlock(msg) => ApiError::UnknownBlock(msg),
Error::RuntimeApiError(err) => err,
e => ApiError::Application(Box::new(e)),
}
}
}
impl Error {
/// Chain a blockchain error.
pub fn from_blockchain(e: Box<Error>) -> Self {
Error::Blockchain(e)
}
/// Chain a state error.
pub fn from_state(e: Box<dyn pezsp_state_machine::Error>) -> Self {
Error::Execution(e)
}
/// Construct from a state db error.
// Can not be done directly, since that would make cargo run out of stack if
// `sc-state-db` is lib is added as dependency.
pub fn from_state_db<E>(e: E) -> Self
where
E: std::fmt::Debug,
{
Error::StateDatabase(format!("{:?}", e))
}
}
@@ -0,0 +1,329 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Implements tree backend, cached header metadata and algorithms
//! to compute routes efficiently over the tree of headers.
use parking_lot::Mutex;
use schnellru::{ByLength, LruMap};
use pezsp_core::U256;
use pezsp_runtime::{
traits::{Block as BlockT, Header, NumberFor, One},
Saturating,
};
/// Set to the expected max difference between `best` and `finalized` blocks at sync.
pub(crate) const LRU_CACHE_SIZE: u32 = 5_000;
/// Get the lowest common ancestor between two blocks in the tree.
///
/// This implementation is efficient because our trees have very few and
/// small branches, and because of our current query pattern:
/// lca(best, final), lca(best + 1, final), lca(best + 2, final), etc.
/// The first call is O(h) but the others are O(1).
pub fn lowest_common_ancestor<Block: BlockT, T: HeaderMetadata<Block> + ?Sized>(
backend: &T,
id_one: Block::Hash,
id_two: Block::Hash,
) -> Result<HashAndNumber<Block>, T::Error> {
let mut header_one = backend.header_metadata(id_one)?;
if header_one.parent == id_two {
return Ok(HashAndNumber { hash: id_two, number: header_one.number - One::one() });
}
let mut header_two = backend.header_metadata(id_two)?;
if header_two.parent == id_one {
return Ok(HashAndNumber { hash: id_one, number: header_one.number });
}
let mut orig_header_one = header_one.clone();
let mut orig_header_two = header_two.clone();
// We move through ancestor links as much as possible, since ancestor >= parent.
while header_one.number > header_two.number {
let ancestor_one = backend.header_metadata(header_one.ancestor)?;
if ancestor_one.number >= header_two.number {
header_one = ancestor_one;
} else {
break;
}
}
while header_one.number < header_two.number {
let ancestor_two = backend.header_metadata(header_two.ancestor)?;
if ancestor_two.number >= header_one.number {
header_two = ancestor_two;
} else {
break;
}
}
// Then we move the remaining path using parent links.
while header_one.hash != header_two.hash {
if header_one.number > header_two.number {
header_one = backend.header_metadata(header_one.parent)?;
} else {
header_two = backend.header_metadata(header_two.parent)?;
}
}
// Update cached ancestor links.
if orig_header_one.number > header_one.number {
orig_header_one.ancestor = header_one.hash;
backend.insert_header_metadata(orig_header_one.hash, orig_header_one);
}
if orig_header_two.number > header_one.number {
orig_header_two.ancestor = header_one.hash;
backend.insert_header_metadata(orig_header_two.hash, orig_header_two);
}
Ok(HashAndNumber { hash: header_one.hash, number: header_one.number })
}
/// Compute a tree-route between two blocks. See tree-route docs for more details.
pub fn tree_route<Block: BlockT, T: HeaderMetadata<Block> + ?Sized>(
backend: &T,
from: Block::Hash,
to: Block::Hash,
) -> Result<TreeRoute<Block>, T::Error> {
let mut from = backend.header_metadata(from)?;
let mut to = backend.header_metadata(to)?;
let mut to_branch =
Vec::with_capacity(Into::<U256>::into(to.number.saturating_sub(from.number)).as_usize());
while to.number > from.number {
to_branch.push(HashAndNumber { number: to.number, hash: to.hash });
to = backend.header_metadata(to.parent)?;
}
let mut from_branch =
Vec::with_capacity(Into::<U256>::into(to.number.saturating_sub(from.number)).as_usize());
while from.number > to.number {
from_branch.push(HashAndNumber { number: from.number, hash: from.hash });
from = backend.header_metadata(from.parent)?;
}
// numbers are equal now. walk backwards until the block is the same
while to.hash != from.hash {
to_branch.push(HashAndNumber { number: to.number, hash: to.hash });
to = backend.header_metadata(to.parent)?;
from_branch.push(HashAndNumber { number: from.number, hash: from.hash });
from = backend.header_metadata(from.parent)?;
}
// add the pivot block. and append the reversed to-branch
// (note that it's reverse order originals)
let pivot = from_branch.len();
from_branch.reserve_exact(to_branch.len() + 1);
from_branch.push(HashAndNumber { number: to.number, hash: to.hash });
from_branch.extend(to_branch.into_iter().rev());
Ok(TreeRoute { route: from_branch, pivot })
}
/// Hash and number of a block.
#[derive(Debug, Clone)]
pub struct HashAndNumber<Block: BlockT> {
/// The number of the block.
pub number: NumberFor<Block>,
/// The hash of the block.
pub hash: Block::Hash,
}
impl<Block: BlockT> Eq for HashAndNumber<Block> {}
impl<Block: BlockT> PartialEq for HashAndNumber<Block> {
fn eq(&self, other: &Self) -> bool {
self.number.eq(&other.number) && self.hash.eq(&other.hash)
}
}
impl<Block: BlockT> Ord for HashAndNumber<Block> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.number.cmp(&other.number) {
std::cmp::Ordering::Equal => self.hash.cmp(&other.hash),
result => result,
}
}
}
impl<Block: BlockT> PartialOrd for HashAndNumber<Block> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
/// A tree-route from one block to another in the chain.
///
/// All blocks prior to the pivot in the vector is the reverse-order unique ancestry
/// of the first block, the block at the pivot index is the common ancestor,
/// and all blocks after the pivot is the ancestry of the second block, in
/// order.
///
/// The ancestry sets will include the given blocks, and thus the tree-route is
/// never empty.
///
/// ```text
/// Tree route from R1 to E2. Retracted is [R1, R2, R3], Common is C, enacted [E1, E2]
/// <- R3 <- R2 <- R1
/// /
/// C
/// \-> E1 -> E2
/// ```
///
/// ```text
/// Tree route from C to E2. Retracted empty. Common is C, enacted [E1, E2]
/// C -> E1 -> E2
/// ```
#[derive(Debug, Clone)]
pub struct TreeRoute<Block: BlockT> {
route: Vec<HashAndNumber<Block>>,
pivot: usize,
}
impl<Block: BlockT> TreeRoute<Block> {
/// Creates a new `TreeRoute`.
///
/// To preserve the structure safety invariants it is required that `pivot < route.len()`.
pub fn new(route: Vec<HashAndNumber<Block>>, pivot: usize) -> Result<Self, String> {
if pivot < route.len() {
Ok(TreeRoute { route, pivot })
} else {
Err(format!(
"TreeRoute pivot ({}) should be less than route length ({})",
pivot,
route.len()
))
}
}
/// Get a slice of all retracted blocks in reverse order (towards common ancestor).
pub fn retracted(&self) -> &[HashAndNumber<Block>] {
&self.route[..self.pivot]
}
/// Convert into all retracted blocks in reverse order (towards common ancestor).
pub fn into_retracted(mut self) -> Vec<HashAndNumber<Block>> {
self.route.truncate(self.pivot);
self.route
}
/// Get the common ancestor block. This might be one of the two blocks of the
/// route.
pub fn common_block(&self) -> &HashAndNumber<Block> {
self.route.get(self.pivot).expect(
"tree-routes are computed between blocks; \
which are included in the route; \
thus it is never empty; qed",
)
}
/// Get a slice of enacted blocks (descendants of the common ancestor)
pub fn enacted(&self) -> &[HashAndNumber<Block>] {
&self.route[self.pivot + 1..]
}
/// Returns the last block.
pub fn last(&self) -> Option<&HashAndNumber<Block>> {
self.route.last()
}
}
/// Handles header metadata: hash, number, parent hash, etc.
pub trait HeaderMetadata<Block: BlockT> {
/// Error used in case the header metadata is not found.
type Error: std::error::Error;
fn header_metadata(
&self,
hash: Block::Hash,
) -> Result<CachedHeaderMetadata<Block>, Self::Error>;
fn insert_header_metadata(
&self,
hash: Block::Hash,
header_metadata: CachedHeaderMetadata<Block>,
);
fn remove_header_metadata(&self, hash: Block::Hash);
}
/// Caches header metadata in an in-memory LRU cache.
pub struct HeaderMetadataCache<Block: BlockT> {
cache: Mutex<LruMap<Block::Hash, CachedHeaderMetadata<Block>>>,
}
impl<Block: BlockT> HeaderMetadataCache<Block> {
/// Creates a new LRU header metadata cache with `capacity`.
pub fn new(capacity: u32) -> Self {
HeaderMetadataCache { cache: Mutex::new(LruMap::new(ByLength::new(capacity))) }
}
}
impl<Block: BlockT> Default for HeaderMetadataCache<Block> {
fn default() -> Self {
Self::new(LRU_CACHE_SIZE)
}
}
impl<Block: BlockT> HeaderMetadataCache<Block> {
pub fn header_metadata(&self, hash: Block::Hash) -> Option<CachedHeaderMetadata<Block>> {
self.cache.lock().get(&hash).cloned()
}
pub fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata<Block>) {
self.cache.lock().insert(hash, metadata);
}
pub fn remove_header_metadata(&self, hash: Block::Hash) {
self.cache.lock().remove(&hash);
}
}
/// Cached header metadata. Used to efficiently traverse the tree.
#[derive(Debug, Clone)]
pub struct CachedHeaderMetadata<Block: BlockT> {
/// Hash of the header.
pub hash: Block::Hash,
/// Block number.
pub number: NumberFor<Block>,
/// Hash of parent header.
pub parent: Block::Hash,
/// Block state root.
pub state_root: Block::Hash,
/// Hash of an ancestor header. Used to jump through the tree.
ancestor: Block::Hash,
}
impl<Block: BlockT> From<&Block::Header> for CachedHeaderMetadata<Block> {
fn from(header: &Block::Header) -> Self {
CachedHeaderMetadata {
hash: header.hash(),
number: *header.number(),
parent: *header.parent_hash(),
state_root: *header.state_root(),
ancestor: *header.parent_hash(),
}
}
}
@@ -0,0 +1,28 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Bizinikiwi blockchain traits and primitives.
mod backend;
mod error;
mod header_metadata;
pub use backend::*;
pub use error::*;
pub use header_metadata::*;
const LOG_TARGET: &str = "db::blockchain";