mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 21:41:02 +00:00
BABE refactoring: split out verification (#3658)
* Refactor parts of BABE verification into separate module * Fix silly compiler error * Move more of the verification code to verification.rs * Remove some unused imports * Fix line width * fix testsuite compile error * Fix compile errors in tests * Move authorship-related code to its own files * fix compile errors in tests * Respond to review comments by @rphmeier * improve docs * fix compile error * Add missing doc comment
This commit is contained in:
committed by
Robert Habermeier
parent
3bfcdeb250
commit
8646cd158e
@@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! BABE authority selection and slot claiming.
|
||||||
|
|
||||||
|
use merlin::Transcript;
|
||||||
|
use babe_primitives::{AuthorityId, BabeAuthorityWeight, BABE_ENGINE_ID, BABE_VRF_PREFIX};
|
||||||
|
use babe_primitives::{Epoch, SlotNumber, AuthorityPair, BabePreDigest, BabeConfiguration};
|
||||||
|
use primitives::{U256, blake2_256};
|
||||||
|
use codec::Encode;
|
||||||
|
use schnorrkel::vrf::VRFInOut;
|
||||||
|
use primitives::Pair;
|
||||||
|
use keystore::KeyStorePtr;
|
||||||
|
|
||||||
|
/// Calculates the primary selection threshold for a given authority, taking
|
||||||
|
/// into account `c` (`1 - c` represents the probability of a slot being empty).
|
||||||
|
pub(super) fn calculate_primary_threshold(
|
||||||
|
c: (u64, u64),
|
||||||
|
authorities: &[(AuthorityId, BabeAuthorityWeight)],
|
||||||
|
authority_index: usize,
|
||||||
|
) -> u128 {
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use num_rational::BigRational;
|
||||||
|
use num_traits::{cast::ToPrimitive, identities::One};
|
||||||
|
|
||||||
|
let c = c.0 as f64 / c.1 as f64;
|
||||||
|
|
||||||
|
let theta =
|
||||||
|
authorities[authority_index].1 as f64 /
|
||||||
|
authorities.iter().map(|(_, weight)| weight).sum::<u64>() as f64;
|
||||||
|
|
||||||
|
let calc = || {
|
||||||
|
let p = BigRational::from_float(1f64 - (1f64 - c).powf(theta))?;
|
||||||
|
let numer = p.numer().to_biguint()?;
|
||||||
|
let denom = p.denom().to_biguint()?;
|
||||||
|
((BigUint::one() << 128) * numer / denom).to_u128()
|
||||||
|
};
|
||||||
|
|
||||||
|
calc().unwrap_or(u128::max_value())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given VRF output is lower than the given threshold,
|
||||||
|
/// false otherwise.
|
||||||
|
pub(super) fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool {
|
||||||
|
u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the expected secondary author for the given slot and with given
|
||||||
|
/// authorities. This should always assign the slot to some authority unless the
|
||||||
|
/// authorities list is empty.
|
||||||
|
pub(super) fn secondary_slot_author(
|
||||||
|
slot_number: u64,
|
||||||
|
authorities: &[(AuthorityId, BabeAuthorityWeight)],
|
||||||
|
randomness: [u8; 32],
|
||||||
|
) -> Option<&AuthorityId> {
|
||||||
|
if authorities.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rand = U256::from((randomness, slot_number).using_encoded(blake2_256));
|
||||||
|
|
||||||
|
let authorities_len = U256::from(authorities.len());
|
||||||
|
let idx = rand % authorities_len;
|
||||||
|
|
||||||
|
let expected_author = authorities.get(idx.as_u32() as usize)
|
||||||
|
.expect("authorities not empty; index constrained to list length; \
|
||||||
|
this is a valid index; qed");
|
||||||
|
|
||||||
|
Some(&expected_author.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
pub(super) fn make_transcript(
|
||||||
|
randomness: &[u8],
|
||||||
|
slot_number: u64,
|
||||||
|
epoch: u64,
|
||||||
|
) -> Transcript {
|
||||||
|
let mut transcript = Transcript::new(&BABE_ENGINE_ID);
|
||||||
|
transcript.commit_bytes(b"slot number", &slot_number.to_le_bytes());
|
||||||
|
transcript.commit_bytes(b"current epoch", &epoch.to_le_bytes());
|
||||||
|
transcript.commit_bytes(b"chain randomness", randomness);
|
||||||
|
transcript
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Claim a secondary slot if it is our turn to propose, returning the
|
||||||
|
/// pre-digest to use when authoring the block, or `None` if it is not our turn
|
||||||
|
/// to propose.
|
||||||
|
fn claim_secondary_slot(
|
||||||
|
slot_number: SlotNumber,
|
||||||
|
authorities: &[(AuthorityId, BabeAuthorityWeight)],
|
||||||
|
keystore: &KeyStorePtr,
|
||||||
|
randomness: [u8; 32],
|
||||||
|
) -> Option<(BabePreDigest, AuthorityPair)> {
|
||||||
|
if authorities.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_author = super::authorship::secondary_slot_author(
|
||||||
|
slot_number,
|
||||||
|
authorities,
|
||||||
|
randomness,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let keystore = keystore.read();
|
||||||
|
|
||||||
|
for (pair, authority_index) in authorities.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(i, a)| {
|
||||||
|
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if pair.public() == *expected_author {
|
||||||
|
let pre_digest = BabePreDigest::Secondary {
|
||||||
|
slot_number,
|
||||||
|
authority_index: authority_index as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Some((pre_digest, pair));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to claim the given slot number. This method starts by trying to claim
|
||||||
|
/// a primary VRF based slot. If we are not able to claim it, then if we have
|
||||||
|
/// secondary slots enabled for the given epoch, we will fallback to trying to
|
||||||
|
/// claim a secondary slot.
|
||||||
|
pub(super) fn claim_slot(
|
||||||
|
slot_number: SlotNumber,
|
||||||
|
epoch: &Epoch,
|
||||||
|
config: &BabeConfiguration,
|
||||||
|
keystore: &KeyStorePtr,
|
||||||
|
) -> Option<(BabePreDigest, AuthorityPair)> {
|
||||||
|
claim_primary_slot(slot_number, epoch, config.c, keystore)
|
||||||
|
.or_else(|| {
|
||||||
|
if config.secondary_slots {
|
||||||
|
claim_secondary_slot(
|
||||||
|
slot_number,
|
||||||
|
&epoch.authorities,
|
||||||
|
keystore,
|
||||||
|
epoch.randomness,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_keypair(q: &AuthorityPair) -> &schnorrkel::Keypair {
|
||||||
|
use primitives::crypto::IsWrappedBy;
|
||||||
|
primitives::sr25519::Pair::from_ref(q).as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claim a primary slot if it is our turn. Returns `None` if it is not our turn.
|
||||||
|
/// This hashes the slot number, epoch, genesis hash, and chain randomness into
|
||||||
|
/// the VRF. If the VRF produces a value less than `threshold`, it is our turn,
|
||||||
|
/// so it returns `Some(_)`. Otherwise, it returns `None`.
|
||||||
|
fn claim_primary_slot(
|
||||||
|
slot_number: SlotNumber,
|
||||||
|
epoch: &Epoch,
|
||||||
|
c: (u64, u64),
|
||||||
|
keystore: &KeyStorePtr,
|
||||||
|
) -> Option<(BabePreDigest, AuthorityPair)> {
|
||||||
|
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
|
||||||
|
let keystore = keystore.read();
|
||||||
|
|
||||||
|
for (pair, authority_index) in authorities.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(i, a)| {
|
||||||
|
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let transcript = super::authorship::make_transcript(randomness, slot_number, *epoch_index);
|
||||||
|
|
||||||
|
// Compute the threshold we will use.
|
||||||
|
//
|
||||||
|
// We already checked that authorities contains `key.public()`, so it can't
|
||||||
|
// be empty. Therefore, this division in `calculate_threshold` is safe.
|
||||||
|
let threshold = super::authorship::calculate_primary_threshold(c, authorities, authority_index);
|
||||||
|
|
||||||
|
let pre_digest = get_keypair(&pair)
|
||||||
|
.vrf_sign_after_check(transcript, |inout| super::authorship::check_primary_threshold(inout, threshold))
|
||||||
|
.map(|s| {
|
||||||
|
BabePreDigest::Primary {
|
||||||
|
slot_number,
|
||||||
|
vrf_output: s.0.to_output(),
|
||||||
|
vrf_proof: s.1,
|
||||||
|
authority_index: authority_index as u32,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// early exit on first successful claim
|
||||||
|
if let Some(pre_digest) = pre_digest {
|
||||||
|
return Some((pre_digest, pair));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -72,22 +72,14 @@ use sr_primitives::traits::{
|
|||||||
Zero,
|
Zero,
|
||||||
};
|
};
|
||||||
use keystore::KeyStorePtr;
|
use keystore::KeyStorePtr;
|
||||||
use codec::Encode;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use primitives::{blake2_256, Blake2Hasher, H256, Pair, Public, U256};
|
use primitives::{Blake2Hasher, H256, Pair};
|
||||||
use merlin::Transcript;
|
|
||||||
use inherents::{InherentDataProviders, InherentData};
|
use inherents::{InherentDataProviders, InherentData};
|
||||||
use substrate_telemetry::{
|
use substrate_telemetry::{
|
||||||
telemetry,
|
telemetry,
|
||||||
CONSENSUS_TRACE,
|
CONSENSUS_TRACE,
|
||||||
CONSENSUS_DEBUG,
|
CONSENSUS_DEBUG,
|
||||||
};
|
};
|
||||||
use schnorrkel::{
|
|
||||||
keys::Keypair,
|
|
||||||
vrf::{
|
|
||||||
VRFProof, VRFInOut, VRFOutput,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use consensus_common::{
|
use consensus_common::{
|
||||||
self, BlockImport, Environment, Proposer, BlockCheckParams,
|
self, BlockImport, Environment, Proposer, BlockCheckParams,
|
||||||
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
|
ForkChoiceStrategy, BlockImportParams, BlockOrigin, Error as ConsensusError,
|
||||||
@@ -107,12 +99,12 @@ use client::{
|
|||||||
use slots::{CheckedHeader, check_equivocation};
|
use slots::{CheckedHeader, check_equivocation};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use log::{warn, debug, info, trace};
|
use log::{warn, debug, info, trace};
|
||||||
|
|
||||||
use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible};
|
use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible};
|
||||||
use epoch_changes::descendent_query;
|
use epoch_changes::descendent_query;
|
||||||
|
|
||||||
mod aux_schema;
|
mod aux_schema;
|
||||||
|
mod verification;
|
||||||
mod epoch_changes;
|
mod epoch_changes;
|
||||||
|
mod authorship;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
pub use babe_primitives::{
|
pub use babe_primitives::{
|
||||||
@@ -351,7 +343,7 @@ impl<B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<B, C, E, I
|
|||||||
epoch_data: &Epoch,
|
epoch_data: &Epoch,
|
||||||
) -> Option<Self::Claim> {
|
) -> Option<Self::Claim> {
|
||||||
debug!(target: "babe", "Attempting to claim slot {}", slot_number);
|
debug!(target: "babe", "Attempting to claim slot {}", slot_number);
|
||||||
let s = claim_slot(
|
let s = authorship::claim_slot(
|
||||||
slot_number,
|
slot_number,
|
||||||
epoch_data,
|
epoch_data,
|
||||||
&*self.config,
|
&*self.config,
|
||||||
@@ -434,8 +426,7 @@ impl<B, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<B, C, E, I, SO> where
|
|||||||
|
|
||||||
/// Extract the BABE pre digest from the given header. Pre-runtime digests are
|
/// Extract the BABE pre digest from the given header. Pre-runtime digests are
|
||||||
/// mandatory, the function will return `Err` if none is found.
|
/// mandatory, the function will return `Err` if none is found.
|
||||||
fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<BabePreDigest, String>
|
fn find_pre_digest<H: Header>(header: &H) -> Result<BabePreDigest, String>
|
||||||
where DigestItemFor<B>: CompatibleDigestItem,
|
|
||||||
{
|
{
|
||||||
// genesis block doesn't contain a pre digest so let's generate a
|
// 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
|
// dummy one to not break any invariants in the rest of the code
|
||||||
@@ -477,221 +468,6 @@ fn find_next_epoch_digest<B: BlockT>(header: &B::Header)
|
|||||||
Ok(epoch_digest)
|
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<BabePreDigest>,
|
|
||||||
/// 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<B: BlockT> {
|
|
||||||
pre_digest: DigestItemFor<B>,
|
|
||||||
seal: DigestItemFor<B>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<B: BlockT + Sized, C: AuxStore>(
|
|
||||||
params: VerificationParams<B>,
|
|
||||||
client: &C,
|
|
||||||
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, String> where
|
|
||||||
DigestItemFor<B>: CompatibleDigestItem,
|
|
||||||
{
|
|
||||||
let VerificationParams {
|
|
||||||
mut header,
|
|
||||||
pre_digest,
|
|
||||||
slot_now,
|
|
||||||
epoch,
|
|
||||||
config,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
let authorities = &epoch.authorities;
|
|
||||||
let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::<B>(&header))?;
|
|
||||||
|
|
||||||
trace!(target: "babe", "Checking header");
|
|
||||||
let seal = match header.digest_mut().pop() {
|
|
||||||
Some(x) => x,
|
|
||||||
None => return Err(babe_err!("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::<B>(
|
|
||||||
pre_hash,
|
|
||||||
digest,
|
|
||||||
sig,
|
|
||||||
&epoch,
|
|
||||||
config.c,
|
|
||||||
)?;
|
|
||||||
},
|
|
||||||
BabePreDigest::Secondary { authority_index, slot_number } if config.secondary_slots => {
|
|
||||||
debug!(target: "babe", "Verifying Secondary block");
|
|
||||||
|
|
||||||
let digest = (*authority_index, *slot_number);
|
|
||||||
|
|
||||||
check_secondary_header::<B>(
|
|
||||||
pre_hash,
|
|
||||||
digest,
|
|
||||||
sig,
|
|
||||||
&epoch,
|
|
||||||
)?;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(babe_err!("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<B: BlockT + Sized>(
|
|
||||||
pre_hash: B::Hash,
|
|
||||||
pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber),
|
|
||||||
signature: AuthoritySignature,
|
|
||||||
epoch: &Epoch,
|
|
||||||
c: (u64, u64),
|
|
||||||
) -> Result<(), String>
|
|
||||||
where DigestItemFor<B>: 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<B: BlockT>(
|
|
||||||
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)]
|
#[derive(Default, Clone)]
|
||||||
struct TimeSource(Arc<Mutex<(Option<Duration>, Vec<(Instant, u64)>)>>);
|
struct TimeSource(Arc<Mutex<(Option<Duration>, Vec<(Instant, u64)>)>>);
|
||||||
@@ -717,7 +493,6 @@ pub struct BabeLink<Block: BlockT> {
|
|||||||
epoch_changes: SharedEpochChanges<Block>,
|
epoch_changes: SharedEpochChanges<Block>,
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A verifier for Babe blocks.
|
/// A verifier for Babe blocks.
|
||||||
pub struct BabeVerifier<B, E, Block: BlockT, RA, PRA> {
|
pub struct BabeVerifier<B, E, Block: BlockT, RA, PRA> {
|
||||||
client: Arc<Client<B, E, Block, RA>>,
|
client: Arc<Client<B, E, Block, RA>>,
|
||||||
@@ -838,7 +613,7 @@ impl<B, E, Block, RA, PRA> Verifier<Block> for BabeVerifier<B, E, Block, RA, PRA
|
|||||||
.map_err(|e| format!("Could not fetch parent header {:?}: {:?}", parent_hash, e))?
|
.map_err(|e| format!("Could not fetch parent header {:?}: {:?}", parent_hash, e))?
|
||||||
.ok_or_else(|| format!("Parent header {:?} not found.", parent_hash))?;
|
.ok_or_else(|| format!("Parent header {:?} not found.", parent_hash))?;
|
||||||
|
|
||||||
let pre_digest = find_pre_digest::<Block>(&header)?;
|
let pre_digest = find_pre_digest::<Block::Header>(&header)?;
|
||||||
let epoch = {
|
let epoch = {
|
||||||
let epoch_changes = self.epoch_changes.lock();
|
let epoch_changes = self.epoch_changes.lock();
|
||||||
epoch_changes.epoch_for_child_of(
|
epoch_changes.epoch_for_child_of(
|
||||||
@@ -854,22 +629,41 @@ impl<B, E, Block, RA, PRA> Verifier<Block> for BabeVerifier<B, E, Block, RA, PRA
|
|||||||
|
|
||||||
// We add one to the current slot to allow for some small drift.
|
// We add one to the current slot 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 v_params = VerificationParams {
|
let v_params = verification::VerificationParams {
|
||||||
header,
|
header: header.clone(),
|
||||||
pre_digest: Some(pre_digest.clone()),
|
pre_digest: Some(pre_digest.clone()),
|
||||||
slot_now: slot_now + 1,
|
slot_now: slot_now + 1,
|
||||||
epoch: epoch.as_ref(),
|
epoch: epoch.as_ref(),
|
||||||
config: &self.config,
|
config: &self.config,
|
||||||
};
|
};
|
||||||
let checked_header = check_header::<Block, PRA>(v_params, &self.api)?;
|
|
||||||
|
|
||||||
match checked_header {
|
match verification::check_header::<Block>(v_params)? {
|
||||||
CheckedHeader::Checked(pre_header, verified_info) => {
|
CheckedHeader::Checked(pre_header, verified_info) => {
|
||||||
let babe_pre_digest = verified_info.pre_digest.as_babe_pre_digest()
|
let babe_pre_digest = verified_info.pre_digest.as_babe_pre_digest()
|
||||||
.expect("check_header always returns a pre-digest digest item; qed");
|
.expect("check_header always returns a pre-digest digest item; qed");
|
||||||
|
|
||||||
let slot_number = babe_pre_digest.slot_number();
|
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
|
// if the body is passed through, we need to use the runtime
|
||||||
// to check that the internally-set timestamp in the inherents
|
// to check that the internally-set timestamp in the inherents
|
||||||
// actually matches the slot set in the seal.
|
// 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::<u64>() as f64;
|
|
||||||
|
|
||||||
let calc = || {
|
|
||||||
let p = BigRational::from_float(1f64 - (1f64 - c).powf(theta))?;
|
|
||||||
let numer = p.numer().to_biguint()?;
|
|
||||||
let denom = p.denom().to_biguint()?;
|
|
||||||
((BigUint::one() << 128) * numer / denom).to_u128()
|
|
||||||
};
|
|
||||||
|
|
||||||
calc().unwrap_or(u128::max_value())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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::<AuthorityPair>(&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::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
|
|
||||||
})
|
|
||||||
{
|
|
||||||
if pair.public() == *expected_author {
|
|
||||||
let pre_digest = BabePreDigest::Secondary {
|
|
||||||
slot_number,
|
|
||||||
authority_index: authority_index as u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Some((pre_digest, pair));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A block-import handler for BABE.
|
/// A block-import handler for BABE.
|
||||||
///
|
///
|
||||||
/// This scans each imported block for epoch change signals. The signals are
|
/// This scans each imported block for epoch change signals. The signals are
|
||||||
@@ -1200,7 +807,7 @@ impl<B, E, Block, I, RA, PRA> BlockImport<Block> for BabeBlockImport<B, E, Block
|
|||||||
Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()),
|
Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let pre_digest = find_pre_digest::<Block>(&block.header)
|
let pre_digest = find_pre_digest::<Block::Header>(&block.header)
|
||||||
.expect("valid babe headers must contain a predigest; \
|
.expect("valid babe headers must contain a predigest; \
|
||||||
header has been already verified; qed");
|
header has been already verified; qed");
|
||||||
let slot_number = pre_digest.slot_number();
|
let slot_number = pre_digest.slot_number();
|
||||||
@@ -1222,7 +829,7 @@ impl<B, E, Block, I, RA, PRA> BlockImport<Block> for BabeBlockImport<B, E, Block
|
|||||||
hash
|
hash
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
let parent_slot = find_pre_digest::<Block>(&parent_header)
|
let parent_slot = find_pre_digest::<Block::Header>(&parent_header)
|
||||||
.map(|d| d.slot_number())
|
.map(|d| d.slot_number())
|
||||||
.expect("parent is non-genesis; valid BABE headers contain a pre-digest; \
|
.expect("parent is non-genesis; valid BABE headers contain a pre-digest; \
|
||||||
header has already been verified; qed");
|
header has already been verified; qed");
|
||||||
@@ -1475,7 +1082,7 @@ pub mod test_helpers {
|
|||||||
|slot| link.config.genesis_epoch(slot),
|
|slot| link.config.genesis_epoch(slot),
|
||||||
).unwrap().unwrap();
|
).unwrap().unwrap();
|
||||||
|
|
||||||
super::claim_slot(
|
authorship::claim_slot(
|
||||||
slot_number,
|
slot_number,
|
||||||
epoch.as_ref(),
|
epoch.as_ref(),
|
||||||
&link.config,
|
&link.config,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
// https://github.com/paritytech/substrate/issues/2532
|
// https://github.com/paritytech/substrate/issues/2532
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use authorship::claim_slot;
|
||||||
|
|
||||||
use babe_primitives::{AuthorityPair, SlotNumber};
|
use babe_primitives::{AuthorityPair, SlotNumber};
|
||||||
use client::block_builder::BlockBuilder;
|
use client::block_builder::BlockBuilder;
|
||||||
@@ -80,7 +81,7 @@ impl Environment<TestBlock> for DummyFactory {
|
|||||||
-> Result<DummyProposer, Error>
|
-> Result<DummyProposer, Error>
|
||||||
{
|
{
|
||||||
|
|
||||||
let parent_slot = crate::find_pre_digest::<TestBlock>(parent_header)
|
let parent_slot = crate::find_pre_digest(parent_header)
|
||||||
.expect("parent header has a pre-digest")
|
.expect("parent header has a pre-digest")
|
||||||
.slot_number();
|
.slot_number();
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ impl DummyProposer {
|
|||||||
fn propose_with(&mut self, pre_digests: DigestFor<TestBlock>)
|
fn propose_with(&mut self, pre_digests: DigestFor<TestBlock>)
|
||||||
-> future::Ready<Result<TestBlock, Error>>
|
-> future::Ready<Result<TestBlock, Error>>
|
||||||
{
|
{
|
||||||
|
use codec::Encode;
|
||||||
let block_builder = self.factory.client.new_block_at(
|
let block_builder = self.factory.client.new_block_at(
|
||||||
&BlockId::Hash(self.parent_hash),
|
&BlockId::Hash(self.parent_hash),
|
||||||
pre_digests,
|
pre_digests,
|
||||||
@@ -106,7 +108,7 @@ impl DummyProposer {
|
|||||||
Err(e) => return future::ready(Err(e)),
|
Err(e) => return future::ready(Err(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let this_slot = crate::find_pre_digest::<TestBlock>(block.header())
|
let this_slot = crate::find_pre_digest(block.header())
|
||||||
.expect("baked block has valid pre-digest")
|
.expect("baked block has valid pre-digest")
|
||||||
.slot_number();
|
.slot_number();
|
||||||
|
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Verification for BABE headers.
|
||||||
|
use schnorrkel::vrf::{VRFOutput, VRFProof};
|
||||||
|
use sr_primitives::{traits::Header, traits::DigestItemFor};
|
||||||
|
use primitives::{Pair, Public};
|
||||||
|
use babe_primitives::{Epoch, BabePreDigest, CompatibleDigestItem, AuthorityId};
|
||||||
|
use babe_primitives::{AuthoritySignature, SlotNumber, AuthorityIndex, AuthorityPair};
|
||||||
|
use slots::CheckedHeader;
|
||||||
|
use log::{debug, trace};
|
||||||
|
use super::{find_pre_digest, 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<BabePreDigest>,
|
||||||
|
/// the slot number of the current time.
|
||||||
|
pub(super) slot_now: SlotNumber,
|
||||||
|
/// epoch descriptor of the epoch this block _should_ be under, if it's valid.
|
||||||
|
pub(super) epoch: &'a Epoch,
|
||||||
|
/// genesis config of this BABE chain.
|
||||||
|
pub(super) config: &'a super::Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<B: BlockT + Sized>(
|
||||||
|
params: VerificationParams<B>,
|
||||||
|
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, String> where
|
||||||
|
DigestItemFor<B>: CompatibleDigestItem,
|
||||||
|
{
|
||||||
|
let VerificationParams {
|
||||||
|
mut header,
|
||||||
|
pre_digest,
|
||||||
|
slot_now,
|
||||||
|
epoch,
|
||||||
|
config,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
let authorities = &epoch.authorities;
|
||||||
|
let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::<B::Header>(&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::<B>(
|
||||||
|
pre_hash,
|
||||||
|
digest,
|
||||||
|
sig,
|
||||||
|
&epoch,
|
||||||
|
config.c,
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
BabePreDigest::Secondary { authority_index, slot_number } if config.secondary_slots => {
|
||||||
|
debug!(target: "babe", "Verifying Secondary block");
|
||||||
|
|
||||||
|
let digest = (*authority_index, *slot_number);
|
||||||
|
|
||||||
|
check_secondary_header::<B>(
|
||||||
|
pre_hash,
|
||||||
|
digest,
|
||||||
|
sig,
|
||||||
|
&epoch,
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(babe_err!("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<B: BlockT> {
|
||||||
|
pub(super) pre_digest: DigestItemFor<B>,
|
||||||
|
pub(super) seal: DigestItemFor<B>,
|
||||||
|
pub(super) author: AuthorityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check a primary slot proposal header. We validate that the given header is
|
||||||
|
/// properly signed by the expected authority, and that the contained VRF proof
|
||||||
|
/// is valid. Additionally, the weight of this block must increase compared to
|
||||||
|
/// its parent since it is a primary block.
|
||||||
|
fn check_primary_header<B: BlockT + Sized>(
|
||||||
|
pre_hash: B::Hash,
|
||||||
|
pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber),
|
||||||
|
signature: AuthoritySignature,
|
||||||
|
epoch: &Epoch,
|
||||||
|
c: (u64, u64),
|
||||||
|
) -> Result<(), 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<B: BlockT>(
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user