mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 11:07:56 +00:00
Move client consensus parts out of primitives and into client/consensus/api (#9319)
* moved client code out of primitives * bump ci * Fixup from merge. * Removed unused deps thanks to review feedback * Removing unneeded deps * updating lock file * note about rustfmt * fixed typo to bump ci * Move lonely CacheKeyId to parent * cargo fmt * updating import style * Update docs/STYLE_GUIDE.md Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
@@ -1,451 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Block import helpers.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_runtime::{
|
||||
traits::{Block as BlockT, DigestItemFor, HashFor, Header as HeaderT, NumberFor},
|
||||
Justification, Justifications,
|
||||
};
|
||||
use std::{any::Any, borrow::Cow, collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::{import_queue::CacheKeyId, 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(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 mut aux = ImportedAux::default();
|
||||
aux.is_new_best = is_new_best;
|
||||
|
||||
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: &mut 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);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block data origin.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum BlockOrigin {
|
||||
/// Genesis block built into the client.
|
||||
Genesis,
|
||||
/// Block is part of the initial sync with the network.
|
||||
NetworkInitialSync,
|
||||
/// Block was broadcasted on the network.
|
||||
NetworkBroadcast,
|
||||
/// Block that was received from the network and validated in the consensus process.
|
||||
ConsensusBroadcast,
|
||||
/// Block that was collated by this node.
|
||||
Own,
|
||||
/// Block was imported from a file.
|
||||
File,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// Re-validate existing block.
|
||||
pub import_existing: bool,
|
||||
}
|
||||
|
||||
/// Precomputed storage.
|
||||
pub enum StorageChanges<Block: BlockT, Transaction> {
|
||||
/// Changes coming from block execution.
|
||||
Changes(sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<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: Vec<(Vec<u8>, Vec<u8>)>,
|
||||
}
|
||||
|
||||
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, Transaction> {
|
||||
/// Apply precomputed changes coming from block execution or state sync.
|
||||
ApplyChanges(StorageChanges<Block, Transaction>),
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Data required to import a Block.
|
||||
#[non_exhaustive]
|
||||
pub struct BlockImportParams<Block: BlockT, Transaction> {
|
||||
/// 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<DigestItemFor<Block>>,
|
||||
/// 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, Transaction>,
|
||||
/// 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.
|
||||
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,
|
||||
/// Cached full header hash (with post-digests applied).
|
||||
pub post_hash: Option<Block::Hash>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT, Transaction> BlockImportParams<Block, Transaction> {
|
||||
/// 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,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Auxiliary function for "converting" the transaction type.
|
||||
///
|
||||
/// Actually this just sets `StorageChanges::Changes` to `None` and makes rustc think that `Self` now
|
||||
/// uses a different transaction type.
|
||||
pub fn clear_storage_changes_and_mutate<Transaction2>(
|
||||
self,
|
||||
) -> BlockImportParams<Block, Transaction2> {
|
||||
// Preserve imported state.
|
||||
let state_action = match self.state_action {
|
||||
StateAction::ApplyChanges(StorageChanges::Import(state)) =>
|
||||
StateAction::ApplyChanges(StorageChanges::Import(state)),
|
||||
StateAction::ApplyChanges(StorageChanges::Changes(_)) => StateAction::Skip,
|
||||
StateAction::Execute => StateAction::Execute,
|
||||
StateAction::ExecuteIfPossible => StateAction::ExecuteIfPossible,
|
||||
StateAction::Skip => StateAction::Skip,
|
||||
};
|
||||
BlockImportParams {
|
||||
origin: self.origin,
|
||||
header: self.header,
|
||||
justifications: self.justifications,
|
||||
post_digests: self.post_digests,
|
||||
body: self.body,
|
||||
indexed_body: self.indexed_body,
|
||||
state_action,
|
||||
finalized: self.finalized,
|
||||
auxiliary: self.auxiliary,
|
||||
intermediates: self.intermediates,
|
||||
fork_choice: self.fork_choice,
|
||||
import_existing: self.import_existing,
|
||||
post_hash: self.post_hash,
|
||||
}
|
||||
}
|
||||
|
||||
/// Take intermediate by given key, and remove it from the processing list.
|
||||
pub fn take_intermediate<T: 'static>(&mut self, key: &[u8]) -> Result<Box<T>, Error> {
|
||||
let (k, v) = self.intermediates.remove_entry(key).ok_or(Error::NoIntermediate)?;
|
||||
|
||||
v.downcast::<T>().or_else(|v| {
|
||||
self.intermediates.insert(k, v);
|
||||
Err(Error::InvalidIntermediate)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to a given intermediate.
|
||||
pub fn 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Block import trait.
|
||||
#[async_trait::async_trait]
|
||||
pub trait BlockImport<B: BlockT> {
|
||||
/// The error type.
|
||||
type Error: std::error::Error + Send + 'static;
|
||||
/// The transaction type used by the backend.
|
||||
type Transaction: Send + 'static;
|
||||
|
||||
/// Check block preconditions.
|
||||
async fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<B>,
|
||||
) -> Result<ImportResult, Self::Error>;
|
||||
|
||||
/// Import a block.
|
||||
///
|
||||
/// Cached data can be accessed through the blockchain cache.
|
||||
async fn import_block(
|
||||
&mut self,
|
||||
block: BlockImportParams<B, Self::Transaction>,
|
||||
cache: HashMap<CacheKeyId, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockT, Transaction> BlockImport<B> for crate::import_queue::BoxBlockImport<B, Transaction>
|
||||
where
|
||||
Transaction: Send + 'static,
|
||||
{
|
||||
type Error = crate::error::Error;
|
||||
type Transaction = Transaction;
|
||||
|
||||
/// Check block preconditions.
|
||||
async fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<B>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
(**self).check_block(block).await
|
||||
}
|
||||
|
||||
/// Import a block.
|
||||
///
|
||||
/// Cached data can be accessed through the blockchain cache.
|
||||
async fn import_block(
|
||||
&mut self,
|
||||
block: BlockImportParams<B, Transaction>,
|
||||
cache: HashMap<CacheKeyId, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
(**self).import_block(block, cache).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockT, T, E: std::error::Error + Send + 'static, Transaction> BlockImport<B> for Arc<T>
|
||||
where
|
||||
for<'r> &'r T: BlockImport<B, Error = E, Transaction = Transaction>,
|
||||
T: Send + Sync,
|
||||
Transaction: Send + 'static,
|
||||
{
|
||||
type Error = E;
|
||||
type Transaction = Transaction;
|
||||
|
||||
async fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<B>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
(&**self).check_block(block).await
|
||||
}
|
||||
|
||||
async fn import_block(
|
||||
&mut self,
|
||||
block: BlockImportParams<B, Transaction>,
|
||||
cache: HashMap<CacheKeyId, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
(&**self).import_block(block, cache).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);
|
||||
}
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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 std::collections::HashMap;
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{Block as BlockT, Header as _, NumberFor},
|
||||
Justifications,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
block_import::{
|
||||
BlockCheckParams, BlockImport, BlockImportParams, BlockOrigin, ImportResult, ImportedAux,
|
||||
ImportedState, JustificationImport, StateAction,
|
||||
},
|
||||
error::Error as ConsensusError,
|
||||
metrics::Metrics,
|
||||
};
|
||||
pub use basic_queue::BasicQueue;
|
||||
|
||||
/// 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, Client> =
|
||||
BasicQueue<Block, sp_api::TransactionFor<Client, Block>>;
|
||||
|
||||
mod basic_queue;
|
||||
pub mod buffered_link;
|
||||
|
||||
/// Shared block import struct used by the queue.
|
||||
pub type BoxBlockImport<B, Transaction> =
|
||||
Box<dyn BlockImport<B, Error = ConsensusError, Transaction = Transaction> + 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 Origin used by the network.
|
||||
pub type Origin = libp2p::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<Origin>,
|
||||
/// Allow importing the block skipping state verification if parent state is missing.
|
||||
pub allow_missing_state: bool,
|
||||
/// Skip block exection 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>>,
|
||||
}
|
||||
|
||||
/// Type of keys in the blockchain cache that consensus module could use for its needs.
|
||||
pub type CacheKeyId = [u8; 4];
|
||||
|
||||
/// Verify a justification of a block
|
||||
#[async_trait::async_trait]
|
||||
pub trait Verifier<B: BlockT>: Send + Sync {
|
||||
/// Verify the given data and return the BlockImportParams and an optional
|
||||
/// new set of validators to import. If not, err with an Error-Message
|
||||
/// presented to the User in the logs.
|
||||
async fn verify(
|
||||
&mut self,
|
||||
origin: BlockOrigin,
|
||||
header: B::Header,
|
||||
justifications: Option<Justifications>,
|
||||
body: Option<Vec<B::Extrinsic>>,
|
||||
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String>;
|
||||
}
|
||||
|
||||
/// Blocks import queue API.
|
||||
///
|
||||
/// The `import_*` methods can be called in order to send elements for the import queue to verify.
|
||||
/// Afterwards, call `poll_actions` to determine how to respond to these elements.
|
||||
pub trait ImportQueue<B: BlockT>: Send {
|
||||
/// Import bunch of blocks.
|
||||
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
|
||||
/// Import block justifications.
|
||||
fn import_justifications(
|
||||
&mut self,
|
||||
who: Origin,
|
||||
hash: B::Hash,
|
||||
number: NumberFor<B>,
|
||||
justifications: Justifications,
|
||||
);
|
||||
/// Polls for actions to perform on the network.
|
||||
///
|
||||
/// 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: &mut dyn Link<B>);
|
||||
}
|
||||
|
||||
/// Hooks that the verification queue can use to influence the synchronization
|
||||
/// algorithm.
|
||||
pub trait Link<B: BlockT>: Send {
|
||||
/// Batch of blocks imported, with or without error.
|
||||
fn blocks_processed(
|
||||
&mut self,
|
||||
_imported: usize,
|
||||
_count: usize,
|
||||
_results: Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)>,
|
||||
) {
|
||||
}
|
||||
/// Justification import result.
|
||||
fn justification_imported(
|
||||
&mut self,
|
||||
_who: Origin,
|
||||
_hash: &B::Hash,
|
||||
_number: NumberFor<B>,
|
||||
_success: bool,
|
||||
) {
|
||||
}
|
||||
/// Request a justification for the given block.
|
||||
fn request_justification(&mut self, _hash: &B::Hash, _number: NumberFor<B>) {}
|
||||
}
|
||||
|
||||
/// Block import successful result.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockImportResult<N: std::fmt::Debug + PartialEq> {
|
||||
/// Imported known block.
|
||||
ImportedKnown(N, Option<Origin>),
|
||||
/// Imported unknown block.
|
||||
ImportedUnknown(N, ImportedAux, Option<Origin>),
|
||||
}
|
||||
|
||||
/// Block import error.
|
||||
#[derive(Debug)]
|
||||
pub enum BlockImportError {
|
||||
/// Block missed header, can't be imported
|
||||
IncompleteHeader(Option<Origin>),
|
||||
/// Block verification failed, can't be imported
|
||||
VerificationFailed(Option<Origin>, String),
|
||||
/// Block is known to be Bad
|
||||
BadBlock(Option<Origin>),
|
||||
/// Parent state is missing.
|
||||
MissingState,
|
||||
/// Block has an unknown parent
|
||||
UnknownParent,
|
||||
/// Block import has been cancelled. This can happen if the parent block fails to be imported.
|
||||
Cancelled,
|
||||
/// Other error.
|
||||
Other(ConsensusError),
|
||||
}
|
||||
|
||||
/// Single block import function.
|
||||
pub async fn import_single_block<B: BlockT, V: Verifier<B>, Transaction: Send + 'static>(
|
||||
import_handle: &mut impl BlockImport<B, Transaction = Transaction, Error = ConsensusError>,
|
||||
block_origin: BlockOrigin,
|
||||
block: IncomingBlock<B>,
|
||||
verifier: &mut V,
|
||||
) -> Result<BlockImportResult<NumberFor<B>>, BlockImportError> {
|
||||
import_single_block_metered(import_handle, block_origin, block, verifier, None).await
|
||||
}
|
||||
|
||||
/// Single block import function with metering.
|
||||
pub(crate) async fn import_single_block_metered<
|
||||
B: BlockT,
|
||||
V: Verifier<B>,
|
||||
Transaction: Send + 'static,
|
||||
>(
|
||||
import_handle: &mut impl BlockImport<B, Transaction = Transaction, Error = ConsensusError>,
|
||||
block_origin: BlockOrigin,
|
||||
block: IncomingBlock<B>,
|
||||
verifier: &mut V,
|
||||
metrics: Option<Metrics>,
|
||||
) -> Result<BlockImportResult<NumberFor<B>>, BlockImportError> {
|
||||
let peer = block.origin;
|
||||
|
||||
let (header, justifications) = match (block.header, block.justifications) {
|
||||
(Some(header), justifications) => (header, justifications),
|
||||
(None, _) => {
|
||||
if let Some(ref peer) = peer {
|
||||
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer);
|
||||
} else {
|
||||
debug!(target: "sync", "Header {} was not provided ", block.hash);
|
||||
}
|
||||
return Err(BlockImportError::IncompleteHeader(peer))
|
||||
},
|
||||
};
|
||||
|
||||
trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len());
|
||||
|
||||
let number = header.number().clone();
|
||||
let hash = header.hash();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
|
||||
let import_handler = |import| match import {
|
||||
Ok(ImportResult::AlreadyInChain) => {
|
||||
trace!(target: "sync", "Block already in chain {}: {:?}", number, hash);
|
||||
Ok(BlockImportResult::ImportedKnown(number, peer.clone()))
|
||||
},
|
||||
Ok(ImportResult::Imported(aux)) =>
|
||||
Ok(BlockImportResult::ImportedUnknown(number, aux, peer.clone())),
|
||||
Ok(ImportResult::MissingState) => {
|
||||
debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash);
|
||||
Err(BlockImportError::MissingState)
|
||||
},
|
||||
Ok(ImportResult::UnknownParent) => {
|
||||
debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent_hash);
|
||||
Err(BlockImportError::UnknownParent)
|
||||
},
|
||||
Ok(ImportResult::KnownBad) => {
|
||||
debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash);
|
||||
Err(BlockImportError::BadBlock(peer.clone()))
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(target: "sync", "Error importing block {}: {:?}: {:?}", number, hash, e);
|
||||
Err(BlockImportError::Other(e))
|
||||
},
|
||||
};
|
||||
|
||||
match import_handler(
|
||||
import_handle
|
||||
.check_block(BlockCheckParams {
|
||||
hash,
|
||||
number,
|
||||
parent_hash,
|
||||
allow_missing_state: block.allow_missing_state,
|
||||
import_existing: block.import_existing,
|
||||
})
|
||||
.await,
|
||||
)? {
|
||||
BlockImportResult::ImportedUnknown { .. } => (),
|
||||
r => return Ok(r), // Any other successful result means that the block is already imported.
|
||||
}
|
||||
|
||||
let started = wasm_timer::Instant::now();
|
||||
let (mut import_block, maybe_keys) = verifier
|
||||
.verify(block_origin, header, justifications, block.body)
|
||||
.await
|
||||
.map_err(|msg| {
|
||||
if let Some(ref peer) = peer {
|
||||
trace!(target: "sync", "Verifying {}({}) from {} failed: {}", number, hash, peer, msg);
|
||||
} else {
|
||||
trace!(target: "sync", "Verifying {}({}) failed: {}", number, hash, msg);
|
||||
}
|
||||
if let Some(metrics) = metrics.as_ref() {
|
||||
metrics.report_verification(false, started.elapsed());
|
||||
}
|
||||
BlockImportError::VerificationFailed(peer.clone(), msg)
|
||||
})?;
|
||||
|
||||
if let Some(metrics) = metrics.as_ref() {
|
||||
metrics.report_verification(true, started.elapsed());
|
||||
}
|
||||
|
||||
let mut cache = HashMap::new();
|
||||
if let Some(keys) = maybe_keys {
|
||||
cache.extend(keys.into_iter());
|
||||
}
|
||||
import_block.import_existing = block.import_existing;
|
||||
import_block.indexed_body = block.indexed_body;
|
||||
let mut import_block = import_block.clear_storage_changes_and_mutate();
|
||||
if let Some(state) = block.state {
|
||||
import_block.state_action = StateAction::ApplyChanges(crate::StorageChanges::Import(state));
|
||||
} else if block.skip_execution {
|
||||
import_block.state_action = StateAction::Skip;
|
||||
} else if block.allow_missing_state {
|
||||
import_block.state_action = StateAction::ExecuteIfPossible;
|
||||
}
|
||||
|
||||
let imported = import_handle.import_block(import_block, cache).await;
|
||||
if let Some(metrics) = metrics.as_ref() {
|
||||
metrics.report_verification_and_import(started.elapsed());
|
||||
}
|
||||
import_handler(imported)
|
||||
}
|
||||
@@ -1,632 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::{
|
||||
prelude::*,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use prometheus_endpoint::Registry;
|
||||
use sp_runtime::{
|
||||
traits::{Block as BlockT, Header as HeaderT, NumberFor},
|
||||
Justification, Justifications,
|
||||
};
|
||||
use sp_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
|
||||
use std::{marker::PhantomData, pin::Pin, time::Duration};
|
||||
|
||||
use crate::{
|
||||
block_import::BlockOrigin,
|
||||
import_queue::{
|
||||
buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender},
|
||||
import_single_block_metered, BlockImportError, BlockImportResult, BoxBlockImport,
|
||||
BoxJustificationImport, ImportQueue, IncomingBlock, Link, Origin, Verifier,
|
||||
},
|
||||
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, Transaction> {
|
||||
/// Channel to send justifcation 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>>,
|
||||
/// Results coming from the worker task.
|
||||
result_port: BufferedLinkReceiver<B>,
|
||||
_phantom: PhantomData<Transaction>,
|
||||
}
|
||||
|
||||
impl<B: BlockT, Transaction> Drop for BasicQueue<B, Transaction> {
|
||||
fn drop(&mut self) {
|
||||
// Flush the queue and close the receiver to terminate the future.
|
||||
self.justification_sender.close_channel();
|
||||
self.block_import_sender.close_channel();
|
||||
self.result_port.close();
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT, Transaction: Send + 'static> BasicQueue<B, Transaction> {
|
||||
/// 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: 'static + Verifier<B>>(
|
||||
verifier: V,
|
||||
block_import: BoxBlockImport<B, Transaction>,
|
||||
justification_import: Option<BoxJustificationImport<B>>,
|
||||
spawner: &impl sp_core::traits::SpawnEssentialNamed,
|
||||
prometheus_registry: Option<&Registry>,
|
||||
) -> Self {
|
||||
let (result_sender, result_port) = buffered_link::buffered_link();
|
||||
|
||||
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", future.boxed());
|
||||
|
||||
Self { justification_sender, block_import_sender, result_port, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT, Transaction: Send> ImportQueue<B> for BasicQueue<B, Transaction> {
|
||||
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>) {
|
||||
if blocks.is_empty() {
|
||||
return
|
||||
}
|
||||
|
||||
trace!(target: "sync", "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: "sync",
|
||||
"import_blocks: Background import task is no longer alive"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn import_justifications(
|
||||
&mut self,
|
||||
who: Origin,
|
||||
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: "sync",
|
||||
"import_justification: Background import task is no longer alive"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_actions(&mut self, cx: &mut Context, link: &mut dyn Link<B>) {
|
||||
if self.result_port.poll_actions(cx, link).is_err() {
|
||||
log::error!(target: "sync", "poll_actions: Background import task is no longer alive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages destinated 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 Origin,
|
||||
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, Transaction: Send + 'static>(
|
||||
mut block_import: BoxBlockImport<B, Transaction>,
|
||||
mut verifier: impl Verifier<B>,
|
||||
mut result_sender: BufferedLinkSender<B>,
|
||||
mut block_import_receiver: TracingUnboundedReceiver<worker_messages::ImportBlocks<B>>,
|
||||
metrics: Option<Metrics>,
|
||||
delay_between_blocks: Duration,
|
||||
) {
|
||||
loop {
|
||||
let worker_messages::ImportBlocks(origin, blocks) = match block_import_receiver.next().await
|
||||
{
|
||||
Some(blocks) => blocks,
|
||||
None => {
|
||||
log::debug!(
|
||||
target: "block-import",
|
||||
"Stopping block import because the import channel was closed!",
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let res = import_many_blocks(
|
||||
&mut block_import,
|
||||
origin,
|
||||
blocks,
|
||||
&mut verifier,
|
||||
delay_between_blocks,
|
||||
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: 'static + Verifier<B>, Transaction: Send + 'static>(
|
||||
result_sender: BufferedLinkSender<B>,
|
||||
verifier: V,
|
||||
block_import: BoxBlockImport<B, Transaction>,
|
||||
justification_import: Option<BoxJustificationImport<B>>,
|
||||
metrics: Option<Metrics>,
|
||||
) -> (
|
||||
impl Future<Output = ()> + Send,
|
||||
TracingUnboundedSender<worker_messages::ImportJustification<B>>,
|
||||
TracingUnboundedSender<worker_messages::ImportBlocks<B>>,
|
||||
) {
|
||||
use worker_messages::*;
|
||||
|
||||
let (justification_sender, mut justification_port) =
|
||||
tracing_unbounded("mpsc_import_queue_worker_justification");
|
||||
|
||||
let (block_import_sender, block_import_port) =
|
||||
tracing_unbounded("mpsc_import_queue_worker_blocks");
|
||||
|
||||
let mut worker = BlockImportWorker { result_sender, justification_import, metrics };
|
||||
|
||||
let delay_between_blocks = Duration::default();
|
||||
|
||||
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_port,
|
||||
worker.metrics.clone(),
|
||||
delay_between_blocks,
|
||||
);
|
||||
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: "block-import",
|
||||
"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: "block-import",
|
||||
"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: Origin,
|
||||
hash: B::Hash,
|
||||
number: NumberFor<B>,
|
||||
justification: Justification,
|
||||
) {
|
||||
let started = wasm_timer::Instant::now();
|
||||
|
||||
let success = match self.justification_import.as_mut() {
|
||||
Some(justification_import) => justification_import
|
||||
.import_justification(hash, number, justification)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!(
|
||||
target: "sync",
|
||||
"Justification import failed with {:?} for hash: {:?} number: {:?} coming from node: {:?}",
|
||||
e,
|
||||
hash,
|
||||
number,
|
||||
who,
|
||||
);
|
||||
e
|
||||
})
|
||||
.is_ok(),
|
||||
None => false,
|
||||
};
|
||||
|
||||
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, success);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<BlockImportResult<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>, Transaction: Send + 'static>(
|
||||
import_handle: &mut BoxBlockImport<B, Transaction>,
|
||||
blocks_origin: BlockOrigin,
|
||||
blocks: Vec<IncomingBlock<B>>,
|
||||
verifier: &mut V,
|
||||
delay_between_blocks: Duration,
|
||||
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: "sync", "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().clone());
|
||||
let block_hash = block.hash;
|
||||
let import_result = if has_error {
|
||||
Err(BlockImportError::Cancelled)
|
||||
} else {
|
||||
// The actual import.
|
||||
import_single_block_metered(
|
||||
import_handle,
|
||||
blocks_origin.clone(),
|
||||
block,
|
||||
verifier,
|
||||
metrics.clone(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
if let Some(metrics) = metrics.as_ref() {
|
||||
metrics.report_import::<B>(&import_result);
|
||||
}
|
||||
|
||||
if import_result.is_ok() {
|
||||
trace!(
|
||||
target: "sync",
|
||||
"Block imported successfully {:?} ({})",
|
||||
block_number,
|
||||
block_hash,
|
||||
);
|
||||
imported += 1;
|
||||
} else {
|
||||
has_error = true;
|
||||
}
|
||||
|
||||
results.push((import_result, block_hash));
|
||||
|
||||
if delay_between_blocks != Duration::default() && !has_error {
|
||||
Delay::new(delay_between_blocks).await;
|
||||
} else {
|
||||
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::{
|
||||
import_queue::{CacheKeyId, Verifier},
|
||||
BlockCheckParams, BlockImport, BlockImportParams, ImportResult, JustificationImport,
|
||||
};
|
||||
use futures::{executor::block_on, Future};
|
||||
use sp_test_primitives::{Block, BlockNumber, Extrinsic, Hash, Header};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Verifier<Block> for () {
|
||||
async fn verify(
|
||||
&mut self,
|
||||
origin: BlockOrigin,
|
||||
header: Header,
|
||||
_justifications: Option<Justifications>,
|
||||
_body: Option<Vec<Extrinsic>>,
|
||||
) -> Result<(BlockImportParams<Block, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
|
||||
Ok((BlockImportParams::new(origin, header), None))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl BlockImport<Block> for () {
|
||||
type Error = crate::Error;
|
||||
type Transaction = Extrinsic;
|
||||
|
||||
async fn check_block(
|
||||
&mut self,
|
||||
_block: BlockCheckParams<Block>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
Ok(ImportResult::imported(false))
|
||||
}
|
||||
|
||||
async fn import_block(
|
||||
&mut self,
|
||||
_block: BlockImportParams<Block, Self::Transaction>,
|
||||
_cache: HashMap<CacheKeyId, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
Ok(ImportResult::imported(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl JustificationImport<Block> for () {
|
||||
type Error = crate::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: Vec<Event>,
|
||||
}
|
||||
|
||||
impl Link<Block> for TestLink {
|
||||
fn blocks_processed(
|
||||
&mut self,
|
||||
_imported: usize,
|
||||
_count: usize,
|
||||
results: Vec<(Result<BlockImportResult<BlockNumber>, BlockImportError>, Hash)>,
|
||||
) {
|
||||
if let Some(hash) = results.into_iter().find_map(|(r, h)| r.ok().map(|_| h)) {
|
||||
self.events.push(Event::BlockImported(hash));
|
||||
}
|
||||
}
|
||||
|
||||
fn justification_imported(
|
||||
&mut self,
|
||||
_who: Origin,
|
||||
hash: &Hash,
|
||||
_number: BlockNumber,
|
||||
_success: bool,
|
||||
) {
|
||||
self.events.push(Event::JustificationImported(hash.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prioritizes_finality_work_over_block_import() {
|
||||
let (result_sender, mut result_port) = buffered_link::buffered_link();
|
||||
|
||||
let (worker, mut finality_sender, mut block_import_sender) =
|
||||
BlockImportWorker::new(result_sender, (), Box::new(()), Some(Box::new(())), None);
|
||||
futures::pin_mut!(worker);
|
||||
|
||||
let mut 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_on(block_import_sender.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 mut import_justification = || {
|
||||
let hash = Hash::random();
|
||||
block_on(finality_sender.send(worker_messages::ImportJustification(
|
||||
libp2p::PeerId::random(),
|
||||
hash,
|
||||
1,
|
||||
(*b"TEST", Vec::new()),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
hash
|
||||
};
|
||||
|
||||
let mut 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.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, &mut link).unwrap();
|
||||
}
|
||||
|
||||
Poll::Ready(())
|
||||
}));
|
||||
|
||||
// all justification tasks must be done before any block import work
|
||||
assert_eq!(
|
||||
link.events,
|
||||
vec![
|
||||
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),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Provides the `buffered_link` utility.
|
||||
//!
|
||||
//! The buffered link is a channel that allows buffering the method calls on `Link`.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use sp_consensus::import_queue::Link;
|
||||
//! # use sp_consensus::import_queue::buffered_link::buffered_link;
|
||||
//! # use sp_test_primitives::Block;
|
||||
//! # struct DummyLink; impl Link<Block> for DummyLink {}
|
||||
//! # let mut my_link = DummyLink;
|
||||
//! let (mut tx, mut rx) = buffered_link::<Block>();
|
||||
//! 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, &mut my_link);
|
||||
//! std::task::Poll::Pending::<()>
|
||||
//! });
|
||||
//! ```
|
||||
|
||||
use crate::import_queue::{BlockImportError, BlockImportResult, Link, Origin};
|
||||
use futures::prelude::*;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
use sp_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/// 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.
|
||||
pub fn buffered_link<B: BlockT>() -> (BufferedLinkSender<B>, BufferedLinkReceiver<B>) {
|
||||
let (tx, rx) = tracing_unbounded("mpsc_buffered_link");
|
||||
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.
|
||||
enum BlockImportWorkerMsg<B: BlockT> {
|
||||
BlocksProcessed(
|
||||
usize,
|
||||
usize,
|
||||
Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)>,
|
||||
),
|
||||
JustificationImported(Origin, B::Hash, NumberFor<B>, bool),
|
||||
RequestJustification(B::Hash, NumberFor<B>),
|
||||
}
|
||||
|
||||
impl<B: BlockT> Link<B> for BufferedLinkSender<B> {
|
||||
fn blocks_processed(
|
||||
&mut self,
|
||||
imported: usize,
|
||||
count: usize,
|
||||
results: Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)>,
|
||||
) {
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(BlockImportWorkerMsg::BlocksProcessed(imported, count, results));
|
||||
}
|
||||
|
||||
fn justification_imported(
|
||||
&mut self,
|
||||
who: Origin,
|
||||
hash: &B::Hash,
|
||||
number: NumberFor<B>,
|
||||
success: bool,
|
||||
) {
|
||||
let msg = BlockImportWorkerMsg::JustificationImported(who, hash.clone(), number, success);
|
||||
let _ = self.tx.unbounded_send(msg);
|
||||
}
|
||||
|
||||
fn request_justification(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(BlockImportWorkerMsg::RequestJustification(hash.clone(), number));
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`buffered_link`].
|
||||
pub struct BufferedLinkReceiver<B: BlockT> {
|
||||
rx: stream::Fuse<TracingUnboundedReceiver<BlockImportWorkerMsg<B>>>,
|
||||
}
|
||||
|
||||
impl<B: BlockT> BufferedLinkReceiver<B> {
|
||||
/// 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: &mut 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(()),
|
||||
};
|
||||
|
||||
match msg {
|
||||
BlockImportWorkerMsg::BlocksProcessed(imported, count, results) =>
|
||||
link.blocks_processed(imported, count, results),
|
||||
BlockImportWorkerMsg::JustificationImported(who, hash, number, success) =>
|
||||
link.justification_imported(who, &hash, number, success),
|
||||
BlockImportWorkerMsg::RequestJustification(hash, number) =>
|
||||
link.request_justification(&hash, number),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the channel.
|
||||
pub fn close(&mut self) {
|
||||
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>();
|
||||
assert!(!tx.is_closed());
|
||||
drop(rx);
|
||||
assert!(tx.is_closed());
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,6 @@
|
||||
//! change. Implementors of traits should not rely on the interfaces to remain
|
||||
//! the same.
|
||||
|
||||
// This provides "unused" building blocks to other crates
|
||||
#![allow(dead_code)]
|
||||
// our error-chain could potentially blow up otherwise
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use futures::prelude::*;
|
||||
@@ -38,25 +30,19 @@ use sp_runtime::{
|
||||
};
|
||||
use sp_state_machine::StorageProof;
|
||||
|
||||
pub mod block_import;
|
||||
pub mod block_validation;
|
||||
pub mod error;
|
||||
pub mod evaluation;
|
||||
pub mod import_queue;
|
||||
mod metrics;
|
||||
mod select_chain;
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use block_import::{
|
||||
BlockCheckParams, BlockImport, BlockImportParams, BlockOrigin, ForkChoiceStrategy,
|
||||
ImportResult, ImportedAux, ImportedState, JustificationImport, JustificationSyncLink,
|
||||
StateAction, StorageChanges,
|
||||
};
|
||||
pub use import_queue::DefaultImportQueue;
|
||||
pub use select_chain::SelectChain;
|
||||
pub use sp_inherents::InherentData;
|
||||
pub use sp_state_machine::Backend as StateBackend;
|
||||
|
||||
/// Type of keys in the blockchain cache that consensus module could use for its needs.
|
||||
pub type CacheKeyId = [u8; 4];
|
||||
|
||||
/// Block status.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BlockStatus {
|
||||
@@ -72,6 +58,23 @@ pub enum BlockStatus {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Block data origin.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum BlockOrigin {
|
||||
/// Genesis block built into the client.
|
||||
Genesis,
|
||||
/// Block is part of the initial sync with the network.
|
||||
NetworkInitialSync,
|
||||
/// Block was broadcasted on the network.
|
||||
NetworkBroadcast,
|
||||
/// Block that was received from the network and validated in the consensus process.
|
||||
ConsensusBroadcast,
|
||||
/// Block that was collated by this node.
|
||||
Own,
|
||||
/// Block was imported from a file.
|
||||
File,
|
||||
}
|
||||
|
||||
/// Environment for a Consensus instance.
|
||||
///
|
||||
/// Creates proposer instance.
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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, BlockImportResult};
|
||||
|
||||
/// 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("import_queue_processed_total", "Blocks processed by import queue"),
|
||||
&["result"], // 'success or failure
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
block_verification_time: register(
|
||||
HistogramVec::new(
|
||||
HistogramOpts::new("block_verification_time", "Time taken to verify blocks"),
|
||||
&["result"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
block_verification_and_import_time: register(
|
||||
Histogram::with_opts(HistogramOpts::new(
|
||||
"block_verification_and_import_time",
|
||||
"Time taken to verify and import blocks",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
justification_import_time: register(
|
||||
Histogram::with_opts(HistogramOpts::new(
|
||||
"justification_import_time",
|
||||
"Time taken to import justifications",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn report_import<B: BlockT>(
|
||||
&self,
|
||||
result: &Result<BlockImportResult<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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user