// Copyright 2020 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 . //! The session info module provides information about validator sets //! from prior sessions needed for approvals and disputes. //! //! See https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html. use primitives::v1::{AuthorityDiscoveryId, SessionIndex, SessionInfo}; use frame_support::{ decl_storage, decl_module, decl_error, weights::Weight, }; use crate::{configuration, paras, scheduler}; use sp_std::{cmp, vec::Vec}; pub trait Trait: frame_system::Trait + configuration::Trait + paras::Trait + scheduler::Trait + AuthorityDiscoveryTrait { } decl_storage! { trait Store for Module as ParaSessionInfo { /// The earliest session for which previous session info is stored. EarliestStoredSession get(fn earliest_stored_session): SessionIndex; /// Session information in a rolling window. /// Should have an entry in range `EarliestStoredSession..=CurrentSessionIndex`. Sessions get(fn session_info): map hasher(identity) SessionIndex => Option; } } decl_error! { pub enum Error for Module { } } decl_module! { /// The session info module. pub struct Module for enum Call where origin: ::Origin { type Error = Error; } } /// An abstraction for the authority discovery pallet /// to help with mock testing. pub trait AuthorityDiscoveryTrait { /// Retrieve authority identifiers of the current and next authority set. fn authorities() -> Vec; } impl AuthorityDiscoveryTrait for T { fn authorities() -> Vec { >::authorities() } } impl Module { /// Handle an incoming session change. pub(crate) fn initializer_on_new_session( notification: &crate::initializer::SessionChangeNotification ) { let config = >::config(); let dispute_period = config.dispute_period; let n_parachains = >::parachains().len() as u32; let validators = notification.validators.clone(); let discovery_keys = ::authorities(); // FIXME: once we define these keys let approval_keys = Default::default(); let validator_groups = >::validator_groups(); let n_cores = n_parachains + config.parathread_cores; let zeroth_delay_tranche_width = config.zeroth_delay_tranche_width; let relay_vrf_modulo_samples = config.relay_vrf_modulo_samples; let n_delay_tranches = config.n_delay_tranches; let no_show_slots = config.no_show_slots; let needed_approvals = config.needed_approvals; let new_session_index = notification.session_index; let old_earliest_stored_session = EarliestStoredSession::get(); let dispute_period = cmp::max(1, dispute_period); let new_earliest_stored_session = new_session_index.checked_sub(dispute_period - 1).unwrap_or(0); let new_earliest_stored_session = cmp::max(new_earliest_stored_session, old_earliest_stored_session); // update `EarliestStoredSession` based on `config.dispute_period` EarliestStoredSession::set(new_earliest_stored_session); // remove all entries from `Sessions` from the previous value up to the new value for idx in old_earliest_stored_session..new_earliest_stored_session { Sessions::remove(&idx); } // create a new entry in `Sessions` with information about the current session let new_session_info = SessionInfo { validators, discovery_keys, approval_keys, validator_groups, n_cores, zeroth_delay_tranche_width, relay_vrf_modulo_samples, n_delay_tranches, no_show_slots, needed_approvals, }; Sessions::insert(&new_session_index, &new_session_info); } /// Called by the initializer to initialize the session info module. pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight { 0 } /// Called by the initializer to finalize the session info module. pub(crate) fn initializer_finalize() {} } #[cfg(test)] mod tests { use super::*; use crate::mock::{ new_test_ext, Configuration, SessionInfo, System, GenesisConfig as MockGenesisConfig, Origin, }; use crate::initializer::SessionChangeNotification; use crate::configuration::HostConfiguration; use frame_support::traits::{OnFinalize, OnInitialize}; use primitives::v1::BlockNumber; fn run_to_block( to: BlockNumber, new_session: impl Fn(BlockNumber) -> Option>, ) { while System::block_number() < to { let b = System::block_number(); SessionInfo::initializer_finalize(); Configuration::initializer_finalize(); System::on_finalize(b); System::on_initialize(b + 1); System::set_block_number(b + 1); if let Some(notification) = new_session(b + 1) { Configuration::initializer_on_new_session(¬ification.validators, ¬ification.queued); SessionInfo::initializer_on_new_session(¬ification); } Configuration::initializer_initialize(b + 1); SessionInfo::initializer_initialize(b + 1); } } fn default_config() -> HostConfiguration { HostConfiguration { parathread_cores: 1, dispute_period: 2, needed_approvals: 3, ..Default::default() } } fn genesis_config() -> MockGenesisConfig { MockGenesisConfig { configuration: configuration::GenesisConfig { config: default_config(), ..Default::default() }, ..Default::default() } } fn session_changes(n: BlockNumber) -> Option> { match n { 100 => Some(SessionChangeNotification { session_index: 10, ..Default::default() }), 200 => Some(SessionChangeNotification { session_index: 20, ..Default::default() }), 300 => Some(SessionChangeNotification { session_index: 30, ..Default::default() }), 400 => Some(SessionChangeNotification { session_index: 40, ..Default::default() }), _ => None, } } fn new_session_every_block(n: BlockNumber) -> Option> { Some(SessionChangeNotification{ session_index: n, ..Default::default() }) } #[test] fn session_pruning_is_based_on_dispute_deriod() { new_test_ext(genesis_config()).execute_with(|| { run_to_block(100, session_changes); assert_eq!(EarliestStoredSession::get(), 9); // changing dispute_period works let dispute_period = 5; Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap(); run_to_block(200, session_changes); assert_eq!(EarliestStoredSession::get(), 20 - dispute_period + 1); // we don't have that many sessions stored let new_dispute_period = 16; Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap(); run_to_block(300, session_changes); assert_eq!(EarliestStoredSession::get(), 20 - dispute_period + 1); // now we do run_to_block(400, session_changes); assert_eq!(EarliestStoredSession::get(), 40 - new_dispute_period + 1); }) } #[test] fn session_info_is_based_on_config() { new_test_ext(genesis_config()).execute_with(|| { run_to_block(1, new_session_every_block); let session = Sessions::get(&1).unwrap(); assert_eq!(session.needed_approvals, 3); // change some param Configuration::set_needed_approvals(Origin::root(), 42).unwrap(); run_to_block(2, new_session_every_block); let session = Sessions::get(&2).unwrap(); assert_eq!(session.needed_approvals, 42); }) } }