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,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,
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user