// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see .
//! # BABE (Blind Assignment for Blockchain Extension)
//!
//! BABE is a slot-based block production mechanism which uses a VRF PRNG to
//! randomly perform the slot allocation. On every slot, all the authorities
//! generate a new random number with the VRF function and if it is lower than a
//! given threshold (which is proportional to their weight/stake) they have a
//! right to produce a block. The proof of the VRF function execution will be
//! used by other peer to validate the legitimacy of the slot claim.
//!
//! The engine is also responsible for collecting entropy on-chain which will be
//! used to seed the given VRF PRNG. An epoch is a contiguous number of slots
//! under which we will be using the same authority set. During an epoch all VRF
//! outputs produced as a result of block production will be collected on an
//! on-chain randomness pool. Epoch changes are announced one epoch in advance,
//! i.e. when ending epoch N, we announce the parameters (randomness,
//! authorities, etc.) for epoch N+2.
//!
//! Since the slot assignment is randomized, it is possible that a slot is
//! assigned to multiple validators in which case we will have a temporary fork,
//! or that a slot is assigned to no validator in which case no block is
//! produced. Which means that block times are not deterministic.
//!
//! The protocol has a parameter `c` [0, 1] for which `1 - c` is the probability
//! of a slot being empty. The choice of this parameter affects the security of
//! the protocol relating to maximum tolerable network delays.
//!
//! In addition to the VRF-based slot assignment described above, which we will
//! call primary slots, the engine also supports a deterministic secondary slot
//! assignment. Primary slots take precedence over secondary slots, when
//! authoring the node starts by trying to claim a primary slot and falls back
//! to a secondary slot claim attempt. The secondary slot assignment is done
//! by picking the authority at index:
//!
//! `blake2_256(epoch_randomness ++ slot_number) % authorities_len`.
//!
//! The fork choice rule is weight-based, where weight equals the number of
//! primary blocks in the chain. We will pick the heaviest chain (more primary
//! blocks) and will go with the longest one in case of a tie.
//!
//! An in-depth description and analysis of the protocol can be found here:
//!
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub use babe_primitives::{
BabeApi, ConsensusLog, BABE_ENGINE_ID, BabePreDigest, SlotNumber, BabeConfiguration,
CompatibleDigestItem,
};
pub use consensus_common::SyncOracle;
use std::{collections::HashMap, sync::Arc, u64, pin::Pin, time::{Instant, Duration}};
use babe_primitives;
use consensus_common::{ImportResult, CanAuthorWith};
use consensus_common::import_queue::{
BoxJustificationImport, BoxFinalityProofImport,
};
use sp_runtime::{generic::{BlockId, OpaqueDigestItemId}, Justification};
use sp_runtime::traits::{
Block as BlockT, Header, DigestItemFor, ProvideRuntimeApi,
Zero,
};
use keystore::KeyStorePtr;
use parking_lot::Mutex;
use primitives::{Blake2Hasher, H256, Pair};
use inherents::{InherentDataProviders, InherentData};
use sc_telemetry::{telemetry, CONSENSUS_TRACE, CONSENSUS_DEBUG};
use consensus_common::{
self, BlockImport, Environment, Proposer, BlockCheckParams,
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
SelectChain, SlotData,
};
use babe_primitives::inherents::BabeInherentData;
use sp_timestamp::{TimestampInherentData, InherentType as TimestampInherent};
use consensus_common::import_queue::{Verifier, BasicQueue, CacheKeyId};
use client_api::{
backend::{AuxStore, Backend},
call_executor::CallExecutor,
BlockchainEvents, ProvideUncles,
};
use client::Client;
use block_builder_api::BlockBuilder as BlockBuilderApi;
use slots::{CheckedHeader, check_equivocation};
use futures::prelude::*;
use log::{warn, debug, info, trace};
use slots::{SlotWorker, SlotInfo, SlotCompatible};
use epoch_changes::descendent_query;
use sp_blockchain::{
Result as ClientResult, Error as ClientError,
HeaderBackend, ProvideCache, HeaderMetadata
};
use schnorrkel::SignatureError;
use sp_api::ApiExt;
mod aux_schema;
mod verification;
mod epoch_changes;
mod authorship;
#[cfg(test)]
mod tests;
pub use babe_primitives::{
AuthorityId, AuthorityPair, AuthoritySignature, Epoch, NextEpochDescriptor,
};
pub use epoch_changes::{EpochChanges, EpochChangesFor, SharedEpochChanges};
#[derive(derive_more::Display, Debug)]
enum Error {
#[display(fmt = "Multiple BABE pre-runtime digests, rejecting!")]
MultiplePreRuntimeDigests,
#[display(fmt = "No BABE pre-runtime digest found")]
NoPreRuntimeDigest,
#[display(fmt = "Multiple BABE epoch change digests, rejecting!")]
MultipleEpochChangeDigests,
#[display(fmt = "Could not extract timestamp and slot: {:?}", _0)]
Extraction(consensus_common::Error),
#[display(fmt = "Could not fetch epoch at {:?}", _0)]
FetchEpoch(B::Hash),
#[display(fmt = "Header {:?} rejected: too far in the future", _0)]
TooFarInFuture(B::Hash),
#[display(fmt = "Parent ({}) of {} unavailable. Cannot import", _0, _1)]
ParentUnavailable(B::Hash, B::Hash),
#[display(fmt = "Slot number must increase: parent slot: {}, this slot: {}", _0, _1)]
SlotNumberMustIncrease(u64, u64),
#[display(fmt = "Header {:?} has a bad seal", _0)]
HeaderBadSeal(B::Hash),
#[display(fmt = "Header {:?} is unsealed", _0)]
HeaderUnsealed(B::Hash),
#[display(fmt = "Slot author not found")]
SlotAuthorNotFound,
#[display(fmt = "Secondary slot assignments are disabled for the current epoch.")]
SecondarySlotAssignmentsDisabled,
#[display(fmt = "Bad signature on {:?}", _0)]
BadSignature(B::Hash),
#[display(fmt = "Invalid author: Expected secondary author: {:?}, got: {:?}.", _0, _1)]
InvalidAuthor(AuthorityId, AuthorityId),
#[display(fmt = "No secondary author expected.")]
NoSecondaryAuthorExpected,
#[display(fmt = "VRF verification of block by author {:?} failed: threshold {} exceeded", _0, _1)]
VRFVerificationOfBlockFailed(AuthorityId, u128),
#[display(fmt = "VRF verification failed: {:?}", _0)]
VRFVerificationFailed(SignatureError),
#[display(fmt = "Could not fetch parent header: {:?}", _0)]
FetchParentHeader(sp_blockchain::Error),
#[display(fmt = "Expected epoch change to happen at {:?}, s{}", _0, _1)]
ExpectedEpochChange(B::Hash, u64),
#[display(fmt = "Could not look up epoch: {:?}", _0)]
CouldNotLookUpEpoch(Box>),
#[display(fmt = "Block {} is not valid under any epoch.", _0)]
BlockNotValid(B::Hash),
#[display(fmt = "Unexpected epoch change")]
UnexpectedEpochChange,
#[display(fmt = "Parent block of {} has no associated weight", _0)]
ParentBlockNoAssociatedWeight(B::Hash),
#[display(fmt = "Checking inherents failed: {}", _0)]
CheckInherents(String),
Client(sp_blockchain::Error),
Runtime(inherents::Error),
ForkTree(Box>),
}
impl std::convert::From> for String {
fn from(error: Error) -> String {
error.to_string()
}
}
fn babe_err(error: Error) -> Error {
debug!(target: "babe", "{}", error);
error
}
macro_rules! babe_info {
($($i: expr),+) => {
{
info!(target: "babe", $($i),+);
format!($($i),+)
}
};
}
/// A slot duration. Create with `get_or_compute`.
// FIXME: Once Rust has higher-kinded types, the duplication between this
// and `super::babe::Config` can be eliminated.
// https://github.com/paritytech/substrate/issues/2434
#[derive(Clone)]
pub struct Config(slots::SlotDuration);
impl Config {
/// Either fetch the slot duration from disk or compute it from the genesis
/// state.
pub fn get_or_compute(client: &C) -> ClientResult where
C: AuxStore + ProvideRuntimeApi, C::Api: BabeApi,
{
trace!(target: "babe", "Getting slot duration");
match slots::SlotDuration::get_or_compute(client, |a, b| a.configuration(b)).map(Self) {
Ok(s) => Ok(s),
Err(s) => {
warn!(target: "babe", "Failed to get slot duration");
Err(s)
}
}
}
/// Create the genesis epoch (epoch #0). This is defined to start at the slot of
/// the first block, so that has to be provided.
pub fn genesis_epoch(&self, slot_number: SlotNumber) -> Epoch {
Epoch {
epoch_index: 0,
start_slot: slot_number,
duration: self.epoch_length,
authorities: self.genesis_authorities.clone(),
randomness: self.randomness.clone(),
}
}
}
impl std::ops::Deref for Config {
type Target = BabeConfiguration;
fn deref(&self) -> &BabeConfiguration {
&*self.0
}
}
/// Parameters for BABE.
pub struct BabeParams {
/// The keystore that manages the keys of the node.
pub keystore: KeyStorePtr,
/// The client to use
pub client: Arc,
/// The SelectChain Strategy
pub select_chain: SC,
/// The environment we are producing blocks for.
pub env: E,
/// The underlying block-import object to supply our produced blocks to.
/// This must be a `BabeBlockImport` or a wrapper of it, otherwise
/// critical consensus logic will be omitted.
pub block_import: I,
/// A sync oracle
pub sync_oracle: SO,
/// Providers for inherent data.
pub inherent_data_providers: InherentDataProviders,
/// Force authoring of blocks even if we are offline
pub force_authoring: bool,
/// The source of timestamps for relative slots
pub babe_link: BabeLink,
/// Checks if the current native implementation can author with a runtime at a given block.
pub can_author_with: CAW,
}
/// Start the babe worker. The returned future should be run in a tokio runtime.
pub fn start_babe(BabeParams {
keystore,
client,
select_chain,
env,
block_import,
sync_oracle,
inherent_data_providers,
force_authoring,
babe_link,
can_author_with,
}: BabeParams) -> Result<
impl futures01::Future,
consensus_common::Error,
> where
B: BlockT,
C: ProvideRuntimeApi + ProvideCache + ProvideUncles + BlockchainEvents
+ HeaderBackend + HeaderMetadata + Send + Sync + 'static,
C::Api: BabeApi,
SC: SelectChain + 'static,
E: Environment + Send + Sync,
E::Proposer: Proposer,
>::Create: Unpin + Send + 'static,
I: BlockImport + Send + Sync + 'static,
Error: std::error::Error + Send + From<::consensus_common::Error> + From + 'static,
SO: SyncOracle + Send + Sync + Clone,
CAW: CanAuthorWith + Send,
{
let config = babe_link.config;
let worker = BabeWorker {
client: client.clone(),
block_import: Arc::new(Mutex::new(block_import)),
env,
sync_oracle: sync_oracle.clone(),
force_authoring,
keystore,
epoch_changes: babe_link.epoch_changes.clone(),
config: config.clone(),
};
register_babe_inherent_data_provider(&inherent_data_providers, config.slot_duration())?;
uncles::register_uncles_inherent_data_provider(
client.clone(),
select_chain.clone(),
&inherent_data_providers,
)?;
babe_info!("Starting BABE Authorship worker");
let slot_worker = slots::start_slot_worker(
config.0,
select_chain,
worker,
sync_oracle,
inherent_data_providers,
babe_link.time_source,
can_author_with,
);
Ok(slot_worker.map(|_| Ok::<(), ()>(())).compat())
}
struct BabeWorker {
client: Arc,
block_import: Arc>,
env: E,
sync_oracle: SO,
force_authoring: bool,
keystore: KeyStorePtr,
epoch_changes: SharedEpochChanges,
config: Config,
}
impl slots::SimpleSlotWorker for BabeWorker where
B: BlockT,
C: ProvideRuntimeApi + ProvideCache + HeaderBackend + HeaderMetadata,
C::Api: BabeApi,
E: Environment,
E::Proposer: Proposer,
>::Create: Unpin + Send + 'static,
I: BlockImport + Send + Sync + 'static,
SO: SyncOracle + Send + Clone,
Error: std::error::Error + Send + From<::consensus_common::Error> + From + 'static,
{
type EpochData = Epoch;
type Claim = (BabePreDigest, AuthorityPair);
type SyncOracle = SO;
type Proposer = E::Proposer;
type BlockImport = I;
fn logging_target(&self) -> &'static str {
"babe"
}
fn block_import(&self) -> Arc> {
self.block_import.clone()
}
fn epoch_data(&self, parent: &B::Header, slot_number: u64) -> Result {
self.epoch_changes.lock().epoch_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
parent.number().clone(),
slot_number,
|slot| self.config.genesis_epoch(slot)
)
.map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?
.map(|e| e.into_inner())
.ok_or(consensus_common::Error::InvalidAuthoritiesSet)
}
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize {
epoch_data.authorities.len()
}
fn claim_slot(
&self,
_parent_header: &B::Header,
slot_number: SlotNumber,
epoch_data: &Epoch,
) -> Option {
debug!(target: "babe", "Attempting to claim slot {}", slot_number);
let s = authorship::claim_slot(
slot_number,
epoch_data,
&*self.config,
&self.keystore,
);
if let Some(_) = s {
debug!(target: "babe", "Claimed slot {}", slot_number);
}
s
}
fn pre_digest_data(&self, _slot_number: u64, claim: &Self::Claim) -> Vec> {
vec![
as CompatibleDigestItem>::babe_pre_digest(claim.0.clone()),
]
}
fn block_import_params(&self) -> Box,
Self::Claim,
) -> consensus_common::BlockImportParams + Send> {
Box::new(|header, header_hash, body, (_, pair)| {
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let signature = pair.sign(header_hash.as_ref());
let signature_digest_item = as CompatibleDigestItem>::babe_seal(signature);
BlockImportParams {
origin: BlockOrigin::Own,
header,
justification: None,
post_digests: vec![signature_digest_item],
body: Some(body),
finalized: false,
auxiliary: Vec::new(), // block-weight is written in block import.
// TODO: block-import handles fork choice and this shouldn't even have the
// option to specify one.
// https://github.com/paritytech/substrate/issues/3623
fork_choice: ForkChoiceStrategy::LongestChain,
allow_missing_state: false,
import_existing: false,
}
})
}
fn force_authoring(&self) -> bool {
self.force_authoring
}
fn sync_oracle(&mut self) -> &mut Self::SyncOracle {
&mut self.sync_oracle
}
fn proposer(&mut self, block: &B::Header) -> Result {
self.env.init(block).map_err(|e| {
consensus_common::Error::ClientImport(format!("{:?}", e))
})
}
fn proposing_remaining_duration(
&self,
head: &B::Header,
slot_info: &SlotInfo
) -> Option {
// never give more than 20 times more lenience.
const BACKOFF_CAP: u64 = 20;
let slot_remaining = self.slot_remaining_duration(slot_info);
let parent_slot = match find_pre_digest::(head) {
Err(_) => return Some(slot_remaining),
Ok(d) => d.slot_number(),
};
// we allow a lenience of the number of slots since the head of the
// chain was produced, minus 1 (since there is always a difference of at least 1)
//
// linear back-off.
// in normal cases we only attempt to issue blocks up to the end of the slot.
// when the chain has been stalled for a few slots, we give more lenience.
let slot_lenience = slot_info.number.saturating_sub(parent_slot + 1);
let slot_lenience = std::cmp::min(slot_lenience, BACKOFF_CAP);
let slot_lenience = Duration::from_secs(slot_lenience * slot_info.duration);
Some(slot_lenience + slot_remaining)
}
}
impl SlotWorker for BabeWorker where
B: BlockT,
C: ProvideRuntimeApi + ProvideCache + HeaderBackend + HeaderMetadata + Send + Sync,
C::Api: BabeApi,
E: Environment + Send + Sync,
E::Proposer: Proposer,
>::Create: Unpin + Send + 'static,
I: BlockImport + Send + Sync + 'static,
SO: SyncOracle + Send + Sync + Clone,
Error: std::error::Error + Send + From<::consensus_common::Error> + From + 'static,
{
type OnSlot = Pin> + Send>>;
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo) -> Self::OnSlot {
>::on_slot(self, chain_head, slot_info)
}
}
/// Extract the BABE pre digest from the given header. Pre-runtime digests are
/// mandatory, the function will return `Err` if none is found.
fn find_pre_digest(header: &B::Header) -> Result>
{
// genesis block doesn't contain a pre digest so let's generate a
// dummy one to not break any invariants in the rest of the code
if header.number().is_zero() {
return Ok(BabePreDigest::Secondary {
slot_number: 0,
authority_index: 0,
});
}
let mut pre_digest: Option<_> = None;
for log in header.digest().logs() {
trace!(target: "babe", "Checking log {:?}, looking for pre runtime digest", log);
match (log.as_babe_pre_digest(), pre_digest.is_some()) {
(Some(_), true) => return Err(babe_err(Error::MultiplePreRuntimeDigests)),
(None, _) => trace!(target: "babe", "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or_else(|| babe_err(Error::NoPreRuntimeDigest))
}
/// Extract the BABE epoch change digest from the given header, if it exists.
fn find_next_epoch_digest(header: &B::Header)
-> Result