Fixing BABE epochs to change between blocks (#3583)

* always fetch epoch from runtime

* node integration tests don't test light nodes

* give stand-in full node a FULL role

* rejig babe APIs

* introduce next-epoch-descriptor type

* overhaul srml-BABE epoch logic

* ensure VRF outputs end up in the right epoch-randomness

* rewrite `do_initialize` to remove unnecessary loop

* begin accounting for next epoch in epoch function

* slots passes header to epoch_data

* pass slot_number to SlotWorker::epoch_data

* begin extracting epoch-change logic into its own module

* aux methods for block weight

* aux methods for genesis configuration

* comment-out most, refactor header-check pipeline

* mostly flesh out verifier again

* reinstantiate babe BlockImport implementation

* reinstate import-queue instantiation

* reintroduce slot-worker implementation

* reinstate pretty much all the rest

* move fork-choice logic to BlockImport

* fix some, but not all errors

* patch test-runtime

* make is_descendent of slightly more generic

* get skeleton compiling when passing is_descendent_of

* make descendent-of-builder more succinct

* restore ordering of authority_index / slot_number

* start fiddling with tests

* fix warnings

* improve initialization architecture and handle genesis

* tests use correct block-import

* fix BABE tests

* fix some compiler errors

* fix node-cli compilation

* all crates compile

* bump runtime versions and fix some warnings

* tweak fork-tree search implementation

* do backtracking search in fork-tree

* node-cli integration tests now work

* fix broken assumption in test_connectivity

* babe tests fail for the right reasons.

* test genesis epoch logic for epoch_changes

* test that epochs can change between blocks

* First BABE SRML test

* Testing infrastructure for BABE

Also includes a trivial additional test.

* Apply suggestions from code review

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* A little more test progress

* More work on BABE testing

* Try to get the tests working

* Implement `UintAuthorityId`-based test mocks

* Fix compilation errors

* Adjust to upstream changes

* Block numbers are ignored in BABE epoch calculation

* authority_index() should ignore invalid authorities

* Fix compile error

* Add tests that session transitions happen

* Check if BABE produces logs

It currently does not.

* Fix test suite

This was really nasty, due to a type confusion that showed up as an
off-by-1 buffer error.

* Add additional tests

Most of these were derived from the current output, so they are only
useful to guard against regressions.

* Make the tests more readable

Also bump impl_version.

* Fix excessive line width

* Remove unused imports

* Update srml/babe/src/lib.rs

Co-Authored-By: André Silva <andre.beat@gmail.com>

* try to fix imports

* Fix build errors in test suite

* tests did not pass

* Try to get at least one digest to be output

Currently, the code emits either no digests (if I don’t call
`Session::rotate_session()` or two digests (if I do), which is wrong.

* More tests

They still don’t work, but this should help debugging.

* fix silly error

* Don’t even try to compile a broken test

* remove broken check_epoch test and add one for genesis epoch

* Check that the length of the pre-digests is correct

* Bump `impl_version`

* use epoch_for_descendent_of even for genesis

* account for competing block 1s

* finish srml-babe docs

Co-Authored-By: André Silva <andre.beat@gmail.com>

* address grumbles
This commit is contained in:
Robert Habermeier
2019-09-23 16:03:05 +02:00
committed by GitHub
parent e6d4a76521
commit c200ce757b
34 changed files with 2168 additions and 989 deletions
+2
View File
@@ -3969,6 +3969,7 @@ dependencies = [
"sr-primitives 2.0.0",
"sr-staking-primitives 2.0.0",
"sr-std 2.0.0",
"sr-version 2.0.0",
"srml-session 2.0.0",
"srml-support 2.0.0",
"srml-system 2.0.0",
@@ -3976,6 +3977,7 @@ dependencies = [
"substrate-consensus-babe-primitives 2.0.0",
"substrate-inherents 2.0.0",
"substrate-primitives 2.0.0",
"substrate-test-runtime 2.0.0",
]
[[package]]
+19 -7
View File
@@ -1482,6 +1482,9 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
}
}
/// NOTE: only use this implementation when you are sure there are NO consensus-level BlockImport
/// objects. Otherwise, importing blocks directly into the client would be bypassing
/// important verification work.
impl<'a, B, E, Block, RA> consensus::BlockImport<Block> for &'a Client<B, E, Block, RA> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Clone + Send + Sync,
@@ -1491,6 +1494,13 @@ impl<'a, B, E, Block, RA> consensus::BlockImport<Block> for &'a Client<B, E, Blo
/// Import a checked and validated block. If a justification is provided in
/// `BlockImportParams` then `finalized` *must* be true.
///
/// NOTE: only use this implementation when there are NO consensus-level BlockImport
/// objects. Otherwise, importing blocks directly into the client would be bypassing
/// important verification work.
///
/// If you are not sure that there are no BlockImport objects provided by the consensus
/// algorithm, don't use this function.
fn import_block(
&mut self,
import_block: BlockImportParams<Block>,
@@ -1899,8 +1909,9 @@ where
/// Utility methods for the client.
pub mod utils {
use super::*;
use crate::{backend::Backend, blockchain, error};
use crate::{blockchain, error};
use primitives::H256;
use std::borrow::Borrow;
/// Returns a function for checking block ancestry, the returned function will
/// return `true` if the given hash (second parameter) is a descendent of the
@@ -1908,16 +1919,17 @@ pub mod utils {
/// represent the current block `hash` and its `parent hash`, if given the
/// function that's returned will assume that `hash` isn't part of the local DB
/// yet, and all searches in the DB will instead reference the parent.
pub fn is_descendent_of<'a, B, E, Block: BlockT<Hash=H256>, RA>(
client: &'a Client<B, E, Block, RA>,
current: Option<(&'a H256, &'a H256)>,
pub fn is_descendent_of<'a, Block: BlockT<Hash=H256>, T, H: Borrow<H256> + 'a>(
client: &'a T,
current: Option<(H, H)>,
) -> impl Fn(&H256, &H256) -> Result<bool, error::Error> + 'a
where B: Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
where T: ChainHeaderBackend<Block>,
{
move |base, hash| {
if base == hash { return Ok(false); }
let current = current.as_ref().map(|(c, p)| (c.borrow(), p.borrow()));
let mut hash = hash;
if let Some((current_hash, current_parent_hash)) = current {
if base == current_hash { return Ok(false); }
@@ -1931,7 +1943,7 @@ pub mod utils {
}
let tree_route = blockchain::tree_route(
|id| client.header(&id)?.ok_or_else(|| Error::UnknownBlock(format!("{:?}", id))),
|id| client.header(id)?.ok_or_else(|| Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(*hash),
BlockId::Hash(*base),
)?;
+3 -3
View File
@@ -218,8 +218,8 @@ impl<H, B, C, E, I, P, Error, SO> slots::SimpleSlotWorker<B> for AuraWorker<C, E
self.block_import.clone()
}
fn epoch_data(&self, block: &B::Hash) -> Result<Self::EpochData, consensus_common::Error> {
authorities(self.client.as_ref(), &BlockId::Hash(*block))
fn epoch_data(&self, header: &B::Header, _slot_number: u64) -> Result<Self::EpochData, consensus_common::Error> {
authorities(self.client.as_ref(), &BlockId::Hash(header.hash()))
}
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize {
@@ -741,7 +741,7 @@ mod tests {
}
}
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig)
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig, _peer_data: &())
-> Self::Verifier
{
match client {
@@ -17,12 +17,10 @@
//! Private implementation details of BABE digests.
#[cfg(feature = "std")]
use super::AuthoritySignature;
#[cfg(feature = "std")]
use super::{BABE_ENGINE_ID, Epoch};
use super::{BABE_ENGINE_ID, AuthoritySignature};
#[cfg(not(feature = "std"))]
use super::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH};
use super::{AuthorityIndex, BabeBlockWeight, SlotNumber};
use super::{AuthorityId, AuthorityIndex, SlotNumber, BabeAuthorityWeight};
#[cfg(feature = "std")]
use sr_primitives::{DigestItem, generic::OpaqueDigestItemId};
#[cfg(feature = "std")]
@@ -35,6 +33,8 @@ use schnorrkel::{
SignatureError, errors::MultiSignatureStage,
vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}
};
use rstd::vec::Vec;
/// 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
@@ -52,8 +52,6 @@ pub enum BabePreDigest {
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 {
@@ -61,8 +59,6 @@ pub enum BabePreDigest {
authority_index: super::AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
},
}
@@ -84,11 +80,12 @@ impl BabePreDigest {
}
}
/// Returns the weight of the pre digest.
pub fn weight(&self) -> BabeBlockWeight {
/// Returns the weight _added_ by this digest, not the cumulative weight
/// of the chain.
pub fn added_weight(&self) -> crate::BabeBlockWeight {
match self {
BabePreDigest::Primary { weight, .. } => *weight,
BabePreDigest::Secondary { weight, .. } => *weight,
BabePreDigest::Primary { .. } => 1,
BabePreDigest::Secondary { .. } => 0,
}
}
}
@@ -100,26 +97,29 @@ pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf";
#[derive(Copy, Clone, Encode, Decode)]
pub enum RawBabePreDigest {
/// A primary VRF-based slot assignment.
#[codec(index = "1")]
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.
#[codec(index = "2")]
Secondary {
/// Authority index
///
/// This is not strictly-speaking necessary, since the secondary slots
/// are assigned based on slot number and epoch randomness. But including
/// it makes things easier for higher-level users of the chain data to
/// be aware of the author of a secondary-slot block.
authority_index: AuthorityIndex,
/// Slot number
slot_number: SlotNumber,
/// Chain weight (measured in number of Primary blocks)
weight: BabeBlockWeight,
},
}
@@ -142,25 +142,21 @@ impl Encode for BabePreDigest {
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,
}
},
};
@@ -176,7 +172,7 @@ impl codec::EncodeLike for BabePreDigest {}
impl Decode for BabePreDigest {
fn decode<R: Input>(i: &mut R) -> Result<Self, Error> {
let pre_digest = match Decode::decode(i)? {
RawBabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number, weight } => {
RawBabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number } => {
// 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;
@@ -186,11 +182,10 @@ impl Decode for BabePreDigest {
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 }
RawBabePreDigest::Secondary { authority_index, slot_number } => {
BabePreDigest::Secondary { authority_index, slot_number }
},
};
@@ -198,6 +193,18 @@ impl Decode for BabePreDigest {
}
}
/// Information about the next epoch. This is broadcast in the first block
/// of the epoch.
#[derive(Decode, Encode, Default, PartialEq, Eq, Clone)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
pub struct NextEpochDescriptor {
/// The authorities.
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
/// The value of randomness to use for the slot-assignment.
pub randomness: [u8; VRF_OUTPUT_LENGTH],
}
/// A digest item which is usable with BABE consensus.
#[cfg(feature = "std")]
pub trait CompatibleDigestItem: Sized {
@@ -214,7 +221,7 @@ pub trait CompatibleDigestItem: Sized {
fn as_babe_seal(&self) -> Option<AuthoritySignature>;
/// If this item is a BABE epoch, return it.
fn as_babe_epoch(&self) -> Option<Epoch>;
fn as_next_epoch_descriptor(&self) -> Option<NextEpochDescriptor>;
}
#[cfg(feature = "std")]
@@ -237,8 +244,12 @@ impl<Hash> CompatibleDigestItem for DigestItem<Hash> where
self.try_to(OpaqueDigestItemId::Seal(&BABE_ENGINE_ID))
}
fn as_babe_epoch(&self) -> Option<Epoch> {
fn as_next_epoch_descriptor(&self) -> Option<NextEpochDescriptor> {
self.try_to(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID))
.and_then(|x: super::ConsensusLog| match x {
super::ConsensusLog::NextEpochData(n) => Some(n),
_ => None,
})
}
}
@@ -28,7 +28,7 @@ use substrate_client::decl_runtime_apis;
#[cfg(feature = "std")]
pub use digest::{BabePreDigest, CompatibleDigestItem};
pub use digest::{BABE_VRF_PREFIX, RawBabePreDigest};
pub use digest::{BABE_VRF_PREFIX, RawBabePreDigest, NextEpochDescriptor};
mod app {
use app_crypto::{app_crypto, key_types::BABE, sr25519};
@@ -59,6 +59,11 @@ pub const VRF_PROOF_LENGTH: usize = 64;
/// The length of the public key
pub const PUBLIC_KEY_LENGTH: usize = 32;
/// How many blocks to wait before running the median algorithm for relative time
/// This will not vary from chain to chain as it is not dependent on slot duration
/// or epoch length.
pub const MEDIAN_ALGORITHM_CARDINALITY: usize = 1200; // arbitrary suggestion by w3f-research.
/// The index of an authority.
pub type AuthorityIndex = u32;
@@ -80,33 +85,50 @@ pub struct Epoch {
/// The epoch index
pub epoch_index: u64,
/// The starting slot of the epoch,
pub start_slot: u64,
pub start_slot: SlotNumber,
/// The duration of this epoch
pub duration: SlotNumber,
/// The authorities and their weights
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,
}
impl Epoch {
/// "increment" the epoch, with given descriptor for the next.
pub fn increment(&self, descriptor: NextEpochDescriptor) -> Epoch {
Epoch {
epoch_index: self.epoch_index + 1,
start_slot: self.start_slot + self.duration,
duration: self.duration,
authorities: descriptor.authorities,
randomness: descriptor.randomness,
}
}
/// Produce the "end slot" of the epoch. This is NOT inclusive to the epoch,
// i.e. the slots covered by the epoch are `self.start_slot .. self.end_slot()`.
pub fn end_slot(&self) -> SlotNumber {
self.start_slot + self.duration
}
}
/// An consensus log item for BABE.
#[derive(Decode, Encode, Clone, PartialEq, Eq)]
pub enum ConsensusLog {
/// The epoch has changed. This provides information about the
/// epoch _after_ next: what slot number it will start at, who are the authorities (and their weights)
/// and the next epoch randomness. The information for the _next_ epoch should already
/// be available.
/// The epoch has changed. This provides information about the _next_
/// epoch - information about the _current_ epoch (i.e. the one we've just
/// entered) should already be available earlier in the chain.
#[codec(index = "1")]
NextEpochData(Epoch),
NextEpochData(NextEpochDescriptor),
/// Disable the authority with given index.
#[codec(index = "2")]
OnDisabled(AuthorityIndex),
}
/// Configuration data used by the BABE consensus engine.
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
pub struct BabeConfiguration {
/// The slot duration in milliseconds for BABE. Currently, only
/// the value provided by this type at genesis will be used.
@@ -114,35 +136,35 @@ pub struct BabeConfiguration {
/// Dynamic slot duration may be supported in the future.
pub slot_duration: u64,
/// The duration of epochs in slots.
pub epoch_length: SlotNumber,
/// A constant value that is used in the threshold calculation formula.
/// Expressed as a fraction where the first member of the tuple is the
/// numerator and the second is the denominator. The fraction should
/// Expressed as a rational where the first member of the tuple is the
/// numerator and the second is the denominator. The rational should
/// represent a value between 0 and 1.
/// In the threshold formula calculation, `1 - c` represents the probability
/// of a slot being empty.
pub c: (u64, u64),
/// The minimum number of blocks that must be received before running the
/// median algorithm to compute the offset between the on-chain time and the
/// local time. Currently, only the value provided by this type at genesis
/// will be used, but this is subject to change.
///
/// Blocks less than `self.median_required_blocks` must be generated by an
/// *initial validator* ― that is, a node that was a validator at genesis.
pub median_required_blocks: u64,
/// The authorities for the genesis epoch.
pub genesis_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
/// The randomness for the genesis epoch.
pub randomness: [u8; VRF_OUTPUT_LENGTH],
/// Whether this chain should run with secondary slots, which are assigned
/// in round-robin manner.
pub secondary_slots: bool,
}
#[cfg(feature = "std")]
impl slots::SlotData for BabeConfiguration {
/// Return the slot duration in milliseconds for BABE. Currently, only
/// the value provided by this type at genesis will be used.
///
/// Dynamic slot duration may be supported in the future.
fn slot_duration(&self) -> u64 {
self.slot_duration
}
const SLOT_KEY: &'static [u8] = b"babe_bootstrap_data";
const SLOT_KEY: &'static [u8] = b"babe_configuration";
}
decl_runtime_apis! {
@@ -152,9 +174,6 @@ decl_runtime_apis! {
/// only the value provided by this type at genesis will be used.
///
/// Dynamic configuration may be supported in the future.
fn startup_data() -> BabeConfiguration;
/// Get the current epoch data for Babe.
fn epoch() -> Epoch;
fn configuration() -> BabeConfiguration;
}
}
@@ -22,11 +22,16 @@ use codec::{Decode, Encode};
use client::backend::AuxStore;
use client::error::{Result as ClientResult, Error as ClientError};
use sr_primitives::traits::Block as BlockT;
use babe_primitives::BabeBlockWeight;
use super::{EpochChanges, SharedEpochChanges};
use super::{epoch_changes::EpochChangesFor, SharedEpochChanges};
const BABE_EPOCH_CHANGES: &[u8] = b"babe_epoch_changes";
fn block_weight_key<H: Encode>(block_hash: H) -> Vec<u8> {
(b"block_weight", block_hash).encode()
}
fn load_decode<B, T>(backend: &B, key: &[u8]) -> ClientResult<Option<T>>
where
B: AuxStore,
@@ -45,7 +50,7 @@ fn load_decode<B, T>(backend: &B, key: &[u8]) -> ClientResult<Option<T>>
pub(crate) fn load_epoch_changes<Block: BlockT, B: AuxStore>(
backend: &B,
) -> ClientResult<SharedEpochChanges<Block>> {
let epoch_changes = load_decode::<_, EpochChanges<Block>>(backend, BABE_EPOCH_CHANGES)?
let epoch_changes = load_decode::<_, EpochChangesFor<Block>>(backend, BABE_EPOCH_CHANGES)?
.map(Into::into)
.unwrap_or_else(|| {
info!(target: "babe",
@@ -59,7 +64,7 @@ pub(crate) fn load_epoch_changes<Block: BlockT, B: AuxStore>(
/// Update the epoch changes on disk after a change.
pub(crate) fn write_epoch_changes<Block: BlockT, F, R>(
epoch_changes: &EpochChanges<Block>,
epoch_changes: &EpochChangesFor<Block>,
write_aux: F,
) -> R where
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
@@ -69,3 +74,28 @@ pub(crate) fn write_epoch_changes<Block: BlockT, F, R>(
&[(BABE_EPOCH_CHANGES, encoded_epoch_changes.as_slice())],
)
}
/// Write the cumulative chain-weight of a block ot aux storage.
pub(crate) fn write_block_weight<H: Encode, F, R>(
block_hash: H,
block_weight: &BabeBlockWeight,
write_aux: F,
) -> R where
F: FnOnce(&[(Vec<u8>, &[u8])]) -> R,
{
let key = block_weight_key(block_hash);
block_weight.using_encoded(|s|
write_aux(
&[(key, s)],
)
)
}
/// Load the cumulative chain-weight associated with a block.
pub(crate) fn load_block_weight<H: Encode, B: AuxStore>(
backend: &B,
block_hash: H,
) -> ClientResult<Option<BabeBlockWeight>> {
load_decode(backend, block_weight_key(block_hash).as_slice())
}
@@ -0,0 +1,637 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Handling epoch changes in BABE.
//!
//! This exposes the `SharedEpochChanges`, which is a wrapper around a
//! persistent DAG superimposed over the forks of the blockchain.
use std::sync::Arc;
use babe_primitives::{Epoch, SlotNumber, NextEpochDescriptor};
use fork_tree::ForkTree;
use parking_lot::{Mutex, MutexGuard};
use sr_primitives::traits::{Block as BlockT, NumberFor, One, Zero};
use codec::{Encode, Decode};
use client::error::Error as ClientError;
use client::utils as client_utils;
use client::blockchain::HeaderBackend;
use primitives::H256;
use std::ops::Add;
/// A builder for `is_descendent_of` functions.
pub trait IsDescendentOfBuilder<Hash> {
/// The error returned by the function.
type Error: std::error::Error;
/// A function that can tell you if the second parameter is a descendent of
/// the first.
type IsDescendentOf: Fn(&Hash, &Hash) -> Result<bool, Self::Error>;
/// Build an `is_descendent_of` function.
///
/// The `current` parameter can be `Some` with the details a fresh block whose
/// details aren't yet stored, but its parent is.
///
/// The format of `current` when `Some` is `(current, current_parent)`.
fn build_is_descendent_of(&self, current: Option<(Hash, Hash)>)
-> Self::IsDescendentOf;
}
/// Produce a descendent query object given the client.
pub(crate) fn descendent_query<H, Block>(client: &H) -> HeaderBackendDescendentBuilder<&H, Block> {
HeaderBackendDescendentBuilder(client, std::marker::PhantomData)
}
/// Wrapper to get around unconstrained type errors when implementing
/// `IsDescendentOfBuilder` for header backends.
pub(crate) struct HeaderBackendDescendentBuilder<H, Block>(H, std::marker::PhantomData<Block>);
// TODO: relying on Hash = H256 is awful.
// https://github.com/paritytech/substrate/issues/3624
impl<'a, H, Block> IsDescendentOfBuilder<H256>
for HeaderBackendDescendentBuilder<&'a H, Block> where
H: HeaderBackend<Block>,
Block: BlockT<Hash = H256>,
{
type Error = ClientError;
type IsDescendentOf = Box<dyn Fn(&H256, &H256) -> Result<bool, ClientError> + 'a>;
fn build_is_descendent_of(&self, current: Option<(H256, H256)>)
-> Self::IsDescendentOf
{
Box::new(client_utils::is_descendent_of(self.0, current))
}
}
/// An unimported genesis epoch.
pub struct UnimportedGenesis(Epoch);
/// The viable epoch under which a block can be verified.
///
/// If this is the first non-genesis block in the chain, then it will
/// hold an `UnimportedGenesis` epoch.
pub enum ViableEpoch {
Genesis(UnimportedGenesis),
Regular(Epoch),
}
impl From<Epoch> for ViableEpoch {
fn from(epoch: Epoch) -> ViableEpoch {
ViableEpoch::Regular(epoch)
}
}
impl AsRef<Epoch> for ViableEpoch {
fn as_ref(&self) -> &Epoch {
match *self {
ViableEpoch::Genesis(UnimportedGenesis(ref e)) => e,
ViableEpoch::Regular(ref e) => e,
}
}
}
impl ViableEpoch {
/// Extract the underlying epoch, disregarding the fact that a genesis
/// epoch may be unimported.
pub fn into_inner(self) -> Epoch {
match self {
ViableEpoch::Genesis(UnimportedGenesis(e)) => e,
ViableEpoch::Regular(e) => e,
}
}
/// Increment the epoch, yielding an `IncrementedEpoch` to be imported
/// into the fork-tree.
pub fn increment(&self, next_descriptor: NextEpochDescriptor) -> IncrementedEpoch {
let next = self.as_ref().increment(next_descriptor);
let to_persist = match *self {
ViableEpoch::Genesis(UnimportedGenesis(ref epoch_0)) =>
PersistedEpoch::Genesis(epoch_0.clone(), next),
ViableEpoch::Regular(_) => PersistedEpoch::Regular(next),
};
IncrementedEpoch(to_persist)
}
}
/// The datatype encoded on disk.
// This really shouldn't be public, but the encode/decode derives force it to be.
#[derive(Clone, Encode, Decode)]
pub enum PersistedEpoch {
// epoch_0, epoch_1,
Genesis(Epoch, Epoch),
// epoch_n
Regular(Epoch),
}
/// A fresh, incremented epoch to import into the underlying fork-tree.
///
/// Create this with `ViableEpoch::increment`.
#[must_use = "Freshly-incremented epoch must be imported with `EpochChanges::import`"]
pub struct IncrementedEpoch(PersistedEpoch);
impl AsRef<Epoch> for IncrementedEpoch {
fn as_ref(&self) -> &Epoch {
match self.0 {
PersistedEpoch::Genesis(_, ref epoch_1) => epoch_1,
PersistedEpoch::Regular(ref epoch_n) => epoch_n,
}
}
}
/// Tree of all epoch changes across all *seen* forks. Data stored in tree is
/// the hash and block number of the block signaling the epoch change, and the
/// epoch that was signalled at that block.
///
/// BABE special-cases the first epoch, epoch_0, by saying that it starts at
/// slot number of the first block in the chain. When bootstrapping a chain,
/// there can be multiple competing block #1s, so we have to ensure that the overlayed
/// DAG doesn't get confused.
///
/// The first block of every epoch should be producing a descriptor for the next
/// epoch - this is checked in higher-level code. So the first block of epoch_0 contains
/// a descriptor for epoch_1. We special-case these and bundle them together in the
/// same DAG entry, pinned to a specific block #1.
///
/// Further epochs (epoch_2, ..., epoch_n) each get their own entry.
#[derive(Clone, Encode, Decode)]
pub struct EpochChanges<Hash, Number> {
inner: ForkTree<Hash, Number, PersistedEpoch>,
}
// create a fake header hash which hasn't been included in the chain.
fn fake_head_hash<H: AsRef<[u8]> + AsMut<[u8]> + Clone>(parent_hash: &H) -> H {
let mut h = parent_hash.clone();
// dirty trick: flip the first bit of the parent hash to create a hash
// which has not been in the chain before (assuming a strong hash function).
h.as_mut()[0] ^= 0b10000000;
h
}
impl<Hash, Number> EpochChanges<Hash, Number> where
Hash: PartialEq + AsRef<[u8]> + AsMut<[u8]> + Copy,
Number: Ord + One + Zero + Add<Output=Number> + Copy,
{
/// Create a new epoch-change tracker.
fn new() -> Self {
EpochChanges { inner: ForkTree::new() }
}
/// Prune out finalized epochs, except for the ancestor of the finalized block.
pub fn prune_finalized<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
_hash: &Hash,
_number: Number,
) -> Result<(), fork_tree::Error<D::Error>> {
let _is_descendent_of = descendent_of_builder
.build_is_descendent_of(None);
// TODO:
// https://github.com/paritytech/substrate/issues/3651
//
// prune any epochs which could not be _live_ as of the children of the
// finalized block.
// i.e. re-root the fork tree to the oldest ancestor of (hash, number)
// where epoch.end_slot() >= slot(hash)
Ok(())
}
/// Finds the epoch for a child of the given block, assuming the given slot number.
///
/// If the returned epoch is an `UnimportedGenesis` epoch, it should be imported into the
/// tree.
pub fn epoch_for_child_of<D: IsDescendentOfBuilder<Hash>, G>(
&self,
descendent_of_builder: D,
parent_hash: &Hash,
parent_number: Number,
slot_number: SlotNumber,
make_genesis: G,
) -> Result<Option<ViableEpoch>, fork_tree::Error<D::Error>>
where G: FnOnce(SlotNumber) -> Epoch
{
// find_node_where will give you the node in the fork-tree which is an ancestor
// of the `parent_hash` by default. if the last epoch was signalled at the parent_hash,
// then it won't be returned. we need to create a new fake chain head hash which
// "descends" from our parent-hash.
let fake_head_hash = fake_head_hash(parent_hash);
let is_descendent_of = descendent_of_builder
.build_is_descendent_of(Some((fake_head_hash, *parent_hash)));
if parent_number == Zero::zero() {
// need to insert the genesis epoch.
let genesis_epoch = make_genesis(slot_number);
return Ok(Some(ViableEpoch::Genesis(UnimportedGenesis(genesis_epoch))));
}
// We want to find the deepest node in the tree which is an ancestor
// of our block and where the start slot of the epoch was before the
// slot of our block. The genesis special-case doesn't need to look
// at epoch_1 -- all we're doing here is figuring out which node
// we need.
let predicate = |epoch: &PersistedEpoch| match *epoch {
PersistedEpoch::Genesis(ref epoch_0, _) =>
epoch_0.start_slot <= slot_number,
PersistedEpoch::Regular(ref epoch_n) =>
epoch_n.start_slot <= slot_number,
};
self.inner.find_node_where(
&fake_head_hash,
&(parent_number + One::one()),
&is_descendent_of,
&predicate,
)
.map(|n| n.map(|node| ViableEpoch::Regular(match node.data {
// Ok, we found our node.
// and here we figure out which of the internal epochs
// of a genesis node to use based on their start slot.
PersistedEpoch::Genesis(ref epoch_0, ref epoch_1) =>
if epoch_1.start_slot <= slot_number {
epoch_1.clone()
} else {
epoch_0.clone()
},
PersistedEpoch::Regular(ref epoch_n) => epoch_n.clone(),
})))
}
/// Import a new epoch-change, signalled at the given block.
///
/// This assumes that the given block is prospective (i.e. has not been
/// imported yet), but its parent has. This is why the parent hash needs
/// to be provided.
pub fn import<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: Hash,
number: Number,
parent_hash: Hash,
epoch: IncrementedEpoch,
) -> Result<(), fork_tree::Error<D::Error>> {
let is_descendent_of = descendent_of_builder
.build_is_descendent_of(Some((hash, parent_hash)));
let res = self.inner.import(
hash,
number,
epoch.0,
&is_descendent_of,
);
match res {
Ok(_) | Err(fork_tree::Error::Duplicate) => Ok(()),
Err(e) => Err(e),
}
}
}
/// Type alias to produce the epoch-changes tree from a block type.
pub type EpochChangesFor<Block> = EpochChanges<<Block as BlockT>::Hash, NumberFor<Block>>;
/// A shared epoch changes tree.
#[derive(Clone)]
pub struct SharedEpochChanges<Block: BlockT> {
inner: Arc<Mutex<EpochChangesFor<Block>>>,
}
impl<Block: BlockT> SharedEpochChanges<Block> {
/// Create a new instance of the `SharedEpochChanges`.
pub fn new() -> Self {
SharedEpochChanges {
inner: Arc::new(Mutex::new(EpochChanges::<_, _>::new()))
}
}
/// Lock the shared epoch changes,
pub fn lock(&self) -> MutexGuard<EpochChangesFor<Block>> {
self.inner.lock()
}
}
impl<Block: BlockT> From<EpochChangesFor<Block>> for SharedEpochChanges<Block> {
fn from(epoch_changes: EpochChangesFor<Block>) -> Self {
SharedEpochChanges {
inner: Arc::new(Mutex::new(epoch_changes))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
pub struct TestError;
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "TestError")
}
}
impl std::error::Error for TestError {}
impl<'a, F: 'a , H: 'a + PartialEq + std::fmt::Debug> IsDescendentOfBuilder<H> for &'a F
where F: Fn(&H, &H) -> Result<bool, TestError>
{
type Error = TestError;
type IsDescendentOf = Box<dyn Fn(&H, &H) -> Result<bool, TestError> + 'a>;
fn build_is_descendent_of(&self, current: Option<(H, H)>)
-> Self::IsDescendentOf
{
let f = *self;
Box::new(move |base, head| {
let mut head = head;
if let Some((ref c_head, ref c_parent)) = current {
if head == c_head {
if base == c_parent {
return Ok(true);
} else {
head = c_parent;
}
}
}
f(base, head)
})
}
}
type Hash = [u8; 1];
#[test]
fn genesis_epoch_is_created_but_not_imported() {
//
// A - B
// \
// — C
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration: 100,
authorities: Vec::new(),
randomness: [0; 32],
};
let epoch_changes = EpochChanges::new();
let genesis_epoch = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
10101,
&make_genesis,
).unwrap().unwrap();
match genesis_epoch {
ViableEpoch::Genesis(_) => {},
_ => panic!("should be unimported genesis"),
};
assert_eq!(genesis_epoch.as_ref(), &make_genesis(10101));
let genesis_epoch_2 = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
10102,
&make_genesis,
).unwrap().unwrap();
match genesis_epoch_2 {
ViableEpoch::Genesis(_) => {},
_ => panic!("should be unimported genesis"),
};
assert_eq!(genesis_epoch_2.as_ref(), &make_genesis(10102));
}
#[test]
fn epoch_changes_between_blocks() {
//
// A - B
// \
// — C
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration: 100,
authorities: Vec::new(),
randomness: [0; 32],
};
let mut epoch_changes = EpochChanges::new();
let genesis_epoch = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
100,
&make_genesis,
).unwrap().unwrap();
assert_eq!(genesis_epoch.as_ref(), &make_genesis(100));
let import_epoch_1 = genesis_epoch.increment(NextEpochDescriptor {
authorities: Vec::new(),
randomness: [1; 32],
});
let epoch_1 = import_epoch_1.as_ref().clone();
epoch_changes.import(
&is_descendent_of,
*b"A",
1,
*b"0",
import_epoch_1,
).unwrap();
let genesis_epoch = genesis_epoch.into_inner();
assert!(is_descendent_of(b"0", b"A").unwrap());
let end_slot = genesis_epoch.end_slot();
assert_eq!(end_slot, epoch_1.start_slot);
{
// x is still within the genesis epoch.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
end_slot - 1,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, genesis_epoch);
}
{
// x is now at the next epoch, because the block is now at the
// start slot of epoch 1.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
end_slot,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, epoch_1);
}
{
// x is now at the next epoch, because the block is now after
// start slot of epoch 1.
let x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
epoch_1.end_slot() - 1,
&make_genesis,
).unwrap().unwrap().into_inner();
assert_eq!(x, epoch_1);
}
}
#[test]
fn two_block_ones_dont_conflict() {
// X - Y
// /
// 0 - A - B
//
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B"),
(b"X", b) => Ok(b == *b"Y"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let duration = 100;
let make_genesis = |slot| Epoch {
epoch_index: 0,
start_slot: slot,
duration,
authorities: Vec::new(),
randomness: [0; 32],
};
let mut epoch_changes = EpochChanges::new();
let next_descriptor = NextEpochDescriptor {
authorities: Vec::new(),
randomness: [0; 32],
};
// insert genesis epoch for A
{
let genesis_epoch_a = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
100,
&make_genesis,
).unwrap().unwrap();
epoch_changes.import(
&is_descendent_of,
*b"A",
1,
*b"0",
genesis_epoch_a.increment(next_descriptor.clone()),
).unwrap();
}
// insert genesis epoch for X
{
let genesis_epoch_x = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"0",
0,
1000,
&make_genesis,
).unwrap().unwrap();
epoch_changes.import(
&is_descendent_of,
*b"X",
1,
*b"0",
genesis_epoch_x.increment(next_descriptor.clone()),
).unwrap();
}
// now check that the genesis epochs for our respective block 1s
// respect the chain structure.
{
let epoch_for_a_child = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"A",
1,
101,
&make_genesis,
).unwrap().unwrap();
assert_eq!(epoch_for_a_child.into_inner(), make_genesis(100));
let epoch_for_x_child = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"X",
1,
1001,
&make_genesis,
).unwrap().unwrap();
assert_eq!(epoch_for_x_child.into_inner(), make_genesis(1000));
let epoch_for_x_child_before_genesis = epoch_changes.epoch_for_child_of(
&is_descendent_of,
b"X",
1,
101,
&make_genesis,
).unwrap();
// even though there is a genesis epoch at that slot, it's not in
// this chain.
assert!(epoch_for_x_child_before_genesis.is_none());
}
}
}
File diff suppressed because it is too large Load Diff
+303 -76
View File
@@ -21,19 +21,22 @@
#![allow(deprecated)]
use super::*;
use babe_primitives::AuthorityPair;
use babe_primitives::{AuthorityPair, SlotNumber};
use client::block_builder::BlockBuilder;
use consensus_common::NoNetwork as DummyOracle;
use consensus_common::import_queue::{
BoxBlockImport, BoxJustificationImport, BoxFinalityProofImport,
};
use network::test::*;
use network::test::{Block as TestBlock, PeersClient};
use network::config::BoxFinalityProofRequestBuilder;
use sr_primitives::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
use network::config::ProtocolConfig;
use tokio::runtime::current_thread;
use keyring::sr25519::Keyring;
use client::BlockchainEvents;
use test_client;
use log::debug;
use std::{time::Duration, borrow::Borrow, cell::RefCell};
use std::{time::Duration, cell::RefCell};
type Item = DigestItem<Hash>;
@@ -46,8 +49,28 @@ type TestClient = client::Client<
test_client::runtime::RuntimeApi,
>;
struct DummyFactory(Arc<TestClient>);
struct DummyProposer(u64, Arc<TestClient>);
#[derive(Copy, Clone, PartialEq)]
enum Stage {
PreSeal,
PostSeal,
}
type Mutator = Arc<dyn Fn(&mut TestHeader, Stage) + Send + Sync>;
#[derive(Clone)]
struct DummyFactory {
client: Arc<TestClient>,
epoch_changes: crate::SharedEpochChanges<TestBlock>,
config: Config,
mutator: Mutator,
}
struct DummyProposer {
factory: DummyFactory,
parent_hash: Hash,
parent_number: u64,
parent_slot: SlotNumber,
}
impl Environment<TestBlock> for DummyFactory {
type Proposer = DummyProposer;
@@ -56,7 +79,69 @@ impl Environment<TestBlock> for DummyFactory {
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
-> Result<DummyProposer, Error>
{
Ok(DummyProposer(parent_header.number + 1, self.0.clone()))
let parent_slot = crate::find_pre_digest::<TestBlock>(parent_header)
.expect("parent header has a pre-digest")
.slot_number();
Ok(DummyProposer {
factory: self.clone(),
parent_hash: parent_header.hash(),
parent_number: *parent_header.number(),
parent_slot,
})
}
}
impl DummyProposer {
fn propose_with(&mut self, pre_digests: DigestFor<TestBlock>)
-> future::Ready<Result<TestBlock, Error>>
{
let block_builder = self.factory.client.new_block_at(
&BlockId::Hash(self.parent_hash),
pre_digests,
).unwrap();
let mut block = match block_builder.bake().map_err(|e| e.into()) {
Ok(b) => b,
Err(e) => return future::ready(Err(e)),
};
let this_slot = crate::find_pre_digest::<TestBlock>(block.header())
.expect("baked block has valid pre-digest")
.slot_number();
// figure out if we should add a consensus digest, since the test runtime
// doesn't.
let epoch_changes = self.factory.epoch_changes.lock();
let epoch = epoch_changes.epoch_for_child_of(
descendent_query(&*self.factory.client),
&self.parent_hash,
self.parent_number,
this_slot,
|slot| self.factory.config.genesis_epoch(slot),
)
.expect("client has data to find epoch")
.expect("can compute epoch for baked block")
.into_inner();
let first_in_epoch = self.parent_slot < epoch.start_slot;
if first_in_epoch {
// push a `Consensus` digest signalling next change.
// we just reuse the same randomness and authorities as the prior
// epoch. this will break when we add light client support, since
// that will re-check the randomness logic off-chain.
let digest_data = ConsensusLog::NextEpochData(NextEpochDescriptor {
authorities: epoch.authorities.clone(),
randomness: epoch.randomness.clone(),
}).encode();
let digest = DigestItem::Consensus(BABE_ENGINE_ID, digest_data);
block.header.digest_mut().push(digest)
}
// mutate the block header according to the mutator.
(self.factory.mutator)(&mut block.header, Stage::PreSeal);
future::ready(Ok(block))
}
}
@@ -67,21 +152,42 @@ impl Proposer<TestBlock> for DummyProposer {
fn propose(
&mut self,
_: InherentData,
digests: DigestFor<TestBlock>,
pre_digests: DigestFor<TestBlock>,
_: Duration,
) -> Self::Create {
future::ready(self.1.new_block(digests).unwrap().bake().map_err(|e| e.into()))
self.propose_with(pre_digests)
}
}
type Mutator = Arc<dyn for<'r> Fn(&'r mut TestHeader) + Send + Sync>;
thread_local! {
static MUTATOR: RefCell<Mutator> = RefCell::new(Arc::new(|_|()));
static MUTATOR: RefCell<Mutator> = RefCell::new(Arc::new(|_, _|()));
}
#[derive(Clone)]
struct PanickingBlockImport<B>(B);
impl<B: BlockImport<TestBlock>> BlockImport<TestBlock> for PanickingBlockImport<B> {
type Error = B::Error;
fn import_block(
&mut self,
block: BlockImportParams<TestBlock>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.import_block(block, new_cache).expect("importing block failed"))
}
fn check_block(
&mut self,
hash: Hash,
parent_hash: Hash,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.check_block(hash, parent_hash).expect("checking block failed"))
}
}
pub struct BabeTestNet {
peers: Vec<Peer<(), DummySpecialization>>,
peers: Vec<Peer<Option<PeerData>, DummySpecialization>>,
}
type TestHeader = <TestBlock as BlockT>::Header;
@@ -94,7 +200,6 @@ pub struct TestVerifier {
TestBlock,
test_client::runtime::RuntimeApi,
PeersFullClient,
(),
>,
mutator: Mutator,
}
@@ -110,16 +215,22 @@ impl Verifier<TestBlock> for TestVerifier {
justification: Option<Justification>,
body: Option<Vec<TestExtrinsic>>,
) -> Result<(BlockImportParams<TestBlock>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let cb: &(dyn Fn(&mut TestHeader) + Send + Sync) = self.mutator.borrow();
cb(&mut header);
// apply post-sealing mutations (i.e. stripping seal, if desired).
(self.mutator)(&mut header, Stage::PostSeal);
Ok(self.inner.verify(origin, header, justification, body).expect("verification failed!"))
}
}
pub struct PeerData {
link: BabeLink<TestBlock>,
inherent_data_providers: InherentDataProviders,
block_import: Mutex<Option<BoxBlockImport<TestBlock>>>,
}
impl TestNetFactory for BabeTestNet {
type Specialization = DummySpecialization;
type Verifier = TestVerifier;
type PeerData = ();
type PeerData = Option<PeerData>;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
@@ -129,31 +240,62 @@ impl TestNetFactory for BabeTestNet {
}
}
/// KLUDGE: this function gets the mutator from thread-local storage.
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig)
fn make_block_import(&self, client: PeersClient)
-> (
BoxBlockImport<Block>,
Option<BoxJustificationImport<Block>>,
Option<BoxFinalityProofImport<Block>>,
Option<BoxFinalityProofRequestBuilder<Block>>,
Option<PeerData>,
)
{
let client = client.as_full().expect("only full clients are tested");
let inherent_data_providers = InherentDataProviders::new();
let config = Config::get_or_compute(&*client).expect("config available");
let (block_import, link) = crate::block_import(
config,
client.clone(),
client.clone(),
client.clone(),
).expect("can initialize block-import");
let block_import = PanickingBlockImport(block_import);
let data_block_import = Mutex::new(Some(Box::new(block_import.clone()) as BoxBlockImport<_>));
(
Box::new(block_import),
None,
None,
None,
Some(PeerData { link, inherent_data_providers, block_import: data_block_import }),
)
}
fn make_verifier(
&self,
client: PeersClient,
_cfg: &ProtocolConfig,
maybe_link: &Option<PeerData>,
)
-> Self::Verifier
{
let client = client.as_full().expect("only full clients are used in test");
trace!(target: "babe", "Creating a verifier");
let config = Config::get_or_compute(&*client)
.expect("slot duration available");
let inherent_data_providers = InherentDataProviders::new();
register_babe_inherent_data_provider(
&inherent_data_providers,
config.get()
).expect("Registers babe inherent data provider");
trace!(target: "babe", "Provider registered");
// ensure block import and verifier are linked correctly.
let data = maybe_link.as_ref().expect("babe link always provided to verifier instantiation");
TestVerifier {
inner: BabeVerifier {
client: client.clone(),
api: client,
inherent_data_providers,
config,
time_source: Default::default(),
transaction_pool : Default::default(),
inherent_data_providers: data.inherent_data_providers.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
time_source: data.link.time_source.clone(),
},
mutator: MUTATOR.with(|s| s.borrow().clone()),
mutator: MUTATOR.with(|m| m.borrow().clone()),
}
}
@@ -188,8 +330,13 @@ fn rejects_empty_block() {
})
}
fn run_one_test() {
fn run_one_test(
mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static,
) {
let _ = env_logger::try_init();
let mutator = Arc::new(mutator) as Mutator;
MUTATOR.with(|m| *m.borrow_mut() = mutator.clone());
let net = BabeTestNet::new(3);
let peers = &[
@@ -202,6 +349,7 @@ fn run_one_test() {
let mut import_notifications = Vec::new();
let mut runtime = current_thread::Runtime::new().unwrap();
let mut keystore_paths = Vec::new();
for (peer_id, seed) in peers {
let mut net = net.lock();
let peer = net.peer(*peer_id);
@@ -213,30 +361,46 @@ fn run_one_test() {
keystore.write().insert_ephemeral_from_seed::<AuthorityPair>(seed).expect("Generates authority key");
keystore_paths.push(keystore_path);
let environ = DummyFactory(client.clone());
let mut got_own = false;
let mut got_other = false;
let data = peer.data.as_ref().expect("babe link set up during initialization");
let environ = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: mutator.clone(),
};
import_notifications.push(
// run each future until we get one of our own blocks with number higher than 5
// that was produced locally.
client.import_notification_stream()
.take_while(|n| future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5)))
.for_each(move |_| future::ready(()))
.take_while(move |n| future::ready(n.header.number() < &5 || {
if n.origin == BlockOrigin::Own {
got_own = true;
} else {
got_other = true;
}
// continue until we have at least one block of our own
// and one of another peer.
!(got_own && got_other)
}))
.for_each(|_| future::ready(()) )
);
let config = Config::get_or_compute(&*client).expect("slot duration available");
let inherent_data_providers = InherentDataProviders::new();
register_babe_inherent_data_provider(
&inherent_data_providers, config.get()
).expect("Registers babe inherent data provider");
runtime.spawn(start_babe(BabeParams {
config,
block_import: client.clone(),
block_import: data.block_import.lock().take().expect("import set up during init"),
select_chain,
client,
env: environ,
sync_oracle: DummyOracle,
inherent_data_providers,
inherent_data_providers: data.inherent_data_providers.clone(),
force_authoring: false,
time_source: Default::default(),
babe_link: data.link.clone(),
keystore,
}).expect("Starts babe"));
}
@@ -251,45 +415,41 @@ fn run_one_test() {
}
#[test]
fn authoring_blocks() { run_one_test() }
fn authoring_blocks() {
run_one_test(|_, _| ())
}
#[test]
#[should_panic]
fn rejects_missing_inherent_digest() {
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| v.as_babe_pre_digest().is_none())
.filter(|v| stage == Stage::PostSeal || v.as_babe_pre_digest().is_none())
.collect()
}));
run_one_test()
})
}
#[test]
#[should_panic]
fn rejects_missing_seals() {
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| v.as_babe_seal().is_none())
.filter(|v| stage == Stage::PreSeal || v.as_babe_seal().is_none())
.collect()
}));
run_one_test()
})
}
// TODO: this test assumes that the test runtime will trigger epoch changes
// which isn't the case since it doesn't include the session module.
#[test]
#[should_panic]
#[ignore]
fn rejects_missing_consensus_digests() {
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
run_one_test(|header: &mut TestHeader, stage| {
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
header.digest_mut().logs = v.into_iter()
.filter(|v| v.as_babe_epoch().is_none())
.filter(|v| stage == Stage::PostSeal || v.as_next_epoch_descriptor().is_none())
.collect()
}));
run_one_test()
});
}
#[test]
@@ -326,28 +486,34 @@ fn can_author_block() {
.expect("Generates authority pair");
let mut i = 0;
let mut epoch = Epoch {
let epoch = Epoch {
start_slot: 0,
authorities: vec![(pair.public(), 1)],
randomness: [0; 32],
epoch_index: 1,
duration: 100,
};
let mut config = crate::BabeConfiguration {
slot_duration: 1000,
epoch_length: 100,
c: (3, 10),
genesis_authorities: Vec::new(),
randomness: [0; 32],
secondary_slots: true,
};
let parent_weight = 0;
// with secondary slots enabled it should never be empty
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
match claim_slot(i, &epoch, &config, &keystore) {
None => i += 1,
Some(s) => debug!(target: "babe", "Authored block {:?}", s.0),
}
// otherwise with only vrf-based primary slots we might need to try a couple
// of times.
epoch.secondary_slots = false;
config.secondary_slots = false;
loop {
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
match claim_slot(i, &epoch, &config, &keystore) {
None => i += 1,
Some(s) => {
debug!(target: "babe", "Authored block {:?}", s.0);
@@ -358,14 +524,75 @@ fn can_author_block() {
}
#[test]
fn authorities_call_works() {
let _ = env_logger::try_init();
let client = test_client::new();
fn importing_block_one_sets_genesis_epoch() {
let mut net = BabeTestNet::new(1);
assert_eq!(client.info().chain.best_number, 0);
assert_eq!(epoch(&client, &BlockId::Number(0)).unwrap().into_regular().unwrap().authorities, vec![
(Keyring::Alice.public().into(), 1),
(Keyring::Bob.public().into(), 1),
(Keyring::Charlie.public().into(), 1),
]);
let peer = net.peer(0);
let data = peer.data.as_ref().expect("babe link set up during initialization");
let client = peer.client().as_full().expect("Only full clients are used in tests").clone();
let mut environ = DummyFactory {
client: client.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
mutator: Arc::new(|_, _| ()),
};
let genesis_header = client.header(&BlockId::Number(0)).unwrap().unwrap();
let mut proposer = environ.init(&genesis_header).unwrap();
let babe_claim = Item::babe_pre_digest(babe_primitives::BabePreDigest::Secondary {
authority_index: 0,
slot_number: 999,
});
let pre_digest = sr_primitives::generic::Digest { logs: vec![babe_claim] };
let genesis_epoch = data.link.config.genesis_epoch(999);
let mut block = futures::executor::block_on(proposer.propose_with(pre_digest)).unwrap();
// seal by alice.
let seal = {
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let pair = AuthorityPair::from_seed(&[1; 32]);
let pre_hash = block.header.hash();
let signature = pair.sign(pre_hash.as_ref());
Item::babe_seal(signature)
};
let post_hash = {
block.header.digest_mut().push(seal.clone());
let h = block.header.hash();
block.header.digest_mut().pop();
h
};
assert_eq!(*block.header.number(), 1);
let (header, body) = block.deconstruct();
let post_digests = vec![seal];
let mut block_import = data.block_import.lock().take().expect("import set up during init");
block_import.import_block(
BlockImportParams {
origin: BlockOrigin::Own,
header,
justification: None,
post_digests,
body: Some(body),
finalized: false,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
},
Default::default(),
).unwrap();
let epoch_changes = data.link.epoch_changes.lock();
let epoch_for_second_block = epoch_changes.epoch_for_child_of(
descendent_query(&*client),
&post_hash,
1,
1000,
|slot| data.link.config.genesis_epoch(slot),
).unwrap().unwrap().into_inner();
assert_eq!(epoch_for_second_block, genesis_epoch);
}
@@ -120,7 +120,8 @@ pub struct BlockImportParams<Block: BlockT> {
/// Contains a list of key-value pairs. If values are `None`, the keys
/// will be deleted.
pub auxiliary: Vec<(Vec<u8>, Option<Vec<u8>>)>,
/// Fork choice strategy of this import.
/// Fork choice strategy of this import. This should only be set by a
/// synchronous import, otherwise it may race against other imports.
pub fork_choice: ForkChoiceStrategy,
}
@@ -185,7 +186,31 @@ pub trait BlockImport<B: BlockT> {
) -> Result<ImportResult, Self::Error>;
}
impl<B: BlockT, T, E: ::std::error::Error + Send + 'static> BlockImport<B> for Arc<T>
impl<B: BlockT> BlockImport<B> for crate::import_queue::BoxBlockImport<B> {
type Error = crate::error::Error;
/// Check block preconditions.
fn check_block(
&mut self,
hash: B::Hash,
parent_hash: B::Hash,
) -> Result<ImportResult, Self::Error> {
(**self).check_block(hash, parent_hash)
}
/// Import a block.
///
/// Cached data can be accessed through the blockchain cache.
fn import_block(
&mut self,
block: BlockImportParams<B>,
cache: HashMap<CacheKeyId, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
(**self).import_block(block, cache)
}
}
impl<B: BlockT, T, E: std::error::Error + Send + 'static> BlockImport<B> for Arc<T>
where for<'r> &'r T: BlockImport<B, Error = E>
{
type Error = E;
@@ -105,6 +105,7 @@ pub trait ImportQueue<B: BlockT>: Send {
number: NumberFor<B>,
finality_proof: Vec<u8>
);
/// Polls for actions to perform on the network.
///
/// This method should behave in a way similar to `Future::poll`. It can register the current
+6 -4
View File
@@ -77,8 +77,9 @@ pub trait SimpleSlotWorker<B: BlockT> {
/// A handle to a `BlockImport`.
fn block_import(&self) -> Arc<Mutex<Self::BlockImport>>;
/// Returns the epoch data necessary for authoring.
fn epoch_data(&self, block: &B::Hash) -> Result<Self::EpochData, consensus_common::Error>;
/// Returns the epoch data necessary for authoring. For time-dependent epochs,
/// use the provided slot number as a canonical source of time.
fn epoch_data(&self, header: &B::Header, slot_number: u64) -> Result<Self::EpochData, consensus_common::Error>;
/// Returns the number of authorities given the epoch data.
fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize;
@@ -120,7 +121,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
let (timestamp, slot_number, slot_duration) =
(slot_info.timestamp, slot_info.number, slot_info.duration);
let epoch_data = match self.epoch_data(&chain_head.hash()) {
let epoch_data = match self.epoch_data(&chain_head, slot_number) {
Ok(epoch_data) => epoch_data,
Err(err) => {
warn!("Unable to fetch epoch data at block {:?}: {:?}", chain_head.hash(), err);
@@ -359,7 +360,8 @@ impl SlotData for u64 {
}
/// A slot duration. Create with `get_or_compute`.
// The internal member should stay private here.
// The internal member should stay private here to maintain invariants of
// `get_or_compute`.
#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct SlotDuration<T>(T);
@@ -906,7 +906,7 @@ pub(crate) fn finalize_block<B, Block: BlockT<Hash=H256>, E, RA>(
let status = authority_set.apply_standard_changes(
hash,
number,
&is_descendent_of(client, None),
&is_descendent_of::<_, _, Block::Hash>(client, None),
).map_err(|e| Error::Safety(e.to_string()))?;
// check if this is this is the first finalization of some consensus changes
@@ -294,7 +294,7 @@ where
// returns a function for checking whether a block is a descendent of another
// consistent with querying client directly after importing the block.
let parent_hash = *block.header.parent_hash();
let is_descendent_of = is_descendent_of(&self.inner, Some((&hash, &parent_hash)));
let is_descendent_of = is_descendent_of(&*self.inner, Some((&hash, &parent_hash)));
let mut guard = InnerGuard {
guard: Some(self.authority_set.inner().write()),
+6 -1
View File
@@ -98,7 +98,12 @@ impl TestNetFactory for GrandpaTestNet {
}
}
fn make_verifier(&self, _client: PeersClient, _cfg: &ProtocolConfig) -> Self::Verifier {
fn make_verifier(
&self,
_client: PeersClient,
_cfg: &ProtocolConfig,
_: &PeerData,
) -> Self::Verifier {
PassThroughVerifier(false) // use non-instant finality.
}
+38 -12
View File
@@ -461,7 +461,12 @@ pub trait TestNetFactory: Sized {
/// These two need to be implemented!
fn from_config(config: &ProtocolConfig) -> Self;
fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig) -> Self::Verifier;
fn make_verifier(
&self,
client: PeersClient,
config: &ProtocolConfig,
peer_data: &Self::PeerData,
) -> Self::Verifier;
/// Get reference to peer.
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, Self::Specialization>;
@@ -509,12 +514,23 @@ pub trait TestNetFactory: Sized {
let backend = test_client_builder.backend();
let (c, longest_chain) = test_client_builder.build_with_longest_chain();
let client = Arc::new(c);
let verifier = self.make_verifier(PeersClient::Full(client.clone(), backend.clone()), config);
let verifier = VerifierAdapter(Arc::new(Mutex::new(Box::new(verifier) as Box<_>)));
let (block_import, justification_import, finality_proof_import, finality_proof_request_builder, data)
= self.make_block_import(PeersClient::Full(client.clone(), backend.clone()));
let (
block_import,
justification_import,
finality_proof_import,
finality_proof_request_builder,
data,
) = self.make_block_import(PeersClient::Full(client.clone(), backend.clone()));
let block_import = BlockImportAdapter(Arc::new(Mutex::new(block_import)));
let verifier = self.make_verifier(
PeersClient::Full(client.clone(), backend.clone()),
config,
&data,
);
let verifier = VerifierAdapter(Arc::new(Mutex::new(Box::new(verifier) as Box<_>)));
let import_queue = Box::new(BasicQueue::new(
verifier.clone(),
Box::new(block_import.clone()),
@@ -572,12 +588,22 @@ pub trait TestNetFactory: Sized {
let (c, backend) = test_client::new_light();
let client = Arc::new(c);
let verifier = self.make_verifier(PeersClient::Light(client.clone(), backend.clone()), &config);
let verifier = VerifierAdapter(Arc::new(Mutex::new(Box::new(verifier) as Box<_>)));
let (block_import, justification_import, finality_proof_import, finality_proof_request_builder, data)
= self.make_block_import(PeersClient::Light(client.clone(), backend.clone()));
let (
block_import,
justification_import,
finality_proof_import,
finality_proof_request_builder,
data,
) = self.make_block_import(PeersClient::Light(client.clone(), backend.clone()));
let block_import = BlockImportAdapter(Arc::new(Mutex::new(block_import)));
let verifier = self.make_verifier(
PeersClient::Light(client.clone(), backend.clone()),
&config,
&data,
);
let verifier = VerifierAdapter(Arc::new(Mutex::new(Box::new(verifier) as Box<_>)));
let import_queue = Box::new(BasicQueue::new(
verifier.clone(),
Box::new(block_import.clone()),
@@ -696,7 +722,7 @@ impl TestNetFactory for TestNet {
}
}
fn make_verifier(&self, _client: PeersClient, _config: &ProtocolConfig)
fn make_verifier(&self, _client: PeersClient, _config: &ProtocolConfig, _peer_data: &())
-> Self::Verifier
{
PassThroughVerifier(false)
@@ -742,8 +768,8 @@ impl TestNetFactory for JustificationTestNet {
JustificationTestNet(TestNet::from_config(config))
}
fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig) -> Self::Verifier {
self.0.make_verifier(client, config)
fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig, peer_data: &()) -> Self::Verifier {
self.0.make_verifier(client, config, peer_data)
}
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, Self::Specialization> {
+1 -1
View File
@@ -16,7 +16,7 @@
//! Note that execution times will not be accurate in an absolute scale, since
//! - Everything is executed in the context of `TestExternalities`
//! - Everything is executed in native environment.
#![cfg(feature = "bench")]
#![feature(test)]
extern crate test;
+4 -1
View File
@@ -20,6 +20,7 @@ use crate::RuntimeGenesis;
use crate::error;
use crate::chain_spec::ChainSpec;
/// Defines the logic for an operation exporting blocks within a range.
#[macro_export]
macro_rules! export_blocks {
($client:ident, $exit:ident, $output:ident, $from:ident, $to:ident, $json:ident) => {{
@@ -36,7 +37,7 @@ macro_rules! export_blocks {
}
let (exit_send, exit_recv) = std::sync::mpsc::channel();
::std::thread::spawn(move || {
std::thread::spawn(move || {
let _ = $exit.wait();
let _ = exit_send.send(());
});
@@ -75,6 +76,7 @@ macro_rules! export_blocks {
}}
}
/// Defines the logic for an operation importing blocks from some known import.
#[macro_export]
macro_rules! import_blocks {
($block:ty, $client:ident, $queue:ident, $exit:ident, $input:ident) => {{
@@ -203,6 +205,7 @@ macro_rules! import_blocks {
}}
}
/// Revert the chain some number of blocks.
#[macro_export]
macro_rules! revert_chain {
($client:ident, $blocks:ident) => {{
+32 -17
View File
@@ -20,7 +20,6 @@ use std::iter;
use std::sync::{Arc, Mutex, MutexGuard};
use std::net::Ipv4Addr;
use std::time::Duration;
use std::collections::HashMap;
use log::info;
use futures::{Future, Stream, Poll};
use tempdir::TempDir;
@@ -36,7 +35,6 @@ use service::{
use network::{multiaddr, Multiaddr};
use network::config::{NetworkConfiguration, TransportConfig, NodeKeyConfig, Secret, NonReservedPeerMode};
use sr_primitives::{generic::BlockId, traits::Block as BlockT};
use consensus::{BlockImportParams, BlockImport};
/// Maximum duration of single wait call.
const MAX_WAIT_TIME: Duration = Duration::from_secs(60 * 3);
@@ -276,7 +274,13 @@ impl<G, F, L, U> TestNet<G, F, L, U> where
}
}
pub fn connectivity<G, Fb, F, Lb, L>(spec: ChainSpec<G>, full_builder: Fb, light_builder: Lb) where
pub fn connectivity<G, Fb, F, Lb, L>(
spec: ChainSpec<G>,
full_builder: Fb,
light_builder: Lb,
light_node_interconnectivity: bool, // should normally be false, unless the light nodes
// aren't actually light.
) where
Fb: Fn(Configuration<(), G>) -> Result<F, Error>,
F: AbstractService,
Lb: Fn(Configuration<(), G>) -> Result<L, Error>,
@@ -284,6 +288,14 @@ pub fn connectivity<G, Fb, F, Lb, L>(spec: ChainSpec<G>, full_builder: Fb, light
{
const NUM_FULL_NODES: usize = 5;
const NUM_LIGHT_NODES: usize = 5;
let expected_full_connections = NUM_FULL_NODES - 1 + NUM_LIGHT_NODES;
let expected_light_connections = if light_node_interconnectivity {
expected_full_connections
} else {
NUM_FULL_NODES
};
{
let temp = TempDir::new("substrate-connectivity-test").expect("Error creating test dir");
let runtime = {
@@ -307,11 +319,14 @@ pub fn connectivity<G, Fb, F, Lb, L>(spec: ChainSpec<G>, full_builder: Fb, light
service.get().network().add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
network.run_until_all_full(
|_index, service| service.get().network().num_connected() == NUM_FULL_NODES - 1
+ NUM_LIGHT_NODES,
|_index, service| service.get().network().num_connected() == NUM_FULL_NODES,
move |_index, service| service.get().network().num_connected()
== expected_full_connections,
move |_index, service| service.get().network().num_connected()
== expected_light_connections,
);
network.runtime
};
@@ -350,10 +365,12 @@ pub fn connectivity<G, Fb, F, Lb, L>(spec: ChainSpec<G>, full_builder: Fb, light
address = node_id.clone();
}
}
network.run_until_all_full(
|_index, service| service.get().network().num_connected() == NUM_FULL_NODES - 1
+ NUM_LIGHT_NODES,
|_index, service| service.get().network().num_connected() == NUM_FULL_NODES,
move |_index, service| service.get().network().num_connected()
== expected_full_connections,
move |_index, service| service.get().network().num_connected()
== expected_light_connections,
);
}
temp.close().expect("Error removing temp dir");
@@ -364,14 +381,14 @@ pub fn sync<G, Fb, F, Lb, L, B, E, U>(
spec: ChainSpec<G>,
full_builder: Fb,
light_builder: Lb,
mut block_factory: B,
mut make_block_and_import: B,
mut extrinsic_factory: E
) where
Fb: Fn(Configuration<(), G>) -> Result<(F, U), Error>,
F: AbstractService,
Lb: Fn(Configuration<(), G>) -> Result<L, Error>,
L: AbstractService,
B: FnMut(&F, &U) -> BlockImportParams<F::Block>,
B: FnMut(&F, &mut U),
E: FnMut(&F, &U) -> <F::Block as BlockT>::Extrinsic,
U: Clone + Send + 'static,
{
@@ -392,15 +409,13 @@ pub fn sync<G, Fb, F, Lb, L, B, E, U>(
);
info!("Checking block sync");
let first_address = {
let first_service = &network.full_nodes[0].1;
let first_user_data = &network.full_nodes[0].2;
let mut client = first_service.get().client();
let &mut (_, ref first_service, ref mut first_user_data, _) = &mut network.full_nodes[0];
for i in 0 .. NUM_BLOCKS {
if i % 128 == 0 {
info!("Generating #{}", i);
info!("Generating #{}", i + 1);
}
let import_data = block_factory(&first_service.get(), first_user_data);
client.import_block(import_data, HashMap::new()).expect("Error importing test block");
make_block_and_import(&first_service.get(), first_user_data);
}
network.full_nodes[0].3.clone()
};
-1
View File
@@ -26,7 +26,6 @@
#![cfg_attr(feature = "std", doc = "Substrate runtime standard library as compiled when linked with Rust's standard library.")]
#![cfg_attr(not(feature = "std"), doc = "Substrate's runtime standard library as compiled without Rust's standard library.")]
use hash_db::Hasher;
use rstd::vec::Vec;
use primitives::{
+1
View File
@@ -17,6 +17,7 @@
use primitives::{
blake2_128, blake2_256, twox_128, twox_256, twox_64, ed25519, Blake2Hasher, sr25519, Pair, H256,
traits::Externalities, child_storage_key::ChildStorageKey, hexdisplay::HexDisplay, offchain,
Hasher,
};
// Switch to this after PoC-3
// pub use primitives::BlakeHasher;
+2 -2
View File
@@ -20,7 +20,7 @@ pub use rstd::{mem, slice};
use core::{intrinsics, panic::PanicInfo};
use rstd::{vec::Vec, cell::Cell, convert::TryInto};
use primitives::{offchain, Blake2Hasher};
use primitives::offchain;
use codec::Decode;
#[cfg(not(feature = "no_panic_handler"))]
@@ -732,7 +732,7 @@ impl StorageApi for () {
}
fn blake2_256_trie_root(input: Vec<(Vec<u8>, Vec<u8>)>) -> H256 {
fn blake2_256_trie_root(_input: Vec<(Vec<u8>, Vec<u8>)>) -> H256 {
unimplemented!()
}
+1 -1
View File
@@ -25,7 +25,7 @@ use crate::traits::{
};
use crate::{generic, KeyTypeId, ApplyResult};
use crate::weights::{GetDispatchInfo, DispatchInfo};
pub use primitives::H256;
pub use primitives::{H256, sr25519};
use primitives::{crypto::{CryptoType, Dummy, key_types, Public}, U256};
use crate::transaction_validity::{TransactionValidity, TransactionValidityError};
+11 -31
View File
@@ -619,25 +619,15 @@ cfg_if! {
}
impl babe_primitives::BabeApi<Block> for Runtime {
fn startup_data() -> babe_primitives::BabeConfiguration {
fn configuration() -> babe_primitives::BabeConfiguration {
babe_primitives::BabeConfiguration {
median_required_blocks: 0,
slot_duration: 3000,
slot_duration: 1000,
epoch_length: EpochDuration::get(),
c: (3, 10),
}
}
fn epoch() -> babe_primitives::Epoch {
let authorities = system::authorities();
let authorities: Vec<_> = authorities.into_iter().map(|x|(x, 1)).collect();
babe_primitives::Epoch {
start_slot: <srml_babe::Module<Runtime>>::epoch_start_slot(),
authorities,
genesis_authorities: system::authorities()
.into_iter().map(|x|(x, 1)).collect(),
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,
secondary_slots: true,
}
}
}
@@ -839,25 +829,15 @@ cfg_if! {
}
impl babe_primitives::BabeApi<Block> for Runtime {
fn startup_data() -> babe_primitives::BabeConfiguration {
fn configuration() -> babe_primitives::BabeConfiguration {
babe_primitives::BabeConfiguration {
median_required_blocks: 0,
slot_duration: 1000,
epoch_length: EpochDuration::get(),
c: (3, 10),
}
}
fn epoch() -> babe_primitives::Epoch {
let authorities = system::authorities();
let authorities: Vec<_> = authorities.into_iter().map(|x|(x, 1)).collect();
babe_primitives::Epoch {
start_slot: <srml_babe::Module<Runtime>>::epoch_start_slot(),
authorities,
genesis_authorities: system::authorities()
.into_iter().map(|x|(x, 1)).collect(),
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,
secondary_slots: true,
}
}
}
+49 -26
View File
@@ -153,6 +153,8 @@ impl<H, N, V> ForkTree<H, N, V> where
/// should return `true` if the second hash (target) is a descendent of the
/// first hash (base). This method assumes that nodes in the same branch are
/// imported in order.
///
/// Returns `true` if the imported node is a root.
pub fn import<F, E>(
&mut self,
mut hash: H,
@@ -208,7 +210,7 @@ impl<H, N, V> ForkTree<H, N, V> where
self.node_iter().map(|node| (&node.hash, &node.number, &node.data))
}
/// Find a node in the tree that is the lowest ancestor of the given
/// Find a node in the tree that is the deepest ancestor of the given
/// block hash and which passes the given predicate. The given function
/// `is_descendent_of` should return `true` if the second hash (target)
/// is a descendent of the first hash (base).
@@ -228,8 +230,8 @@ impl<H, N, V> ForkTree<H, N, V> where
let node = root.find_node_where(hash, number, is_descendent_of, predicate)?;
// found the node, early exit
if let Some(node) = node {
return Ok(node);
if let FindOutcome::Found(node) = node {
return Ok(Some(node));
}
}
@@ -510,6 +512,17 @@ impl<H, N, V> ForkTree<H, N, V> where
mod node_implementation {
use super::*;
/// The outcome of a search within a node.
pub enum FindOutcome<T> {
// this is the node we were looking for.
Found(T),
// not the node we're looking for. contains a flag indicating
// whether the node was a descendent. true implies the predicate failed.
Failure(bool),
// Abort search.
Abort,
}
#[derive(Clone, Debug, Decode, Encode, PartialEq)]
pub struct Node<H, N, V> {
pub hash: H,
@@ -560,9 +573,10 @@ mod node_implementation {
}
}
/// Find a node in the tree that is the lowest ancestor of the given
/// block hash and which passes the given predicate. The given function
/// `is_descendent_of` should return `true` if the second hash (target)
/// Find a node in the tree that is the deepest ancestor of the given
/// block hash which also passes the given predicate, backtracking
/// when the predicate fails.
/// The given function `is_descendent_of` should return `true` if the second hash (target)
/// is a descendent of the first hash (base).
// FIXME: it would be useful if this returned a mutable reference but
// rustc can't deal with lifetimes properly. an option would be to try
@@ -573,23 +587,32 @@ mod node_implementation {
number: &N,
is_descendent_of: &F,
predicate: &P,
) -> Result<Option<Option<&Node<H, N, V>>>, Error<E>>
) -> Result<FindOutcome<&Node<H, N, V>>, Error<E>>
where E: std::error::Error,
F: Fn(&H, &H) -> Result<bool, E>,
P: Fn(&V) -> bool,
{
// stop searching this branch
if *number < self.number {
return Ok(None);
return Ok(FindOutcome::Failure(false));
}
let mut known_descendent_of = false;
// continue depth-first search through all children
for node in self.children.iter() {
let node = node.find_node_where(hash, number, is_descendent_of, predicate)?;
// found node, early exit
if node.is_some() {
return Ok(node);
match node.find_node_where(hash, number, is_descendent_of, predicate)? {
FindOutcome::Abort => return Ok(FindOutcome::Abort),
FindOutcome::Found(x) => return Ok(FindOutcome::Found(x)),
FindOutcome::Failure(true) => {
// if the block was a descendent of this child,
// then it cannot be a descendent of any others,
// so we don't search them.
known_descendent_of = true;
break;
},
FindOutcome::Failure(false) => {},
}
}
@@ -597,24 +620,23 @@ mod node_implementation {
// searching for is a descendent of this node then we will stop the
// search here, since there aren't any more children and we found
// the correct node so we don't want to backtrack.
if is_descendent_of(&self.hash, hash)? {
let is_descendent_of = known_descendent_of || is_descendent_of(&self.hash, hash)?;
if is_descendent_of {
// if the predicate passes we return the node
if predicate(&self.data) {
Ok(Some(Some(self)))
// otherwise we stop the search returning `None`
} else {
Ok(Some(None))
return Ok(FindOutcome::Found(self));
}
} else {
Ok(None)
}
// otherwise, tell our ancestor that we failed, and whether
// the block was a descendent.
Ok(FindOutcome::Failure(is_descendent_of))
}
}
}
// Workaround for: https://github.com/rust-lang/rust/issues/34537
use node_implementation::Node;
use node_implementation::{Node, FindOutcome};
struct ForkTreeIterator<'a, H, N, V> {
stack: Vec<&'a Node<H, N, V>>,
@@ -1197,7 +1219,7 @@ mod test {
}
#[test]
fn find_node_doesnt_backtrack_after_finding_highest_descending_node() {
fn find_node_backtracks_after_finding_highest_descending_node() {
let mut tree = ForkTree::new();
//
@@ -1215,11 +1237,12 @@ mod test {
};
tree.import("A", 1, 1, &is_descendent_of).unwrap();
tree.import("B", 2, 4, &is_descendent_of).unwrap();
tree.import("B", 2, 2, &is_descendent_of).unwrap();
tree.import("C", 2, 4, &is_descendent_of).unwrap();
// when searching the tree we reach both node `B` and `C`, but the
// predicate doesn't pass. still, we should not backtrack to node `A`.
// when searching the tree we reach node `C`, but the
// predicate doesn't pass. we should backtrack to `B`, but not to `A`,
// since "B" fulfills the predicate.
let node = tree.find_node_where(
&"D",
&3,
@@ -1227,6 +1250,6 @@ mod test {
&|data| *data < 3,
).unwrap();
assert_eq!(node, None);
assert_eq!(node.unwrap().hash, "B");
}
}
+4 -12
View File
@@ -374,27 +374,19 @@ impl_runtime_apis! {
}
impl babe_primitives::BabeApi<Block> for Runtime {
fn startup_data() -> babe_primitives::BabeConfiguration {
fn configuration() -> babe_primitives::BabeConfiguration {
// The choice of `c` parameter (where `1 - c` represents the
// probability of a slot being empty), is done in accordance to the
// slot duration and expected target block time, for safely
// resisting network delays of maximum two seconds.
// <https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results>
babe_primitives::BabeConfiguration {
median_required_blocks: 1000,
slot_duration: Babe::slot_duration(),
epoch_length: EpochDuration::get(),
c: PRIMARY_PROBABILITY,
}
}
fn epoch() -> babe_primitives::Epoch {
babe_primitives::Epoch {
start_slot: Babe::epoch_start_slot(),
authorities: Babe::authorities(),
epoch_index: Babe::epoch_index(),
genesis_authorities: Babe::authorities(),
randomness: Babe::randomness(),
duration: EpochDuration::get(),
secondary_slots: Babe::secondary_slots().0,
secondary_slots: true,
}
}
}
+36 -39
View File
@@ -3,7 +3,7 @@
use std::sync::Arc;
use std::time::Duration;
use substrate_client::LongestChain;
use babe::{import_queue, start_babe, Config};
use babe;
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
use futures::prelude::*;
use node_template_runtime::{self, GenesisConfig, opaque::Block, RuntimeApi};
@@ -34,7 +34,6 @@ macro_rules! new_full_start {
($config:expr) => {{
let mut import_setup = None;
let inherent_data_providers = inherents::InherentDataProviders::new();
let mut tasks_to_spawn = None;
let builder = substrate_service::ServiceBuilder::new_full::<
node_template_runtime::opaque::Block, node_template_runtime::RuntimeApi, crate::service::Executor
@@ -45,33 +44,38 @@ macro_rules! new_full_start {
.with_transaction_pool(|config, client|
Ok(transaction_pool::txpool::Pool::new(config, transaction_pool::ChainApi::new(client)))
)?
.with_import_queue(|_config, client, mut select_chain, transaction_pool| {
.with_import_queue(|_config, client, mut select_chain, _transaction_pool| {
let select_chain = select_chain.take()
.ok_or_else(|| substrate_service::Error::SelectChainRequired)?;
let (block_import, link_half) =
let (grandpa_block_import, grandpa_link) =
grandpa::block_import::<_, _, _, node_template_runtime::RuntimeApi, _, _>(
client.clone(), client.clone(), select_chain
)?;
let justification_import = block_import.clone();
let justification_import = grandpa_block_import.clone();
let (import_queue, babe_link, babe_block_import, pruning_task) = babe::import_queue(
let (babe_block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
block_import,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link.clone(),
babe_block_import.clone(),
Some(Box::new(justification_import)),
None,
client.clone(),
client,
inherent_data_providers.clone(),
Some(transaction_pool)
)?;
import_setup = Some((babe_block_import.clone(), link_half, babe_link));
tasks_to_spawn = Some(vec![Box::new(pruning_task)]);
import_setup = Some((babe_block_import, grandpa_link, babe_link));
Ok(import_queue)
})?;
(builder, import_setup, inherent_data_providers, tasks_to_spawn)
(builder, import_setup, inherent_data_providers)
}}
}
@@ -85,7 +89,7 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
let disable_grandpa = config.disable_grandpa;
let force_authoring = config.force_authoring;
let (builder, mut import_setup, inherent_data_providers, mut tasks_to_spawn) = new_full_start!(config);
let (builder, mut import_setup, inherent_data_providers) = new_full_start!(config);
let service = builder.with_network_protocol(|_| Ok(NodeProtocol::new()))?
.with_finality_proof_provider(|client, backend|
@@ -93,21 +97,10 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
)?
.build()?;
let (block_import, link_half, babe_link) =
let (block_import, grandpa_link, babe_link) =
import_setup.take()
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
// spawn any futures that were created in the previous setup steps
if let Some(tasks) = tasks_to_spawn.take() {
for task in tasks {
service.spawn_task(
task.select(service.on_exit())
.map(|_| ())
.map_err(|_| ())
);
}
}
if is_authority {
let proposer = basic_authorship::ProposerFactory {
client: service.client(),
@@ -119,19 +112,18 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
.ok_or(ServiceError::SelectChainRequired)?;
let babe_config = babe::BabeParams {
config: Config::get_or_compute(&*client)?,
keystore: service.keystore(),
client,
select_chain,
block_import,
env: proposer,
block_import,
sync_oracle: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
force_authoring: force_authoring,
time_source: babe_link,
force_authoring,
babe_link,
};
let babe = start_babe(babe_config)?;
let babe = babe::start_babe(babe_config)?;
let select = babe.select(service.on_exit()).then(|_| Ok(()));
// the BABE authoring task is considered infallible, i.e. if it
@@ -152,7 +144,7 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
// start the lightweight GRANDPA observer
service.spawn_task(Box::new(grandpa::run_grandpa_observer(
grandpa_config,
link_half,
grandpa_link,
service.network(),
service.on_exit(),
)?));
@@ -161,7 +153,7 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
// start the full GRANDPA voter
let voter_config = grandpa::GrandpaParams {
config: grandpa_config,
link: link_half,
link: grandpa_link,
network: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
on_exit: service.on_exit(),
@@ -197,28 +189,33 @@ pub fn new_light<C: Send + Default + 'static>(config: Configuration<C, GenesisCo
.with_transaction_pool(|config, client|
Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
)?
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, transaction_pool| {
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, _tx_pool| {
let fetch_checker = fetcher
.map(|fetcher| fetcher.checker().clone())
.ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
let block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, _>(
let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, _>(
client.clone(), backend, Arc::new(fetch_checker), client.clone()
)?;
let finality_proof_import = block_import.clone();
let finality_proof_import = grandpa_block_import.clone();
let finality_proof_request_builder =
finality_proof_import.create_finality_proof_request_builder();
// FIXME: pruning task isn't started since light client doesn't do `AuthoritySetup`.
let (import_queue, ..) = import_queue(
Config::get_or_compute(&*client)?,
block_import,
let (babe_block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link.clone(),
babe_block_import,
None,
Some(Box::new(finality_proof_import)),
client.clone(),
client,
inherent_data_providers.clone(),
Some(transaction_pool)
)?;
Ok((import_queue, finality_proof_request_builder))
+8 -2
View File
@@ -350,7 +350,8 @@ pub fn local_testnet_config() -> ChainSpec {
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::service::{new_full, new_light};
use crate::service::new_full;
use substrate_service::Roles;
use service_test;
fn local_testnet_genesis_instant_single() -> GenesisConfig {
@@ -398,7 +399,12 @@ pub(crate) mod tests {
service_test::connectivity(
integration_test_config_with_two_authorities(),
|config| new_full(config),
|config| new_light(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
true,
);
}
}
+82 -59
View File
@@ -20,7 +20,7 @@
use std::sync::Arc;
use babe::{import_queue, Config};
use babe;
use client::{self, LongestChain};
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
use node_executor;
@@ -47,7 +47,6 @@ macro_rules! new_full_start {
type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
let mut import_setup = None;
let inherent_data_providers = inherents::InherentDataProviders::new();
let mut tasks_to_spawn = Vec::new();
let builder = substrate_service::ServiceBuilder::new_full::<
node_primitives::Block, node_runtime::RuntimeApi, node_executor::Executor
@@ -58,36 +57,40 @@ macro_rules! new_full_start {
.with_transaction_pool(|config, client|
Ok(transaction_pool::txpool::Pool::new(config, transaction_pool::ChainApi::new(client)))
)?
.with_import_queue(|_config, client, mut select_chain, transaction_pool| {
.with_import_queue(|_config, client, mut select_chain, _transaction_pool| {
let select_chain = select_chain.take()
.ok_or_else(|| substrate_service::Error::SelectChainRequired)?;
let (block_import, link_half) =
let (grandpa_block_import, grandpa_link) =
grandpa::block_import::<_, _, _, node_runtime::RuntimeApi, _, _>(
client.clone(), client.clone(), select_chain
)?;
let justification_import = block_import.clone();
let justification_import = grandpa_block_import.clone();
let (import_queue, babe_link, babe_block_import, pruning_task) = babe::import_queue(
let (block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
block_import,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link.clone(),
block_import.clone(),
Some(Box::new(justification_import)),
None,
client.clone(),
client,
inherent_data_providers.clone(),
Some(transaction_pool)
)?;
import_setup = Some((babe_block_import.clone(), link_half, babe_link));
tasks_to_spawn.push(Box::new(pruning_task));
import_setup = Some((block_import, grandpa_link, babe_link));
Ok(import_queue)
})?
.with_rpc_extensions(|client, pool| -> RpcExtension {
node_rpc::create(client, pool)
})?;
(builder, import_setup, inherent_data_providers, tasks_to_spawn)
(builder, import_setup, inherent_data_providers)
}}
}
@@ -96,7 +99,7 @@ macro_rules! new_full_start {
/// We need to use a macro because the test suit doesn't work with an opaque service. It expects
/// concrete types instead.
macro_rules! new_full {
($config:expr) => {{
($config:expr, $with_startup_data: expr) => {{
use futures::sync::mpsc;
use network::DhtEvent;
@@ -112,7 +115,7 @@ macro_rules! new_full {
$config.disable_grandpa
);
let (builder, mut import_setup, inherent_data_providers, tasks_to_spawn) = new_full_start!($config);
let (builder, mut import_setup, inherent_data_providers) = new_full_start!($config);
// Dht event channel from the network to the authority discovery module. Use bounded channel to ensure
// back-pressure. Authority discovery is triggering one event per authority within the current authority set.
@@ -128,11 +131,10 @@ macro_rules! new_full {
.with_dht_event_tx(dht_event_tx)?
.build()?;
let (block_import, link_half, babe_link) = import_setup.take()
let (block_import, grandpa_link, babe_link) = import_setup.take()
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
// spawn any futures that were created in the previous setup steps
tasks_to_spawn.into_iter().for_each(|t| service.spawn_task(t));
($with_startup_data)(&block_import, &babe_link);
if is_authority {
let proposer = substrate_basic_authorship::ProposerFactory {
@@ -145,16 +147,15 @@ macro_rules! new_full {
.ok_or(substrate_service::Error::SelectChainRequired)?;
let babe_config = babe::BabeParams {
config: babe::Config::get_or_compute(&*client)?,
keystore: service.keystore(),
client,
select_chain,
block_import,
env: proposer,
block_import,
sync_oracle: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
force_authoring: force_authoring,
time_source: babe_link,
force_authoring,
babe_link,
};
let babe = babe::start_babe(babe_config)?;
@@ -181,7 +182,7 @@ macro_rules! new_full {
// start the lightweight GRANDPA observer
service.spawn_task(Box::new(grandpa::run_grandpa_observer(
config,
link_half,
grandpa_link,
service.network(),
service.on_exit(),
)?));
@@ -190,7 +191,7 @@ macro_rules! new_full {
// start the full GRANDPA voter
let grandpa_config = grandpa::GrandpaParams {
config: config,
link: link_half,
link: grandpa_link,
network: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
on_exit: service.on_exit(),
@@ -208,6 +209,9 @@ macro_rules! new_full {
}
Ok((service, inherent_data_providers))
}};
($config:expr) => {{
new_full!($config, |_, _| {})
}}
}
@@ -220,11 +224,8 @@ pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisCon
/// Builds a new service for a light client.
pub fn new_light<C: Send + Default + 'static>(config: Configuration<C, GenesisConfig>)
-> Result<impl AbstractService, ServiceError> {
use futures::Future;
type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
let inherent_data_providers = InherentDataProviders::new();
let mut tasks_to_spawn = Vec::new();
let service = ServiceBuilder::new_light::<Block, RuntimeApi, node_executor::Executor>(config)?
.with_select_chain(|_config, backend| {
@@ -233,31 +234,35 @@ pub fn new_light<C: Send + Default + 'static>(config: Configuration<C, GenesisCo
.with_transaction_pool(|config, client|
Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
)?
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, transaction_pool| {
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, _tx_pool| {
let fetch_checker = fetcher
.map(|fetcher| fetcher.checker().clone())
.ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
let block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, _>(
let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, _>(
client.clone(), backend, Arc::new(fetch_checker), client.clone()
)?;
let finality_proof_import = block_import.clone();
let finality_proof_import = grandpa_block_import.clone();
let finality_proof_request_builder =
finality_proof_import.create_finality_proof_request_builder();
let (import_queue, _, _, pruning_task) = import_queue(
Config::get_or_compute(&*client)?,
block_import,
let (babe_block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link,
babe_block_import,
None,
Some(Box::new(finality_proof_import)),
client.clone(),
client,
inherent_data_providers.clone(),
Some(transaction_pool)
)?;
tasks_to_spawn.push(Box::new(pruning_task));
Ok((import_queue, finality_proof_request_builder))
})?
.with_network_protocol(|_| Ok(NodeProtocol::new()))?
@@ -269,15 +274,6 @@ pub fn new_light<C: Send + Default + 'static>(config: Configuration<C, GenesisCo
})?
.build()?;
// spawn any futures that were created in the previous setup steps
for task in tasks_to_spawn.drain(..) {
service.spawn_task(
task.select(service.on_exit())
.map(|_| ())
.map_err(|_| ())
);
}
Ok(service)
}
@@ -286,22 +282,26 @@ mod tests {
use std::sync::Arc;
use babe::CompatibleDigestItem;
use consensus_common::{
Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy
Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport,
};
use node_primitives::DigestItem;
use node_primitives::{Block, DigestItem};
use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
use node_runtime::constants::{currency::CENTS, time::{PRIMARY_PROBABILITY, SLOT_DURATION}};
use node_runtime::constants::{currency::CENTS, time::SLOT_DURATION};
use codec::{Encode, Decode};
use primitives::{
crypto::Pair as CryptoPair, blake2_256,
crypto::Pair as CryptoPair,
sr25519::Public as AddressPublic, H256,
};
use sr_primitives::{generic::{BlockId, Era, Digest, SignedPayload}, traits::Block, OpaqueExtrinsic};
use sr_primitives::{
generic::{BlockId, Era, Digest, SignedPayload},
traits::Block as BlockT,
OpaqueExtrinsic,
};
use timestamp;
use finality_tracker;
use keyring::AccountKeyring;
use substrate_service::AbstractService;
use crate::service::{new_full, new_light};
use substrate_service::{AbstractService, Roles};
use crate::service::new_full;
#[cfg(feature = "rhd")]
fn test_sync() {
@@ -359,7 +359,11 @@ mod tests {
service_test::sync(
chain_spec::integration_test_config(),
|config| new_full(config),
|config| new_light(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
block_factory,
extrinsic_factory,
);
@@ -386,9 +390,21 @@ mod tests {
service_test::sync(
chain_spec,
|config| new_full!(config),
|config| new_light(config),
|service, inherent_data_providers| {
|config| {
let mut setup_handles = None;
new_full!(config, |
block_import: &babe::BabeBlockImport<_, _, Block, _, _, _>,
babe_link: &babe::BabeLink<Block>,
| {
setup_handles = Some((block_import.clone(), babe_link.clone()));
}).map(move |(node, x)| (node, (x, setup_handles.unwrap())))
},
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
|service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
let mut inherent_data = inherent_data_providers
.create_inherent_data()
.expect("Creates inherent data.");
@@ -411,8 +427,8 @@ mod tests {
slot_num,
&parent_header,
&*service.client(),
PRIMARY_PROBABILITY,
&keystore,
&babe_link,
) {
break babe_pre_digest;
}
@@ -440,7 +456,7 @@ mod tests {
);
slot_num += 1;
BlockImportParams {
let params = BlockImportParams {
origin: BlockOrigin::File,
header: new_header,
justification: None,
@@ -449,7 +465,10 @@ mod tests {
finalized: true,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
}
};
block_import.import_block(params, Default::default())
.expect("error importing test block");
},
|service, _| {
let amount = 5 * CENTS;
@@ -506,7 +525,11 @@ mod tests {
service_test::consensus(
crate::chain_spec::tests::integration_test_config_with_two_authorities(),
|config| new_full(config),
|config| new_light(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
vec![
"//Alice".into(),
"//Bob".into(),
+8 -16
View File
@@ -29,7 +29,7 @@ use node_primitives::{
AccountId, AccountIndex, Balance, BlockNumber, Hash, Index,
Moment, Signature, ContractExecResult,
};
use babe::{AuthorityId as BabeId};
use babe_primitives::{AuthorityId as BabeId};
use grandpa::fg_primitives::{self, ScheduledChange};
use client::{
block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult},
@@ -84,8 +84,8 @@ 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: 161,
impl_version: 161,
spec_version: 162,
impl_version: 162,
apis: RUNTIME_API_VERSIONS,
};
@@ -223,7 +223,7 @@ impl session::Trait for Runtime {
type ShouldEndSession = Babe;
type Event = Event;
type Keys = SessionKeys;
type ValidatorId = AccountId;
type ValidatorId = <Self as system::Trait>::AccountId;
type ValidatorIdOf = staking::StashOf<Self>;
type SelectInitialValidators = Staking;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
@@ -614,27 +614,19 @@ impl_runtime_apis! {
}
impl babe_primitives::BabeApi<Block> for Runtime {
fn startup_data() -> babe_primitives::BabeConfiguration {
fn configuration() -> babe_primitives::BabeConfiguration {
// The choice of `c` parameter (where `1 - c` represents the
// probability of a slot being empty), is done in accordance to the
// slot duration and expected target block time, for safely
// resisting network delays of maximum two seconds.
// <https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results>
babe_primitives::BabeConfiguration {
median_required_blocks: 1000,
slot_duration: Babe::slot_duration(),
epoch_length: EpochDuration::get(),
c: PRIMARY_PROBABILITY,
}
}
fn epoch() -> babe_primitives::Epoch {
babe_primitives::Epoch {
start_slot: Babe::epoch_start_slot(),
authorities: Babe::authorities(),
epoch_index: Babe::epoch_index(),
genesis_authorities: Babe::authorities(),
randomness: Babe::randomness(),
duration: EpochDuration::get(),
secondary_slots: Babe::secondary_slots().0,
secondary_slots: true,
}
}
}
+2
View File
@@ -22,7 +22,9 @@ runtime-io ={ package = "sr-io", path = "../../core/sr-io", default-features = f
[dev-dependencies]
lazy_static = "1.3.0"
parking_lot = "0.9.0"
sr-version = { path = "../../core/sr-version", default-features = false }
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
test-runtime = { package = "substrate-test-runtime", path = "../../core/test-runtime" }
[features]
default = ["std"]
+84 -94
View File
@@ -18,32 +18,36 @@
//! from VRF outputs and manages epoch transitions.
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unused_must_use, unsafe_code, unused_variables)]
// TODO: @marcio uncomment this when BabeEquivocation is integrated.
// #![forbid(dead_code)]
#![forbid(unused_must_use, unsafe_code, unused_variables, unused_must_use)]
#![deny(unused_imports)]
pub use timestamp;
use rstd::{result, prelude::*};
use support::{decl_storage, decl_module, StorageValue, StorageMap, traits::FindAuthor, traits::Get};
use timestamp::{OnTimestampSet};
use timestamp::OnTimestampSet;
use sr_primitives::{generic::DigestItem, ConsensusEngineId, Perbill};
use sr_primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon};
use sr_staking_primitives::{
SessionIndex,
offence::{Offence, Kind},
};
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, BabeAuthorityWeight, Epoch, RawBabePreDigest};
use babe_primitives::{
BABE_ENGINE_ID, ConsensusLog, BabeAuthorityWeight, NextEpochDescriptor, RawBabePreDigest,
SlotNumber,
};
pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH};
use system::ensure_root;
#[cfg(all(feature = "std", test))]
mod tests;
#[cfg(all(feature = "std", test))]
mod mock;
/// The BABE inherent identifier.
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot";
@@ -118,7 +122,7 @@ impl ProvideInherentData for InherentDataProvider {
}
pub trait Trait: timestamp::Trait {
type EpochDuration: Get<u64>;
type EpochDuration: Get<SlotNumber>;
type ExpectedBlockTime: Get<Self::Moment>;
}
@@ -127,6 +131,8 @@ pub const RANDOMNESS_LENGTH: usize = 32;
const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256;
type MaybeVrf = Option<[u8; 32 /* VRF_OUTPUT_LENGTH */]>;
decl_storage! {
trait Store for Module<T: Trait> as Babe {
/// Current epoch index.
@@ -135,22 +141,13 @@ decl_storage! {
/// Current epoch authorities.
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
/// signalled later than this.
pub EpochStartSlot get(epoch_start_slot): u64;
/// The slot at which the first epoch actually started. This is 0
/// until the first block of the chain.
pub GenesisSlot get(genesis_slot): u64;
/// 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
@@ -181,9 +178,9 @@ decl_storage! {
SegmentIndex build(|_| 0): u32;
UnderConstruction: map u32 => Vec<[u8; 32 /* VRF_OUTPUT_LENGTH */]>;
/// Temporary value (cleared at block finalization) which is true
/// Temporary value (cleared at block finalization) which is `Some`
/// if per-block initialization has already been called for current block.
Initialized get(initialized): Option<bool>;
Initialized get(initialized): Option<MaybeVrf>;
}
add_extra_genesis {
config(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>;
@@ -212,20 +209,13 @@ decl_module! {
/// Block finalization
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();
},
// at the end of the block, we can safely include the new VRF output
// from this block into the under-construction randomness. If we've determined
// that this block was the first in a new epoch, the changeover logic has
// already occurred at this point, so the under-construction randomness
// will only contain outputs from the right epoch.
if let Some(Some(vrf_output)) = Initialized::take() {
Self::deposit_vrf_output(&vrf_output);
}
}
}
@@ -269,15 +259,25 @@ impl<T: Trait> IsMember<AuthorityId> for Module<T> {
}
impl<T: Trait> session::ShouldEndSession<T::BlockNumber> for Module<T> {
fn should_end_session(_: T::BlockNumber) -> bool {
fn should_end_session(now: T::BlockNumber) -> bool {
// it might be (and it is in current implementation) that session module is calling
// should_end_session() from it's own on_initialize() handler
// => because session on_initialize() is called earlier than ours, let's ensure
// that we have synced with digest before checking if session should be ended
Self::do_initialize();
let diff = CurrentSlot::get().saturating_sub(EpochStartSlot::get());
diff >= T::EpochDuration::get()
// The session has technically ended during the passage of time
// between this block and the last, but we have to "end" the session now,
// since there is no earlier possible block we could have done it.
//
// The exception is for block 1: the genesis has slot 0, so we treat
// epoch 0 as having started at the slot of block 1. We want to use
// the same randomness and validator set as signalled in the genesis,
// so we don't rotate the session.
now != sr_primitives::traits::One::one() && {
let diff = CurrentSlot::get().saturating_sub(Self::current_epoch_start());
diff >= T::EpochDuration::get()
}
}
}
@@ -336,15 +336,18 @@ impl<T: Trait> Module<T> {
<T as timestamp::Trait>::MinimumPeriod::get().saturating_mul(2.into())
}
// finds the start slot of the current epoch. only guaranteed to
// give correct results after `do_initialize` of the first block
// in the chain (as its result is based off of `GenesisSlot`).
fn current_epoch_start() -> SlotNumber {
(EpochIndex::get() * T::EpochDuration::get()) + GenesisSlot::get()
}
fn deposit_consensus<U: Encode>(new: U) {
let log: DigestItem<T::Hash> = DigestItem::Consensus(BABE_ENGINE_ID, new.encode());
<system::Module<T>>::deposit_log(log.into())
}
fn get_inherent_digests() -> system::DigestOf<T> {
<system::Module<T>>::digest()
}
fn deposit_vrf_output(vrf_output: &[u8; VRF_OUTPUT_LENGTH]) {
let segment_idx = <SegmentIndex>::get();
let mut segment = <UnderConstruction>::get(&segment_idx);
@@ -363,13 +366,12 @@ impl<T: Trait> Module<T> {
fn do_initialize() {
// since do_initialize can be called twice (if session module is present)
// => let's ensure that we only modify the storage once per block
let initialized = Self::initialized().unwrap_or(false);
let initialized = Self::initialized().is_some();
if initialized {
return;
}
Initialized::put(true);
for digest in Self::get_inherent_digests()
let maybe_pre_digest = <system::Module<T>>::digest()
.logs
.iter()
.filter_map(|s| s.as_pre_runtime())
@@ -378,19 +380,40 @@ impl<T: Trait> Module<T> {
} else {
None
})
{
if EpochStartSlot::get() == 0 {
EpochStartSlot::put(digest.slot_number());
.next();
let maybe_vrf = maybe_pre_digest.and_then(|digest| {
// on the first non-zero block (i.e. block #1)
// this is where the first epoch (epoch #0) actually starts.
// we need to adjust internal storage accordingly.
if GenesisSlot::get() == 0 {
GenesisSlot::put(digest.slot_number());
debug_assert_ne!(GenesisSlot::get(), 0);
// deposit a log because this is the first block in epoch #0
// we use the same values as genesis because we haven't collected any
// randomness yet.
let next = NextEpochDescriptor {
authorities: Self::authorities(),
randomness: Self::randomness(),
};
Self::deposit_consensus(ConsensusLog::NextEpochData(next))
}
CurrentSlot::put(digest.slot_number());
if let RawBabePreDigest::Primary { vrf_output, .. } = digest {
Self::deposit_vrf_output(&vrf_output);
// place the VRF output into the `Initialized` storage item
// and it'll be put onto the under-construction randomness
// later, once we've decided which epoch this block is in.
Some(vrf_output)
} else {
None
}
});
return;
}
Initialized::put(maybe_vrf);
}
/// Call this function exactly once when an epoch changes, to update the
@@ -437,7 +460,12 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
{
Self::do_initialize();
// PRECONDITION: `should_end_session` has done initialization and is guaranteed
// by the session module to be called before this.
#[cfg(debug_assertions)]
{
assert!(Self::initialized().is_some())
}
// Update epoch index
let epoch_index = EpochIndex::get()
@@ -453,21 +481,6 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
Authorities::put(authorities);
// Update epoch start slot.
let now = CurrentSlot::get();
EpochStartSlot::mutate(|previous| {
loop {
// on the first epoch we must account for skipping at least one
// whole epoch, in case the first block is authored with a slot
// number far in the past.
if now.saturating_sub(*previous) < T::EpochDuration::get() {
break;
}
*previous = previous.saturating_add(T::EpochDuration::get());
}
});
// Update epoch randomness.
let next_epoch_index = epoch_index
.checked_add(1)
@@ -484,34 +497,11 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
(k, 1)
}).collect::<Vec<_>>();
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(),
let next = NextEpochDescriptor {
authorities: next_authorities,
randomness: next_randomness,
secondary_slots: secondary_slots.1,
};
Self::deposit_consensus(ConsensusLog::NextEpochData(next))
+112
View File
@@ -0,0 +1,112 @@
// 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/>.
//! Test utilities
#![allow(dead_code, unused_imports)]
use super::{Trait, Module, GenesisConfig};
use babe_primitives::AuthorityId;
use sr_primitives::{
traits::IdentityLookup, Perbill,
testing::{Header, UintAuthorityId},
impl_opaque_keys, key_types::DUMMY,
};
use sr_version::RuntimeVersion;
use support::{impl_outer_origin, parameter_types};
use runtime_io;
use primitives::{H256, Blake2Hasher};
impl_outer_origin!{
pub enum Origin for Test {}
}
type DummyValidatorId = u64;
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: u32 = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const MinimumPeriod: u64 = 1;
pub const EpochDuration: u64 = 3;
pub const ExpectedBlockTime: u64 = 1;
pub const Version: RuntimeVersion = test_runtime::VERSION;
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16);
}
impl system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Version = Version;
type Hashing = sr_primitives::traits::BlakeTwo256;
type AccountId = DummyValidatorId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type WeightMultiplierUpdate = ();
type Event = ();
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type AvailableBlockRatio = AvailableBlockRatio;
type MaximumBlockLength = MaximumBlockLength;
}
impl_opaque_keys! {
pub struct MockSessionKeys {
#[id(DUMMY)]
pub dummy: UintAuthorityId,
}
}
impl session::Trait for Test {
type Event = ();
type ValidatorId = <Self as system::Trait>::AccountId;
type ShouldEndSession = Babe;
type SessionHandler = (Babe,Babe,);
type OnSessionEnding = ();
type ValidatorIdOf = ();
type SelectInitialValidators = ();
type Keys = MockSessionKeys;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
impl timestamp::Trait for Test {
type Moment = u64;
type OnTimestampSet = Babe;
type MinimumPeriod = MinimumPeriod;
}
impl Trait for Test {
type EpochDuration = EpochDuration;
type ExpectedBlockTime = ExpectedBlockTime;
}
pub fn new_test_ext(authorities: Vec<DummyValidatorId>) -> runtime_io::TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
GenesisConfig {
authorities: authorities.into_iter().map(|a| (UintAuthorityId(a).to_public_key(), 1)).collect(),
}.assimilate_storage::<Test>(&mut t).unwrap();
t.into()
}
pub type System = system::Module<Test>;
pub type Babe = Module<Test>;
+127
View File
@@ -0,0 +1,127 @@
// 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/>.
//! Consensus extension module tests for BABE consensus.
use super::*;
use runtime_io::with_externalities;
use mock::{new_test_ext, Babe, Test};
use sr_primitives::{traits::OnFinalize, testing::{Digest, DigestItem}};
use session::ShouldEndSession;
const EMPTY_RANDOMNESS: [u8; 32] = [
74, 25, 49, 128, 53, 97, 244, 49,
222, 202, 176, 2, 231, 66, 95, 10,
133, 49, 213, 228, 86, 161, 164, 127,
217, 153, 138, 37, 48, 192, 248, 0,
];
fn make_pre_digest(
authority_index: babe_primitives::AuthorityIndex,
slot_number: babe_primitives::SlotNumber,
vrf_output: [u8; babe_primitives::VRF_OUTPUT_LENGTH],
vrf_proof: [u8; babe_primitives::VRF_PROOF_LENGTH],
) -> Digest {
let digest_data = babe_primitives::RawBabePreDigest::Primary {
authority_index,
slot_number,
vrf_output,
vrf_proof,
};
let log = DigestItem::PreRuntime(babe_primitives::BABE_ENGINE_ID, digest_data.encode());
Digest { logs: vec![log] }
}
#[test]
fn empty_randomness_is_correct() {
let s = compute_randomness([0; RANDOMNESS_LENGTH], 0, std::iter::empty(), None);
assert_eq!(s, EMPTY_RANDOMNESS);
}
#[test]
fn initial_values() {
with_externalities(&mut new_test_ext(vec![0, 1, 2, 3]), || {
assert_eq!(Babe::authorities().len(), 4)
})
}
#[test]
fn check_module() {
with_externalities(&mut new_test_ext(vec![0, 1, 2, 3]), || {
assert!(!Babe::should_end_session(0), "Genesis does not change sessions");
assert!(!Babe::should_end_session(200000),
"BABE does not include the block number in epoch calculations");
})
}
type System = system::Module<Test>;
#[test]
fn first_block_epoch_zero_start() {
with_externalities(&mut new_test_ext(vec![0, 1, 2, 3]), || {
let genesis_slot = 100;
let first_vrf = [1; 32];
let pre_digest = make_pre_digest(
0,
genesis_slot,
first_vrf,
[0xff; 64],
);
assert_eq!(Babe::genesis_slot(), 0);
System::initialize(&1, &Default::default(), &Default::default(), &pre_digest);
// see implementation of the function for details why: we issue an
// epoch-change digest but don't do it via the normal session mechanism.
assert!(!Babe::should_end_session(1));
assert_eq!(Babe::genesis_slot(), genesis_slot);
assert_eq!(Babe::current_slot(), genesis_slot);
assert_eq!(Babe::epoch_index(), 0);
Babe::on_finalize(1);
let header = System::finalize();
assert_eq!(SegmentIndex::get(), 0);
assert_eq!(UnderConstruction::get(0), vec![first_vrf]);
assert_eq!(Babe::randomness(), [0; 32]);
assert_eq!(NextRandomness::get(), [0; 32]);
assert_eq!(header.digest.logs.len(), 2);
assert_eq!(pre_digest.logs.len(), 1);
assert_eq!(header.digest.logs[0], pre_digest.logs[0]);
let authorities = Babe::authorities();
let consensus_log = babe_primitives::ConsensusLog::NextEpochData(
babe_primitives::NextEpochDescriptor {
authorities,
randomness: Babe::randomness(),
}
);
let consensus_digest = DigestItem::Consensus(BABE_ENGINE_ID, consensus_log.encode());
// first epoch descriptor has same info as last.
assert_eq!(header.digest.logs[1], consensus_digest.clone())
})
}
#[test]
fn authority_index() {
with_externalities(&mut new_test_ext(vec![0, 1, 2, 3]), || {
assert_eq!(
Babe::find_author((&[(BABE_ENGINE_ID, &[][..])]).into_iter().cloned()), None,
"Trivially invalid authorities are ignored")
})
}