Reorganising the repository - external renames and moves (#4074)

* Adding first rough ouline of the repository structure

* Remove old CI stuff

* add title

* formatting fixes

* move node-exits job's script to scripts dir

* Move docs into subdir

* move to bin

* move maintainence scripts, configs and helpers into its own dir

* add .local to ignore

* move core->client

* start up 'test' area

* move test client

* move test runtime

* make test move compile

* Add dependencies rule enforcement.

* Fix indexing.

* Update docs to reflect latest changes

* Moving /srml->/paint

* update docs

* move client/sr-* -> primitives/

* clean old readme

* remove old broken code in rhd

* update lock

* Step 1.

* starting to untangle client

* Fix after merge.

* start splitting out client interfaces

* move children and blockchain interfaces

* Move trie and state-machine to primitives.

* Fix WASM builds.

* fixing broken imports

* more interface moves

* move backend and light to interfaces

* move CallExecutor

* move cli off client

* moving around more interfaces

* re-add consensus crates into the mix

* fix subkey path

* relieve client from executor

* starting to pull out client from grandpa

* move is_decendent_of out of client

* grandpa still depends on client directly

* lemme tests pass

* rename srml->paint

* Make it compile.

* rename interfaces->client-api

* Move keyring to primitives.

* fixup libp2p dep

* fix broken use

* allow dependency enforcement to fail

* move fork-tree

* Moving wasm-builder

* make env

* move build-script-utils

* fixup broken crate depdencies and names

* fix imports for authority discovery

* fix typo

* update cargo.lock

* fixing imports

* Fix paths and add missing crates

* re-add missing crates
This commit is contained in:
Benjamin Kampmann
2019-11-14 21:51:17 +01:00
committed by Bastian Köcher
parent becc3b0a4f
commit 60e5011c72
809 changed files with 7801 additions and 6464 deletions
@@ -0,0 +1,41 @@
[package]
name = "substrate-consensus-aura"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Aura consensus algorithm for substrate"
edition = "2018"
[dependencies]
app-crypto = { package = "substrate-application-crypto", path = "../../../primitives/application-crypto" }
aura_primitives = { package = "substrate-consensus-aura-primitives", path = "../../../primitives/consensus/aura" }
block-builder-api = { package = "substrate-block-builder-runtime-api", path = "../../../primitives/block-builder/runtime-api" }
client = { package = "substrate-client", path = "../../" }
client-api = { package = "substrate-client-api", path = "../../api" }
codec = { package = "parity-scale-codec", version = "1.0.0" }
consensus_common = { package = "substrate-consensus-common", path = "../../../primitives/consensus/common" }
derive_more = "0.15.0"
futures-preview = { version = "0.3.0-alpha.19", features = ["compat"] }
futures-timer = "0.4.0"
futures01 = { package = "futures", version = "0.1" }
inherents = { package = "substrate-inherents", path = "../../../primitives/inherents" }
keystore = { package = "substrate-keystore", path = "../../keystore" }
log = "0.4.8"
parking_lot = "0.9.0"
primitives = { package = "substrate-primitives", path = "../../../primitives/core" }
runtime_io = { package = "sr-io", path = "../../../primitives/sr-io" }
runtime_version = { package = "sr-version", path = "../../../primitives/sr-version" }
slots = { package = "substrate-consensus-slots", path = "../slots" }
sr-api = { path = "../../../primitives/sr-api" }
sr-primitives = { path = "../../../primitives/sr-primitives" }
paint-aura = { path = "../../../paint/aura" }
substrate-telemetry = { path = "../../telemetry" }
[dev-dependencies]
keyring = { package = "substrate-keyring", path = "../../../primitives/keyring" }
substrate-executor = { path = "../../executor" }
network = { package = "substrate-network", path = "../../network", features = ["test-helpers"]}
service = { package = "substrate-service", path = "../../service" }
test-client = { package = "substrate-test-runtime-client", path = "../../../test/utils/runtime/client" }
tokio = "0.1.22"
env_logger = "0.7.0"
tempfile = "3.1.0"
@@ -0,0 +1,65 @@
// Copyright 2018-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 <http://www.gnu.org/licenses/>.
//! Aura (Authority-Round) digests
//!
//! This implements the digests for AuRa, to allow the private
//! `CompatibleDigestItem` trait to appear in public interfaces.
use primitives::Pair;
use aura_primitives::AURA_ENGINE_ID;
use sr_primitives::generic::{DigestItem, OpaqueDigestItemId};
use codec::{Encode, Codec};
use std::fmt::Debug;
type Signature<P> = <P as Pair>::Signature;
/// A digest item which is usable with aura consensus.
pub trait CompatibleDigestItem<P: Pair>: Sized {
/// Construct a digest item which contains a signature on the hash.
fn aura_seal(signature: Signature<P>) -> Self;
/// If this item is an Aura seal, return the signature.
fn as_aura_seal(&self) -> Option<Signature<P>>;
/// Construct a digest item which contains the slot number
fn aura_pre_digest(slot_num: u64) -> Self;
/// If this item is an AuRa pre-digest, return the slot number
fn as_aura_pre_digest(&self) -> Option<u64>;
}
impl<P, Hash> CompatibleDigestItem<P> for DigestItem<Hash> where
P: Pair,
Signature<P>: Codec,
Hash: Debug + Send + Sync + Eq + Clone + Codec + 'static
{
fn aura_seal(signature: Signature<P>) -> Self {
DigestItem::Seal(AURA_ENGINE_ID, signature.encode())
}
fn as_aura_seal(&self) -> Option<Signature<P>> {
self.try_to(OpaqueDigestItemId::Seal(&AURA_ENGINE_ID))
}
fn aura_pre_digest(slot_num: u64) -> Self {
DigestItem::PreRuntime(AURA_ENGINE_ID, slot_num.encode())
}
fn as_aura_pre_digest(&self) -> Option<u64> {
self.try_to(OpaqueDigestItemId::PreRuntime(&AURA_ENGINE_ID))
}
}
+891
View File
@@ -0,0 +1,891 @@
// Copyright 2018-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 <http://www.gnu.org/licenses/>.
//! Aura (Authority-round) consensus in substrate.
//!
//! Aura works by having a list of authorities A who are expected to roughly
//! agree on the current time. Time is divided up into discrete slots of t
//! seconds each. For each slot s, the author of that slot is A[s % |A|].
//!
//! The author is allowed to issue one block but not more during that slot,
//! and it will be built upon the longest valid chain that has been seen.
//!
//! Blocks from future steps will be either deferred or rejected depending on how
//! far in the future they are.
//!
//! NOTE: Aura itself is designed to be generic over the crypto used.
#![forbid(missing_docs, unsafe_code)]
use std::{sync::Arc, time::Duration, thread, marker::PhantomData, hash::Hash, fmt::Debug, pin::Pin};
use codec::{Encode, Decode, Codec};
use consensus_common::{self, BlockImport, Environment, Proposer,
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
SelectChain,
};
use consensus_common::import_queue::{
Verifier, BasicQueue, BoxBlockImport, BoxJustificationImport, BoxFinalityProofImport,
};
use client_api::{ error::Result as CResult, backend::AuxStore };
use client::{
blockchain::ProvideCache, BlockOf,
well_known_cache_keys::{self, Id as CacheKeyId},
};
use block_builder_api::BlockBuilder as BlockBuilderApi;
use sr_primitives::{generic::{BlockId, OpaqueDigestItemId}, Justification};
use sr_primitives::traits::{Block as BlockT, Header, DigestItemFor, ProvideRuntimeApi, Zero, Member};
use primitives::crypto::Pair;
use inherents::{InherentDataProviders, InherentData};
use futures::prelude::*;
use parking_lot::Mutex;
use log::{debug, info, trace};
use paint_aura::{
InherentType as AuraInherent, AuraInherentData,
timestamp::{TimestampInherentData, InherentType as TimestampInherent, InherentError as TIError}
};
use substrate_telemetry::{telemetry, CONSENSUS_TRACE, CONSENSUS_DEBUG, CONSENSUS_INFO};
use slots::{CheckedHeader, SlotData, SlotWorker, SlotInfo, SlotCompatible};
use slots::check_equivocation;
use keystore::KeyStorePtr;
use sr_api::ApiExt;
pub use aura_primitives::*;
pub use consensus_common::SyncOracle;
pub use digest::CompatibleDigestItem;
mod digest;
type AuthorityId<P> = <P as Pair>::Public;
/// A slot duration. Create with `get_or_compute`.
#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct SlotDuration(slots::SlotDuration<u64>);
impl SlotDuration {
/// Either fetch the slot duration from disk or compute it from the genesis
/// state.
pub fn get_or_compute<A, B, C>(client: &C) -> CResult<Self>
where
A: Codec,
B: BlockT,
C: AuxStore + ProvideRuntimeApi,
C::Api: AuraApi<B, A, Error = client::error::Error>,
{
slots::SlotDuration::get_or_compute(client, |a, b| a.slot_duration(b)).map(Self)
}
/// Get the slot duration in milliseconds.
pub fn get(&self) -> u64 {
self.0.get()
}
}
/// Get slot author for given block along with authorities.
fn slot_author<P: Pair>(slot_num: u64, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
if authorities.is_empty() { return None }
let idx = slot_num % (authorities.len() as u64);
assert!(
idx <= usize::max_value() as u64,
"It is impossible to have a vector with length beyond the address space; qed",
);
let current_author = authorities.get(idx as usize)
.expect("authorities not empty; index constrained to list length;\
this is a valid index; qed");
Some(current_author)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
struct AuraSlotCompatible;
impl SlotCompatible for AuraSlotCompatible {
fn extract_timestamp_and_slot(
&self,
data: &InherentData
) -> Result<(TimestampInherent, AuraInherent, std::time::Duration), consensus_common::Error> {
data.timestamp_inherent_data()
.and_then(|t| data.aura_inherent_data().map(|a| (t, a)))
.map_err(Into::into)
.map_err(consensus_common::Error::InherentData)
.map(|(x, y)| (x, y, Default::default()))
}
}
/// Start the aura worker. The returned future should be run in a futures executor.
pub fn start_aura<B, C, SC, E, I, P, SO, Error, H>(
slot_duration: SlotDuration,
client: Arc<C>,
select_chain: SC,
block_import: I,
env: E,
sync_oracle: SO,
inherent_data_providers: InherentDataProviders,
force_authoring: bool,
keystore: KeyStorePtr,
) -> Result<impl futures01::Future<Item = (), Error = ()>, consensus_common::Error> where
B: BlockT<Header=H>,
C: ProvideRuntimeApi + BlockOf + ProvideCache<B> + AuxStore + Send + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
SC: SelectChain<B>,
E: Environment<B, Error=Error> + Send + Sync + 'static,
E::Proposer: Proposer<B, Error=Error>,
<E::Proposer as Proposer<B>>::Create: Unpin + Send,
P: Pair + Send + Sync,
P::Public: Hash + Member + Encode + Decode,
P::Signature: Hash + Member + Encode + Decode,
H: Header<Hash=B::Hash>,
I: BlockImport<B> + Send + Sync + 'static,
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
SO: SyncOracle + Send + Sync + Clone,
{
let worker = AuraWorker {
client: client.clone(),
block_import: Arc::new(Mutex::new(block_import)),
env,
keystore,
sync_oracle: sync_oracle.clone(),
force_authoring,
_key_type: PhantomData::<P>,
};
register_aura_inherent_data_provider(
&inherent_data_providers,
slot_duration.0.slot_duration()
)?;
Ok(slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible>(
slot_duration.0,
select_chain,
worker,
sync_oracle,
inherent_data_providers,
AuraSlotCompatible,
).map(|()| Ok::<(), ()>(())).compat())
}
struct AuraWorker<C, E, I, P, SO> {
client: Arc<C>,
block_import: Arc<Mutex<I>>,
env: E,
keystore: KeyStorePtr,
sync_oracle: SO,
force_authoring: bool,
_key_type: PhantomData<P>,
}
impl<H, B, C, E, I, P, Error, SO> slots::SimpleSlotWorker<B> for AuraWorker<C, E, I, P, SO> where
B: BlockT<Header=H>,
C: ProvideRuntimeApi + BlockOf + ProvideCache<B> + Sync,
C::Api: AuraApi<B, AuthorityId<P>>,
E: Environment<B, Error=Error>,
E::Proposer: Proposer<B, Error=Error>,
<E::Proposer as Proposer<B>>::Create: Unpin + Send,
H: Header<Hash=B::Hash>,
I: BlockImport<B> + Send + Sync + 'static,
P: Pair + Send + Sync,
P::Public: Member + Encode + Decode + Hash,
P::Signature: Member + Encode + Decode + Hash + Debug,
SO: SyncOracle + Send + Clone,
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
{
type EpochData = Vec<AuthorityId<P>>;
type Claim = P;
type SyncOracle = SO;
type Proposer = E::Proposer;
type BlockImport = I;
fn logging_target(&self) -> &'static str {
"aura"
}
fn block_import(&self) -> Arc<Mutex<Self::BlockImport>> {
self.block_import.clone()
}
fn epoch_data(&self, header: &B::Header, _slot_number: u64) -> Result<Self::EpochData, consensus_common::Error> {
authorities(self.client.as_ref(), &BlockId::Hash(header.hash()))
}
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize {
epoch_data.len()
}
fn claim_slot(
&self,
_header: &B::Header,
slot_number: u64,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim> {
let expected_author = slot_author::<P>(slot_number, epoch_data);
expected_author.and_then(|p| {
self.keystore.read()
.key_pair_by_type::<P>(&p, app_crypto::key_types::AURA).ok()
})
}
fn pre_digest_data(&self, slot_number: u64, _claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>> {
vec![
<DigestItemFor<B> as CompatibleDigestItem<P>>::aura_pre_digest(slot_number),
]
}
fn block_import_params(&self) -> Box<dyn Fn(
B::Header,
&B::Hash,
Vec<B::Extrinsic>,
Self::Claim,
) -> consensus_common::BlockImportParams<B> + 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 = <DigestItemFor<B> as CompatibleDigestItem<P>>::aura_seal(signature);
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,
allow_missing_state: 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::Proposer, consensus_common::Error> {
self.env.init(block).map_err(|e| {
consensus_common::Error::ClientImport(format!("{:?}", e)).into()
})
}
}
impl<H, B: BlockT, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, SO> where
B: BlockT<Header=H>,
C: ProvideRuntimeApi + BlockOf + ProvideCache<B> + Sync + Send,
C::Api: AuraApi<B, AuthorityId<P>>,
E: Environment<B, Error=Error> + Send + Sync,
E::Proposer: Proposer<B, Error=Error>,
<E::Proposer as Proposer<B>>::Create: Unpin + Send + 'static,
H: Header<Hash=B::Hash>,
I: BlockImport<B> + Send + Sync + 'static,
P: Pair + Send + Sync,
P::Public: Member + Encode + Decode + Hash,
P::Signature: Member + Encode + Decode + Hash + Debug,
SO: SyncOracle + Send + Sync + Clone,
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
{
type OnSlot = Pin<Box<dyn Future<Output = Result<(), consensus_common::Error>> + Send>>;
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo) -> Self::OnSlot {
<Self as slots::SimpleSlotWorker<B>>::on_slot(self, chain_head, slot_info)
}
}
fn aura_err<B: BlockT>(error: Error<B>) -> Error<B> {
debug!(target: "aura", "{}", error);
error
}
#[derive(derive_more::Display)]
enum Error<B: BlockT> {
#[display(fmt = "Multiple Aura pre-runtime headers")]
MultipleHeaders,
#[display(fmt = "No Aura pre-runtime digest found")]
NoDigestFound,
#[display(fmt = "Header {:?} is unsealed", _0)]
HeaderUnsealed(B::Hash),
#[display(fmt = "Header {:?} has a bad seal", _0)]
HeaderBadSeal(B::Hash),
#[display(fmt = "Slot Author not found")]
SlotAuthorNotFound,
#[display(fmt = "Bad signature on {:?}", _0)]
BadSignature(B::Hash),
#[display(fmt = "Rejecting block too far in future")]
TooFarInFuture,
Client(client::error::Error),
DataProvider(String),
Runtime(String),
}
fn find_pre_digest<B: BlockT, P: Pair>(header: &B::Header) -> Result<u64, Error<B>>
where DigestItemFor<B>: CompatibleDigestItem<P>,
P::Signature: Decode,
P::Public: Encode + Decode + PartialEq + Clone,
{
let mut pre_digest: Option<u64> = None;
for log in header.digest().logs() {
trace!(target: "aura", "Checking log {:?}", log);
match (log.as_aura_pre_digest(), pre_digest.is_some()) {
(Some(_), true) => Err(aura_err(Error::MultipleHeaders))?,
(None, _) => trace!(target: "aura", "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound))
}
/// check a header has been signed by the right key. If the slot is too far in the future, an error will be returned.
/// if it's successful, returns the pre-header and the digest item containing the seal.
///
/// This digest item will always return `Some` when used with `as_aura_seal`.
//
// FIXME #1018 needs misbehavior types. The `transaction_pool` parameter will be
// used to submit such misbehavior reports.
fn check_header<C, B: BlockT, P: Pair, T>(
client: &C,
slot_now: u64,
mut header: B::Header,
hash: B::Hash,
authorities: &[AuthorityId<P>],
_transaction_pool: Option<&T>,
) -> Result<CheckedHeader<B::Header, (u64, DigestItemFor<B>)>, Error<B>> where
DigestItemFor<B>: CompatibleDigestItem<P>,
P::Signature: Decode,
C: client_api::backend::AuxStore,
P::Public: Encode + Decode + PartialEq + Clone,
T: Send + Sync + 'static,
{
let seal = match header.digest_mut().pop() {
Some(x) => x,
None => return Err(Error::HeaderUnsealed(hash)),
};
let sig = seal.as_aura_seal().ok_or_else(|| {
aura_err(Error::HeaderBadSeal(hash))
})?;
let slot_num = find_pre_digest::<B, _>(&header)?;
if slot_num > slot_now {
header.digest_mut().push(seal);
Ok(CheckedHeader::Deferred(header, slot_num))
} else {
// check the signature is valid under the expected authority and
// chain state.
let expected_author = match slot_author::<P>(slot_num, &authorities) {
None => return Err(Error::SlotAuthorNotFound),
Some(author) => author,
};
let pre_hash = header.hash();
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
if let Some(equivocation_proof) = check_equivocation(
client,
slot_now,
slot_num,
&header,
expected_author,
).map_err(Error::Client)? {
info!(
"Slot author is equivocating at slot {} with headers {:?} and {:?}",
slot_num,
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
}
Ok(CheckedHeader::Checked(header, (slot_num, seal)))
} else {
Err(Error::BadSignature(hash))
}
}
}
/// A verifier for Aura blocks.
pub struct AuraVerifier<C, P, T> {
client: Arc<C>,
phantom: PhantomData<P>,
inherent_data_providers: inherents::InherentDataProviders,
transaction_pool: Option<Arc<T>>,
}
impl<C, P, T> AuraVerifier<C, P, T>
where P: Send + Sync + 'static
{
fn check_inherents<B: BlockT>(
&self,
block: B,
block_id: BlockId<B>,
inherent_data: InherentData,
timestamp_now: u64,
) -> Result<(), Error<B>>
where C: ProvideRuntimeApi, C::Api: BlockBuilderApi<B, Error = client::error::Error>
{
const MAX_TIMESTAMP_DRIFT_SECS: u64 = 60;
let inherent_res = self.client.runtime_api().check_inherents(
&block_id,
block,
inherent_data,
).map_err(Error::Client)?;
if !inherent_res.ok() {
inherent_res
.into_errors()
.try_for_each(|(i, e)| match TIError::try_from(&i, &e) {
Some(TIError::ValidAtTimestamp(timestamp)) => {
// halt import until timestamp is valid.
// reject when too far ahead.
if timestamp > timestamp_now + MAX_TIMESTAMP_DRIFT_SECS {
return Err(Error::TooFarInFuture);
}
let diff = timestamp.saturating_sub(timestamp_now);
info!(
target: "aura",
"halting for block {} seconds in the future",
diff
);
telemetry!(CONSENSUS_INFO; "aura.halting_for_future_block";
"diff" => ?diff
);
thread::sleep(Duration::from_secs(diff));
Ok(())
},
Some(TIError::Other(e)) => Err(Error::Runtime(e.into())),
None => Err(Error::DataProvider(
self.inherent_data_providers.error_to_string(&i, &e)
)),
})
} else {
Ok(())
}
}
}
#[forbid(deprecated)]
impl<B: BlockT, C, P, T> Verifier<B> for AuraVerifier<C, P, T> where
C: ProvideRuntimeApi + Send + Sync + client_api::backend::AuxStore + ProvideCache<B> + BlockOf,
C::Api: BlockBuilderApi<B> + AuraApi<B, AuthorityId<P>> + ApiExt<B, Error = client::error::Error>,
DigestItemFor<B>: CompatibleDigestItem<P>,
P: Pair + Send + Sync + 'static,
P::Public: Send + Sync + Hash + Eq + Clone + Decode + Encode + Debug + 'static,
P::Signature: Encode + Decode,
T: Send + Sync + 'static,
{
fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
mut body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let mut inherent_data = self.inherent_data_providers
.create_inherent_data()
.map_err(|e| e.into_string())?;
let (timestamp_now, slot_now, _) = AuraSlotCompatible.extract_timestamp_and_slot(&inherent_data)
.map_err(|e| format!("Could not extract timestamp and slot: {:?}", e))?;
let hash = header.hash();
let parent_hash = *header.parent_hash();
let authorities = authorities(self.client.as_ref(), &BlockId::Hash(parent_hash))
.map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?;
// we add one to allow for some small drift.
// FIXME #1019 in the future, alter this queue to allow deferring of
// headers
let checked_header = check_header::<C, B, P, T>(
&self.client,
slot_now + 1,
header,
hash,
&authorities[..],
self.transaction_pool.as_ref().map(|x| &**x),
).map_err(|e| e.to_string())?;
match checked_header {
CheckedHeader::Checked(pre_header, (slot_num, seal)) => {
// if the body is passed through, we need to use the runtime
// to check that the internally-set timestamp in the inherents
// actually matches the slot set in the seal.
if let Some(inner_body) = body.take() {
inherent_data.aura_replace_inherent_data(slot_num);
let block = B::new(pre_header.clone(), inner_body);
// skip the inherents verification if the runtime API is old.
if self.client
.runtime_api()
.has_api_with::<dyn BlockBuilderApi<B, Error = ()>, _>(
&BlockId::Hash(parent_hash),
|v| v >= 2,
)
.map_err(|e| format!("{:?}", e))?
{
self.check_inherents(
block.clone(),
BlockId::Hash(parent_hash),
inherent_data,
timestamp_now,
).map_err(|e| e.to_string())?;
}
let (_, inner_body) = block.deconstruct();
body = Some(inner_body);
}
trace!(target: "aura", "Checked {:?}; importing.", pre_header);
telemetry!(CONSENSUS_TRACE; "aura.checked_and_importing"; "pre_header" => ?pre_header);
// Look for an authorities-change log.
let maybe_keys = pre_header.digest()
.logs()
.iter()
.filter_map(|l| l.try_to::<ConsensusLog<AuthorityId<P>>>(
OpaqueDigestItemId::Consensus(&AURA_ENGINE_ID)
))
.find_map(|l| match l {
ConsensusLog::AuthoritiesChange(a) => Some(
vec![(well_known_cache_keys::AUTHORITIES, a.encode())]
),
_ => None,
});
let block_import_params = BlockImportParams {
origin,
header: pre_header,
post_digests: vec![seal],
body,
finalized: false,
justification,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
allow_missing_state: false,
};
Ok((block_import_params, maybe_keys))
}
CheckedHeader::Deferred(a, b) => {
debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b);
telemetry!(CONSENSUS_DEBUG; "aura.header_too_far_in_future";
"hash" => ?hash, "a" => ?a, "b" => ?b
);
Err(format!("Header {:?} rejected: too far in the future", hash))
}
}
}
}
fn initialize_authorities_cache<A, B, C>(client: &C) -> Result<(), ConsensusError> where
A: Codec,
B: BlockT,
C: ProvideRuntimeApi + BlockOf + ProvideCache<B>,
C::Api: AuraApi<B, A>,
{
// no cache => no initialization
let cache = match client.cache() {
Some(cache) => cache,
None => return Ok(()),
};
// check if we already have initialized the cache
let genesis_id = BlockId::Number(Zero::zero());
let genesis_authorities: Option<Vec<A>> = cache
.get_at(&well_known_cache_keys::AUTHORITIES, &genesis_id)
.and_then(|(_, _, v)| Decode::decode(&mut &v[..]).ok());
if genesis_authorities.is_some() {
return Ok(());
}
let map_err = |error| consensus_common::Error::from(consensus_common::Error::ClientImport(
format!(
"Error initializing authorities cache: {}",
error,
)));
let genesis_authorities = authorities(client, &genesis_id)?;
cache.initialize(&well_known_cache_keys::AUTHORITIES, genesis_authorities.encode())
.map_err(map_err)?;
Ok(())
}
#[allow(deprecated)]
fn authorities<A, B, C>(client: &C, at: &BlockId<B>) -> Result<Vec<A>, ConsensusError> where
A: Codec,
B: BlockT,
C: ProvideRuntimeApi + BlockOf + ProvideCache<B>,
C::Api: AuraApi<B, A>,
{
client
.cache()
.and_then(|cache| cache
.get_at(&well_known_cache_keys::AUTHORITIES, at)
.and_then(|(_, _, v)| Decode::decode(&mut &v[..]).ok())
)
.or_else(|| AuraApi::authorities(&*client.runtime_api(), at).ok())
.ok_or_else(|| consensus_common::Error::InvalidAuthoritiesSet.into())
}
/// The Aura import queue type.
pub type AuraImportQueue<B> = BasicQueue<B>;
/// Register the aura inherent data provider, if not registered already.
fn register_aura_inherent_data_provider(
inherent_data_providers: &InherentDataProviders,
slot_duration: u64,
) -> Result<(), consensus_common::Error> {
if !inherent_data_providers.has_provider(&paint_aura::INHERENT_IDENTIFIER) {
inherent_data_providers
.register_provider(paint_aura::InherentDataProvider::new(slot_duration))
.map_err(Into::into)
.map_err(consensus_common::Error::InherentData)
} else {
Ok(())
}
}
/// Start an import queue for the Aura consensus algorithm.
pub fn import_queue<B, C, P, T>(
slot_duration: SlotDuration,
block_import: BoxBlockImport<B>,
justification_import: Option<BoxJustificationImport<B>>,
finality_proof_import: Option<BoxFinalityProofImport<B>>,
client: Arc<C>,
inherent_data_providers: InherentDataProviders,
transaction_pool: Option<Arc<T>>,
) -> Result<AuraImportQueue<B>, consensus_common::Error> where
B: BlockT,
C: 'static + ProvideRuntimeApi + BlockOf + ProvideCache<B> + Send + Sync + AuxStore,
C::Api: BlockBuilderApi<B> + AuraApi<B, AuthorityId<P>> + ApiExt<B, Error = client::error::Error>,
DigestItemFor<B>: CompatibleDigestItem<P>,
P: Pair + Send + Sync + 'static,
P::Public: Clone + Eq + Send + Sync + Hash + Debug + Encode + Decode,
P::Signature: Encode + Decode,
T: Send + Sync + 'static,
{
register_aura_inherent_data_provider(&inherent_data_providers, slot_duration.get())?;
initialize_authorities_cache(&*client)?;
let verifier = AuraVerifier {
client: client.clone(),
inherent_data_providers,
phantom: PhantomData,
transaction_pool,
};
Ok(BasicQueue::new(
verifier,
block_import,
justification_import,
finality_proof_import,
))
}
#[cfg(test)]
mod tests {
use super::*;
use consensus_common::NoNetwork as DummyOracle;
use network::test::*;
use network::test::{Block as TestBlock, PeersClient, PeersFullClient};
use sr_primitives::traits::{Block as BlockT, DigestFor};
use network::config::ProtocolConfig;
use parking_lot::Mutex;
use tokio::runtime::current_thread;
use keyring::sr25519::Keyring;
use client::BlockchainEvents;
use test_client;
use aura_primitives::sr25519::AuthorityPair;
type Error = client::error::Error;
type TestClient = client::Client<
test_client::Backend,
test_client::Executor,
TestBlock,
test_client::runtime::RuntimeApi
>;
struct DummyFactory(Arc<TestClient>);
struct DummyProposer(u64, Arc<TestClient>);
impl Environment<TestBlock> for DummyFactory {
type Proposer = DummyProposer;
type Error = Error;
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
-> Result<DummyProposer, Error>
{
Ok(DummyProposer(parent_header.number + 1, self.0.clone()))
}
}
impl Proposer<TestBlock> for DummyProposer {
type Error = Error;
type Create = future::Ready<Result<TestBlock, Error>>;
fn propose(
&mut self,
_: InherentData,
digests: DigestFor<TestBlock>,
_: Duration,
) -> Self::Create {
let r = self.1.new_block(digests).unwrap().bake().map_err(|e| e.into());
future::ready(r)
}
}
const SLOT_DURATION: u64 = 1000;
pub struct AuraTestNet {
peers: Vec<Peer<(), DummySpecialization>>,
}
impl TestNetFactory for AuraTestNet {
type Specialization = DummySpecialization;
type Verifier = AuraVerifier<PeersFullClient, AuthorityPair, ()>;
type PeerData = ();
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
AuraTestNet {
peers: Vec::new(),
}
}
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig, _peer_data: &())
-> Self::Verifier
{
match client {
PeersClient::Full(client, _) => {
let slot_duration = SlotDuration::get_or_compute(&*client)
.expect("slot duration available");
let inherent_data_providers = InherentDataProviders::new();
register_aura_inherent_data_provider(
&inherent_data_providers,
slot_duration.get()
).expect("Registers aura inherent data provider");
assert_eq!(slot_duration.get(), SLOT_DURATION);
AuraVerifier {
client,
inherent_data_providers,
transaction_pool: Default::default(),
phantom: Default::default(),
}
},
PeersClient::Light(_, _) => unreachable!("No (yet) tests for light client + Aura"),
}
}
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, DummySpecialization> {
&mut self.peers[i]
}
fn peers(&self) -> &Vec<Peer<Self::PeerData, DummySpecialization>> {
&self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData, DummySpecialization>>)>(&mut self, closure: F) {
closure(&mut self.peers);
}
}
#[test]
#[allow(deprecated)]
fn authoring_blocks() {
let _ = env_logger::try_init();
let net = AuraTestNet::new(3);
let peers = &[
(0, Keyring::Alice),
(1, Keyring::Bob),
(2, Keyring::Charlie),
];
let net = Arc::new(Mutex::new(net));
let mut import_notifications = Vec::new();
let mut runtime = current_thread::Runtime::new().unwrap();
let mut keystore_paths = Vec::new();
for (peer_id, key) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
let client = peer.client().as_full().expect("full clients are created").clone();
let select_chain = peer.select_chain().expect("full client has a select chain");
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore.");
keystore.write().insert_ephemeral_from_seed::<AuthorityPair>(&key.to_seed())
.expect("Creates authority key");
keystore_paths.push(keystore_path);
let environ = DummyFactory(client.clone());
import_notifications.push(
client.import_notification_stream()
.take_while(|n| future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5)))
.for_each(move |_| future::ready(()))
);
let slot_duration = SlotDuration::get_or_compute(&*client)
.expect("slot duration available");
let inherent_data_providers = InherentDataProviders::new();
register_aura_inherent_data_provider(
&inherent_data_providers, slot_duration.get()
).expect("Registers aura inherent data provider");
let aura = start_aura::<_, _, _, _, _, AuthorityPair, _, _, _>(
slot_duration,
client.clone(),
select_chain,
client,
environ,
DummyOracle,
inherent_data_providers,
false,
keystore,
).expect("Starts aura");
runtime.spawn(aura);
}
runtime.spawn(futures01::future::poll_fn(move || {
net.lock().poll();
Ok::<_, ()>(futures01::Async::NotReady::<()>)
}));
runtime.block_on(future::join_all(import_notifications)
.map(|_| Ok::<(), ()>(())).compat()).unwrap();
}
#[test]
fn authorities_call_works() {
let client = test_client::new();
assert_eq!(client.info().chain.best_number, 0);
assert_eq!(authorities(&client, &BlockId::Number(0)).unwrap(), vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into()
]);
}
}
@@ -0,0 +1,55 @@
[package]
name = "substrate-consensus-babe"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "BABE consensus algorithm for substrate"
edition = "2018"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.0.0", features = ["derive"] }
babe_primitives = { package = "substrate-consensus-babe-primitives", path = "../../../primitives/consensus/babe" }
primitives = { package = "substrate-primitives", path = "../../../primitives/core" }
app-crypto = { package = "substrate-application-crypto", path = "../../../primitives/application-crypto" }
num-bigint = "0.2.3"
num-rational = "0.2.2"
num-traits = "0.2.8"
runtime-version = { package = "sr-version", path = "../../../primitives/sr-version" }
runtime-io = { package = "sr-io", path = "../../../primitives/sr-io" }
inherents = { package = "substrate-inherents", path = "../../../primitives/inherents" }
substrate-telemetry = { path = "../../telemetry" }
keystore = { package = "substrate-keystore", path = "../../keystore" }
paint-babe = { path = "../../../paint/babe" }
client-api = { package = "substrate-client-api", path = "../../api" }
client = { package = "substrate-client", path = "../../" }
sr-api = { path = "../../../primitives/sr-api" }
block-builder-api = { package = "substrate-block-builder-runtime-api", path = "../../../primitives/block-builder/runtime-api" }
header-metadata = { package = "substrate-header-metadata", path = "../../header-metadata" }
consensus-common = { package = "substrate-consensus-common", path = "../../../primitives/consensus/common" }
uncles = { package = "substrate-consensus-uncles", path = "../uncles" }
slots = { package = "substrate-consensus-slots", path = "../slots" }
sr-primitives = { path = "../../../primitives/sr-primitives" }
fork-tree = { path = "../../../utils/fork-tree" }
futures-preview = { version = "0.3.0-alpha.19", features = ["compat"] }
futures01 = { package = "futures", version = "0.1" }
futures-timer = "0.4.0"
parking_lot = "0.9.0"
log = "0.4.8"
schnorrkel = { version = "0.8.5", features = ["preaudit_deprecated"] }
rand = "0.7.2"
merlin = "1.2.1"
pdqselect = "0.1.0"
derive_more = "0.15.0"
[dev-dependencies]
keyring = { package = "substrate-keyring", path = "../../../primitives/keyring" }
substrate-executor = { path = "../../executor" }
network = { package = "substrate-network", path = "../../network", features = ["test-helpers"]}
service = { package = "substrate-service", path = "../../service" }
test-client = { package = "substrate-test-runtime-client", path = "../../../test/utils/runtime/client" }
block-builder = { package = "substrate-block-builder", path = "../../block-builder" }
tokio = "0.1.22"
env_logger = "0.7.0"
tempfile = "3.1.0"
[features]
test-helpers = []
@@ -0,0 +1,214 @@
// 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 <http://www.gnu.org/licenses/>.
//! BABE authority selection and slot claiming.
use merlin::Transcript;
use babe_primitives::{AuthorityId, BabeAuthorityWeight, BABE_ENGINE_ID, BABE_VRF_PREFIX};
use babe_primitives::{Epoch, SlotNumber, AuthorityPair, BabePreDigest, BabeConfiguration};
use primitives::{U256, blake2_256};
use codec::Encode;
use schnorrkel::vrf::VRFInOut;
use primitives::Pair;
use keystore::KeyStorePtr;
/// Calculates the primary selection threshold for a given authority, taking
/// into account `c` (`1 - c` represents the probability of a slot being empty).
pub(super) fn calculate_primary_threshold(
c: (u64, u64),
authorities: &[(AuthorityId, BabeAuthorityWeight)],
authority_index: usize,
) -> u128 {
use num_bigint::BigUint;
use num_rational::BigRational;
use num_traits::{cast::ToPrimitive, identities::One};
let c = c.0 as f64 / c.1 as f64;
let theta =
authorities[authority_index].1 as f64 /
authorities.iter().map(|(_, weight)| weight).sum::<u64>() as f64;
let calc = || {
let p = BigRational::from_float(1f64 - (1f64 - c).powf(theta))?;
let numer = p.numer().to_biguint()?;
let denom = p.denom().to_biguint()?;
((BigUint::one() << 128) * numer / denom).to_u128()
};
calc().unwrap_or(u128::max_value())
}
/// Returns true if the given VRF output is lower than the given threshold,
/// false otherwise.
pub(super) fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool {
u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold
}
/// Get the expected secondary author for the given slot and with given
/// authorities. This should always assign the slot to some authority unless the
/// authorities list is empty.
pub(super) fn secondary_slot_author(
slot_number: u64,
authorities: &[(AuthorityId, BabeAuthorityWeight)],
randomness: [u8; 32],
) -> Option<&AuthorityId> {
if authorities.is_empty() {
return None;
}
let rand = U256::from((randomness, slot_number).using_encoded(blake2_256));
let authorities_len = U256::from(authorities.len());
let idx = rand % authorities_len;
let expected_author = authorities.get(idx.as_u32() as usize)
.expect("authorities not empty; index constrained to list length; \
this is a valid index; qed");
Some(&expected_author.0)
}
#[allow(deprecated)]
pub(super) fn make_transcript(
randomness: &[u8],
slot_number: u64,
epoch: u64,
) -> Transcript {
let mut transcript = Transcript::new(&BABE_ENGINE_ID);
transcript.commit_bytes(b"slot number", &slot_number.to_le_bytes());
transcript.commit_bytes(b"current epoch", &epoch.to_le_bytes());
transcript.commit_bytes(b"chain randomness", randomness);
transcript
}
/// Claim a secondary slot if it is our turn to propose, returning the
/// pre-digest to use when authoring the block, or `None` if it is not our turn
/// to propose.
fn claim_secondary_slot(
slot_number: SlotNumber,
authorities: &[(AuthorityId, BabeAuthorityWeight)],
keystore: &KeyStorePtr,
randomness: [u8; 32],
) -> Option<(BabePreDigest, AuthorityPair)> {
if authorities.is_empty() {
return None;
}
let expected_author = super::authorship::secondary_slot_author(
slot_number,
authorities,
randomness,
)?;
let keystore = keystore.read();
for (pair, authority_index) in authorities.iter()
.enumerate()
.flat_map(|(i, a)| {
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
})
{
if pair.public() == *expected_author {
let pre_digest = BabePreDigest::Secondary {
slot_number,
authority_index: authority_index as u32,
};
return Some((pre_digest, pair));
}
}
None
}
/// Tries to claim the given slot number. This method starts by trying to claim
/// a primary VRF based slot. If we are not able to claim it, then if we have
/// secondary slots enabled for the given epoch, we will fallback to trying to
/// claim a secondary slot.
pub(super) fn claim_slot(
slot_number: SlotNumber,
epoch: &Epoch,
config: &BabeConfiguration,
keystore: &KeyStorePtr,
) -> Option<(BabePreDigest, AuthorityPair)> {
claim_primary_slot(slot_number, epoch, config.c, keystore)
.or_else(|| {
if config.secondary_slots {
claim_secondary_slot(
slot_number,
&epoch.authorities,
keystore,
epoch.randomness,
)
} else {
None
}
})
}
fn get_keypair(q: &AuthorityPair) -> &schnorrkel::Keypair {
use primitives::crypto::IsWrappedBy;
primitives::sr25519::Pair::from_ref(q).as_ref()
}
/// Claim a primary slot if it is our turn. Returns `None` if it is not our turn.
/// This hashes the slot number, epoch, genesis hash, and chain randomness into
/// the VRF. If the VRF produces a value less than `threshold`, it is our turn,
/// so it returns `Some(_)`. Otherwise, it returns `None`.
fn claim_primary_slot(
slot_number: SlotNumber,
epoch: &Epoch,
c: (u64, u64),
keystore: &KeyStorePtr,
) -> Option<(BabePreDigest, AuthorityPair)> {
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
let keystore = keystore.read();
for (pair, authority_index) in authorities.iter()
.enumerate()
.flat_map(|(i, a)| {
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
})
{
let transcript = super::authorship::make_transcript(randomness, slot_number, *epoch_index);
// Compute the threshold we will use.
//
// We already checked that authorities contains `key.public()`, so it can't
// be empty. Therefore, this division in `calculate_threshold` is safe.
let threshold = super::authorship::calculate_primary_threshold(c, authorities, authority_index);
let pre_digest = get_keypair(&pair)
.vrf_sign_after_check(transcript, |inout| super::authorship::check_primary_threshold(inout, threshold))
.map(|s| {
BabePreDigest::Primary {
slot_number,
vrf_output: s.0.to_output(),
vrf_proof: s.1,
authority_index: authority_index as u32,
}
});
// early exit on first successful claim
if let Some(pre_digest) = pre_digest {
return Some((pre_digest, pair));
}
}
None
}
@@ -0,0 +1,103 @@
// 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 <http://www.gnu.org/licenses/>.
//! Schema for BABE epoch changes in the aux-db.
use log::info;
use codec::{Decode, Encode};
use client_api::{
backend::AuxStore,
error::{Result as ClientResult, Error as ClientError},
};
use sr_primitives::traits::Block as BlockT;
use babe_primitives::BabeBlockWeight;
use super::{epoch_changes::EpochChangesFor, SharedEpochChanges};
const BABE_EPOCH_CHANGES: &[u8] = b"babe_epoch_changes";
fn block_weight_key<H: Encode>(block_hash: H) -> Vec<u8> {
(b"block_weight", block_hash).encode()
}
fn load_decode<B, T>(backend: &B, key: &[u8]) -> ClientResult<Option<T>>
where
B: AuxStore,
T: Decode,
{
let corrupt = |e: codec::Error| {
ClientError::Backend(format!("BABE DB is corrupted. Decode error: {}", e.what()))
};
match backend.get_aux(key)? {
None => Ok(None),
Some(t) => T::decode(&mut &t[..]).map(Some).map_err(corrupt)
}
}
/// Load or initialize persistent epoch change data from backend.
pub(crate) fn load_epoch_changes<Block: BlockT, B: AuxStore>(
backend: &B,
) -> ClientResult<SharedEpochChanges<Block>> {
let epoch_changes = load_decode::<_, EpochChangesFor<Block>>(backend, BABE_EPOCH_CHANGES)?
.map(Into::into)
.unwrap_or_else(|| {
info!(target: "babe",
"Creating empty BABE epoch changes on what appears to be first startup."
);
SharedEpochChanges::new()
});
Ok(epoch_changes)
}
/// Update the epoch changes on disk after a change.
pub(crate) fn write_epoch_changes<Block: BlockT, F, R>(
epoch_changes: &EpochChangesFor<Block>,
write_aux: F,
) -> R where
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
{
let encoded_epoch_changes = epoch_changes.encode();
write_aux(
&[(BABE_EPOCH_CHANGES, encoded_epoch_changes.as_slice())],
)
}
/// Write the cumulative chain-weight of a block ot aux storage.
pub(crate) fn write_block_weight<H: Encode, F, R>(
block_hash: H,
block_weight: &BabeBlockWeight,
write_aux: F,
) -> R where
F: FnOnce(&[(Vec<u8>, &[u8])]) -> R,
{
let key = block_weight_key(block_hash);
block_weight.using_encoded(|s|
write_aux(
&[(key, s)],
)
)
}
/// Load the cumulative chain-weight associated with a block.
pub(crate) fn load_block_weight<H: Encode, B: AuxStore>(
backend: &B,
block_hash: H,
) -> ClientResult<Option<BabeBlockWeight>> {
load_decode(backend, block_weight_key(block_hash).as_slice())
}
@@ -0,0 +1,658 @@
// 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 <http://www.gnu.org/licenses/>.
//! Handling epoch changes in BABE.
//!
//! This exposes the `SharedEpochChanges`, which is a wrapper around a
//! persistent DAG superimposed over the forks of the blockchain.
use std::sync::Arc;
use babe_primitives::{Epoch, SlotNumber, NextEpochDescriptor};
use fork_tree::ForkTree;
use parking_lot::{Mutex, MutexGuard};
use sr_primitives::traits::{Block as BlockT, NumberFor, One, Zero};
use codec::{Encode, Decode};
use client_api::{
error::Error as ClientError,
utils::is_descendent_of,
blockchain::HeaderBackend
};
use header_metadata::HeaderMetadata;
use primitives::H256;
use std::ops::Add;
/// A builder for `is_descendent_of` functions.
pub trait IsDescendentOfBuilder<Hash> {
/// The error returned by the function.
type Error: std::error::Error;
/// A function that can tell you if the second parameter is a descendent of
/// the first.
type IsDescendentOf: Fn(&Hash, &Hash) -> Result<bool, Self::Error>;
/// Build an `is_descendent_of` function.
///
/// The `current` parameter can be `Some` with the details a fresh block whose
/// details aren't yet stored, but its parent is.
///
/// The format of `current` when `Some` is `(current, current_parent)`.
fn build_is_descendent_of(&self, current: Option<(Hash, Hash)>)
-> Self::IsDescendentOf;
}
/// Produce a descendent query object given the client.
pub(crate) fn descendent_query<H, Block>(client: &H) -> HeaderBackendDescendentBuilder<&H, Block> {
HeaderBackendDescendentBuilder(client, std::marker::PhantomData)
}
/// Wrapper to get around unconstrained type errors when implementing
/// `IsDescendentOfBuilder` for header backends.
pub(crate) struct HeaderBackendDescendentBuilder<H, Block>(H, std::marker::PhantomData<Block>);
// TODO: relying on Hash = H256 is awful.
// https://github.com/paritytech/substrate/issues/3624
impl<'a, H, Block> IsDescendentOfBuilder<H256>
for HeaderBackendDescendentBuilder<&'a H, Block> where
H: HeaderBackend<Block> + HeaderMetadata<Block, Error=ClientError>,
Block: BlockT<Hash = H256>,
{
type Error = ClientError;
type IsDescendentOf = Box<dyn Fn(&H256, &H256) -> Result<bool, ClientError> + 'a>;
fn build_is_descendent_of(&self, current: Option<(H256, H256)>)
-> Self::IsDescendentOf
{
Box::new(is_descendent_of(self.0, current))
}
}
/// An unimported genesis epoch.
pub struct UnimportedGenesis(Epoch);
/// The viable epoch under which a block can be verified.
///
/// If this is the first non-genesis block in the chain, then it will
/// hold an `UnimportedGenesis` epoch.
pub enum ViableEpoch {
Genesis(UnimportedGenesis),
Regular(Epoch),
}
impl From<Epoch> for ViableEpoch {
fn from(epoch: Epoch) -> ViableEpoch {
ViableEpoch::Regular(epoch)
}
}
impl AsRef<Epoch> for ViableEpoch {
fn as_ref(&self) -> &Epoch {
match *self {
ViableEpoch::Genesis(UnimportedGenesis(ref e)) => e,
ViableEpoch::Regular(ref e) => e,
}
}
}
impl ViableEpoch {
/// Extract the underlying epoch, disregarding the fact that a genesis
/// epoch may be unimported.
pub fn into_inner(self) -> Epoch {
match self {
ViableEpoch::Genesis(UnimportedGenesis(e)) => e,
ViableEpoch::Regular(e) => e,
}
}
/// Increment the epoch, yielding an `IncrementedEpoch` to be imported
/// into the fork-tree.
pub fn increment(&self, next_descriptor: NextEpochDescriptor) -> IncrementedEpoch {
let next = self.as_ref().increment(next_descriptor);
let to_persist = match *self {
ViableEpoch::Genesis(UnimportedGenesis(ref epoch_0)) =>
PersistedEpoch::Genesis(epoch_0.clone(), next),
ViableEpoch::Regular(_) => PersistedEpoch::Regular(next),
};
IncrementedEpoch(to_persist)
}
}
/// The datatype encoded on disk.
// This really shouldn't be public, but the encode/decode derives force it to be.
#[derive(Clone, Encode, Decode)]
pub enum PersistedEpoch {
// epoch_0, epoch_1,
Genesis(Epoch, Epoch),
// epoch_n
Regular(Epoch),
}
/// A fresh, incremented epoch to import into the underlying fork-tree.
///
/// Create this with `ViableEpoch::increment`.
#[must_use = "Freshly-incremented epoch must be imported with `EpochChanges::import`"]
pub struct IncrementedEpoch(PersistedEpoch);
impl AsRef<Epoch> for IncrementedEpoch {
fn as_ref(&self) -> &Epoch {
match self.0 {
PersistedEpoch::Genesis(_, ref epoch_1) => epoch_1,
PersistedEpoch::Regular(ref epoch_n) => epoch_n,
}
}
}
/// Tree of all epoch changes across all *seen* forks. Data stored in tree is
/// the hash and block number of the block signaling the epoch change, and the
/// epoch that was signalled at that block.
///
/// BABE special-cases the first epoch, epoch_0, by saying that it starts at
/// slot number of the first block in the chain. When bootstrapping a chain,
/// there can be multiple competing block #1s, so we have to ensure that the overlayed
/// DAG doesn't get confused.
///
/// The first block of every epoch should be producing a descriptor for the next
/// epoch - this is checked in higher-level code. So the first block of epoch_0 contains
/// a descriptor for epoch_1. We special-case these and bundle them together in the
/// same DAG entry, pinned to a specific block #1.
///
/// Further epochs (epoch_2, ..., epoch_n) each get their own entry.
#[derive(Clone, Encode, Decode)]
pub struct EpochChanges<Hash, Number> {
inner: ForkTree<Hash, Number, PersistedEpoch>,
}
// create a fake header hash which hasn't been included in the chain.
fn fake_head_hash<H: AsRef<[u8]> + AsMut<[u8]> + Clone>(parent_hash: &H) -> H {
let mut h = parent_hash.clone();
// dirty trick: flip the first bit of the parent hash to create a hash
// which has not been in the chain before (assuming a strong hash function).
h.as_mut()[0] ^= 0b10000000;
h
}
impl<Hash, Number> EpochChanges<Hash, Number> where
Hash: PartialEq + AsRef<[u8]> + AsMut<[u8]> + Copy,
Number: Ord + One + Zero + Add<Output=Number> + Copy,
{
/// Create a new epoch-change tracker.
fn new() -> Self {
EpochChanges { inner: ForkTree::new() }
}
/// Prune out finalized epochs, except for the ancestor of the finalized
/// block. The given slot should be the slot number at which the finalized
/// block was authored.
pub fn prune_finalized<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: &Hash,
number: Number,
slot: SlotNumber,
) -> Result<(), fork_tree::Error<D::Error>> {
let is_descendent_of = descendent_of_builder
.build_is_descendent_of(None);
let predicate = |epoch: &PersistedEpoch| match *epoch {
PersistedEpoch::Genesis(_, ref epoch_1) =>
slot >= epoch_1.end_slot(),
PersistedEpoch::Regular(ref epoch_n) =>
slot >= epoch_n.end_slot(),
};
// prune any epochs which could not be _live_ as of the children of the
// finalized block, i.e. re-root the fork tree to the oldest ancestor of
// (hash, number) where epoch.end_slot() >= finalized_slot
self.inner.prune(
hash,
&number,
&is_descendent_of,
&predicate,
)?;
Ok(())
}
/// Finds the epoch for a child of the given block, assuming the given slot number.
///
/// If the returned epoch is an `UnimportedGenesis` epoch, it should be imported into the
/// tree.
pub fn epoch_for_child_of<D: IsDescendentOfBuilder<Hash>, G>(
&self,
descendent_of_builder: D,
parent_hash: &Hash,
parent_number: Number,
slot_number: SlotNumber,
make_genesis: G,
) -> Result<Option<ViableEpoch>, fork_tree::Error<D::Error>>
where G: FnOnce(SlotNumber) -> Epoch
{
// find_node_where will give you the node in the fork-tree which is an ancestor
// of the `parent_hash` by default. if the last epoch was signalled at the parent_hash,
// then it won't be returned. we need to create a new fake chain head hash which
// "descends" from our parent-hash.
let fake_head_hash = fake_head_hash(parent_hash);
let is_descendent_of = descendent_of_builder
.build_is_descendent_of(Some((fake_head_hash, *parent_hash)));
if parent_number == Zero::zero() {
// need to insert the genesis epoch.
let genesis_epoch = make_genesis(slot_number);
return Ok(Some(ViableEpoch::Genesis(UnimportedGenesis(genesis_epoch))));
}
// We want to find the deepest node in the tree which is an ancestor
// of our block and where the start slot of the epoch was before the
// slot of our block. The genesis special-case doesn't need to look
// at epoch_1 -- all we're doing here is figuring out which node
// we need.
let predicate = |epoch: &PersistedEpoch| match *epoch {
PersistedEpoch::Genesis(ref epoch_0, _) =>
epoch_0.start_slot <= slot_number,
PersistedEpoch::Regular(ref epoch_n) =>
epoch_n.start_slot <= slot_number,
};
self.inner.find_node_where(
&fake_head_hash,
&(parent_number + One::one()),
&is_descendent_of,
&predicate,
)
.map(|n| n.map(|node| ViableEpoch::Regular(match node.data {
// Ok, we found our node.
// and here we figure out which of the internal epochs
// of a genesis node to use based on their start slot.
PersistedEpoch::Genesis(ref epoch_0, ref epoch_1) =>
if epoch_1.start_slot <= slot_number {
epoch_1.clone()
} else {
epoch_0.clone()
},
PersistedEpoch::Regular(ref epoch_n) => epoch_n.clone(),
})))
}
/// Import a new epoch-change, signalled at the given block.
///
/// This assumes that the given block is prospective (i.e. has not been
/// imported yet), but its parent has. This is why the parent hash needs
/// to be provided.
pub fn import<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: Hash,
number: Number,
parent_hash: Hash,
epoch: IncrementedEpoch,
) -> Result<(), fork_tree::Error<D::Error>> {
let is_descendent_of = descendent_of_builder
.build_is_descendent_of(Some((hash, parent_hash)));
let res = self.inner.import(
hash,
number,
epoch.0,
&is_descendent_of,
);
match res {
Ok(_) | Err(fork_tree::Error::Duplicate) => Ok(()),
Err(e) => Err(e),
}
}
/// Return the inner fork tree, useful for testing purposes.
#[cfg(test)]
pub fn tree(&self) -> &ForkTree<Hash, Number, PersistedEpoch> {
&self.inner
}
}
/// Type alias to produce the epoch-changes tree from a block type.
pub type EpochChangesFor<Block> = EpochChanges<<Block as BlockT>::Hash, NumberFor<Block>>;
/// A shared epoch changes tree.
#[derive(Clone)]
pub struct SharedEpochChanges<Block: BlockT> {
inner: Arc<Mutex<EpochChangesFor<Block>>>,
}
impl<Block: BlockT> SharedEpochChanges<Block> {
/// Create a new instance of the `SharedEpochChanges`.
pub fn new() -> Self {
SharedEpochChanges {
inner: Arc::new(Mutex::new(EpochChanges::<_, _>::new()))
}
}
/// Lock the shared epoch changes,
pub fn lock(&self) -> MutexGuard<EpochChangesFor<Block>> {
self.inner.lock()
}
}
impl<Block: BlockT> From<EpochChangesFor<Block>> for SharedEpochChanges<Block> {
fn from(epoch_changes: EpochChangesFor<Block>) -> Self {
SharedEpochChanges {
inner: Arc::new(Mutex::new(epoch_changes))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
pub struct TestError;
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "TestError")
}
}
impl std::error::Error for TestError {}
impl<'a, F: 'a , H: 'a + PartialEq + std::fmt::Debug> IsDescendentOfBuilder<H> for &'a F
where F: Fn(&H, &H) -> Result<bool, TestError>
{
type Error = TestError;
type IsDescendentOf = Box<dyn Fn(&H, &H) -> Result<bool, TestError> + 'a>;
fn build_is_descendent_of(&self, current: Option<(H, H)>)
-> Self::IsDescendentOf
{
let f = *self;
Box::new(move |base, head| {
let mut head = head;
if let Some((ref c_head, ref c_parent)) = current {
if head == c_head {
if base == c_parent {
return Ok(true);
} else {
head = c_parent;
}
}
}
f(base, head)
})
}
}
type Hash = [u8; 1];
#[test]
fn genesis_epoch_is_created_but_not_imported() {
//
// A - B
// \
// — C
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration: 100,
authorities: Vec::new(),
randomness: [0; 32],
};
let epoch_changes = EpochChanges::new();
let genesis_epoch = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
10101,
&make_genesis,
).unwrap().unwrap();
match genesis_epoch {
ViableEpoch::Genesis(_) => {},
_ => panic!("should be unimported genesis"),
};
assert_eq!(genesis_epoch.as_ref(), &make_genesis(10101));
let genesis_epoch_2 = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
10102,
&make_genesis,
).unwrap().unwrap();
match genesis_epoch_2 {
ViableEpoch::Genesis(_) => {},
_ => panic!("should be unimported genesis"),
};
assert_eq!(genesis_epoch_2.as_ref(), &make_genesis(10102));
}
#[test]
fn epoch_changes_between_blocks() {
//
// A - B
// \
// — C
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration: 100,
authorities: Vec::new(),
randomness: [0; 32],
};
let mut epoch_changes = EpochChanges::new();
let genesis_epoch = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
100,
&make_genesis,
).unwrap().unwrap();
assert_eq!(genesis_epoch.as_ref(), &make_genesis(100));
let import_epoch_1 = genesis_epoch.increment(NextEpochDescriptor {
authorities: Vec::new(),
randomness: [1; 32],
});
let epoch_1 = import_epoch_1.as_ref().clone();
epoch_changes.import(
&is_descendent_of,
*b"A",
1,
*b"0",
import_epoch_1,
).unwrap();
let genesis_epoch = genesis_epoch.into_inner();
assert!(is_descendent_of(b"0", b"A").unwrap());
let end_slot = genesis_epoch.end_slot();
assert_eq!(end_slot, epoch_1.start_slot);
{
// x is still within the genesis epoch.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
end_slot - 1,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, genesis_epoch);
}
{
// x is now at the next epoch, because the block is now at the
// start slot of epoch 1.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
end_slot,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, epoch_1);
}
{
// x is now at the next epoch, because the block is now after
// start slot of epoch 1.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
epoch_1.end_slot() - 1,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, epoch_1);
}
}
#[test]
fn two_block_ones_dont_conflict() {
// X - Y
// /
// 0 - A - B
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B"),
(b"X", b) => Ok(b == *b"Y"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let duration = 100;
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration,
authorities: Vec::new(),
randomness: [0; 32],
};
let mut epoch_changes = EpochChanges::new();
let next_descriptor = NextEpochDescriptor {
authorities: Vec::new(),
randomness: [0; 32],
};
// insert genesis epoch for A
{
let genesis_epoch_a = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
100,
&make_genesis,
).unwrap().unwrap();
epoch_changes.import(
&is_descendent_of,
*b"A",
1,
*b"0",
genesis_epoch_a.increment(next_descriptor.clone()),
).unwrap();
}
// insert genesis epoch for X
{
let genesis_epoch_x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
1000,
&make_genesis,
).unwrap().unwrap();
epoch_changes.import(
&is_descendent_of,
*b"X",
1,
*b"0",
genesis_epoch_x.increment(next_descriptor.clone()),
).unwrap();
}
// now check that the genesis epochs for our respective block 1s
// respect the chain structure.
{
let epoch_for_a_child = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
101,
&make_genesis,
).unwrap().unwrap();
assert_eq!(epoch_for_a_child.into_inner(), make_genesis(100));
let epoch_for_x_child = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"X",
1,
1001,
&make_genesis,
).unwrap().unwrap();
assert_eq!(epoch_for_x_child.into_inner(), make_genesis(1000));
let epoch_for_x_child_before_genesis = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"X",
1,
101,
&make_genesis,
).unwrap();
// even though there is a genesis epoch at that slot, it's not in
// this chain.
assert!(epoch_for_x_child_before_genesis.is_none());
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,779 @@
// 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 <http://www.gnu.org/licenses/>.
//! BABE testsuite
// FIXME #2532: need to allow deprecated until refactor is done
// https://github.com/paritytech/substrate/issues/2532
#![allow(deprecated)]
use super::*;
use authorship::claim_slot;
use babe_primitives::{AuthorityPair, SlotNumber};
use block_builder::BlockBuilder;
use consensus_common::NoNetwork as DummyOracle;
use consensus_common::import_queue::{
BoxBlockImport, BoxJustificationImport, BoxFinalityProofImport,
};
use network::test::*;
use network::test::{Block as TestBlock, PeersClient};
use network::config::BoxFinalityProofRequestBuilder;
use sr_primitives::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
use network::config::ProtocolConfig;
use tokio::runtime::current_thread;
use client_api::BlockchainEvents;
use test_client;
use log::debug;
use std::{time::Duration, cell::RefCell};
type Item = DigestItem<Hash>;
type Error = client::error::Error;
type TestClient = client::Client<
test_client::Backend,
test_client::Executor,
TestBlock,
test_client::runtime::RuntimeApi,
>;
#[derive(Copy, Clone, PartialEq)]
enum Stage {
PreSeal,
PostSeal,
}
type Mutator = Arc<dyn Fn(&mut TestHeader, Stage) + Send + Sync>;
#[derive(Clone)]
struct DummyFactory {
client: Arc<TestClient>,
epoch_changes: crate::SharedEpochChanges<TestBlock>,
config: Config,
mutator: Mutator,
}
struct DummyProposer {
factory: DummyFactory,
parent_hash: Hash,
parent_number: u64,
parent_slot: SlotNumber,
}
impl Environment<TestBlock> for DummyFactory {
type Proposer = DummyProposer;
type Error = Error;
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
-> Result<DummyProposer, Error>
{
let parent_slot = crate::find_pre_digest::<TestBlock>(parent_header)
.expect("parent header has a pre-digest")
.slot_number();
Ok(DummyProposer {
factory: self.clone(),
parent_hash: parent_header.hash(),
parent_number: *parent_header.number(),
parent_slot,
})
}
}
impl DummyProposer {
fn propose_with(&mut self, pre_digests: DigestFor<TestBlock>)
-> future::Ready<Result<TestBlock, Error>>
{
use codec::Encode;
let block_builder = self.factory.client.new_block_at(
&BlockId::Hash(self.parent_hash),
pre_digests,
).unwrap();
let mut block = match block_builder.bake().map_err(|e| e.into()) {
Ok(b) => b,
Err(e) => return future::ready(Err(e)),
};
let this_slot = crate::find_pre_digest::<TestBlock>(block.header())
.expect("baked block has valid pre-digest")
.slot_number();
// figure out if we should add a consensus digest, since the test runtime
// doesn't.
let epoch_changes = self.factory.epoch_changes.lock();
let epoch = epoch_changes.epoch_for_child_of(
descendent_query(&*self.factory.client),
&self.parent_hash,
self.parent_number,
this_slot,
|slot| self.factory.config.genesis_epoch(slot),
)
.expect("client has data to find epoch")
.expect("can compute epoch for baked block")
.into_inner();
let first_in_epoch = self.parent_slot < epoch.start_slot;
if first_in_epoch {
// push a `Consensus` digest signalling next change.
// we just reuse the same randomness and authorities as the prior
// epoch. this will break when we add light client support, since
// that will re-check the randomness logic off-chain.
let digest_data = ConsensusLog::NextEpochData(NextEpochDescriptor {
authorities: epoch.authorities.clone(),
randomness: epoch.randomness.clone(),
}).encode();
let digest = DigestItem::Consensus(BABE_ENGINE_ID, digest_data);
block.header.digest_mut().push(digest)
}
// mutate the block header according to the mutator.
(self.factory.mutator)(&mut block.header, Stage::PreSeal);
future::ready(Ok(block))
}
}
impl Proposer<TestBlock> for DummyProposer {
type Error = Error;
type Create = future::Ready<Result<TestBlock, Error>>;
fn propose(
&mut self,
_: InherentData,
pre_digests: DigestFor<TestBlock>,
_: Duration,
) -> Self::Create {
self.propose_with(pre_digests)
}
}
thread_local! {
static MUTATOR: RefCell<Mutator> = RefCell::new(Arc::new(|_, _|()));
}
#[derive(Clone)]
struct PanickingBlockImport<B>(B);
impl<B: BlockImport<TestBlock>> BlockImport<TestBlock> for PanickingBlockImport<B> {
type Error = B::Error;
fn import_block(
&mut self,
block: BlockImportParams<TestBlock>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.import_block(block, new_cache).expect("importing block failed"))
}
fn check_block(
&mut self,
block: BlockCheckParams<TestBlock>,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.check_block(block).expect("checking block failed"))
}
}
pub struct BabeTestNet {
peers: Vec<Peer<Option<PeerData>, DummySpecialization>>,
}
type TestHeader = <TestBlock as BlockT>::Header;
type TestExtrinsic = <TestBlock as BlockT>::Extrinsic;
pub struct TestVerifier {
inner: BabeVerifier<
test_client::Backend,
test_client::Executor,
TestBlock,
test_client::runtime::RuntimeApi,
PeersFullClient,
>,
mutator: Mutator,
}
impl Verifier<TestBlock> for TestVerifier {
/// 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.
fn verify(
&mut self,
origin: BlockOrigin,
mut header: TestHeader,
justification: Option<Justification>,
body: Option<Vec<TestExtrinsic>>,
) -> Result<(BlockImportParams<TestBlock>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
// apply post-sealing mutations (i.e. stripping seal, if desired).
(self.mutator)(&mut header, Stage::PostSeal);
Ok(self.inner.verify(origin, header, justification, body).expect("verification failed!"))
}
}
pub struct PeerData {
link: BabeLink<TestBlock>,
inherent_data_providers: InherentDataProviders,
block_import: Mutex<Option<BoxBlockImport<TestBlock>>>,
}
impl TestNetFactory for BabeTestNet {
type Specialization = DummySpecialization;
type Verifier = TestVerifier;
type PeerData = Option<PeerData>;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
debug!(target: "babe", "Creating test network from config");
BabeTestNet {
peers: Vec::new(),
}
}
fn make_block_import(&self, client: PeersClient)
-> (
BoxBlockImport<Block>,
Option<BoxJustificationImport<Block>>,
Option<BoxFinalityProofImport<Block>>,
Option<BoxFinalityProofRequestBuilder<Block>>,
Option<PeerData>,
)
{
let client = client.as_full().expect("only full clients are tested");
let inherent_data_providers = InherentDataProviders::new();
let config = Config::get_or_compute(&*client).expect("config available");
let (block_import, link) = crate::block_import(
config,
client.clone(),
client.clone(),
client.clone(),
).expect("can initialize block-import");
let block_import = PanickingBlockImport(block_import);
let data_block_import = Mutex::new(Some(Box::new(block_import.clone()) as BoxBlockImport<_>));
(
Box::new(block_import),
None,
None,
None,
Some(PeerData { link, inherent_data_providers, block_import: data_block_import }),
)
}
fn make_verifier(
&self,
client: PeersClient,
_cfg: &ProtocolConfig,
maybe_link: &Option<PeerData>,
)
-> Self::Verifier
{
let client = client.as_full().expect("only full clients are used in test");
trace!(target: "babe", "Creating a verifier");
// ensure block import and verifier are linked correctly.
let data = maybe_link.as_ref().expect("babe link always provided to verifier instantiation");
TestVerifier {
inner: BabeVerifier {
client: client.clone(),
api: client,
inherent_data_providers: data.inherent_data_providers.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
time_source: data.link.time_source.clone(),
},
mutator: MUTATOR.with(|m| m.borrow().clone()),
}
}
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, DummySpecialization> {
trace!(target: "babe", "Retreiving a peer");
&mut self.peers[i]
}
fn peers(&self) -> &Vec<Peer<Self::PeerData, DummySpecialization>> {
trace!(target: "babe", "Retreiving peers");
&self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData, DummySpecialization>>)>(
&mut self,
closure: F,
) {
closure(&mut self.peers);
}
}
#[test]
#[should_panic]
fn rejects_empty_block() {
env_logger::try_init().unwrap();
let mut net = BabeTestNet::new(3);
let block_builder = |builder: BlockBuilder<_, _>| {
builder.bake().unwrap()
};
net.mut_peers(|peer| {
peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder);
})
}
fn run_one_test(
mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static,
) {
let _ = env_logger::try_init();
let mutator = Arc::new(mutator) as Mutator;
MUTATOR.with(|m| *m.borrow_mut() = mutator.clone());
let net = BabeTestNet::new(3);
let peers = &[
(0, "//Alice"),
(1, "//Bob"),
(2, "//Charlie"),
];
let net = Arc::new(Mutex::new(net));
let mut import_notifications = Vec::new();
let mut runtime = current_thread::Runtime::new().unwrap();
let mut keystore_paths = Vec::new();
for (peer_id, seed) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let select_chain = peer.select_chain().expect("Full client has select_chain");
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
keystore.write().insert_ephemeral_from_seed::<AuthorityPair>(seed).expect("Generates authority key");
keystore_paths.push(keystore_path);
let mut got_own = false;
let mut got_other = false;
let data = peer.data.as_ref().expect("babe link set up during initialization");
let environ = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: mutator.clone(),
};
import_notifications.push(
// run each future until we get one of our own blocks with number higher than 5
// that was produced locally.
client.import_notification_stream()
.take_while(move |n| future::ready(n.header.number() < &5 || {
if n.origin == BlockOrigin::Own {
got_own = true;
} else {
got_other = true;
}
// continue until we have at least one block of our own
// and one of another peer.
!(got_own && got_other)
}))
.for_each(|_| future::ready(()) )
);
runtime.spawn(start_babe(BabeParams {
block_import: data.block_import.lock().take().expect("import set up during init"),
select_chain,
client,
env: environ,
sync_oracle: DummyOracle,
inherent_data_providers: data.inherent_data_providers.clone(),
force_authoring: false,
babe_link: data.link.clone(),
keystore,
}).expect("Starts babe"));
}
runtime.spawn(futures01::future::poll_fn(move || {
net.lock().poll();
Ok::<_, ()>(futures01::Async::NotReady::<()>)
}));
runtime.block_on(future::join_all(import_notifications)
.map(|_| Ok::<(), ()>(())).compat()).unwrap();
}
#[test]
fn authoring_blocks() {
run_one_test(|_, _| ())
}
#[test]
#[should_panic]
fn rejects_missing_inherent_digest() {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| stage == Stage::PostSeal || v.as_babe_pre_digest().is_none())
.collect()
})
}
#[test]
#[should_panic]
fn rejects_missing_seals() {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| stage == Stage::PreSeal || v.as_babe_seal().is_none())
.collect()
})
}
#[test]
#[should_panic]
fn rejects_missing_consensus_digests() {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| stage == Stage::PostSeal || v.as_next_epoch_descriptor().is_none())
.collect()
});
}
#[test]
fn wrong_consensus_engine_id_rejected() {
let _ = env_logger::try_init();
let sig = AuthorityPair::generate().0.sign(b"");
let bad_seal: Item = DigestItem::Seal([0; 4], sig.to_vec());
assert!(bad_seal.as_babe_pre_digest().is_none());
assert!(bad_seal.as_babe_seal().is_none())
}
#[test]
fn malformed_pre_digest_rejected() {
let _ = env_logger::try_init();
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, [0; 64].to_vec());
assert!(bad_seal.as_babe_pre_digest().is_none());
}
#[test]
fn sig_is_not_pre_digest() {
let _ = env_logger::try_init();
let sig = AuthorityPair::generate().0.sign(b"");
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, sig.to_vec());
assert!(bad_seal.as_babe_pre_digest().is_none());
assert!(bad_seal.as_babe_seal().is_some())
}
#[test]
fn can_author_block() {
let _ = env_logger::try_init();
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
let pair = keystore.write().insert_ephemeral_from_seed::<AuthorityPair>("//Alice")
.expect("Generates authority pair");
let mut i = 0;
let epoch = Epoch {
start_slot: 0,
authorities: vec![(pair.public(), 1)],
randomness: [0; 32],
epoch_index: 1,
duration: 100,
};
let mut config = crate::BabeConfiguration {
slot_duration: 1000,
epoch_length: 100,
c: (3, 10),
genesis_authorities: Vec::new(),
randomness: [0; 32],
secondary_slots: true,
};
// with secondary slots enabled it should never be empty
match claim_slot(i, &epoch, &config, &keystore) {
None => i += 1,
Some(s) => debug!(target: "babe", "Authored block {:?}", s.0),
}
// otherwise with only vrf-based primary slots we might need to try a couple
// of times.
config.secondary_slots = false;
loop {
match claim_slot(i, &epoch, &config, &keystore) {
None => i += 1,
Some(s) => {
debug!(target: "babe", "Authored block {:?}", s.0);
break;
}
}
}
}
// Propose and import a new BABE block on top of the given parent.
fn propose_and_import_block(
parent: &TestHeader,
slot_number: Option<SlotNumber>,
proposer_factory: &mut DummyFactory,
block_import: &mut BoxBlockImport<TestBlock>,
) -> H256 {
let mut proposer = proposer_factory.init(parent).unwrap();
let slot_number = slot_number.unwrap_or_else(|| {
let parent_pre_digest = find_pre_digest::<TestBlock>(parent).unwrap();
parent_pre_digest.slot_number() + 1
});
let pre_digest = sr_primitives::generic::Digest {
logs: vec![
Item::babe_pre_digest(
BabePreDigest::Secondary {
authority_index: 0,
slot_number,
},
),
],
};
let mut block = futures::executor::block_on(proposer.propose_with(pre_digest)).unwrap();
let seal = {
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let pair = AuthorityPair::from_seed(&[1; 32]);
let pre_hash = block.header.hash();
let signature = pair.sign(pre_hash.as_ref());
Item::babe_seal(signature)
};
let post_hash = {
block.header.digest_mut().push(seal.clone());
let h = block.header.hash();
block.header.digest_mut().pop();
h
};
let import_result = block_import.import_block(
BlockImportParams {
origin: BlockOrigin::Own,
header: block.header,
justification: None,
post_digests: vec![seal],
body: Some(block.extrinsics),
finalized: false,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
allow_missing_state: false,
},
Default::default(),
).unwrap();
match import_result {
ImportResult::Imported(_) => {},
_ => panic!("expected block to be imported"),
}
post_hash
}
#[test]
fn importing_block_one_sets_genesis_epoch() {
let mut net = BabeTestNet::new(1);
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let mut proposer_factory = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: Arc::new(|_, _| ()),
};
let mut block_import = data.block_import.lock().take().expect("import set up during init");
let genesis_header = client.header(&BlockId::Number(0)).unwrap().unwrap();
let block_hash = propose_and_import_block(
&genesis_header,
Some(999),
&mut proposer_factory,
&mut block_import,
);
let genesis_epoch = data.link.config.genesis_epoch(999);
let epoch_changes = data.link.epoch_changes.lock();
let epoch_for_second_block = epoch_changes.epoch_for_child_of(
descendent_query(&*client),
&block_hash,
1,
1000,
|slot| data.link.config.genesis_epoch(slot),
).unwrap().unwrap().into_inner();
assert_eq!(epoch_for_second_block, genesis_epoch);
}
#[test]
fn importing_epoch_change_block_prunes_tree() {
use client_api::Finalizer;
let mut net = BabeTestNet::new(1);
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let mut block_import = data.block_import.lock().take().expect("import set up during init");
let epoch_changes = data.link.epoch_changes.clone();
let mut proposer_factory = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: Arc::new(|_, _| ()),
};
// This is just boilerplate code for proposing and importing n valid BABE
// blocks that are built on top of the given parent. The proposer takes care
// of producing epoch change digests according to the epoch duration (which
// is set to 6 slots in the test runtime).
let mut propose_and_import_blocks = |parent_id, n| {
let mut hashes = Vec::new();
let mut parent_header = client.header(&parent_id).unwrap().unwrap();
for _ in 0..n {
let block_hash = propose_and_import_block(
&parent_header,
None,
&mut proposer_factory,
&mut block_import,
);
hashes.push(block_hash);
parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap();
}
hashes
};
// This is the block tree that we're going to use in this test. Each node
// represents an epoch change block, the epoch duration is 6 slots.
//
// *---- F (#7)
// / *------ G (#19) - H (#25)
// / /
// A (#1) - B (#7) - C (#13) - D (#19) - E (#25)
// \
// *------ I (#25)
// Create and import the canon chain and keep track of fork blocks (A, C, D)
// from the diagram above.
let canon_hashes = propose_and_import_blocks(BlockId::Number(0), 30);
// Create the forks
let fork_1 = propose_and_import_blocks(BlockId::Hash(canon_hashes[0]), 10);
let fork_2 = propose_and_import_blocks(BlockId::Hash(canon_hashes[12]), 15);
let fork_3 = propose_and_import_blocks(BlockId::Hash(canon_hashes[18]), 10);
// We should be tracking a total of 9 epochs in the fork tree
assert_eq!(
epoch_changes.lock().tree().iter().count(),
9,
);
// And only one root
assert_eq!(
epoch_changes.lock().tree().roots().count(),
1,
);
// We finalize block #13 from the canon chain, so on the next epoch
// change the tree should be pruned, to not contain F (#7).
client.finalize_block(BlockId::Hash(canon_hashes[12]), None, false).unwrap();
propose_and_import_blocks(BlockId::Hash(client.info().chain.best_hash), 7);
// at this point no hashes from the first fork must exist on the tree
assert!(
!epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_1.contains(h)),
);
// but the epoch changes from the other forks must still exist
assert!(
epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h))
);
assert!(
epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
);
// finalizing block #25 from the canon chain should prune out the second fork
client.finalize_block(BlockId::Hash(canon_hashes[24]), None, false).unwrap();
propose_and_import_blocks(BlockId::Hash(client.info().chain.best_hash), 8);
// at this point no hashes from the second fork must exist on the tree
assert!(
!epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h)),
);
// while epoch changes from the last fork should still exist
assert!(
epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
);
}
#[test]
#[should_panic]
fn verify_slots_are_strictly_increasing() {
let mut net = BabeTestNet::new(1);
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let mut block_import = data.block_import.lock().take().expect("import set up during init");
let mut proposer_factory = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: Arc::new(|_, _| ()),
};
let genesis_header = client.header(&BlockId::Number(0)).unwrap().unwrap();
// we should have no issue importing this block
let b1 = propose_and_import_block(
&genesis_header,
Some(999),
&mut proposer_factory,
&mut block_import,
);
let b1 = client.header(&BlockId::Hash(b1)).unwrap().unwrap();
// we should fail to import this block since the slot number didn't increase.
// we will panic due to the `PanickingBlockImport` defined above.
propose_and_import_block(
&b1,
Some(999),
&mut proposer_factory,
&mut block_import,
);
}
@@ -0,0 +1,217 @@
// 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 <http://www.gnu.org/licenses/>.
//! Verification for BABE headers.
use schnorrkel::vrf::{VRFOutput, VRFProof};
use sr_primitives::{traits::Header, traits::DigestItemFor};
use primitives::{Pair, Public};
use babe_primitives::{Epoch, BabePreDigest, CompatibleDigestItem, AuthorityId};
use babe_primitives::{AuthoritySignature, SlotNumber, AuthorityIndex, AuthorityPair};
use slots::CheckedHeader;
use log::{debug, trace};
use super::{find_pre_digest, babe_err, BlockT, Error};
use super::authorship::{make_transcript, calculate_primary_threshold, check_primary_threshold, secondary_slot_author};
/// BABE verification parameters
pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
/// the header being verified.
pub(super) header: B::Header,
/// the pre-digest of the header being verified. this is optional - if prior
/// verification code had to read it, it can be included here to avoid duplicate
/// work.
pub(super) pre_digest: Option<BabePreDigest>,
/// the slot number of the current time.
pub(super) slot_now: SlotNumber,
/// epoch descriptor of the epoch this block _should_ be under, if it's valid.
pub(super) epoch: &'a Epoch,
/// genesis config of this BABE chain.
pub(super) config: &'a super::Config,
}
/// Check a header has been signed by the right key. If the slot is too far in
/// the future, an error will be returned. If successful, returns the pre-header
/// and the digest item containing the seal.
///
/// The seal must be the last digest. Otherwise, the whole header is considered
/// unsigned. This is required for security and must not be changed.
///
/// This digest item will always return `Some` when used with `as_babe_pre_digest`.
///
/// The given header can either be from a primary or secondary slot assignment,
/// with each having different validation logic.
pub(super) fn check_header<B: BlockT + Sized>(
params: VerificationParams<B>,
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, Error<B>> where
DigestItemFor<B>: CompatibleDigestItem,
{
let VerificationParams {
mut header,
pre_digest,
slot_now,
epoch,
config,
} = params;
let authorities = &epoch.authorities;
let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::<B>(&header))?;
trace!(target: "babe", "Checking header");
let seal = match header.digest_mut().pop() {
Some(x) => x,
None => return Err(babe_err(Error::HeaderUnsealed(header.hash()))),
};
let sig = seal.as_babe_seal().ok_or_else(|| {
babe_err(Error::HeaderBadSeal(header.hash()))
})?;
// the pre-hash of the header doesn't include the seal
// and that's what we sign
let pre_hash = header.hash();
if pre_digest.slot_number() > slot_now {
header.digest_mut().push(seal);
return Ok(CheckedHeader::Deferred(header, pre_digest.slot_number()));
}
let author = match authorities.get(pre_digest.authority_index() as usize) {
Some(author) => author.0.clone(),
None => return Err(babe_err(Error::SlotAuthorNotFound)),
};
match &pre_digest {
BabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number } => {
debug!(target: "babe", "Verifying Primary block");
let digest = (vrf_output, vrf_proof, *authority_index, *slot_number);
check_primary_header::<B>(
pre_hash,
digest,
sig,
&epoch,
config.c,
)?;
},
BabePreDigest::Secondary { authority_index, slot_number } if config.secondary_slots => {
debug!(target: "babe", "Verifying Secondary block");
let digest = (*authority_index, *slot_number);
check_secondary_header::<B>(
pre_hash,
digest,
sig,
&epoch,
)?;
},
_ => {
return Err(babe_err(Error::SecondarySlotAssignmentsDisabled));
}
}
let info = VerifiedHeaderInfo {
pre_digest: CompatibleDigestItem::babe_pre_digest(pre_digest),
seal,
author,
};
Ok(CheckedHeader::Checked(header, info))
}
pub(super) struct VerifiedHeaderInfo<B: BlockT> {
pub(super) pre_digest: DigestItemFor<B>,
pub(super) seal: DigestItemFor<B>,
pub(super) author: AuthorityId,
}
/// Check a primary slot proposal header. We validate that the given header is
/// properly signed by the expected authority, and that the contained VRF proof
/// is valid. Additionally, the weight of this block must increase compared to
/// its parent since it is a primary block.
fn check_primary_header<B: BlockT + Sized>(
pre_hash: B::Hash,
pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber),
signature: AuthoritySignature,
epoch: &Epoch,
c: (u64, u64),
) -> Result<(), Error<B>> {
let (vrf_output, vrf_proof, authority_index, slot_number) = pre_digest;
let author = &epoch.authorities[authority_index as usize].0;
if AuthorityPair::verify(&signature, pre_hash, &author) {
let (inout, _) = {
let transcript = make_transcript(
&epoch.randomness,
slot_number,
epoch.epoch_index,
);
schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| {
p.vrf_verify(transcript, vrf_output, vrf_proof)
}).map_err(|s| {
babe_err(Error::VRFVerificationFailed(s))
})?
};
let threshold = calculate_primary_threshold(
c,
&epoch.authorities,
authority_index as usize,
);
if !check_primary_threshold(&inout, threshold) {
return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold)));
}
Ok(())
} else {
Err(babe_err(Error::BadSignature(pre_hash)))
}
}
/// Check a secondary slot proposal header. We validate that the given header is
/// properly signed by the expected authority, which we have a deterministic way
/// of computing. Additionally, the weight of this block must stay the same
/// compared to its parent since it is a secondary block.
fn check_secondary_header<B: BlockT>(
pre_hash: B::Hash,
pre_digest: (AuthorityIndex, SlotNumber),
signature: AuthoritySignature,
epoch: &Epoch,
) -> Result<(), Error<B>> {
let (authority_index, slot_number) = pre_digest;
// check the signature is valid under the expected authority and
// chain state.
let expected_author = secondary_slot_author(
slot_number,
&epoch.authorities,
epoch.randomness,
).ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
let author = &epoch.authorities[authority_index as usize].0;
if expected_author != author {
return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()));
}
if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
Ok(())
} else {
Err(Error::BadSignature(pre_hash))
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "substrate-consensus-pow"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "PoW consensus algorithm for substrate"
edition = "2018"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.0.0", features = ["derive"] }
primitives = { package = "substrate-primitives", path = "../../../primitives/core" }
sr-primitives = { path = "../../../primitives/sr-primitives" }
client-api = { package = "substrate-client-api", path = "../../api" }
block-builder-api = { package = "substrate-block-builder-runtime-api", path = "../../../primitives/block-builder/runtime-api" }
paint-timestamp = { path = "../../../paint/timestamp" }
inherents = { package = "substrate-inherents", path = "../../../primitives/inherents" }
pow-primitives = { package = "substrate-consensus-pow-primitives", path = "../../../primitives/consensus/pow" }
consensus-common = { package = "substrate-consensus-common", path = "../../../primitives/consensus/common" }
log = "0.4.8"
futures-preview = { version = "0.3.0-alpha.19", features = ["compat"] }
derive_more = "0.15.0"
+542
View File
@@ -0,0 +1,542 @@
// Copyright 2017-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 <http://www.gnu.org/licenses/>.
//! Proof of work consensus for Substrate.
//!
//! To use this engine, you can need to have a struct that implements
//! `PowAlgorithm`. After that, pass an instance of the struct, along
//! with other necessary client references to `import_queue` to setup
//! the queue. Use the `start_mine` function for basic CPU mining.
//!
//! The auxiliary storage for PoW engine only stores the total difficulty.
//! For other storage requirements for particular PoW algorithm (such as
//! the actual difficulty for each particular blocks), you can take a client
//! reference in your `PowAlgorithm` implementation, and use a separate prefix
//! for the auxiliary storage. It is also possible to just use the runtime
//! as the storage, but it is not recommended as it won't work well with light
//! clients.
use std::sync::Arc;
use std::thread;
use std::collections::HashMap;
use client_api::{
BlockOf, blockchain::{HeaderBackend, ProvideCache}, backend::AuxStore,
well_known_cache_keys::Id as CacheKeyId,
};
use block_builder_api::BlockBuilder as BlockBuilderApi;
use sr_primitives::{Justification, RuntimeString};
use sr_primitives::generic::{BlockId, Digest, DigestItem};
use sr_primitives::traits::{Block as BlockT, Header as HeaderT, ProvideRuntimeApi};
use paint_timestamp::{TimestampInherentData, InherentError as TIError};
use pow_primitives::{Seal, TotalDifficulty, POW_ENGINE_ID};
use primitives::H256;
use inherents::{InherentDataProviders, InherentData};
use consensus_common::{
BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer,
SelectChain, Error as ConsensusError
};
use consensus_common::import_queue::{BoxBlockImport, BasicQueue, Verifier};
use codec::{Encode, Decode};
use client_api;
use log::*;
#[derive(derive_more::Display, Debug)]
pub enum Error<B: BlockT> {
#[display(fmt = "Header uses the wrong engine {:?}", _0)]
WrongEngine([u8; 4]),
#[display(fmt = "Header {:?} is unsealed", _0)]
HeaderUnsealed(B::Hash),
#[display(fmt = "PoW validation error: invalid seal")]
InvalidSeal,
#[display(fmt = "Rejecting block too far in future")]
TooFarInFuture,
#[display(fmt = "Fetching best header failed using select chain: {:?}", _0)]
BestHeaderSelectChain(ConsensusError),
#[display(fmt = "Fetching best header failed: {:?}", _0)]
BestHeader(client_api::error::Error),
#[display(fmt = "Best header does not exist")]
NoBestHeader,
#[display(fmt = "Block proposing error: {:?}", _0)]
BlockProposingError(String),
#[display(fmt = "Fetch best hash failed via select chain: {:?}", _0)]
BestHashSelectChain(ConsensusError),
#[display(fmt = "Error with block built on {:?}: {:?}", _0, _1)]
BlockBuiltError(B::Hash, ConsensusError),
#[display(fmt = "Creating inherents failed: {}", _0)]
CreateInherents(inherents::Error),
#[display(fmt = "Checking inherents failed: {}", _0)]
CheckInherents(String),
Client(client_api::error::Error),
Codec(codec::Error),
Environment(String),
Runtime(RuntimeString)
}
impl<B: BlockT> std::convert::From<Error<B>> for String {
fn from(error: Error<B>) -> String {
error.to_string()
}
}
/// Auxiliary storage prefix for PoW engine.
pub const POW_AUX_PREFIX: [u8; 4] = *b"PoW:";
/// Get the auxiliary storage key used by engine to store total difficulty.
fn aux_key(hash: &H256) -> Vec<u8> {
POW_AUX_PREFIX.iter().chain(&hash[..])
.cloned().collect::<Vec<_>>()
}
/// Auxiliary storage data for PoW.
#[derive(Encode, Decode, Clone, Debug, Default)]
pub struct PowAux<Difficulty> {
/// Difficulty of the current block.
pub difficulty: Difficulty,
/// Total difficulty up to current block.
pub total_difficulty: Difficulty,
}
impl<Difficulty> PowAux<Difficulty> where
Difficulty: Decode + Default,
{
/// Read the auxiliary from client.
pub fn read<C: AuxStore, B: BlockT>(client: &C, hash: &H256) -> Result<Self, Error<B>> {
let key = aux_key(hash);
match client.get_aux(&key).map_err(Error::Client)? {
Some(bytes) => Self::decode(&mut &bytes[..])
.map_err(Error::Codec),
None => Ok(Self::default()),
}
}
}
/// Algorithm used for proof of work.
pub trait PowAlgorithm<B: BlockT> {
/// Difficulty for the algorithm.
type Difficulty: TotalDifficulty + Default + Encode + Decode + Ord + Clone + Copy;
/// Get the next block's difficulty.
fn difficulty(&self, parent: &BlockId<B>) -> Result<Self::Difficulty, Error<B>>;
/// Verify proof of work against the given difficulty.
fn verify(
&self,
parent: &BlockId<B>,
pre_hash: &H256,
seal: &Seal,
difficulty: Self::Difficulty,
) -> Result<bool, Error<B>>;
/// Mine a seal that satisfies the given difficulty.
fn mine(
&self,
parent: &BlockId<B>,
pre_hash: &H256,
difficulty: Self::Difficulty,
round: u32,
) -> Result<Option<Seal>, Error<B>>;
}
/// A verifier for PoW blocks.
pub struct PowVerifier<B: BlockT<Hash=H256>, C, S, Algorithm> {
client: Arc<C>,
algorithm: Algorithm,
inherent_data_providers: inherents::InherentDataProviders,
select_chain: Option<S>,
check_inherents_after: <<B as BlockT>::Header as HeaderT>::Number,
}
impl<B: BlockT<Hash=H256>, C, S, Algorithm> PowVerifier<B, C, S, Algorithm> {
pub fn new(
client: Arc<C>,
algorithm: Algorithm,
check_inherents_after: <<B as BlockT>::Header as HeaderT>::Number,
select_chain: Option<S>,
inherent_data_providers: inherents::InherentDataProviders,
) -> Self {
Self { client, algorithm, inherent_data_providers, select_chain, check_inherents_after }
}
fn check_header(
&self,
mut header: B::Header,
parent_block_id: BlockId<B>,
) -> Result<(B::Header, Algorithm::Difficulty, DigestItem<H256>), Error<B>> where
Algorithm: PowAlgorithm<B>,
{
let hash = header.hash();
let (seal, inner_seal) = match header.digest_mut().pop() {
Some(DigestItem::Seal(id, seal)) => {
if id == POW_ENGINE_ID {
(DigestItem::Seal(id, seal.clone()), seal)
} else {
return Err(Error::WrongEngine(id))
}
},
_ => return Err(Error::HeaderUnsealed(hash)),
};
let pre_hash = header.hash();
let difficulty = self.algorithm.difficulty(&parent_block_id)?;
if !self.algorithm.verify(
&parent_block_id,
&pre_hash,
&inner_seal,
difficulty,
)? {
return Err(Error::InvalidSeal);
}
Ok((header, difficulty, seal))
}
fn check_inherents(
&self,
block: B,
block_id: BlockId<B>,
inherent_data: InherentData,
timestamp_now: u64,
) -> Result<(), Error<B>> where
C: ProvideRuntimeApi, C::Api: BlockBuilderApi<B, Error = client_api::error::Error>
{
const MAX_TIMESTAMP_DRIFT_SECS: u64 = 60;
if *block.header().number() < self.check_inherents_after {
return Ok(())
}
let inherent_res = self.client.runtime_api().check_inherents(
&block_id,
block,
inherent_data,
).map_err(Error::Client)?;
if !inherent_res.ok() {
inherent_res
.into_errors()
.try_for_each(|(i, e)| match TIError::try_from(&i, &e) {
Some(TIError::ValidAtTimestamp(timestamp)) => {
if timestamp > timestamp_now + MAX_TIMESTAMP_DRIFT_SECS {
return Err(Error::TooFarInFuture);
}
Ok(())
},
Some(TIError::Other(e)) => Err(Error::Runtime(e)),
None => Err(Error::CheckInherents(
self.inherent_data_providers.error_to_string(&i, &e)
)),
})
} else {
Ok(())
}
}
}
impl<B: BlockT<Hash=H256>, C, S, Algorithm> Verifier<B> for PowVerifier<B, C, S, Algorithm> where
C: ProvideRuntimeApi + Send + Sync + HeaderBackend<B> + AuxStore + ProvideCache<B> + BlockOf,
C::Api: BlockBuilderApi<B, Error = client_api::error::Error>,
S: SelectChain<B>,
Algorithm: PowAlgorithm<B> + Send + Sync,
{
fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
mut body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let inherent_data = self.inherent_data_providers
.create_inherent_data().map_err(|e| e.into_string())?;
let timestamp_now = inherent_data.timestamp_inherent_data().map_err(|e| e.into_string())?;
let best_hash = match self.select_chain.as_ref() {
Some(select_chain) => select_chain.best_chain()
.map_err(|e| format!("Fetch best chain failed via select chain: {:?}", e))?
.hash(),
None => self.client.info().best_hash,
};
let hash = header.hash();
let parent_hash = *header.parent_hash();
let best_aux = PowAux::read::<_, B>(self.client.as_ref(), &best_hash)?;
let mut aux = PowAux::read::<_, B>(self.client.as_ref(), &parent_hash)?;
let (checked_header, difficulty, seal) = self.check_header(
header,
BlockId::Hash(parent_hash),
)?;
aux.difficulty = difficulty;
aux.total_difficulty.increment(difficulty);
if let Some(inner_body) = body.take() {
let block = B::new(checked_header.clone(), inner_body);
self.check_inherents(
block.clone(),
BlockId::Hash(parent_hash),
inherent_data,
timestamp_now
)?;
let (_, inner_body) = block.deconstruct();
body = Some(inner_body);
}
let key = aux_key(&hash);
let import_block = BlockImportParams {
origin,
header: checked_header,
post_digests: vec![seal],
body,
finalized: false,
justification,
auxiliary: vec![(key, Some(aux.encode()))],
fork_choice: ForkChoiceStrategy::Custom(aux.total_difficulty > best_aux.total_difficulty),
allow_missing_state: false,
};
Ok((import_block, None))
}
}
/// Register the PoW inherent data provider, if not registered already.
pub fn register_pow_inherent_data_provider(
inherent_data_providers: &InherentDataProviders,
) -> Result<(), consensus_common::Error> {
if !inherent_data_providers.has_provider(&paint_timestamp::INHERENT_IDENTIFIER) {
inherent_data_providers
.register_provider(paint_timestamp::InherentDataProvider)
.map_err(Into::into)
.map_err(consensus_common::Error::InherentData)
} else {
Ok(())
}
}
/// The PoW import queue type.
pub type PowImportQueue<B> = BasicQueue<B>;
/// Import queue for PoW engine.
pub fn import_queue<B, C, S, Algorithm>(
block_import: BoxBlockImport<B>,
client: Arc<C>,
algorithm: Algorithm,
check_inherents_after: <<B as BlockT>::Header as HeaderT>::Number,
select_chain: Option<S>,
inherent_data_providers: InherentDataProviders,
) -> Result<PowImportQueue<B>, consensus_common::Error> where
B: BlockT<Hash=H256>,
C: ProvideRuntimeApi + HeaderBackend<B> + BlockOf + ProvideCache<B> + AuxStore,
C: Send + Sync + AuxStore + 'static,
C::Api: BlockBuilderApi<B, Error = client_api::error::Error>,
Algorithm: PowAlgorithm<B> + Send + Sync + 'static,
S: SelectChain<B> + 'static,
{
register_pow_inherent_data_provider(&inherent_data_providers)?;
let verifier = PowVerifier::new(
client.clone(),
algorithm,
check_inherents_after,
select_chain,
inherent_data_providers,
);
Ok(BasicQueue::new(
verifier,
block_import,
None,
None
))
}
/// Start the background mining thread for PoW. Note that because PoW mining
/// is CPU-intensive, it is not possible to use an async future to define this.
/// However, it's not recommended to use background threads in the rest of the
/// codebase.
///
/// `preruntime` is a parameter that allows a custom additional pre-runtime
/// digest to be inserted for blocks being built. This can encode authorship
/// information, or just be a graffiti. `round` is for number of rounds the
/// CPU miner runs each time. This parameter should be tweaked so that each
/// mining round is within sub-second time.
pub fn start_mine<B: BlockT<Hash=H256>, C, Algorithm, E, SO, S>(
mut block_import: BoxBlockImport<B>,
client: Arc<C>,
algorithm: Algorithm,
mut env: E,
preruntime: Option<Vec<u8>>,
round: u32,
mut sync_oracle: SO,
build_time: std::time::Duration,
select_chain: Option<S>,
inherent_data_providers: inherents::InherentDataProviders,
) where
C: HeaderBackend<B> + AuxStore + 'static,
Algorithm: PowAlgorithm<B> + Send + Sync + 'static,
E: Environment<B> + Send + Sync + 'static,
E::Error: std::fmt::Debug,
SO: SyncOracle + Send + Sync + 'static,
S: SelectChain<B> + 'static,
{
if let Err(_) = register_pow_inherent_data_provider(&inherent_data_providers) {
warn!("Registering inherent data provider for timestamp failed");
}
thread::spawn(move || {
loop {
match mine_loop(
&mut block_import,
client.as_ref(),
&algorithm,
&mut env,
preruntime.as_ref(),
round,
&mut sync_oracle,
build_time.clone(),
select_chain.as_ref(),
&inherent_data_providers
) {
Ok(()) => (),
Err(e) => error!(
"Mining block failed with {:?}. Sleep for 1 second before restarting...",
e
),
}
std::thread::sleep(std::time::Duration::new(1, 0));
}
});
}
fn mine_loop<B: BlockT<Hash=H256>, C, Algorithm, E, SO, S>(
block_import: &mut BoxBlockImport<B>,
client: &C,
algorithm: &Algorithm,
env: &mut E,
preruntime: Option<&Vec<u8>>,
round: u32,
sync_oracle: &mut SO,
build_time: std::time::Duration,
select_chain: Option<&S>,
inherent_data_providers: &inherents::InherentDataProviders,
) -> Result<(), Error<B>> where
C: HeaderBackend<B> + AuxStore,
Algorithm: PowAlgorithm<B>,
E: Environment<B>,
E::Error: std::fmt::Debug,
SO: SyncOracle,
S: SelectChain<B>,
{
'outer: loop {
if sync_oracle.is_major_syncing() {
debug!(target: "pow", "Skipping proposal due to sync.");
std::thread::sleep(std::time::Duration::new(1, 0));
continue 'outer
}
let (best_hash, best_header) = match select_chain {
Some(select_chain) => {
let header = select_chain.best_chain()
.map_err(Error::BestHeaderSelectChain)?;
let hash = header.hash();
(hash, header)
},
None => {
let hash = client.info().best_hash;
let header = client.header(BlockId::Hash(hash))
.map_err(Error::BestHeader)?
.ok_or(Error::NoBestHeader)?;
(hash, header)
},
};
let mut aux = PowAux::read(client, &best_hash)?;
let mut proposer = env.init(&best_header)
.map_err(|e| Error::Environment(format!("{:?}", e)))?;
let inherent_data = inherent_data_providers
.create_inherent_data().map_err(Error::CreateInherents)?;
let mut inherent_digest = Digest::default();
if let Some(preruntime) = &preruntime {
inherent_digest.push(DigestItem::PreRuntime(POW_ENGINE_ID, preruntime.to_vec()));
}
let block = futures::executor::block_on(proposer.propose(
inherent_data,
inherent_digest,
build_time.clone(),
)).map_err(|e| Error::BlockProposingError(format!("{:?}", e)))?;
let (header, body) = block.deconstruct();
let (difficulty, seal) = {
let difficulty = algorithm.difficulty(
&BlockId::Hash(best_hash),
)?;
loop {
let seal = algorithm.mine(
&BlockId::Hash(best_hash),
&header.hash(),
difficulty,
round,
)?;
if let Some(seal) = seal {
break (difficulty, seal)
}
if best_hash != client.info().best_hash {
continue 'outer
}
}
};
aux.difficulty = difficulty;
aux.total_difficulty.increment(difficulty);
let hash = {
let mut header = header.clone();
header.digest_mut().push(DigestItem::Seal(POW_ENGINE_ID, seal.clone()));
header.hash()
};
let key = aux_key(&hash);
let best_hash = match select_chain {
Some(select_chain) => select_chain.best_chain()
.map_err(Error::BestHashSelectChain)?
.hash(),
None => client.info().best_hash,
};
let best_aux = PowAux::<Algorithm::Difficulty>::read(client, &best_hash)?;
// if the best block has changed in the meantime drop our proposal
if best_aux.total_difficulty > aux.total_difficulty {
continue 'outer
}
let import_block = BlockImportParams {
origin: BlockOrigin::Own,
header,
justification: None,
post_digests: vec![DigestItem::Seal(POW_ENGINE_ID, seal)],
body: Some(body),
finalized: false,
auxiliary: vec![(key, Some(aux.encode()))],
fork_choice: ForkChoiceStrategy::Custom(true),
allow_missing_state: false,
};
block_import.import_block(import_block, HashMap::default())
.map_err(|e| Error::BlockBuiltError(best_hash, e))?;
}
}
@@ -0,0 +1,23 @@
[package]
name = "substrate-consensus-slots"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Generic slots-based utilities for consensus"
edition = "2018"
build = "build.rs"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.0.0" }
client-api = { package = "substrate-client-api", path = "../../api" }
primitives = { package = "substrate-primitives", path = "../../../primitives/core" }
sr-primitives = { path = "../../../primitives/sr-primitives" }
substrate-telemetry = { path = "../../telemetry" }
consensus_common = { package = "substrate-consensus-common", path = "../../../primitives/consensus/common" }
inherents = { package = "substrate-inherents", path = "../../../primitives/inherents" }
futures-preview = "0.3.0-alpha.19"
futures-timer = "0.4.0"
parking_lot = "0.9.0"
log = "0.4.8"
[dev-dependencies]
test-client = { package = "substrate-test-runtime-client", path = "../../../test/utils/runtime/client" }
+23
View File
@@ -0,0 +1,23 @@
// 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 <http://www.gnu.org/licenses/>.
use std::env;
fn main() {
if let Ok(profile) = env::var("PROFILE") {
println!("cargo:rustc-cfg=build_type=\"{}\"", profile);
}
}
@@ -0,0 +1,265 @@
// 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 <http://www.gnu.org/licenses/>.
//! Schema for slots in the aux-db.
use codec::{Encode, Decode};
use client_api::backend::AuxStore;
use client_api::error::{Result as ClientResult, Error as ClientError};
use sr_primitives::traits::Header;
const SLOT_HEADER_MAP_KEY: &[u8] = b"slot_header_map";
const SLOT_HEADER_START: &[u8] = b"slot_header_start";
/// We keep at least this number of slots in database.
pub const MAX_SLOT_CAPACITY: u64 = 1000;
/// We prune slots when they reach this number.
pub const PRUNING_BOUND: u64 = 2 * MAX_SLOT_CAPACITY;
fn load_decode<C, T>(backend: &C, key: &[u8]) -> ClientResult<Option<T>>
where
C: AuxStore,
T: Decode,
{
match backend.get_aux(key)? {
None => Ok(None),
Some(t) => T::decode(&mut &t[..])
.map_err(
|e| ClientError::Backend(format!("Slots DB is corrupted. Decode error: {}", e.what())),
)
.map(Some)
}
}
/// Represents an equivocation proof.
#[derive(Debug, Clone)]
pub struct EquivocationProof<H> {
slot: u64,
fst_header: H,
snd_header: H,
}
impl<H> EquivocationProof<H> {
/// Get the slot number where the equivocation happened.
pub fn slot(&self) -> u64 {
self.slot
}
/// Get the first header involved in the equivocation.
pub fn fst_header(&self) -> &H {
&self.fst_header
}
/// Get the second header involved in the equivocation.
pub fn snd_header(&self) -> &H {
&self.snd_header
}
}
/// Checks if the header is an equivocation and returns the proof in that case.
///
/// Note: it detects equivocations only when slot_now - slot <= MAX_SLOT_CAPACITY.
pub fn check_equivocation<C, H, P>(
backend: &C,
slot_now: u64,
slot: u64,
header: &H,
signer: &P,
) -> ClientResult<Option<EquivocationProof<H>>>
where
H: Header,
C: AuxStore,
P: Clone + Encode + Decode + PartialEq,
{
// We don't check equivocations for old headers out of our capacity.
if slot_now - slot > MAX_SLOT_CAPACITY {
return Ok(None)
}
// Key for this slot.
let mut curr_slot_key = SLOT_HEADER_MAP_KEY.to_vec();
slot.using_encoded(|s| curr_slot_key.extend(s));
// Get headers of this slot.
let mut headers_with_sig = load_decode::<_, Vec<(H, P)>>(backend, &curr_slot_key[..])?
.unwrap_or_else(Vec::new);
// Get first slot saved.
let slot_header_start = SLOT_HEADER_START.to_vec();
let first_saved_slot = load_decode::<_, u64>(backend, &slot_header_start[..])?
.unwrap_or(slot);
for (prev_header, prev_signer) in headers_with_sig.iter() {
// A proof of equivocation consists of two headers:
// 1) signed by the same voter,
if prev_signer == signer {
// 2) with different hash
if header.hash() != prev_header.hash() {
return Ok(Some(EquivocationProof {
slot, // 3) and mentioning the same slot.
fst_header: prev_header.clone(),
snd_header: header.clone(),
}));
} else {
// We don't need to continue in case of duplicated header,
// since it's already saved and a possible equivocation
// would have been detected before.
return Ok(None)
}
}
}
let mut keys_to_delete = vec![];
let mut new_first_saved_slot = first_saved_slot;
if slot_now - first_saved_slot >= PRUNING_BOUND {
let prefix = SLOT_HEADER_MAP_KEY.to_vec();
new_first_saved_slot = slot_now.saturating_sub(MAX_SLOT_CAPACITY);
for s in first_saved_slot..new_first_saved_slot {
let mut p = prefix.clone();
s.using_encoded(|s| p.extend(s));
keys_to_delete.push(p);
}
}
headers_with_sig.push((header.clone(), signer.clone()));
backend.insert_aux(
&[
(&curr_slot_key[..], headers_with_sig.encode().as_slice()),
(&slot_header_start[..], new_first_saved_slot.encode().as_slice()),
],
&keys_to_delete.iter().map(|k| &k[..]).collect::<Vec<&[u8]>>()[..],
)?;
Ok(None)
}
#[cfg(test)]
mod test {
use primitives::{sr25519, Pair};
use primitives::hash::H256;
use sr_primitives::testing::{Header as HeaderTest, Digest as DigestTest};
use test_client;
use super::{MAX_SLOT_CAPACITY, PRUNING_BOUND, check_equivocation};
fn create_header(number: u64) -> HeaderTest {
// so that different headers for the same number get different hashes
let parent_hash = H256::random();
let header = HeaderTest {
parent_hash,
number,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: DigestTest { logs: vec![], },
};
header
}
#[test]
fn check_equivocation_works() {
let client = test_client::new();
let (pair, _seed) = sr25519::Pair::generate();
let public = pair.public();
let header1 = create_header(1); // @ slot 2
let header2 = create_header(2); // @ slot 2
let header3 = create_header(2); // @ slot 4
let header4 = create_header(3); // @ slot MAX_SLOT_CAPACITY + 4
let header5 = create_header(4); // @ slot MAX_SLOT_CAPACITY + 4
let header6 = create_header(3); // @ slot 4
// It's ok to sign same headers.
assert!(
check_equivocation(
&client,
2,
2,
&header1,
&public,
).unwrap().is_none(),
);
assert!(
check_equivocation(
&client,
3,
2,
&header1,
&public,
).unwrap().is_none(),
);
// But not two different headers at the same slot.
assert!(
check_equivocation(
&client,
4,
2,
&header2,
&public,
).unwrap().is_some(),
);
// Different slot is ok.
assert!(
check_equivocation(
&client,
5,
4,
&header3,
&public,
).unwrap().is_none(),
);
// Here we trigger pruning and save header 4.
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 2,
MAX_SLOT_CAPACITY + 4,
&header4,
&public,
).unwrap().is_none(),
);
// This fails because header 5 is an equivocation of header 4.
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 3,
MAX_SLOT_CAPACITY + 4,
&header5,
&public,
).unwrap().is_some(),
);
// This is ok because we pruned the corresponding header. Shows that we are pruning.
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 4,
4,
&header6,
&public,
).unwrap().is_none(),
);
}
}
+430
View File
@@ -0,0 +1,430 @@
// 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 <http://www.gnu.org/licenses/>.
//! Slots functionality for Substrate.
//!
//! Some consensus algorithms have a concept of *slots*, which are intervals in
//! time during which certain events can and/or must occur. This crate
//! provides generic functionality for slots.
#![deny(warnings)]
#![forbid(unsafe_code, missing_docs)]
mod slots;
mod aux_schema;
pub use slots::{SignedDuration, SlotInfo};
use slots::Slots;
pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
use codec::{Decode, Encode};
use consensus_common::{BlockImport, Proposer, SyncOracle, SelectChain};
use futures::{prelude::*, future::{self, Either}};
use futures_timer::Delay;
use inherents::{InherentData, InherentDataProviders};
use log::{debug, error, info, warn};
use sr_primitives::generic::BlockId;
use sr_primitives::traits::{ApiRef, Block as BlockT, Header, ProvideRuntimeApi};
use std::{fmt::Debug, ops::Deref, pin::Pin, sync::Arc};
use substrate_telemetry::{telemetry, CONSENSUS_DEBUG, CONSENSUS_WARN, CONSENSUS_INFO};
use parking_lot::Mutex;
use client_api;
/// A worker that should be invoked at every new slot.
pub trait SlotWorker<B: BlockT> {
/// The type of the future that will be returned when a new slot is
/// triggered.
type OnSlot: Future<Output = Result<(), consensus_common::Error>>;
/// Called when a new slot is triggered.
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo) -> Self::OnSlot;
}
/// A skeleton implementation for `SlotWorker` which tries to claim a slot at
/// its beginning and tries to produce a block if successfully claimed, timing
/// out if block production takes too long.
pub trait SimpleSlotWorker<B: BlockT> {
/// A handle to a `BlockImport`.
type BlockImport: BlockImport<B> + Send + 'static;
/// A handle to a `SyncOracle`.
type SyncOracle: SyncOracle;
/// The type of proposer to use to build blocks.
type Proposer: Proposer<B>;
/// Data associated with a slot claim.
type Claim: Send + 'static;
/// Epoch data necessary for authoring.
type EpochData;
/// The logging target to use when logging messages.
fn logging_target(&self) -> &'static str;
/// A handle to a `BlockImport`.
fn block_import(&self) -> Arc<Mutex<Self::BlockImport>>;
/// Returns the epoch data necessary for authoring. For time-dependent epochs,
/// use the provided slot number as a canonical source of time.
fn epoch_data(&self, header: &B::Header, slot_number: u64) -> Result<Self::EpochData, consensus_common::Error>;
/// Returns the number of authorities given the epoch data.
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize;
/// Tries to claim the given slot, returning an object with claim data if successful.
fn claim_slot(
&self,
header: &B::Header,
slot_number: u64,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim>;
/// Return the pre digest data to include in a block authored with the given claim.
fn pre_digest_data(&self, slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>>;
/// Returns a function which produces a `BlockImportParams`.
fn block_import_params(&self) -> Box<dyn Fn(
B::Header,
&B::Hash,
Vec<B::Extrinsic>,
Self::Claim,
) -> consensus_common::BlockImportParams<B> + Send>;
/// Whether to force authoring if offline.
fn force_authoring(&self) -> bool;
/// Returns a handle to a `SyncOracle`.
fn sync_oracle(&mut self) -> &mut Self::SyncOracle;
/// Returns a `Proposer` to author on top of the given block.
fn proposer(&mut self, block: &B::Header) -> Result<Self::Proposer, consensus_common::Error>;
/// Implements the `on_slot` functionality from `SlotWorker`.
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo)
-> Pin<Box<dyn Future<Output = Result<(), consensus_common::Error>> + Send>> where
Self: Send + Sync,
<Self::Proposer as Proposer<B>>::Create: Unpin + Send + 'static,
{
let (timestamp, slot_number, slot_duration) =
(slot_info.timestamp, slot_info.number, slot_info.duration);
let epoch_data = match self.epoch_data(&chain_head, slot_number) {
Ok(epoch_data) => epoch_data,
Err(err) => {
warn!("Unable to fetch epoch data at block {:?}: {:?}", chain_head.hash(), err);
telemetry!(
CONSENSUS_WARN; "slots.unable_fetching_authorities";
"slot" => ?chain_head.hash(),
"err" => ?err,
);
return Box::pin(future::ready(Ok(())));
}
};
let authorities_len = self.authorities_len(&epoch_data);
if !self.force_authoring() && self.sync_oracle().is_offline() && authorities_len > 1 {
debug!(target: self.logging_target(), "Skipping proposal slot. Waiting for the network.");
telemetry!(
CONSENSUS_DEBUG;
"slots.skipping_proposal_slot";
"authorities_len" => authorities_len,
);
return Box::pin(future::ready(Ok(())));
}
let claim = match self.claim_slot(&chain_head, slot_number, &epoch_data) {
None => return Box::pin(future::ready(Ok(()))),
Some(claim) => claim,
};
debug!(
target: self.logging_target(), "Starting authorship at slot {}; timestamp = {}",
slot_number,
timestamp,
);
telemetry!(CONSENSUS_DEBUG; "slots.starting_authorship";
"slot_num" => slot_number,
"timestamp" => timestamp,
);
let mut proposer = match self.proposer(&chain_head) {
Ok(proposer) => proposer,
Err(err) => {
warn!("Unable to author block in slot {:?}: {:?}", slot_number, err);
telemetry!(CONSENSUS_WARN; "slots.unable_authoring_block";
"slot" => slot_number, "err" => ?err
);
return Box::pin(future::ready(Ok(())));
},
};
let remaining_duration = slot_info.remaining_duration();
let logs = self.pre_digest_data(slot_number, &claim);
// deadline our production to approx. the end of the slot
let proposal_work = futures::future::select(
proposer.propose(
slot_info.inherent_data,
sr_primitives::generic::Digest {
logs,
},
remaining_duration,
).map_err(|e| consensus_common::Error::ClientImport(format!("{:?}", e))),
Delay::new(remaining_duration)
.map_err(consensus_common::Error::FaultyTimer)
).map(|v| match v {
futures::future::Either::Left((b, _)) => b.map(|b| (b, claim)),
futures::future::Either::Right((Ok(_), _)) =>
Err(consensus_common::Error::ClientImport("Timeout in the Slots proposer".into())),
futures::future::Either::Right((Err(err), _)) => Err(err),
});
let block_import_params_maker = self.block_import_params();
let block_import = self.block_import();
let logging_target = self.logging_target();
Box::pin(proposal_work.map_ok(move |(block, claim)| {
// 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!("Discarding proposal for slot {}; block production took too long", slot_number);
// If the node was compiled with debug, tell the user to use release optimizations.
#[cfg(build_type="debug")]
info!("Recompile your node in `--release` mode to mitigate this problem.");
telemetry!(CONSENSUS_INFO; "slots.discarding_proposal_took_too_long";
"slot" => slot_number,
);
return;
}
let (header, body) = block.deconstruct();
let header_num = *header.number();
let header_hash = header.hash();
let parent_hash = *header.parent_hash();
let block_import_params = block_import_params_maker(
header,
&header_hash,
body,
claim,
);
info!("Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
header_num,
block_import_params.post_header().hash(),
header_hash,
);
telemetry!(CONSENSUS_INFO; "slots.pre_sealed_block";
"header_num" => ?header_num,
"hash_now" => ?block_import_params.post_header().hash(),
"hash_previously" => ?header_hash,
);
if let Err(err) = block_import.lock().import_block(block_import_params, Default::default()) {
warn!(target: logging_target,
"Error with block built on {:?}: {:?}",
parent_hash,
err,
);
telemetry!(CONSENSUS_WARN; "slots.err_with_block_built_on";
"hash" => ?parent_hash, "err" => ?err,
);
}
}))
}
}
/// Slot compatible inherent data.
pub trait SlotCompatible {
/// Extract timestamp and slot from inherent data.
fn extract_timestamp_and_slot(
&self,
inherent: &InherentData,
) -> Result<(u64, u64, std::time::Duration), consensus_common::Error>;
/// Get the difference between chain time and local time. Defaults to
/// always returning zero.
fn time_offset() -> SignedDuration { Default::default() }
}
/// Start a new slot worker.
///
/// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is
/// polled until completion, unless we are major syncing.
pub fn start_slot_worker<B, C, W, T, SO, SC>(
slot_duration: SlotDuration<T>,
client: C,
mut worker: W,
mut sync_oracle: SO,
inherent_data_providers: InherentDataProviders,
timestamp_extractor: SC,
) -> impl Future<Output = ()>
where
B: BlockT,
C: SelectChain<B> + Clone,
W: SlotWorker<B>,
W::OnSlot: Unpin,
SO: SyncOracle + Send + Clone,
SC: SlotCompatible + Unpin,
T: SlotData + Clone,
{
let SlotDuration(slot_duration) = slot_duration;
// rather than use a timer interval, we schedule our waits ourselves
Slots::<SC>::new(
slot_duration.slot_duration(),
inherent_data_providers,
timestamp_extractor,
).inspect_err(|e| debug!(target: "slots", "Faulty timer: {:?}", e))
.try_for_each(move |slot_info| {
// only propose when we are not syncing.
if sync_oracle.is_major_syncing() {
debug!(target: "slots", "Skipping proposal slot due to sync.");
return Either::Right(future::ready(Ok(())));
}
let slot_num = slot_info.number;
let chain_head = match client.best_chain() {
Ok(x) => x,
Err(e) => {
warn!(target: "slots", "Unable to author block in slot {}. \
no best block header: {:?}", slot_num, e);
return Either::Right(future::ready(Ok(())));
}
};
Either::Left(worker.on_slot(chain_head, slot_info).map_err(
|e| {
warn!(target: "slots", "Encountered consensus error: {:?}", e);
}).or_else(|_| future::ready(Ok(())))
)
}).then(|res| {
if let Err(err) = res {
warn!(target: "slots", "Slots stream terminated with an error: {:?}", err);
}
future::ready(())
})
}
/// A header which has been checked
pub enum CheckedHeader<H, S> {
/// A header which has slot in the future. this is the full header (not stripped)
/// and the slot in which it should be processed.
Deferred(H, u64),
/// A header which is fully checked, including signature. This is the pre-header
/// accompanied by the seal components.
///
/// Includes the digest item that encoded the seal.
Checked(H, S),
}
/// A type from which a slot duration can be obtained.
pub trait SlotData {
/// Gets the slot duration.
fn slot_duration(&self) -> u64;
/// The static slot key
const SLOT_KEY: &'static [u8];
}
impl SlotData for u64 {
fn slot_duration(&self) -> u64 {
*self
}
const SLOT_KEY: &'static [u8] = b"aura_slot_duration";
}
/// A slot duration. Create with `get_or_compute`.
// The internal member should stay private here to maintain invariants of
// `get_or_compute`.
#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct SlotDuration<T>(T);
impl<T> Deref for SlotDuration<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: SlotData + Clone> SlotData for SlotDuration<T> {
/// Get the slot duration in milliseconds.
fn slot_duration(&self) -> u64
where T: SlotData,
{
self.0.slot_duration()
}
const SLOT_KEY: &'static [u8] = T::SLOT_KEY;
}
impl<T: Clone> SlotDuration<T> {
/// Either fetch the slot duration from disk or compute it from the
/// genesis state.
///
/// `slot_key` is marked as `'static`, as it should really be a
/// compile-time constant.
pub fn get_or_compute<B: BlockT, C, CB>(client: &C, cb: CB) -> client_api::error::Result<Self> where
C: client_api::backend::AuxStore,
C: ProvideRuntimeApi,
CB: FnOnce(ApiRef<C::Api>, &BlockId<B>) -> client_api::error::Result<T>,
T: SlotData + Encode + Decode + Debug,
{
match client.get_aux(T::SLOT_KEY)? {
Some(v) => <T as codec::Decode>::decode(&mut &v[..])
.map(SlotDuration)
.map_err(|_| {
client_api::error::Error::Backend({
error!(target: "slots", "slot duration kept in invalid format");
"slot duration kept in invalid format".to_string()
})
}),
None => {
use sr_primitives::traits::Zero;
let genesis_slot_duration =
cb(client.runtime_api(), &BlockId::number(Zero::zero()))?;
info!(
"Loaded block-time = {:?} milliseconds from genesis on first-launch",
genesis_slot_duration
);
genesis_slot_duration
.using_encoded(|s| client.insert_aux(&[(T::SLOT_KEY, &s[..])], &[]))?;
Ok(SlotDuration(genesis_slot_duration))
}
}
}
/// Returns slot data value.
pub fn get(&self) -> T {
self.0.clone()
}
}
@@ -0,0 +1,176 @@
// 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 <http://www.gnu.org/licenses/>.
//! Utility stream for yielding slots in a loop.
//!
//! This is used instead of `futures_timer::Interval` because it was unreliable.
use super::SlotCompatible;
use consensus_common::Error;
use futures::{prelude::*, task::Context, task::Poll};
use inherents::{InherentData, InherentDataProviders};
use std::{pin::Pin, time::{Duration, Instant}};
use futures_timer::Delay;
/// Returns current duration since unix epoch.
pub fn duration_now() -> Duration {
use std::time::SystemTime;
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| panic!(
"Current time {:?} is before unix epoch. Something is wrong: {:?}",
now,
e,
))
}
/// A `Duration` with a sign (before or after). Immutable.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SignedDuration {
offset: Duration,
is_positive: bool,
}
impl SignedDuration {
/// Construct a `SignedDuration`
pub fn new(offset: Duration, is_positive: bool) -> Self {
Self { offset, is_positive }
}
/// Get the slot for now. Panics if `slot_duration` is 0.
pub fn slot_now(&self, slot_duration: u64) -> u64 {
(if self.is_positive {
duration_now() + self.offset
} else {
duration_now() - self.offset
}.as_millis() as u64) / slot_duration
}
}
/// Returns the duration until the next slot, based on current duration since
pub fn time_until_next(now: Duration, slot_duration: u64) -> Duration {
let remaining_full_millis = slot_duration - (now.as_millis() as u64 % slot_duration) - 1;
Duration::from_millis(remaining_full_millis)
}
/// Information about a slot.
pub struct SlotInfo {
/// The slot number.
pub number: u64,
/// Current timestamp.
pub timestamp: u64,
/// The instant at which the slot ends.
pub ends_at: Instant,
/// The inherent data.
pub inherent_data: InherentData,
/// Slot duration.
pub duration: u64,
}
impl SlotInfo {
/// Yields the remaining duration in the slot.
pub fn remaining_duration(&self) -> Duration {
let now = Instant::now();
if now < self.ends_at {
self.ends_at.duration_since(now)
} else {
Duration::from_millis(0)
}
}
}
/// A stream that returns every time there is a new slot.
pub(crate) struct Slots<SC> {
last_slot: u64,
slot_duration: u64,
inner_delay: Option<Delay>,
inherent_data_providers: InherentDataProviders,
timestamp_extractor: SC,
}
impl<SC> Slots<SC> {
/// Create a new `Slots` stream.
pub fn new(
slot_duration: u64,
inherent_data_providers: InherentDataProviders,
timestamp_extractor: SC,
) -> Self {
Slots {
last_slot: 0,
slot_duration,
inner_delay: None,
inherent_data_providers,
timestamp_extractor,
}
}
}
impl<SC: SlotCompatible + Unpin> Stream for Slots<SC> {
type Item = Result<SlotInfo, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
loop {
let slot_duration = self.slot_duration;
self.inner_delay = match self.inner_delay.take() {
None => {
// schedule wait.
let wait_dur = time_until_next(duration_now(), slot_duration);
Some(Delay::new(wait_dur))
}
Some(d) => Some(d),
};
if let Some(ref mut inner_delay) = self.inner_delay {
match Future::poll(Pin::new(inner_delay), cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(Error::FaultyTimer(err)))),
Poll::Ready(Ok(())) => {}
}
}
// timeout has fired.
let inherent_data = match self.inherent_data_providers.create_inherent_data() {
Ok(id) => id,
Err(err) => return Poll::Ready(Some(Err(consensus_common::Error::InherentData(err)))),
};
let result = self.timestamp_extractor.extract_timestamp_and_slot(&inherent_data);
let (timestamp, slot_num, offset) = match result {
Ok(v) => v,
Err(err) => return Poll::Ready(Some(Err(err))),
};
// reschedule delay for next slot.
let ends_in = offset +
time_until_next(Duration::from_millis(timestamp), slot_duration);
let ends_at = Instant::now() + ends_in;
self.inner_delay = Some(Delay::new(ends_in));
// never yield the same slot twice.
if slot_num > self.last_slot {
self.last_slot = slot_num;
break Poll::Ready(Some(Ok(SlotInfo {
number: slot_num,
duration: self.slot_duration,
timestamp,
ends_at,
inherent_data,
})))
}
}
}
}
@@ -0,0 +1,15 @@
[package]
name = "substrate-consensus-uncles"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Generic uncle inclusion utilities for consensus"
edition = "2018"
[dependencies]
client-api = { package = "substrate-client-api", path = "../../api" }
primitives = { package = "substrate-primitives", path = "../../../primitives/core" }
sr-primitives = { path = "../../../primitives/sr-primitives" }
paint-authorship = { path = "../../../paint/authorship" }
consensus_common = { package = "substrate-consensus-common", path = "../../../primitives/consensus/common" }
inherents = { package = "substrate-inherents", path = "../../../primitives/inherents" }
log = "0.4.8"
@@ -0,0 +1,66 @@
// 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 <http://www.gnu.org/licenses/>.
//! Uncles functionality for Substrate.
//!
#![deny(warnings)]
#![forbid(unsafe_code, missing_docs)]
use consensus_common::SelectChain;
use inherents::{InherentDataProviders};
use log::warn;
use client_api::ProvideUncles;
use sr_primitives::traits::{Block as BlockT, Header};
use std::sync::Arc;
/// Maximum uncles generations we may provide to the runtime.
const MAX_UNCLE_GENERATIONS: u32 = 8;
/// Register uncles inherent data provider, if not registered already.
pub fn register_uncles_inherent_data_provider<B, C, SC>(
client: Arc<C>,
select_chain: SC,
inherent_data_providers: &InherentDataProviders,
) -> Result<(), consensus_common::Error> where
B: BlockT,
C: ProvideUncles<B> + Send + Sync + 'static,
SC: SelectChain<B> + 'static,
{
if !inherent_data_providers.has_provider(&paint_authorship::INHERENT_IDENTIFIER) {
inherent_data_providers
.register_provider(paint_authorship::InherentDataProvider::new(move || {
{
let chain_head = match select_chain.best_chain() {
Ok(x) => x,
Err(e) => {
warn!(target: "uncles", "Unable to get chain head: {:?}", e);
return Vec::new();
}
};
match client.uncles(chain_head.hash(), MAX_UNCLE_GENERATIONS.into()) {
Ok(uncles) => uncles,
Err(e) => {
warn!(target: "uncles", "Unable to get uncles: {:?}", e);
Vec::new()
}
}
}
}))
.map_err(|err| consensus_common::Error::InherentData(err.into()))?;
}
Ok(())
}