feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user