feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
@@ -0,0 +1,410 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Block import helpers.
use serde::{Deserialize, Serialize};
use sp_runtime::{
traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor},
DigestItem, Justification, Justifications,
};
use std::{any::Any, borrow::Cow, collections::HashMap, sync::Arc};
use sp_consensus::{BlockOrigin, Error};
/// Block import result.
#[derive(Debug, PartialEq, Eq)]
pub enum ImportResult {
/// Block imported.
Imported(ImportedAux),
/// Already in the blockchain.
AlreadyInChain,
/// Block or parent is known to be bad.
KnownBad,
/// Block parent is not in the chain.
UnknownParent,
/// Parent state is missing.
MissingState,
}
/// Auxiliary data associated with an imported block result.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ImportedAux {
/// Only the header has been imported. Block body verification was skipped.
pub header_only: bool,
/// Clear all pending justification requests.
pub clear_justification_requests: bool,
/// Request a justification for the given block.
pub needs_justification: bool,
/// Received a bad justification.
pub bad_justification: bool,
/// Whether the block that was imported is the new best block.
pub is_new_best: bool,
}
impl ImportResult {
/// Returns default value for `ImportResult::Imported` with
/// `clear_justification_requests`, `needs_justification`,
/// `bad_justification` set to false.
pub fn imported(is_new_best: bool) -> ImportResult {
let aux = ImportedAux { is_new_best, ..Default::default() };
ImportResult::Imported(aux)
}
/// Handles any necessary request for justifications (or clearing of pending requests) based on
/// the outcome of this block import.
pub fn handle_justification<B>(
&self,
hash: &B::Hash,
number: NumberFor<B>,
justification_sync_link: &dyn JustificationSyncLink<B>,
) where
B: BlockT,
{
match self {
ImportResult::Imported(aux) => {
if aux.clear_justification_requests {
justification_sync_link.clear_justification_requests();
}
if aux.needs_justification {
justification_sync_link.request_justification(hash, number);
}
},
_ => {},
}
}
}
/// Fork choice strategy.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ForkChoiceStrategy {
/// Longest chain fork choice.
LongestChain,
/// Custom fork choice rule, where true indicates the new block should be the best block.
Custom(bool),
}
/// Data required to check validity of a Block.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BlockCheckParams<Block: BlockT> {
/// Hash of the block that we verify.
pub hash: Block::Hash,
/// Block number of the block that we verify.
pub number: NumberFor<Block>,
/// Parent hash of the block that we verify.
pub parent_hash: Block::Hash,
/// Allow importing the block skipping state verification if parent state is missing.
pub allow_missing_state: bool,
/// Allow importing the block if parent block is missing.
pub allow_missing_parent: bool,
/// Re-validate existing block.
pub import_existing: bool,
}
/// Precomputed storage.
pub enum StorageChanges<Block: BlockT> {
/// Changes coming from block execution.
Changes(sp_state_machine::StorageChanges<HashingFor<Block>>),
/// Whole new state.
Import(ImportedState<Block>),
}
/// Imported state data. A vector of key-value pairs that should form a trie.
#[derive(PartialEq, Eq, Clone)]
pub struct ImportedState<B: BlockT> {
/// Target block hash.
pub block: B::Hash,
/// State keys and values.
pub state: sp_state_machine::KeyValueStates,
}
impl<B: BlockT> std::fmt::Debug for ImportedState<B> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("ImportedState").field("block", &self.block).finish()
}
}
/// Defines how a new state is computed for a given imported block.
pub enum StateAction<Block: BlockT> {
/// Apply precomputed changes coming from block execution or state sync.
ApplyChanges(StorageChanges<Block>),
/// Execute block body (required) and compute state.
Execute,
/// Execute block body if parent state is available and compute state.
ExecuteIfPossible,
/// Don't execute or import state.
Skip,
}
impl<Block: BlockT> StateAction<Block> {
/// Check if execution checks that require runtime calls should be skipped.
pub fn skip_execution_checks(&self) -> bool {
match self {
StateAction::ApplyChanges(_) |
StateAction::Execute |
StateAction::ExecuteIfPossible => false,
StateAction::Skip => true,
}
}
}
impl<Block: BlockT> From<StorageChanges<Block>> for StateAction<Block> {
fn from(value: StorageChanges<Block>) -> Self {
Self::ApplyChanges(value)
}
}
impl<Block: BlockT> From<sp_state_machine::StorageChanges<HashingFor<Block>>>
for StateAction<Block>
{
fn from(value: sp_state_machine::StorageChanges<HashingFor<Block>>) -> Self {
Self::ApplyChanges(StorageChanges::Changes(value))
}
}
/// Data required to import a Block.
#[non_exhaustive]
pub struct BlockImportParams<Block: BlockT> {
/// Origin of the Block
pub origin: BlockOrigin,
/// The header, without consensus post-digests applied. This should be in the same
/// state as it comes out of the runtime.
///
/// Consensus engines which alter the header (by adding post-runtime digests)
/// should strip those off in the initial verification process and pass them
/// via the `post_digests` field. During block authorship, they should
/// not be pushed to the header directly.
///
/// The reason for this distinction is so the header can be directly
/// re-executed in a runtime that checks digest equivalence -- the
/// post-runtime digests are pushed back on after.
pub header: Block::Header,
/// Justification(s) provided for this block from the outside.
pub justifications: Option<Justifications>,
/// Digest items that have been added after the runtime for external
/// work, like a consensus signature.
pub post_digests: Vec<DigestItem>,
/// The body of the block.
pub body: Option<Vec<Block::Extrinsic>>,
/// Indexed transaction body of the block.
pub indexed_body: Option<Vec<Vec<u8>>>,
/// Specify how the new state is computed.
pub state_action: StateAction<Block>,
/// Is this block finalized already?
/// `true` implies instant finality.
pub finalized: bool,
/// Intermediate values that are interpreted by block importers. Each block importer,
/// upon handling a value, removes it from the intermediate list. The final block importer
/// rejects block import if there are still intermediate values that remain unhandled.
pub intermediates: HashMap<Cow<'static, [u8]>, Box<dyn Any + Send>>,
/// Auxiliary consensus data produced by the block.
/// Contains a list of key-value pairs. If values are `None`, the keys will be deleted. These
/// changes will be applied to `AuxStore` database all as one batch, which is more efficient
/// than updating `AuxStore` directly.
pub auxiliary: Vec<(Vec<u8>, Option<Vec<u8>>)>,
/// Fork choice strategy of this import. This should only be set by a
/// synchronous import, otherwise it may race against other imports.
/// `None` indicates that the current verifier or importer cannot yet
/// determine the fork choice value, and it expects subsequent importer
/// to modify it. If `None` is passed all the way down to bottom block
/// importer, the import fails with an `IncompletePipeline` error.
pub fork_choice: Option<ForkChoiceStrategy>,
/// Re-validate existing block.
pub import_existing: bool,
/// Whether to create "block gap" in case this block doesn't have parent.
pub create_gap: bool,
/// Cached full header hash (with post-digests applied).
pub post_hash: Option<Block::Hash>,
}
impl<Block: BlockT> BlockImportParams<Block> {
/// Create a new block import params.
pub fn new(origin: BlockOrigin, header: Block::Header) -> Self {
Self {
origin,
header,
justifications: None,
post_digests: Vec::new(),
body: None,
indexed_body: None,
state_action: StateAction::Execute,
finalized: false,
intermediates: HashMap::new(),
auxiliary: Vec::new(),
fork_choice: None,
import_existing: false,
create_gap: true,
post_hash: None,
}
}
/// Get the full header hash (with post-digests applied).
pub fn post_hash(&self) -> Block::Hash {
if let Some(hash) = self.post_hash {
hash
} else {
self.post_header().hash()
}
}
/// Get the post header.
pub fn post_header(&self) -> Block::Header {
if self.post_digests.is_empty() {
self.header.clone()
} else {
let mut hdr = self.header.clone();
for digest_item in &self.post_digests {
hdr.digest_mut().push(digest_item.clone());
}
hdr
}
}
/// Insert intermediate by given key.
pub fn insert_intermediate<T: 'static + Send>(&mut self, key: &'static [u8], value: T) {
self.intermediates.insert(Cow::from(key), Box::new(value));
}
/// Remove and return intermediate by given key.
pub fn remove_intermediate<T: 'static>(&mut self, key: &[u8]) -> Result<T, Error> {
let (k, v) = self.intermediates.remove_entry(key).ok_or(Error::NoIntermediate)?;
v.downcast::<T>().map(|v| *v).map_err(|v| {
self.intermediates.insert(k, v);
Error::InvalidIntermediate
})
}
/// Get a reference to a given intermediate.
pub fn get_intermediate<T: 'static>(&self, key: &[u8]) -> Result<&T, Error> {
self.intermediates
.get(key)
.ok_or(Error::NoIntermediate)?
.downcast_ref::<T>()
.ok_or(Error::InvalidIntermediate)
}
/// Get a mutable reference to a given intermediate.
pub fn get_intermediate_mut<T: 'static>(&mut self, key: &[u8]) -> Result<&mut T, Error> {
self.intermediates
.get_mut(key)
.ok_or(Error::NoIntermediate)?
.downcast_mut::<T>()
.ok_or(Error::InvalidIntermediate)
}
/// Check if this block contains state import action
pub fn with_state(&self) -> bool {
matches!(self.state_action, StateAction::ApplyChanges(StorageChanges::Import(_)))
}
}
/// Block import trait.
#[async_trait::async_trait]
pub trait BlockImport<B: BlockT> {
/// The error type.
type Error: std::error::Error + Send + 'static;
/// Check block preconditions.
async fn check_block(&self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error>;
/// Import a block.
async fn import_block(&self, block: BlockImportParams<B>) -> Result<ImportResult, Self::Error>;
}
#[async_trait::async_trait]
impl<B: BlockT> BlockImport<B> for crate::import_queue::BoxBlockImport<B> {
type Error = sp_consensus::error::Error;
/// Check block preconditions.
async fn check_block(&self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error> {
(**self).check_block(block).await
}
/// Import a block.
async fn import_block(&self, block: BlockImportParams<B>) -> Result<ImportResult, Self::Error> {
(**self).import_block(block).await
}
}
#[async_trait::async_trait]
impl<B: BlockT, T, E: std::error::Error + Send + 'static> BlockImport<B> for Arc<T>
where
for<'r> &'r T: BlockImport<B, Error = E>,
T: Send + Sync,
{
type Error = E;
async fn check_block(&self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error> {
(&**self).check_block(block).await
}
async fn import_block(&self, block: BlockImportParams<B>) -> Result<ImportResult, Self::Error> {
(&**self).import_block(block).await
}
}
/// Justification import trait
#[async_trait::async_trait]
pub trait JustificationImport<B: BlockT> {
type Error: std::error::Error + Send + 'static;
/// Called by the import queue when it is started. Returns a list of justifications to request
/// from the network.
async fn on_start(&mut self) -> Vec<(B::Hash, NumberFor<B>)>;
/// Import a Block justification and finalize the given block.
async fn import_justification(
&mut self,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification,
) -> Result<(), Self::Error>;
}
/// Control the synchronization process of block justifications.
///
/// When importing blocks different consensus engines might require that
/// additional finality data is provided (i.e. a justification for the block).
/// This trait abstracts the required methods to issue those requests
pub trait JustificationSyncLink<B: BlockT>: Send + Sync {
/// Request a justification for the given block.
fn request_justification(&self, hash: &B::Hash, number: NumberFor<B>);
/// Clear all pending justification requests.
fn clear_justification_requests(&self);
}
impl<B: BlockT> JustificationSyncLink<B> for () {
fn request_justification(&self, _hash: &B::Hash, _number: NumberFor<B>) {}
fn clear_justification_requests(&self) {}
}
impl<B: BlockT, L: JustificationSyncLink<B>> JustificationSyncLink<B> for Arc<L> {
fn request_justification(&self, hash: &B::Hash, number: NumberFor<B>) {
L::request_justification(self, hash, number);
}
fn clear_justification_requests(&self) {
L::clear_justification_requests(self);
}
}
@@ -0,0 +1,424 @@
// This file is part of Substrate.
// 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/>.
//! Import Queue primitive: something which can verify and import blocks.
//!
//! This serves as an intermediate and abstracted step between synchronization
//! and import. Each mode of consensus will have its own requirements for block
//! verification. Some algorithms can verify in parallel, while others only
//! sequentially.
//!
//! The `ImportQueue` trait allows such verification strategies to be
//! instantiated. The `BasicQueue` and `BasicVerifier` traits allow serial
//! queues to be instantiated simply.
use log::{debug, trace};
use std::{
fmt,
time::{Duration, Instant},
};
use sp_consensus::{error::Error as ConsensusError, BlockOrigin};
use sp_runtime::{
traits::{Block as BlockT, Header as _, NumberFor},
Justifications,
};
use crate::{
block_import::{
BlockCheckParams, BlockImport, BlockImportParams, ImportResult, ImportedAux, ImportedState,
JustificationImport, StateAction,
},
metrics::Metrics,
};
pub use basic_queue::BasicQueue;
const LOG_TARGET: &str = "sync::import-queue";
/// A commonly-used Import Queue type.
///
/// This defines the transaction type of the `BasicQueue` to be the transaction type for a client.
pub type DefaultImportQueue<Block> = BasicQueue<Block>;
mod basic_queue;
pub mod buffered_link;
pub mod mock;
/// Shared block import struct used by the queue.
pub type BoxBlockImport<B> = Box<dyn BlockImport<B, Error = ConsensusError> + Send + Sync>;
/// Shared justification import struct used by the queue.
pub type BoxJustificationImport<B> =
Box<dyn JustificationImport<B, Error = ConsensusError> + Send + Sync>;
/// Maps to the RuntimeOrigin used by the network.
pub type RuntimeOrigin = sc_network_types::PeerId;
/// Block data used by the queue.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct IncomingBlock<B: BlockT> {
/// Block header hash.
pub hash: <B as BlockT>::Hash,
/// Block header if requested.
pub header: Option<<B as BlockT>::Header>,
/// Block body if requested.
pub body: Option<Vec<<B as BlockT>::Extrinsic>>,
/// Indexed block body if requested.
pub indexed_body: Option<Vec<Vec<u8>>>,
/// Justification(s) if requested.
pub justifications: Option<Justifications>,
/// The peer, we received this from
pub origin: Option<RuntimeOrigin>,
/// Allow importing the block skipping state verification if parent state is missing.
pub allow_missing_state: bool,
/// Skip block execution and state verification.
pub skip_execution: bool,
/// Re-validate existing block.
pub import_existing: bool,
/// Do not compute new state, but rather set it to the given set.
pub state: Option<ImportedState<B>>,
}
/// Verify a justification of a block
#[async_trait::async_trait]
pub trait Verifier<B: BlockT>: Send + Sync {
/// Verify the given block data and return the `BlockImportParams` to
/// continue the block import process.
async fn verify(&self, block: BlockImportParams<B>) -> Result<BlockImportParams<B>, String>;
}
/// Blocks import queue API.
///
/// The `import_*` methods can be called in order to send elements for the import queue to verify.
pub trait ImportQueueService<B: BlockT>: Send {
/// Import a bunch of blocks, every next block must be an ancestor of the previous block in the
/// list.
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
/// Import block justifications.
fn import_justifications(
&mut self,
who: RuntimeOrigin,
hash: B::Hash,
number: NumberFor<B>,
justifications: Justifications,
);
}
#[async_trait::async_trait]
pub trait ImportQueue<B: BlockT>: Send {
/// Get a copy of the handle to [`ImportQueueService`].
fn service(&self) -> Box<dyn ImportQueueService<B>>;
/// Get a reference to the handle to [`ImportQueueService`].
fn service_ref(&mut self) -> &mut dyn ImportQueueService<B>;
/// This method should behave in a way similar to `Future::poll`. It can register the current
/// task and notify later when more actions are ready to be polled. To continue the comparison,
/// it is as if this method always returned `Poll::Pending`.
fn poll_actions(&mut self, cx: &mut futures::task::Context, link: &dyn Link<B>);
/// Start asynchronous runner for import queue.
///
/// Takes an object implementing [`Link`] which allows the import queue to
/// influence the synchronization process.
async fn run(self, link: &dyn Link<B>);
}
/// The result of importing a justification.
#[derive(Debug, PartialEq)]
pub enum JustificationImportResult {
/// Justification was imported successfully.
Success,
/// Justification was not imported successfully.
Failure,
/// Justification was not imported successfully, because it is outdated.
OutdatedJustification,
}
/// Hooks that the verification queue can use to influence the synchronization
/// algorithm.
pub trait Link<B: BlockT>: Send + Sync {
/// Batch of blocks imported, with or without error.
fn blocks_processed(
&self,
_imported: usize,
_count: usize,
_results: Vec<(BlockImportResult<B>, B::Hash)>,
) {
}
/// Justification import result.
fn justification_imported(
&self,
_who: RuntimeOrigin,
_hash: &B::Hash,
_number: NumberFor<B>,
_import_result: JustificationImportResult,
) {
}
/// Request a justification for the given block.
fn request_justification(&self, _hash: &B::Hash, _number: NumberFor<B>) {}
}
/// Block import successful result.
#[derive(Debug, PartialEq)]
pub enum BlockImportStatus<BlockNumber: fmt::Debug + PartialEq> {
/// Imported known block.
ImportedKnown(BlockNumber, Option<RuntimeOrigin>),
/// Imported unknown block.
ImportedUnknown(BlockNumber, ImportedAux, Option<RuntimeOrigin>),
}
impl<BlockNumber: fmt::Debug + PartialEq> BlockImportStatus<BlockNumber> {
/// Returns the imported block number.
pub fn number(&self) -> &BlockNumber {
match self {
BlockImportStatus::ImportedKnown(n, _) |
BlockImportStatus::ImportedUnknown(n, _, _) => n,
}
}
}
/// Block import error.
#[derive(Debug, thiserror::Error)]
pub enum BlockImportError {
/// Block missed header, can't be imported
#[error("block is missing a header (origin = {0:?})")]
IncompleteHeader(Option<RuntimeOrigin>),
/// Block verification failed, can't be imported
#[error("block verification failed (origin = {0:?}): {1}")]
VerificationFailed(Option<RuntimeOrigin>, String),
/// Block is known to be Bad
#[error("bad block (origin = {0:?})")]
BadBlock(Option<RuntimeOrigin>),
/// Parent state is missing.
#[error("block is missing parent state")]
MissingState,
/// Block has an unknown parent
#[error("block has an unknown parent")]
UnknownParent,
/// Block import has been cancelled. This can happen if the parent block fails to be imported.
#[error("import has been cancelled")]
Cancelled,
/// Other error.
#[error("consensus error: {0}")]
Other(ConsensusError),
}
type BlockImportResult<B> = Result<BlockImportStatus<NumberFor<B>>, BlockImportError>;
/// Single block import function.
pub async fn import_single_block<B: BlockT, V: Verifier<B>>(
import_handle: &mut impl BlockImport<B, Error = ConsensusError>,
block_origin: BlockOrigin,
block: IncomingBlock<B>,
verifier: &V,
) -> BlockImportResult<B> {
match verify_single_block_metered(import_handle, block_origin, block, verifier, None).await? {
SingleBlockVerificationOutcome::Imported(import_status) => Ok(import_status),
SingleBlockVerificationOutcome::Verified(import_parameters) =>
import_single_block_metered(import_handle, import_parameters, None).await,
}
}
fn import_handler<Block>(
number: NumberFor<Block>,
hash: Block::Hash,
parent_hash: Block::Hash,
block_origin: Option<RuntimeOrigin>,
import: Result<ImportResult, ConsensusError>,
) -> Result<BlockImportStatus<NumberFor<Block>>, BlockImportError>
where
Block: BlockT,
{
match import {
Ok(ImportResult::AlreadyInChain) => {
trace!(target: LOG_TARGET, "Block already in chain {}: {:?}", number, hash);
Ok(BlockImportStatus::ImportedKnown(number, block_origin))
},
Ok(ImportResult::Imported(aux)) =>
Ok(BlockImportStatus::ImportedUnknown(number, aux, block_origin)),
Ok(ImportResult::MissingState) => {
debug!(
target: LOG_TARGET,
"Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash
);
Err(BlockImportError::MissingState)
},
Ok(ImportResult::UnknownParent) => {
debug!(
target: LOG_TARGET,
"Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent_hash
);
Err(BlockImportError::UnknownParent)
},
Ok(ImportResult::KnownBad) => {
debug!(target: LOG_TARGET, "Peer gave us a bad block {}: {:?}", number, hash);
Err(BlockImportError::BadBlock(block_origin))
},
Err(e) => {
debug!(target: LOG_TARGET, "Error importing block {}: {:?}: {}", number, hash, e);
Err(BlockImportError::Other(e))
},
}
}
pub(crate) enum SingleBlockVerificationOutcome<Block: BlockT> {
/// Block is already imported.
Imported(BlockImportStatus<NumberFor<Block>>),
/// Block is verified, but needs to be imported.
Verified(SingleBlockImportParameters<Block>),
}
pub(crate) struct SingleBlockImportParameters<Block: BlockT> {
import_block: BlockImportParams<Block>,
hash: Block::Hash,
block_origin: Option<RuntimeOrigin>,
verification_time: Duration,
}
/// Single block import function with metering.
pub(crate) async fn verify_single_block_metered<B: BlockT, V: Verifier<B>>(
import_handle: &impl BlockImport<B, Error = ConsensusError>,
block_origin: BlockOrigin,
block: IncomingBlock<B>,
verifier: &V,
metrics: Option<&Metrics>,
) -> Result<SingleBlockVerificationOutcome<B>, BlockImportError> {
let peer = block.origin;
let justifications = block.justifications;
let Some(header) = block.header else {
if let Some(ref peer) = peer {
debug!(target: LOG_TARGET, "Header {} was not provided by {peer} ", block.hash);
} else {
debug!(target: LOG_TARGET, "Header {} was not provided ", block.hash);
}
return Err(BlockImportError::IncompleteHeader(peer));
};
trace!(target: LOG_TARGET, "Header {} has {:?} logs", block.hash, header.digest().logs().len());
let number = *header.number();
let hash = block.hash;
let parent_hash = *header.parent_hash();
match import_handler::<B>(
number,
hash,
parent_hash,
peer,
import_handle
.check_block(BlockCheckParams {
hash,
number,
parent_hash,
allow_missing_state: block.allow_missing_state,
import_existing: block.import_existing,
allow_missing_parent: block.state.is_some(),
})
.await,
)? {
BlockImportStatus::ImportedUnknown { .. } => (),
r => {
// Any other successful result means that the block is already imported.
return Ok(SingleBlockVerificationOutcome::Imported(r));
},
}
let started = Instant::now();
let mut import_block = BlockImportParams::new(block_origin, header);
import_block.body = block.body;
import_block.justifications = justifications;
import_block.post_hash = Some(hash);
import_block.import_existing = block.import_existing;
import_block.indexed_body = block.indexed_body;
if let Some(state) = block.state {
let changes = crate::block_import::StorageChanges::Import(state);
import_block.state_action = StateAction::ApplyChanges(changes);
} else if block.skip_execution {
import_block.state_action = StateAction::Skip;
} else if block.allow_missing_state {
import_block.state_action = StateAction::ExecuteIfPossible;
}
let import_block = verifier.verify(import_block).await.map_err(|msg| {
if let Some(ref peer) = peer {
trace!(
target: LOG_TARGET,
"Verifying {}({}) from {} failed: {}",
number,
hash,
peer,
msg
);
} else {
trace!(target: LOG_TARGET, "Verifying {}({}) failed: {}", number, hash, msg);
}
if let Some(metrics) = metrics {
metrics.report_verification(false, started.elapsed());
}
BlockImportError::VerificationFailed(peer, msg)
})?;
let verification_time = started.elapsed();
if let Some(metrics) = metrics {
metrics.report_verification(true, verification_time);
}
Ok(SingleBlockVerificationOutcome::Verified(SingleBlockImportParameters {
import_block,
hash,
block_origin: peer,
verification_time,
}))
}
pub(crate) async fn import_single_block_metered<Block: BlockT>(
import_handle: &mut impl BlockImport<Block, Error = ConsensusError>,
import_parameters: SingleBlockImportParameters<Block>,
metrics: Option<&Metrics>,
) -> BlockImportResult<Block> {
let started = Instant::now();
let SingleBlockImportParameters { import_block, hash, block_origin, verification_time } =
import_parameters;
let number = *import_block.header.number();
let parent_hash = *import_block.header.parent_hash();
let imported = import_handle.import_block(import_block).await;
if let Some(metrics) = metrics {
metrics.report_verification_and_import(started.elapsed() + verification_time);
}
import_handler::<Block>(number, hash, parent_hash, block_origin, imported)
}
@@ -0,0 +1,693 @@
// This file is part of Substrate.
// 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/>.
use futures::{
prelude::*,
task::{Context, Poll},
};
use log::{debug, trace};
use prometheus_endpoint::Registry;
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use sp_consensus::BlockOrigin;
use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT, NumberFor},
Justification, Justifications,
};
use std::pin::Pin;
use crate::{
import_queue::{
buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender},
import_single_block_metered, verify_single_block_metered, BlockImportError,
BlockImportStatus, BoxBlockImport, BoxJustificationImport, ImportQueue, ImportQueueService,
IncomingBlock, JustificationImportResult, Link, RuntimeOrigin,
SingleBlockVerificationOutcome, Verifier, LOG_TARGET,
},
metrics::Metrics,
};
/// Interface to a basic block import queue that is importing blocks sequentially in a separate
/// task, with plugable verification.
pub struct BasicQueue<B: BlockT> {
/// Handle for sending justification and block import messages to the background task.
handle: BasicQueueHandle<B>,
/// Results coming from the worker task.
result_port: BufferedLinkReceiver<B>,
}
impl<B: BlockT> Drop for BasicQueue<B> {
fn drop(&mut self) {
// Flush the queue and close the receiver to terminate the future.
self.handle.close();
self.result_port.close();
}
}
impl<B: BlockT> BasicQueue<B> {
/// Instantiate a new basic queue, with given verifier.
///
/// This creates a background task, and calls `on_start` on the justification importer.
pub fn new<V>(
verifier: V,
block_import: BoxBlockImport<B>,
justification_import: Option<BoxJustificationImport<B>>,
spawner: &impl sp_core::traits::SpawnEssentialNamed,
prometheus_registry: Option<&Registry>,
) -> Self
where
V: Verifier<B> + 'static,
{
let (result_sender, result_port) = buffered_link::buffered_link(100_000);
let metrics = prometheus_registry.and_then(|r| {
Metrics::register(r)
.map_err(|err| {
log::warn!("Failed to register Prometheus metrics: {}", err);
})
.ok()
});
let (future, justification_sender, block_import_sender) = BlockImportWorker::new(
result_sender,
verifier,
block_import,
justification_import,
metrics,
);
spawner.spawn_essential_blocking(
"basic-block-import-worker",
Some("block-import"),
future.boxed(),
);
Self {
handle: BasicQueueHandle::new(justification_sender, block_import_sender),
result_port,
}
}
}
#[derive(Clone)]
struct BasicQueueHandle<B: BlockT> {
/// Channel to send justification import messages to the background task.
justification_sender: TracingUnboundedSender<worker_messages::ImportJustification<B>>,
/// Channel to send block import messages to the background task.
block_import_sender: TracingUnboundedSender<worker_messages::ImportBlocks<B>>,
}
impl<B: BlockT> BasicQueueHandle<B> {
pub fn new(
justification_sender: TracingUnboundedSender<worker_messages::ImportJustification<B>>,
block_import_sender: TracingUnboundedSender<worker_messages::ImportBlocks<B>>,
) -> Self {
Self { justification_sender, block_import_sender }
}
pub fn close(&mut self) {
self.justification_sender.close();
self.block_import_sender.close();
}
}
impl<B: BlockT> ImportQueueService<B> for BasicQueueHandle<B> {
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>) {
if blocks.is_empty() {
return;
}
trace!(target: LOG_TARGET, "Scheduling {} blocks for import", blocks.len());
let res = self
.block_import_sender
.unbounded_send(worker_messages::ImportBlocks(origin, blocks));
if res.is_err() {
log::error!(
target: LOG_TARGET,
"import_blocks: Background import task is no longer alive"
);
}
}
fn import_justifications(
&mut self,
who: RuntimeOrigin,
hash: B::Hash,
number: NumberFor<B>,
justifications: Justifications,
) {
for justification in justifications {
let res = self.justification_sender.unbounded_send(
worker_messages::ImportJustification(who, hash, number, justification),
);
if res.is_err() {
log::error!(
target: LOG_TARGET,
"import_justification: Background import task is no longer alive"
);
}
}
}
}
#[async_trait::async_trait]
impl<B: BlockT> ImportQueue<B> for BasicQueue<B> {
/// Get handle to [`ImportQueueService`].
fn service(&self) -> Box<dyn ImportQueueService<B>> {
Box::new(self.handle.clone())
}
/// Get a reference to the handle to [`ImportQueueService`].
fn service_ref(&mut self) -> &mut dyn ImportQueueService<B> {
&mut self.handle
}
/// Poll actions from network.
fn poll_actions(&mut self, cx: &mut Context, link: &dyn Link<B>) {
if self.result_port.poll_actions(cx, link).is_err() {
log::error!(
target: LOG_TARGET,
"poll_actions: Background import task is no longer alive"
);
}
}
/// Start asynchronous runner for import queue.
///
/// Takes an object implementing [`Link`] which allows the import queue to
/// influence the synchronization process.
async fn run(mut self, link: &dyn Link<B>) {
loop {
if let Err(_) = self.result_port.next_action(link).await {
log::error!(target: "sync", "poll_actions: Background import task is no longer alive");
return;
}
}
}
}
/// Messages designated to the background worker.
mod worker_messages {
use super::*;
pub struct ImportBlocks<B: BlockT>(pub BlockOrigin, pub Vec<IncomingBlock<B>>);
pub struct ImportJustification<B: BlockT>(
pub RuntimeOrigin,
pub B::Hash,
pub NumberFor<B>,
pub Justification,
);
}
/// The process of importing blocks.
///
/// This polls the `block_import_receiver` for new blocks to import and than awaits on
/// importing these blocks. After each block is imported, this async function yields once
/// to give other futures the possibility to be run.
///
/// Returns when `block_import` ended.
async fn block_import_process<B: BlockT>(
mut block_import: BoxBlockImport<B>,
verifier: impl Verifier<B>,
result_sender: BufferedLinkSender<B>,
mut block_import_receiver: TracingUnboundedReceiver<worker_messages::ImportBlocks<B>>,
metrics: Option<Metrics>,
) {
loop {
let worker_messages::ImportBlocks(origin, blocks) = match block_import_receiver.next().await
{
Some(blocks) => blocks,
None => {
log::debug!(
target: LOG_TARGET,
"Stopping block import because the import channel was closed!",
);
return;
},
};
let res =
import_many_blocks(&mut block_import, origin, blocks, &verifier, metrics.clone()).await;
result_sender.blocks_processed(res.imported, res.block_count, res.results);
}
}
struct BlockImportWorker<B: BlockT> {
result_sender: BufferedLinkSender<B>,
justification_import: Option<BoxJustificationImport<B>>,
metrics: Option<Metrics>,
}
impl<B: BlockT> BlockImportWorker<B> {
fn new<V>(
result_sender: BufferedLinkSender<B>,
verifier: V,
block_import: BoxBlockImport<B>,
justification_import: Option<BoxJustificationImport<B>>,
metrics: Option<Metrics>,
) -> (
impl Future<Output = ()> + Send,
TracingUnboundedSender<worker_messages::ImportJustification<B>>,
TracingUnboundedSender<worker_messages::ImportBlocks<B>>,
)
where
V: Verifier<B> + 'static,
{
use worker_messages::*;
let (justification_sender, mut justification_port) =
tracing_unbounded("mpsc_import_queue_worker_justification", 100_000);
let (block_import_sender, block_import_receiver) =
tracing_unbounded("mpsc_import_queue_worker_blocks", 100_000);
let mut worker = BlockImportWorker { result_sender, justification_import, metrics };
let future = async move {
// Let's initialize `justification_import`
if let Some(justification_import) = worker.justification_import.as_mut() {
for (hash, number) in justification_import.on_start().await {
worker.result_sender.request_justification(&hash, number);
}
}
let block_import_process = block_import_process(
block_import,
verifier,
worker.result_sender.clone(),
block_import_receiver,
worker.metrics.clone(),
);
futures::pin_mut!(block_import_process);
loop {
// If the results sender is closed, that means that the import queue is shutting
// down and we should end this future.
if worker.result_sender.is_closed() {
log::debug!(
target: LOG_TARGET,
"Stopping block import because result channel was closed!",
);
return;
}
// Make sure to first process all justifications
while let Poll::Ready(justification) = futures::poll!(justification_port.next()) {
match justification {
Some(ImportJustification(who, hash, number, justification)) =>
worker.import_justification(who, hash, number, justification).await,
None => {
log::debug!(
target: LOG_TARGET,
"Stopping block import because justification channel was closed!",
);
return;
},
}
}
if let Poll::Ready(()) = futures::poll!(&mut block_import_process) {
return;
}
// All futures that we polled are now pending.
futures::pending!()
}
};
(future, justification_sender, block_import_sender)
}
async fn import_justification(
&mut self,
who: RuntimeOrigin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification,
) {
let started = std::time::Instant::now();
let import_result = match self.justification_import.as_mut() {
Some(justification_import) => {
let result = justification_import
.import_justification(hash, number, justification)
.await
.map_err(|e| {
debug!(
target: LOG_TARGET,
"Justification import failed for hash = {:?} with number = {:?} coming from node = {:?} with error: {}",
hash,
number,
who,
e,
);
e
});
match result {
Ok(()) => JustificationImportResult::Success,
Err(sp_consensus::Error::OutdatedJustification) =>
JustificationImportResult::OutdatedJustification,
Err(_) => JustificationImportResult::Failure,
}
},
None => JustificationImportResult::Failure,
};
if let Some(metrics) = self.metrics.as_ref() {
metrics.justification_import_time.observe(started.elapsed().as_secs_f64());
}
self.result_sender.justification_imported(who, &hash, number, import_result);
}
}
/// Result of [`import_many_blocks`].
struct ImportManyBlocksResult<B: BlockT> {
/// The number of blocks imported successfully.
imported: usize,
/// The total number of blocks processed.
block_count: usize,
/// The import results for each block.
results: Vec<(Result<BlockImportStatus<NumberFor<B>>, BlockImportError>, B::Hash)>,
}
/// Import several blocks at once, returning import result for each block.
///
/// This will yield after each imported block once, to ensure that other futures can
/// be called as well.
async fn import_many_blocks<B: BlockT, V: Verifier<B>>(
import_handle: &mut BoxBlockImport<B>,
blocks_origin: BlockOrigin,
blocks: Vec<IncomingBlock<B>>,
verifier: &V,
metrics: Option<Metrics>,
) -> ImportManyBlocksResult<B> {
let count = blocks.len();
let blocks_range = match (
blocks.first().and_then(|b| b.header.as_ref().map(|h| h.number())),
blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())),
) {
(Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last),
(Some(first), Some(_)) => format!(" ({})", first),
_ => Default::default(),
};
trace!(target: LOG_TARGET, "Starting import of {} blocks {}", count, blocks_range);
let mut imported = 0;
let mut results = vec![];
let mut has_error = false;
let mut blocks = blocks.into_iter();
// Blocks in the response/drain should be in ascending order.
loop {
// Is there any block left to import?
let block = match blocks.next() {
Some(b) => b,
None => {
// No block left to import, success!
return ImportManyBlocksResult { block_count: count, imported, results };
},
};
let block_number = block.header.as_ref().map(|h| *h.number());
let block_hash = block.hash;
let import_result = if has_error {
Err(BlockImportError::Cancelled)
} else {
let verification_fut = verify_single_block_metered(
import_handle,
blocks_origin,
block,
verifier,
metrics.as_ref(),
);
match verification_fut.await {
Ok(SingleBlockVerificationOutcome::Imported(import_status)) => Ok(import_status),
Ok(SingleBlockVerificationOutcome::Verified(import_parameters)) => {
// The actual import.
import_single_block_metered(import_handle, import_parameters, metrics.as_ref())
.await
},
Err(e) => Err(e),
}
};
if let Some(metrics) = metrics.as_ref() {
metrics.report_import::<B>(&import_result);
}
if import_result.is_ok() {
trace!(
target: LOG_TARGET,
"Block imported successfully {:?} ({})",
block_number,
block_hash,
);
imported += 1;
} else {
has_error = true;
}
results.push((import_result, block_hash));
Yield::new().await
}
}
/// A future that will always `yield` on the first call of `poll` but schedules the
/// current task for re-execution.
///
/// This is done by getting the waker and calling `wake_by_ref` followed by returning
/// `Pending`. The next time the `poll` is called, it will return `Ready`.
struct Yield(bool);
impl Yield {
fn new() -> Self {
Self(false)
}
}
impl Future for Yield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if !self.0 {
self.0 = true;
cx.waker().wake_by_ref();
Poll::Pending
} else {
Poll::Ready(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
block_import::{
BlockCheckParams, BlockImport, BlockImportParams, ImportResult, JustificationImport,
},
import_queue::Verifier,
};
use futures::{executor::block_on, Future};
use parking_lot::Mutex;
use sp_test_primitives::{Block, BlockNumber, Hash, Header};
#[async_trait::async_trait]
impl Verifier<Block> for () {
async fn verify(
&self,
block: BlockImportParams<Block>,
) -> Result<BlockImportParams<Block>, String> {
Ok(BlockImportParams::new(block.origin, block.header))
}
}
#[async_trait::async_trait]
impl BlockImport<Block> for () {
type Error = sp_consensus::Error;
async fn check_block(
&self,
_block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
Ok(ImportResult::imported(false))
}
async fn import_block(
&self,
_block: BlockImportParams<Block>,
) -> Result<ImportResult, Self::Error> {
Ok(ImportResult::imported(true))
}
}
#[async_trait::async_trait]
impl JustificationImport<Block> for () {
type Error = sp_consensus::Error;
async fn on_start(&mut self) -> Vec<(Hash, BlockNumber)> {
Vec::new()
}
async fn import_justification(
&mut self,
_hash: Hash,
_number: BlockNumber,
_justification: Justification,
) -> Result<(), Self::Error> {
Ok(())
}
}
#[derive(Debug, PartialEq)]
enum Event {
JustificationImported(Hash),
BlockImported(Hash),
}
#[derive(Default)]
struct TestLink {
events: Mutex<Vec<Event>>,
}
impl Link<Block> for TestLink {
fn blocks_processed(
&self,
_imported: usize,
_count: usize,
results: Vec<(Result<BlockImportStatus<BlockNumber>, BlockImportError>, Hash)>,
) {
if let Some(hash) = results.into_iter().find_map(|(r, h)| r.ok().map(|_| h)) {
self.events.lock().push(Event::BlockImported(hash));
}
}
fn justification_imported(
&self,
_who: RuntimeOrigin,
hash: &Hash,
_number: BlockNumber,
_import_result: JustificationImportResult,
) {
self.events.lock().push(Event::JustificationImported(*hash))
}
}
#[test]
fn prioritizes_finality_work_over_block_import() {
let (result_sender, mut result_port) = buffered_link::buffered_link(100_000);
let (worker, finality_sender, block_import_sender) =
BlockImportWorker::new(result_sender, (), Box::new(()), Some(Box::new(())), None);
futures::pin_mut!(worker);
let import_block = |n| {
let header = Header {
parent_hash: Hash::random(),
number: n,
extrinsics_root: Hash::random(),
state_root: Default::default(),
digest: Default::default(),
};
let hash = header.hash();
block_import_sender
.unbounded_send(worker_messages::ImportBlocks(
BlockOrigin::Own,
vec![IncomingBlock {
hash,
header: Some(header),
body: None,
indexed_body: None,
justifications: None,
origin: None,
allow_missing_state: false,
import_existing: false,
state: None,
skip_execution: false,
}],
))
.unwrap();
hash
};
let import_justification = || {
let hash = Hash::random();
finality_sender
.unbounded_send(worker_messages::ImportJustification(
sc_network_types::PeerId::random(),
hash,
1,
(*b"TEST", Vec::new()),
))
.unwrap();
hash
};
let link = TestLink::default();
// we send a bunch of tasks to the worker
let block1 = import_block(1);
let block2 = import_block(2);
let block3 = import_block(3);
let justification1 = import_justification();
let justification2 = import_justification();
let block4 = import_block(4);
let block5 = import_block(5);
let block6 = import_block(6);
let justification3 = import_justification();
// we poll the worker until we have processed 9 events
block_on(futures::future::poll_fn(|cx| {
while link.events.lock().len() < 9 {
match Future::poll(Pin::new(&mut worker), cx) {
Poll::Pending => {},
Poll::Ready(()) => panic!("import queue worker should not conclude."),
}
result_port.poll_actions(cx, &link).unwrap();
}
Poll::Ready(())
}));
// all justification tasks must be done before any block import work
assert_eq!(
&*link.events.lock(),
&[
Event::JustificationImported(justification1),
Event::JustificationImported(justification2),
Event::JustificationImported(justification3),
Event::BlockImported(block1),
Event::BlockImported(block2),
Event::BlockImported(block3),
Event::BlockImported(block4),
Event::BlockImported(block5),
Event::BlockImported(block6),
]
);
}
}
@@ -0,0 +1,185 @@
// This file is part of Substrate.
// 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/>.
//! Provides the `buffered_link` utility.
//!
//! The buffered link is a channel that allows buffering the method calls on `Link`.
//!
//! # Example
//!
//! ```
//! use sc_consensus::import_queue::Link;
//! # use sc_consensus::import_queue::buffered_link::buffered_link;
//! # use sp_test_primitives::Block;
//! # struct DummyLink; impl Link<Block> for DummyLink {}
//! # let my_link = DummyLink;
//! let (mut tx, mut rx) = buffered_link::<Block>(100_000);
//! tx.blocks_processed(0, 0, vec![]);
//!
//! // Calls `my_link.blocks_processed(0, 0, vec![])` when polled.
//! let _fut = futures::future::poll_fn(move |cx| {
//! rx.poll_actions(cx, &my_link).unwrap();
//! std::task::Poll::Pending::<()>
//! });
//! ```
use crate::import_queue::{JustificationImportResult, Link, RuntimeOrigin};
use futures::prelude::*;
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use sp_runtime::traits::{Block as BlockT, NumberFor};
use std::{
pin::Pin,
task::{Context, Poll},
};
use super::BlockImportResult;
/// Wraps around an unbounded channel from the `futures` crate. The sender implements `Link` and
/// can be used to buffer commands, and the receiver can be used to poll said commands and transfer
/// them to another link. `queue_size_warning` sets the warning threshold of the channel queue size.
pub fn buffered_link<B: BlockT>(
queue_size_warning: usize,
) -> (BufferedLinkSender<B>, BufferedLinkReceiver<B>) {
let (tx, rx) = tracing_unbounded("mpsc_buffered_link", queue_size_warning);
let tx = BufferedLinkSender { tx };
let rx = BufferedLinkReceiver { rx: rx.fuse() };
(tx, rx)
}
/// See [`buffered_link`].
pub struct BufferedLinkSender<B: BlockT> {
tx: TracingUnboundedSender<BlockImportWorkerMsg<B>>,
}
impl<B: BlockT> BufferedLinkSender<B> {
/// Returns true if the sender points to nowhere.
///
/// Once `true` is returned, it is pointless to use the sender anymore.
pub fn is_closed(&self) -> bool {
self.tx.is_closed()
}
}
impl<B: BlockT> Clone for BufferedLinkSender<B> {
fn clone(&self) -> Self {
BufferedLinkSender { tx: self.tx.clone() }
}
}
/// Internal buffered message.
pub enum BlockImportWorkerMsg<B: BlockT> {
BlocksProcessed(usize, usize, Vec<(BlockImportResult<B>, B::Hash)>),
JustificationImported(RuntimeOrigin, B::Hash, NumberFor<B>, JustificationImportResult),
RequestJustification(B::Hash, NumberFor<B>),
}
impl<B: BlockT> Link<B> for BufferedLinkSender<B> {
fn blocks_processed(
&self,
imported: usize,
count: usize,
results: Vec<(BlockImportResult<B>, B::Hash)>,
) {
let _ = self
.tx
.unbounded_send(BlockImportWorkerMsg::BlocksProcessed(imported, count, results));
}
fn justification_imported(
&self,
who: RuntimeOrigin,
hash: &B::Hash,
number: NumberFor<B>,
import_result: JustificationImportResult,
) {
let msg = BlockImportWorkerMsg::JustificationImported(who, *hash, number, import_result);
let _ = self.tx.unbounded_send(msg);
}
fn request_justification(&self, hash: &B::Hash, number: NumberFor<B>) {
let _ = self
.tx
.unbounded_send(BlockImportWorkerMsg::RequestJustification(*hash, number));
}
}
/// See [`buffered_link`].
pub struct BufferedLinkReceiver<B: BlockT> {
rx: stream::Fuse<TracingUnboundedReceiver<BlockImportWorkerMsg<B>>>,
}
impl<B: BlockT> BufferedLinkReceiver<B> {
/// Send action for the synchronization to perform.
pub fn send_actions(&mut self, msg: BlockImportWorkerMsg<B>, link: &dyn Link<B>) {
match msg {
BlockImportWorkerMsg::BlocksProcessed(imported, count, results) =>
link.blocks_processed(imported, count, results),
BlockImportWorkerMsg::JustificationImported(who, hash, number, import_result) =>
link.justification_imported(who, &hash, number, import_result),
BlockImportWorkerMsg::RequestJustification(hash, number) =>
link.request_justification(&hash, number),
}
}
/// Polls for the buffered link actions. Any enqueued action will be propagated to the link
/// passed as parameter.
///
/// This method should behave in a way similar to `Future::poll`. It can register the current
/// task and notify later when more actions are ready to be polled. To continue the comparison,
/// it is as if this method always returned `Poll::Pending`.
///
/// Returns an error if the corresponding [`BufferedLinkSender`] has been closed.
pub fn poll_actions(&mut self, cx: &mut Context, link: &dyn Link<B>) -> Result<(), ()> {
loop {
let msg = match Stream::poll_next(Pin::new(&mut self.rx), cx) {
Poll::Ready(Some(msg)) => msg,
Poll::Ready(None) => break Err(()),
Poll::Pending => break Ok(()),
};
self.send_actions(msg, link);
}
}
/// Poll next element from import queue and send the corresponding action command over the link.
pub async fn next_action(&mut self, link: &dyn Link<B>) -> Result<(), ()> {
if let Some(msg) = self.rx.next().await {
self.send_actions(msg, link);
return Ok(());
}
Err(())
}
/// Close the channel.
pub fn close(&mut self) -> bool {
self.rx.get_mut().close()
}
}
#[cfg(test)]
mod tests {
use sp_test_primitives::Block;
#[test]
fn is_closed() {
let (tx, rx) = super::buffered_link::<Block>(1);
assert!(!tx.is_closed());
drop(rx);
assert!(tx.is_closed());
}
}
@@ -0,0 +1,46 @@
// This file is part of Substrate.
// 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/>.
use super::*;
mockall::mock! {
pub ImportQueueHandle<B: BlockT> {}
impl<B: BlockT> ImportQueueService<B> for ImportQueueHandle<B> {
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
fn import_justifications(
&mut self,
who: RuntimeOrigin,
hash: B::Hash,
number: NumberFor<B>,
justifications: Justifications,
);
}
}
mockall::mock! {
pub ImportQueue<B: BlockT> {}
#[async_trait::async_trait]
impl<B: BlockT> ImportQueue<B> for ImportQueue<B> {
fn service(&self) -> Box<dyn ImportQueueService<B>>;
fn service_ref(&mut self) -> &mut dyn ImportQueueService<B>;
fn poll_actions<'a>(&mut self, cx: &mut futures::task::Context<'a>, link: &dyn Link<B>);
async fn run(self, link: &'__mockall_link dyn Link<B>);
}
}
@@ -0,0 +1,40 @@
// This file is part of Substrate.
// 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/>.
//! Collection of common consensus specific implementations
pub mod block_import;
pub mod import_queue;
pub mod metrics;
pub use block_import::{
BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult,
ImportedAux, ImportedState, JustificationImport, JustificationSyncLink, StateAction,
StorageChanges,
};
pub use import_queue::{
import_single_block, BasicQueue, BlockImportError, BlockImportStatus, BoxBlockImport,
BoxJustificationImport, DefaultImportQueue, ImportQueue, IncomingBlock,
JustificationImportResult, Link, Verifier,
};
mod longest_chain;
pub mod shared_data;
pub use longest_chain::LongestChain;
@@ -0,0 +1,157 @@
// This file is part of Substrate.
// 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/>.
//! Longest chain implementation
use sc_client_api::backend;
use sp_blockchain::{Backend, HeaderBackend};
use sp_consensus::{Error as ConsensusError, SelectChain};
use sp_runtime::traits::{Block as BlockT, Header, NumberFor};
use std::{marker::PhantomData, sync::Arc};
/// Implement Longest Chain Select implementation
/// where 'longest' is defined as the highest number of blocks
pub struct LongestChain<B, Block> {
backend: Arc<B>,
_phantom: PhantomData<Block>,
}
impl<B, Block> Clone for LongestChain<B, Block> {
fn clone(&self) -> Self {
let backend = self.backend.clone();
LongestChain { backend, _phantom: Default::default() }
}
}
impl<B, Block> LongestChain<B, Block>
where
B: backend::Backend<Block>,
Block: BlockT,
{
/// Instantiate a new LongestChain for Backend B
pub fn new(backend: Arc<B>) -> Self {
LongestChain { backend, _phantom: Default::default() }
}
fn best_hash(&self) -> sp_blockchain::Result<<Block as BlockT>::Hash> {
let info = self.backend.blockchain().info();
let import_lock = self.backend.get_import_lock();
let best_hash = self
.backend
.blockchain()
.longest_containing(info.best_hash, import_lock)?
.unwrap_or(info.best_hash);
Ok(best_hash)
}
fn best_header(&self) -> sp_blockchain::Result<<Block as BlockT>::Header> {
let best_hash = self.best_hash()?;
Ok(self
.backend
.blockchain()
.header(best_hash)?
.expect("given block hash was fetched from block in db; qed"))
}
/// Returns the highest descendant of the given block that is a valid
/// candidate to be finalized.
///
/// In this context, being a valid target means being an ancestor of
/// the best chain according to the `best_header` method.
///
/// If `maybe_max_number` is `Some(max_block_number)` the search is
/// limited to block `number <= max_block_number`. In other words
/// as if there were no blocks greater than `max_block_number`.
fn finality_target(
&self,
base_hash: Block::Hash,
maybe_max_number: Option<NumberFor<Block>>,
) -> sp_blockchain::Result<Block::Hash> {
use sp_blockchain::Error::{Application, MissingHeader};
let blockchain = self.backend.blockchain();
let mut current_head = self.best_header()?;
let mut best_hash = current_head.hash();
let base_header = blockchain
.header(base_hash)?
.ok_or_else(|| MissingHeader(base_hash.to_string()))?;
let base_number = *base_header.number();
if let Some(max_number) = maybe_max_number {
if max_number < base_number {
let msg = format!(
"Requested a finality target using max number {} below the base number {}",
max_number, base_number
);
return Err(Application(msg.into()));
}
while current_head.number() > &max_number {
best_hash = *current_head.parent_hash();
current_head = blockchain
.header(best_hash)?
.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
}
}
while current_head.hash() != base_hash {
if *current_head.number() < base_number {
let msg = format!(
"Requested a finality target using a base {:?} not in the best chain {:?}",
base_hash, best_hash,
);
return Err(Application(msg.into()));
}
let current_hash = *current_head.parent_hash();
current_head = blockchain
.header(current_hash)?
.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
}
Ok(best_hash)
}
fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, sp_blockchain::Error> {
self.backend.blockchain().leaves()
}
}
#[async_trait::async_trait]
impl<B, Block> SelectChain<Block> for LongestChain<B, Block>
where
B: backend::Backend<Block>,
Block: BlockT,
{
async fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, ConsensusError> {
LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
async fn best_chain(&self) -> Result<<Block as BlockT>::Header, ConsensusError> {
LongestChain::best_header(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
async fn finality_target(
&self,
base_hash: Block::Hash,
maybe_max_number: Option<NumberFor<Block>>,
) -> Result<Block::Hash, ConsensusError> {
LongestChain::finality_target(self, base_hash, maybe_max_number)
.map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
}
@@ -0,0 +1,106 @@
// This file is part of Substrate.
// 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/>.
//! Metering tools for consensus
use prometheus_endpoint::{
register, CounterVec, Histogram, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry,
U64,
};
use sp_runtime::traits::{Block as BlockT, NumberFor};
use crate::import_queue::{BlockImportError, BlockImportStatus};
/// Generic Prometheus metrics for common consensus functionality.
#[derive(Clone)]
pub(crate) struct Metrics {
pub import_queue_processed: CounterVec<U64>,
pub block_verification_time: HistogramVec,
pub block_verification_and_import_time: Histogram,
pub justification_import_time: Histogram,
}
impl Metrics {
pub(crate) fn register(registry: &Registry) -> Result<Self, PrometheusError> {
Ok(Self {
import_queue_processed: register(
CounterVec::new(
Opts::new(
"substrate_import_queue_processed_total",
"Blocks processed by import queue",
),
&["result"], // 'success or failure
)?,
registry,
)?,
block_verification_time: register(
HistogramVec::new(
HistogramOpts::new(
"substrate_block_verification_time",
"Time taken to verify blocks",
),
&["result"],
)?,
registry,
)?,
block_verification_and_import_time: register(
Histogram::with_opts(HistogramOpts::new(
"substrate_block_verification_and_import_time",
"Time taken to verify and import blocks",
))?,
registry,
)?,
justification_import_time: register(
Histogram::with_opts(HistogramOpts::new(
"substrate_justification_import_time",
"Time taken to import justifications",
))?,
registry,
)?,
})
}
pub fn report_import<B: BlockT>(
&self,
result: &Result<BlockImportStatus<NumberFor<B>>, BlockImportError>,
) {
let label = match result {
Ok(_) => "success",
Err(BlockImportError::IncompleteHeader(_)) => "incomplete_header",
Err(BlockImportError::VerificationFailed(_, _)) => "verification_failed",
Err(BlockImportError::BadBlock(_)) => "bad_block",
Err(BlockImportError::MissingState) => "missing_state",
Err(BlockImportError::UnknownParent) => "unknown_parent",
Err(BlockImportError::Cancelled) => "cancelled",
Err(BlockImportError::Other(_)) => "failed",
};
self.import_queue_processed.with_label_values(&[label]).inc();
}
pub fn report_verification(&self, success: bool, time: std::time::Duration) {
self.block_verification_time
.with_label_values(&[if success { "success" } else { "verification_failed" }])
.observe(time.as_secs_f64());
}
pub fn report_verification_and_import(&self, time: std::time::Duration) {
self.block_verification_and_import_time.observe(time.as_secs_f64());
}
}
@@ -0,0 +1,272 @@
// This file is part of Substrate.
// 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/>.
//! Provides a generic wrapper around shared data. See [`SharedData`] for more information.
use parking_lot::{Condvar, MappedMutexGuard, Mutex, MutexGuard};
use std::sync::Arc;
/// Created by [`SharedDataLocked::release_mutex`].
///
/// As long as the object isn't dropped, the shared data is locked. It is advised to drop this
/// object when the shared data doesn't need to be locked anymore. To get access to the shared data
/// [`Self::upgrade`] is provided.
#[must_use = "Shared data will be unlocked on drop!"]
pub struct SharedDataLockedUpgradable<T> {
shared_data: SharedData<T>,
}
impl<T> SharedDataLockedUpgradable<T> {
/// Upgrade to a *real* mutex guard that will give access to the inner data.
///
/// Every call to this function will reaquire the mutex again.
pub fn upgrade(&mut self) -> MappedMutexGuard<'_, T> {
MutexGuard::map(self.shared_data.inner.lock(), |i| &mut i.shared_data)
}
}
impl<T> Drop for SharedDataLockedUpgradable<T> {
fn drop(&mut self) {
let mut inner = self.shared_data.inner.lock();
// It should not be locked anymore
inner.locked = false;
// Notify all waiting threads.
self.shared_data.cond_var.notify_all();
}
}
/// Created by [`SharedData::shared_data_locked`].
///
/// As long as this object isn't dropped, the shared data is held in a mutex guard and the shared
/// data is tagged as locked. Access to the shared data is provided through
/// [`Deref`](std::ops::Deref) and [`DerefMut`](std::ops::DerefMut). The trick is to use
/// [`Self::release_mutex`] to release the mutex, but still keep the shared data locked. This means
/// every other thread trying to access the shared data in this time will need to wait until this
/// lock is freed.
///
/// If this object is dropped without calling [`Self::release_mutex`], the lock will be dropped
/// immediately.
#[must_use = "Shared data will be unlocked on drop!"]
pub struct SharedDataLocked<'a, T> {
/// The current active mutex guard holding the inner data.
inner: MutexGuard<'a, SharedDataInner<T>>,
/// The [`SharedData`] instance that created this instance.
///
/// This instance is only taken on drop or when calling [`Self::release_mutex`].
shared_data: Option<SharedData<T>>,
}
impl<'a, T> SharedDataLocked<'a, T> {
/// Release the mutex, but keep the shared data locked.
pub fn release_mutex(mut self) -> SharedDataLockedUpgradable<T> {
SharedDataLockedUpgradable {
shared_data: self.shared_data.take().expect("`shared_data` is only taken on drop; qed"),
}
}
}
impl<'a, T> Drop for SharedDataLocked<'a, T> {
fn drop(&mut self) {
if let Some(shared_data) = self.shared_data.take() {
// If the `shared_data` is still set, it means [`Self::release_mutex`] wasn't
// called and the lock should be released.
self.inner.locked = false;
// Notify all waiting threads about the released lock.
shared_data.cond_var.notify_all();
}
}
}
impl<'a, T> std::ops::Deref for SharedDataLocked<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner.shared_data
}
}
impl<'a, T> std::ops::DerefMut for SharedDataLocked<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner.shared_data
}
}
/// Holds the shared data and if the shared data is currently locked.
///
/// For more information see [`SharedData`].
struct SharedDataInner<T> {
/// The actual shared data that is protected here against concurrent access.
shared_data: T,
/// Is `shared_data` currently locked and can not be accessed?
locked: bool,
}
/// Some shared data that provides support for locking this shared data for some time.
///
/// When working with consensus engines there is often data that needs to be shared between multiple
/// parts of the system, like block production and block import. This struct provides an abstraction
/// for this shared data in a generic way.
///
/// The pain point when sharing this data is often the usage of mutex guards in an async context as
/// this doesn't work for most of them as these guards don't implement `Send`. This abstraction
/// provides a way to lock the shared data, while not having the mutex locked. So, the data stays
/// locked and we are still able to hold this lock over an `await` call.
///
/// # Example
///
/// ```
/// # use sc_consensus::shared_data::SharedData;
///
/// let shared_data = SharedData::new(String::from("hello world"));
///
/// let lock = shared_data.shared_data_locked();
///
/// let shared_data2 = shared_data.clone();
/// let join_handle1 = std::thread::spawn(move || {
/// // This will need to wait for the outer lock to be released before it can access the data.
/// shared_data2.shared_data().push_str("1");
/// });
///
/// assert_eq!(*lock, "hello world");
///
/// // Let us release the mutex, but we still keep it locked.
/// // Now we could call `await` for example.
/// let mut lock = lock.release_mutex();
///
/// let shared_data2 = shared_data.clone();
/// let join_handle2 = std::thread::spawn(move || {
/// shared_data2.shared_data().push_str("2");
/// });
///
/// // We still have the lock and can upgrade it to access the data.
/// assert_eq!(*lock.upgrade(), "hello world");
/// lock.upgrade().push_str("3");
///
/// drop(lock);
/// join_handle1.join().unwrap();
/// join_handle2.join().unwrap();
///
/// let data = shared_data.shared_data();
/// // As we don't know the order of the threads, we need to check for both combinations
/// assert!(*data == "hello world321" || *data == "hello world312");
/// ```
///
/// # Deadlock
///
/// Be aware that this data structure doesn't give you any guarantees that you can not create a
/// deadlock. If you use [`release_mutex`](SharedDataLocked::release_mutex) followed by a call
/// to [`shared_data`](Self::shared_data) in the same thread will make your program dead lock.
/// The same applies when you are using a single threaded executor.
pub struct SharedData<T> {
inner: Arc<Mutex<SharedDataInner<T>>>,
cond_var: Arc<Condvar>,
}
impl<T> Clone for SharedData<T> {
fn clone(&self) -> Self {
Self { inner: self.inner.clone(), cond_var: self.cond_var.clone() }
}
}
impl<T> SharedData<T> {
/// Create a new instance of [`SharedData`] to share the given `shared_data`.
pub fn new(shared_data: T) -> Self {
Self {
inner: Arc::new(Mutex::new(SharedDataInner { shared_data, locked: false })),
cond_var: Default::default(),
}
}
/// Acquire access to the shared data.
///
/// This will give mutable access to the shared data. After the returned mutex guard is dropped,
/// the shared data is accessible by other threads. So, this function should be used when
/// reading/writing of the shared data in a local context is required.
///
/// When requiring to lock shared data for some longer time, even with temporarily releasing the
/// lock, [`Self::shared_data_locked`] should be used.
pub fn shared_data(&self) -> MappedMutexGuard<'_, T> {
let mut guard = self.inner.lock();
while guard.locked {
self.cond_var.wait(&mut guard);
}
debug_assert!(!guard.locked);
MutexGuard::map(guard, |i| &mut i.shared_data)
}
/// Acquire access to the shared data and lock it.
///
/// This will give mutable access to the shared data. The returned [`SharedDataLocked`]
/// provides the function [`SharedDataLocked::release_mutex`] to release the mutex, but
/// keeping the data locked. This is useful in async contexts for example where the data needs
/// to be locked, but a mutex guard can not be held.
///
/// For an example see [`SharedData`].
pub fn shared_data_locked(&self) -> SharedDataLocked<'_, T> {
let mut guard = self.inner.lock();
while guard.locked {
self.cond_var.wait(&mut guard);
}
debug_assert!(!guard.locked);
guard.locked = true;
SharedDataLocked { inner: guard, shared_data: Some(self.clone()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_data_locking_works() {
const THREADS: u32 = 100;
let shared_data = SharedData::new(0u32);
let lock = shared_data.shared_data_locked();
for i in 0..THREADS {
let data = shared_data.clone();
std::thread::spawn(move || {
if i % 2 == 1 {
*data.shared_data() += 1;
} else {
let mut lock = data.shared_data_locked().release_mutex();
// Give the other threads some time to wake up
std::thread::sleep(std::time::Duration::from_millis(10));
*lock.upgrade() += 1;
}
});
}
let lock = lock.release_mutex();
std::thread::sleep(std::time::Duration::from_millis(100));
drop(lock);
while *shared_data.shared_data() < THREADS {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}