// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus 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.
// Cumulus 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 Cumulus. If not, see .
use polkadot_primitives::{Hash as PHash, PersistedValidationData};
use sc_client_api::Backend;
use sc_consensus::{shared_data::SharedData, BlockImport, ImportResult};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::sync::Arc;
mod level_monitor;
mod parachain_consensus;
#[cfg(test)]
mod tests;
pub use parachain_consensus::run_parachain_consensus;
use level_monitor::LevelMonitor;
pub use level_monitor::{LevelLimit, MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT};
/// The result of [`ParachainConsensus::produce_candidate`].
pub struct ParachainCandidate {
/// The block that was built for this candidate.
pub block: B,
/// The proof that was recorded while building the block.
pub proof: sp_trie::StorageProof,
}
/// A specific parachain consensus implementation that can be used by a collator to produce candidates.
///
/// The collator will call [`Self::produce_candidate`] every time there is a free core for the parachain
/// this collator is collating for. It is the job of the consensus implementation to decide if this
/// specific collator should build a candidate for the given relay chain block. The consensus
/// implementation could, for example, check whether this specific collator is part of a staked set.
#[async_trait::async_trait]
pub trait ParachainConsensus: Send + Sync + dyn_clone::DynClone {
/// Produce a new candidate at the given parent block and relay-parent blocks.
///
/// Should return `None` if the consensus implementation decided that it shouldn't build a
/// candidate or if there occurred any error.
///
/// # NOTE
///
/// It is expected that the block is already imported when the future resolves.
async fn produce_candidate(
&mut self,
parent: &B::Header,
relay_parent: PHash,
validation_data: &PersistedValidationData,
) -> Option>;
}
dyn_clone::clone_trait_object!( ParachainConsensus where B: BlockT);
#[async_trait::async_trait]
impl ParachainConsensus for Box + Send + Sync> {
async fn produce_candidate(
&mut self,
parent: &B::Header,
relay_parent: PHash,
validation_data: &PersistedValidationData,
) -> Option> {
(*self).produce_candidate(parent, relay_parent, validation_data).await
}
}
/// Parachain specific block import.
///
/// This is used to set `block_import_params.fork_choice` to `false` as long as the block origin is
/// not `NetworkInitialSync`. The best block for parachains is determined by the relay chain. Meaning
/// we will update the best block, as it is included by the relay-chain.
pub struct ParachainBlockImport {
inner: BI,
monitor: Option>>,
}
impl> ParachainBlockImport {
/// Create a new instance.
///
/// The number of leaves per level limit is set to `LevelLimit::Default`.
pub fn new(inner: BI, backend: Arc) -> Self {
Self::new_with_limit(inner, backend, LevelLimit::Default)
}
/// Create a new instance with an explicit limit to the number of leaves per level.
///
/// This function alone doesn't enforce the limit on levels for old imported blocks,
/// the limit is eventually enforced only when new blocks are imported.
pub fn new_with_limit(inner: BI, backend: Arc, level_leaves_max: LevelLimit) -> Self {
let level_limit = match level_leaves_max {
LevelLimit::None => None,
LevelLimit::Some(limit) => Some(limit),
LevelLimit::Default => Some(MAX_LEAVES_PER_LEVEL_SENSIBLE_DEFAULT),
};
let monitor =
level_limit.map(|level_limit| SharedData::new(LevelMonitor::new(level_limit, backend)));
Self { inner, monitor }
}
}
impl Clone for ParachainBlockImport {
fn clone(&self) -> Self {
ParachainBlockImport { inner: self.inner.clone(), monitor: self.monitor.clone() }
}
}
#[async_trait::async_trait]
impl BlockImport for ParachainBlockImport
where
Block: BlockT,
BI: BlockImport + Send,
BE: Backend,
{
type Error = BI::Error;
type Transaction = BI::Transaction;
async fn check_block(
&mut self,
block: sc_consensus::BlockCheckParams,
) -> Result {
self.inner.check_block(block).await
}
async fn import_block(
&mut self,
mut params: sc_consensus::BlockImportParams,
) -> Result {
// Blocks are stored within the backend by using POST hash.
let hash = params.post_hash();
let number = *params.header.number();
// Best block is determined by the relay chain, or if we are doing the initial sync
// we import all blocks as new best.
params.fork_choice = Some(sc_consensus::ForkChoiceStrategy::Custom(
params.origin == sp_consensus::BlockOrigin::NetworkInitialSync,
));
let maybe_lock = self.monitor.as_ref().map(|monitor_lock| {
let mut monitor = monitor_lock.shared_data_locked();
monitor.enforce_limit(number);
monitor.release_mutex()
});
let res = self.inner.import_block(params).await?;
if let (Some(mut monitor_lock), ImportResult::Imported(_)) = (maybe_lock, &res) {
let mut monitor = monitor_lock.upgrade();
monitor.block_imported(number, hash);
}
Ok(res)
}
}
/// Marker trait denoting a block import type that fits the parachain requirements.
pub trait ParachainBlockImportMarker {}
impl ParachainBlockImportMarker for ParachainBlockImport {}