diff --git a/substrate/core/consensus/babe/src/authorship.rs b/substrate/core/consensus/babe/src/authorship.rs
new file mode 100644
index 0000000000..93405ff777
--- /dev/null
+++ b/substrate/core/consensus/babe/src/authorship.rs
@@ -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 .
+
+//! 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::() 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::(&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::(&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
+}
diff --git a/substrate/core/consensus/babe/src/lib.rs b/substrate/core/consensus/babe/src/lib.rs
index 800caf9d2b..bd2830d4e7 100644
--- a/substrate/core/consensus/babe/src/lib.rs
+++ b/substrate/core/consensus/babe/src/lib.rs
@@ -72,22 +72,14 @@ use sr_primitives::traits::{
Zero,
};
use keystore::KeyStorePtr;
-use codec::Encode;
use parking_lot::Mutex;
-use primitives::{blake2_256, Blake2Hasher, H256, Pair, Public, U256};
-use merlin::Transcript;
+use primitives::{Blake2Hasher, H256, Pair};
use inherents::{InherentDataProviders, InherentData};
use substrate_telemetry::{
telemetry,
CONSENSUS_TRACE,
CONSENSUS_DEBUG,
};
-use schnorrkel::{
- keys::Keypair,
- vrf::{
- VRFProof, VRFInOut, VRFOutput,
- },
-};
use consensus_common::{
self, BlockImport, Environment, Proposer, BlockCheckParams,
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
@@ -107,12 +99,12 @@ use client::{
use slots::{CheckedHeader, check_equivocation};
use futures::prelude::*;
use log::{warn, debug, info, trace};
-
use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible};
use epoch_changes::descendent_query;
-
mod aux_schema;
+mod verification;
mod epoch_changes;
+mod authorship;
#[cfg(test)]
mod tests;
pub use babe_primitives::{
@@ -351,7 +343,7 @@ impl slots::SimpleSlotWorker for BabeWorker Option {
debug!(target: "babe", "Attempting to claim slot {}", slot_number);
- let s = claim_slot(
+ let s = authorship::claim_slot(
slot_number,
epoch_data,
&*self.config,
@@ -434,8 +426,7 @@ impl SlotWorker for BabeWorker where
/// Extract the BABE pre digest from the given header. Pre-runtime digests are
/// mandatory, the function will return `Err` if none is found.
-fn find_pre_digest(header: &B::Header) -> Result
- where DigestItemFor: CompatibleDigestItem,
+fn find_pre_digest(header: &H) -> Result
{
// genesis block doesn't contain a pre digest so let's generate a
// dummy one to not break any invariants in the rest of the code
@@ -477,221 +468,6 @@ fn find_next_epoch_digest(header: &B::Header)
Ok(epoch_digest)
}
-struct VerificationParams<'a, B: 'a + BlockT> {
- /// the header being verified.
- 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.
- pre_digest: Option,
- /// the slot number of the current time.
- slot_now: SlotNumber,
- /// epoch descriptor of the epoch this block _should_ be under, if it's valid.
- epoch: &'a Epoch,
- /// genesis config of this BABE chain.
- config: &'a Config,
-}
-
-struct VerifiedHeaderInfo {
- pre_digest: DigestItemFor,
- seal: DigestItemFor,
-}
-
-/// 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.
-fn check_header(
- params: VerificationParams,
- client: &C,
-) -> Result>, String> where
- DigestItemFor: 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::(&header))?;
-
- trace!(target: "babe", "Checking header");
- let seal = match header.digest_mut().pop() {
- Some(x) => x,
- None => return Err(babe_err!("Header {:?} is unsealed", header.hash())),
- };
-
- let sig = seal.as_babe_seal().ok_or_else(|| {
- babe_err!("Header {:?} has a bad seal", 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()));
- }
-
- if pre_digest.authority_index() > authorities.len() as u32 {
- return Err(babe_err!("Slot author not found"));
- }
-
- 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::(
- 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::(
- pre_hash,
- digest,
- sig,
- &epoch,
- )?;
- },
- _ => {
- return Err(babe_err!("Secondary slot assignments are disabled for the current epoch."));
- }
- }
-
- let author = &authorities[pre_digest.authority_index() as usize].0;
-
- // the header is valid but let's check if there was something else already
- // proposed at the same slot by the given author
- if let Some(equivocation_proof) = check_equivocation(
- client,
- slot_now,
- pre_digest.slot_number(),
- &header,
- author,
- ).map_err(|e| e.to_string())? {
- babe_info!(
- "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
- author,
- pre_digest.slot_number(),
- equivocation_proof.fst_header().hash(),
- equivocation_proof.snd_header().hash()
- );
- }
-
- let info = VerifiedHeaderInfo {
- pre_digest: CompatibleDigestItem::babe_pre_digest(pre_digest),
- seal,
- };
- Ok(CheckedHeader::Checked(header, info))
-}
-
-/// 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(
- pre_hash: B::Hash,
- pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber),
- signature: AuthoritySignature,
- epoch: &Epoch,
- c: (u64, u64),
-) -> Result<(), String>
- where DigestItemFor: CompatibleDigestItem,
-{
- 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!("VRF verification failed: {:?}", s)
- })?
- };
-
- let threshold = calculate_primary_threshold(
- c,
- &epoch.authorities,
- authority_index as usize,
- );
-
- if !check_primary_threshold(&inout, threshold) {
- return Err(babe_err!("VRF verification of block by author {:?} failed: \
- threshold {} exceeded", author, threshold));
- }
-
- Ok(())
- } else {
- Err(babe_err!("Bad signature on {:?}", 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(
- pre_hash: B::Hash,
- pre_digest: (AuthorityIndex, SlotNumber),
- signature: AuthoritySignature,
- epoch: &Epoch,
-) -> Result<(), String> {
- 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(|| "No secondary author expected.".to_string())?;
-
- let author = &epoch.authorities[authority_index as usize].0;
-
- if expected_author != author {
- let msg = format!("Invalid author: Expected secondary author: {:?}, got: {:?}.",
- expected_author,
- author,
- );
-
- return Err(msg);
- }
-
- if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
- Ok(())
- } else {
- Err(format!("Bad signature on {:?}", pre_hash))
- }
-}
#[derive(Default, Clone)]
struct TimeSource(Arc, Vec<(Instant, u64)>)>>);
@@ -717,7 +493,6 @@ pub struct BabeLink {
epoch_changes: SharedEpochChanges,
config: Config,
}
-
/// A verifier for Babe blocks.
pub struct BabeVerifier {
client: Arc>,
@@ -838,7 +613,7 @@ impl Verifier for BabeVerifier(&header)?;
+ let pre_digest = find_pre_digest::(&header)?;
let epoch = {
let epoch_changes = self.epoch_changes.lock();
epoch_changes.epoch_for_child_of(
@@ -854,22 +629,41 @@ impl Verifier for BabeVerifier(v_params, &self.api)?;
- match checked_header {
+ match verification::check_header::(v_params)? {
CheckedHeader::Checked(pre_header, verified_info) => {
let babe_pre_digest = verified_info.pre_digest.as_babe_pre_digest()
.expect("check_header always returns a pre-digest digest item; qed");
let slot_number = babe_pre_digest.slot_number();
+ let author = verified_info.author;
+
+ // the header is valid but let's check if there was something else already
+ // proposed at the same slot by the given author
+ if let Some(equivocation_proof) = check_equivocation(
+ &*self.api,
+ slot_now,
+ babe_pre_digest.slot_number(),
+ &header,
+ &author,
+ ).map_err(|e| e.to_string())? {
+ info!(
+ "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
+ author,
+ babe_pre_digest.slot_number(),
+ equivocation_proof.fst_header().hash(),
+ equivocation_proof.snd_header().hash(),
+ );
+ }
+
// 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.
@@ -939,193 +733,6 @@ fn register_babe_inherent_data_provider(
}
}
-fn get_keypair(q: &AuthorityPair) -> &Keypair {
- use primitives::crypto::IsWrappedBy;
- primitives::sr25519::Pair::from_ref(q).as_ref()
-}
-
-#[allow(deprecated)]
-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
-}
-
-/// Returns true if the given VRF output is lower than the given threshold,
-/// false otherwise.
-fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool {
- u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold
-}
-
-/// Calculates the primary selection threshold for a given authority, taking
-/// into account `c` (`1 - c` represents the probability of a slot being empty).
-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::() 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())
-}
-
-/// 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.
-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
- }
- })
-}
-
-/// 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::(&a.0).ok().map(|kp| (kp, i))
- })
- {
- let transcript = 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 = calculate_primary_threshold(c, authorities, authority_index);
-
- let pre_digest = get_keypair(&pair)
- .vrf_sign_after_check(transcript, |inout| 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
-}
-
-/// 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.
-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)
-}
-
-/// 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 = 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::(&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
-}
-
/// A block-import handler for BABE.
///
/// This scans each imported block for epoch change signals. The signals are
@@ -1200,7 +807,7 @@ impl BlockImport for BabeBlockImport return Err(ConsensusError::ClientImport(e.to_string()).into()),
}
- let pre_digest = find_pre_digest::(&block.header)
+ let pre_digest = find_pre_digest::(&block.header)
.expect("valid babe headers must contain a predigest; \
header has been already verified; qed");
let slot_number = pre_digest.slot_number();
@@ -1222,7 +829,7 @@ impl BlockImport for BabeBlockImport(&parent_header)
+ let parent_slot = find_pre_digest::(&parent_header)
.map(|d| d.slot_number())
.expect("parent is non-genesis; valid BABE headers contain a pre-digest; \
header has already been verified; qed");
@@ -1475,7 +1082,7 @@ pub mod test_helpers {
|slot| link.config.genesis_epoch(slot),
).unwrap().unwrap();
- super::claim_slot(
+ authorship::claim_slot(
slot_number,
epoch.as_ref(),
&link.config,
diff --git a/substrate/core/consensus/babe/src/tests.rs b/substrate/core/consensus/babe/src/tests.rs
index 32ad826cb1..adfe0f03af 100644
--- a/substrate/core/consensus/babe/src/tests.rs
+++ b/substrate/core/consensus/babe/src/tests.rs
@@ -20,6 +20,7 @@
// https://github.com/paritytech/substrate/issues/2532
#![allow(deprecated)]
use super::*;
+use authorship::claim_slot;
use babe_primitives::{AuthorityPair, SlotNumber};
use client::block_builder::BlockBuilder;
@@ -80,7 +81,7 @@ impl Environment for DummyFactory {
-> Result
{
- let parent_slot = crate::find_pre_digest::(parent_header)
+ let parent_slot = crate::find_pre_digest(parent_header)
.expect("parent header has a pre-digest")
.slot_number();
@@ -97,6 +98,7 @@ impl DummyProposer {
fn propose_with(&mut self, pre_digests: DigestFor)
-> future::Ready>
{
+ use codec::Encode;
let block_builder = self.factory.client.new_block_at(
&BlockId::Hash(self.parent_hash),
pre_digests,
@@ -106,7 +108,7 @@ impl DummyProposer {
Err(e) => return future::ready(Err(e)),
};
- let this_slot = crate::find_pre_digest::(block.header())
+ let this_slot = crate::find_pre_digest(block.header())
.expect("baked block has valid pre-digest")
.slot_number();
diff --git a/substrate/core/consensus/babe/src/verification.rs b/substrate/core/consensus/babe/src/verification.rs
new file mode 100644
index 0000000000..05d6102450
--- /dev/null
+++ b/substrate/core/consensus/babe/src/verification.rs
@@ -0,0 +1,232 @@
+// 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 .
+
+//! 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, BlockT};
+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,
+ /// 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,
+}
+
+macro_rules! babe_err {
+ ($($i: expr),+) => {
+ {
+ debug!(target: "babe", $($i),+);
+ format!($($i),+)
+ }
+ };
+}
+
+/// 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(
+ params: VerificationParams,
+) -> Result>, String> where
+ DigestItemFor: 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::(&header))?;
+
+ trace!(target: "babe", "Checking header");
+ let seal = match header.digest_mut().pop() {
+ Some(x) => x,
+ None => return Err(babe_err!("Header {:?} is unsealed", header.hash())),
+ };
+
+ let sig = seal.as_babe_seal().ok_or_else(|| {
+ babe_err!("Header {:?} has a bad seal", 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!("Slot author not found")),
+ };
+
+ 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::(
+ 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::(
+ pre_hash,
+ digest,
+ sig,
+ &epoch,
+ )?;
+ },
+ _ => {
+ return Err(babe_err!("Secondary slot assignments are disabled for the current epoch."));
+ }
+ }
+
+ let info = VerifiedHeaderInfo {
+ pre_digest: CompatibleDigestItem::babe_pre_digest(pre_digest),
+ seal,
+ author,
+ };
+ Ok(CheckedHeader::Checked(header, info))
+}
+
+pub(super) struct VerifiedHeaderInfo {
+ pub(super) pre_digest: DigestItemFor,
+ pub(super) seal: DigestItemFor,
+ 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(
+ pre_hash: B::Hash,
+ pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber),
+ signature: AuthoritySignature,
+ epoch: &Epoch,
+ c: (u64, u64),
+) -> Result<(), String> {
+ 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!("VRF verification failed: {:?}", s)
+ })?
+ };
+
+ let threshold = calculate_primary_threshold(
+ c,
+ &epoch.authorities,
+ authority_index as usize,
+ );
+
+ if !check_primary_threshold(&inout, threshold) {
+ return Err(babe_err!("VRF verification of block by author {:?} failed: \
+ threshold {} exceeded", author, threshold));
+ }
+
+ Ok(())
+ } else {
+ Err(babe_err!("Bad signature on {:?}", 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(
+ pre_hash: B::Hash,
+ pre_digest: (AuthorityIndex, SlotNumber),
+ signature: AuthoritySignature,
+ epoch: &Epoch,
+) -> Result<(), String> {
+ 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(|| "No secondary author expected.".to_string())?;
+
+ let author = &epoch.authorities[authority_index as usize].0;
+
+ if expected_author != author {
+ let msg = format!("Invalid author: Expected secondary author: {:?}, got: {:?}.",
+ expected_author,
+ author,
+ );
+
+ return Err(msg);
+ }
+
+ if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
+ Ok(())
+ } else {
+ Err(format!("Bad signature on {:?}", pre_hash))
+ }
+}