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,74 @@
[package]
name = "pezsc-consensus-aura"
version = "0.34.0"
authors.workspace = true
description = "Aura consensus algorithm 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]
async-trait = { workspace = true }
codec = { workspace = true, default-features = true }
fork-tree = { workspace = true, default-features = true }
futures = { workspace = true }
log = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
pezsc-block-builder = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-consensus = { workspace = true, default-features = true }
pezsc-consensus-slots = { workspace = true, default-features = true }
pezsc-telemetry = { workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-application-crypto = { workspace = true, default-features = true }
pezsp-block-builder = { 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-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 }
thiserror = { workspace = true }
[dev-dependencies]
pezsc-keystore = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-network-test = { workspace = true }
pezsp-keyring = { workspace = true, default-features = true }
pezsp-timestamp = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsc-block-builder/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-consensus-slots/runtime-benchmarks",
"pezsc-consensus/runtime-benchmarks",
"pezsc-network-test/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-block-builder/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus-aura/runtime-benchmarks",
"pezsp-consensus-slots/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-inherents/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-timestamp/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,15 @@
Aura (Authority-round) consensus in Bizinikiwi.
Aura works by having a list of authorities A who are expected to roughly
agree on the current time. Time is divided up into discrete slots of t
seconds each. For each slot s, the author of that slot is A[s % |A|].
The author is allowed to issue one block but not more during that slot,
and it will be built upon the longest valid chain that has been seen.
Blocks from future steps will be either deferred or rejected depending on how
far in the future they are.
NOTE: Aura itself is designed to be generic over the crypto used.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,182 @@
// 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/>.
//! Module implementing the logic for verifying and importing AuRa blocks.
use std::{fmt::Debug, sync::Arc};
use codec::Codec;
use fork_tree::ForkTree;
use parking_lot::RwLock;
use pezsp_api::ProvideRuntimeApi;
use pezsp_blockchain::{HeaderBackend, HeaderMetadata};
use pezsp_consensus_aura::{AuraApi, ConsensusLog, AURA_ENGINE_ID};
use pezsp_core::Pair;
use pezsp_runtime::{
generic::OpaqueDigestItemId,
traits::{Block, Header, NumberFor},
};
use crate::{fetch_authorities_from_runtime, AuthorityId, CompatibilityMode};
const LOG_TARGET: &str = "aura::authorities_tracker";
/// AURA authorities tracker. Updates authorities based on the AURA authorities change
/// digest in the block header.
pub struct AuthoritiesTracker<P: Pair, B: Block, C> {
authorities: RwLock<ForkTree<B::Hash, NumberFor<B>, Vec<AuthorityId<P>>>>,
client: Arc<C>,
}
impl<P: Pair, B: Block, C> AuthoritiesTracker<P, B, C> {
/// Create a new `AuthoritiesTracker`.
pub fn new(client: Arc<C>) -> Self {
Self { authorities: RwLock::new(ForkTree::new()), client }
}
}
impl<P, B, C> AuthoritiesTracker<P, B, C>
where
P: Pair,
B: Block,
C: HeaderBackend<B> + HeaderMetadata<B, Error = pezsp_blockchain::Error> + ProvideRuntimeApi<B>,
P::Public: Codec + Debug,
C::Api: AuraApi<B, AuthorityId<P>>,
{
/// Fetch authorities from the tracker, if available. If not available, fetch from the client
/// and update the tracker.
pub fn fetch_or_update(
&self,
header: &B::Header,
compatibility_mode: &CompatibilityMode<NumberFor<B>>,
) -> Result<Vec<AuthorityId<P>>, String> {
let hash = header.hash();
let number = *header.number();
let parent_hash = *header.parent_hash();
// Fetch authorities from cache, if available.
let authorities = {
let is_descendent_of =
pezsc_client_api::utils::is_descendent_of(&*self.client, Some((hash, parent_hash)));
let authorities_cache = self.authorities.read();
authorities_cache
.find_node_where(&hash, &number, &is_descendent_of, &|_| true)
.map_err(|e| {
format!("Could not find authorities for block {hash:?} at number {number}: {e}")
})?
.map(|node| node.data.clone())
};
match authorities {
Some(authorities) => {
log::debug!(
target: LOG_TARGET,
"Authorities for block {:?} at number {} found in cache",
hash,
number,
);
Ok(authorities)
},
None => {
// Authorities are missing from the cache. Fetch them from the runtime and cache
// them.
log::debug!(
target: LOG_TARGET,
"Authorities for block {:?} at number {} not found in cache, fetching from runtime",
hash,
number
);
let authorities = fetch_authorities_from_runtime(
&*self.client,
parent_hash,
number,
compatibility_mode,
)
.map_err(|e| format!("Could not fetch authorities at {:?}: {}", parent_hash, e))?;
let is_descendent_of = pezsc_client_api::utils::is_descendent_of(&*self.client, None);
let mut authorities_cache = self.authorities.write();
authorities_cache
.import(
parent_hash,
number - 1u32.into(),
authorities.clone(),
&is_descendent_of,
)
.map_err(|e| {
format!("Could not import authorities for block {parent_hash:?} at number {}: {e}", number - 1u32.into())
})?;
Ok(authorities)
},
}
}
/// If there is an authorities change digest in the header, import it into the tracker.
pub fn import(&self, post_header: &B::Header) -> Result<(), String> {
if let Some(authorities_change) = find_authorities_change_digest::<B, P>(&post_header) {
let hash = post_header.hash();
let parent_hash = *post_header.parent_hash();
let number = *post_header.number();
log::debug!(
target: LOG_TARGET,
"Importing authorities change for block {:?} at number {} found in header digest",
hash,
number,
);
self.prune_finalized()?;
let is_descendent_of =
pezsc_client_api::utils::is_descendent_of(&*self.client, Some((hash, parent_hash)));
let mut authorities_cache = self.authorities.write();
authorities_cache
.import(hash, number, authorities_change, &is_descendent_of)
.map_err(|e| {
format!(
"Could not import authorities for block {hash:?} at number {number}: {e}"
)
})?;
}
Ok(())
}
fn prune_finalized(&self) -> Result<(), String> {
let is_descendent_of = pezsc_client_api::utils::is_descendent_of(&*self.client, None);
let info = self.client.info();
let mut authorities_cache = self.authorities.write();
let _pruned = authorities_cache
.prune(&info.finalized_hash, &info.finalized_number, &is_descendent_of, &|_| true)
.map_err(|e| e.to_string())?;
Ok(())
}
}
/// Extract the AURA authorities change digest from the given header, if it exists.
fn find_authorities_change_digest<B, P>(header: &B::Header) -> Option<Vec<AuthorityId<P>>>
where
B: Block,
P: Pair,
P::Public: Codec,
{
for log in header.digest().logs() {
log::trace!(target: LOG_TARGET, "Checking log {:?}, looking for authorities change digest.", log);
let log = log
.try_to::<ConsensusLog<AuthorityId<P>>>(OpaqueDigestItemId::Consensus(&AURA_ENGINE_ID));
if let Some(ConsensusLog::AuthoritiesChange(authorities)) = log {
return Some(authorities);
}
}
None
}
@@ -0,0 +1,388 @@
// 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/>.
//! Module implementing the logic for verifying and importing AuRa blocks.
use crate::{
fetch_authorities_from_runtime, standalone::SealVerificationError, AuthoritiesTracker,
AuthorityId, CompatibilityMode, Error, LOG_TARGET,
};
use codec::Codec;
use log::{debug, info, trace};
use prometheus_endpoint::Registry;
use pezsc_client_api::{backend::AuxStore, BlockOf, UsageProvider};
use pezsc_consensus::{
block_import::{BlockImport, BlockImportParams, ForkChoiceStrategy},
import_queue::{BasicQueue, BoxJustificationImport, DefaultImportQueue, Verifier},
};
use pezsc_consensus_slots::{check_equivocation, CheckedHeader, InherentDataProviderExt};
use pezsc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE};
use pezsp_api::{ApiExt, ProvideRuntimeApi};
use pezsp_block_builder::BlockBuilder as BlockBuilderApi;
use pezsp_blockchain::{HeaderBackend, HeaderMetadata};
use pezsp_consensus::Error as ConsensusError;
use pezsp_consensus_aura::{inherents::AuraInherentData, AuraApi};
use pezsp_consensus_slots::Slot;
use pezsp_core::crypto::Pair;
use pezsp_inherents::{CreateInherentDataProviders, InherentDataProvider as _};
use pezsp_runtime::{
traits::{Block as BlockT, Header, NumberFor},
DigestItem,
};
use std::{fmt::Debug, sync::Arc};
/// check a header has been signed by the right key. If the slot is too far in the future, an error
/// will be returned. If it's successful, returns the pre-header and the digest item
/// containing the seal.
///
/// This digest item will always return `Some` when used with `as_aura_seal`.
fn check_header<C, B: BlockT, P: Pair>(
client: &C,
slot_now: Slot,
header: B::Header,
hash: B::Hash,
authorities: &[AuthorityId<P>],
check_for_equivocation: CheckForEquivocation,
) -> Result<CheckedHeader<B::Header, (Slot, DigestItem)>, Error<B>>
where
P::Public: Codec,
P::Signature: Codec,
C: pezsc_client_api::backend::AuxStore,
{
let check_result =
crate::standalone::check_header_slot_and_seal::<B, P>(slot_now, header, authorities);
match check_result {
Ok((header, slot, seal)) => {
let expected_author = crate::standalone::slot_author::<P>(slot, &authorities);
let should_equiv_check = check_for_equivocation.check_for_equivocation();
if let (true, Some(expected)) = (should_equiv_check, expected_author) {
if let Some(equivocation_proof) =
check_equivocation(client, slot_now, slot, &header, expected)
.map_err(Error::Client)?
{
info!(
target: LOG_TARGET,
"Slot author is equivocating at slot {} with headers {:?} and {:?}",
slot,
equivocation_proof.first_header.hash(),
equivocation_proof.second_header.hash(),
);
}
}
Ok(CheckedHeader::Checked(header, (slot, seal)))
},
Err(SealVerificationError::Deferred(header, slot)) =>
Ok(CheckedHeader::Deferred(header, slot)),
Err(SealVerificationError::Unsealed) => Err(Error::HeaderUnsealed(hash)),
Err(SealVerificationError::BadSeal) => Err(Error::HeaderBadSeal(hash)),
Err(SealVerificationError::BadSignature) => Err(Error::BadSignature(hash)),
Err(SealVerificationError::SlotAuthorNotFound) => Err(Error::SlotAuthorNotFound),
Err(SealVerificationError::InvalidPreDigest(e)) => Err(Error::from(e)),
}
}
/// A verifier for Aura blocks.
pub struct AuraVerifier<C, P: Pair, CIDP, B: BlockT> {
client: Arc<C>,
create_inherent_data_providers: CIDP,
check_for_equivocation: CheckForEquivocation,
telemetry: Option<TelemetryHandle>,
compatibility_mode: CompatibilityMode<NumberFor<B>>,
_authorities_tracker: AuthoritiesTracker<P, B, C>,
}
impl<C, P: Pair, CIDP, B: BlockT> AuraVerifier<C, P, CIDP, B> {
pub(crate) fn new(
client: Arc<C>,
create_inherent_data_providers: CIDP,
check_for_equivocation: CheckForEquivocation,
telemetry: Option<TelemetryHandle>,
compatibility_mode: CompatibilityMode<NumberFor<B>>,
) -> Self {
Self {
client: client.clone(),
create_inherent_data_providers,
check_for_equivocation,
telemetry,
compatibility_mode,
_authorities_tracker: AuthoritiesTracker::new(client),
}
}
}
#[async_trait::async_trait]
impl<B, C, P, CIDP> Verifier<B> for AuraVerifier<C, P, CIDP, B>
where
B: BlockT,
C: HeaderBackend<B>
+ HeaderMetadata<B, Error = pezsp_blockchain::Error>
+ ProvideRuntimeApi<B>
+ Send
+ Sync
+ pezsc_client_api::backend::AuxStore,
C::Api: BlockBuilderApi<B> + AuraApi<B, AuthorityId<P>> + ApiExt<B>,
P: Pair,
P::Public: Codec + Debug,
P::Signature: Codec,
CIDP: CreateInherentDataProviders<B, ()> + Send + Sync,
CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync,
{
async fn verify(
&self,
mut block: BlockImportParams<B>,
) -> Result<BlockImportParams<B>, String> {
// Skip checks that include execution, if being told so or when importing only state.
//
// This is done for example when gap syncing and it is expected that the block after the gap
// was checked/chosen properly, e.g. by warp syncing to this block using a finality proof.
// Or when we are importing state only and can not verify the seal.
if block.with_state() || block.state_action.skip_execution_checks() {
// When we are importing only the state of a block, it will be the best block.
block.fork_choice = Some(ForkChoiceStrategy::Custom(block.with_state()));
return Ok(block);
}
let hash = block.header.hash();
let parent_hash = *block.header.parent_hash();
let authorities = fetch_authorities_from_runtime(
self.client.as_ref(),
parent_hash,
*block.header.number(),
&self.compatibility_mode,
)
.map_err(|e| format!("Could not fetch authorities at {:?}: {}", parent_hash, e))?;
let create_inherent_data_providers = self
.create_inherent_data_providers
.create_inherent_data_providers(parent_hash, ())
.await
.map_err(|e| Error::<B>::Client(pezsp_blockchain::Error::Application(e)))?;
let mut inherent_data = create_inherent_data_providers
.create_inherent_data()
.await
.map_err(Error::<B>::Inherent)?;
let slot_now = create_inherent_data_providers.slot();
// we add one to allow for some small drift.
// FIXME #1019 in the future, alter this queue to allow deferring of
// headers
let checked_header = check_header::<C, B, P>(
&self.client,
slot_now + 1,
block.header,
hash,
&authorities[..],
self.check_for_equivocation,
)
.map_err(|e| e.to_string())?;
match checked_header {
CheckedHeader::Checked(pre_header, (slot, seal)) => {
// if the body is passed through, we need to use the runtime
// to check that the internally-set timestamp in the inherents
// actually matches the slot set in the seal.
if let Some(inner_body) = block.body.take() {
let new_block = B::new(pre_header.clone(), inner_body);
inherent_data.aura_replace_inherent_data(slot);
// skip the inherents verification if the runtime API is old or not expected to
// exist.
if self
.client
.runtime_api()
.has_api_with::<dyn BlockBuilderApi<B>, _>(parent_hash, |v| v >= 2)
.map_err(|e| e.to_string())?
{
pezsp_block_builder::check_inherents_with_data(
self.client.clone(),
parent_hash,
new_block.clone(),
&create_inherent_data_providers,
inherent_data,
)
.await
.map_err(|e| format!("Error checking block inherents {:?}", e))?;
}
let (_, inner_body) = new_block.deconstruct();
block.body = Some(inner_body);
}
trace!(target: LOG_TARGET, "Checked {:?}; importing.", pre_header);
telemetry!(
self.telemetry;
CONSENSUS_TRACE;
"aura.checked_and_importing";
"pre_header" => ?pre_header,
);
block.header = pre_header;
block.post_digests.push(seal);
block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
block.post_hash = Some(hash);
Ok(block)
},
CheckedHeader::Deferred(a, b) => {
debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b);
telemetry!(
self.telemetry;
CONSENSUS_DEBUG;
"aura.header_too_far_in_future";
"hash" => ?hash,
"a" => ?a,
"b" => ?b,
);
Err(format!("Header {:?} rejected: too far in the future", hash))
},
}
}
}
/// Should we check for equivocation of a block author?
#[derive(Debug, Clone, Copy)]
pub enum CheckForEquivocation {
/// Yes, check for equivocation.
///
/// This is the default setting for this.
Yes,
/// No, don't check for equivocation.
No,
}
impl CheckForEquivocation {
/// Should we check for equivocation?
fn check_for_equivocation(self) -> bool {
matches!(self, Self::Yes)
}
}
impl Default for CheckForEquivocation {
fn default() -> Self {
Self::Yes
}
}
/// Parameters of [`import_queue`].
pub struct ImportQueueParams<'a, Block: BlockT, I, C, S, CIDP> {
/// The block import to use.
pub block_import: I,
/// The justification import.
pub justification_import: Option<BoxJustificationImport<Block>>,
/// The client to interact with the chain.
pub client: Arc<C>,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: CIDP,
/// The spawner to spawn background tasks.
pub spawner: &'a S,
/// The prometheus registry.
pub registry: Option<&'a Registry>,
/// Should we check for equivocation?
pub check_for_equivocation: CheckForEquivocation,
/// Telemetry instance used to report telemetry metrics.
pub telemetry: Option<TelemetryHandle>,
/// Compatibility mode that should be used.
///
/// If in doubt, use `Default::default()`.
pub compatibility_mode: CompatibilityMode<NumberFor<Block>>,
}
/// Start an import queue for the Aura consensus algorithm.
pub fn import_queue<P, Block, I, C, S, CIDP>(
ImportQueueParams {
block_import,
justification_import,
client,
create_inherent_data_providers,
spawner,
registry,
check_for_equivocation,
telemetry,
compatibility_mode,
}: ImportQueueParams<Block, I, C, S, CIDP>,
) -> Result<DefaultImportQueue<Block>, pezsp_consensus::Error>
where
Block: BlockT,
C::Api: BlockBuilderApi<Block> + AuraApi<Block, AuthorityId<P>> + ApiExt<Block>,
C: 'static
+ ProvideRuntimeApi<Block>
+ BlockOf
+ Send
+ Sync
+ AuxStore
+ UsageProvider<Block>
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = pezsp_blockchain::Error>,
I: BlockImport<Block, Error = ConsensusError> + Send + Sync + 'static,
P: Pair + 'static,
P::Public: Codec + Debug,
P::Signature: Codec,
S: pezsp_core::traits::SpawnEssentialNamed,
CIDP: CreateInherentDataProviders<Block, ()> + Sync + Send + 'static,
CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync,
{
let verifier = build_verifier::<P, _, _, _>(BuildVerifierParams {
client,
create_inherent_data_providers,
check_for_equivocation,
telemetry,
compatibility_mode,
});
Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry))
}
/// Parameters of [`build_verifier`].
pub struct BuildVerifierParams<C, CIDP, N> {
/// The client to interact with the chain.
pub client: Arc<C>,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: CIDP,
/// Should we check for equivocation?
pub check_for_equivocation: CheckForEquivocation,
/// Telemetry instance used to report telemetry metrics.
pub telemetry: Option<TelemetryHandle>,
/// Compatibility mode that should be used.
///
/// If in doubt, use `Default::default()`.
pub compatibility_mode: CompatibilityMode<N>,
}
/// Build the [`AuraVerifier`]
pub fn build_verifier<P: Pair, C, CIDP, B: BlockT>(
BuildVerifierParams {
client,
create_inherent_data_providers,
check_for_equivocation,
telemetry,
compatibility_mode,
}: BuildVerifierParams<C, CIDP, NumberFor<B>>,
) -> AuraVerifier<C, P, CIDP, B> {
AuraVerifier::<_, P, _, _>::new(
client,
create_inherent_data_providers,
check_for_equivocation,
telemetry,
compatibility_mode,
)
}
+882
View File
@@ -0,0 +1,882 @@
// 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 (Authority-round) consensus in bizinikiwi.
//!
//! Aura works by having a list of authorities A who are expected to roughly
//! agree on the current time. Time is divided up into discrete slots of t
//! seconds each. For each slot s, the author of that slot is A[s % |A|].
//!
//! The author is allowed to issue one block but not more during that slot,
//! and it will be built upon the longest valid chain that has been seen.
//!
//! Blocks from future steps will be either deferred or rejected depending on how
//! far in the future they are.
//!
//! NOTE: Aura itself is designed to be generic over the crypto used.
#![forbid(missing_docs, unsafe_code)]
use std::{fmt::Debug, marker::PhantomData, pin::Pin, sync::Arc};
use codec::Codec;
use futures::prelude::*;
use pezsc_client_api::{backend::AuxStore, BlockOf};
use pezsc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction};
use pezsc_consensus_slots::{
BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker,
SlotInfo, StorageChanges,
};
use pezsc_telemetry::TelemetryHandle;
use pezsp_api::{Core, ProvideRuntimeApi};
use pezsp_application_crypto::AppPublic;
use pezsp_blockchain::HeaderBackend;
use pezsp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain};
use pezsp_consensus_slots::Slot;
use pezsp_core::crypto::Pair;
use pezsp_inherents::CreateInherentDataProviders;
use pezsp_keystore::KeystorePtr;
use pezsp_runtime::traits::{Block as BlockT, Header, Member, NumberFor};
mod authorities_tracker;
mod import_queue;
pub mod standalone;
pub use crate::standalone::{find_pre_digest, slot_duration};
pub use authorities_tracker::AuthoritiesTracker;
pub use import_queue::{
build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation,
ImportQueueParams,
};
pub use pezsc_consensus_slots::SlotProportion;
pub use pezsp_consensus::SyncOracle;
pub use pezsp_consensus_aura::{
digests::CompatibleDigestItem,
inherents::{InherentDataProvider, InherentType as AuraInherent, INHERENT_IDENTIFIER},
AuraApi, ConsensusLog, SlotDuration, AURA_ENGINE_ID,
};
const LOG_TARGET: &str = "aura";
type AuthorityId<P> = <P as Pair>::Public;
/// Run `AURA` in a compatibility mode.
///
/// This is required for when the chain was launched and later there
/// was a consensus breaking change.
#[derive(Debug, Clone)]
pub enum CompatibilityMode<N> {
/// Don't use any compatibility mode.
None,
/// Call `initialize_block` before doing any runtime calls.
///
/// Previously the node would execute `initialize_block` before fetching the authorities
/// from the runtime. This behaviour changed in: <https://github.com/pezkuwichain/kurdistan-sdk/issues/77>
///
/// By calling `initialize_block` before fetching the authorities, on a block that
/// would enact a new validator set, the block would already be build/sealed by an
/// authority of the new set. With this mode disabled (the default) a block that enacts a new
/// set isn't sealed/built by an authority of the new set, however to make new nodes be able to
/// sync old chains this compatibility mode exists.
UseInitializeBlock {
/// The block number until this compatibility mode should be executed. The first runtime
/// call in the context of the `until` block (importing it/building it) will disable the
/// compatibility mode (i.e. at `until` the default rules will apply). When enabling this
/// compatibility mode the `until` block should be a future block on which all nodes will
/// have upgraded to a release that includes the updated compatibility mode configuration.
/// At `until` block there will be a hard fork when the authority set changes, between the
/// old nodes (running with `initialize_block`, i.e. without the compatibility mode
/// configuration) and the new nodes.
until: N,
},
}
impl<N> Default for CompatibilityMode<N> {
fn default() -> Self {
Self::None
}
}
/// Parameters of [`start_aura`].
pub struct StartAuraParams<C, SC, I, PF, SO, L, CIDP, BS, N> {
/// The duration of a slot.
pub slot_duration: SlotDuration,
/// The client to interact with the chain.
pub client: Arc<C>,
/// A select chain implementation to select the best block.
pub select_chain: SC,
/// The block import.
pub block_import: I,
/// The proposer factory to build proposer instances.
pub proposer_factory: PF,
/// The sync oracle that can give us the current sync status.
pub sync_oracle: SO,
/// Hook into the sync module to control the justification sync process.
pub justification_sync_link: L,
/// Something that can create the inherent data providers.
pub create_inherent_data_providers: CIDP,
/// Should we force the authoring of blocks?
pub force_authoring: bool,
/// The backoff strategy when we miss slots.
pub backoff_authoring_blocks: Option<BS>,
/// The keystore used by the node.
pub keystore: KeystorePtr,
/// The proportion of the slot dedicated to proposing.
///
/// The block proposing will be limited to this proportion of the slot from the starting of the
/// slot. However, the proposing can still take longer when there is some lenience factor
/// applied, because there were no blocks produced for some slots.
pub block_proposal_slot_portion: SlotProportion,
/// The maximum proportion of the slot dedicated to proposing with any lenience factor applied
/// due to no blocks being produced.
pub max_block_proposal_slot_portion: Option<SlotProportion>,
/// Telemetry instance used to report telemetry metrics.
pub telemetry: Option<TelemetryHandle>,
/// Compatibility mode that should be used.
///
/// If in doubt, use `Default::default()`.
pub compatibility_mode: CompatibilityMode<N>,
}
/// Start the aura worker. The returned future should be run in a futures executor.
pub fn start_aura<P, B, C, SC, I, PF, SO, L, CIDP, BS, Error>(
StartAuraParams {
slot_duration,
client,
select_chain,
block_import,
proposer_factory,
sync_oracle,
justification_sync_link,
create_inherent_data_providers,
force_authoring,
backoff_authoring_blocks,
keystore,
block_proposal_slot_portion,
max_block_proposal_slot_portion,
telemetry,
compatibility_mode,
}: StartAuraParams<C, SC, I, PF, SO, L, CIDP, BS, NumberFor<B>>,
) -> Result<impl Future<Output = ()>, ConsensusError>
where
P: Pair,
P::Public: AppPublic + Member,
P::Signature: TryFrom<Vec<u8>> + Member + Codec,
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + AuxStore + HeaderBackend<B> + Send + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
SC: SelectChain<B>,
I: BlockImport<B> + Send + Sync + 'static,
PF: Environment<B, Error = Error> + Send + Sync + 'static,
PF::Proposer: Proposer<B, Error = Error>,
SO: SyncOracle + Send + Sync + Clone,
L: pezsc_consensus::JustificationSyncLink<B>,
CIDP: CreateInherentDataProviders<B, ()> + Send + 'static,
CIDP::InherentDataProviders: InherentDataProviderExt + Send,
BS: BackoffAuthoringBlocksStrategy<NumberFor<B>> + Send + Sync + 'static,
Error: std::error::Error + Send + From<ConsensusError> + 'static,
{
let worker = build_aura_worker::<P, _, _, _, _, _, _, _, _>(BuildAuraWorkerParams {
client,
block_import,
proposer_factory,
keystore,
sync_oracle: sync_oracle.clone(),
justification_sync_link,
force_authoring,
backoff_authoring_blocks,
telemetry,
block_proposal_slot_portion,
max_block_proposal_slot_portion,
compatibility_mode,
});
Ok(pezsc_consensus_slots::start_slot_worker(
slot_duration,
select_chain,
SimpleSlotWorkerToSlotWorker(worker),
sync_oracle,
create_inherent_data_providers,
))
}
/// Parameters of [`build_aura_worker`].
pub struct BuildAuraWorkerParams<C, I, PF, SO, L, BS, N> {
/// The client to interact with the chain.
pub client: Arc<C>,
/// The block import.
pub block_import: I,
/// The proposer factory to build proposer instances.
pub proposer_factory: PF,
/// The sync oracle that can give us the current sync status.
pub sync_oracle: SO,
/// Hook into the sync module to control the justification sync process.
pub justification_sync_link: L,
/// Should we force the authoring of blocks?
pub force_authoring: bool,
/// The backoff strategy when we miss slots.
pub backoff_authoring_blocks: Option<BS>,
/// The keystore used by the node.
pub keystore: KeystorePtr,
/// The proportion of the slot dedicated to proposing.
///
/// The block proposing will be limited to this proportion of the slot from the starting of the
/// slot. However, the proposing can still take longer when there is some lenience factor
/// applied, because there were no blocks produced for some slots.
pub block_proposal_slot_portion: SlotProportion,
/// The maximum proportion of the slot dedicated to proposing with any lenience factor applied
/// due to no blocks being produced.
pub max_block_proposal_slot_portion: Option<SlotProportion>,
/// Telemetry instance used to report telemetry metrics.
pub telemetry: Option<TelemetryHandle>,
/// Compatibility mode that should be used.
///
/// If in doubt, use `Default::default()`.
pub compatibility_mode: CompatibilityMode<N>,
}
/// Build the aura worker.
///
/// The caller is responsible for running this worker, otherwise it will do nothing.
pub fn build_aura_worker<P, B, C, PF, I, SO, L, BS, Error>(
BuildAuraWorkerParams {
client,
block_import,
proposer_factory,
sync_oracle,
justification_sync_link,
backoff_authoring_blocks,
keystore,
block_proposal_slot_portion,
max_block_proposal_slot_portion,
telemetry,
force_authoring,
compatibility_mode,
}: BuildAuraWorkerParams<C, I, PF, SO, L, BS, NumberFor<B>>,
) -> impl pezsc_consensus_slots::SimpleSlotWorker<
B,
Proposer = PF::Proposer,
BlockImport = I,
SyncOracle = SO,
JustificationSyncLink = L,
Claim = P::Public,
AuxData = Vec<AuthorityId<P>>,
>
where
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + AuxStore + HeaderBackend<B> + Send + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
PF: Environment<B, Error = Error> + Send + Sync + 'static,
PF::Proposer: Proposer<B, Error = Error>,
P: Pair,
P::Public: AppPublic + Member,
P::Signature: TryFrom<Vec<u8>> + Member + Codec,
I: BlockImport<B> + Send + Sync + 'static,
Error: std::error::Error + Send + From<ConsensusError> + 'static,
SO: SyncOracle + Send + Sync + Clone,
L: pezsc_consensus::JustificationSyncLink<B>,
BS: BackoffAuthoringBlocksStrategy<NumberFor<B>> + Send + Sync + 'static,
{
AuraWorker {
client,
block_import,
env: proposer_factory,
keystore,
sync_oracle,
justification_sync_link,
force_authoring,
backoff_authoring_blocks,
telemetry,
block_proposal_slot_portion,
max_block_proposal_slot_portion,
compatibility_mode,
_phantom: PhantomData::<fn() -> P>,
}
}
struct AuraWorker<C, E, I, P, SO, L, BS, N> {
client: Arc<C>,
block_import: I,
env: E,
keystore: KeystorePtr,
sync_oracle: SO,
justification_sync_link: L,
force_authoring: bool,
backoff_authoring_blocks: Option<BS>,
block_proposal_slot_portion: SlotProportion,
max_block_proposal_slot_portion: Option<SlotProportion>,
telemetry: Option<TelemetryHandle>,
compatibility_mode: CompatibilityMode<N>,
_phantom: PhantomData<fn() -> P>,
}
#[async_trait::async_trait]
impl<B, C, E, I, P, Error, SO, L, BS> pezsc_consensus_slots::SimpleSlotWorker<B>
for AuraWorker<C, E, I, P, SO, L, BS, NumberFor<B>>
where
B: BlockT,
C: ProvideRuntimeApi<B> + BlockOf + HeaderBackend<B> + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
E: Environment<B, Error = Error> + Send + Sync,
E::Proposer: Proposer<B, Error = Error>,
I: BlockImport<B> + Send + Sync + 'static,
P: Pair,
P::Public: AppPublic + Member,
P::Signature: TryFrom<Vec<u8>> + Member + Codec,
SO: SyncOracle + Send + Clone + Sync,
L: pezsc_consensus::JustificationSyncLink<B>,
BS: BackoffAuthoringBlocksStrategy<NumberFor<B>> + Send + Sync + 'static,
Error: std::error::Error + Send + From<ConsensusError> + 'static,
{
type BlockImport = I;
type SyncOracle = SO;
type JustificationSyncLink = L;
type CreateProposer =
Pin<Box<dyn Future<Output = Result<E::Proposer, ConsensusError>> + Send + 'static>>;
type Proposer = E::Proposer;
type Claim = P::Public;
type AuxData = Vec<AuthorityId<P>>;
fn logging_target(&self) -> &'static str {
"aura"
}
fn block_import(&mut self) -> &mut Self::BlockImport {
&mut self.block_import
}
fn aux_data(&self, header: &B::Header, _slot: Slot) -> Result<Self::AuxData, ConsensusError> {
fetch_authorities_from_runtime(
self.client.as_ref(),
header.hash(),
*header.number() + 1u32.into(),
&self.compatibility_mode,
)
}
fn authorities_len(&self, authorities: &Self::AuxData) -> Option<usize> {
Some(authorities.len())
}
async fn claim_slot(
&mut self,
_header: &B::Header,
slot: Slot,
authorities: &Self::AuxData,
) -> Option<Self::Claim> {
crate::standalone::claim_slot::<P>(slot, authorities, &self.keystore).await
}
fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec<pezsp_runtime::DigestItem> {
vec![crate::standalone::pre_digest::<P>(slot)]
}
async fn block_import_params(
&self,
header: B::Header,
header_hash: &B::Hash,
body: Vec<B::Extrinsic>,
storage_changes: StorageChanges<B>,
public: Self::Claim,
_authorities: Self::AuxData,
) -> Result<pezsc_consensus::BlockImportParams<B>, ConsensusError> {
let signature_digest_item =
crate::standalone::seal::<_, P>(header_hash, &public, &self.keystore)?;
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
import_block.post_digests.push(signature_digest_item);
import_block.body = Some(body);
import_block.state_action =
StateAction::ApplyChanges(pezsc_consensus::StorageChanges::Changes(storage_changes));
import_block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
Ok(import_block)
}
fn force_authoring(&self) -> bool {
self.force_authoring
}
fn should_backoff(&self, slot: Slot, chain_head: &B::Header) -> bool {
if let Some(ref strategy) = self.backoff_authoring_blocks {
if let Ok(chain_head_slot) = find_pre_digest::<B, P::Signature>(chain_head) {
return strategy.should_backoff(
*chain_head.number(),
chain_head_slot,
self.client.info().finalized_number,
slot,
self.logging_target(),
);
}
}
false
}
fn sync_oracle(&mut self) -> &mut Self::SyncOracle {
&mut self.sync_oracle
}
fn justification_sync_link(&mut self) -> &mut Self::JustificationSyncLink {
&mut self.justification_sync_link
}
fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer {
self.env
.init(block)
.map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))
.boxed()
}
fn telemetry(&self) -> Option<TelemetryHandle> {
self.telemetry.clone()
}
fn proposing_remaining_duration(&self, slot_info: &SlotInfo<B>) -> std::time::Duration {
let parent_slot = find_pre_digest::<B, P::Signature>(&slot_info.chain_head).ok();
pezsc_consensus_slots::proposing_remaining_duration(
parent_slot,
slot_info,
&self.block_proposal_slot_portion,
self.max_block_proposal_slot_portion.as_ref(),
pezsc_consensus_slots::SlotLenienceType::Exponential,
self.logging_target(),
)
}
}
/// Aura Errors
#[derive(Debug, thiserror::Error)]
pub enum Error<B: BlockT> {
/// Multiple Aura pre-runtime headers
#[error("Multiple Aura pre-runtime headers")]
MultipleHeaders,
/// No Aura pre-runtime digest found
#[error("No Aura pre-runtime digest found")]
NoDigestFound,
/// Header is unsealed
#[error("Header {0:?} is unsealed")]
HeaderUnsealed(B::Hash),
/// Header has a bad seal
#[error("Header {0:?} has a bad seal")]
HeaderBadSeal(B::Hash),
/// Slot Author not found
#[error("Slot Author not found")]
SlotAuthorNotFound,
/// Bad signature
#[error("Bad signature on {0:?}")]
BadSignature(B::Hash),
/// Client Error
#[error(transparent)]
Client(pezsp_blockchain::Error),
/// Inherents Error
#[error("Inherent error: {0}")]
Inherent(pezsp_inherents::Error),
}
impl<B: BlockT> From<Error<B>> for String {
fn from(error: Error<B>) -> String {
error.to_string()
}
}
impl<B: BlockT> From<crate::standalone::PreDigestLookupError> for Error<B> {
fn from(e: crate::standalone::PreDigestLookupError) -> Self {
match e {
crate::standalone::PreDigestLookupError::MultipleHeaders => Error::MultipleHeaders,
crate::standalone::PreDigestLookupError::NoDigestFound => Error::NoDigestFound,
}
}
}
fn fetch_authorities_from_runtime<A, B, C>(
client: &C,
parent_hash: B::Hash,
context_block_number: NumberFor<B>,
compatibility_mode: &CompatibilityMode<NumberFor<B>>,
) -> Result<Vec<A>, ConsensusError>
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
let runtime_api = client.runtime_api();
match compatibility_mode {
CompatibilityMode::None => {},
// Use `initialize_block` until we hit the block that should disable the mode.
CompatibilityMode::UseInitializeBlock { until } =>
if *until > context_block_number {
runtime_api
.initialize_block(
parent_hash,
&B::Header::new(
context_block_number,
Default::default(),
Default::default(),
parent_hash,
Default::default(),
),
)
.map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
},
}
runtime_api
.authorities(parent_hash)
.ok()
.ok_or(ConsensusError::InvalidAuthoritiesSet)
}
#[cfg(test)]
mod tests {
use super::*;
use parking_lot::Mutex;
use pezsc_block_builder::BlockBuilderBuilder;
use pezsc_client_api::BlockchainEvents;
use pezsc_consensus::BoxJustificationImport;
use pezsc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker};
use pezsc_keystore::LocalKeystore;
use pezsc_network_test::{Block as TestBlock, *};
use pezsp_application_crypto::{key_types::AURA, AppCrypto};
use pezsp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal};
use pezsp_consensus_aura::sr25519::AuthorityPair;
use pezsp_inherents::InherentData;
use pezsp_keyring::sr25519::Keyring;
use pezsp_keystore::Keystore;
use pezsp_runtime::{
traits::{Block as BlockT, Header as _},
Digest,
};
use pezsp_timestamp::Timestamp;
use std::{
task::Poll,
time::{Duration, Instant},
};
use bizinikiwi_test_runtime_client::{
runtime::{Header, H256},
TestClient,
};
const SLOT_DURATION_MS: u64 = 1000;
type Error = pezsp_blockchain::Error;
struct DummyFactory(Arc<TestClient>);
struct DummyProposer(Arc<TestClient>);
impl Environment<TestBlock> for DummyFactory {
type Proposer = DummyProposer;
type CreateProposer = futures::future::Ready<Result<DummyProposer, Error>>;
type Error = Error;
fn init(&mut self, _: &<TestBlock as BlockT>::Header) -> Self::CreateProposer {
futures::future::ready(Ok(DummyProposer(self.0.clone())))
}
}
impl Proposer<TestBlock> for DummyProposer {
type Error = Error;
type Proposal = future::Ready<Result<Proposal<TestBlock, ()>, Error>>;
type ProofRecording = DisableProofRecording;
type Proof = ();
fn propose(
self,
_: InherentData,
digests: Digest,
_: Duration,
_: Option<usize>,
) -> Self::Proposal {
let r = BlockBuilderBuilder::new(&*self.0)
.on_parent_block(self.0.chain_info().best_hash)
.fetch_parent_block_number(&*self.0)
.unwrap()
.with_inherent_digests(digests)
.build()
.unwrap()
.build();
future::ready(r.map(|b| Proposal {
block: b.block,
proof: (),
storage_changes: b.storage_changes,
}))
}
}
type AuraVerifier = import_queue::AuraVerifier<
PeersFullClient,
AuthorityPair,
Box<
dyn CreateInherentDataProviders<
TestBlock,
(),
InherentDataProviders = (InherentDataProvider,),
>,
>,
TestBlock,
>;
type AuraPeer = Peer<(), PeersClient>;
#[derive(Default)]
pub struct AuraTestNet {
peers: Vec<AuraPeer>,
}
impl TestNetFactory for AuraTestNet {
type Verifier = AuraVerifier;
type PeerData = ();
type BlockImport = PeersClient;
fn make_verifier(&self, client: PeersClient, _peer_data: &()) -> Self::Verifier {
let client = client.as_client();
let slot_duration = slot_duration(&*client).expect("slot duration available");
assert_eq!(slot_duration.as_millis() as u64, SLOT_DURATION_MS);
import_queue::AuraVerifier::new(
client,
Box::new(|_, _| async {
let slot = InherentDataProvider::from_timestamp_and_slot_duration(
Timestamp::current(),
SlotDuration::from_millis(SLOT_DURATION_MS),
);
Ok((slot,))
}),
CheckForEquivocation::Yes,
None,
CompatibilityMode::None,
)
}
fn make_block_import(
&self,
client: PeersClient,
) -> (
BlockImportAdapter<Self::BlockImport>,
Option<BoxJustificationImport<Block>>,
Self::PeerData,
) {
(client.as_block_import(), None, ())
}
fn peer(&mut self, i: usize) -> &mut AuraPeer {
&mut self.peers[i]
}
fn peers(&self) -> &Vec<AuraPeer> {
&self.peers
}
fn peers_mut(&mut self) -> &mut Vec<AuraPeer> {
&mut self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<AuraPeer>)>(&mut self, closure: F) {
closure(&mut self.peers);
}
}
#[tokio::test]
async fn authoring_blocks() {
pezsp_tracing::try_init_simple();
let net = AuraTestNet::new(3);
let peers = &[(0, Keyring::Alice), (1, Keyring::Bob), (2, Keyring::Charlie)];
let net = Arc::new(Mutex::new(net));
let mut import_notifications = Vec::new();
let mut aura_futures = Vec::new();
let mut keystore_paths = Vec::new();
for (peer_id, key) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
let client = peer.client().as_client();
let select_chain = peer.select_chain().expect("full client has a select chain");
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = Arc::new(
LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."),
);
keystore
.sr25519_generate_new(AURA, Some(&key.to_seed()))
.expect("Creates authority key");
keystore_paths.push(keystore_path);
let environ = DummyFactory(client.clone());
import_notifications.push(
client
.import_notification_stream()
.take_while(|n| {
future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5))
})
.for_each(move |_| future::ready(())),
);
let slot_duration = slot_duration(&*client).expect("slot duration available");
aura_futures.push(
start_aura::<AuthorityPair, _, _, _, _, _, _, _, _, _, _>(StartAuraParams {
slot_duration,
block_import: client.clone(),
select_chain,
client,
proposer_factory: environ,
sync_oracle: DummyOracle,
justification_sync_link: (),
create_inherent_data_providers: |_, _| async {
let slot = InherentDataProvider::from_timestamp_and_slot_duration(
Timestamp::current(),
SlotDuration::from_millis(SLOT_DURATION_MS),
);
Ok((slot,))
},
force_authoring: false,
backoff_authoring_blocks: Some(
BackoffAuthoringOnFinalizedHeadLagging::default(),
),
keystore,
block_proposal_slot_portion: SlotProportion::new(0.5),
max_block_proposal_slot_portion: None,
telemetry: None,
compatibility_mode: CompatibilityMode::None,
})
.expect("Starts aura"),
);
}
future::select(
future::poll_fn(move |cx| {
net.lock().poll(cx);
Poll::<()>::Pending
}),
future::select(future::join_all(aura_futures), future::join_all(import_notifications)),
)
.await;
}
#[tokio::test]
async fn current_node_authority_should_claim_slot() {
let net = AuraTestNet::new(4);
let mut authorities = vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into(),
];
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore.");
let public = keystore
.sr25519_generate_new(AuthorityPair::ID, None)
.expect("Key should be created");
authorities.push(public.into());
let net = Arc::new(Mutex::new(net));
let mut net = net.lock();
let peer = net.peer(3);
let client = peer.client().as_client();
let environ = DummyFactory(client.clone());
let mut worker = AuraWorker {
client: client.clone(),
block_import: client,
env: environ,
keystore: keystore.into(),
sync_oracle: DummyOracle,
justification_sync_link: (),
force_authoring: false,
backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()),
telemetry: None,
block_proposal_slot_portion: SlotProportion::new(0.5),
max_block_proposal_slot_portion: None,
compatibility_mode: Default::default(),
_phantom: PhantomData::<fn() -> AuthorityPair>,
};
let head = Header::new(
1,
H256::from_low_u64_be(0),
H256::from_low_u64_be(0),
Default::default(),
Default::default(),
);
assert!(worker.claim_slot(&head, 0.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 1.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 2.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 3.into(), &authorities).await.is_some());
assert!(worker.claim_slot(&head, 4.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 5.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 6.into(), &authorities).await.is_none());
assert!(worker.claim_slot(&head, 7.into(), &authorities).await.is_some());
}
#[tokio::test]
async fn on_slot_returns_correct_block() {
let net = AuraTestNet::new(4);
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore.");
keystore
.sr25519_generate_new(AuthorityPair::ID, Some(&Keyring::Alice.to_seed()))
.expect("Key should be created");
let net = Arc::new(Mutex::new(net));
let mut net = net.lock();
let peer = net.peer(3);
let client = peer.client().as_client();
let environ = DummyFactory(client.clone());
let mut worker = AuraWorker {
client: client.clone(),
block_import: client.clone(),
env: environ,
keystore: keystore.into(),
sync_oracle: DummyOracle,
justification_sync_link: (),
force_authoring: false,
backoff_authoring_blocks: Option::<()>::None,
telemetry: None,
block_proposal_slot_portion: SlotProportion::new(0.5),
max_block_proposal_slot_portion: None,
compatibility_mode: Default::default(),
_phantom: PhantomData::<fn() -> AuthorityPair>,
};
let head = client.expect_header(client.info().genesis_hash).unwrap();
let res = worker
.on_slot(SlotInfo {
slot: 0.into(),
ends_at: Instant::now() + Duration::from_secs(100),
create_inherent_data: Box::new(()),
duration: Duration::from_millis(1000),
chain_head: head,
block_size_limit: None,
})
.await
.unwrap();
// The returned block should be imported and we should be able to get its header by now.
assert!(client.header(res.block.hash()).unwrap().is_some());
}
}
@@ -0,0 +1,357 @@
// 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/>.
//! Standalone functions used within the implementation of Aura.
use std::fmt::Debug;
use log::trace;
use codec::Codec;
use pezsc_client_api::UsageProvider;
use pezsp_api::{Core, ProvideRuntimeApi};
use pezsp_application_crypto::{AppCrypto, AppPublic};
use pezsp_blockchain::Result as CResult;
use pezsp_consensus::Error as ConsensusError;
use pezsp_consensus_slots::Slot;
use pezsp_core::crypto::{ByteArray, Pair};
use pezsp_keystore::KeystorePtr;
use pezsp_runtime::{
traits::{Block as BlockT, Header, NumberFor, Zero},
DigestItem,
};
pub use pezsc_consensus_slots::check_equivocation;
use super::{
AuraApi, AuthorityId, CompatibilityMode, CompatibleDigestItem, SlotDuration, LOG_TARGET,
};
/// Get the slot duration for Aura by reading from a runtime API at the best block's state.
pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration>
where
A: Codec,
B: BlockT,
C: ProvideRuntimeApi<B> + UsageProvider<B>,
C::Api: AuraApi<B, A>,
{
slot_duration_at(client, client.usage_info().chain.best_hash)
}
/// Get the slot duration for Aura by reading from a runtime API at a given block's state.
pub fn slot_duration_at<A, B, C>(client: &C, block_hash: B::Hash) -> CResult<SlotDuration>
where
A: Codec,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
client.runtime_api().slot_duration(block_hash).map_err(|err| err.into())
}
/// Get the slot author for given block along with authorities.
pub fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
if authorities.is_empty() {
return None;
}
let idx = *slot % (authorities.len() as u64);
assert!(
idx <= usize::MAX as u64,
"It is impossible to have a vector with length beyond the address space; qed",
);
let current_author = authorities.get(idx as usize).expect(
"authorities not empty; index constrained to list length;this is a valid index; qed",
);
Some(current_author)
}
/// Attempt to claim a slot using a keystore.
///
/// This returns `None` if the slot author is not locally controlled, and `Some` if it is,
/// with the public key of the slot author.
pub async fn claim_slot<P: Pair>(
slot: Slot,
authorities: &[AuthorityId<P>],
keystore: &KeystorePtr,
) -> Option<P::Public> {
let expected_author = slot_author::<P>(slot, authorities);
expected_author.and_then(|p| {
if keystore.has_keys(&[(p.to_raw_vec(), pezsp_application_crypto::key_types::AURA)]) {
Some(p.clone())
} else {
None
}
})
}
/// Produce the pre-runtime digest containing the slot info.
///
/// This is intended to be put into the block header prior to runtime execution,
/// so the runtime can read the slot in this way.
pub fn pre_digest<P: Pair>(slot: Slot) -> pezsp_runtime::DigestItem
where
P::Signature: Codec,
{
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)
}
/// Produce the seal digest item by signing the hash of a block.
///
/// Note that after this is added to a block header, the hash of the block will change.
pub fn seal<Hash, P>(
header_hash: &Hash,
public: &P::Public,
keystore: &KeystorePtr,
) -> Result<pezsp_runtime::DigestItem, ConsensusError>
where
Hash: AsRef<[u8]>,
P: Pair,
P::Signature: Codec + TryFrom<Vec<u8>>,
P::Public: AppPublic,
{
let signature = keystore
.sign_with(
<AuthorityId<P> as AppCrypto>::ID,
<AuthorityId<P> as AppCrypto>::CRYPTO_ID,
public.as_slice(),
header_hash.as_ref(),
)
.map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))?
.ok_or_else(|| {
ConsensusError::CannotSign(format!("Could not find key in keystore. Key: {:?}", public))
})?;
let signature = signature
.as_slice()
.try_into()
.map_err(|_| ConsensusError::InvalidSignature(signature, public.to_raw_vec()))?;
let signature_digest_item =
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
Ok(signature_digest_item)
}
/// Errors in pre-digest lookup.
#[derive(Debug, thiserror::Error)]
pub enum PreDigestLookupError {
/// Multiple Aura pre-runtime headers
#[error("Multiple Aura pre-runtime headers")]
MultipleHeaders,
/// No Aura pre-runtime digest found
#[error("No Aura pre-runtime digest found")]
NoDigestFound,
}
/// Extract a pre-digest from a block header.
///
/// This fails if there is no pre-digest or there are multiple.
///
/// Returns the `slot` stored in the pre-digest or an error if no pre-digest was found.
pub fn find_pre_digest<B: BlockT, Signature: Codec>(
header: &B::Header,
) -> Result<Slot, PreDigestLookupError> {
if header.number().is_zero() {
return Ok(0.into());
}
let mut pre_digest: Option<Slot> = None;
for log in header.digest().logs() {
trace!(target: LOG_TARGET, "Checking log {:?}", log);
match (CompatibleDigestItem::<Signature>::as_aura_pre_digest(log), pre_digest.is_some()) {
(Some(_), true) => return Err(PreDigestLookupError::MultipleHeaders),
(None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or_else(|| PreDigestLookupError::NoDigestFound)
}
/// Fetch the current set of authorities from the runtime at a specific block.
///
/// The compatibility mode and context block number informs this function whether
/// to initialize the hypothetical block created by the runtime API as backwards compatibility
/// for older chains.
pub fn fetch_authorities_with_compatibility_mode<A, B, C>(
client: &C,
parent_hash: B::Hash,
context_block_number: NumberFor<B>,
compatibility_mode: &CompatibilityMode<NumberFor<B>>,
) -> Result<Vec<A>, ConsensusError>
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
let runtime_api = client.runtime_api();
match compatibility_mode {
CompatibilityMode::None => {},
// Use `initialize_block` until we hit the block that should disable the mode.
CompatibilityMode::UseInitializeBlock { until } =>
if *until > context_block_number {
runtime_api
.initialize_block(
parent_hash,
&B::Header::new(
context_block_number,
Default::default(),
Default::default(),
parent_hash,
Default::default(),
),
)
.map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
},
}
runtime_api
.authorities(parent_hash)
.ok()
.ok_or(ConsensusError::InvalidAuthoritiesSet)
}
/// Load the current set of authorities from a runtime at a specific block.
pub fn fetch_authorities<A, B, C>(
client: &C,
parent_hash: B::Hash,
) -> Result<Vec<A>, ConsensusError>
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
client
.runtime_api()
.authorities(parent_hash)
.ok()
.ok_or(ConsensusError::InvalidAuthoritiesSet)
}
/// Errors in slot and seal verification.
#[derive(Debug, thiserror::Error)]
pub enum SealVerificationError<Header> {
/// Header is deferred to the future.
#[error("Header slot is in the future")]
Deferred(Header, Slot),
/// The header has no seal digest.
#[error("Header is unsealed.")]
Unsealed,
/// The header has a malformed seal.
#[error("Header has a malformed seal")]
BadSeal,
/// The header has a bad signature.
#[error("Header has a bad signature")]
BadSignature,
/// No slot author found.
#[error("No slot author for provided slot")]
SlotAuthorNotFound,
/// Header has no valid slot pre-digest.
#[error("Header has no valid slot pre-digest")]
InvalidPreDigest(PreDigestLookupError),
}
/// Check a header has been signed by the right key. If the slot is too far in the future, an error
/// will be returned. If it's successful, returns the pre-header (i.e. without the seal),
/// the slot, and the digest item containing the seal.
///
/// Note that this does not check for equivocations, and [`check_equivocation`] is recommended
/// for that purpose.
///
/// This digest item will always return `Some` when used with `as_aura_seal`.
pub fn check_header_slot_and_seal<B: BlockT, P: Pair>(
slot_now: Slot,
mut header: B::Header,
authorities: &[AuthorityId<P>],
) -> Result<(B::Header, Slot, DigestItem), SealVerificationError<B::Header>>
where
P::Signature: Codec,
P::Public: Codec + PartialEq + Clone,
{
let seal = header.digest_mut().pop().ok_or(SealVerificationError::Unsealed)?;
let sig = seal.as_aura_seal().ok_or(SealVerificationError::BadSeal)?;
let slot = find_pre_digest::<B, P::Signature>(&header)
.map_err(SealVerificationError::InvalidPreDigest)?;
if slot > slot_now {
header.digest_mut().push(seal);
return Err(SealVerificationError::Deferred(header, slot));
} else {
// check the signature is valid under the expected authority and
// chain state.
let expected_author =
slot_author::<P>(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?;
let pre_hash = header.hash();
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
Ok((header, slot, seal))
} else {
Err(SealVerificationError::BadSignature)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsp_keyring::sr25519::Keyring;
#[test]
fn authorities_call_works() {
let client = bizinikiwi_test_runtime_client::new();
assert_eq!(client.chain_info().best_number, 0);
assert_eq!(
fetch_authorities_with_compatibility_mode(
&client,
client.chain_info().best_hash,
1,
&CompatibilityMode::None
)
.unwrap(),
vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into()
]
);
assert_eq!(
fetch_authorities(&client, client.chain_info().best_hash).unwrap(),
vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into()
]
);
}
}