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) {