// 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 consensus
//!
//! BABE (Blind Assignment for Blockchain Extension) consensus in Substrate.
#![forbid(unsafe_code, missing_docs)]
pub use babe_primitives::*;
pub use consensus_common::SyncOracle;
use std::{collections::HashMap, sync::Arc, u64, fmt::{Debug, Display}, pin::Pin, time::{Instant, Duration}};
use babe_primitives;
use consensus_common::ImportResult;
use consensus_common::import_queue::{
BoxJustificationImport, BoxFinalityProofImport,
};
use consensus_common::well_known_cache_keys::Id as CacheKeyId;
use sr_primitives::{generic, generic::{BlockId, OpaqueDigestItemId}, Justification};
use sr_primitives::traits::{
Block as BlockT, Header, DigestItemFor, NumberFor, ProvideRuntimeApi,
SimpleBitOps, Zero,
};
use keystore::KeyStorePtr;
use runtime_support::serde::{Serialize, Deserialize};
use codec::{Decode, Encode};
use parking_lot::{Mutex, MutexGuard};
use primitives::{Blake2Hasher, H256, Pair, Public};
use merlin::Transcript;
use inherents::{InherentDataProviders, InherentData};
use substrate_telemetry::{
telemetry,
CONSENSUS_TRACE,
CONSENSUS_DEBUG,
CONSENSUS_WARN,
CONSENSUS_INFO,
};
use schnorrkel::{
keys::Keypair,
vrf::{
VRFProof, VRFProofBatchable, VRFInOut,
},
};
use consensus_common::{
self, BlockImport, Environment, Proposer,
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
};
use srml_babe::{
BabeInherentData,
timestamp::{TimestampInherentData, InherentType as TimestampInherent}
};
use consensus_common::{SelectChain, well_known_cache_keys};
use consensus_common::import_queue::{Verifier, BasicQueue};
use client::{
block_builder::api::BlockBuilder as BlockBuilderApi,
blockchain::{self, HeaderBackend, ProvideCache}, BlockchainEvents, CallExecutor, Client,
runtime_api::ApiExt, error::Result as ClientResult, backend::{AuxStore, Backend},
ProvideUncles,
utils::is_descendent_of,
};
use fork_tree::ForkTree;
use slots::{CheckedHeader, check_equivocation};
use futures::{prelude::*, future};
use futures01::Stream as _;
use futures_timer::Delay;
use log::{error, warn, debug, info, trace};
use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible, SignedDuration};
mod aux_schema;
#[cfg(test)]
mod tests;
pub use babe_primitives::{AuthorityId, AuthorityPair, AuthoritySignature};
/// 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
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.startup_data(b)).map(Self) {
Ok(s) => Ok(s),
Err(s) => {
warn!(target: "babe", "Failed to get slot duration");
Err(s)
}
}
}
/// Get the slot duration in milliseconds.
pub fn get(&self) -> u64 {
self.0.slot_duration
}
/// Retrieve the threshold calculation constant `c`.
pub fn c(&self) -> (u64, u64) {
self.0.c
}
}
impl SlotCompatible for BabeLink {
fn extract_timestamp_and_slot(
&self,
data: &InherentData,
) -> Result<(TimestampInherent, u64, std::time::Duration), consensus_common::Error> {
trace!(target: "babe", "extract timestamp");
data.timestamp_inherent_data()
.and_then(|t| data.babe_inherent_data().map(|a| (t, a)))
.map_err(Into::into)
.map_err(consensus_common::Error::InherentData)
.map(|(x, y)| (x, y, self.0.lock().0.take().unwrap_or_default()))
}
}
/// Parameters for BABE.
pub struct BabeParams {
/// The configuration for BABE. Includes the slot duration, threshold, and
/// other parameters.
pub config: Config,
/// 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,
/// A block importer
pub block_import: I,
/// The environment
pub env: E,
/// 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 time_source: BabeLink,
}
/// Start the babe worker. The returned future should be run in a tokio runtime.
pub fn start_babe(BabeParams {
config,
client,
keystore,
select_chain,
block_import,
env,
sync_oracle,
inherent_data_providers,
force_authoring,
time_source,
}: BabeParams) -> Result<
impl futures01::Future,
consensus_common::Error,
> where
B: BlockT,
C: ProvideRuntimeApi + ProvideCache + ProvideUncles + Send + Sync + 'static,
C::Api: BabeApi,
SC: SelectChain + 'static,
E::Proposer: Proposer,
>::Create: Unpin + Send + 'static,
H: Header,
E: Environment,
I: BlockImport + Send + Sync + 'static,
Error: std::error::Error + Send + From<::consensus_common::Error> + From + 'static,
SO: SyncOracle + Send + Sync + Clone,
{
let worker = BabeWorker {
client: client.clone(),
block_import: Arc::new(Mutex::new(block_import)),
env,
sync_oracle: sync_oracle.clone(),
force_authoring,
c: config.c(),
keystore,
};
register_babe_inherent_data_provider(&inherent_data_providers, config.0.slot_duration())?;
uncles::register_uncles_inherent_data_provider(
client.clone(),
select_chain.clone(),
&inherent_data_providers,
)?;
Ok(slots::start_slot_worker(
config.0,
select_chain,
worker,
sync_oracle,
inherent_data_providers,
time_source,
).map(|()| Ok::<(), ()>(())).compat())
}
struct BabeWorker {
client: Arc,
block_import: Arc>,
env: E,
sync_oracle: SO,
force_authoring: bool,
c: (u64, u64),
keystore: KeyStorePtr,
}
impl SlotWorker for BabeWorker where
B: BlockT,
C: ProvideRuntimeApi + ProvideCache,
C::Api: BabeApi,
E: Environment,
E::Proposer: Proposer,
>::Create: Unpin + Send + 'static,
Hash: Debug + Eq + Copy + SimpleBitOps + Encode + Decode + Serialize +
for<'de> Deserialize<'de> + Debug + Default + AsRef<[u8]> + AsMut<[u8]> +
std::hash::Hash + Display + Send + Sync + 'static,
H: Header,
I: BlockImport + Send + Sync + 'static,
SO: SyncOracle + Send + 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 {
let ref client = self.client;
let block_import = self.block_import.clone();
let (timestamp, slot_number, slot_duration) =
(slot_info.timestamp, slot_info.number, slot_info.duration);
let epoch = match epoch(client.as_ref(), &BlockId::Hash(chain_head.hash())) {
Ok(authorities) => authorities,
Err(e) => {
error!(
target: "babe",
"Unable to fetch authorities at block {:?}: {:?}",
chain_head.hash(),
e
);
telemetry!(CONSENSUS_WARN; "babe.unable_fetching_authorities";
"slot" => ?chain_head.hash(), "err" => ?e
);
return Box::pin(future::ready(Ok(())));
}
};
let Epoch { ref authorities, .. } = epoch;
if authorities.is_empty() {
error!(target: "babe", "No authorities at block {:?}", chain_head.hash());
}
if !self.force_authoring && self.sync_oracle.is_offline() && authorities.len() > 1 {
debug!(target: "babe", "Skipping proposal slot. Waiting for the network.");
telemetry!(CONSENSUS_DEBUG; "babe.skipping_proposal_slot";
"authorities_len" => authorities.len()
);
return Box::pin(future::ready(Ok(())));
}
let proposal_work = if let Some(claim) = claim_slot(
slot_info.number,
epoch,
self.c,
&self.keystore,
) {
let ((inout, vrf_proof, _batchable_proof), authority_index, key) = claim;
debug!(
target: "babe", "Starting authorship at slot {}; timestamp = {}",
slot_number,
timestamp,
);
telemetry!(CONSENSUS_DEBUG; "babe.starting_authorship";
"slot_number" => slot_number, "timestamp" => timestamp
);
// we are the slot author. make a block and sign it.
let mut proposer = match self.env.init(&chain_head) {
Ok(p) => p,
Err(e) => {
warn!(target: "babe",
"Unable to author block in slot {:?}: {:?}",
slot_number,
e,
);
telemetry!(CONSENSUS_WARN; "babe.unable_authoring_block";
"slot" => slot_number, "err" => ?e
);
return Box::pin(future::ready(Ok(())))
}
};
let inherent_digest = BabePreDigest {
vrf_proof,
vrf_output: inout.to_output(),
authority_index: authority_index as u32,
slot_number,
};
// deadline our production to approx. the end of the slot
let remaining_duration = slot_info.remaining_duration();
futures::future::select(
proposer.propose(
slot_info.inherent_data,
generic::Digest {
logs: vec![
generic::DigestItem::babe_pre_digest(inherent_digest.clone()),
],
},
remaining_duration,
).map_err(|e| consensus_common::Error::ClientImport(format!("{:?}", e)).into()),
Delay::new(remaining_duration)
.map_err(|err| consensus_common::Error::FaultyTimer(err).into())
).map(|v| match v {
futures::future::Either::Left((v, _)) => v.map(|v| (v, key)),
futures::future::Either::Right((Ok(_), _)) =>
Err(consensus_common::Error::ClientImport("Timeout in the BaBe proposer".into())),
futures::future::Either::Right((Err(err), _)) => Err(err),
})
} else {
return Box::pin(future::ready(Ok(())));
};
Box::pin(proposal_work.map_ok(move |(b, key)| {
// minor hack since we don't have access to the timestamp
// that is actually set by the proposer.
let slot_after_building = SignedDuration::default().slot_now(slot_duration);
if slot_after_building != slot_number {
info!(
target: "babe",
"Discarding proposal for slot {}; block production took too long",
slot_number
);
telemetry!(CONSENSUS_INFO; "babe.discarding_proposal_took_too_long";
"slot" => slot_number
);
return;
}
let (header, body) = b.deconstruct();
let header_num = header.number().clone();
let parent_hash = header.parent_hash().clone();
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let header_hash = header.hash();
let signature = key.sign(header_hash.as_ref());
let signature_digest_item = DigestItemFor::::babe_seal(signature);
let import_block = BlockImportParams:: {
origin: BlockOrigin::Own,
header,
justification: None,
post_digests: vec![signature_digest_item],
body: Some(body),
finalized: false,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
};
info!(target: "babe",
"Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
header_num,
import_block.post_header().hash(),
header_hash,
);
telemetry!(CONSENSUS_INFO; "babe.pre_sealed_block";
"header_num" => ?header_num,
"hash_now" => ?import_block.post_header().hash(),
"hash_previously" => ?header_hash,
);
if let Err(e) = block_import.lock().import_block(import_block, Default::default()) {
warn!(target: "babe", "Error with block built on {:?}: {:?}",
parent_hash, e);
telemetry!(CONSENSUS_WARN; "babe.err_with_block_built_on";
"hash" => ?parent_hash, "err" => ?e
);
}
}))
}
}
macro_rules! babe_err {
($($i: expr),+) => {
{ debug!(target: "babe", $($i),+)
; format!($($i),+)
}
};
}
/// 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
where DigestItemFor: CompatibleDigestItem,
{
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) => Err(babe_err!("Multiple BABE pre-runtime digests, rejecting!"))?,
(None, _) => trace!(target: "babe", "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or_else(|| babe_err!("No BABE pre-runtime digest found"))
}
/// Extract the BABE epoch change digest from the given header, if it exists.
fn find_next_epoch_digest(header: &B::Header) -> Result