// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . //! This module is responsible for maintaining a consistent initialization order for all other //! parachains modules. It's also responsible for finalization and session change notifications. //! //! This module can throw fatal errors if session-change notifications are received after initialization. use crate::{ configuration::{self, HostConfiguration}, disputes::{self, DisputesHandler as _, SlashingHandler as _}, dmp, hrmp, inclusion, paras, scheduler, session_info, shared, }; use frame_support::{ traits::{OneSessionHandler, Randomness}, weights::Weight, }; use frame_system::limits::BlockWeights; use parity_scale_codec::{Decode, Encode}; use primitives::{BlockNumber, ConsensusLog, SessionIndex, ValidatorId}; use scale_info::TypeInfo; use sp_std::prelude::*; #[cfg(test)] mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub use pallet::*; /// Information about a session change that has just occurred. #[derive(Clone)] pub struct SessionChangeNotification { /// The new validators in the session. pub validators: Vec, /// The queued validators for the following session. pub queued: Vec, /// The configuration before handling the session change pub prev_config: HostConfiguration, /// The configuration after handling the session change. pub new_config: HostConfiguration, /// A secure random seed for the session, gathered from BABE. pub random_seed: [u8; 32], /// New session index. pub session_index: SessionIndex, } /// Number of validators (not only parachain) in a session. pub type ValidatorSetCount = u32; impl> Default for SessionChangeNotification { fn default() -> Self { Self { validators: Vec::new(), queued: Vec::new(), prev_config: HostConfiguration::default(), new_config: HostConfiguration::default(), random_seed: Default::default(), session_index: Default::default(), } } } #[derive(Encode, Decode, TypeInfo)] struct BufferedSessionChange { validators: Vec, queued: Vec, session_index: SessionIndex, } pub trait WeightInfo { fn force_approve(d: u32) -> Weight; } impl WeightInfo for () { fn force_approve(_: u32) -> Weight { BlockWeights::default().max_block } } #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config + configuration::Config + shared::Config + paras::Config + scheduler::Config + inclusion::Config + session_info::Config + disputes::Config + dmp::Config + hrmp::Config { /// A randomness beacon. type Randomness: Randomness; /// An origin which is allowed to force updates to parachains. type ForceOrigin: EnsureOrigin<::RuntimeOrigin>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } /// Whether the parachains modules have been initialized within this block. /// /// Semantically a `bool`, but this guarantees it should never hit the trie, /// as this is cleared in `on_finalize` and Frame optimizes `None` values to be empty values. /// /// As a `bool`, `set(false)` and `remove()` both lead to the next `get()` being false, but one of /// them writes to the trie and one does not. This confusion makes `Option<()>` more suitable for /// the semantics of this variable. #[pallet::storage] pub(super) type HasInitialized = StorageValue<_, ()>; /// Buffered session changes along with the block number at which they should be applied. /// /// Typically this will be empty or one element long. Apart from that this item never hits /// the storage. /// /// However this is a `Vec` regardless to handle various edge cases that may occur at runtime /// upgrade boundaries or if governance intervenes. #[pallet::storage] pub(super) type BufferedSessionChanges = StorageValue<_, Vec, ValueQuery>; #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: T::BlockNumber) -> Weight { // The other modules are initialized in this order: // - Configuration // - Paras // - Scheduler // - Inclusion // - `SessionInfo` // - Disputes // - DMP // - UMP // - HRMP let total_weight = configuration::Pallet::::initializer_initialize(now) + shared::Pallet::::initializer_initialize(now) + paras::Pallet::::initializer_initialize(now) + scheduler::Pallet::::initializer_initialize(now) + inclusion::Pallet::::initializer_initialize(now) + session_info::Pallet::::initializer_initialize(now) + T::DisputesHandler::initializer_initialize(now) + T::SlashingHandler::initializer_initialize(now) + dmp::Pallet::::initializer_initialize(now) + hrmp::Pallet::::initializer_initialize(now); HasInitialized::::set(Some(())); total_weight } fn on_finalize(now: T::BlockNumber) { // reverse initialization order. hrmp::Pallet::::initializer_finalize(); dmp::Pallet::::initializer_finalize(); T::SlashingHandler::initializer_finalize(); T::DisputesHandler::initializer_finalize(); session_info::Pallet::::initializer_finalize(); inclusion::Pallet::::initializer_finalize(); scheduler::Pallet::::initializer_finalize(); paras::Pallet::::initializer_finalize(now); shared::Pallet::::initializer_finalize(); configuration::Pallet::::initializer_finalize(); // Apply buffered session changes as the last thing. This way the runtime APIs and the // next block will observe the next session. // // Note that we only apply the last session as all others lasted less than a block (weirdly). if let Some(BufferedSessionChange { session_index, validators, queued }) = BufferedSessionChanges::::take().pop() { Self::apply_new_session(session_index, validators, queued); } HasInitialized::::take(); } } #[pallet::call] impl Pallet { /// Issue a signal to the consensus engine to forcibly act as though all parachain /// blocks in all relay chain blocks up to and including the given number in the current /// chain are valid and should be finalized. #[pallet::call_index(0)] #[pallet::weight(( ::WeightInfo::force_approve( frame_system::Pallet::::digest().logs.len() as u32, ), DispatchClass::Operational, ))] pub fn force_approve(origin: OriginFor, up_to: BlockNumber) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; frame_system::Pallet::::deposit_log(ConsensusLog::ForceApprove(up_to).into()); Ok(()) } } } impl Pallet { fn apply_new_session( session_index: SessionIndex, all_validators: Vec, queued: Vec, ) { let random_seed = { let mut buf = [0u8; 32]; // TODO: audit usage of randomness API // https://github.com/paritytech/polkadot/issues/2601 let (random_hash, _) = T::Randomness::random(&b"paras"[..]); let len = sp_std::cmp::min(32, random_hash.as_ref().len()); buf[..len].copy_from_slice(&random_hash.as_ref()[..len]); buf }; let configuration::SessionChangeOutcome { prev_config, new_config } = configuration::Pallet::::initializer_on_new_session(&session_index); let new_config = new_config.unwrap_or_else(|| prev_config.clone()); let validators = shared::Pallet::::initializer_on_new_session( session_index, random_seed, &new_config, all_validators, ); let notification = SessionChangeNotification { validators, queued, prev_config, new_config, random_seed, session_index, }; let outgoing_paras = paras::Pallet::::initializer_on_new_session(¬ification); scheduler::Pallet::::initializer_on_new_session(¬ification); inclusion::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); session_info::Pallet::::initializer_on_new_session(¬ification); T::DisputesHandler::initializer_on_new_session(¬ification); T::SlashingHandler::initializer_on_new_session(session_index); dmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); hrmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); } /// Should be called when a new session occurs. Buffers the session notification to be applied /// at the end of the block. If `queued` is `None`, the `validators` are considered queued. fn on_new_session<'a, I: 'a>( _changed: bool, session_index: SessionIndex, validators: I, queued: Option, ) where I: Iterator, { let validators: Vec<_> = validators.map(|(_, v)| v).collect(); let queued: Vec<_> = if let Some(queued) = queued { queued.map(|(_, v)| v).collect() } else { validators.clone() }; if session_index == 0 { // Genesis session should be immediately enacted. Self::apply_new_session(0, validators, queued); } else { BufferedSessionChanges::::mutate(|v| { v.push(BufferedSessionChange { validators, queued, session_index }) }); } } // Allow to trigger `on_new_session` in tests, this is needed as long as `pallet_session` is not // implemented in mock. #[cfg(any(test, feature = "runtime-benchmarks"))] pub(crate) fn test_trigger_on_new_session<'a, I: 'a>( changed: bool, session_index: SessionIndex, validators: I, queued: Option, ) where I: Iterator, { Self::on_new_session(changed, session_index, validators, queued) } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { type Public = ValidatorId; } impl OneSessionHandler for Pallet { type Key = ValidatorId; fn on_genesis_session<'a, I: 'a>(validators: I) where I: Iterator, { >::on_new_session(false, 0, validators, None); } fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued: I) where I: Iterator, { let session_index = >::current_index(); >::on_new_session(changed, session_index, validators, Some(queued)); } fn on_disabled(_i: u32) {} }