// 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 . //! Configuration manager for the Polkadot runtime parachains logic. //! //! Configuration can change only at session boundaries and is buffered until then. use sp_std::prelude::*; use primitives::v1::ValidatorId; use frame_support::{ decl_storage, decl_module, decl_error, dispatch::DispatchResult, weights::{DispatchClass, Weight}, }; use codec::{Encode, Decode}; use frame_system::ensure_root; /// All configuration of the runtime with respect to parachains and parathreads. #[derive(Clone, Encode, Decode, PartialEq, Default)] #[cfg_attr(test, derive(Debug))] pub struct HostConfiguration { /// The minimum frequency at which parachains can update their validation code. pub validation_upgrade_frequency: BlockNumber, /// The delay, in blocks, before a validation upgrade is applied. pub validation_upgrade_delay: BlockNumber, /// The acceptance period, in blocks. This is the amount of blocks after availability that validators /// and fishermen have to perform secondary checks or issue reports. pub acceptance_period: BlockNumber, /// The maximum validation code size, in bytes. pub max_code_size: u32, /// The maximum head-data size, in bytes. pub max_head_data_size: u32, /// The amount of execution cores to dedicate to parathread execution. pub parathread_cores: u32, /// The number of retries that a parathread author has to submit their block. pub parathread_retries: u32, /// How often parachain groups should be rotated across parachains. Must be non-zero. pub group_rotation_frequency: BlockNumber, /// The availability period, in blocks, for parachains. This is the amount of blocks /// after inclusion that validators have to make the block available and signal its availability to /// the chain. Must be at least 1. pub chain_availability_period: BlockNumber, /// The availability period, in blocks, for parathreads. Same as the `chain_availability_period`, /// but a differing timeout due to differing requirements. Must be at least 1. pub thread_availability_period: BlockNumber, /// The amount of blocks ahead to schedule parachains and parathreads. pub scheduling_lookahead: u32, /// The maximum size of a message that can be put in a downward message queue. /// /// Since we require receiving at least one DMP message the obvious upper bound of the size is /// the PoV size. Of course, there is a lot of other different things that a parachain may /// decide to do with its PoV so this value in practice will be picked as a fraction of the PoV /// size. pub max_downward_message_size: u32, } pub trait Trait: frame_system::Trait { } decl_storage! { trait Store for Module as Configuration { /// The active configuration for the current session. Config get(fn config) config(): HostConfiguration; /// Pending configuration (if any) for the next session. PendingConfig: Option>; } } decl_error! { pub enum Error for Module { } } decl_module! { /// The parachains configuration module. pub struct Module for enum Call where origin: ::Origin { type Error = Error; /// Set the validation upgrade frequency. #[weight = (1_000, DispatchClass::Operational)] pub fn set_validation_upgrade_frequency(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.validation_upgrade_frequency, new) != new }); Ok(()) } /// Set the validation upgrade delay. #[weight = (1_000, DispatchClass::Operational)] pub fn set_validation_upgrade_delay(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.validation_upgrade_delay, new) != new }); Ok(()) } /// Set the acceptance period for an included candidate. #[weight = (1_000, DispatchClass::Operational)] pub fn set_acceptance_period(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.acceptance_period, new) != new }); Ok(()) } /// Set the max validation code size for incoming upgrades. #[weight = (1_000, DispatchClass::Operational)] pub fn set_max_code_size(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.max_code_size, new) != new }); Ok(()) } /// Set the max head data size for paras. #[weight = (1_000, DispatchClass::Operational)] pub fn set_max_head_data_size(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.max_head_data_size, new) != new }); Ok(()) } /// Set the number of parathread execution cores. #[weight = (1_000, DispatchClass::Operational)] pub fn set_parathread_cores(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.parathread_cores, new) != new }); Ok(()) } /// Set the number of retries for a particular parathread. #[weight = (1_000, DispatchClass::Operational)] pub fn set_parathread_retries(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.parathread_retries, new) != new }); Ok(()) } /// Set the parachain validator-group rotation frequency #[weight = (1_000, DispatchClass::Operational)] pub fn set_group_rotation_frequency(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.group_rotation_frequency, new) != new }); Ok(()) } /// Set the availability period for parachains. #[weight = (1_000, DispatchClass::Operational)] pub fn set_chain_availability_period(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.chain_availability_period, new) != new }); Ok(()) } /// Set the availability period for parathreads. #[weight = (1_000, DispatchClass::Operational)] pub fn set_thread_availability_period(origin, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.thread_availability_period, new) != new }); Ok(()) } /// Set the scheduling lookahead, in expected number of blocks at peak throughput. #[weight = (1_000, DispatchClass::Operational)] pub fn set_scheduling_lookahead(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.scheduling_lookahead, new) != new }); Ok(()) } /// Set the critical downward message size. #[weight = (1_000, DispatchClass::Operational)] pub fn set_max_downward_message_size(origin, new: u32) -> DispatchResult { ensure_root(origin)?; Self::update_config_member(|config| { sp_std::mem::replace(&mut config.max_downward_message_size, new) != new }); Ok(()) } } } impl Module { /// Called by the initializer to initialize the configuration module. pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight { 0 } /// Called by the initializer to finalize the configuration module. pub(crate) fn initializer_finalize() { } /// Called by the initializer to note that a new session has started. pub(crate) fn initializer_on_new_session(_validators: &[ValidatorId], _queued: &[ValidatorId]) { if let Some(pending) = ::PendingConfig::take() { ::Config::set(pending); } } fn update_config_member( updater: impl FnOnce(&mut HostConfiguration) -> bool, ) { let pending = ::PendingConfig::get(); let mut prev = pending.unwrap_or_else(Self::config); if updater(&mut prev) { ::PendingConfig::set(Some(prev)); } } } #[cfg(test)] mod tests { use super::*; use crate::mock::{new_test_ext, Initializer, Configuration, Origin}; use frame_support::traits::{OnFinalize, OnInitialize}; #[test] fn config_changes_on_session_boundary() { new_test_ext(Default::default()).execute_with(|| { let old_config = Configuration::config(); let mut config = old_config.clone(); config.validation_upgrade_delay = 100; assert!(old_config != config); ::PendingConfig::set(Some(config.clone())); Initializer::on_initialize(1); assert_eq!(Configuration::config(), old_config); assert_eq!(::PendingConfig::get(), Some(config.clone())); Initializer::on_finalize(1); Configuration::initializer_on_new_session(&[], &[]); assert_eq!(Configuration::config(), config); assert!(::PendingConfig::get().is_none()); }) } #[test] fn setting_pending_config_members() { new_test_ext(Default::default()).execute_with(|| { let new_config = HostConfiguration { validation_upgrade_frequency: 100, validation_upgrade_delay: 10, acceptance_period: 5, max_code_size: 100_000, max_head_data_size: 1_000, parathread_cores: 2, parathread_retries: 5, group_rotation_frequency: 20, chain_availability_period: 10, thread_availability_period: 8, scheduling_lookahead: 3, max_downward_message_size: 2048, }; assert!(::PendingConfig::get().is_none()); Configuration::set_validation_upgrade_frequency( Origin::root(), new_config.validation_upgrade_frequency, ).unwrap(); Configuration::set_validation_upgrade_delay( Origin::root(), new_config.validation_upgrade_delay, ).unwrap(); Configuration::set_acceptance_period( Origin::root(), new_config.acceptance_period, ).unwrap(); Configuration::set_max_code_size( Origin::root(), new_config.max_code_size, ).unwrap(); Configuration::set_max_head_data_size( Origin::root(), new_config.max_head_data_size, ).unwrap(); Configuration::set_parathread_cores( Origin::root(), new_config.parathread_cores, ).unwrap(); Configuration::set_parathread_retries( Origin::root(), new_config.parathread_retries, ).unwrap(); Configuration::set_group_rotation_frequency( Origin::root(), new_config.group_rotation_frequency, ).unwrap(); Configuration::set_chain_availability_period( Origin::root(), new_config.chain_availability_period, ).unwrap(); Configuration::set_thread_availability_period( Origin::root(), new_config.thread_availability_period, ).unwrap(); Configuration::set_scheduling_lookahead( Origin::root(), new_config.scheduling_lookahead, ).unwrap(); Configuration::set_max_downward_message_size( Origin::root(), new_config.max_downward_message_size, ).unwrap(); assert_eq!(::PendingConfig::get(), Some(new_config)); }) } #[test] fn non_root_cannot_set_config() { new_test_ext(Default::default()).execute_with(|| { assert!(Configuration::set_validation_upgrade_delay(Origin::signed(1), 100).is_err()); }); } #[test] fn setting_config_to_same_as_current_is_noop() { new_test_ext(Default::default()).execute_with(|| { Configuration::set_validation_upgrade_delay(Origin::root(), Default::default()).unwrap(); assert!(::PendingConfig::get().is_none()) }); } }