// This file is part of Substrate. // Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Consensus extension module for BABE consensus. Collects on-chain randomness //! from VRF outputs and manages epoch transitions. #![cfg_attr(not(feature = "std"), no_std)] #![warn(unused_must_use, unsafe_code, unused_variables, unused_must_use)] use codec::{Decode, Encode}; use frame_support::{ decl_error, decl_module, decl_storage, dispatch::DispatchResultWithPostInfo, traits::{FindAuthor, Get, KeyOwnerProofSystem, OneSessionHandler, OnTimestampSet}, weights::{Pays, Weight}, Parameter, }; use frame_system::{ensure_none, ensure_root, ensure_signed}; use sp_application_crypto::Public; use sp_runtime::{ generic::DigestItem, traits::{IsMember, One, SaturatedConversion, Saturating, Zero}, ConsensusEngineId, KeyTypeId, Percent, }; use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_std::prelude::*; use sp_consensus_babe::{ digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, EquivocationProof, Slot, BABE_ENGINE_ID, }; use sp_consensus_vrf::schnorrkel; pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; mod default_weights; mod equivocation; mod randomness; #[cfg(any(feature = "runtime-benchmarks", test))] mod benchmarking; #[cfg(all(feature = "std", test))] mod mock; #[cfg(all(feature = "std", test))] mod tests; pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation}; pub use randomness::{ CurrentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, }; pub trait Config: pallet_timestamp::Config { /// The amount of time, in slots, that each epoch should last. /// NOTE: Currently it is not possible to change the epoch duration after /// the chain has started. Attempting to do so will brick block production. type EpochDuration: Get; /// The expected average block time at which BABE should be creating /// blocks. Since BABE is probabilistic it is not trivial to figure out /// what the expected average block time should be based on the slot /// duration and the security parameter `c` (where `1 - c` represents /// the probability of a slot being empty). type ExpectedBlockTime: Get; /// BABE requires some logic to be triggered on every block to query for whether an epoch /// has ended and to perform the transition to the next epoch. /// /// Typically, the `ExternalTrigger` type should be used. An internal trigger should only be used /// when no other module is responsible for changing authority set. type EpochChangeTrigger: EpochChangeTrigger; /// The proof of key ownership, used for validating equivocation reports. /// The proof must include the session index and validator count of the /// session at which the equivocation occurred. type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; /// The identification of a key owner, used when reporting equivocations. type KeyOwnerIdentification: Parameter; /// A system for proving ownership of keys, i.e. that a given key was part /// of a validator set, needed for validating equivocation reports. type KeyOwnerProofSystem: KeyOwnerProofSystem< (KeyTypeId, AuthorityId), Proof = Self::KeyOwnerProof, IdentificationTuple = Self::KeyOwnerIdentification, >; /// The equivocation handling subsystem, defines methods to report an /// offence (after the equivocation has been validated) and for submitting a /// transaction to report an equivocation (from an offchain context). /// NOTE: when enabling equivocation handling (i.e. this type isn't set to /// `()`) you must use this pallet's `ValidateUnsigned` in the runtime /// definition. type HandleEquivocation: HandleEquivocation; type WeightInfo: WeightInfo; } pub trait WeightInfo { fn plan_config_change() -> Weight; fn report_equivocation(validator_count: u32) -> Weight; } /// Trigger an epoch change, if any should take place. pub trait EpochChangeTrigger { /// Trigger an epoch change, if any should take place. This should be called /// during every block, after initialization is done. fn trigger(now: T::BlockNumber); } /// A type signifying to BABE that an external trigger /// for epoch changes (e.g. pallet-session) is used. pub struct ExternalTrigger; impl EpochChangeTrigger for ExternalTrigger { fn trigger(_: T::BlockNumber) { } // nothing - trigger is external. } /// A type signifying to BABE that it should perform epoch changes /// with an internal trigger, recycling the same authorities forever. pub struct SameAuthoritiesForever; impl EpochChangeTrigger for SameAuthoritiesForever { fn trigger(now: T::BlockNumber) { if >::should_epoch_change(now) { let authorities = >::authorities(); let next_authorities = authorities.clone(); >::enact_epoch_change(authorities, next_authorities); } } } const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256; type MaybeRandomness = Option; decl_error! { pub enum Error for Module { /// An equivocation proof provided as part of an equivocation report is invalid. InvalidEquivocationProof, /// A key ownership proof provided as part of an equivocation report is invalid. InvalidKeyOwnershipProof, /// A given equivocation report is valid but already previously reported. DuplicateOffenceReport, } } decl_storage! { trait Store for Module as Babe { /// Current epoch index. pub EpochIndex get(fn epoch_index): u64; /// Current epoch authorities. pub Authorities get(fn authorities): Vec<(AuthorityId, BabeAuthorityWeight)>; /// The slot at which the first epoch actually started. This is 0 /// until the first block of the chain. pub GenesisSlot get(fn genesis_slot): Slot; /// Current slot number. pub CurrentSlot get(fn current_slot): Slot; /// The epoch randomness for the *current* epoch. /// /// # Security /// /// This MUST NOT be used for gambling, as it can be influenced by a /// malicious validator in the short term. It MAY be used in many /// cryptographic protocols, however, so long as one remembers that this /// (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. // NOTE: the following fields don't use the constants to define the // array size because the metadata API currently doesn't resolve the // variable to its underlying value. pub Randomness get(fn randomness): schnorrkel::Randomness; /// Pending epoch configuration change that will be applied when the next epoch is enacted. PendingEpochConfigChange: Option; /// Next epoch randomness. NextRandomness: schnorrkel::Randomness; /// Next epoch authorities. NextAuthorities: Vec<(AuthorityId, BabeAuthorityWeight)>; /// Randomness under construction. /// /// We make a tradeoff between storage accesses and list length. /// We store the under-construction randomness in segments of up to /// `UNDER_CONSTRUCTION_SEGMENT_LENGTH`. /// /// Once a segment reaches this length, we begin the next one. /// We reset all segments and return to `0` at the beginning of every /// epoch. SegmentIndex build(|_| 0): u32; /// TWOX-NOTE: `SegmentIndex` is an increasing integer, so this is okay. UnderConstruction: map hasher(twox_64_concat) u32 => Vec; /// Temporary value (cleared at block finalization) which is `Some` /// if per-block initialization has already been called for current block. Initialized get(fn initialized): Option; /// Temporary value (cleared at block finalization) that includes the VRF output generated /// at this block. This field should always be populated during block processing unless /// secondary plain slots are enabled (which don't contain a VRF output). AuthorVrfRandomness get(fn author_vrf_randomness): MaybeRandomness; /// The block numbers when the last and current epoch have started, respectively `N-1` and /// `N`. /// NOTE: We track this is in order to annotate the block number when a given pool of /// entropy was fixed (i.e. it was known to chain observers). Since epochs are defined in /// slots, which may be skipped, the block numbers may not line up with the slot numbers. EpochStart: (T::BlockNumber, T::BlockNumber); /// How late the current block is compared to its parent. /// /// This entry is populated as part of block execution and is cleaned up /// on block finalization. Querying this storage entry outside of block /// execution context should always yield zero. Lateness get(fn lateness): T::BlockNumber; /// The configuration for the current epoch. Should never be `None` as it is initialized in genesis. EpochConfig: Option; /// The configuration for the next epoch, `None` if the config will not change /// (you can fallback to `EpochConfig` instead in that case). NextEpochConfig: Option; } add_extra_genesis { config(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>; config(epoch_config): Option; build(|config| { Module::::initialize_authorities(&config.authorities); EpochConfig::put(config.epoch_config.clone().expect("epoch_config must not be None")); }) } } decl_module! { /// The BABE Pallet pub struct Module for enum Call where origin: T::Origin { /// The number of **slots** that an epoch takes. We couple sessions to /// epochs, i.e. we start a new session once the new epoch begins. /// NOTE: Currently it is not possible to change the epoch duration /// after the chain has started. Attempting to do so will brick block /// production. const EpochDuration: u64 = T::EpochDuration::get(); /// The expected average block time at which BABE should be creating /// blocks. Since BABE is probabilistic it is not trivial to figure out /// what the expected average block time should be based on the slot /// duration and the security parameter `c` (where `1 - c` represents /// the probability of a slot being empty). const ExpectedBlockTime: T::Moment = T::ExpectedBlockTime::get(); /// Initialization fn on_initialize(now: T::BlockNumber) -> Weight { Self::do_initialize(now); 0 } /// Block finalization fn on_finalize() { // at the end of the block, we can safely include the new VRF output // from this block into the under-construction randomness. If we've determined // that this block was the first in a new epoch, the changeover logic has // already occurred at this point, so the under-construction randomness // will only contain outputs from the right epoch. if let Some(Some(randomness)) = Initialized::take() { Self::deposit_randomness(&randomness); } // The stored author generated VRF output is ephemeral. AuthorVrfRandomness::kill(); // remove temporary "environment" entry from storage Lateness::::kill(); } /// Report authority equivocation/misbehavior. This method will verify /// the equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence will /// be reported. #[weight = ::WeightInfo::report_equivocation(key_owner_proof.validator_count())] fn report_equivocation( origin, equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { let reporter = ensure_signed(origin)?; Self::do_report_equivocation( Some(reporter), equivocation_proof, key_owner_proof, ) } /// Report authority equivocation/misbehavior. This method will verify /// the equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence will /// be reported. /// This extrinsic must be called unsigned and it is expected that only /// block authors will call it (validated in `ValidateUnsigned`), as such /// if the block author is defined it will be defined as the equivocation /// reporter. #[weight = ::WeightInfo::report_equivocation(key_owner_proof.validator_count())] fn report_equivocation_unsigned( origin, equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; Self::do_report_equivocation( T::HandleEquivocation::block_author(), equivocation_proof, key_owner_proof, ) } /// Plan an epoch config change. The epoch config change is recorded and will be enacted on /// the next call to `enact_epoch_change`. The config will be activated one epoch after. /// Multiple calls to this method will replace any existing planned config change that had /// not been enacted yet. #[weight = ::WeightInfo::plan_config_change()] fn plan_config_change( origin, config: NextConfigDescriptor, ) { ensure_root(origin)?; PendingEpochConfigChange::put(config); } } } /// A BABE public key pub type BabeKey = [u8; PUBLIC_KEY_LENGTH]; impl FindAuthor for Module { fn find_author<'a, I>(digests: I) -> Option where I: 'a + IntoIterator { for (id, mut data) in digests.into_iter() { if id == BABE_ENGINE_ID { let pre_digest: PreDigest = PreDigest::decode(&mut data).ok()?; return Some(pre_digest.authority_index()) } } return None; } } impl IsMember for Module { fn is_member(authority_id: &AuthorityId) -> bool { >::authorities() .iter() .any(|id| &id.0 == authority_id) } } impl pallet_session::ShouldEndSession for Module { fn should_end_session(now: T::BlockNumber) -> bool { // it might be (and it is in current implementation) that session module is calling // should_end_session() from it's own on_initialize() handler // => because pallet_session on_initialize() is called earlier than ours, let's ensure // that we have synced with digest before checking if session should be ended. Self::do_initialize(now); Self::should_epoch_change(now) } } impl Module { /// Determine the BABE slot duration based on the Timestamp module configuration. pub fn slot_duration() -> T::Moment { // we double the minimum block-period so each author can always propose within // the majority of their slot. ::MinimumPeriod::get().saturating_mul(2u32.into()) } /// Determine whether an epoch change should take place at this block. /// Assumes that initialization has already taken place. pub fn should_epoch_change(now: T::BlockNumber) -> bool { // The epoch has technically ended during the passage of time // between this block and the last, but we have to "end" the epoch now, // since there is no earlier possible block we could have done it. // // The exception is for block 1: the genesis has slot 0, so we treat // epoch 0 as having started at the slot of block 1. We want to use // the same randomness and validator set as signalled in the genesis, // so we don't rotate the epoch. now != One::one() && { let diff = CurrentSlot::get().saturating_sub(Self::current_epoch_start()); *diff >= T::EpochDuration::get() } } /// Return the _best guess_ block number, at which the next epoch change is predicted to happen. /// /// Returns None if the prediction is in the past; This implies an error internally in the Babe /// and should not happen under normal circumstances. /// /// In other word, this is only accurate if no slots are missed. Given missed slots, the slot /// number will grow while the block number will not. Hence, the result can be interpreted as an /// upper bound. // // ## IMPORTANT NOTE // // This implementation is linked to how [`should_epoch_change`] is working. This might need to // be updated accordingly, if the underlying mechanics of slot and epochs change. // // WEIGHT NOTE: This function is tied to the weight of `EstimateNextSessionRotation`. If you // update this function, you must also update the corresponding weight. pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option { let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get()); next_slot .checked_sub(*CurrentSlot::get()) .map(|slots_remaining| { // This is a best effort guess. Drifts in the slot/block ratio will cause errors here. let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into(); now.saturating_add(blocks_remaining) }) } /// DANGEROUS: Enact an epoch change. Should be done on every block where `should_epoch_change` has returned `true`, /// and the caller is the only caller of this function. /// /// Typically, this is not handled directly by the user, but by higher-level validator-set manager logic like /// `pallet-session`. pub fn enact_epoch_change( authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, next_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, ) { // PRECONDITION: caller has done initialization and is guaranteed // by the session module to be called before this. debug_assert!(Self::initialized().is_some()); // Update epoch index 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); Authorities::put(authorities); // Update epoch randomness. let next_epoch_index = epoch_index .checked_add(1) .expect("epoch indices will never reach 2^64 before the death of the universe; qed"); // Returns randomness for the current epoch and computes the *next* // epoch randomness. let randomness = Self::randomness_change_epoch(next_epoch_index); Randomness::put(randomness); // Update the next epoch authorities. NextAuthorities::put(&next_authorities); // Update the start blocks of the previous and new current epoch. >::mutate(|(previous_epoch_start_block, current_epoch_start_block)| { *previous_epoch_start_block = sp_std::mem::take(current_epoch_start_block); *current_epoch_start_block = >::block_number(); }); // After we update the current epoch, we signal the *next* epoch change // so that nodes can track changes. let next_randomness = NextRandomness::get(); let next_epoch = NextEpochDescriptor { authorities: next_authorities, randomness: next_randomness, }; Self::deposit_consensus(ConsensusLog::NextEpochData(next_epoch)); if let Some(next_config) = NextEpochConfig::get() { EpochConfig::put(next_config); } if let Some(pending_epoch_config_change) = PendingEpochConfigChange::take() { let next_epoch_config: BabeEpochConfiguration = pending_epoch_config_change.clone().into(); NextEpochConfig::put(next_epoch_config); Self::deposit_consensus(ConsensusLog::NextConfigData(pending_epoch_config_change)); } } /// Finds the start slot of the current epoch. only guaranteed to /// give correct results after `do_initialize` of the first block /// in the chain (as its result is based off of `GenesisSlot`). pub fn current_epoch_start() -> Slot { Self::epoch_start(EpochIndex::get()) } /// Produces information about the current epoch. pub fn current_epoch() -> Epoch { Epoch { epoch_index: EpochIndex::get(), start_slot: Self::current_epoch_start(), duration: T::EpochDuration::get(), authorities: Self::authorities(), randomness: Self::randomness(), config: EpochConfig::get().expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed"), } } /// Produces information about the next epoch (which was already previously /// announced). pub fn next_epoch() -> Epoch { let next_epoch_index = EpochIndex::get().checked_add(1).expect( "epoch index is u64; it is always only incremented by one; \ if u64 is not enough we should crash for safety; qed.", ); Epoch { epoch_index: next_epoch_index, start_slot: Self::epoch_start(next_epoch_index), duration: T::EpochDuration::get(), authorities: NextAuthorities::get(), randomness: NextRandomness::get(), config: NextEpochConfig::get().unwrap_or_else(|| { EpochConfig::get().expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed") }), } } fn epoch_start(epoch_index: u64) -> Slot { // (epoch_index * epoch_duration) + genesis_slot const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \ if u64 is not enough we should crash for safety; qed."; let epoch_start = epoch_index .checked_mul(T::EpochDuration::get()) .expect(PROOF); epoch_start.checked_add(*GenesisSlot::get()).expect(PROOF).into() } fn deposit_consensus(new: U) { let log: DigestItem = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); >::deposit_log(log.into()) } fn deposit_randomness(randomness: &schnorrkel::Randomness) { let segment_idx = ::get(); let mut segment = ::get(&segment_idx); if segment.len() < UNDER_CONSTRUCTION_SEGMENT_LENGTH { // push onto current segment: not full. segment.push(*randomness); ::insert(&segment_idx, &segment); } else { // move onto the next segment and update the index. let segment_idx = segment_idx + 1; ::insert(&segment_idx, &vec![randomness.clone()]); ::put(&segment_idx); } } fn do_initialize(now: T::BlockNumber) { // since do_initialize can be called twice (if session module is present) // => let's ensure that we only modify the storage once per block let initialized = Self::initialized().is_some(); if initialized { return; } let maybe_pre_digest: Option = >::digest() .logs .iter() .filter_map(|s| s.as_pre_runtime()) .filter_map(|(id, mut data)| if id == BABE_ENGINE_ID { PreDigest::decode(&mut data).ok() } else { None }) .next(); let is_primary = matches!(maybe_pre_digest, Some(PreDigest::Primary(..))); let maybe_randomness: MaybeRandomness = maybe_pre_digest.and_then(|digest| { // on the first non-zero block (i.e. block #1) // this is where the first epoch (epoch #0) actually starts. // we need to adjust internal storage accordingly. if *GenesisSlot::get() == 0 { GenesisSlot::put(digest.slot()); debug_assert_ne!(*GenesisSlot::get(), 0); // deposit a log because this is the first block in epoch #0 // we use the same values as genesis because we haven't collected any // randomness yet. let next = NextEpochDescriptor { authorities: Self::authorities(), randomness: Self::randomness(), }; Self::deposit_consensus(ConsensusLog::NextEpochData(next)) } // the slot number of the current block being initialized let current_slot = digest.slot(); // how many slots were skipped between current and last block let lateness = current_slot.saturating_sub(CurrentSlot::get() + 1); let lateness = T::BlockNumber::from(*lateness as u32); Lateness::::put(lateness); CurrentSlot::put(current_slot); let authority_index = digest.authority_index(); // Extract out the VRF output if we have it digest .vrf_output() .and_then(|vrf_output| { // Reconstruct the bytes of VRFInOut using the authority id. Authorities::get() .get(authority_index as usize) .and_then(|author| { schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok() }) .and_then(|pubkey| { let transcript = sp_consensus_babe::make_transcript( &Self::randomness(), current_slot, EpochIndex::get(), ); vrf_output.0.attach_input_hash( &pubkey, transcript ).ok() }) .map(|inout| { inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT) }) }) }); // For primary VRF output we place it in the `Initialized` storage // item and it'll be put onto the under-construction randomness later, // once we've decided which epoch this block is in. Initialized::put(if is_primary { maybe_randomness } else { None }); // Place either the primary or secondary VRF output into the // `AuthorVrfRandomness` storage item. AuthorVrfRandomness::put(maybe_randomness); // enact epoch change, if necessary. T::EpochChangeTrigger::trigger::(now) } /// Call this function exactly once when an epoch changes, to update the /// randomness. Returns the new randomness. fn randomness_change_epoch(next_epoch_index: u64) -> schnorrkel::Randomness { let this_randomness = NextRandomness::get(); let segment_idx: u32 = ::mutate(|s| sp_std::mem::replace(s, 0)); // overestimate to the segment being full. let rho_size = segment_idx.saturating_add(1) as usize * UNDER_CONSTRUCTION_SEGMENT_LENGTH; let next_randomness = compute_randomness( this_randomness, next_epoch_index, (0..segment_idx).flat_map(|i| ::take(&i)), Some(rho_size), ); NextRandomness::put(&next_randomness); this_randomness } fn initialize_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { if !authorities.is_empty() { assert!(Authorities::get().is_empty(), "Authorities are already initialized!"); Authorities::put(authorities); NextAuthorities::put(authorities); } } fn do_report_equivocation( reporter: Option, equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { let offender = equivocation_proof.offender.clone(); let slot = equivocation_proof.slot; // validate the equivocation proof if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) { return Err(Error::::InvalidEquivocationProof.into()); } let validator_set_count = key_owner_proof.validator_count(); let session_index = key_owner_proof.session(); let epoch_index = (*slot.saturating_sub(GenesisSlot::get()) / T::EpochDuration::get()) .saturated_into::(); // check that the slot number is consistent with the session index // in the key ownership proof (i.e. slot is for that epoch) if epoch_index != session_index { return Err(Error::::InvalidKeyOwnershipProof.into()); } // check the membership proof and extract the offender's id let key = (sp_consensus_babe::KEY_TYPE, offender); let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof) .ok_or(Error::::InvalidKeyOwnershipProof)?; let offence = BabeEquivocationOffence { slot, validator_set_count, offender, session_index, }; let reporters = match reporter { Some(id) => vec![id], None => vec![], }; T::HandleEquivocation::report_offence(reporters, offence) .map_err(|_| Error::::DuplicateOffenceReport)?; // waive the fee since the report is valid and beneficial Ok(Pays::No.into()) } /// Submits an extrinsic to report an equivocation. This method will create /// an unsigned extrinsic with a call to `report_equivocation_unsigned` and /// will push the transaction to the pool. Only useful in an offchain /// context. pub fn submit_unsigned_equivocation_report( equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> Option<()> { T::HandleEquivocation::submit_unsigned_equivocation_report( equivocation_proof, key_owner_proof, ) .ok() } } impl OnTimestampSet for Module { fn on_timestamp_set(moment: T::Moment) { let slot_duration = Self::slot_duration(); assert!(!slot_duration.is_zero(), "Babe slot duration cannot be zero."); let timestamp_slot = moment / slot_duration; let timestamp_slot = Slot::from(timestamp_slot.saturated_into::()); assert!(CurrentSlot::get() == timestamp_slot, "Timestamp slot must match `CurrentSlot`"); } } impl frame_support::traits::EstimateNextSessionRotation for Module { fn average_session_length() -> T::BlockNumber { T::EpochDuration::get().saturated_into() } fn estimate_current_session_progress(_now: T::BlockNumber) -> (Option, Weight) { let elapsed = CurrentSlot::get().saturating_sub(Self::current_epoch_start()) + 1; ( Some(Percent::from_rational( *elapsed, T::EpochDuration::get(), )), // Read: Current Slot, Epoch Index, Genesis Slot T::DbWeight::get().reads(3), ) } fn estimate_next_session_rotation(now: T::BlockNumber) -> (Option, Weight) { ( Self::next_expected_epoch_change(now), // Read: Current Slot, Epoch Index, Genesis Slot T::DbWeight::get().reads(3), ) } } impl frame_support::traits::Lateness for Module { fn lateness(&self) -> T::BlockNumber { Self::lateness() } } impl sp_runtime::BoundToRuntimeAppPublic for Module { type Public = AuthorityId; } impl OneSessionHandler for Module { type Key = AuthorityId; fn on_genesis_session<'a, I: 'a>(validators: I) where I: Iterator { let authorities = validators.map(|(_, k)| (k, 1)).collect::>(); Self::initialize_authorities(&authorities); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) where I: Iterator { let authorities = validators.map(|(_account, k)| { (k, 1) }).collect::>(); let next_authorities = queued_validators.map(|(_account, k)| { (k, 1) }).collect::>(); Self::enact_epoch_change(authorities, next_authorities) } fn on_disabled(i: usize) { Self::deposit_consensus(ConsensusLog::OnDisabled(i as u32)) } } // compute randomness for a new epoch. rho is the concatenation of all // VRF outputs in the prior epoch. // // an optional size hint as to how many VRF outputs there were may be provided. fn compute_randomness( last_epoch_randomness: schnorrkel::Randomness, epoch_index: u64, rho: impl Iterator, rho_size_hint: Option, ) -> schnorrkel::Randomness { let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH); s.extend_from_slice(&last_epoch_randomness); s.extend_from_slice(&epoch_index.to_le_bytes()); for vrf_output in rho { s.extend_from_slice(&vrf_output[..]); } sp_io::hashing::blake2_256(&s) } pub mod migrations { use super::*; use frame_support::pallet_prelude::{ValueQuery, StorageValue}; /// Something that can return the storage prefix of the `Babe` pallet. pub trait BabePalletPrefix: Config { fn pallet_prefix() -> &'static str; } struct __OldNextEpochConfig(sp_std::marker::PhantomData); impl frame_support::traits::StorageInstance for __OldNextEpochConfig { fn pallet_prefix() -> &'static str { T::pallet_prefix() } const STORAGE_PREFIX: &'static str = "NextEpochConfig"; } type OldNextEpochConfig = StorageValue< __OldNextEpochConfig, Option, ValueQuery >; /// A storage migration that adds the current epoch configuration for Babe /// to storage. pub fn add_epoch_configuration( epoch_config: BabeEpochConfiguration, ) -> Weight { let mut writes = 0; let mut reads = 0; if let Some(pending_change) = OldNextEpochConfig::::get() { PendingEpochConfigChange::put(pending_change); writes += 1; } reads += 1; OldNextEpochConfig::::kill(); EpochConfig::put(epoch_config.clone()); NextEpochConfig::put(epoch_config); writes += 3; T::DbWeight::get().writes(writes) + T::DbWeight::get().reads(reads) } }