mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-18 07:11:03 +00:00
Introduce inherent digests (#2466)
* Introduce inherent digests * Implement inherent digests * fix silly error * Implementation of inherent digests in BABE All tests pass. There are still limitations: 1. The runtime strips out inherent digests, so BABE must re-add them. 2. The test runtime checks that it can re-compute all digests. It can’t, so I had to comment out that test. * Fix compilation and seal import Seals were not imported correctly: the pre-digest was imported twice, instead of both it and the seal being imported. Also, other parts of the code did not compile due to incomplete refactoring. * Remove bogus assertion * Fix testsuite compilation * Remove unused import * Fix compiler diagnostics * Add inherent digest parameters to block constructors This enforces that inherent digests are added first. * Fixup Cargo.lock * Fix build errors * Re-add an incorrectly removed import * Bump primitive-types version * Update Cargo.lock * Refactoring * Use inherent digests for AuRa They do reach the runtime, but get stripped. I have not figured out where. * Fix compilation errors * Fix compilation errors due to incorrect types * Fix whitespace Suggested-by: Tomasz Drwiega <tomasz@parity.io> * Add preamble Suggested-by: Tomasz Drwiega <tomasz@parity.io> * Fix silly compile error * Refactor pre-digest finding code into a separate function * Remove unwanted assertion It is too likely to bring down the entire blockchain. Suggested-by: Tomasz Drwiega <tomasz@parity.io> * Use `find_pre_digest` after runtime, too Also, use `Member` trait rather than rolling our own requirements. Suggested-by: Tomasz Drwiega <tomasz@parity.io> * Fix various warnings mostly due to upgrading the dependency on `error_chain`. * Pre-digests nearly complete This nearly completes the implementation of pre-runtime digests. * `Seal2` → `Seal` and fix test suite * Try to fix the storage error * Try to fix storage (again) * Fix tests * Hopefully finish pre-runtime digests The key is to pass *only* the pre-runtime digests to the runtime. The others must be stripped out by `initialize_block`. * Fix silly typo * Fix another silly mistake * Remove unnecessary filtering of BABE pre-digests We no longer get duplicate BABE pre-digests, so if they appear, the header should be rejected outright. * Update Cargo.lock files * Reformatting * Fix silly typo in inherent digest code Also, revert `error.rs` files that contained calls to the `error_chain!` macro. * Try to keep the runtime from stripping pre-digests Currently runs into the “Storage root must match that calculated” assertion. * Don’t compute storage root until storage changes are done. Also, fix a compilation error. * Fix compile-time error * Fix compilation errors * Fix more compile errors * Hopefully it compiles this time… * Fix compilation and add docs * Prevent BABE from adding duplicate pre-runtime digests Found by comparing with the AuRa code. I also did some refactoring. * Respond to review and fix some warnings * Delete some dead code introduced earlier * More dead code goes away * `ref mut` → `&mut` * Respond to review and fix some warnings * Fix compilation error * Remove unneeded `HashT` type parameter Suggested-by: Robert Habermeier <robert@parity.io> * Remove spurious #[allow(deprecated)] * Document inherent digest parameter to `build_block` * Delete `Simple` trait It wasn’t needed * delete wrongly added files * Fix trait bounds * Digest serialization tests I also did some reformatting and cleanup. * Apply suggestions from code review Reformatting Co-Authored-By: André Silva <andre.beat@gmail.com> * Swap two arguments to `propose` and `propose_with` Also, remove some needless unsafe code. * Remove bogus `#![allow(deprecated)]` annotations With the removal of the deprecated `Seal` variant, these are not needed. * Add a missing `#[allow(deprecated)]` in the AuRa tests * Fix silly compile error * Fix silly compiler error RLS did not tell me that I hadn’t fixed `babe/lib.rs`, so I missed it. * Fixes made automatically by Cargo
This commit is contained in:
committed by
Gavin Wood
parent
e9a4c80c40
commit
c7d1204ce5
@@ -0,0 +1,67 @@
|
||||
// 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 runtime_primitives::generic::DigestItem;
|
||||
use parity_codec::{Encode, Decode};
|
||||
|
||||
type Signature<P> = <P as Pair>::Signature;
|
||||
|
||||
/// A digest item which is usable with aura consensus.
|
||||
pub trait CompatibleDigestItem<T: Pair>: Sized {
|
||||
/// Construct a digest item which contains a signature on the hash.
|
||||
fn aura_seal(signature: Signature<T>) -> Self;
|
||||
|
||||
/// If this item is an Aura seal, return the signature.
|
||||
fn as_aura_seal(&self) -> Option<&Signature<T>>;
|
||||
|
||||
/// 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, P::Public, P::Signature>
|
||||
where P: Pair, P::Signature: Clone + Encode + Decode,
|
||||
{
|
||||
fn aura_seal(signature: Signature<P>) -> Self {
|
||||
DigestItem::Seal(AURA_ENGINE_ID, signature)
|
||||
}
|
||||
|
||||
fn as_aura_seal(&self) -> Option<&Signature<P>> {
|
||||
match self {
|
||||
DigestItem::Seal(AURA_ENGINE_ID, ref sig) => Some(sig),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn aura_pre_digest(slot_num: u64) -> Self {
|
||||
DigestItem::PreRuntime(AURA_ENGINE_ID, slot_num.encode())
|
||||
}
|
||||
|
||||
fn as_aura_pre_digest(&self) -> Option<u64> {
|
||||
match self {
|
||||
DigestItem::PreRuntime(AURA_ENGINE_ID, ref buffer) => Decode::decode(&mut &buffer[..]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,18 +44,20 @@ use client::{
|
||||
error::Result as CResult,
|
||||
backend::AuxStore,
|
||||
};
|
||||
use aura_primitives::AURA_ENGINE_ID;
|
||||
use runtime_primitives::{generic, generic::BlockId, Justification};
|
||||
|
||||
use runtime_primitives::{generic::{self, BlockId}, Justification};
|
||||
use runtime_primitives::traits::{
|
||||
Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, AuthorityIdFor, Zero,
|
||||
Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, AuthorityIdFor,
|
||||
Zero, Member,
|
||||
};
|
||||
|
||||
use primitives::Pair;
|
||||
use inherents::{InherentDataProviders, InherentData};
|
||||
use authorities::AuthoritiesApi;
|
||||
|
||||
use futures::{Future, IntoFuture, future, stream::Stream};
|
||||
use futures::{Future, IntoFuture, future};
|
||||
use tokio::timer::Timeout;
|
||||
use log::{warn, debug, info, trace};
|
||||
use log::{error, warn, debug, info, trace};
|
||||
|
||||
use srml_aura::{
|
||||
InherentType as AuraInherent, AuraInherentData,
|
||||
@@ -68,24 +70,10 @@ use slots::{CheckedHeader, SlotWorker, SlotInfo, SlotCompatible, slot_now, check
|
||||
pub use aura_primitives::*;
|
||||
pub use consensus_common::{SyncOracle, ExtraVerification};
|
||||
|
||||
mod digest;
|
||||
use digest::CompatibleDigestItem;
|
||||
|
||||
type AuthorityId<P> = <P as Pair>::Public;
|
||||
type Signature<P> = <P as Pair>::Signature;
|
||||
|
||||
/// A handle to the network. This is generally implemented by providing some
|
||||
/// handle to a gossip service or similar.
|
||||
///
|
||||
/// Intended to be a lightweight handle such as an `Arc`.
|
||||
#[deprecated(
|
||||
since = "1.0.1",
|
||||
note = "This is dead code and will be removed in a future release",
|
||||
)]
|
||||
pub trait Network: Clone {
|
||||
/// A stream of input messages for a topic.
|
||||
type In: Stream<Item=Vec<u8>,Error=()>;
|
||||
|
||||
/// Send a message at a specific round out.
|
||||
fn send_message(&self, slot: u64, message: Vec<u8>);
|
||||
}
|
||||
|
||||
/// A slot duration. Create with `get_or_compute`.
|
||||
#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||
@@ -122,48 +110,6 @@ fn slot_author<P: Pair>(slot_num: u64, authorities: &[AuthorityId<P>]) -> Option
|
||||
Some(current_author)
|
||||
}
|
||||
|
||||
/// A digest item which is usable with aura consensus.
|
||||
pub trait CompatibleDigestItem<T: Pair>: Sized {
|
||||
/// Construct a digest item which contains a slot number and a signature on the
|
||||
/// hash.
|
||||
fn aura_seal(slot_num: u64, signature: Signature<T>) -> Self;
|
||||
|
||||
/// If this item is an Aura seal, return the slot number and signature.
|
||||
fn as_aura_seal(&self) -> Option<(u64, Signature<T>)>;
|
||||
|
||||
/// Return `true` if this seal type is deprecated. Otherwise, return
|
||||
/// `false`.
|
||||
fn is_deprecated(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<P, Hash> CompatibleDigestItem<P> for generic::DigestItem<Hash, P::Public, P::Signature>
|
||||
where P: Pair, P::Signature: Clone + Encode + Decode,
|
||||
{
|
||||
/// Construct a digest item which is a slot number and a signature on the
|
||||
/// hash.
|
||||
fn aura_seal(slot_number: u64, signature: Signature<P>) -> Self {
|
||||
generic::DigestItem::Consensus(AURA_ENGINE_ID, (slot_number, signature).encode())
|
||||
}
|
||||
|
||||
/// If this item is an Aura seal, return the slot number and signature.
|
||||
#[allow(deprecated)]
|
||||
fn as_aura_seal(&self) -> Option<(u64, Signature<P>)> {
|
||||
match self {
|
||||
generic::DigestItem::Seal(slot, ref sig) => Some((*slot, (*sig).clone())),
|
||||
generic::DigestItem::Consensus(AURA_ENGINE_ID, seal) => Decode::decode(&mut &seal[..]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn is_deprecated(&self) -> bool {
|
||||
match self {
|
||||
generic::DigestItem::Seal(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct AuraSlotCompatible;
|
||||
|
||||
@@ -178,60 +124,8 @@ impl SlotCompatible for AuraSlotCompatible {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the aura worker in a separate thread.
|
||||
#[deprecated(since = "1.1", note = "Please spawn a thread manually")]
|
||||
pub fn start_aura_thread<B, C, SC, E, I, P, SO, Error, OnExit>(
|
||||
slot_duration: SlotDuration,
|
||||
local_key: Arc<P>,
|
||||
client: Arc<C>,
|
||||
select_chain: SC,
|
||||
block_import: Arc<I>,
|
||||
env: Arc<E>,
|
||||
sync_oracle: SO,
|
||||
on_exit: OnExit,
|
||||
inherent_data_providers: InherentDataProviders,
|
||||
force_authoring: bool,
|
||||
) -> Result<(), consensus_common::Error> where
|
||||
B: Block + 'static,
|
||||
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore + Send + Sync + 'static,
|
||||
C::Api: AuthoritiesApi<B>,
|
||||
SC: SelectChain<B> + Clone + 'static,
|
||||
E: Environment<B, Error=Error> + Send + Sync + 'static,
|
||||
E::Proposer: Proposer<B, Error=Error> + Send + 'static,
|
||||
<<E::Proposer as Proposer<B>>::Create as IntoFuture>::Future: Send + 'static,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
Error: From<I::Error> + 'static,
|
||||
P: Pair + Send + Sync + 'static,
|
||||
P::Public: Encode + Decode + Eq + Clone + Debug + Hash + Send + Sync + 'static,
|
||||
P::Signature: Encode,
|
||||
SO: SyncOracle + Send + Sync + Clone + 'static,
|
||||
OnExit: Future<Item=(), Error=()> + Send + 'static,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>> + 'static,
|
||||
Error: ::std::error::Error + Send + From<::consensus_common::Error> + 'static,
|
||||
{
|
||||
let worker = AuraWorker {
|
||||
client: client.clone(),
|
||||
block_import,
|
||||
env,
|
||||
local_key,
|
||||
inherent_data_providers: inherent_data_providers.clone(),
|
||||
sync_oracle: sync_oracle.clone(),
|
||||
force_authoring,
|
||||
};
|
||||
|
||||
#[allow(deprecated)] // The function we are in is also deprecated.
|
||||
slots::start_slot_worker_thread::<_, _, _, _, AuraSlotCompatible, u64, _>(
|
||||
slot_duration.0,
|
||||
select_chain,
|
||||
Arc::new(worker),
|
||||
sync_oracle,
|
||||
on_exit,
|
||||
inherent_data_providers
|
||||
)
|
||||
}
|
||||
|
||||
/// Start the aura worker. The returned future should be run in a tokio runtime.
|
||||
pub fn start_aura<B, C, SC, E, I, P, SO, Error, OnExit>(
|
||||
pub fn start_aura<B, C, SC, E, I, P, SO, Error, OnExit, H>(
|
||||
slot_duration: SlotDuration,
|
||||
local_key: Arc<P>,
|
||||
client: Arc<C>,
|
||||
@@ -243,20 +137,25 @@ pub fn start_aura<B, C, SC, E, I, P, SO, Error, OnExit>(
|
||||
inherent_data_providers: InherentDataProviders,
|
||||
force_authoring: bool,
|
||||
) -> Result<impl Future<Item=(), Error=()>, consensus_common::Error> where
|
||||
B: Block,
|
||||
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore,
|
||||
B: Block<Header=H>,
|
||||
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore + Send + Sync,
|
||||
C::Api: AuthoritiesApi<B>,
|
||||
SC: SelectChain<B> + Clone,
|
||||
E: Environment<B, Error=Error>,
|
||||
SC: SelectChain<B>,
|
||||
generic::DigestItem<B::Hash, P::Public, P::Signature>: DigestItem<Hash=B::Hash>,
|
||||
E::Proposer: Proposer<B, Error=Error>,
|
||||
<<E::Proposer as Proposer<B>>::Create as IntoFuture>::Future: Send + 'static,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
P: Pair + Send + Sync + 'static,
|
||||
P::Public: Hash + Eq + Send + Sync + Clone + Debug + Encode + Decode + 'static,
|
||||
P::Signature: Encode,
|
||||
SO: SyncOracle + Send + Sync + Clone,
|
||||
P::Public: Hash + Member + Encode + Decode,
|
||||
P::Signature: Hash + Member + Encode + Decode,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
|
||||
H: Header<
|
||||
Digest=generic::Digest<generic::DigestItem<B::Hash, P::Public, P::Signature>>,
|
||||
Hash=B::Hash,
|
||||
>,
|
||||
E: Environment<B, Error=Error>,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
|
||||
SO: SyncOracle + Send + Sync + Clone,
|
||||
OnExit: Future<Item=(), Error=()>,
|
||||
{
|
||||
let worker = AuraWorker {
|
||||
@@ -288,18 +187,23 @@ struct AuraWorker<C, E, I, P, SO> {
|
||||
force_authoring: bool,
|
||||
}
|
||||
|
||||
impl<B: Block, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, SO> where
|
||||
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore,
|
||||
impl<H, B, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, SO> where
|
||||
B: Block<Header=H>,
|
||||
C: ProvideRuntimeApi + ProvideCache<B> + Sync,
|
||||
C::Api: AuthoritiesApi<B>,
|
||||
E: Environment<B, Error=Error>,
|
||||
E::Proposer: Proposer<B, Error=Error>,
|
||||
<<E::Proposer as Proposer<B>>::Create as IntoFuture>::Future: Send + 'static,
|
||||
H: Header<
|
||||
Digest=generic::Digest<generic::DigestItem<B::Hash, P::Public, P::Signature>>,
|
||||
Hash=B::Hash,
|
||||
>,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
P: Pair + Send + Sync + 'static,
|
||||
P::Public: Hash + Eq + Send + Sync + Clone + Debug + Encode + Decode + 'static,
|
||||
P::Signature: Encode,
|
||||
P::Public: Member + Encode + Decode + Hash,
|
||||
P::Signature: Member + Encode + Decode + Hash + Debug,
|
||||
SO: SyncOracle + Send + Clone,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>, Hash=B::Hash>,
|
||||
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
|
||||
{
|
||||
type OnSlot = Box<Future<Item=(), Error=consensus_common::Error> + Send>;
|
||||
@@ -376,7 +280,15 @@ impl<B: Block, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, S
|
||||
// deadline our production to approx. the end of the
|
||||
// slot
|
||||
Timeout::new(
|
||||
proposer.propose(slot_info.inherent_data, remaining_duration).into_future(),
|
||||
proposer.propose(
|
||||
slot_info.inherent_data,
|
||||
generic::Digest {
|
||||
logs: vec![
|
||||
<DigestItemFor<B> as CompatibleDigestItem<P>>::aura_pre_digest(slot_num),
|
||||
],
|
||||
},
|
||||
remaining_duration,
|
||||
).into_future(),
|
||||
remaining_duration,
|
||||
)
|
||||
} else {
|
||||
@@ -384,72 +296,98 @@ impl<B: Block, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, S
|
||||
}
|
||||
};
|
||||
|
||||
Box::new(
|
||||
proposal_work
|
||||
.map(move |b| {
|
||||
// minor hack since we don't have access to the timestamp
|
||||
// that is actually set by the proposer.
|
||||
let slot_after_building = slot_now(slot_duration);
|
||||
if slot_after_building != Some(slot_num) {
|
||||
info!(
|
||||
"Discarding proposal for slot {}; block production took too long",
|
||||
slot_num
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "aura.discarding_proposal_took_too_long";
|
||||
"slot" => slot_num
|
||||
);
|
||||
return
|
||||
}
|
||||
Box::new(proposal_work.map(move |b| {
|
||||
// minor hack since we don't have access to the timestamp
|
||||
// that is actually set by the proposer.
|
||||
let slot_after_building = slot_now(slot_duration);
|
||||
if slot_after_building != Some(slot_num) {
|
||||
info!(
|
||||
"Discarding proposal for slot {}; block production took too long",
|
||||
slot_num
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "aura.discarding_proposal_took_too_long";
|
||||
"slot" => slot_num
|
||||
);
|
||||
return
|
||||
}
|
||||
|
||||
let (header, body) = b.deconstruct();
|
||||
let header_num = header.number().clone();
|
||||
let pre_hash = header.hash();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
let (header, body) = b.deconstruct();
|
||||
let pre_digest: Result<u64, String> = find_pre_digest::<B, P>(&header);
|
||||
if let Err(e) = pre_digest {
|
||||
error!(target: "aura", "FATAL ERROR: Invalid pre-digest: {}!", e);
|
||||
return
|
||||
} else {
|
||||
trace!(target: "aura", "Got correct number of seals. Good!")
|
||||
};
|
||||
|
||||
// sign the pre-sealed hash of the block and then
|
||||
// add it to a digest item.
|
||||
let to_sign = (slot_num, pre_hash).encode();
|
||||
let signature = pair.sign(&to_sign[..]);
|
||||
let item = <DigestItemFor<B> as CompatibleDigestItem<P>>::aura_seal(
|
||||
slot_num,
|
||||
signature,
|
||||
);
|
||||
let header_num = header.number().clone();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
|
||||
let import_block: ImportBlock<B> = ImportBlock {
|
||||
origin: BlockOrigin::Own,
|
||||
header,
|
||||
justification: None,
|
||||
post_digests: vec![item],
|
||||
body: Some(body),
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
};
|
||||
// sign the pre-sealed hash of the block and then
|
||||
// add it to a digest item.
|
||||
let header_hash = header.hash();
|
||||
let signature = pair.sign(header_hash.as_ref());
|
||||
let signature_digest_item = <DigestItemFor<B> as CompatibleDigestItem<P>>::aura_seal(signature);
|
||||
|
||||
info!("Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
|
||||
header_num,
|
||||
import_block.post_header().hash(),
|
||||
pre_hash
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "aura.pre_sealed_block";
|
||||
"header_num" => ?header_num,
|
||||
"hash_now" => ?import_block.post_header().hash(),
|
||||
"hash_previously" => ?pre_hash
|
||||
);
|
||||
let import_block: ImportBlock<B> = ImportBlock {
|
||||
origin: BlockOrigin::Own,
|
||||
header,
|
||||
justification: None,
|
||||
post_digests: vec![signature_digest_item],
|
||||
body: Some(body),
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
};
|
||||
|
||||
if let Err(e) = block_import.import_block(import_block, Default::default()) {
|
||||
warn!(target: "aura", "Error with block built on {:?}: {:?}",
|
||||
parent_hash, e);
|
||||
telemetry!(CONSENSUS_WARN; "aura.err_with_block_built_on";
|
||||
"hash" => ?parent_hash, "err" => ?e
|
||||
);
|
||||
}
|
||||
})
|
||||
.map_err(|e| consensus_common::Error::ClientImport(format!("{:?}", e)).into())
|
||||
)
|
||||
info!("Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
|
||||
header_num,
|
||||
import_block.post_header().hash(),
|
||||
header_hash
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "aura.pre_sealed_block";
|
||||
"header_num" => ?header_num,
|
||||
"hash_now" => ?import_block.post_header().hash(),
|
||||
"hash_previously" => ?header_hash
|
||||
);
|
||||
|
||||
if let Err(e) = block_import.import_block(import_block, Default::default()) {
|
||||
warn!(target: "aura", "Error with block built on {:?}: {:?}",
|
||||
parent_hash, e);
|
||||
telemetry!(CONSENSUS_WARN; "aura.err_with_block_built_on";
|
||||
"hash" => ?parent_hash, "err" => ?e
|
||||
);
|
||||
}
|
||||
}).map_err(|e| consensus_common::Error::ClientImport(format!("{:?}", e)).into()))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! aura_err {
|
||||
($($i: expr),+) => {
|
||||
{ debug!(target: "aura", $($i),+)
|
||||
; format!($($i),+)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn find_pre_digest<B: Block, P: Pair>(header: &B::Header) -> Result<u64, String>
|
||||
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!("Multiple AuRa pre-runtime headers, rejecting!"))?,
|
||||
(None, _) => trace!(target: "aura", "Ignoring digest not meant for us"),
|
||||
(s, false) => pre_digest = s,
|
||||
}
|
||||
}
|
||||
pre_digest.ok_or_else(|| aura_err!("No AuRa pre-runtime digest found"))
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
///
|
||||
@@ -462,30 +400,25 @@ fn check_header<C, B: Block, P: Pair>(
|
||||
mut header: B::Header,
|
||||
hash: B::Hash,
|
||||
authorities: &[AuthorityId<P>],
|
||||
allow_old_seals: bool,
|
||||
) -> Result<CheckedHeader<B::Header, DigestItemFor<B>>, String>
|
||||
where DigestItemFor<B>: CompatibleDigestItem<P>,
|
||||
P::Signature: Decode,
|
||||
C: client::backend::AuxStore,
|
||||
P::Public: AsRef<P::Public> + Encode + Decode + PartialEq + Clone,
|
||||
) -> Result<CheckedHeader<B::Header, (u64, DigestItemFor<B>)>, String> where
|
||||
DigestItemFor<B>: CompatibleDigestItem<P>,
|
||||
P::Signature: Decode,
|
||||
C: client::backend::AuxStore,
|
||||
P::Public: AsRef<P::Public> + Encode + Decode + PartialEq + Clone,
|
||||
{
|
||||
let digest_item = match header.digest_mut().pop() {
|
||||
let seal = match header.digest_mut().pop() {
|
||||
Some(x) => x,
|
||||
None => return Err(format!("Header {:?} is unsealed", hash)),
|
||||
};
|
||||
|
||||
if !allow_old_seals && digest_item.is_deprecated() {
|
||||
debug!(target: "aura", "Header {:?} uses old seal format, rejecting", hash);
|
||||
return Err(format!("Header {:?} uses old seal format, rejecting", hash))
|
||||
}
|
||||
|
||||
let (slot_num, sig) = digest_item.as_aura_seal().ok_or_else(|| {
|
||||
debug!(target: "aura", "Header {:?} is unsealed", hash);
|
||||
format!("Header {:?} is unsealed", hash)
|
||||
let sig = seal.as_aura_seal().ok_or_else(|| {
|
||||
aura_err!("Header {:?} has a bad seal", hash)
|
||||
})?;
|
||||
|
||||
let slot_num = find_pre_digest::<B, _>(&header)?;
|
||||
|
||||
if slot_num > slot_now {
|
||||
header.digest_mut().push(digest_item);
|
||||
header.digest_mut().push(seal);
|
||||
Ok(CheckedHeader::Deferred(header, slot_num))
|
||||
} else {
|
||||
// check the signature is valid under the expected authority and
|
||||
@@ -496,16 +429,14 @@ fn check_header<C, B: Block, P: Pair>(
|
||||
};
|
||||
|
||||
let pre_hash = header.hash();
|
||||
let to_sign = (slot_num, pre_hash).encode();
|
||||
let public = expected_author;
|
||||
|
||||
if P::verify(&sig, &to_sign[..], public) {
|
||||
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
|
||||
match check_equivocation::<_, _, <P as Pair>::Public>(
|
||||
client,
|
||||
slot_now,
|
||||
slot_num,
|
||||
header.clone(),
|
||||
public.clone(),
|
||||
expected_author.clone(),
|
||||
) {
|
||||
Ok(Some(equivocation_proof)) => {
|
||||
let log_str = format!(
|
||||
@@ -517,7 +448,7 @@ fn check_header<C, B: Block, P: Pair>(
|
||||
info!("{}", log_str);
|
||||
Err(log_str)
|
||||
},
|
||||
Ok(None) => Ok(CheckedHeader::Checked(header, digest_item)),
|
||||
Ok(None) => Ok(CheckedHeader::Checked(header, (slot_num, seal))),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
} else {
|
||||
@@ -532,7 +463,6 @@ pub struct AuraVerifier<C, E, P> {
|
||||
extra: E,
|
||||
phantom: PhantomData<P>,
|
||||
inherent_data_providers: inherents::InherentDataProviders,
|
||||
allow_old_seals: bool,
|
||||
}
|
||||
|
||||
impl<C, E, P> AuraVerifier<C, E, P>
|
||||
@@ -631,20 +561,17 @@ impl<B: Block, C, E, P> Verifier<B> for AuraVerifier<C, E, P> where
|
||||
);
|
||||
|
||||
// we add one to allow for some small drift.
|
||||
// FIXME #1019 in the future, alter this queue to allow deferring of headers
|
||||
// FIXME #1019 in the future, alter this queue to allow deferring of
|
||||
// headers
|
||||
let checked_header = check_header::<C, B, P>(
|
||||
&self.client,
|
||||
slot_now + 1,
|
||||
header,
|
||||
hash,
|
||||
&authorities[..],
|
||||
self.allow_old_seals,
|
||||
)?;
|
||||
match checked_header {
|
||||
CheckedHeader::Checked(pre_header, seal) => {
|
||||
let (slot_num, _) = seal.as_aura_seal()
|
||||
.expect("check_header always returns a seal digest item; qed");
|
||||
|
||||
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.
|
||||
@@ -813,52 +740,6 @@ pub fn import_queue<B, C, E, P>(
|
||||
extra,
|
||||
inherent_data_providers,
|
||||
phantom: PhantomData,
|
||||
allow_old_seals: false,
|
||||
}
|
||||
);
|
||||
Ok(BasicQueue::new(
|
||||
verifier,
|
||||
block_import,
|
||||
justification_import,
|
||||
finality_proof_import,
|
||||
finality_proof_request_builder,
|
||||
))
|
||||
}
|
||||
|
||||
/// Start an import queue for the Aura consensus algorithm with backwards compatibility.
|
||||
#[deprecated(
|
||||
since = "1.0.1",
|
||||
note = "should not be used unless backwards compatibility with an older chain is needed.",
|
||||
)]
|
||||
pub fn import_queue_accept_old_seals<B, C, E, P>(
|
||||
slot_duration: SlotDuration,
|
||||
block_import: SharedBlockImport<B>,
|
||||
justification_import: Option<SharedJustificationImport<B>>,
|
||||
finality_proof_import: Option<SharedFinalityProofImport<B>>,
|
||||
finality_proof_request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
|
||||
client: Arc<C>,
|
||||
extra: E,
|
||||
inherent_data_providers: InherentDataProviders,
|
||||
) -> Result<AuraImportQueue<B>, consensus_common::Error> where
|
||||
B: Block,
|
||||
C: 'static + ProvideRuntimeApi + ProvideCache<B> + Send + Sync + AuxStore,
|
||||
C::Api: BlockBuilderApi<B> + AuthoritiesApi<B>,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
|
||||
E: 'static + ExtraVerification<B>,
|
||||
P: Pair + Send + Sync + 'static,
|
||||
P::Public: Clone + Eq + Send + Sync + Hash + Debug + Encode + Decode + AsRef<P::Public>,
|
||||
P::Signature: Encode + Decode,
|
||||
{
|
||||
register_aura_inherent_data_provider(&inherent_data_providers, slot_duration.get())?;
|
||||
initialize_authorities_cache(&*client)?;
|
||||
|
||||
let verifier = Arc::new(
|
||||
AuraVerifier {
|
||||
client: client.clone(),
|
||||
extra,
|
||||
inherent_data_providers,
|
||||
phantom: PhantomData,
|
||||
allow_old_seals: true,
|
||||
}
|
||||
);
|
||||
Ok(BasicQueue::new(
|
||||
@@ -873,10 +754,11 @@ pub fn import_queue_accept_old_seals<B, C, E, P>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::stream::Stream as _;
|
||||
use consensus_common::NoNetwork as DummyOracle;
|
||||
use network::test::*;
|
||||
use network::test::{Block as TestBlock, PeersClient, PeersFullClient};
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use runtime_primitives::traits::{Block as BlockT, DigestFor};
|
||||
use network::config::ProtocolConfig;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::runtime::current_thread;
|
||||
@@ -910,8 +792,13 @@ mod tests {
|
||||
type Error = Error;
|
||||
type Create = Result<TestBlock, Error>;
|
||||
|
||||
fn propose(&self, _: InherentData, _: Duration) -> Result<TestBlock, Error> {
|
||||
self.1.new_block().unwrap().bake().map_err(|e| e.into())
|
||||
fn propose(
|
||||
&self,
|
||||
_: InherentData,
|
||||
digests: DigestFor<TestBlock>,
|
||||
_: Duration,
|
||||
) -> Result<TestBlock, Error> {
|
||||
self.1.new_block(digests).unwrap().bake().map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +842,6 @@ mod tests {
|
||||
extra: NothingExtra,
|
||||
inherent_data_providers,
|
||||
phantom: Default::default(),
|
||||
allow_old_seals: false,
|
||||
})
|
||||
},
|
||||
PeersClient::Light(_) => unreachable!("No (yet) tests for light client + Aura"),
|
||||
@@ -995,19 +881,18 @@ mod tests {
|
||||
extrinsics_root: Default::default(),
|
||||
digest: DigestTest { logs: vec![], },
|
||||
};
|
||||
let item = <DigestItemFor<TestBlock> as CompatibleDigestItem<sr25519::Pair>>::aura_pre_digest(slot_num);
|
||||
header.digest_mut().push(item);
|
||||
let header_hash: H256 = header.hash();
|
||||
let to_sign = (slot_num, header_hash).encode();
|
||||
let signature = pair.sign(&to_sign[..]);
|
||||
let signature = pair.sign(&header_hash[..]);
|
||||
|
||||
let item = <generic::DigestItem<_, _, _> as CompatibleDigestItem<sr25519::Pair>>::aura_seal(
|
||||
slot_num,
|
||||
signature,
|
||||
);
|
||||
let item = CompatibleDigestItem::<sr25519::Pair>::aura_seal(signature);
|
||||
header.digest_mut().push(item);
|
||||
(header, header_hash)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn authoring_blocks() {
|
||||
let _ = ::env_logger::try_init();
|
||||
let mut net = AuraTestNet::new(3);
|
||||
@@ -1046,7 +931,7 @@ mod tests {
|
||||
&inherent_data_providers, slot_duration.get()
|
||||
).expect("Registers aura inherent data provider");
|
||||
|
||||
let aura = start_aura::<_, _, _, _, _, sr25519::Pair, _, _, _>(
|
||||
let aura = start_aura::<_, _, _, _, _, sr25519::Pair, _, _, _, _>(
|
||||
slot_duration,
|
||||
Arc::new(key.clone().into()),
|
||||
client.clone(),
|
||||
@@ -1111,22 +996,22 @@ mod tests {
|
||||
let c = Arc::new(client);
|
||||
|
||||
// It's ok to sign same headers.
|
||||
assert!(check_header::<_, B, P>(&c, 2, header1.clone(), header1_hash, &authorities, false).is_ok());
|
||||
assert!(check_header::<_, B, P>(&c, 3, header1, header1_hash, &authorities, false).is_ok());
|
||||
check_header::<_, B, P>(&c, 2, header1.clone(), header1_hash, &authorities).unwrap();
|
||||
assert!(check_header::<_, B, P>(&c, 3, header1, header1_hash, &authorities).is_ok());
|
||||
|
||||
// But not two different headers at the same slot.
|
||||
assert!(check_header::<_, B, P>(&c, 4, header2, header2_hash, &authorities, false).is_err());
|
||||
assert!(check_header::<_, B, P>(&c, 4, header2, header2_hash, &authorities).is_err());
|
||||
|
||||
// Different slot is ok.
|
||||
assert!(check_header::<_, B, P>(&c, 5, header3, header3_hash, &authorities, false).is_ok());
|
||||
assert!(check_header::<_, B, P>(&c, 5, header3, header3_hash, &authorities).is_ok());
|
||||
|
||||
// Here we trigger pruning and save header 4.
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 2, header4, header4_hash, &authorities, false).is_ok());
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 2, header4, header4_hash, &authorities).is_ok());
|
||||
|
||||
// This fails because header 5 is an equivocation of header 4.
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 3, header5, header5_hash, &authorities, false).is_err());
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 3, header5, header5_hash, &authorities).is_err());
|
||||
|
||||
// This is ok because we pruned the corresponding header. Shows that we are pruning.
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 4, header6, header6_hash, &authorities, false).is_ok());
|
||||
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 4, header6, header6_hash, &authorities).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives for BABE.
|
||||
#![forbid(warnings, unsafe_code, missing_docs)]
|
||||
#![deny(warnings, unsafe_code, missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use runtime_primitives::ConsensusEngineId;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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/>.
|
||||
|
||||
//! Private mplementation details of BABE digests.
|
||||
use primitives::sr25519::{Public, Signature};
|
||||
use babe_primitives::BABE_ENGINE_ID;
|
||||
use runtime_primitives::generic::DigestItem;
|
||||
use std::fmt::Debug;
|
||||
use parity_codec::{Decode, Encode, Input};
|
||||
use log::info;
|
||||
use schnorrkel::{
|
||||
vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH},
|
||||
PUBLIC_KEY_LENGTH,
|
||||
};
|
||||
|
||||
/// A BABE pre-digest. It includes:
|
||||
///
|
||||
/// * The public key of the author.
|
||||
/// * The VRF proof.
|
||||
/// * The VRF output.
|
||||
/// * The slot number.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BabePreDigest {
|
||||
pub(super) vrf_output: VRFOutput,
|
||||
pub(super) proof: VRFProof,
|
||||
pub(super) author: Public,
|
||||
pub(super) slot_num: u64,
|
||||
}
|
||||
|
||||
/// The prefix used by BABE for its VRF keys.
|
||||
pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf";
|
||||
|
||||
type TmpDecode = (
|
||||
[u8; VRF_OUTPUT_LENGTH],
|
||||
[u8; VRF_PROOF_LENGTH],
|
||||
[u8; PUBLIC_KEY_LENGTH],
|
||||
u64,
|
||||
);
|
||||
|
||||
impl Encode for BabePreDigest {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let tmp: TmpDecode = (
|
||||
*self.vrf_output.as_bytes(),
|
||||
self.proof.to_bytes(),
|
||||
self.author.0,
|
||||
self.slot_num,
|
||||
);
|
||||
parity_codec::Encode::encode(&tmp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BabePreDigest {
|
||||
fn decode<R: Input>(i: &mut R) -> Option<Self> {
|
||||
let (output, proof, public_key, slot_num): TmpDecode = Decode::decode(i)?;
|
||||
Some(BabePreDigest {
|
||||
proof: VRFProof::from_bytes(&proof).ok()?,
|
||||
vrf_output: VRFOutput::from_bytes(&output).ok()?,
|
||||
author: Public(public_key),
|
||||
slot_num,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A digest item which is usable with BABE consensus.
|
||||
pub trait CompatibleDigestItem: Sized {
|
||||
/// Construct a digest item which contains a BABE pre-digest.
|
||||
fn babe_pre_digest(seal: BabePreDigest) -> Self;
|
||||
|
||||
/// If this item is an BABE pre-digest, return it.
|
||||
fn as_babe_pre_digest(&self) -> Option<BabePreDigest>;
|
||||
|
||||
/// Construct a digest item which contains a BABE seal.
|
||||
fn babe_seal(signature: Signature) -> Self;
|
||||
|
||||
/// If this item is a BABE signature, return the signature.
|
||||
fn as_babe_seal(&self) -> Option<Signature>;
|
||||
}
|
||||
|
||||
impl<Hash: Debug> CompatibleDigestItem for DigestItem<Hash, Public, Vec<u8>>
|
||||
{
|
||||
fn babe_pre_digest(digest: BabePreDigest) -> Self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode())
|
||||
}
|
||||
|
||||
fn as_babe_pre_digest(&self) -> Option<BabePreDigest> {
|
||||
match self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, seal) => {
|
||||
let decoded = Decode::decode(&mut &seal[..]);
|
||||
if decoded.is_none() {
|
||||
info!(target: "babe", "Failed to decode {:?}", seal)
|
||||
}
|
||||
decoded
|
||||
}
|
||||
_ => {
|
||||
info!(target: "babe", "Invalid consensus: {:?}!", self);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn babe_seal(signature: Signature) -> Self {
|
||||
DigestItem::Seal(BABE_ENGINE_ID, signature.encode())
|
||||
}
|
||||
|
||||
fn as_babe_seal(&self) -> Option<Signature> {
|
||||
match self {
|
||||
DigestItem::Seal(BABE_ENGINE_ID, signature) => Decode::decode(&mut &signature[..]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Hash: Debug> CompatibleDigestItem for DigestItem<Hash, Public, Signature>
|
||||
{
|
||||
fn babe_pre_digest(digest: BabePreDigest) -> Self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode())
|
||||
}
|
||||
|
||||
fn as_babe_pre_digest(&self) -> Option<BabePreDigest> {
|
||||
match self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, seal) => Decode::decode(&mut &seal[..]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn babe_seal(signature: Signature) -> Self {
|
||||
DigestItem::Seal(BABE_ENGINE_ID, signature)
|
||||
}
|
||||
|
||||
fn as_babe_seal(&self) -> Option<Signature> {
|
||||
match self {
|
||||
DigestItem::Seal(BABE_ENGINE_ID, signature) => Some(signature.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! # BABE consensus
|
||||
//!
|
||||
//! BABE (Blind Assignment for Blockchain Extension) consensus in substrate.
|
||||
//! BABE (Blind Assignment for Blockchain Extension) consensus in Substrate.
|
||||
//!
|
||||
//! # Stability
|
||||
//!
|
||||
@@ -26,32 +26,42 @@
|
||||
#![forbid(unsafe_code, missing_docs)]
|
||||
#![deny(warnings)]
|
||||
extern crate core;
|
||||
mod digest;
|
||||
use digest::CompatibleDigestItem;
|
||||
pub use digest::{BabePreDigest, BABE_VRF_PREFIX};
|
||||
pub use babe_primitives::*;
|
||||
pub use consensus_common::SyncOracle;
|
||||
use consensus_common::ExtraVerification;
|
||||
use runtime_primitives::{generic, generic::BlockId, Justification};
|
||||
use runtime_primitives::traits::{
|
||||
Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, AuthorityIdFor,
|
||||
SimpleBitOps,
|
||||
};
|
||||
use std::{sync::Arc, u64, fmt::Debug};
|
||||
use parity_codec::{Decode, Encode, Input};
|
||||
use std::{sync::Arc, u64, fmt::{Debug, Display}};
|
||||
use runtime_support::serde::{Serialize, Deserialize};
|
||||
use parity_codec::{Decode, Encode};
|
||||
use primitives::{
|
||||
crypto::Pair,
|
||||
sr25519::{Public, Signature, LocalizedSignature, self},
|
||||
sr25519::{Public, Signature, self},
|
||||
};
|
||||
use merlin::Transcript;
|
||||
use inherents::{InherentDataProviders, InherentData};
|
||||
use substrate_telemetry::{telemetry, CONSENSUS_TRACE, CONSENSUS_DEBUG, CONSENSUS_WARN, CONSENSUS_INFO};
|
||||
use substrate_telemetry::{
|
||||
telemetry,
|
||||
CONSENSUS_TRACE,
|
||||
CONSENSUS_DEBUG,
|
||||
CONSENSUS_WARN,
|
||||
CONSENSUS_INFO,
|
||||
};
|
||||
use schnorrkel::{
|
||||
keys::Keypair,
|
||||
vrf::{
|
||||
VRFProof, VRFProofBatchable, VRFInOut, VRFOutput,
|
||||
VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH,
|
||||
VRFProof, VRFProofBatchable, VRFInOut,
|
||||
},
|
||||
PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
|
||||
};
|
||||
use authorities::AuthoritiesApi;
|
||||
use consensus_common::{self, Authorities, BlockImport, Environment, Proposer,
|
||||
use consensus_common::{
|
||||
self, Authorities, BlockImport, Environment, Proposer,
|
||||
ForkChoiceStrategy, ImportBlock, BlockOrigin, Error as ConsensusError,
|
||||
};
|
||||
use srml_babe::{
|
||||
@@ -74,92 +84,10 @@ use log::{error, warn, debug, info, trace};
|
||||
|
||||
use slots::{SlotWorker, SlotInfo, SlotCompatible, slot_now};
|
||||
|
||||
/// A BABE seal. It includes:
|
||||
///
|
||||
/// * The public key
|
||||
/// * The VRF proof
|
||||
/// * The signature
|
||||
/// * The slot number
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BabeSeal {
|
||||
vrf_output: VRFOutput,
|
||||
proof: VRFProof,
|
||||
signature: LocalizedSignature,
|
||||
slot_num: u64,
|
||||
}
|
||||
|
||||
/// The prefix used by BABE for its VRF keys.
|
||||
pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf";
|
||||
|
||||
macro_rules! babe_assert_eq {
|
||||
($a: expr, $b: expr) => {
|
||||
{
|
||||
let ref a = $a;
|
||||
let ref b = $b;
|
||||
if a != b {
|
||||
error!(
|
||||
target: "babe",
|
||||
"Expected {:?} to equal {:?}, but they were not",
|
||||
stringify!($a),
|
||||
stringify!($b),
|
||||
);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type TmpDecode = (
|
||||
[u8; VRF_OUTPUT_LENGTH],
|
||||
[u8; VRF_PROOF_LENGTH],
|
||||
[u8; SIGNATURE_LENGTH],
|
||||
[u8; PUBLIC_KEY_LENGTH],
|
||||
u64,
|
||||
);
|
||||
|
||||
impl Encode for BabeSeal {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let tmp: TmpDecode = (
|
||||
*self.vrf_output.as_bytes(),
|
||||
self.proof.to_bytes(),
|
||||
self.signature.signature.0,
|
||||
self.signature.signer.0,
|
||||
self.slot_num,
|
||||
);
|
||||
let encoded = parity_codec::Encode::encode(&tmp);
|
||||
if cfg!(any(test, debug_assertions)) {
|
||||
debug!(target: "babe", "Checking if encoding was correct");
|
||||
let decoded_version = Self::decode(&mut &encoded[..])
|
||||
.expect("we just encoded this ourselves, so it is correct; qed");
|
||||
babe_assert_eq!(decoded_version.proof, self.proof);
|
||||
babe_assert_eq!(decoded_version.vrf_output, self.vrf_output);
|
||||
babe_assert_eq!(decoded_version.signature.signature, self.signature.signature);
|
||||
babe_assert_eq!(decoded_version.signature.signer, self.signature.signer);
|
||||
babe_assert_eq!(decoded_version.slot_num, self.slot_num);
|
||||
debug!(target: "babe", "Encoding was correct")
|
||||
}
|
||||
encoded
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BabeSeal {
|
||||
fn decode<R: Input>(i: &mut R) -> Option<Self> {
|
||||
let (output, proof, sig, public_key, slot_num): TmpDecode = Decode::decode(i)?;
|
||||
Some(BabeSeal {
|
||||
proof: VRFProof::from_bytes(&proof).ok()?,
|
||||
vrf_output: VRFOutput::from_bytes(&output).ok()?,
|
||||
signature: LocalizedSignature {
|
||||
signature: Signature(sig),
|
||||
signer: Public(public_key),
|
||||
},
|
||||
slot_num,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A slot duration. Create with `get_or_compute`.
|
||||
// FIXME: Once Rust has higher-kinded types, the duplication between this
|
||||
// and `super::aura::Config` can be eliminated.
|
||||
// and `super::babe::Config` can be eliminated.
|
||||
// https://github.com/paritytech/substrate/issues/2434
|
||||
pub struct Config(slots::SlotDuration<BabeConfiguration>);
|
||||
|
||||
@@ -191,45 +119,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// A digest item which is usable with BABE consensus.
|
||||
pub trait CompatibleDigestItem: Sized {
|
||||
/// Construct a digest item which contains a slot number and a signature
|
||||
/// on the hash.
|
||||
fn babe_seal(signature: BabeSeal) -> Self;
|
||||
|
||||
/// If this item is an Babe seal, return the slot number and signature.
|
||||
fn as_babe_seal(&self) -> Option<BabeSeal>;
|
||||
}
|
||||
|
||||
impl<T, Hash> CompatibleDigestItem for generic::DigestItem<Hash, Public, T>
|
||||
where T: Debug, Hash: Debug
|
||||
{
|
||||
/// Construct a digest item which contains a slot number and a signature
|
||||
/// on the hash.
|
||||
fn babe_seal(signature: BabeSeal) -> Self {
|
||||
generic::DigestItem::Consensus(BABE_ENGINE_ID, signature.encode())
|
||||
}
|
||||
|
||||
/// If this item is an BABE seal, return the slot number and signature.
|
||||
fn as_babe_seal(&self) -> Option<BabeSeal> {
|
||||
match self {
|
||||
generic::DigestItem::Consensus(BABE_ENGINE_ID, seal) => {
|
||||
match Decode::decode(&mut &seal[..]) {
|
||||
s @ Some(_) => s,
|
||||
s @ None => {
|
||||
info!(target: "babe", "Failed to decode {:?}", seal);
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
info!(target: "babe", "Invalid consensus: {:?}!", self);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BabeSlotCompatible;
|
||||
|
||||
impl SlotCompatible for BabeSlotCompatible {
|
||||
@@ -280,7 +169,7 @@ pub struct BabeParams<C, E, I, SO, SC, OnExit> {
|
||||
}
|
||||
|
||||
/// Start the babe worker. The returned future should be run in a tokio runtime.
|
||||
pub fn start_babe<B, C, E, I, SO, SC, Error, OnExit>(BabeParams {
|
||||
pub fn start_babe<B, C, SC, E, I, SO, Error, OnExit, H>(BabeParams {
|
||||
config,
|
||||
local_key,
|
||||
client,
|
||||
@@ -295,17 +184,22 @@ pub fn start_babe<B, C, E, I, SO, SC, Error, OnExit>(BabeParams {
|
||||
impl Future<Item=(), Error=()>,
|
||||
consensus_common::Error,
|
||||
> where
|
||||
B: Block,
|
||||
B: Block<Header=H>,
|
||||
C: ProvideRuntimeApi + ProvideCache<B>,
|
||||
C::Api: AuthoritiesApi<B>,
|
||||
E: Environment<B, Error=Error>,
|
||||
SC: SelectChain<B>,
|
||||
generic::DigestItem<B::Hash, Public, Signature>: DigestItem<Hash=B::Hash>,
|
||||
E::Proposer: Proposer<B, Error=Error>,
|
||||
<<E::Proposer as Proposer<B>>::Create as IntoFuture>::Future: Send + 'static,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
SO: SyncOracle + Send + Sync + Clone,
|
||||
SC: SelectChain<B>,
|
||||
DigestItemFor<B>: CompatibleDigestItem + DigestItem<AuthorityId=Public>,
|
||||
Error: ::std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
|
||||
H: Header<
|
||||
Digest=generic::Digest<generic::DigestItem<B::Hash, Public, Signature>>,
|
||||
Hash=B::Hash,
|
||||
>,
|
||||
E: Environment<B, Error=Error>,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
Error: std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
|
||||
SO: SyncOracle + Send + Sync + Clone,
|
||||
OnExit: Future<Item=(), Error=()>,
|
||||
{
|
||||
let worker = BabeWorker {
|
||||
@@ -339,15 +233,22 @@ struct BabeWorker<C, E, I, SO> {
|
||||
threshold: u64,
|
||||
}
|
||||
|
||||
impl<B: Block, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<C, E, I, SO> where
|
||||
impl<Hash, H, B, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<C, E, I, SO> where
|
||||
B: Block<Header=H, Hash=Hash>,
|
||||
C: ProvideRuntimeApi + ProvideCache<B>,
|
||||
C::Api: AuthoritiesApi<B>,
|
||||
E: Environment<B, Error=Error>,
|
||||
E::Proposer: Proposer<B, Error=Error>,
|
||||
<<E::Proposer as Proposer<B>>::Create as IntoFuture>::Future: Send + 'static,
|
||||
Hash: Debug + Eq + Copy + SimpleBitOps + Encode + Decode + Serialize +
|
||||
for<'de> Deserialize<'de> + Debug + Default + AsRef<[u8]> + AsMut<[u8]> +
|
||||
std::hash::Hash + Display + Send + Sync + 'static,
|
||||
H: Header<
|
||||
Digest=generic::Digest<generic::DigestItem<B::Hash, Public, Signature>>,
|
||||
Hash=B::Hash,
|
||||
>,
|
||||
I: BlockImport<B> + Send + Sync + 'static,
|
||||
SO: SyncOracle + Send + Clone,
|
||||
DigestItemFor<B>: CompatibleDigestItem + DigestItem<AuthorityId=Public>,
|
||||
Error: std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
|
||||
{
|
||||
type OnSlot = Box<Future<Item=(), Error=consensus_common::Error> + Send>;
|
||||
@@ -399,7 +300,7 @@ impl<B: Block, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<C, E, I, SO> whe
|
||||
// FIXME replace the dummy empty slices with real data
|
||||
// https://github.com/paritytech/substrate/issues/2435
|
||||
// https://github.com/paritytech/substrate/issues/2436
|
||||
let authoring_result = if let Some((inout, proof, _batchable_proof)) = claim_slot(
|
||||
let proposal_work = if let Some((inout, proof, _batchable_proof)) = claim_slot(
|
||||
&[0u8; 0],
|
||||
slot_info.number,
|
||||
&[0u8; 0],
|
||||
@@ -429,105 +330,133 @@ impl<B: Block, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<C, E, I, SO> whe
|
||||
}
|
||||
};
|
||||
|
||||
let inherent_digest = BabePreDigest {
|
||||
proof,
|
||||
vrf_output: inout.to_output(),
|
||||
author: pair.public(),
|
||||
slot_num,
|
||||
};
|
||||
|
||||
// deadline our production to approx. the end of the slot
|
||||
let remaining_duration = slot_info.remaining_duration();
|
||||
// deadline our production to approx. the end of the
|
||||
// slot
|
||||
(Timeout::new(
|
||||
Timeout::new(
|
||||
proposer.propose(
|
||||
slot_info.inherent_data,
|
||||
generic::Digest {
|
||||
logs: vec![
|
||||
generic::DigestItem::babe_pre_digest(inherent_digest.clone()),
|
||||
],
|
||||
},
|
||||
remaining_duration,
|
||||
).into_future(),
|
||||
remaining_duration,
|
||||
),
|
||||
inout.to_output(),
|
||||
proof)
|
||||
)
|
||||
} else {
|
||||
return Box::new(future::ok(()));
|
||||
};
|
||||
|
||||
let (proposal_work, vrf_output, proof) = authoring_result;
|
||||
Box::new(proposal_work.map(move |b| {
|
||||
// minor hack since we don't have access to the timestamp
|
||||
// that is actually set by the proposer.
|
||||
let slot_after_building = slot_now(slot_duration);
|
||||
if slot_after_building != Some(slot_num) {
|
||||
info!(
|
||||
target: "babe",
|
||||
"Discarding proposal for slot {}; block production took too long",
|
||||
slot_num
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "babe.discarding_proposal_took_too_long";
|
||||
"slot" => slot_num
|
||||
);
|
||||
return
|
||||
}
|
||||
|
||||
Box::new(
|
||||
proposal_work
|
||||
.map(move |b| {
|
||||
// minor hack since we don't have access to the timestamp
|
||||
// that is actually set by the proposer.
|
||||
let slot_after_building = slot_now(slot_duration);
|
||||
if slot_after_building != Some(slot_num) {
|
||||
info!(
|
||||
target: "babe",
|
||||
"Discarding proposal for slot {}; block production took too long",
|
||||
slot_num
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "babe.discarding_proposal_took_too_long";
|
||||
"slot" => slot_num
|
||||
);
|
||||
return
|
||||
}
|
||||
let (header, body) = b.deconstruct();
|
||||
let pre_digest: Result<BabePreDigest, String> = find_pre_digest::<B>(&header);
|
||||
if let Err(e) = pre_digest {
|
||||
error!(target: "babe", "FATAL ERROR: Invalid pre-digest: {}!", e);
|
||||
return
|
||||
} else {
|
||||
trace!(target: "babe", "Got correct number of seals. Good!")
|
||||
};
|
||||
|
||||
let (header, body) = b.deconstruct();
|
||||
let header_num = header.number().clone();
|
||||
let pre_hash = header.hash();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
let header_num = header.number().clone();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
|
||||
// sign the pre-sealed hash of the block and then
|
||||
// add it to a digest item.
|
||||
let to_sign = (slot_num, pre_hash, proof.to_bytes()).encode();
|
||||
let signature = pair.sign(&to_sign[..]);
|
||||
let item = <DigestItemFor<B> as CompatibleDigestItem>::babe_seal(BabeSeal {
|
||||
proof,
|
||||
signature: LocalizedSignature {
|
||||
signature,
|
||||
signer: pair.public(),
|
||||
},
|
||||
slot_num,
|
||||
vrf_output,
|
||||
});
|
||||
// sign the pre-sealed hash of the block and then
|
||||
// add it to a digest item.
|
||||
let header_hash = header.hash();
|
||||
let signature = pair.sign(header_hash.as_ref());
|
||||
let signature_digest_item = DigestItemFor::<B>::babe_seal(signature);
|
||||
|
||||
let import_block: ImportBlock<B> = ImportBlock {
|
||||
origin: BlockOrigin::Own,
|
||||
header,
|
||||
justification: None,
|
||||
post_digests: vec![item],
|
||||
body: Some(body),
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
};
|
||||
let import_block: ImportBlock<B> = ImportBlock {
|
||||
origin: BlockOrigin::Own,
|
||||
header,
|
||||
justification: None,
|
||||
post_digests: vec![signature_digest_item],
|
||||
body: Some(body),
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
};
|
||||
|
||||
info!(target: "babe",
|
||||
"Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
|
||||
header_num,
|
||||
import_block.post_header().hash(),
|
||||
pre_hash
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "babe.pre_sealed_block";
|
||||
"header_num" => ?header_num,
|
||||
"hash_now" => ?import_block.post_header().hash(),
|
||||
"hash_previously" => ?pre_hash
|
||||
);
|
||||
info!(target: "babe",
|
||||
"Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
|
||||
header_num,
|
||||
import_block.post_header().hash(),
|
||||
header_hash,
|
||||
);
|
||||
telemetry!(CONSENSUS_INFO; "babe.pre_sealed_block";
|
||||
"header_num" => ?header_num,
|
||||
"hash_now" => ?import_block.post_header().hash(),
|
||||
"hash_previously" => ?header_hash,
|
||||
);
|
||||
|
||||
if let Err(e) = block_import.import_block(import_block, Default::default()) {
|
||||
warn!(target: "babe", "Error with block built on {:?}: {:?}",
|
||||
parent_hash, e);
|
||||
telemetry!(CONSENSUS_WARN; "babe.err_with_block_built_on";
|
||||
"hash" => ?parent_hash, "err" => ?e
|
||||
);
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
warn!("Client import failed: {:?}", e);
|
||||
consensus_common::Error::ClientImport(format!("{:?}", e))
|
||||
})
|
||||
)
|
||||
if let Err(e) = block_import.import_block(import_block, Default::default()) {
|
||||
warn!(target: "babe", "Error with block built on {:?}: {:?}",
|
||||
parent_hash, e);
|
||||
telemetry!(CONSENSUS_WARN; "babe.err_with_block_built_on";
|
||||
"hash" => ?parent_hash, "err" => ?e
|
||||
);
|
||||
}
|
||||
}).map_err(|e| {
|
||||
warn!("Client import failed: {:?}", e);
|
||||
consensus_common::Error::ClientImport(format!("{:?}", e)).into()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! babe_err {
|
||||
($($i: expr),+) => {
|
||||
{ debug!(target: "babe", $($i),+)
|
||||
; format!($($i),+)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn find_pre_digest<B: Block>(header: &B::Header) -> Result<BabePreDigest, String>
|
||||
where DigestItemFor<B>: CompatibleDigestItem,
|
||||
{
|
||||
let mut pre_digest: Option<_> = None;
|
||||
for log in header.digest().logs() {
|
||||
trace!(target: "babe", "Checking log {:?}", log);
|
||||
match (log.as_babe_pre_digest(), pre_digest.is_some()) {
|
||||
(Some(_), true) => Err(babe_err!("Multiple BABE pre-runtime headers, rejecting!"))?,
|
||||
(None, _) => trace!(target: "babe", "Ignoring digest not meant for us"),
|
||||
(s, false) => pre_digest = s,
|
||||
}
|
||||
}
|
||||
pre_digest.ok_or_else(|| babe_err!("No BABE pre-runtime digest found"))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This digest item will always return `Some` when used with `as_babe_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`.
|
||||
//
|
||||
// FIXME #1018 needs misbehavior types
|
||||
#[forbid(warnings)]
|
||||
@@ -538,36 +467,31 @@ fn check_header<B: Block + Sized, C: AuxStore>(
|
||||
hash: B::Hash,
|
||||
authorities: &[Public],
|
||||
threshold: u64,
|
||||
) -> Result<CheckedHeader<B::Header, DigestItemFor<B>>, String>
|
||||
) -> Result<CheckedHeader<B::Header, (DigestItemFor<B>, DigestItemFor<B>)>, String>
|
||||
where DigestItemFor<B>: CompatibleDigestItem,
|
||||
{
|
||||
trace!(target: "babe", "Checking header");
|
||||
let digest_item = match header.digest_mut().pop() {
|
||||
let seal = match header.digest_mut().pop() {
|
||||
Some(x) => x,
|
||||
None => return Err(format!("Header {:?} is unsealed", hash)),
|
||||
None => return Err(babe_err!("Header {:?} is unsealed", hash)),
|
||||
};
|
||||
|
||||
let BabeSeal {
|
||||
slot_num,
|
||||
signature: LocalizedSignature { signer, signature },
|
||||
proof,
|
||||
vrf_output,
|
||||
} = digest_item.as_babe_seal().ok_or_else(|| {
|
||||
debug!(target: "babe", "Header {:?} is unsealed", hash);
|
||||
format!("Header {:?} is unsealed", hash)
|
||||
let sig = seal.as_babe_seal().ok_or_else(|| {
|
||||
babe_err!("Header {:?} has a bad seal", hash)
|
||||
})?;
|
||||
|
||||
let pre_digest = find_pre_digest::<B>(&header)?;
|
||||
let BabePreDigest { slot_num, ref author, ref proof, ref vrf_output } = pre_digest;
|
||||
|
||||
if slot_num > slot_now {
|
||||
header.digest_mut().push(digest_item);
|
||||
header.digest_mut().push(seal);
|
||||
Ok(CheckedHeader::Deferred(header, slot_num))
|
||||
} else if !authorities.contains(&signer) {
|
||||
debug!(target: "babe", "Slot Author not found");
|
||||
Err("Slot Author not found".to_string())
|
||||
} else if !authorities.contains(&author) {
|
||||
Err(babe_err!("Slot author not found"))
|
||||
} else {
|
||||
let pre_hash = header.hash();
|
||||
let to_sign = (slot_num, pre_hash, proof.to_bytes()).encode();
|
||||
|
||||
if sr25519::Pair::verify(&signature, &to_sign[..], &signer) {
|
||||
if sr25519::Pair::verify(&sig, pre_hash, author) {
|
||||
let (inout, _batchable_proof) = {
|
||||
let transcript = make_transcript(
|
||||
Default::default(),
|
||||
@@ -575,41 +499,39 @@ fn check_header<B: Block + Sized, C: AuxStore>(
|
||||
Default::default(),
|
||||
0,
|
||||
);
|
||||
schnorrkel::PublicKey::from_bytes(signer.as_slice()).and_then(|p| {
|
||||
p.vrf_verify(transcript, &vrf_output, &proof)
|
||||
schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| {
|
||||
p.vrf_verify(transcript, vrf_output, proof)
|
||||
}).map_err(|s| {
|
||||
debug!(target: "babe", "VRF verification failed: {:?}", s);
|
||||
format!("VRF verification failed")
|
||||
babe_err!("VRF verification failed: {:?}", s)
|
||||
})?
|
||||
};
|
||||
|
||||
if check(&inout, threshold) {
|
||||
match check_equivocation(&client, slot_now, slot_num, header.clone(), signer.clone()) {
|
||||
Ok(Some(equivocation_proof)) => {
|
||||
let log_str = format!(
|
||||
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
|
||||
signer,
|
||||
slot_num,
|
||||
equivocation_proof.fst_header().hash(),
|
||||
equivocation_proof.snd_header().hash(),
|
||||
);
|
||||
info!("{}", log_str);
|
||||
Err(log_str)
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(CheckedHeader::Checked(header, digest_item))
|
||||
},
|
||||
Err(e) => {
|
||||
Err(e.to_string())
|
||||
},
|
||||
}
|
||||
} else {
|
||||
debug!(target: "babe", "VRF verification failed: threshold {} exceeded", threshold);
|
||||
Err(format!("Validator {:?} made seal when it wasn’t its turn", signer))
|
||||
if !check(&inout, threshold) {
|
||||
return Err(babe_err!("VRF verification of block by author {:?} failed: \
|
||||
threshold {} exceeded", author, threshold))
|
||||
}
|
||||
match check_equivocation(&client, slot_now, slot_num, header.clone(), author.clone()) {
|
||||
Ok(Some(equivocation_proof)) => {
|
||||
let log_str = format!(
|
||||
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
|
||||
author,
|
||||
slot_num,
|
||||
equivocation_proof.fst_header().hash(),
|
||||
equivocation_proof.snd_header().hash(),
|
||||
);
|
||||
info!(target: "babe", "{}", log_str);
|
||||
Err(log_str)
|
||||
},
|
||||
Ok(None) => {
|
||||
let pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest);
|
||||
Ok(CheckedHeader::Checked(header, (pre_digest, seal)))
|
||||
},
|
||||
Err(e) => {
|
||||
Err(e.to_string())
|
||||
},
|
||||
}
|
||||
} else {
|
||||
debug!(target: "babe", "Bad signature on {:?}", hash);
|
||||
Err(format!("Bad signature on {:?}", hash))
|
||||
Err(babe_err!("Bad signature on {:?}", hash))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -681,6 +603,8 @@ impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
|
||||
justification,
|
||||
body,
|
||||
);
|
||||
|
||||
debug!(target: "babe", "We have {:?} logs in this header", header.digest().logs().len());
|
||||
let mut inherent_data = self
|
||||
.inherent_data_providers
|
||||
.create_inherent_data()
|
||||
@@ -709,9 +633,9 @@ impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
|
||||
self.threshold,
|
||||
)?;
|
||||
match checked_header {
|
||||
CheckedHeader::Checked(pre_header, seal) => {
|
||||
let BabeSeal { slot_num, .. } = seal.as_babe_seal()
|
||||
.expect("check_header always returns a seal digest item; qed");
|
||||
CheckedHeader::Checked(pre_header, (pre_digest, seal)) => {
|
||||
let BabePreDigest { slot_num, .. } = pre_digest.as_babe_pre_digest()
|
||||
.expect("check_header always returns a pre-digest digest item; qed");
|
||||
|
||||
// if the body is passed through, we need to use the runtime
|
||||
// to check that the internally-set timestamp in the inherents
|
||||
@@ -738,6 +662,10 @@ impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
|
||||
|
||||
extra_verification.into_future().wait()?;
|
||||
|
||||
let new_authorities = pre_header.digest()
|
||||
.log(DigestItem::as_authorities_change)
|
||||
.map(|digest| digest.to_vec());
|
||||
|
||||
let import_block = ImportBlock {
|
||||
origin,
|
||||
header: pre_header,
|
||||
@@ -750,7 +678,7 @@ impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
|
||||
};
|
||||
|
||||
// FIXME #1019 extract authorities
|
||||
Ok((import_block, None))
|
||||
Ok((import_block, new_authorities))
|
||||
}
|
||||
CheckedHeader::Deferred(a, b) => {
|
||||
debug!(target: "babe", "Checking {:?} failed; {:?}, {:?}.", hash, a, b);
|
||||
@@ -820,6 +748,7 @@ fn get_keypair(q: &sr25519::Pair) -> &Keypair {
|
||||
q.as_ref()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn make_transcript(
|
||||
randomness: &[u8],
|
||||
slot_number: u64,
|
||||
@@ -871,7 +800,8 @@ fn claim_slot(
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code, unused_imports, deprecated)]
|
||||
// FIXME #2532: need to allow deprecated until refactor is done https://github.com/paritytech/substrate/issues/2532
|
||||
// FIXME #2532: need to allow deprecated until refactor is done
|
||||
// https://github.com/paritytech/substrate/issues/2532
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -880,16 +810,18 @@ mod tests {
|
||||
use consensus_common::NoNetwork as DummyOracle;
|
||||
use network::test::*;
|
||||
use network::test::{Block as TestBlock, PeersClient};
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use runtime_primitives::traits::{Block as BlockT, DigestFor};
|
||||
use network::config::ProtocolConfig;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::runtime::current_thread;
|
||||
use keyring::sr25519::Keyring;
|
||||
use super::generic::DigestItem;
|
||||
use client::BlockchainEvents;
|
||||
use test_client;
|
||||
use futures::stream::Stream;
|
||||
use log::debug;
|
||||
use std::time::Duration;
|
||||
type Item = generic::DigestItem<Hash, Public, Signature>;
|
||||
use test_client::AuthorityKeyring;
|
||||
use primitives::hash::H256;
|
||||
use runtime_primitives::testing::{Header as HeaderTest, Digest as DigestTest, Block as RawBlock, ExtrinsicWrapper};
|
||||
@@ -922,8 +854,8 @@ mod tests {
|
||||
type Error = Error;
|
||||
type Create = Result<TestBlock, Error>;
|
||||
|
||||
fn propose(&self, _: InherentData, _: Duration) -> Result<TestBlock, Error> {
|
||||
self.1.new_block().unwrap().bake().map_err(|e| e.into())
|
||||
fn propose(&self, _: InherentData, digests: DigestFor<TestBlock>, _: Duration) -> Result<TestBlock, Error> {
|
||||
self.1.new_block(digests).unwrap().bake().map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1020,26 +952,30 @@ mod tests {
|
||||
|
||||
let (inout, proof, _batchable_proof) = get_keypair(&pair).vrf_sign_n_check(transcript, |inout| check(inout, u64::MAX)).unwrap();
|
||||
let pre_hash: H256 = header.hash();
|
||||
let to_sign = (slot_num, pre_hash, proof.to_bytes()).encode();
|
||||
let signature = pair.sign(&to_sign[..]);
|
||||
let item = <generic::DigestItem<_, _, _> as CompatibleDigestItem>::babe_seal(BabeSeal {
|
||||
let pre_digest = BabePreDigest {
|
||||
proof,
|
||||
signature: LocalizedSignature {
|
||||
signature,
|
||||
signer: pair.public(),
|
||||
},
|
||||
author: pair.public(),
|
||||
slot_num,
|
||||
vrf_output: inout.to_output(),
|
||||
});
|
||||
|
||||
};
|
||||
assert_eq!(
|
||||
Decode::decode(&mut &pre_digest.encode()[..]).as_ref(),
|
||||
Some(&pre_digest),
|
||||
"Pre-digest encoding and decoding did not round-trip",
|
||||
);
|
||||
let item = generic::DigestItem::babe_pre_digest(pre_digest);
|
||||
header.digest_mut().push(item);
|
||||
|
||||
let to_sign = header.hash();
|
||||
let signature = pair.sign(&to_sign[..]);
|
||||
header.digest_mut().push(generic::DigestItem::babe_seal(signature));
|
||||
(header, pre_hash)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_serialize_block() {
|
||||
drop(env_logger::try_init());
|
||||
assert!(BabeSeal::decode(&mut &b""[..]).is_none());
|
||||
assert!(BabePreDigest::decode(&mut &b""[..]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1118,26 +1054,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
#[should_panic]
|
||||
fn old_seals_rejected() {
|
||||
fn wrong_consensus_engine_id_rejected() {
|
||||
drop(env_logger::try_init());
|
||||
generic::DigestItem::<network::test::Hash, Public, primitives::sr25519::Signature>::Seal(0, Signature([0; 64])).as_babe_seal().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_number_rejected() {
|
||||
drop(env_logger::try_init());
|
||||
let bad_seal = generic::DigestItem::<network::test::Hash, Public, primitives::sr25519::Signature>::Consensus([0; 4], Signature([0; 64]).encode());
|
||||
let sig = sr25519::Pair::generate().sign(b"");
|
||||
let bad_seal: Item = DigestItem::Seal([0; 4], sig);
|
||||
assert!(bad_seal.as_babe_pre_digest().is_none());
|
||||
assert!(bad_seal.as_babe_seal().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn bad_seal_rejected() {
|
||||
fn malformed_pre_digest_rejected() {
|
||||
drop(env_logger::try_init());
|
||||
let bad_seal = generic::DigestItem::<network::test::Hash, Public, primitives::sr25519::Signature>::Consensus(BABE_ENGINE_ID, Signature([0; 64]).encode());
|
||||
bad_seal.as_babe_seal().expect("we should not decode this successfully");
|
||||
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, Signature([0; 64]));
|
||||
assert!(bad_seal.as_babe_pre_digest().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sig_is_not_pre_digest() {
|
||||
drop(env_logger::try_init());
|
||||
let sig = sr25519::Pair::generate().sign(b"");
|
||||
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, sig);
|
||||
assert!(bad_seal.as_babe_pre_digest().is_none());
|
||||
assert!(bad_seal.as_babe_seal().is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1172,6 +1110,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn check_header_works_with_equivocation() {
|
||||
drop(env_logger::try_init());
|
||||
let client = test_client::new();
|
||||
let pair = sr25519::Pair::generate();
|
||||
let public = pair.public();
|
||||
|
||||
@@ -37,7 +37,7 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use runtime_primitives::traits::{
|
||||
AuthorityIdFor, Block as BlockT, Header as HeaderT, NumberFor
|
||||
AuthorityIdFor, Block as BlockT, Header as HeaderT, NumberFor, Digest,
|
||||
};
|
||||
use runtime_primitives::Justification;
|
||||
|
||||
@@ -873,6 +873,8 @@ pub fn import_single_block<B: BlockT, V: Verifier<B>>(
|
||||
},
|
||||
};
|
||||
|
||||
trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len());
|
||||
|
||||
let number = header.number().clone();
|
||||
let hash = header.hash();
|
||||
let parent = header.parent_hash().clone();
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{AuthorityIdFor, Block};
|
||||
use runtime_primitives::traits::{AuthorityIdFor, Block, DigestFor};
|
||||
use futures::prelude::*;
|
||||
pub use inherents::InherentData;
|
||||
|
||||
@@ -87,7 +87,12 @@ pub trait Proposer<B: Block> {
|
||||
/// Future that resolves to a committed proposal.
|
||||
type Create: IntoFuture<Item=B, Error=Self::Error>;
|
||||
/// Create a proposal.
|
||||
fn propose(&self, inherent_data: InherentData, max_duration: Duration) -> Self::Create;
|
||||
fn propose(
|
||||
&self,
|
||||
inherent_data: InherentData,
|
||||
inherent_digests: DigestFor<B>,
|
||||
max_duration: Duration,
|
||||
) -> Self::Create;
|
||||
}
|
||||
|
||||
/// An oracle for when major synchronization work is being undertaken.
|
||||
|
||||
@@ -19,13 +19,13 @@ use runtime_primitives::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
|
||||
/// The SelectChain trait defines the strategy upon which the head is chosen
|
||||
/// if multiple forks are present for an opaque definition of "best" in the
|
||||
/// if multiple forks are present for an opaque definition of "best" in the
|
||||
/// specific chain build.
|
||||
///
|
||||
/// The Strategy can be customised for the two use cases of authoring new blocks
|
||||
/// upon the best chain or which fork to finalise. Unless implemented differently
|
||||
/// by default finalisation methods fall back to use authoring, so as a minimum
|
||||
/// `_authoring`-functions must be implemented.
|
||||
/// `_authoring`-functions must be implemented.
|
||||
///
|
||||
/// Any particular user must make explicit, however, whether they intend to finalise
|
||||
/// or author through the using the right function call, as these might differ in
|
||||
@@ -51,4 +51,4 @@ pub trait SelectChain<Block: BlockT>: Sync + Send + Clone {
|
||||
) -> Result<Option<<Block as BlockT>::Hash>, Error> {
|
||||
Ok(Some(target_hash))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ 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: Arc<C>, key: &[u8]) -> ClientResult<Option<T>>
|
||||
fn load_decode<C, T>(backend: Arc<C>, key: &[u8]) -> ClientResult<Option<T>>
|
||||
where
|
||||
C: AuxStore,
|
||||
T: Decode,
|
||||
|
||||
Reference in New Issue
Block a user