babe: Introduce secondary slots (Aurababeous) (#3380)

* babe: initial implementation of secondary slots

* babe: validate secondary slot author

* babe: implement weight based fork choice

* babe: remove unused

* aura: cleanup unused imports

* babe: pass in parent weight when authoring and verifying

* babe: use epoch randomness for picking secondary slot authors

* babe: fix tests

* babe: fix wasm build

* babe: node-side code for disabling secondary slots

* babe: allow enabling/disabling secondary slots from runtime

* babe: fix test

* babe: use blake2_256 for secondary slot assignment

* babe: run block initialization in should_end_session

* node: increase slot duration to 6s

* babe: add docs

* node: bump spec_version

* Apply suggestions from code review

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* babe: simplify secondary slot assignment calculation

* babe: remove unnecessary comment

* node: bump spec_version

* babe: fix bad merge
This commit is contained in:
André Silva
2019-08-16 15:47:53 +02:00
committed by GitHub
parent f735d067c4
commit cb7527d2b2
12 changed files with 720 additions and 202 deletions
+4 -4
View File
@@ -1727,7 +1727,7 @@ dependencies = [
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unsigned-varint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2862,7 +2862,7 @@ dependencies = [
"fixed-hash 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"impl-codec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"impl-serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -5586,7 +5586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uint"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -6470,7 +6470,7 @@ dependencies = [
"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874"
"checksum uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5375d2c574f89adad4108ad525c93e39669853a602560bf5ed4ca9943b10799"
"checksum uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8f0f47ed099f0db671ce82c66548c5de012e3c0cba3963514d1db15c7588701"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
"checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+6 -1
View File
@@ -225,7 +225,12 @@ impl<H, B, C, E, I, P, Error, SO> slots::SimpleSlotWorker<B> for AuraWorker<C, E
epoch_data.len()
}
fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim> {
fn claim_slot(
&self,
_header: &B::Header,
slot_number: u64,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim> {
let expected_author = slot_author::<P>(slot_number, epoch_data);
expected_author.and_then(|p| {
@@ -22,7 +22,7 @@ use super::AuthoritySignature;
use super::{BABE_ENGINE_ID, Epoch};
#[cfg(not(feature = "std"))]
use super::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH};
use super::SlotNumber;
use super::{AuthorityIndex, BabeBlockWeight, SlotNumber};
#[cfg(feature = "std")]
use sr_primitives::{DigestItem, generic::OpaqueDigestItemId};
#[cfg(feature = "std")]
@@ -36,18 +36,61 @@ use schnorrkel::{
vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}
};
/// A BABE pre-digest
/// A BABE pre-runtime digest. This contains all data required to validate a
/// block and for the BABE runtime module. Slots can be assigned to a primary
/// (VRF based) and to a secondary (slot number based).
#[cfg(feature = "std")]
#[derive(Clone, Debug)]
pub struct BabePreDigest {
/// VRF output
pub vrf_output: VRFOutput,
/// VRF proof
pub vrf_proof: VRFProof,
/// Authority index
pub authority_index: super::AuthorityIndex,
/// Slot number
pub slot_number: SlotNumber,
pub enum BabePreDigest {
/// A primary VRF-based slot assignment.
Primary {
/// VRF output
vrf_output: VRFOutput,
/// VRF proof
vrf_proof: VRFProof,
/// Authority index
authority_index: super::AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
},
/// A secondary deterministic slot assignment.
Secondary {
/// Authority index
authority_index: super::AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
},
}
#[cfg(feature = "std")]
impl BabePreDigest {
/// Returns the slot number of the pre digest.
pub fn authority_index(&self) -> AuthorityIndex {
match self {
BabePreDigest::Primary { authority_index, .. } => *authority_index,
BabePreDigest::Secondary { authority_index, .. } => *authority_index,
}
}
/// Returns the slot number of the pre digest.
pub fn slot_number(&self) -> SlotNumber {
match self {
BabePreDigest::Primary { slot_number, .. } => *slot_number,
BabePreDigest::Secondary { slot_number, .. } => *slot_number,
}
}
/// Returns the weight of the pre digest.
pub fn weight(&self) -> BabeBlockWeight {
match self {
BabePreDigest::Primary { weight, .. } => *weight,
BabePreDigest::Secondary { weight, .. } => *weight,
}
}
}
/// The prefix used by BABE for its VRF keys.
@@ -55,27 +98,74 @@ pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf";
/// A raw version of `BabePreDigest`, usable on `no_std`.
#[derive(Copy, Clone, Encode, Decode)]
pub struct RawBabePreDigest {
/// Slot number
pub slot_number: SlotNumber,
/// Authority index
pub authority_index: super::AuthorityIndex,
/// VRF output
pub vrf_output: [u8; VRF_OUTPUT_LENGTH],
/// VRF proof
pub vrf_proof: [u8; VRF_PROOF_LENGTH],
pub enum RawBabePreDigest {
/// A primary VRF-based slot assignment.
Primary {
/// Authority index
authority_index: AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
/// VRF output
vrf_output: [u8; VRF_OUTPUT_LENGTH],
/// VRF proof
vrf_proof: [u8; VRF_PROOF_LENGTH],
},
/// A secondary deterministic slot assignment.
Secondary {
/// Authority index
authority_index: AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
},
}
impl RawBabePreDigest {
/// Returns the slot number of the pre digest.
pub fn slot_number(&self) -> SlotNumber {
match self {
RawBabePreDigest::Primary { slot_number, .. } => *slot_number,
RawBabePreDigest::Secondary { slot_number, .. } => *slot_number,
}
}
}
#[cfg(feature = "std")]
impl Encode for BabePreDigest {
fn encode(&self) -> Vec<u8> {
let tmp = RawBabePreDigest {
vrf_output: *self.vrf_output.as_bytes(),
vrf_proof: self.vrf_proof.to_bytes(),
authority_index: self.authority_index,
slot_number: self.slot_number,
let raw = match self {
BabePreDigest::Primary {
vrf_output,
vrf_proof,
authority_index,
slot_number,
weight,
} => {
RawBabePreDigest::Primary {
vrf_output: *vrf_output.as_bytes(),
vrf_proof: vrf_proof.to_bytes(),
authority_index: *authority_index,
slot_number: *slot_number,
weight: *weight,
}
},
BabePreDigest::Secondary {
authority_index,
slot_number,
weight,
} => {
RawBabePreDigest::Secondary {
authority_index: *authority_index,
slot_number: *slot_number,
weight: *weight,
}
},
};
codec::Encode::encode(&tmp)
codec::Encode::encode(&raw)
}
}
@@ -85,19 +175,26 @@ impl codec::EncodeLike for BabePreDigest {}
#[cfg(feature = "std")]
impl Decode for BabePreDigest {
fn decode<R: Input>(i: &mut R) -> Result<Self, Error> {
let RawBabePreDigest { vrf_output, vrf_proof, authority_index, slot_number } = Decode::decode(i)?;
let pre_digest = match Decode::decode(i)? {
RawBabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number, weight } => {
// Verify (at compile time) that the sizes in babe_primitives are correct
let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output;
let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof;
// Verify (at compile time) that the sizes in babe_primitives are correct
let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output;
let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof;
Ok(BabePreDigest {
vrf_proof: VRFProof::from_bytes(&vrf_proof)
.map_err(convert_error)?,
vrf_output: VRFOutput::from_bytes(&vrf_output)
.map_err(convert_error)?,
authority_index,
slot_number,
})
BabePreDigest::Primary {
vrf_proof: VRFProof::from_bytes(&vrf_proof).map_err(convert_error)?,
vrf_output: VRFOutput::from_bytes(&vrf_output).map_err(convert_error)?,
authority_index,
slot_number,
weight,
}
},
RawBabePreDigest::Secondary { authority_index, slot_number, weight } => {
BabePreDigest::Secondary { authority_index, slot_number, weight }
},
};
Ok(pre_digest)
}
}
@@ -68,7 +68,10 @@ pub type SlotNumber = u64;
/// The weight of an authority.
// NOTE: we use a unique name for the weight to avoid conflicts with other
// `Weight` types, since the metadata isn't able to disambiguate.
pub type BabeWeight = u64;
pub type BabeAuthorityWeight = u64;
/// The weight of a BABE block.
pub type BabeBlockWeight = u32;
/// BABE epoch information
#[derive(Decode, Encode, Default, PartialEq, Eq, Clone)]
@@ -81,9 +84,11 @@ pub struct Epoch {
/// The duration of this epoch
pub duration: SlotNumber,
/// The authorities and their weights
pub authorities: Vec<(AuthorityId, BabeWeight)>,
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
/// Randomness for this epoch
pub randomness: [u8; VRF_OUTPUT_LENGTH],
/// Whether secondary slot assignments should be used during the epoch.
pub secondary_slots: bool,
}
/// An consensus log item for BABE.
+460 -132
View File
@@ -14,9 +14,47 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # BABE consensus
//! # BABE (Blind Assignment for Blockchain Extension)
//!
//! BABE (Blind Assignment for Blockchain Extension) consensus in Substrate.
//! BABE is a slot-based block production mechanism which uses a VRF PRNG to
//! randomly perform the slot allocation. On every slot, all the authorities
//! generate a new random number with the VRF function and if it is lower than a
//! given threshold (which is proportional to their weight/stake) they have a
//! right to produce a block. The proof of the VRF function execution will be
//! used by other peer to validate the legitimacy of the slot claim.
//!
//! The engine is also responsible for collecting entropy on-chain which will be
//! used to seed the given VRF PRNG. An epoch is a contiguous number of slots
//! under which we will be using the same authority set. During an epoch all VRF
//! outputs produced as a result of block production will be collected on an
//! on-chain randomness pool. Epoch changes are announced one epoch in advance,
//! i.e. when ending epoch N, we announce the parameters (randomness,
//! authorities, etc.) for epoch N+2.
//!
//! Since the slot assignment is randomized, it is possible that a slot is
//! assigned to multiple validators in which case we will have a temporary fork,
//! or that a slot is assigned to no validator in which case no block is
//! produced. Which means that block times are not deterministic.
//!
//! The protocol has a parameter `c` [0, 1] for which `1 - c` is the probability
//! of a slot being empty. The choice of this parameter affects the security of
//! the protocol relating to maximum tolerable network delays.
//!
//! In addition to the VRF-based slot assignment described above, which we will
//! call primary slots, the engine also supports a deterministic secondary slot
//! assignment. Primary slots take precedence over secondary slots, when
//! authoring the node starts by trying to claim a primary slot and falls back
//! to a secondary slot claim attempt. The secondary slot assignment is done
//! by picking the authority at index:
//!
//! `blake2_256(epoch_randomness ++ slot_number) % authorities_len`.
//!
//! The fork choice rule is weight-based, where weight equals the number of
//! primary blocks in the chain. We will pick the heaviest chain (more primary
//! blocks) and will go with the longest one in case of a tie.
//!
//! An in-depth description and analysis of the protocol can be found here:
//! <https://research.web3.foundation/en/latest/polkadot/BABE/Babe>
#![forbid(unsafe_code, missing_docs)]
pub use babe_primitives::*;
@@ -36,7 +74,7 @@ use sr_primitives::traits::{
use keystore::KeyStorePtr;
use codec::{Decode, Encode};
use parking_lot::{Mutex, MutexGuard};
use primitives::{Blake2Hasher, H256, Pair, Public};
use primitives::{blake2_256, Blake2Hasher, H256, Pair, Public, U256};
use merlin::Transcript;
use inherents::{InherentDataProviders, InherentData};
use substrate_telemetry::{
@@ -47,7 +85,7 @@ use substrate_telemetry::{
use schnorrkel::{
keys::Keypair,
vrf::{
VRFProof, VRFProofBatchable, VRFInOut,
VRFProof, VRFInOut, VRFOutput,
},
};
use consensus_common::{
@@ -238,7 +276,7 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I
Error: std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static,
{
type EpochData = Epoch;
type Claim = (VRFInOut, VRFProof, u32, AuthorityPair);
type Claim = (BabePreDigest, AuthorityPair);
type SyncOracle = SO;
type Proposer = E::Proposer;
type BlockImport = I;
@@ -260,27 +298,29 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I
epoch_data.authorities.len()
}
fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim> {
fn claim_slot(
&self,
header: &B::Header,
slot_number: u64,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim> {
let parent_weight = {
let pre_digest = find_pre_digest::<B>(&header).ok()?;
pre_digest.weight()
};
claim_slot(
slot_number,
parent_weight,
epoch_data,
self.c,
&self.keystore,
).map(|((inout, vrf_proof, _), authority_index, key)| {
(inout, vrf_proof, authority_index as u32, key)
})
)
}
fn pre_digest_data(&self, slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>> {
let inherent_digest = BabePreDigest {
vrf_proof: claim.1.clone(),
vrf_output: claim.0.to_output(),
authority_index: claim.2,
slot_number,
};
fn pre_digest_data(&self, _slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>> {
vec![
<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(inherent_digest),
<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(claim.0.clone()),
]
}
@@ -290,12 +330,15 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I
Vec<B::Extrinsic>,
Self::Claim,
) -> consensus_common::BlockImportParams<B> + Send> {
Box::new(|header, header_hash, body, (_, _, _, pair)| {
Box::new(|header, header_hash, body, (_, pair)| {
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let signature = pair.sign(header_hash.as_ref());
let signature_digest_item = <DigestItemFor<B> as CompatibleDigestItem>::babe_seal(signature);
// When we building our own blocks we always author on top of the
// current best according to `SelectChain`, therefore our own block
// proposal should always become the new best.
BlockImportParams {
origin: BlockOrigin::Own,
header,
@@ -304,7 +347,7 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I
body: Some(body),
finalized: false,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
fork_choice: ForkChoiceStrategy::Custom(true),
}
})
}
@@ -356,6 +399,16 @@ macro_rules! babe_err {
fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<BabePreDigest, String>
where DigestItemFor<B>: CompatibleDigestItem,
{
// 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
if header.number().is_zero() {
return Ok(BabePreDigest::Secondary {
slot_number: 0,
authority_index: 0,
weight: 0,
});
}
let mut pre_digest: Option<_> = None;
for log in header.digest().logs() {
trace!(target: "babe", "Checking log {:?}, looking for pre runtime digest", log);
@@ -394,16 +447,20 @@ fn find_next_epoch_digest<B: BlockT>(header: &B::Header) -> Result<Option<Epoch>
/// 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.
// FIXME #1018 needs misbehavior types. The `transaction_pool` parameter will be
// used to submit such misbehavior reports.
fn check_header<B: BlockT + Sized, C: AuxStore, T>(
client: &C,
slot_now: u64,
mut header: B::Header,
hash: B::Hash,
authorities: &[(AuthorityId, BabeWeight)],
parent_header: B::Header,
slot_now: u64,
authorities: &[(AuthorityId, BabeAuthorityWeight)],
client: &C,
randomness: [u8; 32],
epoch_index: u64,
secondary_slots: bool,
c: (u64, u64),
_transaction_pool: Option<&T>,
) -> Result<CheckedHeader<B::Header, (DigestItemFor<B>, DigestItemFor<B>)>, String> where
@@ -413,68 +470,185 @@ fn check_header<B: BlockT + Sized, C: AuxStore, T>(
trace!(target: "babe", "Checking header");
let seal = match header.digest_mut().pop() {
Some(x) => x,
None => return Err(babe_err!("Header {:?} is unsealed", hash)),
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", hash)
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();
let pre_digest = find_pre_digest::<B>(&header)?;
let BabePreDigest { slot_number, authority_index, ref vrf_proof, ref vrf_output } = pre_digest;
if slot_number > slot_now {
if pre_digest.slot_number() > slot_now {
header.digest_mut().push(seal);
Ok(CheckedHeader::Deferred(header, slot_number))
} else if authority_index > authorities.len() as u32 {
Err(babe_err!("Slot author not found"))
} else {
let (pre_hash, author) = (header.hash(), &authorities[authority_index as usize].0);
return Ok(CheckedHeader::Deferred(header, pre_digest.slot_number()));
}
if AuthorityPair::verify(&sig, pre_hash, &author) {
let (inout, _batchable_proof) = {
let transcript = make_transcript(
&randomness,
slot_number,
epoch_index,
);
if pre_digest.authority_index() > authorities.len() as u32 {
return Err(babe_err!("Slot author not found"));
}
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 parent_weight = {
let parent_pre_digest = find_pre_digest::<B>(&parent_header)?;
parent_pre_digest.weight()
};
let threshold = calculate_threshold(c, authorities, authority_index as usize);
if !check(&inout, threshold) {
return Err(babe_err!("VRF verification of block by author {:?} failed: \
threshold {} exceeded", author, threshold));
}
match &pre_digest {
BabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number, weight } => {
debug!(target: "babe", "Verifying Primary block");
if let Some(equivocation_proof) = check_equivocation(
client,
slot_now,
slot_number,
&header,
author,
).map_err(|e| e.to_string())? {
info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
slot_number,
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
}
let digest = (vrf_output, vrf_proof, *authority_index, *slot_number, *weight);
let pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest);
Ok(CheckedHeader::Checked(header, (pre_digest, seal)))
} else {
Err(babe_err!("Bad signature on {:?}", hash))
check_primary_header::<B>(
pre_hash,
digest,
sig,
parent_weight,
authorities,
randomness,
epoch_index,
c,
)?;
},
BabePreDigest::Secondary { authority_index, slot_number, weight } if secondary_slots => {
debug!(target: "babe", "Verifying Secondary block");
let digest = (*authority_index, *slot_number, *weight);
check_secondary_header::<B>(
pre_hash,
digest,
sig,
parent_weight,
&authorities,
randomness,
)?;
},
_ => {
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())? {
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 pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest);
Ok(CheckedHeader::Checked(header, (pre_digest, seal)))
}
/// 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, BabeBlockWeight),
signature: AuthoritySignature,
parent_weight: BabeBlockWeight,
authorities: &[(AuthorityId, BabeAuthorityWeight)],
randomness: [u8; 32],
epoch_index: u64,
c: (u64, u64),
) -> Result<(), String>
where DigestItemFor<B>: CompatibleDigestItem,
{
let (vrf_output, vrf_proof, authority_index, slot_number, weight) = pre_digest;
if weight != parent_weight + 1 {
return Err("Invalid weight: should increase with Primary block.".into());
}
let author = &authorities[authority_index as usize].0;
if AuthorityPair::verify(&signature, pre_hash, &author) {
let (inout, _) = {
let transcript = make_transcript(
&randomness,
slot_number,
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, 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, BabeBlockWeight),
signature: AuthoritySignature,
parent_weight: BabeBlockWeight,
authorities: &[(AuthorityId, BabeAuthorityWeight)],
randomness: [u8; 32],
) -> Result<(), String> {
let (authority_index, slot_number, weight) = pre_digest;
if weight != parent_weight {
return Err("Invalid weight: Should stay the same with secondary block.".into());
}
// check the signature is valid under the expected authority and
// chain state.
let expected_author = secondary_slot_author(
slot_number,
authorities,
randomness,
).ok_or_else(|| "No secondary author expected.".to_string())?;
let author = &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))
}
}
/// State that must be shared between the import queue and the authoring logic.
@@ -482,22 +656,23 @@ fn check_header<B: BlockT + Sized, C: AuxStore, T>(
pub struct BabeLink(Arc<Mutex<(Option<Duration>, Vec<(Instant, u64)>)>>);
/// A verifier for Babe blocks.
pub struct BabeVerifier<C, T> {
api: Arc<C>,
pub struct BabeVerifier<B, E, Block: BlockT, RA, PRA, T> {
client: Arc<Client<B, E, Block, RA>>,
api: Arc<PRA>,
inherent_data_providers: inherents::InherentDataProviders,
config: Config,
time_source: BabeLink,
transaction_pool: Option<Arc<T>>,
}
impl<C, T> BabeVerifier<C, T> {
fn check_inherents<B: BlockT>(
impl<B, E, Block: BlockT, RA, PRA, T> BabeVerifier<B, E, Block, RA, PRA, T> {
fn check_inherents(
&self,
block: B,
block_id: BlockId<B>,
block: Block,
block_id: BlockId<Block>,
inherent_data: InherentData,
) -> Result<(), String>
where C: ProvideRuntimeApi, C::Api: BlockBuilderApi<B>
where PRA: ProvideRuntimeApi, PRA::Api: BlockBuilderApi<Block>
{
let inherent_res = self.api.runtime_api().check_inherents(
&block_id,
@@ -560,18 +735,22 @@ fn median_algorithm(
}
}
impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
C: ProvideRuntimeApi + Send + Sync + AuxStore + ProvideCache<B>,
C::Api: BlockBuilderApi<B> + BabeApi<B>,
impl<B, E, Block, RA, PRA, T> Verifier<Block> for BabeVerifier<B, E, Block, RA, PRA, T> where
Block: BlockT<Hash=H256>,
B: Backend<Block, Blake2Hasher> + 'static,
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
RA: Send + Sync,
PRA: ProvideRuntimeApi + Send + Sync + AuxStore + ProvideCache<Block>,
PRA::Api: BlockBuilderApi<Block> + BabeApi<Block>,
T: Send + Sync + 'static,
{
fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
header: Block::Header,
justification: Option<Justification>,
mut body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
mut body: Option<Vec<Block::Extrinsic>>,
) -> Result<(BlockImportParams<Block>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
trace!(
target: "babe",
"Verifying origin: {:?} header: {:?} justification: {:?} body: {:?}",
@@ -596,18 +775,23 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
let epoch = epoch(self.api.as_ref(), &BlockId::Hash(parent_hash))
.map_err(|e| format!("Could not fetch epoch at {:?}: {:?}", parent_hash, e))?;
let (epoch, maybe_next_epoch) = epoch.deconstruct();
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
let Epoch { authorities, randomness, epoch_index, secondary_slots, .. } = epoch;
let parent_header = self.client.header(&BlockId::Hash(parent_hash))
.map_err(|e| format!("Could not fetch parent header {:?}: {:?}", parent_hash, e))?
.ok_or_else(|| format!("Parent header {:?} not found.", parent_hash))?;
// We add one to allow for some small drift.
// FIXME #1019 in the future, alter this queue to allow deferring of headers
let mut checked_header = check_header::<B, C, T>(
&self.api,
slot_now + 1,
let mut checked_header = check_header::<Block, PRA, T>(
header.clone(),
hash,
parent_header.clone(),
slot_now + 1,
&authorities,
&self.api,
randomness,
epoch_index,
secondary_slots,
self.config.c(),
self.transaction_pool.as_ref().map(|x| &**x),
);
@@ -617,14 +801,15 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
// (this is only possible on the light client at epoch#0)
if epoch_index == 0 && checked_header.is_err() {
if let Some(Epoch { authorities, randomness, epoch_index, .. }) = maybe_next_epoch {
let checked_header_next = check_header::<B, C, T>(
&self.api,
slot_now + 1,
let checked_header_next = check_header::<Block, PRA, T>(
header,
hash,
parent_header,
slot_now + 1,
&authorities,
&self.api,
randomness,
epoch_index,
secondary_slots,
self.config.c(),
self.transaction_pool.as_ref().map(|x| &**x),
);
@@ -639,15 +824,17 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
let checked_header = checked_header?;
match checked_header {
CheckedHeader::Checked(pre_header, (pre_digest, seal)) => {
let BabePreDigest { slot_number, .. } = pre_digest.as_babe_pre_digest()
let babe_pre_digest = 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();
// if the body is passed through, we need to use the runtime
// to check that the internally-set timestamp in the inherents
// actually matches the slot set in the seal.
if let Some(inner_body) = body.take() {
inherent_data.babe_replace_inherent_data(slot_number);
let block = B::new(pre_header.clone(), inner_body);
let block = Block::new(pre_header.clone(), inner_body);
self.check_inherents(
block.clone(),
@@ -665,6 +852,34 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
"babe.checked_and_importing";
"pre_header" => ?pre_header);
// The fork choice rule is that we pick the heaviest chain (i.e.
// more primary blocks), if there's a tie we go with the longest
// chain.
let new_best = {
let (last_best, last_best_number) = {
#[allow(deprecated)]
let info = self.client.backend().blockchain().info();
(info.best_hash, info.best_number)
};
let best_header = self.client.header(&BlockId::Hash(last_best))
.map_err(|_| "Failed fetching best header")?
.expect("parent_header must be imported; qed");
let best_weight = find_pre_digest::<Block>(&best_header)
.map(|babe_pre_digest| babe_pre_digest.weight())?;
let new_weight = babe_pre_digest.weight();
if new_weight > best_weight {
true
} else if new_weight == best_weight {
*pre_header.number() > last_best_number
} else {
false
}
};
let import_block = BlockImportParams {
origin,
header: pre_header,
@@ -673,7 +888,7 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where
finalized: false,
justification,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
fork_choice: ForkChoiceStrategy::Custom(new_best),
};
Ok((import_block, Default::default()))
@@ -797,13 +1012,17 @@ fn make_transcript(
transcript
}
fn check(inout: &VRFInOut, threshold: u128) -> bool {
/// 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
}
fn calculate_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, BabeWeight)],
authorities: &[(AuthorityId, BabeAuthorityWeight)],
authority_index: usize,
) -> u128 {
use num_bigint::BigUint;
@@ -826,34 +1045,146 @@ fn calculate_threshold(
calc().unwrap_or(u128::max_value())
}
/// Claim a 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`.
/// 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: u64,
Epoch { authorities, randomness, epoch_index, .. }: &Epoch,
slot_number: SlotNumber,
parent_weight: BabeBlockWeight,
epoch: &Epoch,
c: (u64, u64),
keystore: &KeyStorePtr,
) -> Option<((VRFInOut, VRFProof, VRFProofBatchable), usize, AuthorityPair)> {
) -> Option<(BabePreDigest, AuthorityPair)> {
claim_primary_slot(slot_number, parent_weight, epoch, c, keystore)
.or_else(|| {
if epoch.secondary_slots {
claim_secondary_slot(
slot_number,
parent_weight,
&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,
parent_weight: BabeBlockWeight,
epoch: &Epoch,
c: (u64, u64),
keystore: &KeyStorePtr,
) -> Option<(BabePreDigest, AuthorityPair)> {
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
let keystore = keystore.read();
let (key_pair, authority_index) = authorities.iter()
for (pair, authority_index) in authorities.iter()
.enumerate()
.find_map(|(i, a)| {
.flat_map(|(i, a)| {
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
})?;
let transcript = make_transcript(randomness, slot_number, *epoch_index);
})
{
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_threshold(c, authorities, authority_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);
get_keypair(&key_pair)
.vrf_sign_after_check(transcript, |inout| check(inout, threshold))
.map(|s|(s, authority_index, key_pair))
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,
weight: parent_weight + 1,
}
});
// 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,
parent_weight: BabeBlockWeight,
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,
weight: parent_weight,
};
return Some((pre_digest, pair));
}
}
None
}
fn initialize_authorities_cache<B, C>(client: &C) -> Result<(), ConsensusError> where
@@ -1001,8 +1332,7 @@ impl<B, E, Block, I, RA, PRA> BlockImport<Block> for BabeBlockImport<B, E, Block
let pre_digest = find_pre_digest::<Block>(&block.header)
.expect("valid babe headers must contain a predigest; \
header has been already verified; qed");
let BabePreDigest { slot_number, .. } = pre_digest;
slot_number
pre_digest.slot_number()
};
// returns a function for checking whether a block is a descendent of another
@@ -1158,6 +1488,7 @@ pub fn import_queue<B, E, Block: BlockT<Hash=H256>, I, RA, PRA, T>(
initialize_authorities_cache(&*api)?;
let verifier = BabeVerifier {
client: client.clone(),
api: api.clone(),
inherent_data_providers,
time_source: Default::default(),
@@ -1209,9 +1540,9 @@ pub mod test_helpers {
/// Try to claim the given slot and return a `BabePreDigest` if
/// successful.
pub fn claim_slot<B, C>(
client: &C,
at: &BlockId<B>,
slot_number: u64,
parent: &B::Header,
client: &C,
c: (u64, u64),
keystore: &KeyStorePtr,
) -> Option<BabePreDigest> where
@@ -1219,23 +1550,20 @@ pub mod test_helpers {
C: ProvideRuntimeApi + ProvideCache<B>,
C::Api: BabeApi<B>,
{
let epoch = match epoch(client, at).unwrap() {
let epoch = match epoch(client, &BlockId::Hash(parent.hash())).unwrap() {
MaybeSpanEpoch::Regular(epoch) => epoch,
_ => unreachable!("it is always Regular epoch on full nodes"),
};
let weight = find_pre_digest::<B>(parent).ok()
.map(|d| d.weight())?;
super::claim_slot(
slot_number,
weight,
&epoch,
c,
keystore,
).map(|((inout, vrf_proof, _), authority_index, _)| {
BabePreDigest {
vrf_proof,
vrf_output: inout.to_output(),
authority_index: authority_index as u32,
slot_number,
}
})
).map(|(digest, _)| digest)
}
}
+31 -10
View File
@@ -20,14 +20,13 @@
// https://github.com/paritytech/substrate/issues/2532
#![allow(deprecated)]
use super::*;
use sr_primitives::generic::{self, DigestItem};
use babe_primitives::AuthorityPair;
use client::{LongestChain, block_builder::BlockBuilder};
use consensus_common::NoNetwork as DummyOracle;
use network::test::*;
use network::test::{Block as TestBlock, PeersClient};
use sr_primitives::traits::{Block as BlockT, DigestFor};
use sr_primitives::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
use network::config::ProtocolConfig;
use tokio::runtime::current_thread;
use keyring::sr25519::Keyring;
@@ -35,7 +34,8 @@ use client::BlockchainEvents;
use test_client;
use log::debug;
use std::{time::Duration, borrow::Borrow, cell::RefCell};
type Item = generic::DigestItem<Hash>;
type Item = DigestItem<Hash>;
type Error = client::error::Error;
@@ -88,7 +88,14 @@ type TestHeader = <TestBlock as BlockT>::Header;
type TestExtrinsic = <TestBlock as BlockT>::Extrinsic;
pub struct TestVerifier {
inner: BabeVerifier<PeersFullClient, ()>,
inner: BabeVerifier<
test_client::Backend,
test_client::Executor,
TestBlock,
test_client::runtime::RuntimeApi,
PeersFullClient,
(),
>,
mutator: Mutator,
}
@@ -126,9 +133,9 @@ impl TestNetFactory for BabeTestNet {
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig)
-> Self::Verifier
{
let api = client.as_full().expect("only full clients are used in test");
let client = client.as_full().expect("only full clients are used in test");
trace!(target: "babe", "Creating a verifier");
let config = Config::get_or_compute(&*api)
let config = Config::get_or_compute(&*client)
.expect("slot duration available");
let inherent_data_providers = InherentDataProviders::new();
register_babe_inherent_data_provider(
@@ -139,7 +146,8 @@ impl TestNetFactory for BabeTestNet {
TestVerifier {
inner: BabeVerifier {
api,
client: client.clone(),
api: client,
inherent_data_providers,
config,
time_source: Default::default(),
@@ -317,19 +325,32 @@ fn can_author_block() {
.expect("Generates authority pair");
let mut i = 0;
let epoch = Epoch {
let mut epoch = Epoch {
start_slot: 0,
authorities: vec![(pair.public(), 1)],
randomness: [0; 32],
epoch_index: 1,
duration: 100,
secondary_slots: true,
};
let parent_weight = 0;
// with secondary slots enabled it should never be empty
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
None => i += 1,
Some(s) => debug!(target: "babe", "Authored block {:?}", s.0),
}
// otherwise with only vrf-based primary slots we might need to try a couple
// of times.
epoch.secondary_slots = false;
loop {
match claim_slot(i, &epoch.clone(), (3, 10), &keystore) {
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
None => i += 1,
Some(s) => {
debug!(target: "babe", "Authored block {:?}", s.0);
break
break;
}
}
}
+7 -2
View File
@@ -84,7 +84,12 @@ pub trait SimpleSlotWorker<B: BlockT> {
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize;
/// Tries to claim the given slot, returning an object with claim data if successful.
fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim>;
fn claim_slot(
&self,
header: &B::Header,
slot_number: u64,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim>;
/// Return the pre digest data to include in a block authored with the given claim.
fn pre_digest_data(&self, slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>>;
@@ -143,7 +148,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
return Box::pin(future::ready(Ok(())));
}
let claim = match self.claim_slot(slot_number, &epoch_data) {
let claim = match self.claim_slot(&chain_head, slot_number, &epoch_data) {
None => return Box::pin(future::ready(Ok(()))),
Some(claim) => claim,
};
+2
View File
@@ -617,6 +617,7 @@ cfg_if! {
randomness: <srml_babe::Module<Runtime>>::randomness(),
epoch_index: <srml_babe::Module<Runtime>>::epoch_index(),
duration: EpochDuration::get(),
secondary_slots: <srml_babe::Module<Runtime>>::secondary_slots().0,
}
}
}
@@ -831,6 +832,7 @@ cfg_if! {
randomness: <srml_babe::Module<Runtime>>::randomness(),
epoch_index: <srml_babe::Module<Runtime>>::epoch_index(),
duration: EpochDuration::get(),
secondary_slots: <srml_babe::Module<Runtime>>::secondary_slots().0,
}
}
}
+2 -2
View File
@@ -401,9 +401,9 @@ mod tests {
let babe_pre_digest = loop {
inherent_data.replace_data(timestamp::INHERENT_IDENTIFIER, &(slot_num * SLOT_DURATION));
if let Some(babe_pre_digest) = babe::test_helpers::claim_slot(
&*service.client(),
&parent_id,
slot_num,
&parent_header,
&*service.client(),
(278, 1000),
&keystore,
) {
+1 -1
View File
@@ -44,7 +44,7 @@ pub mod time {
pub const MILLISECS_PER_BLOCK: Moment = 6000;
pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000;
pub const SLOT_DURATION: Moment = 1650;
pub const SLOT_DURATION: Moment = 6000;
pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES;
pub const EPOCH_DURATION_IN_SLOTS: u64 = {
+2 -1
View File
@@ -80,7 +80,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to equal spec_version. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 144,
spec_version: 145,
impl_version: 145,
apis: RUNTIME_API_VERSIONS,
};
@@ -559,6 +559,7 @@ impl_runtime_apis! {
epoch_index: Babe::epoch_index(),
randomness: Babe::randomness(),
duration: EpochDuration::get(),
secondary_slots: Babe::secondary_slots().0,
}
}
}
+63 -9
View File
@@ -14,7 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Consensus extension module for BABE consensus.
//! Consensus extension module for BABE consensus. Collects on-chain randomness
//! from VRF outputs and manages epoch transitions.
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unused_must_use, unsafe_code, unused_variables, dead_code)]
@@ -26,14 +27,16 @@ use srml_support::{decl_storage, decl_module, StorageValue, StorageMap, traits::
use timestamp::{OnTimestampSet};
use sr_primitives::{generic::DigestItem, ConsensusEngineId};
use sr_primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon};
use sr_primitives::weights::SimpleDispatchInfo;
#[cfg(feature = "std")]
use timestamp::TimestampInherentData;
use codec::{Encode, Decode};
use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent, MakeFatalError};
#[cfg(feature = "std")]
use inherents::{InherentDataProviders, ProvideInherentData};
use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, BabeWeight, Epoch, RawBabePreDigest};
use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, BabeAuthorityWeight, Epoch, RawBabePreDigest};
pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH};
use system::ensure_root;
/// The BABE inherent identifier.
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot";
@@ -123,7 +126,7 @@ decl_storage! {
pub EpochIndex get(epoch_index): u64;
/// Current epoch authorities.
pub Authorities get(authorities): Vec<(AuthorityId, BabeWeight)>;
pub Authorities get(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>;
/// Slot at which the current epoch started. It is possible that no
/// block was authored at the given slot and the epoch change was
@@ -133,6 +136,14 @@ decl_storage! {
/// Current slot number.
pub CurrentSlot get(current_slot): u64;
/// Whether secondary slots are enabled in case the VRF-based slot is
/// empty for the current epoch and the next epoch, respectively.
pub SecondarySlots get(secondary_slots): (bool, bool) = (true, true);
/// Pending change to enable/disable secondary slots which will be
/// triggered at `current_epoch + 2`.
pub PendingSecondarySlotsChange get(pending_secondary_slots_change): Option<bool> = None;
/// The epoch randomness for the *current* epoch.
///
/// # Security
@@ -168,7 +179,7 @@ decl_storage! {
Initialized get(initialized): Option<bool>;
}
add_extra_genesis {
config(authorities): Vec<(AuthorityId, BabeWeight)>;
config(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>;
build(|
storage: &mut (sr_primitives::StorageOverlay, sr_primitives::ChildrenStorageOverlay),
config: &GenesisConfig
@@ -204,6 +215,20 @@ decl_module! {
fn on_finalize() {
Initialized::kill();
}
/// Sets a pending change to enable / disable secondary slot assignment.
/// The pending change will be set at the end of the current epoch and
/// will be enacted at `current_epoch + 2`.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_pending_secondary_slots_change(origin, change: Option<bool>) {
ensure_root(origin)?;
match change {
Some(change) => PendingSecondarySlotsChange::put(change),
None => {
PendingSecondarySlotsChange::take();
},
}
}
}
}
@@ -222,9 +247,16 @@ impl<T: Trait> FindAuthor<u32> for Module<T> {
{
for (id, mut data) in digests.into_iter() {
if id == BABE_ENGINE_ID {
return Some(RawBabePreDigest::decode(&mut data).ok()?.authority_index);
let pre_digest = RawBabePreDigest::decode(&mut data).ok()?;
return Some(match pre_digest {
RawBabePreDigest::Primary { authority_index, .. } =>
authority_index,
RawBabePreDigest::Secondary { authority_index, .. } =>
authority_index,
});
}
}
return None;
}
}
@@ -302,11 +334,14 @@ impl<T: Trait> Module<T> {
})
{
if EpochStartSlot::get() == 0 {
EpochStartSlot::put(digest.slot_number);
EpochStartSlot::put(digest.slot_number());
}
CurrentSlot::put(digest.slot_number);
Self::deposit_vrf_output(&digest.vrf_output);
CurrentSlot::put(digest.slot_number());
if let RawBabePreDigest::Primary { vrf_output, .. } = digest {
Self::deposit_vrf_output(&vrf_output);
}
return;
}
@@ -331,7 +366,7 @@ impl<T: Trait> Module<T> {
this_randomness
}
fn initialize_authorities(authorities: &[(AuthorityId, BabeWeight)]) {
fn initialize_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) {
if !authorities.is_empty() {
assert!(Authorities::get().is_empty(), "Authorities are already initialized!");
Authorities::put_ref(authorities);
@@ -404,12 +439,31 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
let next_epoch_start_slot = EpochStartSlot::get().saturating_add(T::EpochDuration::get());
let next_randomness = NextRandomness::get();
// Update any pending secondary slots change
let mut secondary_slots = SecondarySlots::get();
// change for E + 1 now becomes change at E
secondary_slots.0 = secondary_slots.1;
if let Some(change) = PendingSecondarySlotsChange::take() {
// if there's a pending change schedule it for E + 1
secondary_slots.1 = change;
} else {
// otherwise E + 1 will have the same value as E
secondary_slots.1 = secondary_slots.0;
}
SecondarySlots::mutate(|secondary| {
*secondary = secondary_slots;
});
let next = Epoch {
epoch_index: next_epoch_index,
start_slot: next_epoch_start_slot,
duration: T::EpochDuration::get(),
authorities: next_authorities,
randomness: next_randomness,
secondary_slots: secondary_slots.1,
};
Self::deposit_consensus(ConsensusLog::NextEpochData(next))