From f78a780790b8e56de37c633c187d4f52466cb3a6 Mon Sep 17 00:00:00 2001 From: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com> Date: Tue, 23 Jul 2019 04:36:16 -0400 Subject: [PATCH] BABE Epochs (#3028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `epoch` field to `SlotInfo` * Add slot calculations * More work on epochs in BABE * Apply suggestions from code review Co-Authored-By: Bastian Köcher * Typo: `/` not `%` for division * Delete useless `LastSlotInEpoch::put(false)` * Bump `spec_version` * Make test suite pass again * Implement BABE epoch randomness signing * Try to fix compilation Currently causes a stack overflow in the compiler * Fix rustc stack overflow * Add missing `PartialEq` and `Eq` implementations * Fix compile errors in test suite * Another silly compile error * Clone `epoch` * Fix compile error in benchmarks * Implement `clone` for `Epoch` * Merge master * AUTHORING TEST PASSES!!! * Fix compilation * Bump `spec_version` * Fix compilation * Fix compilation (again) * Remove an outdated FIXME * Fix run.sh and move it to scripts/ * Delete commented-out code * Fix documentation Co-Authored-By: André Silva * Fix BABE initialization and refactor * Respond to review * typo * Remove useless data in `CheckedHeader::Deferred` * Remove `slot_number` from Epoch It is not needed, and only served to waste space and cause confusion. * Remove epoch from BABE digests * Move digest.rs to primitives * Fix incorrect warning names * Fix compile error * Consistent field naming for BABE digests * More compiler error fixex * Unbound variable * more compile errors * another compile error * Fix compile errors in runtime * another compile error * Another compile error * Fix wasm build * missing import * Fix more compile errors * yet another compile error * compile fix in test runtime * Fix and simplify the BABE runtime The BABE runtime was massively overcomplicated and also wrong. It assumed it needed to: 1. delay new authorities taking effect until the next epoch 2. not delay emitting `Consensus` digests to mark epoch changes However, the first is handled by the `srml_session` crate, and the second is flat-out incorrect: `Consensus` digests take effect immediately. Furthermore, `srml_babe` tried to duplicate the functionality of `srml_session::PeriodicSession`, but did it both clumsily and incorrectly. Fortunately, the new code is simpler and far more likely to be correct. * Use `system` to get the test authorities The genesis block used by tests defines no authorities. Only the test suite is affected. * Fix test runtime impl for BabeApi::epoch() with std * Fix compilation * Cached authorities are in the form of an epoch not a `Vec`. * `slots_per_epoch` is not fixed in general The BABE code previously assumed `slots_per_epoch` to be a constant, but that assumption is false in general. Furthermore, removing this assumption also allows a lot of code to go away. * fix compile error * Implement epoch checker * Fix runtime compilation * fork-tree: add method for finding a node in the tree * babe: register epoch transitions in fork tree and validate them * fork-tree: add method for arbitrary pruning * Expose the queued validator set to SRML modules BABE needs to know not only what the current validator set is, but also what the next validator set will be. Expose this to clients of the session module. * Bump hex-literal Hopefully this will fix the panic * babe: prune epoch change fork tree on finality * babe: validate epoch index on transition * babe: persist epoch changes tree * Fix compile error in tests * Fix compile error in tests * Another compile error in tests * Fix compilation of tests * core: move grandpa::is_descendent_of to client utils * babe: use is_descendent_of from client utils * babe: extract slot_number from pre_digest in import_block * Move BABE testsuite to its own file * Initial part of test code * Missing `WeightMultiplierUpdate` in test-runtime * bump `spec_version` * Add a test that a very bogus is rejected * Run the tests again * Fix compiler diagnostics * Bump `spec_version` * Initial infrastructure for mutation testing * Mutation testing of block import * babe: revert epoch changes in case of block import error * babe: fix logging target * babe: BabeBlockImport doesn't box inner BlockImport * babe: fix epoch check in block import * babe: populate authorities cache on block authorship * babe: remove unused functions * babe: use RANDOMNESS_LENGTH const * babe: remove unneeded config parameters * core: revert change to hex dependency version * cleanup gitignore * babe: add docs to aux_schema * babe: remove useless drops in tests * babe: remove annoying macos smart quotes * fork-tree: docs * fork-tree: add tests * babe: style * babe: rename randomness config variable * babe: remove randomness helper function * babe: style fixes * babe: add docs * babe: fix tests * node: bump spec_version * babe: fix tests --- substrate/Cargo.lock | 6 + substrate/Dockerfile | 4 +- substrate/core/client/src/client.rs | 47 + substrate/core/client/src/lib.rs | 1 + substrate/core/consensus/babe/Cargo.toml | 3 +- .../core/consensus/babe/primitives/Cargo.toml | 2 + .../babe/{ => primitives}/src/digest.rs | 95 +- .../core/consensus/babe/primitives/src/lib.rs | 37 +- .../core/consensus/babe/src/aux_schema.rs | 69 ++ substrate/core/consensus/babe/src/lib.rs | 818 +++++++++--------- substrate/core/consensus/babe/src/tests.rs | 340 ++++++++ substrate/core/consensus/slots/src/lib.rs | 6 +- substrate/core/consensus/slots/src/slots.rs | 2 +- .../core/finality-grandpa/src/environment.rs | 42 +- substrate/core/finality-grandpa/src/import.rs | 3 +- substrate/core/network/src/protocol/sync.rs | 1 - .../core/sr-api-macros/tests/runtime_calls.rs | 2 +- substrate/core/test-runtime/Cargo.toml | 6 + substrate/core/test-runtime/src/lib.rs | 76 +- substrate/core/utils/fork-tree/src/lib.rs | 165 +++- substrate/srml/aura/src/lib.rs | 2 +- substrate/srml/babe/Cargo.toml | 2 + substrate/srml/babe/src/lib.rs | 155 ++-- substrate/srml/grandpa/src/lib.rs | 2 +- substrate/srml/im-online/src/lib.rs | 2 +- substrate/srml/session/src/lib.rs | 29 +- substrate/srml/session/src/mock.rs | 6 +- substrate/srml/staking/src/lib.rs | 2 +- substrate/srml/staking/src/mock.rs | 6 +- substrate/srml/system/benches/bench.rs | 8 +- 30 files changed, 1368 insertions(+), 571 deletions(-) rename substrate/core/consensus/babe/{ => primitives}/src/digest.rs (55%) create mode 100644 substrate/core/consensus/babe/src/aux_schema.rs create mode 100644 substrate/core/consensus/babe/src/tests.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 7fea8d6e93..48edf4fa28 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3672,6 +3672,7 @@ dependencies = [ "sr-primitives 2.0.0", "sr-std 2.0.0", "srml-session 2.0.0", + "srml-staking 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", "srml-timestamp 2.0.0", @@ -4318,6 +4319,7 @@ name = "substrate-consensus-babe" version = "2.0.0" dependencies = [ "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fork-tree 2.0.0", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "futures-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4352,6 +4354,7 @@ name = "substrate-consensus-babe-primitives" version = "2.0.0" dependencies = [ "parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "schnorrkel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", "sr-std 2.0.0", "substrate-client 2.0.0", @@ -4854,8 +4857,11 @@ dependencies = [ "sr-primitives 2.0.0", "sr-std 2.0.0", "sr-version 2.0.0", + "srml-babe 2.0.0", "srml-executive 2.0.0", "srml-support 2.0.0", + "srml-system 2.0.0", + "srml-timestamp 2.0.0", "substrate-client 2.0.0", "substrate-consensus-aura-primitives 2.0.0", "substrate-consensus-babe-primitives 2.0.0", diff --git a/substrate/Dockerfile b/substrate/Dockerfile index b7512b2656..0271db8d14 100644 --- a/substrate/Dockerfile +++ b/substrate/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /substrate COPY . /substrate RUN apt-get update && \ - apt-get upgrade -y && \ + apt-get dist-upgrade -y && \ apt-get install -y cmake pkg-config libssl-dev git clang RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \ @@ -21,7 +21,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \ cargo install --git https://github.com/alexcrichton/wasm-gc && \ rustup default nightly && \ rustup default stable && \ - cargo build --$PROFILE + cargo build "--$PROFILE" # ===== SECOND STAGE ====== diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index 67a9140e9e..6decdfc9ca 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -1799,6 +1799,53 @@ impl backend::AuxStore for Client crate::backend::AuxStore::get_aux(&*self.backend, key) } } + +/// Utility methods for the client. +pub mod utils { + use super::*; + use crate::{backend::Backend, blockchain, error}; + use primitives::H256; + + /// Returns a function for checking block ancestry, the returned function will + /// return `true` if the given hash (second parameter) is a descendent of the + /// base (first parameter). If the `current` parameter is defined, it should + /// 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, RA>( + client: &'a Client, + current: Option<(&'a H256, &'a H256)>, + ) -> impl Fn(&H256, &H256) -> Result + 'a + where B: Backend, + E: CallExecutor + Send + Sync, + { + move |base, hash| { + if base == hash { return Ok(false); } + + let mut hash = hash; + if let Some((current_hash, current_parent_hash)) = current { + if base == current_hash { return Ok(false); } + if hash == current_hash { + if base == current_parent_hash { + return Ok(true); + } else { + hash = current_parent_hash; + } + } + } + + let tree_route = blockchain::tree_route( + #[allow(deprecated)] + client.backend().blockchain(), + BlockId::Hash(*hash), + BlockId::Hash(*base), + )?; + + Ok(tree_route.common_block().hash == *base) + } + } +} + #[cfg(test)] pub(crate) mod tests { use std::collections::HashMap; diff --git a/substrate/core/client/src/lib.rs b/substrate/core/client/src/lib.rs index 67cfdd4a64..441b2480e9 100644 --- a/substrate/core/client/src/lib.rs +++ b/substrate/core/client/src/lib.rs @@ -60,6 +60,7 @@ pub use crate::client::{ BlockBody, BlockStatus, ImportNotifications, FinalityNotifications, BlockchainEvents, BlockImportNotification, Client, ClientInfo, ExecutionStrategies, FinalityNotification, LongestChain, + utils, }; #[cfg(feature = "std")] pub use crate::notifications::{StorageEventStream, StorageChangeSet}; diff --git a/substrate/core/consensus/babe/Cargo.toml b/substrate/core/consensus/babe/Cargo.toml index 29c706dd13..8b473932dd 100644 --- a/substrate/core/consensus/babe/Cargo.toml +++ b/substrate/core/consensus/babe/Cargo.toml @@ -19,7 +19,9 @@ client = { package = "substrate-client", path = "../../client" } consensus_common = { package = "substrate-consensus-common", path = "../common" } slots = { package = "substrate-consensus-slots", path = "../slots" } runtime_primitives = { package = "sr-primitives", path = "../../sr-primitives" } +fork-tree = { path = "../../utils/fork-tree" } futures = "0.1.26" +futures03 = { package = "futures-preview", version = "0.3.0-alpha.17", features = ["compat"] } tokio-timer = "0.2.11" parking_lot = "0.8.0" log = "0.4.6" @@ -28,7 +30,6 @@ rand = "0.6.5" merlin = "1.0.3" [dev-dependencies] -futures03 = { package = "futures-preview", version = "0.3.0-alpha.17", features = ["compat"] } keyring = { package = "substrate-keyring", path = "../../keyring" } substrate-executor = { path = "../../executor" } network = { package = "substrate-network", path = "../../network", features = ["test-helpers"]} diff --git a/substrate/core/consensus/babe/primitives/Cargo.toml b/substrate/core/consensus/babe/primitives/Cargo.toml index a41fdeac5a..6eb0a251e1 100644 --- a/substrate/core/consensus/babe/primitives/Cargo.toml +++ b/substrate/core/consensus/babe/primitives/Cargo.toml @@ -12,6 +12,7 @@ runtime_primitives = { package = "sr-primitives", path = "../../../sr-primitives substrate-primitives = { path = "../../../primitives", default-features = false } slots = { package = "substrate-consensus-slots", path = "../../slots", optional = true } parity-codec = { version = "4.1.1", default-features = false } +schnorrkel = { version = "0.1.1", optional = true } [features] default = ["std"] @@ -20,5 +21,6 @@ std = [ "runtime_primitives/std", "substrate-client/std", "parity-codec/std", + "schnorrkel", "slots", ] diff --git a/substrate/core/consensus/babe/src/digest.rs b/substrate/core/consensus/babe/primitives/src/digest.rs similarity index 55% rename from substrate/core/consensus/babe/src/digest.rs rename to substrate/core/consensus/babe/primitives/src/digest.rs index 37ba27a309..f39cc96287 100644 --- a/substrate/core/consensus/babe/src/digest.rs +++ b/substrate/core/consensus/babe/primitives/src/digest.rs @@ -16,66 +16,85 @@ //! Private implementation details of BABE digests. -use primitives::sr25519::Signature; -use babe_primitives::{self, BABE_ENGINE_ID, SlotNumber}; +#[cfg(feature = "std")] +use substrate_primitives::sr25519::Signature; +#[cfg(feature = "std")] +use super::{BABE_ENGINE_ID, Epoch}; +#[cfg(not(feature = "std"))] +use super::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}; +use super::SlotNumber; +#[cfg(feature = "std")] use runtime_primitives::{DigestItem, generic::OpaqueDigestItemId}; +#[cfg(feature = "std")] use std::fmt::Debug; -use parity_codec::{Decode, Encode, Codec, Input}; -use schnorrkel::{vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}}; +use parity_codec::{Decode, Encode}; +#[cfg(feature = "std")] +use parity_codec::{Codec, Input}; +#[cfg(feature = "std")] +use schnorrkel::vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}; -/// A BABE pre-digest. It includes: -/// -/// * The public key of the author. -/// * The VRF proof. -/// * The VRF output. -/// * The slot number. -#[derive(Clone, Debug, PartialEq, Eq)] +/// A BABE pre-digest +#[cfg(feature = "std")] +#[derive(Clone, Debug)] pub struct BabePreDigest { - pub(super) vrf_output: VRFOutput, - pub(super) proof: VRFProof, - pub(super) index: babe_primitives::AuthorityIndex, - pub(super) slot_num: SlotNumber, + /// VRF output + pub vrf_output: VRFOutput, + /// VRF proof + pub vrf_proof: VRFProof, + /// Authority index + pub authority_index: super::AuthorityIndex, + /// Slot number + pub slot_number: SlotNumber, } /// The prefix used by BABE for its VRF keys. pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf"; -type RawBabePreDigest = ( - [u8; VRF_OUTPUT_LENGTH], - [u8; VRF_PROOF_LENGTH], - u64, - u64, -); +/// A raw version of `BabePreDigest`, usable on `no_std`. +#[derive(Copy, Clone, Encode, Decode)] +pub struct RawBabePreDigest { + /// Slot number + pub slot_number: SlotNumber, + /// Authority index + pub authority_index: super::AuthorityIndex, + /// VRF output + pub vrf_output: [u8; VRF_OUTPUT_LENGTH], + /// VRF proof + pub vrf_proof: [u8; VRF_PROOF_LENGTH], +} +#[cfg(feature = "std")] impl Encode for BabePreDigest { fn encode(&self) -> Vec { - let tmp: RawBabePreDigest = ( - *self.vrf_output.as_bytes(), - self.proof.to_bytes(), - self.index, - self.slot_num, - ); + let tmp = RawBabePreDigest { + vrf_output: *self.vrf_output.as_bytes(), + vrf_proof: self.vrf_proof.to_bytes(), + authority_index: self.authority_index, + slot_number: self.slot_number, + }; parity_codec::Encode::encode(&tmp) } } +#[cfg(feature = "std")] impl Decode for BabePreDigest { fn decode(i: &mut R) -> Option { - let (output, proof, index, slot_num): RawBabePreDigest = Decode::decode(i)?; + let RawBabePreDigest { vrf_output, vrf_proof, authority_index, slot_number } = Decode::decode(i)?; // Verify (at compile time) that the sizes in babe_primitives are correct - let _: [u8; babe_primitives::VRF_OUTPUT_LENGTH] = output; - let _: [u8; babe_primitives::VRF_PROOF_LENGTH] = proof; + let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output; + let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof; Some(BabePreDigest { - proof: VRFProof::from_bytes(&proof).ok()?, - vrf_output: VRFOutput::from_bytes(&output).ok()?, - index, - slot_num, + vrf_proof: VRFProof::from_bytes(&vrf_proof).ok()?, + vrf_output: VRFOutput::from_bytes(&vrf_output).ok()?, + authority_index, + slot_number, }) } } /// A digest item which is usable with BABE consensus. +#[cfg(feature = "std")] pub trait CompatibleDigestItem: Sized { /// Construct a digest item which contains a BABE pre-digest. fn babe_pre_digest(seal: BabePreDigest) -> Self; @@ -88,8 +107,12 @@ pub trait CompatibleDigestItem: Sized { /// If this item is a BABE signature, return the signature. fn as_babe_seal(&self) -> Option; + + /// If this item is a BABE epoch, return it. + fn as_babe_epoch(&self) -> Option; } +#[cfg(feature = "std")] impl CompatibleDigestItem for DigestItem where Hash: Debug + Send + Sync + Eq + Clone + Codec + 'static { @@ -108,4 +131,8 @@ impl CompatibleDigestItem for DigestItem where fn as_babe_seal(&self) -> Option { self.try_to(OpaqueDigestItemId::Seal(&BABE_ENGINE_ID)) } + + fn as_babe_epoch(&self) -> Option { + self.try_to(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID)) + } } diff --git a/substrate/core/consensus/babe/primitives/src/lib.rs b/substrate/core/consensus/babe/primitives/src/lib.rs index 655751b763..a7b49364f4 100644 --- a/substrate/core/consensus/babe/primitives/src/lib.rs +++ b/substrate/core/consensus/babe/primitives/src/lib.rs @@ -15,15 +15,22 @@ // along with Substrate. If not, see . //! Primitives for BABE. -#![deny(warnings, unsafe_code, missing_docs)] +#![deny(warnings)] +#![forbid(unsafe_code, missing_docs, unused_variables, unused_imports)] #![cfg_attr(not(feature = "std"), no_std)] +mod digest; + use parity_codec::{Encode, Decode}; use rstd::vec::Vec; use runtime_primitives::ConsensusEngineId; use substrate_primitives::sr25519::Public; use substrate_client::decl_runtime_apis; +#[cfg(feature = "std")] +pub use digest::{BabePreDigest, CompatibleDigestItem}; +pub use digest::{BABE_VRF_PREFIX, RawBabePreDigest}; + /// A Babe authority identifier. Necessarily equivalent to the schnorrkel public key used in /// the main Babe module. If that ever changes, then this must, too. pub type AuthorityId = Public; @@ -49,15 +56,29 @@ pub type SlotNumber = u64; /// The weight of an authority. pub type Weight = u64; +/// BABE epoch information +#[derive(Decode, Encode, Default, PartialEq, Eq, Clone)] +#[cfg_attr(any(feature = "std", test), derive(Debug))] +pub struct Epoch { + /// The authorities and their weights + pub authorities: Vec<(AuthorityId, Weight)>, + /// The epoch index + pub epoch_index: u64, + /// Randomness for this epoch + pub randomness: [u8; VRF_OUTPUT_LENGTH], + /// The duration of this epoch + pub duration: SlotNumber, +} + /// An consensus log item for BABE. -#[derive(Decode, Encode)] +#[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. #[codec(index = "1")] - NextEpochData(SlotNumber, Vec<(AuthorityId, Weight)>, [u8; VRF_OUTPUT_LENGTH]), + NextEpochData(Epoch), /// Disable the authority with given index. #[codec(index = "2")] OnDisabled(AuthorityIndex), @@ -72,6 +93,12 @@ pub struct BabeConfiguration { /// Dynamic slot duration may be supported in the future. pub slot_duration: u64, + /// The number of slots per BABE epoch. Currently, only + /// the value provided by this type at genesis will be used. + /// + /// Dynamic slot duration may be supported in the future. + pub slots_per_epoch: u64, + /// The expected block time in milliseconds for BABE. Currently, /// only the value provided by this type at genesis will be used. /// @@ -116,7 +143,7 @@ decl_runtime_apis! { /// Dynamic configuration may be supported in the future. fn startup_data() -> BabeConfiguration; - /// Get the current authorites for Babe. - fn authorities() -> Vec; + /// Get the current epoch data for Babe. + fn epoch() -> Epoch; } } diff --git a/substrate/core/consensus/babe/src/aux_schema.rs b/substrate/core/consensus/babe/src/aux_schema.rs new file mode 100644 index 0000000000..50d2a727f1 --- /dev/null +++ b/substrate/core/consensus/babe/src/aux_schema.rs @@ -0,0 +1,69 @@ +// 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 . + +//! Schema for BABE epoch changes in the aux-db. + +use log::info; +use parity_codec::{Decode, Encode}; + +use client::backend::AuxStore; +use client::error::{Result as ClientResult, Error as ClientError}; +use runtime_primitives::traits::Block as BlockT; + +use super::{EpochChanges, SharedEpochChanges}; + +const BABE_EPOCH_CHANGES: &[u8] = b"babe_epoch_changes"; + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> + where + B: AuxStore, + T: Decode, +{ + let corrupt = || ClientError::Backend(format!("BABE DB is corrupted.")).into(); + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]).ok_or_else(corrupt).map(Some) + } +} + +/// Load or initialize persistent epoch change data from backend. +pub(crate) fn load_epoch_changes( + backend: &B, +) -> ClientResult> { + let epoch_changes = load_decode::<_, EpochChanges>(backend, BABE_EPOCH_CHANGES)? + .map(Into::into) + .unwrap_or_else(|| { + info!(target: "babe", + "Creating empty BABE epoch changes on what appears to be first startup." + ); + SharedEpochChanges::new() + }); + + Ok(epoch_changes) +} + +/// Update the epoch changes on disk after a change. +pub(crate) fn write_epoch_changes( + epoch_changes: &EpochChanges, + write_aux: F, +) -> R where + F: FnOnce(&[(&'static [u8], &[u8])]) -> R, +{ + let encoded_epoch_changes = epoch_changes.encode(); + write_aux( + &[(BABE_EPOCH_CHANGES, encoded_epoch_changes.as_slice())], + ) +} diff --git a/substrate/core/consensus/babe/src/lib.rs b/substrate/core/consensus/babe/src/lib.rs index 055ceca782..be5b476dd2 100644 --- a/substrate/core/consensus/babe/src/lib.rs +++ b/substrate/core/consensus/babe/src/lib.rs @@ -17,34 +17,26 @@ //! # BABE consensus //! //! BABE (Blind Assignment for Blockchain Extension) consensus in Substrate. -//! -//! # Stability -//! -//! This crate is highly unstable and experimental. Breaking changes may -//! happen at any point. This crate is also missing features, such as banning -//! of malicious validators, that are essential for a production network. -#![forbid(unsafe_code, missing_docs, unused_must_use)] + +#![forbid(unsafe_code, missing_docs, unused_must_use, unused_imports, unused_variables)] #![cfg_attr(not(test), forbid(dead_code))] -extern crate core; -mod digest; -use digest::CompatibleDigestItem; -pub use digest::{BabePreDigest, BABE_VRF_PREFIX}; pub use babe_primitives::*; pub use consensus_common::SyncOracle; +use consensus_common::ImportResult; use consensus_common::import_queue::{ - BoxBlockImport, BoxJustificationImport, BoxFinalityProofImport, + BoxJustificationImport, BoxFinalityProofImport, }; use consensus_common::well_known_cache_keys::Id as CacheKeyId; -use runtime_primitives::{generic, generic::{BlockId, OpaqueDigestItemId}, Justification}; +use runtime_primitives::{generic, generic::BlockId, Justification}; use runtime_primitives::traits::{ - Block as BlockT, Header, DigestItemFor, ProvideRuntimeApi, + Block as BlockT, Header, DigestItemFor, NumberFor, ProvideRuntimeApi, SimpleBitOps, Zero, }; -use std::{sync::Arc, u64, fmt::{Debug, Display}, time::{Instant, Duration}}; +use std::{collections::HashMap, sync::Arc, u64, fmt::{Debug, Display}, time::{Instant, Duration}}; use runtime_support::serde::{Serialize, Deserialize}; use parity_codec::{Decode, Encode}; -use parking_lot::Mutex; -use primitives::{Pair, Public, sr25519}; +use parking_lot::{Mutex, MutexGuard}; +use primitives::{Blake2Hasher, H256, Pair, Public, sr25519}; use merlin::Transcript; use inherents::{InherentDataProviders, InherentData}; use substrate_telemetry::{ @@ -72,18 +64,26 @@ use consensus_common::{SelectChain, well_known_cache_keys}; use consensus_common::import_queue::{Verifier, BasicQueue}; use client::{ block_builder::api::BlockBuilder as BlockBuilderApi, - blockchain::ProvideCache, + blockchain::{self, HeaderBackend, ProvideCache}, + BlockchainEvents, + CallExecutor, Client, runtime_api::ApiExt, - error::Result as CResult, - backend::AuxStore, + error::Result as ClientResult, + backend::{AuxStore, Backend}, + utils::is_descendent_of, }; +use fork_tree::ForkTree; use slots::{CheckedHeader, check_equivocation}; -use futures::{Future, IntoFuture, future}; +use futures::{Future, IntoFuture, future, stream::Stream}; +use futures03::{StreamExt as _, TryStreamExt as _}; use tokio_timer::Timeout; use log::{error, warn, debug, info, trace}; use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible, SignedDuration}; +mod aux_schema; +#[cfg(test)] +mod tests; pub use babe_primitives::AuthorityId; /// A slot duration. Create with `get_or_compute`. @@ -95,7 +95,7 @@ pub struct Config(slots::SlotDuration); impl Config { /// Either fetch the slot duration from disk or compute it from the genesis /// state. - pub fn get_or_compute(client: &C) -> CResult + pub fn get_or_compute(client: &C) -> ClientResult where C: AuxStore + ProvideRuntimeApi, C::Api: BabeApi, { @@ -137,7 +137,7 @@ impl SlotCompatible for BabeLink { /// Parameters for BABE. pub struct BabeParams { - /// The configuration for BABE. Includes the slot duration, threshold, and + /// The configuration for BABE. Includes the slot duration, threshold, and /// other parameters. pub config: Config, @@ -207,7 +207,7 @@ pub fn start_babe(BabeParams { threshold: config.threshold(), }; register_babe_inherent_data_provider(&inherent_data_providers, config.0.slot_duration())?; - Ok(slots::start_slot_worker::<_, _, _, _, _, _>( + Ok(slots::start_slot_worker( config.0, select_chain, worker, @@ -254,10 +254,10 @@ impl SlotWorker for BabeWorker w let block_import = self.block_import.clone(); let ref env = self.env; - let (timestamp, slot_num, slot_duration) = + let (timestamp, slot_number, slot_duration) = (slot_info.timestamp, slot_info.number, slot_info.duration); - let authorities = match authorities(client.as_ref(), &BlockId::Hash(chain_head.hash())) { + let epoch = match epoch(client.as_ref(), &BlockId::Hash(chain_head.hash())) { Ok(authorities) => authorities, Err(e) => { error!( @@ -273,6 +273,12 @@ impl SlotWorker for BabeWorker w } }; + let Epoch { ref authorities, randomness, epoch_index, .. } = epoch; + + if authorities.is_empty() { + error!(target: "babe", "No authorities at block {:?}", chain_head.hash()); + } + if !self.force_authoring && self.sync_oracle.is_offline() && authorities.len() > 1 { debug!(target: "babe", "Skipping proposal slot. Waiting for the network."); telemetry!(CONSENSUS_DEBUG; "babe.skipping_proposal_slot"; @@ -281,44 +287,40 @@ impl SlotWorker for BabeWorker w return Box::new(future::ok(())); } - // FIXME replace the dummy empty slices with real data - // https://github.com/paritytech/substrate/issues/2435 - // https://github.com/paritytech/substrate/issues/2436 - let proposal_work = if let Some(((inout, proof, _batchable_proof), index)) = claim_slot( - &[0u8; 0], + let proposal_work = if let Some(((inout, vrf_proof, _batchable_proof), authority_index)) = claim_slot( + &randomness, slot_info.number, - &[0u8; 0], - 0, - &authorities, + epoch_index, + epoch, &pair, self.threshold, ) { debug!( target: "babe", "Starting authorship at slot {}; timestamp = {}", - slot_num, + slot_number, timestamp, ); telemetry!(CONSENSUS_DEBUG; "babe.starting_authorship"; - "slot_num" => slot_num, "timestamp" => timestamp + "slot_number" => slot_number, "timestamp" => timestamp ); // we are the slot author. make a block and sign it. let proposer = match env.init(&chain_head) { Ok(p) => p, Err(e) => { - warn!(target: "babe", "Unable to author block in slot {:?}: {:?}", slot_num, e); + warn!(target: "babe", "Unable to author block in slot {:?}: {:?}", slot_number, e); telemetry!(CONSENSUS_WARN; "babe.unable_authoring_block"; - "slot" => slot_num, "err" => ?e + "slot" => slot_number, "err" => ?e ); return Box::new(future::ok(())) } }; let inherent_digest = BabePreDigest { - proof, + vrf_proof, vrf_output: inout.to_output(), - index: index as u64, - slot_num, + authority_index: authority_index as u64, + slot_number, }; // deadline our production to approx. the end of the slot @@ -343,27 +345,19 @@ impl SlotWorker for BabeWorker w // minor hack since we don't have access to the timestamp // that is actually set by the proposer. let slot_after_building = SignedDuration::default().slot_now(slot_duration); - if slot_after_building != slot_num { + if slot_after_building != slot_number { info!( target: "babe", "Discarding proposal for slot {}; block production took too long", - slot_num + slot_number ); telemetry!(CONSENSUS_INFO; "babe.discarding_proposal_took_too_long"; - "slot" => slot_num + "slot" => slot_number ); - return + return; } let (header, body) = b.deconstruct(); - let pre_digest: Result = find_pre_digest::(&header); - if let Err(e) = pre_digest { - error!(target: "babe", "FATAL ERROR: Invalid pre-digest: {}!", e); - return - } else { - trace!(target: "babe", "Got correct number of seals. Good!") - }; - let header_num = header.number().clone(); let parent_hash = header.parent_hash().clone(); @@ -373,7 +367,12 @@ impl SlotWorker for BabeWorker w let signature = pair.sign(header_hash.as_ref()); let signature_digest_item = DigestItemFor::::babe_seal(signature); - let import_block: BlockImportParams = BlockImportParams { + let cache = find_epoch_digest::(&header) + .map(|epoch| vec![(well_known_cache_keys::AUTHORITIES, epoch.encode())]) + .map(|keys| keys.into_iter().collect()) + .unwrap_or_default(); + + let import_block = BlockImportParams:: { origin: BlockOrigin::Own, header, justification: None, @@ -390,13 +389,14 @@ impl SlotWorker for BabeWorker w import_block.post_header().hash(), header_hash, ); + telemetry!(CONSENSUS_INFO; "babe.pre_sealed_block"; "header_num" => ?header_num, "hash_now" => ?import_block.post_header().hash(), "hash_previously" => ?header_hash, ); - if let Err(e) = block_import.lock().import_block(import_block, Default::default()) { + if let Err(e) = block_import.lock().import_block(import_block, cache) { warn!(target: "babe", "Error with block built on {:?}: {:?}", parent_hash, e); telemetry!(CONSENSUS_WARN; "babe.err_with_block_built_on"; @@ -418,22 +418,34 @@ macro_rules! babe_err { }; } +/// Extract the BABE pre digest from the given header. Pre-runtime digests are +/// mandatory, the function will return `Err` if none is found. fn find_pre_digest(header: &B::Header) -> Result where DigestItemFor: CompatibleDigestItem, { - let mut pre_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}", log); - match (log.as_babe_pre_digest(), pre_digest.is_some()) { - (Some(_), true) => Err(babe_err!("Multiple BABE pre-runtime headers, rejecting!"))?, - (None, _) => trace!(target: "babe", "Ignoring digest not meant for us"), - (s, false) => pre_digest = s, + if let Some(pre_digest) = log.as_babe_pre_digest() { + return Ok(pre_digest); } } - pre_digest.ok_or_else(|| babe_err!("No BABE pre-runtime digest found")) + + Err(babe_err!("No BABE pre-runtime digest found")) } -/// check a header has been signed by the right key. If the slot is too far in +/// Extract the BABE epoch change digest from the given header, if it exists. +fn find_epoch_digest(header: &B::Header) -> Option + where DigestItemFor: CompatibleDigestItem, +{ + for log in header.digest().logs() { + if let Some(epoch_digest) = log.as_babe_epoch() { + return Some(epoch_digest); + } + } + + return None; +} + +/// Check a header has been signed by the right key. If the slot is too far in /// the future, an error will be returned. If successful, returns the pre-header /// and the digest item containing the seal. /// @@ -441,7 +453,6 @@ fn find_pre_digest(header: &B::Header) -> Result( client: &C, @@ -449,6 +460,8 @@ fn check_header( mut header: B::Header, hash: B::Hash, authorities: &[AuthorityId], + randomness: [u8; 32], + epoch_index: u64, threshold: u64, ) -> Result, DigestItemFor)>, String> where DigestItemFor: CompatibleDigestItem, @@ -464,26 +477,27 @@ fn check_header( })?; let pre_digest = find_pre_digest::(&header)?; - let BabePreDigest { slot_num, index, ref proof, ref vrf_output } = pre_digest; - if slot_num > slot_now { + let BabePreDigest { slot_number, authority_index, ref vrf_proof, ref vrf_output } = pre_digest; + + if slot_number > slot_now { header.digest_mut().push(seal); - Ok(CheckedHeader::Deferred(header, slot_num)) - } else if index > authorities.len() as u64 { + Ok(CheckedHeader::Deferred(header, slot_number)) + } else if authority_index > authorities.len() as u64 { Err(babe_err!("Slot author not found")) } else { - let (pre_hash, author): (_, &sr25519::Public) = (header.hash(), &authorities[index as usize]); + let (pre_hash, author) = (header.hash(), &authorities[authority_index as usize]); if sr25519::Pair::verify(&sig, pre_hash, author.clone()) { let (inout, _batchable_proof) = { let transcript = make_transcript( - Default::default(), - slot_num, - Default::default(), - 0, + &randomness, + slot_number, + epoch_index, ); + schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| { - p.vrf_verify(transcript, vrf_output, proof) + p.vrf_verify(transcript, vrf_output, vrf_proof) }).map_err(|s| { babe_err!("VRF verification failed: {:?}", s) })? @@ -497,14 +511,14 @@ fn check_header( if let Some(equivocation_proof) = check_equivocation( client, slot_now, - slot_num, + slot_number, &header, author, ).map_err(|e| e.to_string())? { info!( "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}", author, - slot_num, + slot_number, equivocation_proof.fst_header().hash(), equivocation_proof.snd_header().hash(), ); @@ -524,7 +538,7 @@ pub struct BabeLink(Arc, Vec<(Instant, u64)>)>>); /// A verifier for Babe blocks. pub struct BabeVerifier { - client: Arc, + api: Arc, inherent_data_providers: inherents::InherentDataProviders, config: Config, time_source: BabeLink, @@ -539,7 +553,7 @@ impl BabeVerifier { ) -> Result<(), String> where C: ProvideRuntimeApi, C::Api: BlockBuilderApi { - let inherent_res = self.client.runtime_api().check_inherents( + let inherent_res = self.api.runtime_api().check_inherents( &block_id, block, inherent_data, @@ -558,29 +572,33 @@ impl BabeVerifier { fn median_algorithm( median_required_blocks: u64, slot_duration: u64, - slot_num: u64, + slot_number: u64, slot_now: u64, time_source: &mut (Option, Vec<(Instant, u64)>), ) { let num_timestamps = time_source.1.len(); if num_timestamps as u64 >= median_required_blocks && median_required_blocks > 0 { let mut new_list: Vec<_> = time_source.1.iter().map(|&(t, sl)| { - let offset: u128 = u128::from(slot_duration) - .checked_mul(1_000_000u128) // self.config.get() returns *milliseconds* - .and_then(|x| x.checked_mul(u128::from(slot_num).saturating_sub(u128::from(sl)))) - .expect("we cannot have timespans long enough for this to overflow; qed"); - const NANOS_PER_SEC: u32 = 1_000_000_000; - let nanos = (offset % u128::from(NANOS_PER_SEC)) as u32; - let secs = (offset / u128::from(NANOS_PER_SEC)) as u64; - t + Duration::new(secs, nanos) - }).collect(); + let offset: u128 = u128::from(slot_duration) + .checked_mul(1_000_000u128) // self.config.get() returns *milliseconds* + .and_then(|x| x.checked_mul(u128::from(slot_number).saturating_sub(u128::from(sl)))) + .expect("we cannot have timespans long enough for this to overflow; qed"); + + const NANOS_PER_SEC: u32 = 1_000_000_000; + let nanos = (offset % u128::from(NANOS_PER_SEC)) as u32; + let secs = (offset / u128::from(NANOS_PER_SEC)) as u64; + + t + Duration::new(secs, nanos) + }).collect(); + // FIXME #2926: use a selection algorithm instead of a full sorting algorithm. new_list.sort_unstable(); + let &median = new_list .get(num_timestamps / 2) .expect("we have at least one timestamp, so this is a valid index; qed"); + time_source.1.clear(); - // FIXME #2927: pass this to the block authoring logic somehow time_source.0.replace(Instant::now() - median); } else { time_source.1.push((Instant::now(), slot_now)) @@ -590,7 +608,6 @@ fn median_algorithm( impl Verifier for BabeVerifier where C: ProvideRuntimeApi + Send + Sync + AuxStore + ProvideCache, C::Api: BlockBuilderApi + BabeApi, - DigestItemFor: CompatibleDigestItem, { fn verify( &self, @@ -613,34 +630,41 @@ impl Verifier for BabeVerifier where .inherent_data_providers .create_inherent_data() .map_err(String::from)?; + let (_, slot_now, _) = self.time_source.extract_timestamp_and_slot(&inherent_data) .map_err(|e| format!("Could not extract timestamp and slot: {:?}", e))?; + let hash = header.hash(); let parent_hash = *header.parent_hash(); - let authorities = authorities(self.client.as_ref(), &BlockId::Hash(parent_hash)) - .map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?; + let Epoch { authorities, randomness, epoch_index, .. } = + epoch(self.api.as_ref(), &BlockId::Hash(parent_hash)) + .map_err(|e| format!("Could not fetch epoch at {:?}: {:?}", parent_hash, e))?; - // we add one to allow for some small drift. - // FIXME #1019 in the future, alter this queue to allow deferring of - // headers + let authorities: Vec<_> = authorities.into_iter().map(|(s, _)| s).collect(); + + // We add one to allow for some small drift. + // FIXME #1019 in the future, alter this queue to allow deferring of headers let checked_header = check_header::( - &self.client, + &self.api, slot_now + 1, header, hash, &authorities[..], + randomness, + epoch_index, self.config.threshold(), )?; + match checked_header { CheckedHeader::Checked(pre_header, (pre_digest, seal)) => { - let BabePreDigest { slot_num, .. } = pre_digest.as_babe_pre_digest() + let BabePreDigest { slot_number, .. } = pre_digest.as_babe_pre_digest() .expect("check_header always returns a pre-digest digest item; qed"); // if the body is passed through, we need to use the runtime // to check that the internally-set timestamp in the inherents // actually matches the slot set in the seal. if let Some(inner_body) = body.take() { - inherent_data.babe_replace_inherent_data(slot_num); + inherent_data.babe_replace_inherent_data(slot_number); let block = B::new(pre_header.clone(), inner_body); self.check_inherents( @@ -660,11 +684,10 @@ impl Verifier for BabeVerifier where "pre_header" => ?pre_header); // `Consensus` is the Babe-specific authorities change log. - // It's an encoded `Vec`, the same format as is stored in the cache, - // so no need to decode/re-encode. - let maybe_keys = pre_header.digest() - .log(|l| l.try_as_raw(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID))) - .map(|blob| vec![(well_known_cache_keys::AUTHORITIES, blob.to_vec())]); + // It's an encoded `Epoch`, the same format as is stored in the + // cache, so no need to decode/re-encode. + let maybe_keys = find_epoch_digest::(&pre_header) + .map(|epoch| vec![(well_known_cache_keys::AUTHORITIES, epoch.encode())]); let import_block = BlockImportParams { origin, @@ -676,13 +699,16 @@ impl Verifier for BabeVerifier where auxiliary: Vec::new(), fork_choice: ForkChoiceStrategy::LongestChain, }; + + // FIXME: this should eventually be moved to BabeBlockImport median_algorithm( self.config.0.median_required_blocks, self.config.get(), - slot_num, + slot_number, slot_now, &mut *self.time_source.0.lock(), ); + // FIXME #1019 extract authorities Ok((import_block, maybe_keys)) } @@ -697,10 +723,9 @@ impl Verifier for BabeVerifier where } } -fn authorities(client: &C, at: &BlockId) -> Result< - Vec, - ConsensusError, -> where +/// Extract current epoch data from cache and fallback to querying the runtime +/// if the cache isn't populated. +fn epoch(client: &C, at: &BlockId) -> Result where B: BlockT, C: ProvideRuntimeApi + ProvideCache, C::Api: BabeApi, @@ -711,10 +736,16 @@ fn authorities(client: &C, at: &BlockId) -> Result< .and_then(|v| Decode::decode(&mut &v[..]))) .or_else(|| { if client.runtime_api().has_api::>(at).unwrap_or(false) { - BabeApi::authorities(&*client.runtime_api(), at).ok() + let s = BabeApi::epoch(&*client.runtime_api(), at).ok()?; + if s.authorities.is_empty() { + error!("No authorities!"); + None + } else { + Some(s) + } } else { - panic!("We don’t support deprecated code with new consensus algorithms, \ - therefore this is unreachable; qed") + error!("bad api!"); + None } }).ok_or(consensus_common::Error::InvalidAuthoritiesSet) } @@ -746,12 +777,10 @@ fn get_keypair(q: &sr25519::Pair) -> &Keypair { fn make_transcript( randomness: &[u8], slot_number: u64, - genesis_hash: &[u8], epoch: u64, ) -> Transcript { let mut transcript = Transcript::new(&BABE_ENGINE_ID); transcript.commit_bytes(b"slot number", &slot_number.to_le_bytes()); - transcript.commit_bytes(b"genesis block hash", genesis_hash); transcript.commit_bytes(b"current epoch", &epoch.to_le_bytes()); transcript.commit_bytes(b"chain randomness", randomness); transcript @@ -769,30 +798,24 @@ fn check(inout: &VRFInOut, threshold: u64) -> bool { fn claim_slot( randomness: &[u8], slot_number: u64, - genesis_hash: &[u8], epoch: u64, - authorities: &[AuthorityId], + Epoch { ref authorities, .. }: Epoch, key: &sr25519::Pair, threshold: u64, ) -> Option<((VRFInOut, VRFProof, VRFProofBatchable), usize)> { let public = &key.public(); - let index = authorities.iter().position(|s| s == public)?; - let transcript = make_transcript( - randomness, - slot_number, - genesis_hash, - epoch, - ); + let authority_index = authorities.iter().position(|s| &s.0 == public)?; + let transcript = make_transcript(randomness, slot_number, epoch); // Compute the threshold we will use. // - // We already checked that authorities contains `key.public()`, so it can’t + // We already checked that authorities contains `key.public()`, so it can't // be empty. Therefore, this division is safe. let threshold = threshold / authorities.len() as u64; get_keypair(key) .vrf_sign_n_check(transcript, |inout| check(inout, threshold)) - .map(|s|(s, index)) + .map(|s|(s, authority_index)) } fn initialize_authorities_cache(client: &C) -> Result<(), ConsensusError> where @@ -808,10 +831,10 @@ fn initialize_authorities_cache(client: &C) -> Result<(), ConsensusError> // check if we already have initialized the cache let genesis_id = BlockId::Number(Zero::zero()); - let genesis_authorities: Option> = cache + let genesis_epoch: Option = cache .get_at(&well_known_cache_keys::AUTHORITIES, &genesis_id) .and_then(|v| Decode::decode(&mut &v[..])); - if genesis_authorities.is_some() { + if genesis_epoch.is_some() { return Ok(()); } @@ -820,285 +843,298 @@ fn initialize_authorities_cache(client: &C) -> Result<(), ConsensusError> "Error initializing authorities cache: {}", error, ))); - let genesis_authorities = authorities(client, &genesis_id)?; - cache.initialize(&well_known_cache_keys::AUTHORITIES, genesis_authorities.encode()) + + let genesis_epoch = epoch(client, &genesis_id)?; + cache.initialize(&well_known_cache_keys::AUTHORITIES, genesis_epoch.encode()) .map_err(map_err) } -/// Start an import queue for the Babe consensus algorithm. -pub fn import_queue( +/// 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, the new epoch +/// index and the minimum *slot number* when the next epoch should start (i.e. +/// slot number begin + duration). +type EpochChanges = ForkTree< + ::Hash, + NumberFor, + (u64, SlotNumber), +>; + +/// A shared epoch changes tree. +#[derive(Clone)] +struct SharedEpochChanges { + inner: Arc>>, +} + +impl SharedEpochChanges { + fn new() -> Self { + SharedEpochChanges { + inner: Arc::new(Mutex::new(EpochChanges::::new())) + } + } + + fn lock(&self) -> MutexGuard> { + self.inner.lock() + } +} + +impl From> for SharedEpochChanges { + fn from(epoch_changes: EpochChanges) -> Self { + SharedEpochChanges { + inner: Arc::new(Mutex::new(epoch_changes)) + } + } +} + +/// A block-import handler for BABE. +/// +/// This scans each imported block for epoch change signals. The signals are +/// tracked in a tree (of all forks), and the import logic validates all epoch +/// change transitions, i.e. whether a given epoch change is expected or whether +/// it is missing. +/// +/// The epoch change tree should be pruned as blocks are finalized. +pub struct BabeBlockImport { + inner: I, + client: Arc>, + epoch_changes: SharedEpochChanges, +} + +impl Clone for BabeBlockImport { + fn clone(&self) -> Self { + BabeBlockImport { + inner: self.inner.clone(), + client: self.client.clone(), + epoch_changes: self.epoch_changes.clone(), + } + } +} + +impl BabeBlockImport { + fn new( + client: Arc>, + epoch_changes: SharedEpochChanges, + block_import: I, + ) -> Self { + BabeBlockImport { + client, + inner: block_import, + epoch_changes, + } + } +} + +impl BlockImport for BabeBlockImport where + Block: BlockT, + I: BlockImport + Send + Sync, + I::Error: Into, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + type Error = ConsensusError; + + fn import_block( + &mut self, + mut block: BlockImportParams, + new_cache: HashMap>, + ) -> Result { + let hash = block.post_header().hash(); + let number = block.header.number().clone(); + + // early exit if block already in chain, otherwise the check for + // epoch changes will error when trying to re-import an epoch change + #[allow(deprecated)] + match self.client.backend().blockchain().status(BlockId::Hash(hash)) { + Ok(blockchain::BlockStatus::InChain) => return Ok(ImportResult::AlreadyInChain), + Ok(blockchain::BlockStatus::Unknown) => {}, + Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()), + } + + let slot_number = { + let pre_digest = find_pre_digest::(&block.header) + .expect("valid babe headers must contain a predigest; \ + header has been already verified; qed"); + let BabePreDigest { slot_number, .. } = pre_digest; + slot_number + }; + + // 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.client, Some((&hash, &parent_hash))); + + // check if there's any epoch change expected to happen at this slot + let mut epoch_changes = self.epoch_changes.lock(); + let epoch_change = epoch_changes.find_node_where( + &hash, + &number, + &is_descendent_of, + &|(_, expected_epoch_change_slot)| { + *expected_epoch_change_slot <= slot_number + } + ).map_err(|e| ConsensusError::from(ConsensusError::ClientImport(e.to_string())))?; + + let check_roots = || -> Result { + // this can only happen when the chain starts, since there's no epoch change at genesis. + // afterwards every time we expect an epoch change it means we will import another one. + for (root, _, _) in epoch_changes.roots() { + let is_descendent_of = is_descendent_of(root, &hash) + .map_err(|e| ConsensusError::from(ConsensusError::ClientImport(e.to_string())))?; + + if is_descendent_of { + return Ok(false); + } + } + + Ok(true) + }; + + let expected_epoch_change = epoch_change.is_some(); + + match (expected_epoch_change, new_cache.contains_key(&well_known_cache_keys::AUTHORITIES)) { + (true, true) => {}, + (false, false) => {}, + (true, false) => { + return Err( + ConsensusError::ClientImport("Expected epoch change to happen by this block".into()) + ); + }, + (false, true) => { + if !check_roots()? { + return Err(ConsensusError::ClientImport("Unexpected epoch change".into())); + } + }, + } + + // if there's a pending epoch we'll save the previous epoch changes here + // this way we can revert it if there's any error + let mut old_epoch_changes = None; + + if let Some(entry) = new_cache.get(&well_known_cache_keys::AUTHORITIES) { + if let Some(epoch) = Epoch::decode(&mut &entry[..]) { + if let Some(last_epoch_change) = epoch_change { + let last_epoch_index = last_epoch_change.data.0; + if epoch.epoch_index.checked_sub(last_epoch_index) != Some(1) { + return Err(ConsensusError::ClientImport(format!( + "Invalid BABE epoch change: expected next epoch to be {:?}, got {:?}", + last_epoch_index.saturating_add(1), + epoch.epoch_index, + ))); + } + } + + old_epoch_changes = Some(epoch_changes.clone()); + + // track the epoch change in the fork tree + epoch_changes.import( + hash, + number, + (epoch.epoch_index, slot_number + epoch.duration), + &is_descendent_of, + ).map_err(|e| ConsensusError::from(ConsensusError::ClientImport(e.to_string())))?; + + crate::aux_schema::write_epoch_changes::( + &*epoch_changes, + |insert| block.auxiliary.extend( + insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))) + ) + ); + } else { + return Err( + ConsensusError::ClientImport("Failed to decode epoch change digest".into()) + ); + } + } + + let import_result = self.inner.import_block(block, new_cache); + + // revert to the original epoch changes in case there's an error + // importing the block + if let Err(_) = import_result { + if let Some(old_epoch_changes) = old_epoch_changes { + *epoch_changes = old_epoch_changes; + } + } + + import_result.map_err(Into::into) + } + + fn check_block( + &mut self, + hash: Block::Hash, + parent_hash: Block::Hash, + ) -> Result { + self.inner.check_block(hash, parent_hash).map_err(Into::into) + } +} + +/// Start an import queue for the BABE consensus algorithm. This method returns +/// the import queue, some data that needs to be passed to the block authoring +/// logic (`BabeLink`), a `BabeBlockImport` which should be used by the +/// authoring when importing its own blocks, and a future that must be run to +/// completion and is responsible for listening to finality notifications and +/// pruning the epoch changes tree. +pub fn import_queue, I, RA, PRA>( config: Config, - block_import: BoxBlockImport, - justification_import: Option>, - finality_proof_import: Option>, - client: Arc, + block_import: I, + justification_import: Option>, + finality_proof_import: Option>, + client: Arc>, + api: Arc, inherent_data_providers: InherentDataProviders, -) -> Result<(BabeImportQueue, BabeLink), consensus_common::Error> where - B: BlockT, - C: 'static + ProvideRuntimeApi + ProvideCache + Send + Sync + AuxStore, - C::Api: BlockBuilderApi + BabeApi, - DigestItemFor: CompatibleDigestItem, - E: 'static, +) -> ClientResult<( + BabeImportQueue, + BabeLink, + BabeBlockImport, + impl Future, +)> where + B: Backend + 'static, + I: BlockImport + Clone + Send + Sync + 'static, + I::Error: Into, + E: CallExecutor + Clone + Send + Sync + 'static, + RA: Send + Sync + 'static, + PRA: ProvideRuntimeApi + ProvideCache + Send + Sync + AuxStore + 'static, + PRA::Api: BlockBuilderApi + BabeApi, { register_babe_inherent_data_provider(&inherent_data_providers, config.get())?; - initialize_authorities_cache(&*client)?; + initialize_authorities_cache(&*api)?; let verifier = BabeVerifier { - client: client, + api, inherent_data_providers, time_source: Default::default(), config, }; - let timestamp_core = verifier.time_source.clone(); - Ok((BasicQueue::new( - Arc::new(verifier), + + #[allow(deprecated)] + let epoch_changes = aux_schema::load_epoch_changes(&**client.backend())?; + + let block_import = BabeBlockImport::new( + client.clone(), + epoch_changes.clone(), block_import, + ); + + let pruning_task = client.finality_notification_stream() + .map(|v| Ok::<_, ()>(v)).compat() + .for_each(move |notification| { + let is_descendent_of = is_descendent_of(&client, None); + epoch_changes.lock().prune( + ¬ification.hash, + *notification.header.number(), + &is_descendent_of, + ).map_err(|e| debug!(target: "babe", "Error pruning epoch changes fork tree: {:?}", e))?; + + Ok(()) + }); + + let timestamp_core = verifier.time_source.clone(); + let queue = BasicQueue::new( + Arc::new(verifier), + Box::new(block_import.clone()), justification_import, finality_proof_import, - ), timestamp_core)) -} - -// FIXME #2532: need to allow deprecated until refactor is done -// https://github.com/paritytech/substrate/issues/2532 -#[cfg(test)] -#[allow(unused_imports, deprecated)] -#[cfg_attr(test, allow(dead_code))] -mod tests { - use super::*; - - use client::LongestChain; - use consensus_common::NoNetwork as DummyOracle; - use network::test::*; - use network::test::{Block as TestBlock, PeersClient}; - use runtime_primitives::traits::{Block as BlockT, DigestFor}; - use network::config::ProtocolConfig; - use tokio::runtime::current_thread; - use keyring::sr25519::Keyring; - use super::generic::DigestItem; - use client::BlockchainEvents; - use test_client; - use futures::{Async, stream::Stream as _}; - use futures03::{StreamExt as _, TryStreamExt as _}; - use log::debug; - use std::time::Duration; - type Item = generic::DigestItem; - use test_client::AuthorityKeyring; - - type Error = client::error::Error; - - type TestClient = client::Client< - test_client::Backend, - test_client::Executor, - TestBlock, - test_client::runtime::RuntimeApi, - >; - - struct DummyFactory(Arc); - struct DummyProposer(u64, Arc); - - impl Environment for DummyFactory { - type Proposer = DummyProposer; - type Error = Error; - - fn init(&self, parent_header: &::Header) - -> Result - { - Ok(DummyProposer(parent_header.number + 1, self.0.clone())) - } - } - - impl Proposer for DummyProposer { - type Error = Error; - type Create = Result; - - fn propose(&self, _: InherentData, digests: DigestFor, _: Duration) -> Result { - self.1.new_block(digests).unwrap().bake().map_err(|e| e.into()) - } - } - - const SLOT_DURATION: u64 = 1; - - pub struct BabeTestNet { - peers: Vec>, - } - - impl TestNetFactory for BabeTestNet { - type Specialization = DummySpecialization; - type Verifier = BabeVerifier; - type PeerData = (); - - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - debug!(target: "babe", "Creating test network from config"); - BabeTestNet { - peers: Vec::new(), - } - } - - fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig) - -> Arc - { - 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"); - - assert_eq!(config.get(), SLOT_DURATION); - Arc::new(BabeVerifier { - client, - inherent_data_providers, - config, - time_source: Default::default(), - }) - } - - fn peer(&mut self, i: usize) -> &mut Peer { - trace!(target: "babe", "Retreiving a peer"); - &mut self.peers[i] - } - - fn peers(&self) -> &Vec> { - trace!(target: "babe", "Retreiving peers"); - &self.peers - } - - fn mut_peers>)>( - &mut self, - closure: F, - ) { - closure(&mut self.peers); - } - } - - #[test] - fn can_serialize_block() { - drop(env_logger::try_init()); - assert!(BabePreDigest::decode(&mut &b""[..]).is_none()); - } - - #[test] - fn authoring_blocks() { - drop(env_logger::try_init()); - debug!(target: "babe", "checkpoint 1"); - let net = BabeTestNet::new(3); - debug!(target: "babe", "checkpoint 2"); - - debug!(target: "babe", "checkpoint 3"); - - let peers = &[ - (0, Keyring::Alice), - (1, Keyring::Bob), - (2, Keyring::Charlie), - ]; - - let net = Arc::new(Mutex::new(net)); - let mut import_notifications = Vec::new(); - debug!(target: "babe", "checkpoint 4"); - let mut runtime = current_thread::Runtime::new().unwrap(); - for (peer_id, key) in peers { - let client = net.lock().peer(*peer_id).client().as_full().unwrap(); - let environ = Arc::new(DummyFactory(client.clone())); - import_notifications.push( - client.import_notification_stream() - .map(|v| Ok::<_, ()>(v)).compat() - .take_while(|n| Ok(!(n.origin != BlockOrigin::Own && n.header.number() < &5))) - .for_each(move |_| Ok(())) - ); - - 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"); - - - #[allow(deprecated)] - let select_chain = LongestChain::new(client.backend().clone()); - - runtime.spawn(start_babe(BabeParams { - config, - local_key: Arc::new(key.clone().into()), - block_import: client.clone(), - select_chain, - client, - env: environ.clone(), - sync_oracle: DummyOracle, - inherent_data_providers, - force_authoring: false, - time_source: Default::default(), - }).expect("Starts babe")); - } - debug!(target: "babe", "checkpoint 5"); - - // wait for all finalized on each. - let wait_for = ::futures::future::join_all(import_notifications) - .map(drop) - .map_err(drop); - - let drive_to_completion = futures::future::poll_fn(|| { net.lock().poll(); Ok(Async::NotReady) }); - let _ = runtime.block_on(wait_for.select(drive_to_completion).map_err(drop)).unwrap(); - } - - #[test] - fn wrong_consensus_engine_id_rejected() { - drop(env_logger::try_init()); - let sig = sr25519::Pair::generate().0.sign(b""); - let bad_seal: Item = DigestItem::Seal([0; 4], sig.0.to_vec()); - assert!(bad_seal.as_babe_pre_digest().is_none()); - assert!(bad_seal.as_babe_seal().is_none()) - } - - #[test] - fn malformed_pre_digest_rejected() { - drop(env_logger::try_init()); - let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, [0; 64].to_vec()); - assert!(bad_seal.as_babe_pre_digest().is_none()); - } - - #[test] - fn sig_is_not_pre_digest() { - drop(env_logger::try_init()); - let sig = sr25519::Pair::generate().0.sign(b""); - let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, sig.0.to_vec()); - assert!(bad_seal.as_babe_pre_digest().is_none()); - assert!(bad_seal.as_babe_seal().is_some()) - } - - #[test] - fn can_author_block() { - drop(env_logger::try_init()); - let randomness = &[]; - let (pair, _) = sr25519::Pair::generate(); - let mut i = 0; - loop { - match claim_slot(randomness, i, &[], 0, &[pair.public()], &pair, u64::MAX / 10) { - None => i += 1, - Some(s) => { - debug!(target: "babe", "Authored block {:?}", s); - break - } - } - } - } - - #[test] - fn authorities_call_works() { - drop(env_logger::try_init()); - let client = test_client::new(); - - assert_eq!(client.info().chain.best_number, 0); - assert_eq!(authorities(&client, &BlockId::Number(0)).unwrap(), vec![ - Keyring::Alice.into(), - Keyring::Bob.into(), - Keyring::Charlie.into() - ]); - } + ); + + Ok((queue, timestamp_core, block_import, pruning_task)) } diff --git a/substrate/core/consensus/babe/src/tests.rs b/substrate/core/consensus/babe/src/tests.rs new file mode 100644 index 0000000000..4054fb9306 --- /dev/null +++ b/substrate/core/consensus/babe/src/tests.rs @@ -0,0 +1,340 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! BABE testsuite + +// FIXME #2532: need to allow deprecated until refactor is done +// https://github.com/paritytech/substrate/issues/2532 +#![allow(deprecated)] +use super::*; + +use client::{LongestChain, block_builder::BlockBuilder}; +use consensus_common::NoNetwork as DummyOracle; +use network::test::*; +use network::test::{Block as TestBlock, PeersClient}; +use runtime_primitives::traits::{Block as BlockT, DigestFor}; +use network::config::ProtocolConfig; +use tokio::runtime::current_thread; +use keyring::sr25519::Keyring; +use super::generic::DigestItem; +use client::BlockchainEvents; +use test_client; +use futures::Async; +use log::debug; +use std::{time::Duration, borrow::Borrow, cell::RefCell}; +type Item = generic::DigestItem; + +type Error = client::error::Error; + +type TestClient = client::Client< + test_client::Backend, + test_client::Executor, + TestBlock, + test_client::runtime::RuntimeApi, +>; + +struct DummyFactory(Arc); +struct DummyProposer(u64, Arc); + +impl Environment for DummyFactory { + type Proposer = DummyProposer; + type Error = Error; + + fn init(&self, parent_header: &::Header) + -> Result + { + Ok(DummyProposer(parent_header.number + 1, self.0.clone())) + } +} + +impl Proposer for DummyProposer { + type Error = Error; + type Create = Result; + + fn propose(&self, _: InherentData, digests: DigestFor, _: Duration) -> Result { + self.1.new_block(digests).unwrap().bake().map_err(|e| e.into()) + } +} + +type Mutator = Arc Fn(&'r mut TestHeader) + Send + Sync>; + +thread_local! { + static MUTATOR: RefCell = RefCell::new(Arc::new(|_|())); +} + +pub struct BabeTestNet { + peers: Vec>, +} + +type TestHeader = ::Header; +type TestExtrinsic = ::Extrinsic; + +pub struct TestVerifier { + inner: BabeVerifier, + mutator: Mutator, +} + +impl Verifier for TestVerifier { + /// Verify the given data and return the BlockImportParams and an optional + /// new set of validators to import. If not, err with an Error-Message + /// presented to the User in the logs. + fn verify( + &self, + origin: BlockOrigin, + mut header: TestHeader, + justification: Option, + body: Option>, + ) -> Result<(BlockImportParams, Option)>>), String> { + let cb: &(dyn Fn(&mut TestHeader) + Send + Sync) = self.mutator.borrow(); + cb(&mut header); + Ok(self.inner.verify(origin, header, justification, body).expect("verification failed!")) + } +} + +impl TestNetFactory for BabeTestNet { + type Specialization = DummySpecialization; + type Verifier = TestVerifier; + type PeerData = (); + + /// Create new test network with peers and given config. + fn from_config(_config: &ProtocolConfig) -> Self { + debug!(target: "babe", "Creating test network from config"); + BabeTestNet { + peers: Vec::new(), + } + } + + /// KLUDGE: this function gets the mutator from thread-local storage. + fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig) + -> Arc + { + let api = client.as_full().expect("only full clients are used in test"); + trace!(target: "babe", "Creating a verifier"); + let config = Config::get_or_compute(&*api) + .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"); + + Arc::new(TestVerifier { + inner: BabeVerifier { + api, + inherent_data_providers, + config, + time_source: Default::default(), + }, + mutator: MUTATOR.with(|s| s.borrow().clone()), + }) + } + + fn peer(&mut self, i: usize) -> &mut Peer { + trace!(target: "babe", "Retreiving a peer"); + &mut self.peers[i] + } + + fn peers(&self) -> &Vec> { + trace!(target: "babe", "Retreiving peers"); + &self.peers + } + + fn mut_peers>)>( + &mut self, + closure: F, + ) { + closure(&mut self.peers); + } +} + +#[test] +fn can_serialize_block() { + let _ = env_logger::try_init(); + assert!(BabePreDigest::decode(&mut &b""[..]).is_none()); +} + +#[test] +#[should_panic] +fn rejects_empty_block() { + env_logger::try_init().unwrap(); + let mut net = BabeTestNet::new(3); + let block_builder = |builder: BlockBuilder<_, _>| { + builder.bake().unwrap() + }; + net.mut_peers(|peer| { + peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder); + }) +} + +fn run_one_test() { + let _ = env_logger::try_init(); + let net = BabeTestNet::new(3); + + let peers = &[ + (0, Keyring::Alice), + (1, Keyring::Bob), + (2, Keyring::Charlie), + ]; + + let net = Arc::new(Mutex::new(net)); + let mut import_notifications = Vec::new(); + let mut runtime = current_thread::Runtime::new().unwrap(); + for (peer_id, key) in peers { + let client = net.lock().peer(*peer_id).client().as_full().unwrap(); + let environ = Arc::new(DummyFactory(client.clone())); + import_notifications.push( + client.import_notification_stream() + .map(|v| Ok::<_, ()>(v)).compat() + .take_while(|n| Ok(n.header.number() < &5)) + .for_each(move |_| Ok(())) + ); + + 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"); + + + #[allow(deprecated)] + let select_chain = LongestChain::new(client.backend().clone()); + + runtime.spawn(start_babe(BabeParams { + config, + local_key: Arc::new(key.clone().into()), + block_import: client.clone(), + select_chain, + client, + env: environ.clone(), + sync_oracle: DummyOracle, + inherent_data_providers, + force_authoring: false, + time_source: Default::default(), + }).expect("Starts babe")); + } + + // wait for all finalized on each. + let wait_for = futures::future::join_all(import_notifications); + + let drive_to_completion = futures::future::poll_fn(|| { net.lock().poll(); Ok(Async::NotReady) }); + runtime.block_on(wait_for.select(drive_to_completion).map_err(|_| ())).unwrap(); +} + +#[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| { + 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()) + .collect() + })); + run_one_test() +} + +#[test] +#[should_panic] +fn rejects_missing_seals() { + MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| { + 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()) + .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| { + 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()) + .collect() + })); + run_one_test() +} + +#[test] +fn wrong_consensus_engine_id_rejected() { + let _ = env_logger::try_init(); + let sig = sr25519::Pair::generate().0.sign(b""); + let bad_seal: Item = DigestItem::Seal([0; 4], sig.0.to_vec()); + assert!(bad_seal.as_babe_pre_digest().is_none()); + assert!(bad_seal.as_babe_seal().is_none()) +} + +#[test] +fn malformed_pre_digest_rejected() { + let _ = env_logger::try_init(); + let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, [0; 64].to_vec()); + assert!(bad_seal.as_babe_pre_digest().is_none()); +} + +#[test] +fn sig_is_not_pre_digest() { + let _ = env_logger::try_init(); + let sig = sr25519::Pair::generate().0.sign(b""); + let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, sig.0.to_vec()); + assert!(bad_seal.as_babe_pre_digest().is_none()); + assert!(bad_seal.as_babe_seal().is_some()) +} + +#[test] +fn can_author_block() { + let _ = env_logger::try_init(); + let randomness = &[]; + let (pair, _) = sr25519::Pair::generate(); + let mut i = 0; + let epoch = Epoch { + authorities: vec![(pair.public(), 0)], + randomness: [0; 32], + epoch_index: 1, + duration: 100, + }; + loop { + match claim_slot(randomness, i, 0, epoch.clone(), &pair, u64::MAX / 10) { + None => i += 1, + Some(s) => { + debug!(target: "babe", "Authored block {:?}", s); + break + } + } + } +} + +#[test] +fn authorities_call_works() { + let _ = env_logger::try_init(); + let client = test_client::new(); + + assert_eq!(client.info().chain.best_number, 0); + assert_eq!(epoch(&client, &BlockId::Number(0)).unwrap().authorities, vec![ + (Keyring::Alice.into(), 1), + (Keyring::Bob.into(), 1), + (Keyring::Charlie.into(), 1), + ]); +} diff --git a/substrate/core/consensus/slots/src/lib.rs b/substrate/core/consensus/slots/src/lib.rs index dd6d55345b..816a61babd 100644 --- a/substrate/core/consensus/slots/src/lib.rs +++ b/substrate/core/consensus/slots/src/lib.rs @@ -20,12 +20,14 @@ //! time during which certain events can and/or must occur. This crate //! provides generic functionality for slots. -#![forbid(warnings, unsafe_code, missing_docs)] +#![deny(warnings)] +#![forbid(unsafe_code, missing_docs)] mod slots; mod aux_schema; -pub use slots::{SignedDuration, SlotInfo, Slots}; +pub use slots::{SignedDuration, SlotInfo}; +use slots::Slots; pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND}; use codec::{Decode, Encode}; diff --git a/substrate/core/consensus/slots/src/slots.rs b/substrate/core/consensus/slots/src/slots.rs index c35b252b40..0142f32b82 100644 --- a/substrate/core/consensus/slots/src/slots.rs +++ b/substrate/core/consensus/slots/src/slots.rs @@ -96,7 +96,7 @@ impl SlotInfo { } /// A stream that returns every time there is a new slot. -pub struct Slots { +pub(crate) struct Slots { last_slot: u64, slot_duration: u64, inner_delay: Option, diff --git a/substrate/core/finality-grandpa/src/environment.rs b/substrate/core/finality-grandpa/src/environment.rs index 414e3ca0ab..e3189ecd92 100644 --- a/substrate/core/finality-grandpa/src/environment.rs +++ b/substrate/core/finality-grandpa/src/environment.rs @@ -26,7 +26,8 @@ use tokio_timer::Delay; use parking_lot::RwLock; use client::{ - backend::Backend, BlockchainEvents, CallExecutor, Client, error::Error as ClientError + backend::Backend, BlockchainEvents, CallExecutor, Client, error::Error as ClientError, + utils::is_descendent_of, }; use grandpa::{ BlockNumberOps, Equivocation, Error as GrandpaError, round::State as RoundState, @@ -990,42 +991,3 @@ pub(crate) fn canonical_at_height, RA>( Ok(Some(current.hash())) } - -/// Returns a function for checking block ancestry, the returned function will -/// return `true` if the given hash (second parameter) is a descendent of the -/// base (first parameter). If the `current` parameter is defined, it should -/// 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, RA>( - client: &'a Client, - current: Option<(&'a H256, &'a H256)>, -) -> impl Fn(&H256, &H256) -> Result + 'a -where B: Backend, - E: CallExecutor + Send + Sync, -{ - move |base, hash| { - if base == hash { return Ok(false); } - - let mut hash = hash; - if let Some((current_hash, current_parent_hash)) = current { - if base == current_hash { return Ok(false); } - if hash == current_hash { - if base == current_parent_hash { - return Ok(true); - } else { - hash = current_parent_hash; - } - } - } - - let tree_route = client::blockchain::tree_route( - #[allow(deprecated)] - client.backend().blockchain(), - BlockId::Hash(*hash), - BlockId::Hash(*base), - )?; - - Ok(tree_route.common_block().hash == *base) - } -} diff --git a/substrate/core/finality-grandpa/src/import.rs b/substrate/core/finality-grandpa/src/import.rs index 8e1efaed6b..b16bf83576 100644 --- a/substrate/core/finality-grandpa/src/import.rs +++ b/substrate/core/finality-grandpa/src/import.rs @@ -25,6 +25,7 @@ use client::{blockchain, CallExecutor, Client}; use client::blockchain::HeaderBackend; use client::backend::Backend; use client::runtime_api::ApiExt; +use client::utils::is_descendent_of; use consensus_common::{ BlockImport, Error as ConsensusError, BlockImportParams, ImportResult, JustificationImport, well_known_cache_keys, @@ -42,7 +43,7 @@ use substrate_primitives::{H256, Blake2Hasher}; use crate::{Error, CommandOrError, NewAuthoritySet, VoterCommand}; use crate::authorities::{AuthoritySet, SharedAuthoritySet, DelayKind, PendingChange}; use crate::consensus_changes::SharedConsensusChanges; -use crate::environment::{finalize_block, is_descendent_of}; +use crate::environment::finalize_block; use crate::justification::GrandpaJustification; /// A block-import handler for GRANDPA. diff --git a/substrate/core/network/src/protocol/sync.rs b/substrate/core/network/src/protocol/sync.rs index 3cc4b8243c..e4e1c35599 100644 --- a/substrate/core/network/src/protocol/sync.rs +++ b/substrate/core/network/src/protocol/sync.rs @@ -1189,4 +1189,3 @@ fn peer_block_request( None } } - diff --git a/substrate/core/sr-api-macros/tests/runtime_calls.rs b/substrate/core/sr-api-macros/tests/runtime_calls.rs index 6fa155437b..fec6015835 100644 --- a/substrate/core/sr-api-macros/tests/runtime_calls.rs +++ b/substrate/core/sr-api-macros/tests/runtime_calls.rs @@ -104,7 +104,7 @@ fn calling_with_both_strategy_and_fail_on_native_should_work() { #[test] -fn calling_with_native_else_wasm_and_faild_on_wasm_should_work() { +fn calling_with_native_else_wasm_and_fail_on_wasm_should_work() { let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::NativeElseWasm).build(); let runtime_api = client.runtime_api(); let block_id = BlockId::Number(client.info().chain.best_number); diff --git a/substrate/core/test-runtime/Cargo.toml b/substrate/core/test-runtime/Cargo.toml index d14d324006..873c1eb624 100644 --- a/substrate/core/test-runtime/Cargo.toml +++ b/substrate/core/test-runtime/Cargo.toml @@ -25,6 +25,9 @@ trie-db = { version = "0.14.0", default-features = false } offchain-primitives = { package = "substrate-offchain-primitives", path = "../offchain/primitives", default-features = false} executive = { package = "srml-executive", path = "../../srml/executive", default-features = false } cfg-if = "0.1.6" +srml-babe = { path = "../../srml/babe", default-features = false } +srml-timestamp = { path = "../../srml/timestamp", default-features = false } +srml-system = { path = "../../srml/system", default-features = false } [dev-dependencies] substrate-executor = { path = "../executor" } @@ -58,4 +61,7 @@ std = [ "trie-db/std", "offchain-primitives/std", "executive/std", + "srml-babe/std", + "srml-timestamp/std", + "srml-system/std", ] diff --git a/substrate/core/test-runtime/src/lib.rs b/substrate/core/test-runtime/src/lib.rs index df30832f12..c40b551a3e 100644 --- a/substrate/core/test-runtime/src/lib.rs +++ b/substrate/core/test-runtime/src/lib.rs @@ -39,7 +39,7 @@ use runtime_primitives::{ transaction_validity::{TransactionValidity, ValidTransaction}, traits::{ BlindCheckable, BlakeTwo256, Block as BlockT, Extrinsic as ExtrinsicT, - GetNodeBlockType, GetRuntimeBlockType, Verify + GetNodeBlockType, GetRuntimeBlockType, Verify, IdentityLookup }, }; use runtime_version::RuntimeVersion; @@ -47,10 +47,10 @@ pub use primitives::hash::H256; use primitives::{sr25519, OpaqueMetadata}; #[cfg(any(feature = "std", test))] use runtime_version::NativeVersion; +use runtime_support::{impl_outer_origin, parameter_types}; use inherents::{CheckInherentsResult, InherentData}; use cfg_if::cfg_if; pub use consensus_babe::AuthorityId; - // Ensure Babe and Aura use the same crypto to simplify things a bit. pub type AuraId = AuthorityId; // Ensure Babe and Aura use the same crypto to simplify things a bit. @@ -301,6 +301,7 @@ cfg_if! { } } +#[derive(Clone, Eq, PartialEq)] pub struct Runtime; impl GetNodeBlockType for Runtime { @@ -311,6 +312,46 @@ impl GetRuntimeBlockType for Runtime { type RuntimeBlock = Block; } +impl_outer_origin!{ + pub enum Origin for Runtime where system = srml_system {} +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Event; + +impl From for Event { + fn from(_evt: srml_system::Event) -> Self { + unimplemented!("Not required in tests!") + } +} + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub const MinimumPeriod: u64 = 5; +} + +impl srml_system::Trait for Runtime { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type WeightMultiplierUpdate = (); + type BlockHashCount = BlockHashCount; +} + +impl srml_timestamp::Trait for Runtime { + /// A timestamp: seconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} + /// Adds one to the given input and returns the final result. #[inline(never)] fn benchmark_add_one(i: u64) -> u64 { @@ -477,13 +518,23 @@ cfg_if! { impl consensus_babe::BabeApi for Runtime { fn startup_data() -> consensus_babe::BabeConfiguration { consensus_babe::BabeConfiguration { - slot_duration: 1, + median_required_blocks: 0, + slot_duration: 3, expected_block_time: 1, - threshold: std::u64::MAX, - median_required_blocks: 100, + threshold: core::u64::MAX, + slots_per_epoch: 6, + } + } + fn epoch() -> consensus_babe::Epoch { + let authorities = system::authorities(); + let authorities: Vec<_> = authorities.into_iter().map(|x|(x, 1)).collect(); + consensus_babe::Epoch { + authorities, + randomness: >::randomness(), + epoch_index: 1, + duration: 6, } } - fn authorities() -> Vec { system::authorities() } } impl offchain_primitives::OffchainWorkerApi for Runtime { @@ -626,9 +677,20 @@ cfg_if! { slot_duration: 1, expected_block_time: 1, threshold: core::u64::MAX, + slots_per_epoch: 6, + } + } + + fn epoch() -> consensus_babe::Epoch { + let authorities = system::authorities(); + let authorities: Vec<_> = authorities.into_iter().map(|x|(x, 1)).collect(); + consensus_babe::Epoch { + authorities, + randomness: >::randomness(), + epoch_index: 1, + duration: 6, } } - fn authorities() -> Vec { system::authorities() } } impl offchain_primitives::OffchainWorkerApi for Runtime { diff --git a/substrate/core/utils/fork-tree/src/lib.rs b/substrate/core/utils/fork-tree/src/lib.rs index 7a2e2f422a..d202ba6c84 100644 --- a/substrate/core/utils/fork-tree/src/lib.rs +++ b/substrate/core/utils/fork-tree/src/lib.rs @@ -81,6 +81,62 @@ pub struct ForkTree { best_finalized_number: Option, } +impl ForkTree where + H: PartialEq + Clone, + N: Ord + Clone, + V: Clone, +{ + /// Prune all nodes that are not descendents of `hash` according to + /// `is_descendent_of`. The given function `is_descendent_of` should return + /// `true` if the second hash (target) is a descendent of the first hash + /// (base). After pruning the tree it should have one or zero roots. The + /// number and order of calls to `is_descendent_of` is unspecified and + /// subject to change. + pub fn prune( + &mut self, + hash: &H, + number: N, + is_descendent_of: &F + ) -> Result<(), Error> + where E: std::error::Error, + F: Fn(&H, &H) -> Result + { + let mut new_root = None; + for node in self.node_iter() { + // if the node has a lower number than the one being finalized then + // we only keep if it has no children and the finalized block is a + // descendent of this node + if node.number < number { + if !node.children.is_empty() || !is_descendent_of(&node.hash, hash)? { + continue; + } + } + + // if the node has the same number as the finalized block then it + // must have the same hash + if node.number == number && node.hash != *hash { + continue; + } + + // if the node has a higher number then we keep it if it is a + // descendent of the finalized block + if node.number > number && !is_descendent_of(hash, &node.hash)? { + continue; + } + + new_root = Some(node); + break; + } + + if let Some(root) = new_root { + self.roots = vec![root.clone()]; + } + + Ok(()) + } + +} + impl ForkTree where H: PartialEq, N: Ord, @@ -152,6 +208,34 @@ impl ForkTree 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 + /// 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). + pub fn find_node_where( + &self, + hash: &H, + number: &N, + is_descendent_of: &F, + predicate: &P, + ) -> Result>, Error> + where E: std::error::Error, + F: Fn(&H, &H) -> Result, + P: Fn(&V) -> bool, + { + // search for node starting from all roots + for root in self.roots.iter() { + let node = root.find_node_where(hash, number, is_descendent_of, predicate)?; + + // found the node, early exit + if node.is_some() { + return Ok(node); + } + } + + Ok(None) + } + /// Finalize a root in the tree and return it, return `None` in case no root /// with the given hash exists. All other roots are pruned, and the children /// of the finalized node become the new roots. @@ -167,7 +251,7 @@ impl ForkTree where } /// Finalize a node in the tree. This method will make sure that the node - /// being finalized is either an existing root (an return its data), or a + /// being finalized is either an existing root (and return its data), or a /// node from a competing branch (not in the tree), tree pruning is done /// accordingly. The given function `is_descendent_of` should return `true` /// if the second hash (target) is a descendent of the first hash (base). @@ -400,6 +484,49 @@ mod node_implementation { Ok(Some((hash, number, data))) } } + + /// 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) + /// 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 + // an iterative definition instead. + pub fn find_node_where( + &self, + hash: &H, + number: &N, + is_descendent_of: &F, + predicate: &P, + ) -> Result>, Error> + where E: std::error::Error, + F: Fn(&H, &H) -> Result, + P: Fn(&V) -> bool, + { + // stop searching this branch + if *number < self.number { + return Ok(None); + } + + // 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); + } + } + + // node not found in any of the descendents, if the node we're + // searching for is a descendent of this node and it passes the + // predicate, then it is this one. + if predicate(&self.data) && is_descendent_of(&self.hash, hash)? { + Ok(Some(self)) + } else { + Ok(None) + } + } } } @@ -870,4 +997,40 @@ mod test { ); } } + + #[test] + fn find_node_works() { + let (tree, is_descendent_of) = test_fork_tree(); + + let node = tree.find_node_where( + &"D", + &4, + &is_descendent_of, + &|_| true, + ).unwrap().unwrap(); + + assert_eq!(node.hash, "C"); + assert_eq!(node.number, 3); + } + + #[test] + fn prune_works() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + tree.prune( + &"C", + 3, + &is_descendent_of, + ).unwrap(); + + assert_eq!( + tree.roots.iter().map(|node| node.hash).collect::>(), + vec!["C"], + ); + + assert_eq!( + tree.iter().map(|(hash, _, _)| *hash).collect::>(), + vec!["C", "D", "E"], + ); + } } diff --git a/substrate/srml/aura/src/lib.rs b/substrate/srml/aura/src/lib.rs index da00f25476..a0f7ad6242 100644 --- a/substrate/srml/aura/src/lib.rs +++ b/substrate/srml/aura/src/lib.rs @@ -188,7 +188,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = T::AuthorityId; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I) + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) where I: Iterator { // instant changes diff --git a/substrate/srml/babe/Cargo.toml b/substrate/srml/babe/Cargo.toml index e8dc183b54..7922c1dfe7 100644 --- a/substrate/srml/babe/Cargo.toml +++ b/substrate/srml/babe/Cargo.toml @@ -11,6 +11,7 @@ serde = { version = "1.0.93", optional = true } inherents = { package = "substrate-inherents", path = "../../core/inherents", default-features = false } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false } +staking = { package = "srml-staking", path = "../staking", default-features = false } srml-support = { path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false } @@ -37,4 +38,5 @@ std = [ "babe-primitives/std", "session/std", "runtime_io/std", + "staking/std", ] diff --git a/substrate/srml/babe/src/lib.rs b/substrate/srml/babe/src/lib.rs index 8c7045675f..577bda8c01 100644 --- a/substrate/srml/babe/src/lib.rs +++ b/substrate/srml/babe/src/lib.rs @@ -23,26 +23,22 @@ pub use timestamp; use rstd::{result, prelude::*}; use srml_support::{decl_storage, decl_module, StorageValue, traits::FindAuthor, traits::Get}; use timestamp::{OnTimestampSet, Trait}; -use primitives::{ - generic::DigestItem, - traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon} -}; -use primitives::ConsensusEngineId; +use primitives::{generic::DigestItem, ConsensusEngineId}; +use primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon, Convert}; #[cfg(feature = "std")] use timestamp::TimestampInherentData; use parity_codec::{Encode, Decode}; use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent, MakeFatalError}; #[cfg(feature = "std")] use inherents::{InherentDataProviders, ProvideInherentData}; -use babe_primitives::{BABE_ENGINE_ID, ConsensusLog}; -pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH, PUBLIC_KEY_LENGTH}; +use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, Weight, Epoch, RawBabePreDigest}; +pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH}; /// The BABE inherent identifier. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot"; /// The type of the BABE inherent. pub type InherentType = u64; - /// Auxiliary trait to extract BABE inherent data. pub trait BabeInherentData { /// Get BABE inherent data. @@ -101,8 +97,8 @@ impl ProvideInherentData for InherentDataProvider { inherent_data: &mut InherentData, ) -> result::Result<(), RuntimeString> { let timestamp = inherent_data.timestamp_inherent_data()?; - let slot_num = timestamp / self.slot_duration; - inherent_data.put_data(INHERENT_IDENTIFIER, &slot_num) + let slot_number = timestamp / self.slot_duration; + inherent_data.put_data(INHERENT_IDENTIFIER, &slot_number) } fn error_to_string(&self, error: &[u8]) -> Option { @@ -115,13 +111,15 @@ pub const RANDOMNESS_LENGTH: usize = 32; decl_storage! { trait Store for Module as Babe { - /// The last timestamp. - LastTimestamp get(last): T::Moment; + NextRandomness: [u8; RANDOMNESS_LENGTH]; - /// The current authorities set. - Authorities get(authorities): Vec; + /// Randomness under construction + UnderConstruction: [u8; VRF_OUTPUT_LENGTH]; - /// The epoch randomness. + /// Current epoch + pub Authorities get(authorities) config(): Vec<(AuthorityId, Weight)>; + + /// The epoch randomness for the *current* epoch. /// /// # Security /// @@ -131,16 +129,10 @@ decl_storage! { /// (like everything else on-chain) it is public. For example, it can be /// used where a number is needed that cannot have been chosen by an /// adversary, for purposes such as public-coin zero-knowledge proofs. - EpochRandomness get(epoch_randomness): [u8; VRF_OUTPUT_LENGTH]; + pub Randomness get(randomness): [u8; RANDOMNESS_LENGTH]; - /// The randomness under construction - UnderConstruction: [u8; VRF_OUTPUT_LENGTH]; - - /// The randomness for the next epoch - NextEpochRandomness: [u8; VRF_OUTPUT_LENGTH]; - - /// The current epoch - EpochIndex get(epoch_index): u64; + /// Current epoch index + EpochIndex: u64; } } @@ -154,11 +146,13 @@ decl_module! { .iter() .filter_map(|s| s.as_pre_runtime()) .filter_map(|(id, mut data)| if id == BABE_ENGINE_ID { - <[u8; VRF_OUTPUT_LENGTH]>::decode(&mut data) + RawBabePreDigest::decode(&mut data) } else { None }) { - Self::deposit_vrf_output(&i); + + Self::deposit_vrf_output(&i.vrf_output); + return; } } } @@ -166,7 +160,7 @@ decl_module! { impl RandomnessBeacon for Module { fn random() -> [u8; VRF_OUTPUT_LENGTH] { - Self::epoch_randomness() + Self::randomness() } } @@ -179,15 +173,10 @@ impl FindAuthor for Module { { for (id, mut data) in digests.into_iter() { if id == BABE_ENGINE_ID { - let (_, _, i): ( - [u8; VRF_OUTPUT_LENGTH], - [u8; VRF_PROOF_LENGTH], - u64, - ) = Decode::decode(&mut data)?; - return Some(i) + return Some(RawBabePreDigest::decode(&mut data)?.authority_index); } } - return None + return None; } } @@ -195,7 +184,7 @@ impl IsMember for Module { fn is_member(authority_id: &AuthorityId) -> bool { >::authorities() .iter() - .any(|id| id == authority_id) + .any(|id| &id.0 == authority_id) } } @@ -207,65 +196,97 @@ impl Module { ::MinimumPeriod::get().saturating_mul(2.into()) } - fn change_authorities(new: Vec) { - Authorities::put(&new); - + fn deposit_consensus(new: U) { let log: DigestItem = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); - >::deposit_log(log.into()); + >::deposit_log(log.into()) + } + + fn get_inherent_digests() -> system::DigestOf { + >::digest() + } + + fn change_epoch(new: Epoch) { + Authorities::put(&new.authorities); + Randomness::put(&new.randomness); + Self::deposit_consensus(ConsensusLog::NextEpochData(new)) } fn deposit_vrf_output(vrf_output: &[u8; VRF_OUTPUT_LENGTH]) { UnderConstruction::mutate(|z| z.iter_mut().zip(vrf_output).for_each(|(x, y)| *x^=y)) } - fn get_inherent_digests() -> system::DigestOf { - >::digest() + /// Call this function exactly once when an epoch changes, to update the + /// randomness. Returns the new randomness. + fn randomness_change_epoch(epoch_index: u64) -> [u8; RANDOMNESS_LENGTH] { + let this_randomness = NextRandomness::get(); + let next_randomness = compute_randomness( + this_randomness, + epoch_index, + UnderConstruction::get(), + ); + UnderConstruction::put(&[0; RANDOMNESS_LENGTH]); + NextRandomness::put(&next_randomness); + this_randomness } + } impl OnTimestampSet for Module { fn on_timestamp_set(_moment: T::Moment) { } } -impl session::OneSessionHandler for Module { - type Key = AuthorityId; +pub trait Duration { + fn babe_epoch_duration() -> u64; +} - fn on_new_session<'a, I: 'a>(changed: bool, validators: I) +impl session::OneSessionHandler for Module { + type Key = AuthorityId; + fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, queued_validators: I) where I: Iterator { - // instant changes - if changed { - let next_authorities = validators.map(|(_, k)| k).collect::>(); - let last_authorities = >::authorities(); - if next_authorities != last_authorities { - Self::change_authorities(next_authorities); - } - } + use staking::BalanceOf; + let to_votes = |b: BalanceOf| { + , u64>>::convert(b) + }; - let rho = UnderConstruction::get(); - UnderConstruction::put([0; 32]); - let last_epoch_randomness = EpochRandomness::get(); let epoch_index = EpochIndex::get() .checked_add(1) .expect("epoch indices will never reach 2^64 before the death of the universe; qed"); + EpochIndex::put(epoch_index); - EpochRandomness::put(NextEpochRandomness::get()); - let mut s = [0; 72]; - s[..32].copy_from_slice(&last_epoch_randomness); - s[32..40].copy_from_slice(&epoch_index.to_le_bytes()); - s[40..].copy_from_slice(&rho); - NextEpochRandomness::put(runtime_io::blake2_256(&s)) + + // *Next* epoch's authorities. + let authorities = queued_validators.map(|(account, k)| { + (k, to_votes(staking::Module::::stakers(account).total)) + }).collect::>(); + + // What was the next epoch is now the current epoch + let randomness = Self::randomness_change_epoch(epoch_index); + Self::change_epoch(Epoch { + randomness, + authorities, + epoch_index, + duration: T::babe_epoch_duration(), + }) } fn on_disabled(i: usize) { - let log: DigestItem = DigestItem::Consensus( - BABE_ENGINE_ID, - ConsensusLog::OnDisabled(i as u64).encode(), - ); - >::deposit_log(log.into()); + Self::deposit_consensus(ConsensusLog::OnDisabled(i as u64)) } } +fn compute_randomness( + last_epoch_randomness: [u8; RANDOMNESS_LENGTH], + epoch_index: u64, + rho: [u8; VRF_OUTPUT_LENGTH], +) -> [u8; RANDOMNESS_LENGTH] { + let mut s = [0; 40 + VRF_OUTPUT_LENGTH]; + s[..32].copy_from_slice(&last_epoch_randomness); + s[32..40].copy_from_slice(&epoch_index.to_le_bytes()); + s[40..].copy_from_slice(&rho); + runtime_io::blake2_256(&s) +} + impl ProvideInherent for Module { type Call = timestamp::Call; type Error = MakeFatalError; @@ -286,7 +307,7 @@ impl ProvideInherent for Module { if timestamp_based_slot == seal_slot { Ok(()) } else { - Err(RuntimeString::from("timestamp set in block doesn’t match slot in seal").into()) + Err(RuntimeString::from("timestamp set in block doesn't match slot in seal").into()) } } } diff --git a/substrate/srml/grandpa/src/lib.rs b/substrate/srml/grandpa/src/lib.rs index a62f5652d4..9615953e67 100644 --- a/substrate/srml/grandpa/src/lib.rs +++ b/substrate/srml/grandpa/src/lib.rs @@ -346,7 +346,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = AuthorityId; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I) + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) where I: Iterator { // instant changes diff --git a/substrate/srml/im-online/src/lib.rs b/substrate/srml/im-online/src/lib.rs index 3ed3d1d7c0..f1868c599b 100644 --- a/substrate/srml/im-online/src/lib.rs +++ b/substrate/srml/im-online/src/lib.rs @@ -356,7 +356,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = ::AuthorityId; - fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I) { + fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _next_validators: I) { Self::new_session(); } diff --git a/substrate/srml/session/src/lib.rs b/substrate/srml/session/src/lib.rs index 546513c953..5495cdbe65 100644 --- a/substrate/srml/session/src/lib.rs +++ b/substrate/srml/session/src/lib.rs @@ -186,7 +186,11 @@ impl OnSessionEnding for () { /// Handler for when a session keys set changes. pub trait SessionHandler { /// Session set has changed; act appropriately. - fn on_new_session(changed: bool, validators: &[(ValidatorId, Ks)]); + fn on_new_session( + changed: bool, + validators: &[(ValidatorId, Ks)], + queued_validators: &[(ValidatorId, Ks)], + ); /// A validator got disabled. Act accordingly until a new session begins. fn on_disabled(validator_index: usize); @@ -197,7 +201,7 @@ pub trait OneSessionHandler { /// The key type expected. type Key: Decode + Default + TypedKey; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I) + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I) where I: Iterator, ValidatorId: 'a; fn on_disabled(i: usize); } @@ -205,19 +209,26 @@ pub trait OneSessionHandler { macro_rules! impl_session_handlers { () => ( impl SessionHandler for () { - fn on_new_session(_: bool, _: &[(AId, Ks)]) {} + fn on_new_session(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {} fn on_disabled(_: usize) {} } ); ( $($t:ident)* ) => { impl ),*> SessionHandler for ( $( $t , )* ) { - fn on_new_session(changed: bool, validators: &[(AId, Ks)]) { + fn on_new_session( + changed: bool, + validators: &[(AId, Ks)], + queued_validators: &[(AId, Ks)], + ) { $( - let our_keys = validators.iter() + let our_keys: Box> = Box::new(validators.iter() .map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE) - .unwrap_or_default())); - $t::on_new_session(changed, our_keys); + .unwrap_or_default()))); + let queued_keys: Box> = Box::new(queued_validators.iter() + .map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE) + .unwrap_or_default()))); + $t::on_new_session(changed, our_keys, queued_keys); )* } fn on_disabled(i: usize) { @@ -420,14 +431,14 @@ impl Module { .map(|a| { let k = Self::load_keys(&a).unwrap_or_default(); (a, k) }) .collect::>(); - >::put(queued_amalgamated); + >::put(queued_amalgamated.clone()); QueuedChanged::put(next_changed); // Record that this happened. Self::deposit_event(Event::NewSession(session_index)); // Tell everyone about the new session keys. - T::SessionHandler::on_new_session::(changed, &session_keys); + T::SessionHandler::on_new_session::(changed, &session_keys, &queued_amalgamated); } /// Disable the validator of index `i`. diff --git a/substrate/srml/session/src/mock.rs b/substrate/srml/session/src/mock.rs index c469993bcf..b5cb7e4278 100644 --- a/substrate/srml/session/src/mock.rs +++ b/substrate/srml/session/src/mock.rs @@ -50,7 +50,11 @@ impl ShouldEndSession for TestShouldEndSession { pub struct TestSessionHandler; impl SessionHandler for TestSessionHandler { - fn on_new_session(changed: bool, validators: &[(u64, T)]) { + fn on_new_session( + changed: bool, + validators: &[(u64, T)], + _queued_validators: &[(u64, T)], + ) { SESSION_CHANGED.with(|l| *l.borrow_mut() = changed); AUTHORITIES.with(|l| *l.borrow_mut() = validators.iter().map(|(_, id)| id.get::(0).unwrap_or_default()).collect() diff --git a/substrate/srml/staking/src/lib.rs b/substrate/srml/staking/src/lib.rs index 788b46731f..c72aa36e8b 100644 --- a/substrate/srml/staking/src/lib.rs +++ b/substrate/srml/staking/src/lib.rs @@ -429,7 +429,7 @@ pub struct Exposure { pub others: Vec>, } -type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type PositiveImbalanceOf = <::Currency as Currency<::AccountId>>::PositiveImbalance; type NegativeImbalanceOf = diff --git a/substrate/srml/staking/src/mock.rs b/substrate/srml/staking/src/mock.rs index 329bdcda2c..f0151bf915 100644 --- a/substrate/srml/staking/src/mock.rs +++ b/substrate/srml/staking/src/mock.rs @@ -51,7 +51,11 @@ thread_local! { pub struct TestSessionHandler; impl session::SessionHandler for TestSessionHandler { - fn on_new_session(_changed: bool, validators: &[(AccountId, Ks)]) { + fn on_new_session( + _changed: bool, + validators: &[(AccountId, Ks)], + _queued_validators: &[(AccountId, Ks)], + ) { SESSION.with(|x| *x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new()) ); diff --git a/substrate/srml/system/benches/bench.rs b/substrate/srml/system/benches/bench.rs index 15aeb3b548..0f4c453ee8 100644 --- a/substrate/srml/system/benches/bench.rs +++ b/substrate/srml/system/benches/bench.rs @@ -20,7 +20,7 @@ use srml_support::{decl_module, decl_event, impl_outer_origin, impl_outer_event} use runtime_io::{with_externalities, Blake2Hasher}; use substrate_primitives::H256; use primitives::{ - BuildStorage, traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, testing::Header, }; @@ -54,6 +54,9 @@ impl_outer_event! { } } +srml_support::parameter_types! { + pub const BlockHashCount: u64 = 250; +} #[derive(Clone, Eq, PartialEq)] pub struct Runtime; impl system::Trait for Runtime { @@ -67,6 +70,7 @@ impl system::Trait for Runtime { type Header = Header; type WeightMultiplierUpdate = (); type Event = Event; + type BlockHashCount = BlockHashCount; } impl module::Trait for Runtime { @@ -74,7 +78,7 @@ impl module::Trait for Runtime { } fn new_test_ext() -> runtime_io::TestExternalities { - system::GenesisConfig::::default().build_storage().unwrap().0.into() + system::GenesisConfig::default().build_storage::().unwrap().0.into() } fn deposit_events(n: usize) {