// This file is part of Substrate.
// Copyright (C) 2018-2022 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 .
//! A consensus proposer for "basic" chains which use the primitive inherent-data.
// FIXME #1021 move this into sp-consensus
use codec::Encode;
use futures::{
channel::oneshot,
future,
future::{Future, FutureExt},
select,
};
use log::{debug, error, info, trace, warn};
use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider};
use sc_client_api::backend;
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO};
use sc_transaction_pool_api::{InPoolTransaction, TransactionPool};
use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed, HeaderBackend};
use sp_consensus::{DisableProofRecording, EnableProofRecording, ProofRecording, Proposal};
use sp_core::traits::SpawnNamed;
use sp_inherents::InherentData;
use sp_runtime::{
generic::BlockId,
traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT},
Digest, Percent, SaturatedConversion,
};
use std::{marker::PhantomData, pin::Pin, sync::Arc, time};
use prometheus_endpoint::Registry as PrometheusRegistry;
use sc_proposer_metrics::{EndProposingReason, MetricsLink as PrometheusMetrics};
/// Default block size limit in bytes used by [`Proposer`].
///
/// Can be overwritten by [`ProposerFactory::set_default_block_size_limit`].
///
/// Be aware that there is also an upper packet size on what the networking code
/// will accept. If the block doesn't fit in such a package, it can not be
/// transferred to other nodes.
pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512;
const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50);
/// [`Proposer`] factory.
pub struct ProposerFactory {
spawn_handle: Box,
/// The client instance.
client: Arc,
/// The transaction pool.
transaction_pool: Arc,
/// Prometheus Link,
metrics: PrometheusMetrics,
/// The default block size limit.
///
/// If no `block_size_limit` is passed to [`sp_consensus::Proposer::propose`], this block size
/// limit will be used.
default_block_size_limit: usize,
/// Soft deadline percentage of hard deadline.
///
/// The value is used to compute soft deadline during block production.
/// The soft deadline indicates where we should stop attempting to add transactions
/// to the block, which exhaust resources. After soft deadline is reached,
/// we switch to a fixed-amount mode, in which after we see `MAX_SKIPPED_TRANSACTIONS`
/// transactions which exhaust resrouces, we will conclude that the block is full.
soft_deadline_percent: Percent,
telemetry: Option,
/// When estimating the block size, should the proof be included?
include_proof_in_block_size_estimation: bool,
/// phantom member to pin the `Backend`/`ProofRecording` type.
_phantom: PhantomData<(B, PR)>,
}
impl ProposerFactory {
/// Create a new proposer factory.
///
/// Proof recording will be disabled when using proposers built by this instance to build
/// blocks.
pub fn new(
spawn_handle: impl SpawnNamed + 'static,
client: Arc,
transaction_pool: Arc,
prometheus: Option<&PrometheusRegistry>,
telemetry: Option,
) -> Self {
ProposerFactory {
spawn_handle: Box::new(spawn_handle),
transaction_pool,
metrics: PrometheusMetrics::new(prometheus),
default_block_size_limit: DEFAULT_BLOCK_SIZE_LIMIT,
soft_deadline_percent: DEFAULT_SOFT_DEADLINE_PERCENT,
telemetry,
client,
include_proof_in_block_size_estimation: false,
_phantom: PhantomData,
}
}
}
impl ProposerFactory {
/// Create a new proposer factory with proof recording enabled.
///
/// Each proposer created by this instance will record a proof while building a block.
///
/// This will also include the proof into the estimation of the block size. This can be disabled
/// by calling [`ProposerFactory::disable_proof_in_block_size_estimation`].
pub fn with_proof_recording(
spawn_handle: impl SpawnNamed + 'static,
client: Arc,
transaction_pool: Arc,
prometheus: Option<&PrometheusRegistry>,
telemetry: Option,
) -> Self {
ProposerFactory {
client,
spawn_handle: Box::new(spawn_handle),
transaction_pool,
metrics: PrometheusMetrics::new(prometheus),
default_block_size_limit: DEFAULT_BLOCK_SIZE_LIMIT,
soft_deadline_percent: DEFAULT_SOFT_DEADLINE_PERCENT,
telemetry,
include_proof_in_block_size_estimation: true,
_phantom: PhantomData,
}
}
/// Disable the proof inclusion when estimating the block size.
pub fn disable_proof_in_block_size_estimation(&mut self) {
self.include_proof_in_block_size_estimation = false;
}
}
impl ProposerFactory {
/// Set the default block size limit in bytes.
///
/// The default value for the block size limit is:
/// [`DEFAULT_BLOCK_SIZE_LIMIT`].
///
/// If there is no block size limit passed to [`sp_consensus::Proposer::propose`], this value
/// will be used.
pub fn set_default_block_size_limit(&mut self, limit: usize) {
self.default_block_size_limit = limit;
}
/// Set soft deadline percentage.
///
/// The value is used to compute soft deadline during block production.
/// The soft deadline indicates where we should stop attempting to add transactions
/// to the block, which exhaust resources. After soft deadline is reached,
/// we switch to a fixed-amount mode, in which after we see `MAX_SKIPPED_TRANSACTIONS`
/// transactions which exhaust resrouces, we will conclude that the block is full.
///
/// Setting the value too low will significantly limit the amount of transactions
/// we try in case they exhaust resources. Setting the value too high can
/// potentially open a DoS vector, where many "exhaust resources" transactions
/// are being tried with no success, hence block producer ends up creating an empty block.
pub fn set_soft_deadline(&mut self, percent: Percent) {
self.soft_deadline_percent = percent;
}
}
impl ProposerFactory
where
A: TransactionPool + 'static,
B: backend::Backend + Send + Sync + 'static,
Block: BlockT,
C: BlockBuilderProvider
+ HeaderBackend
+ ProvideRuntimeApi
+ Send
+ Sync
+ 'static,
C::Api:
ApiExt> + BlockBuilderApi,
{
fn init_with_now(
&mut self,
parent_header: &::Header,
now: Box time::Instant + Send + Sync>,
) -> Proposer {
let parent_hash = parent_header.hash();
let id = BlockId::hash(parent_hash);
info!("🙌 Starting consensus session on top of parent {:?}", parent_hash);
let proposer = Proposer::<_, _, _, _, PR> {
spawn_handle: self.spawn_handle.clone(),
client: self.client.clone(),
parent_id: id,
parent_number: *parent_header.number(),
transaction_pool: self.transaction_pool.clone(),
now,
metrics: self.metrics.clone(),
default_block_size_limit: self.default_block_size_limit,
soft_deadline_percent: self.soft_deadline_percent,
telemetry: self.telemetry.clone(),
_phantom: PhantomData,
include_proof_in_block_size_estimation: self.include_proof_in_block_size_estimation,
};
proposer
}
}
impl sp_consensus::Environment for ProposerFactory
where
A: TransactionPool + 'static,
B: backend::Backend + Send + Sync + 'static,
Block: BlockT,
C: BlockBuilderProvider
+ HeaderBackend
+ ProvideRuntimeApi
+ Send
+ Sync
+ 'static,
C::Api:
ApiExt> + BlockBuilderApi,
PR: ProofRecording,
{
type CreateProposer = future::Ready>;
type Proposer = Proposer;
type Error = sp_blockchain::Error;
fn init(&mut self, parent_header: &::Header) -> Self::CreateProposer {
future::ready(Ok(self.init_with_now(parent_header, Box::new(time::Instant::now))))
}
}
/// The proposer logic.
pub struct Proposer {
spawn_handle: Box,
client: Arc,
parent_id: BlockId,
parent_number: <::Header as HeaderT>::Number,
transaction_pool: Arc,
now: Box time::Instant + Send + Sync>,
metrics: PrometheusMetrics,
default_block_size_limit: usize,
include_proof_in_block_size_estimation: bool,
soft_deadline_percent: Percent,
telemetry: Option,
_phantom: PhantomData<(B, PR)>,
}
impl sp_consensus::Proposer for Proposer
where
A: TransactionPool + 'static,
B: backend::Backend + Send + Sync + 'static,
Block: BlockT,
C: BlockBuilderProvider
+ HeaderBackend
+ ProvideRuntimeApi
+ Send
+ Sync
+ 'static,
C::Api:
ApiExt> + BlockBuilderApi,
PR: ProofRecording,
{
type Transaction = backend::TransactionFor;
type Proposal = Pin<
Box<
dyn Future