feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,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)
|
||||
}
|
||||
Reference in New Issue
Block a user