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,82 @@
[package]
name = "pezsc-consensus-manual-seal"
version = "0.35.0"
authors.workspace = true
description = "Manual sealing engine for Bizinikiwi"
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
assert_matches = { workspace = true }
async-trait = { workspace = true }
codec = { workspace = true, default-features = true }
futures = { workspace = true }
futures-timer = { workspace = true }
jsonrpsee = { features = [
"client-core",
"macros",
"server-core",
], workspace = true }
log = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-consensus = { workspace = true, default-features = true }
pezsc-consensus-aura = { workspace = true, default-features = true }
pezsc-consensus-babe = { workspace = true, default-features = true }
pezsc-consensus-epochs = { workspace = true, default-features = true }
pezsc-transaction-pool = { workspace = true, default-features = true }
pezsc-transaction-pool-api = { workspace = true, default-features = true }
serde = { features = ["derive"], workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-consensus-aura = { workspace = true, default-features = true }
pezsp-consensus-babe = { workspace = true, default-features = true }
pezsp-consensus-slots = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-inherents = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-timestamp = { workspace = true, default-features = true }
thiserror = { workspace = true }
[dev-dependencies]
pezsc-basic-authorship = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
bizinikiwi-test-runtime-transaction-pool = { workspace = true }
tokio = { features = [
"macros",
"rt-multi-thread",
], workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsc-basic-authorship/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-consensus-aura/runtime-benchmarks",
"pezsc-consensus-babe/runtime-benchmarks",
"pezsc-consensus-epochs/runtime-benchmarks",
"pezsc-consensus/runtime-benchmarks",
"pezsc-transaction-pool-api/runtime-benchmarks",
"pezsc-transaction-pool/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus-aura/runtime-benchmarks",
"pezsp-consensus-babe/runtime-benchmarks",
"pezsp-consensus-slots/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-inherents/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-timestamp/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
"bizinikiwi-test-runtime-transaction-pool/runtime-benchmarks",
]
@@ -0,0 +1,4 @@
A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
This is suitable for a testing environment.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,47 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Extensions for manual seal to produce blocks valid for any runtime.
use super::Error;
use pezsc_consensus::BlockImportParams;
use pezsp_inherents::InherentData;
use pezsp_runtime::{traits::Block as BlockT, Digest};
pub mod aura;
pub mod babe;
pub mod timestamp;
/// Consensus data provider, manual seal uses this trait object for authoring blocks valid
/// for any runtime.
pub trait ConsensusDataProvider<B: BlockT>: Send + Sync {
/// The proof type.
type Proof;
/// Attempt to create a consensus digest.
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<Digest, Error>;
/// Set up the necessary import params.
fn append_block_import(
&self,
parent: &B::Header,
params: &mut BlockImportParams<B>,
inherents: &InherentData,
proof: Self::Proof,
) -> Result<(), Error>;
}
@@ -0,0 +1,102 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Aura consensus data provider, This allows manual seal author blocks that are valid for
//! runtimes that expect the aura-specific digests.
use crate::{ConsensusDataProvider, Error};
use pezsc_client_api::{AuxStore, UsageProvider};
use pezsc_consensus::BlockImportParams;
use pezsp_api::ProvideRuntimeApi;
use pezsp_consensus_aura::{
digests::CompatibleDigestItem,
sr25519::{AuthorityId, AuthoritySignature},
AuraApi, Slot, SlotDuration,
};
use pezsp_inherents::InherentData;
use pezsp_runtime::{traits::Block as BlockT, Digest, DigestItem};
use pezsp_timestamp::TimestampInherentData;
use std::{marker::PhantomData, sync::Arc};
/// Consensus data provider for Aura. This allows to use manual-seal driven nodes to author valid
/// AURA blocks. It will inspect incoming [`InherentData`] and look for included timestamps. Based
/// on these timestamps, the [`AuraConsensusDataProvider`] will emit fitting digest items.
pub struct AuraConsensusDataProvider<B, P> {
// slot duration
slot_duration: SlotDuration,
// phantom data for required generics
_phantom: PhantomData<(B, P)>,
}
impl<B, P> AuraConsensusDataProvider<B, P>
where
B: BlockT,
{
/// Creates a new instance of the [`AuraConsensusDataProvider`], requires that `client`
/// implements [`pezsp_consensus_aura::AuraApi`]
pub fn new<C>(client: Arc<C>) -> Self
where
C: AuxStore + ProvideRuntimeApi<B> + UsageProvider<B>,
C::Api: AuraApi<B, AuthorityId>,
{
let slot_duration = pezsc_consensus_aura::slot_duration(&*client)
.expect("slot_duration is always present; qed.");
Self { slot_duration, _phantom: PhantomData }
}
/// Creates a new instance of the [`AuraConsensusDataProvider`]
pub fn new_with_slot_duration(slot_duration: SlotDuration) -> Self {
Self { slot_duration, _phantom: PhantomData }
}
}
impl<B, P> ConsensusDataProvider<B> for AuraConsensusDataProvider<B, P>
where
B: BlockT,
P: Send + Sync,
{
type Proof = P;
fn create_digest(
&self,
_parent: &B::Header,
inherents: &InherentData,
) -> Result<Digest, Error> {
let timestamp =
inherents.timestamp_inherent_data()?.expect("Timestamp is always present; qed");
// we always calculate the new slot number based on the current time-stamp and the slot
// duration.
let digest_item = <DigestItem as CompatibleDigestItem<AuthoritySignature>>::aura_pre_digest(
Slot::from_timestamp(timestamp, self.slot_duration),
);
Ok(Digest { logs: vec![digest_item] })
}
fn append_block_import(
&self,
_parent: &B::Header,
_params: &mut BlockImportParams<B>,
_inherents: &InherentData,
_proof: Self::Proof,
) -> Result<(), Error> {
Ok(())
}
}
@@ -0,0 +1,318 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! BABE consensus data provider, This allows manual seal author blocks that are valid for runtimes
//! that expect babe-specific digests.
use super::ConsensusDataProvider;
use crate::{Error, LOG_TARGET};
use codec::Encode;
use pezsc_client_api::{AuxStore, UsageProvider};
use pezsc_consensus_babe::{
authorship, find_pre_digest, BabeIntermediate, CompatibleDigestItem, Epoch, INTERMEDIATE_KEY,
};
use pezsc_consensus_epochs::{
descendent_query, EpochHeader, SharedEpochChanges, ViableEpochDescriptor,
};
use pezsp_keystore::KeystorePtr;
use std::{marker::PhantomData, sync::Arc};
use pezsc_consensus::{BlockImportParams, ForkChoiceStrategy, Verifier};
use pezsp_api::ProvideRuntimeApi;
use pezsp_blockchain::{HeaderBackend, HeaderMetadata};
use pezsp_consensus_babe::{
digests::{NextEpochDescriptor, PreDigest, SecondaryPlainPreDigest},
inherents::BabeInherentData,
AuthorityId, BabeApi, BabeAuthorityWeight, BabeConfiguration, ConsensusLog, BABE_ENGINE_ID,
};
use pezsp_consensus_slots::Slot;
use pezsp_inherents::InherentData;
use pezsp_runtime::{
generic::Digest,
traits::{Block as BlockT, Header},
DigestItem,
};
use pezsp_timestamp::TimestampInherentData;
/// Provides BABE-compatible predigests and BlockImportParams.
/// Intended for use with BABE runtimes.
pub struct BabeConsensusDataProvider<B: BlockT, C, P> {
/// shared reference to keystore
keystore: KeystorePtr,
/// Shared reference to the client.
client: Arc<C>,
/// Shared epoch changes
epoch_changes: SharedEpochChanges<B, Epoch>,
/// BABE config, gotten from the runtime.
/// NOTE: This is used to fetch `slot_duration` and `epoch_length` in the
/// `ConsensusDataProvider` implementation. Correct as far as these values
/// are not changed during an epoch change.
config: BabeConfiguration,
/// Authorities to be used for this babe chain.
authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
_phantom: PhantomData<P>,
}
/// Verifier to be used for babe chains
pub struct BabeVerifier<B: BlockT, C> {
/// Shared epoch changes
epoch_changes: SharedEpochChanges<B, Epoch>,
/// Shared reference to the client.
client: Arc<C>,
}
impl<B: BlockT, C> BabeVerifier<B, C> {
/// create a new verifier
pub fn new(epoch_changes: SharedEpochChanges<B, Epoch>, client: Arc<C>) -> BabeVerifier<B, C> {
BabeVerifier { epoch_changes, client }
}
}
/// The verifier for the manual seal engine; instantly finalizes.
#[async_trait::async_trait]
impl<B, C> Verifier<B> for BabeVerifier<B, C>
where
B: BlockT,
C: HeaderBackend<B> + HeaderMetadata<B, Error = pezsp_blockchain::Error>,
{
async fn verify(
&self,
mut import_params: BlockImportParams<B>,
) -> Result<BlockImportParams<B>, String> {
import_params.finalized = false;
import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
let pre_digest = find_pre_digest::<B>(&import_params.header)?;
let parent_hash = import_params.header.parent_hash();
let parent = self
.client
.header(*parent_hash)
.ok()
.flatten()
.ok_or_else(|| format!("header for block {} not found", parent_hash))?;
let epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
*parent.number(),
pre_digest.slot(),
)
.map_err(|e| format!("failed to fetch epoch_descriptor: {}", e))?
.ok_or_else(|| format!("{}", pezsp_consensus::Error::InvalidAuthoritiesSet))?;
// drop the lock
drop(epoch_changes);
import_params
.insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate::<B> { epoch_descriptor });
Ok(import_params)
}
}
impl<B, C, P> BabeConsensusDataProvider<B, C, P>
where
B: BlockT,
C: AuxStore
+ HeaderBackend<B>
+ ProvideRuntimeApi<B>
+ HeaderMetadata<B, Error = pezsp_blockchain::Error>
+ UsageProvider<B>,
C::Api: BabeApi<B>,
{
pub fn new(
client: Arc<C>,
keystore: KeystorePtr,
epoch_changes: SharedEpochChanges<B, Epoch>,
authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
) -> Result<Self, Error> {
if authorities.is_empty() {
return Err(Error::StringError("Cannot supply empty authority set!".into()));
}
let config = pezsc_consensus_babe::configuration(&*client)?;
Ok(Self {
config,
client,
keystore,
epoch_changes,
authorities,
_phantom: Default::default(),
})
}
fn epoch(&self, parent: &B::Header, slot: Slot) -> Result<Epoch, Error> {
let epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
*parent.number(),
slot,
)
.map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))?
.ok_or(pezsp_consensus::Error::InvalidAuthoritiesSet)?;
let epoch = epoch_changes
.viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))
.ok_or_else(|| {
log::info!(target: LOG_TARGET, "create_digest: no viable_epoch :(");
pezsp_consensus::Error::InvalidAuthoritiesSet
})?;
Ok(epoch.as_ref().clone())
}
}
impl<B, C, P> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C, P>
where
B: BlockT,
C: AuxStore
+ HeaderBackend<B>
+ HeaderMetadata<B, Error = pezsp_blockchain::Error>
+ UsageProvider<B>
+ ProvideRuntimeApi<B>,
C::Api: BabeApi<B>,
P: Send + Sync,
{
type Proof = P;
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<Digest, Error> {
let slot = inherents
.babe_inherent_data()?
.ok_or_else(|| Error::StringError("No babe inherent data".into()))?;
let epoch = self.epoch(parent, slot)?;
// this is a dev node environment, we should always be able to claim a slot.
let logs = if let Some((predigest, _)) =
authorship::claim_slot(slot, &epoch, &self.keystore)
{
vec![<DigestItem as CompatibleDigestItem>::babe_pre_digest(predigest)]
} else {
// well we couldn't claim a slot because this is an existing chain and we're not in the
// authorities. we need to tell BabeBlockImport that the epoch has changed, and we put
// ourselves in the authorities.
let predigest =
PreDigest::SecondaryPlain(SecondaryPlainPreDigest { slot, authority_index: 0_u32 });
let mut epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
*parent.number(),
slot,
)
.map_err(|e| {
Error::StringError(format!("failed to fetch epoch_descriptor: {}", e))
})?
.ok_or(pezsp_consensus::Error::InvalidAuthoritiesSet)?;
match epoch_descriptor {
ViableEpochDescriptor::Signaled(identifier, _epoch_header) => {
let epoch_mut = epoch_changes
.epoch_mut(&identifier)
.ok_or(pezsp_consensus::Error::InvalidAuthoritiesSet)?;
// mutate the current epoch
epoch_mut.authorities = self.authorities.clone();
let next_epoch = ConsensusLog::NextEpochData(NextEpochDescriptor {
authorities: self.authorities.clone(),
// copy the old randomness
randomness: epoch_mut.randomness,
});
vec![
DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
DigestItem::Consensus(BABE_ENGINE_ID, next_epoch.encode()),
]
},
ViableEpochDescriptor::UnimportedGenesis(_) => {
// since this is the genesis, secondary predigest works for now.
vec![DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode())]
},
}
};
Ok(Digest { logs })
}
fn append_block_import(
&self,
parent: &B::Header,
params: &mut BlockImportParams<B>,
inherents: &InherentData,
_proof: Self::Proof,
) -> Result<(), Error> {
let slot = inherents
.babe_inherent_data()?
.ok_or_else(|| Error::StringError("No babe inherent data".into()))?;
let epoch_changes = self.epoch_changes.shared_data();
let mut epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
*parent.number(),
slot,
)
.map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))?
.ok_or(pezsp_consensus::Error::InvalidAuthoritiesSet)?;
// drop the lock
drop(epoch_changes);
// a quick check to see if we're in the authorities
let epoch = self.epoch(parent, slot)?;
let (authority, _) = self.authorities.first().expect("authorities is non-emptyp; qed");
let has_authority = epoch.authorities.iter().any(|(id, _)| *id == *authority);
if !has_authority {
log::info!(target: LOG_TARGET, "authority not found");
let timestamp = inherents
.timestamp_inherent_data()?
.ok_or_else(|| Error::StringError("No timestamp inherent data".into()))?;
let slot = Slot::from_timestamp(timestamp, self.config.slot_duration());
// manually hard code epoch descriptor
epoch_descriptor = match epoch_descriptor {
ViableEpochDescriptor::Signaled(identifier, _header) =>
ViableEpochDescriptor::Signaled(
identifier,
EpochHeader {
start_slot: slot,
end_slot: (*slot * self.config.epoch_length).into(),
},
),
_ => unreachable!(
"we're not in the authorities, so this isn't the genesis epoch; qed"
),
};
}
params.insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate::<B> { epoch_descriptor });
Ok(())
}
}
@@ -0,0 +1,161 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Mocked timestamp inherent, allows for manual seal to create blocks for runtimes
//! that expect this inherent.
use crate::Error;
use pezsc_client_api::{AuxStore, UsageProvider};
use pezsp_api::ProvideRuntimeApi;
use pezsp_blockchain::HeaderBackend;
use pezsp_consensus_aura::{
sr25519::{AuthorityId, AuthoritySignature},
AuraApi,
};
use pezsp_consensus_babe::BabeApi;
use pezsp_consensus_slots::{Slot, SlotDuration};
use pezsp_inherents::{InherentData, InherentDataProvider, InherentIdentifier};
use pezsp_runtime::traits::{Block as BlockT, Zero};
use pezsp_timestamp::{InherentType, INHERENT_IDENTIFIER};
use std::{
sync::{atomic, Arc},
time::SystemTime,
};
/// Provide duration since unix epoch in millisecond for timestamp inherent.
/// Mocks the timestamp inherent to always produce a valid timestamp for the next slot.
///
/// This works by either fetching the `slot_number` from the most recent header and dividing
/// that value by `slot_duration` in order to fork chains that expect this inherent.
///
/// It produces timestamp inherents that are increased by `slot_duration` whenever
/// `provide_inherent_data` is called.
pub struct SlotTimestampProvider {
// holds the unix millisecond timestamp for the most recent block
unix_millis: atomic::AtomicU64,
// configured slot_duration in the runtime
slot_duration: SlotDuration,
}
impl SlotTimestampProvider {
/// Create a new mocked time stamp provider, for babe.
pub fn new_babe<B, C>(client: Arc<C>) -> Result<Self, Error>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B> + UsageProvider<B>,
C::Api: BabeApi<B>,
{
let slot_duration = pezsc_consensus_babe::configuration(&*client)?.slot_duration();
let time = Self::with_header(&client, slot_duration, |header| {
let slot_number = *pezsc_consensus_babe::find_pre_digest::<B>(&header)
.map_err(|err| format!("{}", err))?
.slot();
Ok(slot_number)
})?;
Ok(Self { unix_millis: atomic::AtomicU64::new(time), slot_duration })
}
/// Create a new mocked time stamp provider, for aura
pub fn new_aura<B, C>(client: Arc<C>) -> Result<Self, Error>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B> + UsageProvider<B>,
C::Api: AuraApi<B, AuthorityId>,
{
let slot_duration = pezsc_consensus_aura::slot_duration(&*client)?;
let time = Self::with_header(&client, slot_duration, |header| {
let slot_number = *pezsc_consensus_aura::find_pre_digest::<B, AuthoritySignature>(&header)
.map_err(|err| format!("{}", err))?;
Ok(slot_number)
})?;
Ok(Self { unix_millis: atomic::AtomicU64::new(time), slot_duration })
}
fn with_header<F, C, B>(
client: &Arc<C>,
slot_duration: SlotDuration,
func: F,
) -> Result<u64, Error>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + UsageProvider<B>,
F: Fn(B::Header) -> Result<u64, Error>,
{
let info = client.info();
// looks like this isn't the first block, rehydrate the fake time.
// otherwise we'd be producing blocks for older slots.
let time = if info.best_number != Zero::zero() {
let header = client
.header(info.best_hash)?
.ok_or_else(|| "best header not found in the db!".to_string())?;
let slot = func(header)?;
// add the slot duration so there's no collision of slots
(slot * slot_duration.as_millis() as u64) + slot_duration.as_millis() as u64
} else {
// this is the first block, use the correct time.
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|err| Error::StringError(format!("{}", err)))?
.as_millis() as u64
};
Ok(time)
}
/// Get the current slot number
pub fn slot(&self) -> Slot {
Slot::from_timestamp(
self.unix_millis.load(atomic::Ordering::SeqCst).into(),
self.slot_duration,
)
}
/// Gets the current time stamp.
pub fn timestamp(&self) -> pezsp_timestamp::Timestamp {
pezsp_timestamp::Timestamp::new(self.unix_millis.load(atomic::Ordering::SeqCst))
}
}
#[async_trait::async_trait]
impl InherentDataProvider for SlotTimestampProvider {
async fn provide_inherent_data(
&self,
inherent_data: &mut InherentData,
) -> Result<(), pezsp_inherents::Error> {
// we update the time here.
let new_time: InherentType = self
.unix_millis
.fetch_add(self.slot_duration.as_millis() as u64, atomic::Ordering::SeqCst)
.into();
inherent_data.put_data(INHERENT_IDENTIFIER, &new_time)?;
Ok(())
}
async fn try_handle_error(
&self,
_: &InherentIdentifier,
_: &[u8],
) -> Option<Result<(), pezsp_inherents::Error>> {
None
}
}
@@ -0,0 +1,110 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
//! This is suitable for a testing environment.
use futures::channel::{mpsc::SendError, oneshot};
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
use pezsc_consensus::ImportResult;
use pezsp_blockchain::Error as BlockchainError;
use pezsp_consensus::Error as ConsensusError;
use pezsp_inherents::Error as InherentsError;
/// Error code for rpc
mod codes {
pub const SERVER_SHUTTING_DOWN: i32 = 10_000;
pub const BLOCK_IMPORT_FAILED: i32 = 11_000;
pub const EMPTY_TRANSACTION_POOL: i32 = 12_000;
pub const BLOCK_NOT_FOUND: i32 = 13_000;
pub const CONSENSUS_ERROR: i32 = 14_000;
pub const INHERENTS_ERROR: i32 = 15_000;
pub const BLOCKCHAIN_ERROR: i32 = 16_000;
pub const UNKNOWN_ERROR: i32 = 20_000;
}
/// errors encountered by background block authorship task
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An error occurred while importing the block
#[error("Block import failed: {0:?}")]
BlockImportError(ImportResult),
/// Transaction pool is empty, cannot create a block
#[error(
"Transaction pool is empty, set create_empty to true, if you want to create empty blocks"
)]
EmptyTransactionPool,
/// encountered during creation of Proposer.
#[error("Consensus Error: {0}")]
ConsensusError(#[from] ConsensusError),
/// Failed to create Inherents data
#[error("Inherents Error: {0}")]
InherentError(#[from] InherentsError),
/// error encountered during finalization
#[error("Finalization Error: {0}")]
BlockchainError(#[from] BlockchainError),
/// Supplied parent_hash doesn't exist in chain
#[error("Supplied parent_hash: {0} doesn't exist in chain")]
BlockNotFound(String),
/// Some string error
#[error("{0}")]
StringError(String),
/// send error
#[error("Consensus process is terminating")]
Canceled(#[from] oneshot::Canceled),
/// send error
#[error("Consensus process is terminating")]
SendError(#[from] SendError),
/// Some other error.
#[error("Other error: {0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl From<ImportResult> for Error {
fn from(err: ImportResult) -> Self {
Error::BlockImportError(err)
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::StringError(s)
}
}
impl Error {
fn to_code(&self) -> i32 {
use Error::*;
match self {
BlockImportError(_) => codes::BLOCK_IMPORT_FAILED,
BlockNotFound(_) => codes::BLOCK_NOT_FOUND,
EmptyTransactionPool => codes::EMPTY_TRANSACTION_POOL,
ConsensusError(_) => codes::CONSENSUS_ERROR,
InherentError(_) => codes::INHERENTS_ERROR,
BlockchainError(_) => codes::BLOCKCHAIN_ERROR,
SendError(_) | Canceled(_) => codes::SERVER_SHUTTING_DOWN,
_ => codes::UNKNOWN_ERROR,
}
}
}
impl From<Error> for ErrorObjectOwned {
fn from(err: Error) -> Self {
ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)
}
}
@@ -0,0 +1,59 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Block finalization utilities
use crate::rpc;
use pezsc_client_api::backend::{Backend as ClientBackend, Finalizer};
use pezsp_runtime::{traits::Block as BlockT, Justification};
use std::{marker::PhantomData, sync::Arc};
/// params for block finalization.
pub struct FinalizeBlockParams<B: BlockT, F, CB> {
/// hash of the block
pub hash: <B as BlockT>::Hash,
/// sender to report errors/success to the rpc.
pub sender: rpc::Sender<()>,
/// finalization justification
pub justification: Option<Justification>,
/// Finalizer trait object.
pub finalizer: Arc<F>,
/// phantom type to pin the Backend type
pub _phantom: PhantomData<CB>,
}
/// finalizes a block in the backend with the given params.
pub async fn finalize_block<B, F, CB>(params: FinalizeBlockParams<B, F, CB>)
where
B: BlockT,
F: Finalizer<B, CB>,
CB: ClientBackend<B>,
{
let FinalizeBlockParams { hash, mut sender, justification, finalizer, .. } = params;
match finalizer.finalize_block(hash, justification, true) {
Err(e) => {
log::warn!("Failed to finalize block {}", e);
rpc::send_result(&mut sender, Err(e.into()))
},
Ok(()) => {
log::info!("✅ Successfully finalized block: {}", hash);
rpc::send_result(&mut sender, Ok(()))
},
}
}
@@ -0,0 +1,803 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
//! This is suitable for a testing environment.
use futures::prelude::*;
use futures_timer::Delay;
use prometheus_endpoint::Registry;
use pezsc_client_api::{
backend::{Backend as ClientBackend, Finalizer},
client::BlockchainEvents,
};
use pezsc_consensus::{
block_import::{BlockImport, BlockImportParams, ForkChoiceStrategy},
import_queue::{BasicQueue, BoxBlockImport, Verifier},
};
use pezsp_blockchain::HeaderBackend;
use pezsp_consensus::{Environment, Proposer, SelectChain};
use pezsp_core::traits::SpawnNamed;
use pezsp_inherents::CreateInherentDataProviders;
use pezsp_runtime::{traits::Block as BlockT, ConsensusEngineId};
use std::{marker::PhantomData, sync::Arc, time::Duration};
mod error;
mod finalize_block;
mod seal_block;
pub mod consensus;
pub mod rpc;
pub use self::{
consensus::ConsensusDataProvider,
error::Error,
finalize_block::{finalize_block, FinalizeBlockParams},
rpc::{CreatedBlock, EngineCommand},
seal_block::{seal_block, SealBlockParams, MAX_PROPOSAL_DURATION},
};
use pezsc_transaction_pool_api::TransactionPool;
use pezsp_api::ProvideRuntimeApi;
const LOG_TARGET: &str = "manual-seal";
/// The `ConsensusEngineId` of Manual Seal.
pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l'];
/// The verifier for the manual seal engine; instantly finalizes.
struct ManualSealVerifier;
#[async_trait::async_trait]
impl<B: BlockT> Verifier<B> for ManualSealVerifier {
async fn verify(
&self,
mut block: BlockImportParams<B>,
) -> Result<BlockImportParams<B>, String> {
block.finalized = false;
block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
Ok(block)
}
}
/// Instantiate the import queue for the manual seal consensus engine.
pub fn import_queue<Block>(
block_import: BoxBlockImport<Block>,
spawner: &impl pezsp_core::traits::SpawnEssentialNamed,
registry: Option<&Registry>,
) -> BasicQueue<Block>
where
Block: BlockT,
{
BasicQueue::new(ManualSealVerifier, block_import, None, spawner, registry)
}
/// Params required to start the manual sealing authorship task.
pub struct ManualSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC, CS, CIDP, P> {
/// Block import instance.
pub block_import: BI,
/// The environment we are producing blocks for.
pub env: E,
/// Client instance
pub client: Arc<C>,
/// Shared reference to the transaction pool.
pub pool: Arc<TP>,
/// Stream<Item = EngineCommands>, Basically the receiving end of a channel for sending
/// commands to the authorship task.
pub commands_stream: CS,
/// SelectChain strategy.
pub select_chain: SC,
/// Digest provider for inclusion in blocks.
pub consensus_data_provider: Option<Box<dyn ConsensusDataProvider<B, Proof = P>>>,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: CIDP,
}
/// Params required to start the instant sealing authorship task.
pub struct InstantSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC, CIDP, P> {
/// Block import instance for well. importing blocks.
pub block_import: BI,
/// The environment we are producing blocks for.
pub env: E,
/// Client instance
pub client: Arc<C>,
/// Shared reference to the transaction pool.
pub pool: Arc<TP>,
/// SelectChain strategy.
pub select_chain: SC,
/// Digest provider for inclusion in blocks.
pub consensus_data_provider: Option<Box<dyn ConsensusDataProvider<B, Proof = P>>>,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: CIDP,
}
/// Params required to start the delayed finalization task.
pub struct DelayedFinalizeParams<C, S> {
/// Block import instance.
pub client: Arc<C>,
/// Handle for spawning delayed finalization tasks.
pub spawn_handle: S,
/// The delay in seconds before a block is finalized.
pub delay_sec: u64,
}
/// Creates the background authorship task for the manually seal engine.
pub async fn run_manual_seal<B, BI, CB, E, C, TP, SC, CS, CIDP, P>(
ManualSealParams {
mut block_import,
mut env,
client,
pool,
mut commands_stream,
select_chain,
consensus_data_provider,
create_inherent_data_providers,
}: ManualSealParams<B, BI, E, C, TP, SC, CS, CIDP, P>,
) where
B: BlockT + 'static,
BI: BlockImport<B, Error = pezsp_consensus::Error> + Send + Sync + 'static,
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
CB: ClientBackend<B> + 'static,
E: Environment<B> + 'static,
E::Proposer: Proposer<B, Proof = P>,
CS: Stream<Item = EngineCommand<<B as BlockT>::Hash>> + Unpin + 'static,
SC: SelectChain<B> + 'static,
TP: TransactionPool<Block = B>,
CIDP: CreateInherentDataProviders<B, ()>,
P: codec::Encode + Send + Sync + 'static,
{
while let Some(command) = commands_stream.next().await {
match command {
EngineCommand::SealNewBlock { create_empty, finalize, parent_hash, sender } => {
seal_block(SealBlockParams {
sender,
parent_hash,
finalize,
create_empty,
env: &mut env,
select_chain: &select_chain,
block_import: &mut block_import,
consensus_data_provider: consensus_data_provider.as_deref(),
pool: pool.clone(),
client: client.clone(),
create_inherent_data_providers: &create_inherent_data_providers,
})
.await;
},
EngineCommand::FinalizeBlock { hash, sender, justification } => {
let justification = justification.map(|j| (MANUAL_SEAL_ENGINE_ID, j));
finalize_block(FinalizeBlockParams {
hash,
sender,
justification,
finalizer: client.clone(),
_phantom: PhantomData,
})
.await
},
}
}
}
/// runs the background authorship task for the instant seal engine.
/// instant-seal creates a new block for every transaction imported into
/// the transaction pool.
pub async fn run_instant_seal<B, BI, CB, E, C, TP, SC, CIDP, P>(
InstantSealParams {
block_import,
env,
client,
pool,
select_chain,
consensus_data_provider,
create_inherent_data_providers,
}: InstantSealParams<B, BI, E, C, TP, SC, CIDP, P>,
) where
B: BlockT + 'static,
BI: BlockImport<B, Error = pezsp_consensus::Error> + Send + Sync + 'static,
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
CB: ClientBackend<B> + 'static,
E: Environment<B> + 'static,
E::Proposer: Proposer<B, Proof = P>,
SC: SelectChain<B> + 'static,
TP: TransactionPool<Block = B>,
CIDP: CreateInherentDataProviders<B, ()>,
P: codec::Encode + Send + Sync + 'static,
{
// instant-seal creates blocks as soon as transactions are imported
// into the transaction pool.
let commands_stream = pool.import_notification_stream().map(|_| EngineCommand::SealNewBlock {
create_empty: true,
finalize: false,
parent_hash: None,
sender: None,
});
run_manual_seal(ManualSealParams {
block_import,
env,
client,
pool,
commands_stream,
select_chain,
consensus_data_provider,
create_inherent_data_providers,
})
.await
}
/// Runs the background authorship task for the instant seal engine.
/// instant-seal creates a new block for every transaction imported into
/// the transaction pool.
///
/// This function will finalize the block immediately as well. If you don't
/// want this behavior use `run_instant_seal` instead.
pub async fn run_instant_seal_and_finalize<B, BI, CB, E, C, TP, SC, CIDP, P>(
InstantSealParams {
block_import,
env,
client,
pool,
select_chain,
consensus_data_provider,
create_inherent_data_providers,
}: InstantSealParams<B, BI, E, C, TP, SC, CIDP, P>,
) where
B: BlockT + 'static,
BI: BlockImport<B, Error = pezsp_consensus::Error> + Send + Sync + 'static,
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
CB: ClientBackend<B> + 'static,
E: Environment<B> + 'static,
E::Proposer: Proposer<B, Proof = P>,
SC: SelectChain<B> + 'static,
TP: TransactionPool<Block = B>,
CIDP: CreateInherentDataProviders<B, ()>,
P: codec::Encode + Send + Sync + 'static,
{
// Creates and finalizes blocks as soon as transactions are imported
// into the transaction pool.
let commands_stream = pool.import_notification_stream().map(|_| EngineCommand::SealNewBlock {
create_empty: false,
finalize: true,
parent_hash: None,
sender: None,
});
run_manual_seal(ManualSealParams {
block_import,
env,
client,
pool,
commands_stream,
select_chain,
consensus_data_provider,
create_inherent_data_providers,
})
.await
}
/// Creates a future for delayed finalization of manual sealed blocks.
///
/// The future needs to be spawned in the background alongside the
/// [`run_manual_seal`]/[`run_instant_seal`] future. It is required that
/// [`EngineCommand::SealNewBlock`] is send with `finalize = false` to not finalize blocks directly
/// after building them. This also means that delayed finality can not be used with
/// [`run_instant_seal_and_finalize`].
pub async fn run_delayed_finalize<B, CB, C, S>(
DelayedFinalizeParams { client, spawn_handle, delay_sec }: DelayedFinalizeParams<C, S>,
) where
B: BlockT + 'static,
CB: ClientBackend<B> + 'static,
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + BlockchainEvents<B> + 'static,
S: SpawnNamed,
{
let mut block_import_stream = client.import_notification_stream();
while let Some(notification) = block_import_stream.next().await {
let delay = Delay::new(Duration::from_secs(delay_sec));
let cloned_client = client.clone();
spawn_handle.spawn(
"delayed-finalize",
None,
Box::pin(async move {
delay.await;
finalize_block(FinalizeBlockParams {
hash: notification.hash,
sender: None,
justification: None,
finalizer: cloned_client,
_phantom: PhantomData,
})
.await
}),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsc_basic_authorship::ProposerFactory;
use pezsc_consensus::ImportedAux;
use pezsc_transaction_pool::{BasicPool, FullChainApi, Options, RevalidationType};
use pezsc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionSource};
use pezsp_inherents::InherentData;
use pezsp_runtime::generic::{Digest, DigestItem};
use bizinikiwi_test_runtime_client::{
DefaultTestClientBuilderExt, Sr25519Keyring::*, TestClientBuilder, TestClientBuilderExt,
};
use bizinikiwi_test_runtime_transaction_pool::{uxt, TestApi};
fn api() -> Arc<TestApi> {
Arc::new(TestApi::empty())
}
const SOURCE: TransactionSource = TransactionSource::External;
struct TestDigestProvider<C> {
_client: Arc<C>,
}
impl<B, C> ConsensusDataProvider<B> for TestDigestProvider<C>
where
B: BlockT,
C: ProvideRuntimeApi<B> + Send + Sync,
{
type Proof = ();
fn create_digest(
&self,
_parent: &B::Header,
_inherents: &InherentData,
) -> Result<Digest, Error> {
Ok(Digest { logs: vec![] })
}
fn append_block_import(
&self,
_parent: &B::Header,
params: &mut BlockImportParams<B>,
_inherents: &InherentData,
_proof: Self::Proof,
) -> Result<(), Error> {
params.post_digests.push(DigestItem::Other(vec![1]));
Ok(())
}
}
#[tokio::test]
async fn instant_seal() {
let builder = TestClientBuilder::new();
let (client, select_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
let spawner = pezsp_core::testing::TaskExecutor::new();
let genesis_hash = client.info().genesis_hash;
let pool_api = Arc::new(FullChainApi::new(client.clone(), None, &spawner.clone()));
let pool = Arc::new(BasicPool::with_revalidation_type(
Options::default(),
true.into(),
pool_api,
None,
RevalidationType::Full,
spawner.clone(),
0,
genesis_hash,
genesis_hash,
));
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
// this test checks that blocks are created as soon as transactions are imported into the
// pool.
let (sender, receiver) = futures::channel::oneshot::channel();
let mut sender = Arc::new(Some(sender));
let commands_stream =
pool.pool().validated_pool().import_notification_stream().map(move |_| {
// we're only going to submit one tx so this fn will only be called once.
let mut_sender = Arc::get_mut(&mut sender).unwrap();
let sender = std::mem::take(mut_sender);
EngineCommand::SealNewBlock {
create_empty: false,
finalize: true,
parent_hash: None,
sender,
}
});
// spawn the background authorship task
tokio::spawn(run_manual_seal(ManualSealParams {
block_import: client.clone(),
env,
client: client.clone(),
pool: pool.clone(),
commands_stream,
select_chain,
create_inherent_data_providers: |_, _| async { Ok(()) },
consensus_data_provider: None,
}));
// submit a transaction to pool.
let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
// assert that the background task returns ok
let created_block = receiver.await.unwrap().unwrap();
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash,
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
is_new_best: true,
},
proof_size: 0
}
);
// assert that there's a new block in the db.
assert!(client.header(created_block.hash).unwrap().is_some());
assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1)
}
// TODO: enable once the flakiness is fixed
// See https://github.com/pezkuwichain/pezkuwi-sdk/issues/131
//#[tokio::test]
#[allow(unused)]
async fn instant_seal_delayed_finalize() {
let builder = TestClientBuilder::new();
let (client, select_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
let spawner = pezsp_core::testing::TaskExecutor::new();
let genesis_hash = client.info().genesis_hash;
let pool_api = Arc::new(FullChainApi::new(client.clone(), None, &spawner.clone()));
let pool = Arc::new(BasicPool::with_revalidation_type(
Options::default(),
true.into(),
pool_api,
None,
RevalidationType::Full,
spawner.clone(),
0,
genesis_hash,
genesis_hash,
));
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
// this test checks that blocks are created as soon as transactions are imported into the
// pool.
let (sender, receiver) = futures::channel::oneshot::channel();
let mut sender = Arc::new(Some(sender));
let commands_stream =
pool.pool().validated_pool().import_notification_stream().map(move |_| {
// we're only going to submit one tx so this fn will only be called once.
let mut_sender = Arc::get_mut(&mut sender).unwrap();
let sender = std::mem::take(mut_sender);
EngineCommand::SealNewBlock {
create_empty: false,
// set to `false`, expecting to be finalized by delayed finalize
finalize: false,
parent_hash: None,
sender,
}
});
// spawn the background authorship task
tokio::spawn(run_manual_seal(ManualSealParams {
block_import: client.clone(),
commands_stream,
env,
client: client.clone(),
pool: pool.clone(),
select_chain,
create_inherent_data_providers: |_, _| async { Ok(()) },
consensus_data_provider: None,
}));
let delay_sec = 5;
// spawn the background finality task
tokio::spawn(run_delayed_finalize(DelayedFinalizeParams {
client: client.clone(),
delay_sec,
spawn_handle: spawner,
}));
let mut finality_stream = client.finality_notification_stream();
// submit a transaction to pool.
let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
// assert that the background task returns ok
let created_block = receiver.await.unwrap().unwrap();
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash,
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
is_new_best: true,
},
proof_size: created_block.proof_size
}
);
// assert that there's a new block in the db.
assert!(client.header(created_block.hash).unwrap().is_some());
assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1);
assert_eq!(client.info().finalized_hash, client.info().genesis_hash);
let finalized = finality_stream.select_next_some().await;
assert_eq!(finalized.hash, created_block.hash);
}
#[tokio::test]
async fn manual_seal_and_finalization() {
let builder = TestClientBuilder::new();
let (client, select_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
let spawner = pezsp_core::testing::TaskExecutor::new();
let genesis_hash = client.info().genesis_hash;
let pool_api = Arc::new(FullChainApi::new(client.clone(), None, &spawner.clone()));
let pool = Arc::new(BasicPool::with_revalidation_type(
Options::default(),
true.into(),
pool_api,
None,
RevalidationType::Full,
spawner.clone(),
0,
genesis_hash,
genesis_hash,
));
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
// this test checks that blocks are created as soon as an engine command is sent over the
// stream.
let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024);
// spawn the background authorship task
tokio::spawn(run_manual_seal(ManualSealParams {
block_import: client.clone(),
env,
client: client.clone(),
pool: pool.clone(),
commands_stream,
select_chain,
consensus_data_provider: None,
create_inherent_data_providers: |_, _| async { Ok(()) },
}));
// submit a transaction to pool.
let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::SealNewBlock {
parent_hash: None,
sender: Some(tx),
create_empty: false,
finalize: false,
})
.await
.unwrap();
let created_block = rx.await.unwrap().unwrap();
// assert that the background task returns ok
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash,
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
is_new_best: true,
},
proof_size: 0
}
);
// assert that there's a new block in the db.
let header = client.header(created_block.hash).unwrap().unwrap();
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::FinalizeBlock {
sender: Some(tx),
hash: header.hash(),
justification: None,
})
.await
.unwrap();
// check that the background task returns ok:
rx.await.unwrap().unwrap();
}
#[tokio::test]
async fn manual_seal_fork_blocks() {
let builder = TestClientBuilder::new();
let (client, select_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
let pool_api = Arc::new(FullChainApi::new(
client.clone(),
None,
&pezsp_core::testing::TaskExecutor::new(),
));
let spawner = pezsp_core::testing::TaskExecutor::new();
let genesis_hash = client.info().genesis_hash;
let pool = Arc::new(BasicPool::with_revalidation_type(
Options::default(),
true.into(),
pool_api.clone(),
None,
RevalidationType::Full,
spawner.clone(),
0,
genesis_hash,
genesis_hash,
));
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
// this test checks that blocks are created as soon as an engine command is sent over the
// stream.
let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024);
// spawn the background authorship task
tokio::spawn(run_manual_seal(ManualSealParams {
block_import: client.clone(),
env,
client: client.clone(),
pool: pool.clone(),
commands_stream,
select_chain,
consensus_data_provider: None,
create_inherent_data_providers: |_, _| async { Ok(()) },
}));
// submit a transaction to pool.
let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::SealNewBlock {
parent_hash: None,
sender: Some(tx),
create_empty: false,
finalize: false,
})
.await
.unwrap();
let created_block = rx.await.unwrap().unwrap();
// assert that the background task returns ok
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash,
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
is_new_best: true
},
proof_size: 0
}
);
assert!(pool.submit_one(created_block.hash, SOURCE, uxt(Alice, 1)).await.is_ok());
let header = client.header(created_block.hash).expect("db error").expect("imported above");
assert_eq!(header.number, 1);
pool.maintain(pezsc_transaction_pool_api::ChainEvent::NewBestBlock {
hash: header.hash(),
tree_route: None,
})
.await;
let (tx1, rx1) = futures::channel::oneshot::channel();
assert!(sink
.send(EngineCommand::SealNewBlock {
parent_hash: Some(created_block.hash),
sender: Some(tx1),
create_empty: false,
finalize: false,
})
.await
.is_ok());
assert_matches::assert_matches!(rx1.await.expect("should be no error receiving"), Ok(_));
assert!(pool.submit_one(created_block.hash, SOURCE, uxt(Bob, 0)).await.is_ok());
let (tx2, rx2) = futures::channel::oneshot::channel();
assert!(sink
.send(EngineCommand::SealNewBlock {
parent_hash: Some(created_block.hash),
sender: Some(tx2),
create_empty: false,
finalize: false,
})
.await
.is_ok());
let imported = rx2.await.unwrap().unwrap();
// assert that fork block is in the db
assert!(client.header(imported.hash).unwrap().is_some())
}
#[tokio::test]
async fn manual_seal_post_hash() {
let builder = TestClientBuilder::new();
let (client, select_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
let spawner = pezsp_core::testing::TaskExecutor::new();
let genesis_hash = client.header(client.info().genesis_hash).unwrap().unwrap().hash();
let pool = Arc::new(BasicPool::with_revalidation_type(
Options::default(),
true.into(),
api(),
None,
RevalidationType::Full,
spawner.clone(),
0,
genesis_hash,
genesis_hash,
));
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024);
// spawn the background authorship task
tokio::spawn(run_manual_seal(ManualSealParams {
block_import: client.clone(),
env,
client: client.clone(),
pool: pool.clone(),
commands_stream,
select_chain,
// use a provider that pushes some post digest data
consensus_data_provider: Some(Box::new(TestDigestProvider { _client: client.clone() })),
create_inherent_data_providers: |_, _| async { Ok(()) },
}));
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::SealNewBlock {
parent_hash: None,
sender: Some(tx),
create_empty: true,
finalize: false,
})
.await
.unwrap();
let created_block = rx.await.unwrap().unwrap();
// assert that the background task returned the actual header hash
let header = client.header(created_block.hash).unwrap().unwrap();
assert_eq!(header.number, 1);
}
}
@@ -0,0 +1,169 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! RPC interface for the `ManualSeal` Engine.
use crate::error::Error;
use futures::{
channel::{mpsc, oneshot},
SinkExt,
};
use jsonrpsee::{core::async_trait, proc_macros::rpc};
use pezsc_consensus::ImportedAux;
use serde::{Deserialize, Serialize};
use pezsp_runtime::EncodedJustification;
/// Sender passed to the authorship task to report errors or successes.
pub type Sender<T> = Option<oneshot::Sender<std::result::Result<T, Error>>>;
/// Message sent to the background authorship task, usually by RPC.
pub enum EngineCommand<Hash> {
/// Tells the engine to propose a new block
///
/// if create_empty == true, it will create empty blocks if there are no transactions
/// in the transaction pool.
///
/// if finalize == true, the block will be instantly finalized.
SealNewBlock {
/// if true, empty blocks(without extrinsics) will be created.
/// otherwise, will return Error::EmptyTransactionPool.
create_empty: bool,
/// instantly finalize this block?
finalize: bool,
/// specify the parent hash of the about-to-created block
parent_hash: Option<Hash>,
/// sender to report errors/success to the rpc.
sender: Sender<CreatedBlock<Hash>>,
},
/// Tells the engine to finalize the block with the supplied hash
FinalizeBlock {
/// hash of the block
hash: Hash,
/// sender to report errors/success to the rpc.
sender: Sender<()>,
/// finalization justification
justification: Option<EncodedJustification>,
},
}
/// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc.
#[rpc(client, server)]
pub trait ManualSealApi<Hash> {
/// Instructs the manual-seal authorship task to create a new block
#[method(name = "engine_createBlock")]
async fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>,
) -> Result<CreatedBlock<Hash>, Error>;
/// Instructs the manual-seal authorship task to finalize a block
#[method(name = "engine_finalizeBlock")]
async fn finalize_block(
&self,
hash: Hash,
justification: Option<EncodedJustification>,
) -> Result<bool, Error>;
}
/// A struct that implements the [`ManualSealApiServer`].
pub struct ManualSeal<Hash> {
import_block_channel: mpsc::Sender<EngineCommand<Hash>>,
}
/// return type of `engine_createBlock`
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct CreatedBlock<Hash> {
/// hash of the created block.
pub hash: Hash,
/// some extra details about the import operation
pub aux: ImportedAux,
/// uncompacted storage proof size (zero mean that there is no proof)
pub proof_size: usize,
}
impl<Hash> ManualSeal<Hash> {
/// Create new `ManualSeal` with the given reference to the client.
pub fn new(import_block_channel: mpsc::Sender<EngineCommand<Hash>>) -> Self {
Self { import_block_channel }
}
}
#[async_trait]
impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
async fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>,
) -> Result<CreatedBlock<Hash>, Error> {
let mut sink = self.import_block_channel.clone();
let (sender, receiver) = oneshot::channel();
// NOTE: this sends a Result over the channel.
let command = EngineCommand::SealNewBlock {
create_empty,
finalize,
parent_hash,
sender: Some(sender),
};
sink.send(command).await?;
match receiver.await {
Ok(Ok(rx)) => Ok(rx),
Ok(Err(e)) => Err(e.into()),
Err(e) => Err(e.into()),
}
}
async fn finalize_block(
&self,
hash: Hash,
justification: Option<EncodedJustification>,
) -> Result<bool, Error> {
let mut sink = self.import_block_channel.clone();
let (sender, receiver) = oneshot::channel();
let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification };
sink.send(command).await?;
receiver.await.map(|_| true).map_err(Into::into)
}
}
/// report any errors or successes encountered by the authorship task back
/// to the rpc
pub fn send_result<T: std::fmt::Debug>(
sender: &mut Sender<T>,
result: std::result::Result<T, crate::Error>,
) {
if let Some(sender) = sender.take() {
if let Err(err) = sender.send(result) {
match err {
Ok(value) => log::warn!("Server is shutting down: {:?}", value),
Err(error) => log::warn!("Server is shutting down with error: {}", error),
}
}
} else {
// Sealing/Finalization with no RPC sender such as instant seal or delayed finalize doesn't
// report errors over rpc, simply log them.
match result {
Ok(r) => log::info!("Consensus with no RPC sender success: {:?}", r),
Err(e) => log::error!("Consensus with no RPC sender encountered an error: {}", e),
}
}
}
@@ -0,0 +1,163 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Block sealing utilities
use crate::{rpc, ConsensusDataProvider, CreatedBlock, Error};
use futures::prelude::*;
use pezsc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction};
use pezsc_transaction_pool_api::TransactionPool;
use pezsp_api::ProvideRuntimeApi;
use pezsp_blockchain::HeaderBackend;
use pezsp_consensus::{self, BlockOrigin, Environment, Proposer, SelectChain};
use pezsp_inherents::{CreateInherentDataProviders, InherentDataProvider};
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{sync::Arc, time::Duration};
/// max duration for creating a proposal in secs
pub const MAX_PROPOSAL_DURATION: u64 = 10;
/// params for sealing a new block
pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi<B>, E, TP, CIDP, P> {
/// if true, empty blocks(without extrinsics) will be created.
/// otherwise, will return Error::EmptyTransactionPool.
pub create_empty: bool,
/// instantly finalize this block?
pub finalize: bool,
/// specify the parent hash of the about-to-created block
pub parent_hash: Option<<B as BlockT>::Hash>,
/// sender to report errors/success to the rpc.
pub sender: rpc::Sender<CreatedBlock<<B as BlockT>::Hash>>,
/// transaction pool
pub pool: Arc<TP>,
/// header backend
pub client: Arc<C>,
/// Environment trait object for creating a proposer
pub env: &'a mut E,
/// SelectChain object
pub select_chain: &'a SC,
/// Digest provider for inclusion in blocks.
pub consensus_data_provider: Option<&'a dyn ConsensusDataProvider<B, Proof = P>>,
/// block import object
pub block_import: &'a mut BI,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: &'a CIDP,
}
/// seals a new block with the given params
pub async fn seal_block<B, BI, SC, C, E, TP, CIDP, P>(
SealBlockParams {
create_empty,
finalize,
pool,
parent_hash,
client,
select_chain,
block_import,
env,
create_inherent_data_providers,
consensus_data_provider: digest_provider,
mut sender,
}: SealBlockParams<'_, B, BI, SC, C, E, TP, CIDP, P>,
) where
B: BlockT,
BI: BlockImport<B, Error = pezsp_consensus::Error> + Send + Sync + 'static,
C: HeaderBackend<B> + ProvideRuntimeApi<B>,
E: Environment<B>,
E::Proposer: Proposer<B, Proof = P>,
TP: TransactionPool<Block = B>,
SC: SelectChain<B>,
CIDP: CreateInherentDataProviders<B, ()>,
P: codec::Encode + Send + Sync + 'static,
{
let future = async {
if pool.status().ready == 0 && !create_empty {
return Err(Error::EmptyTransactionPool);
}
// get the header to build this new block on.
// use the parent_hash supplied via `EngineCommand`
// or fetch the best_block.
let parent = match parent_hash {
Some(hash) =>
client.header(hash)?.ok_or_else(|| Error::BlockNotFound(format!("{}", hash)))?,
None => select_chain.best_chain().await?,
};
let inherent_data_providers = create_inherent_data_providers
.create_inherent_data_providers(parent.hash(), ())
.await
.map_err(|e| Error::Other(e))?;
let inherent_data = inherent_data_providers.create_inherent_data().await?;
let proposer = env.init(&parent).map_err(|err| Error::StringError(err.to_string())).await?;
let inherents_len = inherent_data.len();
let digest = if let Some(digest_provider) = digest_provider {
digest_provider.create_digest(&parent, &inherent_data)?
} else {
Default::default()
};
let proposal = proposer
.propose(
inherent_data.clone(),
digest,
Duration::from_secs(MAX_PROPOSAL_DURATION),
None,
)
.map_err(|err| Error::StringError(err.to_string()))
.await?;
if proposal.block.extrinsics().len() == inherents_len && !create_empty {
return Err(Error::EmptyTransactionPool);
}
let (header, body) = proposal.block.deconstruct();
let proof = proposal.proof;
let proof_size = proof.encoded_size();
let mut params = BlockImportParams::new(BlockOrigin::Own, header.clone());
params.body = Some(body);
params.finalized = finalize;
params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
params.state_action = StateAction::ApplyChanges(pezsc_consensus::StorageChanges::Changes(
proposal.storage_changes,
));
if let Some(digest_provider) = digest_provider {
digest_provider.append_block_import(&parent, &mut params, &inherent_data, proof)?;
}
// Make sure we return the same post-hash that will be calculated when importing the block
// This is important in case the digest_provider added any signature, seal, ect.
let mut post_header = header.clone();
post_header.digest_mut().logs.extend(params.post_digests.iter().cloned());
match block_import.import_block(params).await? {
ImportResult::Imported(aux) => Ok(CreatedBlock {
hash: <B as BlockT>::Header::hash(&post_header),
aux,
proof_size,
}),
other => Err(other.into()),
}
};
rpc::send_result(&mut sender, future.await)
}