diff --git a/roadmap/implementers-guide/src/runtime/README.md b/roadmap/implementers-guide/src/runtime/README.md index 5279752e911c..c3cddbda7a95 100644 --- a/roadmap/implementers-guide/src/runtime/README.md +++ b/roadmap/implementers-guide/src/runtime/README.md @@ -15,11 +15,15 @@ There is some functionality of the relay chain relating to parachains that we al We will split the logic of the runtime up into these modules: * Initializer: manage initialization order of the other modules. +* Shared: manages shared storage and configurations for other modules. * Configuration: manage configuration and configuration updates in a non-racy manner. * Paras: manage chain-head and validation code for parachains and parathreads. * Scheduler: manages parachain and parathread scheduling as well as validator assignments. * Inclusion: handles the inclusion and availability of scheduled parachains and parathreads. * Validity: handles secondary checks and dispute resolution for included, available parablocks. +* Hrmp: handles horizontal messages between paras. +* Ump: Handles upward messages from a para to the relay chain. +* Dmp: Handles downward messages from the relay chain to the para. The [Initializer module](initializer.md) is special - it's responsible for handling the initialization logic of the other modules to ensure that the correct initialization order and related invariants are maintained. The other modules won't specify a on-initialize logic, but will instead expose a special semi-private routine that the initialization module will call. The other modules are relatively straightforward and perform the roles described above. diff --git a/roadmap/implementers-guide/src/runtime/inclusion.md b/roadmap/implementers-guide/src/runtime/inclusion.md index f2037f2d12c4..b1fc9ab8631b 100644 --- a/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/roadmap/implementers-guide/src/runtime/inclusion.md @@ -36,9 +36,6 @@ PendingAvailabilityCommitments: map ParaId => CandidateCommitments; /// The current validators, by their parachain session keys. Validators: Vec; - -/// The current session index. -CurrentSessionIndex: SessionIndex; ``` ## Session Change @@ -46,7 +43,6 @@ CurrentSessionIndex: SessionIndex; 1. Clear out all candidates pending availability. 1. Clear out all validator bitfields. 1. Update `Validators` with the validators from the session change notification. -1. Update `CurrentSessionIndex` with the session index from the session change notification. ## Routines diff --git a/roadmap/implementers-guide/src/runtime/paras.md b/roadmap/implementers-guide/src/runtime/paras.md index a0d659844648..51bc754f40ad 100644 --- a/roadmap/implementers-guide/src/runtime/paras.md +++ b/roadmap/implementers-guide/src/runtime/paras.md @@ -1,9 +1,9 @@ # Paras Module The Paras module is responsible for storing information on parachains and parathreads. Registered -parachains and parathreads cannot change except at session boundaries. This is primarily to ensure -that the number and meaning of bits required for the availability bitfields does not change except at session -boundaries. +parachains and parathreads cannot change except at session boundaries and after at least a full +session has passed. This is primarily to ensure that the number and meaning of bits required for the +availability bitfields does not change except at session boundaries. It's also responsible for managing parachain validation code upgrades as well as maintaining availability of old parachain code and its pruning. @@ -63,9 +63,9 @@ pub enum ParaLifecycle { /// Para is a Parachain. Parachain, /// Para is a Parathread which is upgrading to a Parachain. - UpgradingToParachain, + UpgradingParathread, /// Para is a Parachain which is downgrading to a Parathread. - DowngradingToParathread, + DowngradingParachain, /// Parathread is being offboarded. OutgoingParathread, /// Parachain is being offboarded. @@ -82,7 +82,7 @@ state of the para using the `ParaLifecycle` enum. None Parathread Parachain + + + | | | - | (Session Delay) | | + | (2 Session Delay) | | | | | +----------------------->+ | | Onboarding | | @@ -91,10 +91,10 @@ None Parathread Parachain | Onboarding | | | | | | +------------------------->+ - | | UpgradingToParachain | + | | UpgradingParathread | | | | | +<-------------------------+ - | | DowngradingToParathread | + | | DowngradingParachain | | | | |<-----------------------+ | | OutgoingParathread | | @@ -137,38 +137,31 @@ PastCodePruning: Vec<(ParaId, BlockNumber)>; FutureCodeUpgrades: map ParaId => Option; /// The actual future code of a para. FutureCode: map ParaId => Option; - -/// Upcoming paras (chains and threads). These are only updated on session change. Corresponds to an -/// entry in the upcoming-genesis map. Ordered ascending by ParaId. -UpcomingParas: Vec; +/// The actions to perform during the start of a specific session index. +ActionsQueue: map SessionIndex => Vec; /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map ParaId => Option; -/// Paras that are to be cleaned up at the end of the session. Ordered ascending by ParaId. -OutgoingParas: Vec; -/// Existing Parathreads that should upgrade to be a Parachain. Ordered ascending by ParaId. -UpcomingUpgrades: Vec; -/// Existing Parachains that should downgrade to be a Parathread. Ordered ascending by ParaId. -UpcomingDowngrades: Vec; ``` ## Session Change -1. Clean up outgoing paras. - 1. This means removing the entries under `Heads`, `ValidationCode`, `FutureCodeUpgrades`, and - `FutureCode`. An according entry should be added to `PastCode`, `PastCodeMeta`, and - `PastCodePruning` using the outgoing `ParaId` and removed `ValidationCode` value. This is - because any outdated validation code must remain available on-chain for a determined amount of - blocks, and validation code outdated by de-registering the para is still subject to that - invariant. -1. Apply all incoming paras by initializing the `Heads` and `ValidationCode` using the genesis - parameters. -1. Amend the `Parachains` list and `ParaLifecycle` to reflect changes in registered parachains. -1. Amend the `ParaLifecycle` set to reflect changes in registered parathreads. -1. Upgrade all parathreads that should become parachains, updating the `Parachains` list and - `ParaLifecycle`. -1. Downgrade all parachains that should become parathreads, updating the `Parachains` list and - `ParaLifecycle`. -1. Return list of outgoing paras to the initializer for use by other modules. +1. Execute all queued actions for paralifecycle changes: + 1. Clean up outgoing paras. + 1. This means removing the entries under `Heads`, `ValidationCode`, `FutureCodeUpgrades`, and + `FutureCode`. An according entry should be added to `PastCode`, `PastCodeMeta`, and + `PastCodePruning` using the outgoing `ParaId` and removed `ValidationCode` value. This is + because any outdated validation code must remain available on-chain for a determined amount + of blocks, and validation code outdated by de-registering the para is still subject to that + invariant. + 1. Apply all incoming paras by initializing the `Heads` and `ValidationCode` using the genesis + parameters. + 1. Amend the `Parachains` list and `ParaLifecycle` to reflect changes in registered parachains. + 1. Amend the `ParaLifecycle` set to reflect changes in registered parathreads. + 1. Upgrade all parathreads that should become parachains, updating the `Parachains` list and + `ParaLifecycle`. + 1. Downgrade all parachains that should become parathreads, updating the `Parachains` list and + `ParaLifecycle`. + 1. Return list of outgoing paras to the initializer for use by other modules. ## Initialization @@ -179,11 +172,9 @@ UpcomingDowngrades: Vec; * `schedule_para_initialize(ParaId, ParaGenesisArgs)`: Schedule a para to be initialized at the next session. Noop if para is already registered in the system with some `ParaLifecycle`. -* `schedule_para_cleanup(ParaId)`: Schedule a para to be cleaned up at the next session. -* `schedule_parathread_upgrade(ParaId)`: Schedule a parathread to be upgraded to a parachain. Noop - if `ParaLifecycle` is not `Parathread`. +* `schedule_para_cleanup(ParaId)`: Schedule a para to be cleaned up after the next full session. +* `schedule_parathread_upgrade(ParaId)`: Schedule a parathread to be upgraded to a parachain. * `schedule_parachain_downgrade(ParaId)`: Schedule a parachain to be downgraded to a parathread. - Noop if `ParaLifecycle` is not `Parachain`. * `schedule_code_upgrade(ParaId, ValidationCode, expected_at: BlockNumber)`: Schedule a future code upgrade of the given parachain, to be applied after inclusion of a block of the same parachain executed in the context of a relay-chain block with number >= `expected_at`. @@ -197,8 +188,8 @@ UpcomingDowngrades: Vec; current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If the validation code has been pruned, this will return `None`. * `lifecycle(ParaId) -> Option`: Return the `ParaLifecycle` of a para. -* `is_parachain(ParaId) -> bool`: Returns true if the para ID references any live parachain, including - those which may be transitioning to a parathread in the future. +* `is_parachain(ParaId) -> bool`: Returns true if the para ID references any live parachain, + including those which may be transitioning to a parathread in the future. * `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread, including those which may be transitioning to a parachain in the future. * `is_valid_para(ParaId) -> bool`: Returns true if the para ID references either a live parathread diff --git a/roadmap/implementers-guide/src/runtime/shared.md b/roadmap/implementers-guide/src/runtime/shared.md new file mode 100644 index 000000000000..7151e6acda79 --- /dev/null +++ b/roadmap/implementers-guide/src/runtime/shared.md @@ -0,0 +1,55 @@ +# Shared Module + +This module is responsible for managing shared storage and configuration for other modules. + +It is important that other pallets are able to use the Shared Module, so it should not have a +dependency on any other modules in the Parachains Runtime. + +For the moment, it is used exclusively to track the current session index across the Parachains +Runtime system, and when it should be allowed to schedule future changes to Paras or Configurations. + +## Constants + +```rust +// `SESSION_DELAY` is used to delay any changes to Paras registration or configurations. +// Wait until the session index is 2 larger then the current index to apply any changes, +// which guarantees that at least one full session has passed before any changes are applied. +pub(crate) const SESSION_DELAY: SessionIndex = 2; +``` + +## Storage + +```rust +// The current session index within the Parachains Runtime system. +CurrentSessionIndex: SessionIndex; +``` + +## Initialization + +The Shared Module currently has no initialization routines. + +The Shared Module is initialized directly after the Configuration module, but before all other +modules. It is important to update the Shared Module before any other module since its state may be +used within the logic of other modules, and it is important that the state is consistent across +them. + +## Session Change + +During a session change, the Shared Module receives and stores the current Session Index for that +block through the Session Change Notification. + +This information is used in the: + +* Configuration Module: For delaying updates to configurations until at lease one full session has + passed. +* Paras Module: For delaying updates to paras until at least one full session has passed. + +## Finalization + +The Shared Module currently has no finalization routines. + +## Functions + +* `scheduled_sessions() -> SessionIndex`: Return the next session index where updates to the + Parachains Runtime system would be safe to apply. +* `set_session_index(SessionIndex)`: For tests. Set the current session index in the Shared Module. diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index e9e3c3c234b7..5c26ef57db4a 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -88,6 +88,8 @@ decl_error! { ParathreadsRegistrationDisabled, /// The validation code provided doesn't start with the Wasm file magic string. DefinitelyNotWasm, + /// Cannot deregister para + CannotDeregister, } } @@ -113,22 +115,18 @@ decl_module! { ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); - let outgoing = >::outgoing_paras(); - - ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); - - ::Currency::reserve(&who, T::ParathreadDeposit::get())?; - >::insert(id, who); - - Paras::insert(id, false); - let genesis = ParaGenesisArgs { genesis_head, validation_code, parachain: false, }; + ensure!(paras::Module::::can_schedule_para_initialize(&id, &genesis), Error::::ParaAlreadyExists); + ::Currency::reserve(&who, T::ParathreadDeposit::get())?; - runtime_parachains::schedule_para_initialize::(id, genesis); + >::insert(id, who); + Paras::insert(id, false); + // Checked this shouldn't fail above. + let _ = runtime_parachains::schedule_para_initialize::(id, genesis); Ok(()) } @@ -146,14 +144,16 @@ decl_module! { ensure!(ParathreadsRegistrationEnabled::get(), Error::::ParathreadsRegistrationDisabled); - let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; + let is_parachain = Paras::get(id).ok_or(Error::::InvalidChainId)?; ensure!(!is_parachain, Error::::InvalidThreadId); + runtime_parachains::schedule_para_cleanup::(id).map_err(|_| Error::::CannotDeregister)?; + let debtor = >::take(id); let _ = ::Currency::unreserve(&debtor, T::ParathreadDeposit::get()); - - runtime_parachains::schedule_para_cleanup::(id); + Paras::remove(&id); + PendingSwap::remove(&id); Ok(()) } @@ -176,7 +176,6 @@ decl_module! { Ok(()) } - /// Swap a parachain with another parachain or parathread. The origin must be a `Parachain`. /// The swap will happen only if there is already an opposite swap pending. If there is not, /// the swap will be stored in the pending swaps map, ready for a later confirmatory swap. @@ -222,30 +221,27 @@ impl Module { ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); ensure!(validation_code.0.starts_with(WASM_MAGIC), Error::::DefinitelyNotWasm); - let outgoing = >::outgoing_paras(); - - ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); - - Paras::insert(id, true); - let genesis = ParaGenesisArgs { genesis_head, validation_code, parachain: true, }; - runtime_parachains::schedule_para_initialize::(id, genesis); + runtime_parachains::schedule_para_initialize::(id, genesis).map_err(|_| Error::::ParaAlreadyExists)?; + Paras::insert(id, true); Ok(()) } /// Deregister a parachain with the given ID. Must be called by root. pub fn deregister_parachain(id: ParaId) -> DispatchResult { - let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; + let is_parachain = Paras::get(id).ok_or(Error::::InvalidChainId)?; ensure!(is_parachain, Error::::InvalidChainId); - runtime_parachains::schedule_para_cleanup::(id); + runtime_parachains::schedule_para_cleanup::(id).map_err(|_| Error::::CannotDeregister)?; + Paras::remove(&id); + PendingSwap::remove(&id); Ok(()) } @@ -267,10 +263,13 @@ mod tests { use frame_system::limits; use frame_support::{ traits::{Randomness, OnInitialize, OnFinalize}, - assert_ok, parameter_types, + assert_ok, assert_noop, parameter_types, }; use keyring::Sr25519Keyring; - use runtime_parachains::{initializer, configuration, inclusion, session_info, scheduler, dmp, ump, hrmp}; + use runtime_parachains::{ + initializer, configuration, inclusion, session_info, scheduler, dmp, ump, hrmp, shared, + ParaLifecycle, + }; use frame_support::traits::OneSessionHandler; use crate::paras_registrar; @@ -309,7 +308,7 @@ mod tests { parameter_types! { pub const BlockHashCount: u32 = 250; pub BlockWeights: limits::BlockWeights = - limits::BlockWeights::with_sensible_defaults(4 * 1024 * 1024, NORMAL_RATIO); + frame_system::limits::BlockWeights::simple_max(1024); pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); } @@ -370,7 +369,7 @@ mod tests { } parameter_types! { - pub const Period: BlockNumber = 1; + pub const Period: BlockNumber = 3; pub const Offset: BlockNumber = 0; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; @@ -432,6 +431,8 @@ mod tests { type WeightInfo = (); } + impl shared::Config for Test {} + impl dmp::Config for Test {} impl ump::Config for Test { @@ -590,7 +591,7 @@ mod tests { Initializer::on_finalize(System::block_number()); } // Session change every 3 blocks. - if (b + 1) % 3 == 0 { + if (b + 1) % Period::get() == 0 { println!("New session at {}", System::block_number()); Initializer::on_new_session( false, @@ -601,6 +602,7 @@ mod tests { System::set_block_number(b + 1); println!("Initializing {}", System::block_number()); System::on_initialize(System::block_number()); + Session::on_initialize(System::block_number()); Initializer::on_initialize(System::block_number()); } } @@ -643,7 +645,7 @@ mod tests { assert_eq!(Balances::free_balance(3u64) + ParathreadDeposit::get(), orig_bal); assert_eq!(Balances::reserved_balance(3u64), ParathreadDeposit::get()); - run_to_block(3); + run_to_block(10); assert_ok!(Registrar::deregister_parachain(2u32.into())); @@ -690,10 +692,12 @@ mod tests { assert_ok!(Registrar::swap(runtime_parachains::Origin::Parachain(2u32.into()).into(), 8u32.into())); assert_ok!(Registrar::swap(runtime_parachains::Origin::Parachain(8u32.into()).into(), 2u32.into())); + run_to_block(15); + // Deregister a parathread that was originally a parachain assert_ok!(Registrar::deregister_parathread(runtime_parachains::Origin::Parachain(2u32.into()).into())); - run_to_block(12); + run_to_block(21); // Funds are correctly returned assert_eq!(Balances::free_balance(1), initial_1_balance); @@ -712,20 +716,26 @@ mod tests { WASM_MAGIC.to_vec().into(), )); - run_to_block(4); + // 2 session changes to fully onboard. + run_to_block(12); + + assert_eq!(Parachains::lifecycle(1u32.into()), Some(ParaLifecycle::Parachain)); assert_ok!(Registrar::deregister_parachain(1u32.into())); - run_to_block(5); + run_to_block(13); + + assert_eq!(Parachains::lifecycle(1u32.into()), Some(ParaLifecycle::OffboardingParachain)); - assert!(Registrar::register_parachain( + assert_noop!(Registrar::register_parachain( 1u32.into(), vec![1; 3].into(), WASM_MAGIC.to_vec().into(), - ).is_err()); + ), Error::::ParaAlreadyExists); + + // Need 2 session changes to see the effect, which takes place by block 13. + run_to_block(18); - // The session will be changed on the 6th block, as part of finalization. The change - // will be observed on the 7th. - run_to_block(7); + assert!(Parachains::lifecycle(1u32.into()).is_none()); assert_ok!(Registrar::register_parachain( 1u32.into(), vec![1; 3].into(), diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 796051a27ef5..313d3b09635f 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -40,11 +40,15 @@ decl_error! { pub enum Error for Module { /// The specified parachain or parathread is not registered. ParaDoesntExist, + /// The specified parachain or parathread is already registered. + ParaAlreadyExists, /// A DMP message couldn't be sent because it exceeds the maximum size allowed for a downward /// message. ExceedsMaxMessageSize, /// The validation code provided doesn't start with the Wasm file magic string. DefinitelyNotWasm, + /// Could not schedule para cleanup. + CouldntCleanup, } } @@ -62,7 +66,7 @@ decl_module! { ) -> DispatchResult { ensure_root(origin)?; ensure!(genesis.validation_code.0.starts_with(WASM_MAGIC), Error::::DefinitelyNotWasm); - runtime_parachains::schedule_para_initialize::(id, genesis); + runtime_parachains::schedule_para_initialize::(id, genesis).map_err(|_| Error::::ParaAlreadyExists)?; Ok(()) } @@ -70,7 +74,7 @@ decl_module! { #[weight = (1_000, DispatchClass::Operational)] pub fn sudo_schedule_para_cleanup(origin, id: ParaId) -> DispatchResult { ensure_root(origin)?; - runtime_parachains::schedule_para_cleanup::(id); + runtime_parachains::schedule_para_cleanup::(id).map_err(|_| Error::::CouldntCleanup)?; Ok(()) } diff --git a/runtime/parachains/src/configuration.rs b/runtime/parachains/src/configuration.rs index 4691c0059dde..d01e5fc872db 100644 --- a/runtime/parachains/src/configuration.rs +++ b/runtime/parachains/src/configuration.rs @@ -29,6 +29,7 @@ use frame_support::{ use parity_scale_codec::{Encode, Decode}; use frame_system::ensure_root; use sp_runtime::traits::Zero; +use crate::shared; /// All configuration of the runtime with respect to parachains and parathreads. #[derive(Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug)] @@ -231,14 +232,14 @@ impl HostConfiguration { } } -pub trait Config: frame_system::Config { } +pub trait Config: frame_system::Config + shared::Config { } decl_storage! { trait Store for Module as Configuration { /// The active configuration for the current session. ActiveConfig get(fn config) config(): HostConfiguration; /// Pending configuration (if any) for the next session. - PendingConfig: Option>; + PendingConfig: map hasher(twox_64_concat) SessionIndex => Option>; } add_extra_genesis { build(|config: &Self| { @@ -646,12 +647,21 @@ impl 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() { + pub(crate) fn initializer_on_new_session( + _validators: &[ValidatorId], + _queued: &[ValidatorId], + session_index: &SessionIndex, + ) { + if let Some(pending) = ::PendingConfig::take(session_index) { ::ActiveConfig::set(pending); } } + /// Return the session index that should be used for any future scheduled changes. + fn scheduled_session() -> SessionIndex { + shared::Module::::scheduled_session() + } + // NOTE: Explicitly tell rustc not to inline this because otherwise heuristics note the incoming // closure making it's attractive to inline. However, in this case, we will end up with lots of // duplicated code (making this function to show up in the top of heaviest functions) only for @@ -660,11 +670,12 @@ impl Module { fn update_config_member( updater: impl FnOnce(&mut HostConfiguration) -> bool, ) { - let pending = ::PendingConfig::get(); + let scheduled_session = Self::scheduled_session(); + let pending = ::PendingConfig::get(scheduled_session); let mut prev = pending.unwrap_or_else(Self::config); if updater(&mut prev) { - ::PendingConfig::set(Some(prev)); + ::PendingConfig::insert(scheduled_session, prev); } } } @@ -672,32 +683,32 @@ impl Module { #[cfg(test)] mod tests { use super::*; - use crate::mock::{new_test_ext, Initializer, Configuration, Origin}; + use crate::mock::{new_test_ext, Configuration, Origin}; - use frame_support::traits::{OnFinalize, OnInitialize}; + use frame_support::assert_ok; #[test] - fn config_changes_on_session_boundary() { + fn config_changes_after_2_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_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100)); assert_eq!(Configuration::config(), old_config); - assert_eq!(::PendingConfig::get(), Some(config.clone())); + assert_eq!(::PendingConfig::get(1), None); + + Configuration::initializer_on_new_session(&[], &[], &1); - Initializer::on_finalize(1); + assert_eq!(Configuration::config(), old_config); + assert_eq!(::PendingConfig::get(2), Some(config.clone())); - Configuration::initializer_on_new_session(&[], &[]); + Configuration::initializer_on_new_session(&[], &[], &2); assert_eq!(Configuration::config(), config); - assert!(::PendingConfig::get().is_none()); + assert_eq!(::PendingConfig::get(3), None); }) } @@ -743,7 +754,7 @@ mod tests { hrmp_max_message_num_per_candidate: 20, }; - assert!(::PendingConfig::get().is_none()); + assert!(::PendingConfig::get(shared::SESSION_DELAY).is_none()); Configuration::set_validation_upgrade_frequency( Origin::root(), new_config.validation_upgrade_frequency, @@ -865,7 +876,7 @@ mod tests { new_config.hrmp_max_message_num_per_candidate, ).unwrap(); - assert_eq!(::PendingConfig::get(), Some(new_config)); + assert_eq!(::PendingConfig::get(shared::SESSION_DELAY), Some(new_config)); }) } @@ -880,7 +891,7 @@ mod tests { 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()) + assert!(::PendingConfig::get(shared::SESSION_DELAY).is_none()) }); } diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 54357d773118..2c74a2315e7d 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -1111,9 +1111,9 @@ impl Module { mod tests { use super::*; use crate::mock::{ - new_test_ext, Test, Configuration, Paras, Hrmp, System, MockGenesisConfig, + new_test_ext, Test, Configuration, Paras, Shared, Hrmp, System, MockGenesisConfig, }; - use frame_support::{assert_err, traits::Currency as _}; + use frame_support::{assert_noop, assert_ok, traits::Currency as _}; use primitives::v1::BlockNumber; use std::collections::{BTreeMap, HashSet}; @@ -1127,15 +1127,18 @@ mod tests { // NOTE: this is in reverse initialization order. Hrmp::initializer_finalize(); Paras::initializer_finalize(); + Shared::initializer_finalize(); if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) { let notification = crate::initializer::SessionChangeNotification { prev_config: config.clone(), new_config: config.clone(), + session_index: Shared::session_index() + 1, ..Default::default() }; // NOTE: this is in initialization order. + Shared::initializer_on_new_session(¬ification); let outgoing_paras = Paras::initializer_on_new_session(¬ification); Hrmp::initializer_on_new_session(¬ification, &outgoing_paras); } @@ -1146,6 +1149,7 @@ mod tests { System::set_block_number(b + 1); // NOTE: this is in initialization order. + Shared::initializer_initialize(b + 1); Paras::initializer_initialize(b + 1); Hrmp::initializer_initialize(b + 1); } @@ -1217,14 +1221,14 @@ mod tests { } fn register_parachain_with_balance(id: ParaId, balance: Balance) { - Paras::schedule_para_initialize( + assert_ok!(Paras::schedule_para_initialize( id, crate::paras::ParaGenesisArgs { parachain: true, genesis_head: vec![1].into(), validation_code: vec![1].into(), }, - ); + )); ::Currency::make_free_balance_be(&id.into_account(), balance); } @@ -1233,7 +1237,7 @@ mod tests { } fn deregister_parachain(id: ParaId) { - Paras::schedule_para_cleanup(id); + assert_ok!(Paras::schedule_para_cleanup(id)); } fn channel_exists(sender: ParaId, recipient: ParaId) -> bool { @@ -1430,7 +1434,7 @@ mod tests { register_parachain(para_a); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); assert_storage_consistency_exhaustive(); @@ -1458,7 +1462,7 @@ mod tests { register_parachain(para_a); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); @@ -1497,7 +1501,7 @@ mod tests { register_parachain(para_a); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); @@ -1535,17 +1539,17 @@ mod tests { register_parachain(para_a); register_parachain(para_b); - run_to_block(1, Some(vec![1])); + run_to_block(2, Some(vec![1,2])); Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); - run_to_block(2, Some(vec![2])); + run_to_block(3, Some(vec![3])); let _ = Hrmp::queue_outbound_hrmp(para_a, vec![OutboundHrmpMessage { recipient: para_b, data: vec![1, 2, 3], }]); - run_to_block(3, None); + run_to_block(4, None); let _ = Hrmp::queue_outbound_hrmp(para_a, vec![OutboundHrmpMessage { recipient: para_b, data: vec![4, 5, 6], @@ -1554,7 +1558,7 @@ mod tests { assert_eq!( Hrmp::hrmp_mqc_heads(para_b), vec![ - (para_a, hex_literal::hex!["88dc00db8cc9d22aa62b87807705831f164387dfa49f80a8600ed1cbe1704b6b"].into()), + (para_a, hex_literal::hex!["a964fd3b4f3d3ce92a0e25e576b87590d92bb5cb7031909c7f29050e1f04a375"].into()), ], ); }); @@ -1569,13 +1573,13 @@ mod tests { register_parachain(para_a); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); deregister_parachain(para_a); - // On Block 6: session change. The channel should not be created. - run_to_block(6, Some(vec![6])); + // On Block 7: 2x session change. The channel should not be created. + run_to_block(7, Some(vec![6, 7])); assert!(!Paras::is_valid_para(para_a)); assert!(!channel_exists(para_a, para_b)); assert_storage_consistency_exhaustive(); @@ -1593,7 +1597,7 @@ mod tests { register_parachain(para_b); register_parachain(para_c); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); // Open two channels to the same receiver, b: // a -> b, c -> b @@ -1662,7 +1666,7 @@ mod tests { // request and accept that, and finally wait until the next session. register_parachain(para_a); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); run_to_block(8, Some(vec![8])); @@ -1728,9 +1732,9 @@ mod tests { new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { register_parachain_with_balance(para_a, 0); register_parachain(para_b); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); - assert_err!( + assert_noop!( Hrmp::init_open_channel(para_a, para_b, 2, 8), pallet_balances::Error::::InsufficientBalance ); @@ -1739,11 +1743,11 @@ mod tests { new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { register_parachain(para_a); register_parachain_with_balance(para_b, 0); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); - assert_err!( + assert_noop!( Hrmp::accept_open_channel(para_b, para_a), pallet_balances::Error::::InsufficientBalance ); @@ -1762,7 +1766,7 @@ mod tests { // Register two parachains funded with different amounts of funds and arrange a channel. register_parachain_with_balance(para_a, 100); register_parachain_with_balance(para_b, 110); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); assert_eq!( @@ -1810,7 +1814,7 @@ mod tests { // request but do not accept it. register_parachain_with_balance(para_a, 100); register_parachain_with_balance(para_b, 110); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); assert_eq!( ::Currency::free_balance(¶_a.into_account()), @@ -1849,7 +1853,7 @@ mod tests { // Register two parachains and open a channel between them. register_parachain_with_balance(para_a, 100); register_parachain_with_balance(para_b, 110); - run_to_block(5, Some(vec![5])); + run_to_block(5, Some(vec![4, 5])); Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); Hrmp::accept_open_channel(para_b, para_a).unwrap(); assert_eq!( @@ -1865,7 +1869,7 @@ mod tests { // Then deregister one parachain. deregister_parachain(para_a); - run_to_block(10, Some(vec![10])); + run_to_block(10, Some(vec![9, 10])); // The channel should be removed. assert!(!Paras::is_valid_para(para_a)); diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs index e88a66cd1acb..5692a95e7781 100644 --- a/runtime/parachains/src/inclusion.rs +++ b/runtime/parachains/src/inclusion.rs @@ -33,10 +33,9 @@ use frame_support::{ }; use parity_scale_codec::{Encode, Decode}; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; -use sp_staking::SessionIndex; use sp_runtime::{DispatchError, traits::{One, Saturating}}; -use crate::{configuration, paras, dmp, ump, hrmp, scheduler::CoreAssignment}; +use crate::{configuration, paras, dmp, ump, hrmp, shared, scheduler::CoreAssignment}; /// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding /// for any backed candidates referred to by a `1` bit available. @@ -111,6 +110,7 @@ pub trait RewardValidators { pub trait Config: frame_system::Config + + shared::Config + paras::Config + dmp::Config + ump::Config @@ -137,9 +137,6 @@ decl_storage! { /// The current validators, by their parachain session keys. Validators get(fn validators) config(validators): Vec; - - /// The current session index. - CurrentSessionIndex get(fn session_index): SessionIndex; } } @@ -236,7 +233,6 @@ impl Module { for _ in >::drain() { } Validators::set(notification.validators.clone()); // substrate forces us to clone, stupidly. - CurrentSessionIndex::set(notification.session_index); } /// Process a set of incoming bitfields. Return a vec of cores freed by candidates @@ -246,7 +242,7 @@ impl Module { core_lookup: impl Fn(CoreIndex) -> Option, ) -> Result, DispatchError> { let validators = Validators::get(); - let session_index = CurrentSessionIndex::get(); + let session_index = shared::Module::::session_index(); let config = >::config(); let parachains = >::parachains(); @@ -428,7 +424,7 @@ impl Module { let signing_context = SigningContext { parent_hash, - session_index: CurrentSessionIndex::get(), + session_index: shared::Module::::session_index(), }; // We combine an outer loop over candidates with an inner loop over the scheduled, @@ -917,7 +913,7 @@ mod tests { use sc_keystore::LocalKeystore; use crate::mock::{ new_test_ext, Configuration, Paras, System, Inclusion, - MockGenesisConfig, Test, + MockGenesisConfig, Test, Shared, }; use crate::initializer::SessionChangeNotification; use crate::configuration::HostConfiguration; @@ -1045,8 +1041,10 @@ mod tests { Inclusion::initializer_finalize(); Paras::initializer_finalize(); + Shared::initializer_finalize(); if let Some(notification) = new_session(b + 1) { + Shared::initializer_on_new_session(¬ification); Paras::initializer_on_new_session(¬ification); Inclusion::initializer_on_new_session(¬ification); } @@ -1056,6 +1054,7 @@ mod tests { System::on_initialize(b + 1); System::set_block_number(b + 1); + Shared::initializer_initialize(b + 1); Paras::initializer_initialize(b + 1); Inclusion::initializer_initialize(b + 1); } @@ -1218,7 +1217,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); let signing_context = SigningContext { parent_hash: System::parent_hash(), @@ -1425,7 +1424,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); let signing_context = SigningContext { parent_hash: System::parent_hash(), @@ -1589,7 +1588,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2076,7 +2075,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2273,7 +2272,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2370,7 +2369,7 @@ mod tests { new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); - CurrentSessionIndex::set(5); + shared::Module::::set_session_index(5); let validators_new = vec![ Sr25519Keyring::Alice, @@ -2434,7 +2433,7 @@ mod tests { run_to_block(11, |_| None); assert_eq!(Validators::get(), validator_public); - assert_eq!(CurrentSessionIndex::get(), 5); + assert_eq!(shared::Module::::session_index(), 5); assert!(>::get(&0).is_some()); assert!(>::get(&1).is_some()); @@ -2458,7 +2457,7 @@ mod tests { }); assert_eq!(Validators::get(), validator_public_new); - assert_eq!(CurrentSessionIndex::get(), 6); + assert_eq!(shared::Module::::session_index(), 6); assert!(>::get(&0).is_none()); assert!(>::get(&1).is_none()); diff --git a/runtime/parachains/src/initializer.rs b/runtime/parachains/src/initializer.rs index 4de05e96beec..8e437dc7c334 100644 --- a/runtime/parachains/src/initializer.rs +++ b/runtime/parachains/src/initializer.rs @@ -21,14 +21,14 @@ use sp_std::prelude::*; use frame_support::weights::Weight; -use primitives::v1::ValidatorId; +use primitives::v1::{ValidatorId, SessionIndex}; use frame_support::{ decl_storage, decl_module, decl_error, traits::{OneSessionHandler, Randomness}, }; use parity_scale_codec::{Encode, Decode}; use crate::{ configuration::{self, HostConfiguration}, - paras, scheduler, inclusion, session_info, dmp, ump, hrmp, + shared, paras, scheduler, inclusion, session_info, dmp, ump, hrmp, }; /// Information about a session change that has just occurred. @@ -45,7 +45,7 @@ pub struct SessionChangeNotification { /// A secure random seed for the session, gathered from BABE. pub random_seed: [u8; 32], /// New session index. - pub session_index: sp_staking::SessionIndex, + pub session_index: SessionIndex, } impl> Default for SessionChangeNotification { @@ -65,12 +65,13 @@ impl> Default for SessionChangeNotification, queued: Vec, - session_index: sp_staking::SessionIndex, + session_index: SessionIndex, } pub trait Config: frame_system::Config + configuration::Config + + shared::Config + paras::Config + scheduler::Config + inclusion::Config @@ -126,6 +127,7 @@ decl_module! { // - UMP // - HRMP let total_weight = configuration::Module::::initializer_initialize(now) + + shared::Module::::initializer_initialize(now) + paras::Module::::initializer_initialize(now) + scheduler::Module::::initializer_initialize(now) + inclusion::Module::::initializer_initialize(now) + @@ -148,6 +150,7 @@ decl_module! { inclusion::Module::::initializer_finalize(); scheduler::Module::::initializer_finalize(); paras::Module::::initializer_finalize(); + shared::Module::::initializer_finalize(); configuration::Module::::initializer_finalize(); // Apply buffered session changes as the last thing. This way the runtime APIs and the @@ -170,7 +173,7 @@ decl_module! { impl Module { fn apply_new_session( - session_index: sp_staking::SessionIndex, + session_index: SessionIndex, validators: Vec, queued: Vec, ) { @@ -186,7 +189,7 @@ impl Module { // We can't pass the new config into the thing that determines the new config, // so we don't pass the `SessionChangeNotification` into this module. - configuration::Module::::initializer_on_new_session(&validators, &queued); + configuration::Module::::initializer_on_new_session(&validators, &queued, &session_index); let new_config = >::config(); @@ -199,6 +202,7 @@ impl Module { session_index, }; + shared::Module::::initializer_on_new_session(¬ification); let outgoing_paras = paras::Module::::initializer_on_new_session(¬ification); scheduler::Module::::initializer_on_new_session(¬ification); inclusion::Module::::initializer_on_new_session(¬ification); @@ -212,7 +216,7 @@ impl Module { /// 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: sp_staking::SessionIndex, + session_index: SessionIndex, validators: I, queued: Option, ) @@ -361,10 +365,11 @@ mod tests { assert_ok!(Dmp::queue_downward_message(&Configuration::config(), b, vec![4, 5, 6])); assert_ok!(Dmp::queue_downward_message(&Configuration::config(), c, vec![7, 8, 9])); - Paras::schedule_para_cleanup(a); - Paras::schedule_para_cleanup(b); + assert_ok!(Paras::schedule_para_cleanup(a)); + assert_ok!(Paras::schedule_para_cleanup(b)); - Initializer::apply_new_session(1, vec![], vec![]); + // Apply session 2 in the future + Initializer::apply_new_session(2, vec![], vec![]); assert!(Dmp::dmq_contents(a).is_empty()); assert!(Dmp::dmq_contents(b).is_empty()); diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs index cdc62767cc65..8de3841cc474 100644 --- a/runtime/parachains/src/lib.rs +++ b/runtime/parachains/src/lib.rs @@ -23,6 +23,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod configuration; +pub mod shared; pub mod inclusion; pub mod inclusion_inherent; pub mod initializer; @@ -49,11 +50,11 @@ pub use paras::ParaLifecycle; pub fn schedule_para_initialize( id: primitives::v1::Id, genesis: paras::ParaGenesisArgs, -) { - >::schedule_para_initialize(id, genesis); +) -> Result<(), ()> { + >::schedule_para_initialize(id, genesis).map_err(|_| ()) } /// Schedule a para to be cleaned up at the start of the next session. -pub fn schedule_para_cleanup(id: primitives::v1::Id) { - >::schedule_para_cleanup(id); +pub fn schedule_para_cleanup(id: primitives::v1::Id) -> Result<(), ()> { + >::schedule_para_cleanup(id).map_err(|_| ()) } diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs index edaea92d2270..c791ab461032 100644 --- a/runtime/parachains/src/mock.rs +++ b/runtime/parachains/src/mock.rs @@ -27,7 +27,7 @@ use std::cell::RefCell; use std::collections::HashMap; use crate::{ inclusion, scheduler, dmp, ump, hrmp, session_info, paras, configuration, - initializer, + initializer, shared, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -43,6 +43,7 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Module, Call, Storage, Config, Event}, Paras: paras::{Module, Origin, Call, Storage, Config}, Configuration: configuration::{Module, Call, Storage, Config}, + Shared: shared::{Module, Call, Storage}, Inclusion: inclusion::{Module, Call, Storage, Event}, Scheduler: scheduler::{Module, Call, Storage}, Initializer: initializer::{Module, Call, Storage}, @@ -112,6 +113,8 @@ impl crate::initializer::Config for Test { impl crate::configuration::Config for Test { } +impl crate::shared::Config for Test { } + impl crate::paras::Config for Test { type Origin = Origin; } diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index 6f259154e198..f15284ea6c38 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -28,16 +28,16 @@ use sp_std::result; #[cfg(feature = "std")] use sp_std::marker::PhantomData; use primitives::v1::{ - Id as ParaId, ValidationCode, HeadData, + Id as ParaId, ValidationCode, HeadData, SessionIndex, }; -use sp_runtime::traits::One; +use sp_runtime::{traits::One, DispatchResult}; use frame_support::{ - decl_storage, decl_module, decl_error, + decl_storage, decl_module, decl_error, ensure, traits::Get, weights::Weight, }; use parity_scale_codec::{Encode, Decode}; -use crate::{configuration, initializer::SessionChangeNotification}; +use crate::{configuration, shared, initializer::SessionChangeNotification}; use sp_core::RuntimeDebug; #[cfg(feature = "std")] @@ -45,7 +45,11 @@ use serde::{Serialize, Deserialize}; pub use crate::Origin; -pub trait Config: frame_system::Config + configuration::Config { +pub trait Config: + frame_system::Config + + configuration::Config + + shared::Config +{ /// The outer origin type. type Origin: From + From<::Origin> @@ -93,6 +97,10 @@ enum UseCodeAt { } /// The possible states of a para, to take into account delayed lifecycle changes. +/// +/// If the para is in a "transition state", it is expected that the parachain is +/// queued in the `ActionsQueue` to transition it into a stable state. Its lifecycle +/// state will be used to determine the state transition to apply to the para. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum ParaLifecycle { /// Para is new and is onboarding as a Parathread or Parachain. @@ -102,36 +110,57 @@ pub enum ParaLifecycle { /// Para is a Parachain. Parachain, /// Para is a Parathread which is upgrading to a Parachain. - UpgradingToParachain, + UpgradingParathread, /// Para is a Parachain which is downgrading to a Parathread. - DowngradingToParathread, - /// Parathread is being offboarded. - OutgoingParathread, - /// Parachain is being offboarded. - OutgoingParachain, + DowngradingParachain, + /// Parathread is queued to be offboarded. + OffboardingParathread, + /// Parachain is queued to be offboarded. + OffboardingParachain, } impl ParaLifecycle { + /// Returns true if parachain is currently onboarding. To learn if the + /// parachain is onboarding as a parachain or parathread, look at the + /// `UpcomingGenesis` storage item. pub fn is_onboarding(&self) -> bool { matches!(self, ParaLifecycle::Onboarding) } + /// Returns true if para is in a stable state, i.e. it is currently + /// a parachain or parathread, and not in any transition state. pub fn is_stable(&self) -> bool { matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain) } + /// Returns true if para is currently treated as a parachain. + /// This also includes transitioning states, so you may want to combine + /// this check with `is_stable` if you specifically want `Paralifecycle::Parachain`. pub fn is_parachain(&self) -> bool { - matches!(self, ParaLifecycle::Parachain) + matches!(self, + ParaLifecycle::Parachain | + ParaLifecycle::DowngradingParachain | + ParaLifecycle::OffboardingParachain + ) } + /// Returns true if para is currently treated as a parathread. + /// This also includes transitioning states, so you may want to combine + /// this check with `is_stable` if you specifically want `Paralifecycle::Parathread`. pub fn is_parathread(&self) -> bool { - matches!(self, ParaLifecycle::Parathread) + matches!(self, + ParaLifecycle::Parathread | + ParaLifecycle::UpgradingParathread | + ParaLifecycle::OffboardingParathread + ) } - pub fn is_outgoing(&self) -> bool { - matches!(self, ParaLifecycle::OutgoingParathread | ParaLifecycle::OutgoingParachain) + /// Returns true if para is currently offboarding. + pub fn is_offboarding(&self) -> bool { + matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingParachain) } + /// Returns true if para is in any transitionary state. pub fn is_transitioning(&self) -> bool { !Self::is_stable(self) } @@ -251,18 +280,10 @@ decl_storage! { FutureCodeUpgrades get(fn future_code_upgrade_at): map hasher(twox_64_concat) ParaId => Option; /// The actual future code of a para. FutureCode: map hasher(twox_64_concat) ParaId => Option; - - /// Upcoming paras (chains and threads). These are only updated on session change. Corresponds to an - /// entry in the upcoming-genesis map. Ordered ascending by ParaId. - UpcomingParas get(fn upcoming_paras): Vec; + /// The actions to perform during the start of a specific session index. + ActionsQueue get(fn actions_queue): map hasher(twox_64_concat) SessionIndex => Vec; /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option; - /// Paras that are to be cleaned up at the end of the session. Ordered ascending by ParaId. - OutgoingParas get(fn outgoing_paras): Vec; - /// Existing Parathreads that should upgrade to be a Parachain. Ordered ascending by ParaId. - UpcomingUpgrades: Vec; - /// Existing Parachains that should downgrade to be a Parathread. Ordered ascending by ParaId. - UpcomingDowngrades: Vec; } add_extra_genesis { config(paras): Vec<(ParaId, ParaGenesisArgs)>; @@ -297,7 +318,18 @@ fn build(config: &GenesisConfig) { } decl_error! { - pub enum Error for Module { } + pub enum Error for Module { + /// Para is not registered in our system. + NotRegistered, + /// Para cannot be onboarded because it is already tracked by our system. + CannotOnboard, + /// Para cannot be offboarded at this time. + CannotOffboard, + /// Para cannot be upgraded to a parachain. + CannotUpgrade, + /// Para cannot be downgraded to a parathread. + CannotDowngrade, + } } decl_module! { @@ -318,109 +350,85 @@ impl Module { /// Called by the initializer to note that a new session has started. /// - /// Returns the list of outgoing parachains for this session. - pub(crate) fn initializer_on_new_session(_notification: &SessionChangeNotification) - -> Vec - { - let now = >::block_number(); - let (mut parachains, outgoing) = Self::clean_up_outgoing(now); - Self::apply_incoming(&mut parachains); - Self::apply_upgrades(&mut parachains); - Self::apply_downgrades(&mut parachains); - ::Parachains::set(parachains); - - outgoing + /// Returns the list of outgoing paras from the actions queue. + pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification) -> Vec { + let outgoing_paras = Self::apply_actions_queue(notification.session_index); + outgoing_paras } - /// Cleans up all outgoing paras. Returns the new set of parachains and any outgoing parachains. - fn clean_up_outgoing(now: T::BlockNumber) -> (Vec, Vec) { + // Apply all para actions queued for the given session index. + // + // The actions to take are based on the lifecycle of of the paras. + // + // The final state of any para after the actions queue should be as a + // parachain, parathread, or not registered. (stable states) + // + // Returns the list of outgoing paras from the actions queue. + fn apply_actions_queue(session: SessionIndex) -> Vec { + let actions = ActionsQueue::take(session); let mut parachains = ::Parachains::get(); - let outgoing = ::OutgoingParas::take(); - - for outgoing_para in &outgoing { - // Warn if there is a state error... but still perform the offboarding to be defensive. - if let Some(state) = ParaLifecycles::get(&outgoing_para) { - if !state.is_outgoing() { - frame_support::debug::error!( - target: "parachains", - "Outgoing parachain has wrong lifecycle state." - ) - } - }; - - if let Ok(i) = parachains.binary_search(&outgoing_para) { - parachains.remove(i); - } - - ::Heads::remove(&outgoing_para); - ::FutureCodeUpgrades::remove(&outgoing_para); - ::FutureCode::remove(&outgoing_para); - ParaLifecycles::remove(&outgoing_para); - - let removed_code = ::CurrentCode::take(&outgoing_para); - if let Some(removed_code) = removed_code { - Self::note_past_code(*outgoing_para, now, now, removed_code); - } - } - - (parachains, outgoing) - } - - /// Applies all incoming paras, updating the parachains list for those that are parachains. - fn apply_incoming(parachains: &mut Vec) { - let upcoming = ::UpcomingParas::take(); - for upcoming_para in upcoming { - if ParaLifecycles::get(&upcoming_para) != Some(ParaLifecycle::Onboarding) { - continue; - }; - - let genesis_data = match ::UpcomingParasGenesis::take(&upcoming_para) { - None => continue, - Some(g) => g, - }; - - if genesis_data.parachain { - if let Err(i) = parachains.binary_search(&upcoming_para) { - parachains.insert(i, upcoming_para); - } - ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parachain); - } else { - ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parathread); - } - - ::Heads::insert(&upcoming_para, genesis_data.genesis_head); - ::CurrentCode::insert(&upcoming_para, genesis_data.validation_code); - } - } + let now = >::block_number(); + let mut outgoing = Vec::new(); + + for para in actions { + let lifecycle = ParaLifecycles::get(¶); + match lifecycle { + None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { /* Nothing to do... */ }, + // Onboard a new parathread or parachain. + Some(ParaLifecycle::Onboarding) => { + if let Some(genesis_data) = ::UpcomingParasGenesis::take(¶) { + if genesis_data.parachain { + if let Err(i) = parachains.binary_search(¶) { + parachains.insert(i, para); + } + ParaLifecycles::insert(¶, ParaLifecycle::Parachain); + } else { + ParaLifecycles::insert(¶, ParaLifecycle::Parathread); + } - /// Take an existing parathread and upgrade it to be a parachain. - fn apply_upgrades(parachains: &mut Vec) { - let upgrades = UpcomingUpgrades::take(); - for para in upgrades { - ParaLifecycles::mutate(¶, |state| { - if *state == Some(ParaLifecycle::UpgradingToParachain) { + ::Heads::insert(¶, genesis_data.genesis_head); + ::CurrentCode::insert(¶, genesis_data.validation_code); + } + }, + // Upgrade a parathread to a parachain + Some(ParaLifecycle::UpgradingParathread) => { if let Err(i) = parachains.binary_search(¶) { parachains.insert(i, para); } - *state = Some(ParaLifecycle::Parachain); - } - }); - } - } - - /// Take an existing parachain and downgrade it to be a parathread. Update the list of parachains. - fn apply_downgrades(parachains: &mut Vec) { - let downgrades = UpcomingDowngrades::take(); - for para in downgrades { - ParaLifecycles::mutate(¶, |state| { - if *state == Some(ParaLifecycle::DowngradingToParathread) { + ParaLifecycles::insert(¶, ParaLifecycle::Parachain); + }, + // Downgrade a parachain to a parathread + Some(ParaLifecycle::DowngradingParachain) => { if let Ok(i) = parachains.binary_search(¶) { parachains.remove(i); } - *state = Some(ParaLifecycle::Parathread); - } - }); + ParaLifecycles::insert(¶, ParaLifecycle::Parathread); + }, + // Offboard a parathread or parachain from the system + Some(ParaLifecycle::OffboardingParachain) | Some(ParaLifecycle::OffboardingParathread) => { + if let Ok(i) = parachains.binary_search(¶) { + parachains.remove(i); + } + + ::Heads::remove(¶); + ::FutureCodeUpgrades::remove(¶); + ::FutureCode::remove(¶); + ParaLifecycles::remove(¶); + + let removed_code = ::CurrentCode::take(¶); + if let Some(removed_code) = removed_code { + Self::note_past_code(para, now, now, removed_code); + } + + outgoing.push(para); + }, + } } + + // Place the new parachains set in storage. + ::Parachains::set(parachains); + + return outgoing } // note replacement of the code of para with given `id`, which occured in the @@ -500,195 +508,98 @@ impl Module { T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done) } + /// Verify that `schedule_para_initialize` can be called successfully. + /// + /// Returns false if para is already registered in the system. + pub fn can_schedule_para_initialize(id: &ParaId, _: &ParaGenesisArgs) -> bool { + let lifecycle = ParaLifecycles::get(id); + lifecycle.is_none() + } + /// Schedule a para to be initialized at the start of the next session. /// - /// Noop if Para ID is already registered in the system with some `ParaLifecycle`. - pub(crate) fn schedule_para_initialize(id: ParaId, genesis: ParaGenesisArgs) -> Weight { - let mut weight = T::DbWeight::get().reads_writes(0, 0); + /// Will return error if para is already registered in the system. + pub(crate) fn schedule_para_initialize(id: ParaId, genesis: ParaGenesisArgs) -> DispatchResult { + let scheduled_session = Self::scheduled_session(); // Make sure parachain isn't already in our system. - if ParaLifecycles::contains_key(&id) { - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - return weight; - } + ensure!(Self::can_schedule_para_initialize(&id, &genesis), Error::::CannotOnboard); - let dup = UpcomingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => true, - Err(i) => { - v.insert(i, id); - false - } + ParaLifecycles::insert(&id, ParaLifecycle::Onboarding); + UpcomingParasGenesis::insert(&id, genesis); + ActionsQueue::mutate(scheduled_session, |v| { + if let Err(i) = v.binary_search(&id) { + v.insert(i, id); } }); - ParaLifecycles::insert(&id, ParaLifecycle::Onboarding); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - if dup { - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - return weight; - } - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - UpcomingParasGenesis::insert(&id, &genesis); - weight = weight.saturating_add(T::DbWeight::get().writes(2)); - - weight + Ok(()) } /// Schedule a para to be cleaned up at the start of the next session. /// - /// Noop if para is already outgoing or not known. - pub(crate) fn schedule_para_cleanup(id: ParaId) -> Weight { - match ParaLifecycles::get(&id) { - Some(ParaLifecycle::Onboarding) => { - UpcomingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(i) => { - v.remove(i); - UpcomingParasGenesis::remove(&id); - ParaLifecycles::remove(&id); - // If a para was only in the pending state it should not be moved to `Outgoing` - T::DbWeight::get().reads_writes(1, 3) - } - Err(_) => T::DbWeight::get().reads_writes(1, 0), - } - }) - }, - Some(ParaLifecycle::Parathread) => { - ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread); - OutgoingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => T::DbWeight::get().reads_writes(1, 1), - Err(i) => { - v.insert(i, id); - T::DbWeight::get().reads_writes(1, 2) - } - } - }) - - }, - Some(ParaLifecycle::Parachain) => { - OutgoingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => T::DbWeight::get().reads_writes(1, 0), - Err(i) => { - v.insert(i, id); - ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParachain); - T::DbWeight::get().reads_writes(1, 2) - } - } - }) - }, - Some(ParaLifecycle::UpgradingToParachain) => { - let upgrade_weight = UpcomingUpgrades::mutate(|v| { - match v.binary_search(&id) { - Ok(i) => { - v.remove(i); - T::DbWeight::get().reads_writes(1, 1) - }, - Err(_) => T::DbWeight::get().reads(1), - } - }); - let outgoing_weight = OutgoingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => T::DbWeight::get().reads_writes(1, 0), - Err(i) => { - v.insert(i, id); - ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread); - T::DbWeight::get().reads_writes(1, 2) - } - } - }); - upgrade_weight.saturating_add(outgoing_weight) + /// Will return error if para is not a stable parachain or parathread. + pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult { + let scheduled_session = Self::scheduled_session(); + let lifecycle = ParaLifecycles::get(&id).ok_or(Error::::NotRegistered)?; + + match lifecycle { + ParaLifecycle::Parathread => { + ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParathread); }, - Some(ParaLifecycle::DowngradingToParathread) => { - let downgrade_weight = UpcomingDowngrades::mutate(|v| { - match v.binary_search(&id) { - Ok(i) => { - v.remove(i); - T::DbWeight::get().reads_writes(1, 1) - }, - Err(_) => T::DbWeight::get().reads(1), - } - }); - let outgoing_weight = OutgoingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => T::DbWeight::get().reads_writes(1, 0), - Err(i) => { - v.insert(i, id); - ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread); - T::DbWeight::get().reads_writes(1, 2) - } - } - }); - downgrade_weight.saturating_add(outgoing_weight) + ParaLifecycle::Parachain => { + ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParachain); }, - None | - Some(ParaLifecycle::OutgoingParathread) | - Some(ParaLifecycle::OutgoingParachain) - => { T::DbWeight::get().reads(1) }, + _ => return Err(Error::::CannotOffboard)?, } + + ActionsQueue::mutate(scheduled_session, |v| { + if let Err(i) = v.binary_search(&id) { + v.insert(i, id); + } + }); + + Ok(()) } /// Schedule a parathread to be upgraded to a parachain. /// - /// Noop if `ParaLifecycle` is not `Parathread`. + /// Will return error if `ParaLifecycle` is not `Parathread`. #[allow(unused)] - pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> Weight { - if ParaLifecycles::get(&id) != Some(ParaLifecycle::Parathread) { - let weight = T::DbWeight::get().reads_writes(1, 0); - return weight; - } + pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult { + let scheduled_session = Self::scheduled_session(); + let lifecycle = ParaLifecycles::get(&id).ok_or(Error::::NotRegistered)?; - let dup = UpcomingUpgrades::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => true, - Err(i) => { - v.insert(i, id); - false - } + ensure!(lifecycle == ParaLifecycle::Parathread, Error::::CannotUpgrade); + + ParaLifecycles::insert(&id, ParaLifecycle::UpgradingParathread); + ActionsQueue::mutate(scheduled_session, |v| { + if let Err(i) = v.binary_search(&id) { + v.insert(i, id); } }); - ParaLifecycles::insert(&id, ParaLifecycle::UpgradingToParachain); - - if dup { - let weight = T::DbWeight::get().reads_writes(2, 1); - return weight; - } - - T::DbWeight::get().reads_writes(2, 2) + Ok(()) } /// Schedule a parachain to be downgraded to a parathread. /// /// Noop if `ParaLifecycle` is not `Parachain`. #[allow(unused)] - pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> Weight { - if ParaLifecycles::get(&id) != Some(ParaLifecycle::Parachain) { - let weight = T::DbWeight::get().reads_writes(1, 0); - return weight; - } + pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult { + let scheduled_session = Self::scheduled_session(); + let lifecycle = ParaLifecycles::get(&id).ok_or(Error::::NotRegistered)?; - let dup = UpcomingDowngrades::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => true, - Err(i) => { - v.insert(i, id); - false - } + ensure!(lifecycle == ParaLifecycle::Parachain, Error::::CannotDowngrade); + + ParaLifecycles::insert(&id, ParaLifecycle::DowngradingParachain); + ActionsQueue::mutate(scheduled_session, |v| { + if let Err(i) = v.binary_search(&id) { + v.insert(i, id); } }); - ParaLifecycles::insert(&id, ParaLifecycle::DowngradingToParathread); - - if dup { - let weight = T::DbWeight::get().reads_writes(2, 1); - return weight; - } - - T::DbWeight::get().reads_writes(2, 2) + Ok(()) } /// Schedule a future code upgrade of the given parachain, to be applied after inclusion @@ -794,9 +705,11 @@ impl Module { } /// Returns whether the given ID refers to a valid para. + /// + /// Paras that are onboarding or offboarding are not included. pub fn is_valid_para(id: ParaId) -> bool { if let Some(state) = ParaLifecycles::get(&id) { - !state.is_onboarding() && !state.is_outgoing() + !state.is_onboarding() && !state.is_offboarding() } else { false } @@ -835,29 +748,42 @@ impl Module { Self::past_code_meta(&id).most_recent_change() } + + /// Return the session index that should be used for any future scheduled changes. + fn scheduled_session() -> SessionIndex { + shared::Module::::scheduled_session() + } } #[cfg(test)] mod tests { use super::*; use primitives::v1::BlockNumber; - use frame_support::traits::{OnFinalize, OnInitialize}; + use frame_support::{ + assert_ok, + traits::{OnFinalize, OnInitialize} + }; - use crate::mock::{new_test_ext, Paras, System, MockGenesisConfig}; + use crate::mock::{new_test_ext, Paras, Shared, System, MockGenesisConfig}; use crate::configuration::HostConfiguration; fn run_to_block(to: BlockNumber, new_session: Option>) { while System::block_number() < to { let b = System::block_number(); Paras::initializer_finalize(); + Shared::initializer_finalize(); if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) { - Paras::initializer_on_new_session(&Default::default()); + let mut session_change_notification = SessionChangeNotification::default(); + session_change_notification.session_index = Shared::session_index() + 1; + Shared::initializer_on_new_session(&session_change_notification); + Paras::initializer_on_new_session(&session_change_notification); } System::on_finalize(b); System::on_initialize(b + 1); System::set_block_number(b + 1); + Shared::initializer_initialize(b + 1); Paras::initializer_initialize(b + 1); } } @@ -1316,11 +1242,14 @@ mod tests { expected_at }; - Paras::schedule_para_cleanup(para_id); + assert_ok!(Paras::schedule_para_cleanup(para_id)); // Just scheduling cleanup shouldn't change anything. { - assert_eq!(::OutgoingParas::get(), vec![para_id]); + assert_eq!( + ::ActionsQueue::get(Paras::scheduled_session()), + vec![para_id], + ); assert_eq!(Paras::parachains(), vec![para_id]); assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); @@ -1331,8 +1260,8 @@ mod tests { assert_eq!(::Heads::get(¶_id), Some(Default::default())); } - // run to block №4, with a session change at the end of the block 3. - run_to_block(4, Some(vec![4])); + // run to block #4, with a 2 session changes at the end of the block 2 & 3. + run_to_block(4, Some(vec![3,4])); // cleaning up the parachain should place the current parachain code // into the past code buffer & schedule cleanup. @@ -1366,34 +1295,37 @@ mod tests { let a = ParaId::from(999); let c = ParaId::from(333); - Paras::schedule_para_initialize( + assert_ok!(Paras::schedule_para_initialize( b, ParaGenesisArgs { parachain: true, genesis_head: vec![1].into(), validation_code: vec![1].into(), }, - ); + )); - Paras::schedule_para_initialize( + assert_ok!(Paras::schedule_para_initialize( a, ParaGenesisArgs { parachain: false, genesis_head: vec![2].into(), validation_code: vec![2].into(), }, - ); + )); - Paras::schedule_para_initialize( + assert_ok!(Paras::schedule_para_initialize( c, ParaGenesisArgs { parachain: true, genesis_head: vec![3].into(), validation_code: vec![3].into(), }, - ); + )); - assert_eq!(::UpcomingParas::get(), vec![c, b, a]); + assert_eq!( + ::ActionsQueue::get(Paras::scheduled_session()), + vec![c, b, a], + ); // Lifecycle is tracked correctly assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); @@ -1404,7 +1336,10 @@ mod tests { run_to_block(2, None); assert_eq!(Paras::parachains(), Vec::new()); - assert_eq!(::UpcomingParas::get(), vec![c, b, a]); + assert_eq!( + ::ActionsQueue::get(Paras::scheduled_session()), + vec![c, b, a], + ); // Lifecycle is tracked correctly assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); @@ -1412,10 +1347,11 @@ mod tests { assert_eq!(ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); - run_to_block(3, Some(vec![3])); + // Two sessions pass, so action queue is triggered + run_to_block(4, Some(vec![3,4])); assert_eq!(Paras::parachains(), vec![c, b]); - assert_eq!(::UpcomingParas::get(), Vec::new()); + assert_eq!(::ActionsQueue::get(Paras::scheduled_session()), Vec::new()); // Lifecycle is tracked correctly assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Parathread)); @@ -1428,81 +1364,6 @@ mod tests { }) } - #[test] - fn para_cleanup_removes_upcoming() { - new_test_ext(Default::default()).execute_with(|| { - run_to_block(1, None); - - let b = ParaId::from(525); - let a = ParaId::from(999); - let c = ParaId::from(333); - - Paras::schedule_para_initialize( - b, - ParaGenesisArgs { - parachain: true, - genesis_head: vec![1].into(), - validation_code: vec![1].into(), - }, - ); - - Paras::schedule_para_initialize( - a, - ParaGenesisArgs { - parachain: false, - genesis_head: vec![2].into(), - validation_code: vec![2].into(), - }, - ); - - Paras::schedule_para_initialize( - c, - ParaGenesisArgs { - parachain: true, - genesis_head: vec![3].into(), - validation_code: vec![3].into(), - }, - ); - - assert_eq!(::UpcomingParas::get(), vec![c, b, a]); - - // Lifecycle is tracked correctly - assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); - assert_eq!(ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); - assert_eq!(ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); - - - // run to block without session change. - run_to_block(2, None); - - assert_eq!(Paras::parachains(), Vec::new()); - assert_eq!(::UpcomingParas::get(), vec![c, b, a]); - - // Lifecycle is tracked correctly - assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); - assert_eq!(ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); - assert_eq!(ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); - - Paras::schedule_para_cleanup(c); - - run_to_block(3, Some(vec![3])); - - assert_eq!(Paras::parachains(), vec![b]); - assert_eq!(::OutgoingParas::get(), vec![]); - assert_eq!(::UpcomingParas::get(), Vec::new()); - assert!(::UpcomingParasGenesis::get(a).is_none()); - - // Lifecycle is tracked correctly - assert_eq!(ParaLifecycles::get(&a), Some(ParaLifecycle::Parathread)); - assert_eq!(ParaLifecycles::get(&b), Some(ParaLifecycle::Parachain)); - assert_eq!(ParaLifecycles::get(&c), None); - - assert_eq!(Paras::current_code(&a), Some(vec![2].into())); - assert_eq!(Paras::current_code(&b), Some(vec![1].into())); - assert!(Paras::current_code(&c).is_none()); - }); - } - #[test] fn code_at_with_intermediate() { let acceptance_period = 10; diff --git a/runtime/parachains/src/runtime_api_impl/v1.rs b/runtime/parachains/src/runtime_api_impl/v1.rs index d4437d20a9c4..da10aa36410c 100644 --- a/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/runtime/parachains/src/runtime_api_impl/v1.rs @@ -28,7 +28,7 @@ use primitives::v1::{ InboundDownwardMessage, InboundHrmpMessage, Hash, }; use frame_support::debug; -use crate::{initializer, inclusion, scheduler, configuration, paras, session_info, dmp, hrmp}; +use crate::{initializer, inclusion, scheduler, configuration, paras, session_info, dmp, hrmp, shared}; /// Implementation for the `validators` function of the runtime API. pub fn validators() -> Vec { @@ -228,7 +228,7 @@ pub fn session_index_for_child() -> SessionIndex { // // Incidentally, this is also the rationale for why it is OK to query validators or // occupied cores or etc. and expect the correct response "for child". - >::session_index() + >::session_index() } /// Implementation for the `validation_code` function of the runtime API. diff --git a/runtime/parachains/src/scheduler.rs b/runtime/parachains/src/scheduler.rs index f8e46cffd017..b8639c3ae961 100644 --- a/runtime/parachains/src/scheduler.rs +++ b/runtime/parachains/src/scheduler.rs @@ -731,19 +731,31 @@ impl Module { } } + #[cfg(test)] mod tests { use super::*; - use primitives::v1::{BlockNumber, ValidatorId, CollatorId}; - use frame_support::traits::{OnFinalize, OnInitialize}; + use primitives::v1::{BlockNumber, ValidatorId, CollatorId, SessionIndex}; + use frame_support::{ + assert_ok, + traits::{OnFinalize, OnInitialize}, + }; use keyring::Sr25519Keyring; - use crate::mock::{new_test_ext, Configuration, Paras, System, Scheduler, MockGenesisConfig}; + use crate::mock::{new_test_ext, Configuration, Paras, Shared, System, Scheduler, MockGenesisConfig}; use crate::initializer::SessionChangeNotification; use crate::configuration::HostConfiguration; use crate::paras::ParaGenesisArgs; + fn schedule_blank_para(id: ParaId, is_chain: bool) { + assert_ok!(Paras::schedule_para_initialize(id, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: is_chain, + })); + } + fn run_to_block( to: BlockNumber, new_session: impl Fn(BlockNumber) -> Option>, @@ -755,8 +767,13 @@ mod tests { Paras::initializer_finalize(); if let Some(notification) = new_session(b + 1) { - Paras::initializer_on_new_session(¬ification); - Scheduler::initializer_on_new_session(¬ification); + let mut notification_with_session_index = notification; + // We will make every session change trigger an action queue. Normally this may require 2 or more session changes. + if notification_with_session_index.session_index == SessionIndex::default() { + notification_with_session_index.session_index = Shared::scheduled_session(); + } + Paras::initializer_on_new_session(¬ification_with_session_index); + Scheduler::initializer_on_new_session(¬ification_with_session_index); } System::on_finalize(b); @@ -816,11 +833,7 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); new_test_ext(genesis_config).execute_with(|| { - Paras::schedule_para_initialize(thread_id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); + schedule_blank_para(thread_id, false); assert!(!Paras::is_parathread(thread_id)); @@ -895,11 +908,7 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); new_test_ext(genesis_config).execute_with(|| { - Paras::schedule_para_initialize(thread_id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); + schedule_blank_para(thread_id, false); assert!(!Paras::is_parathread(thread_id)); @@ -935,23 +944,9 @@ mod tests { // threads a, b, and c will be live in next session, but not d. { - Paras::schedule_para_initialize(thread_a, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); - - Paras::schedule_para_initialize(thread_b, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); - - Paras::schedule_para_initialize(thread_c, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); + schedule_blank_para(thread_a, false); + schedule_blank_para(thread_b, false); + schedule_blank_para(thread_c, false); } // set up a queue as if n_cores was 4 and with some with many retries. @@ -1041,16 +1036,9 @@ mod tests { let chain_b = ParaId::from(2); // ensure that we have 5 groups by registering 2 parachains. - Paras::schedule_para_initialize(chain_a, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); - Paras::schedule_para_initialize(chain_b, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); + schedule_blank_para(chain_a, true); + schedule_blank_para(chain_b, true); + run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -1107,21 +1095,10 @@ mod tests { let chain_c = ParaId::from(3); // ensure that we have 5 groups by registering 2 parachains. - Paras::schedule_para_initialize(chain_a, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); - Paras::schedule_para_initialize(chain_b, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); - Paras::schedule_para_initialize(chain_c, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: false, - }); + schedule_blank_para(chain_a, true); + schedule_blank_para(chain_b, true); + schedule_blank_para(chain_c, false); + run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -1170,12 +1147,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -1285,12 +1256,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -1442,12 +1407,6 @@ mod tests { let chain_b = ParaId::from(2); let chain_c = ParaId::from(3); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -1552,12 +1511,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -1630,12 +1583,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -1695,12 +1642,6 @@ mod tests { let chain_a = ParaId::from(1); let thread_a = ParaId::from(2); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { schedule_blank_para(chain_a, true); schedule_blank_para(thread_a, false); @@ -1798,12 +1739,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { schedule_blank_para(thread_a, false); schedule_blank_para(thread_b, false); @@ -1879,12 +1814,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { schedule_blank_para(thread_a, false); schedule_blank_para(thread_b, false); @@ -1966,12 +1895,6 @@ mod tests { let chain_a = ParaId::from(1); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { schedule_blank_para(chain_a, true); @@ -2029,12 +1952,6 @@ mod tests { let chain_a = ParaId::from(1); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { schedule_blank_para(chain_a, true); @@ -2093,16 +2010,9 @@ mod tests { let chain_b = ParaId::from(2); // ensure that we have 5 groups by registering 2 parachains. - Paras::schedule_para_initialize(chain_a, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); - Paras::schedule_para_initialize(chain_b, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: true, - }); + schedule_blank_para(chain_a, true); + schedule_blank_para(chain_b, true); + run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -2127,7 +2037,7 @@ mod tests { let groups = ValidatorGroups::get(); assert_eq!(groups.len(), 5); - Paras::schedule_para_cleanup(chain_b); + assert_ok!(Paras::schedule_para_cleanup(chain_b)); run_to_end_of_block(2, |number| match number { 2 => Some(SessionChangeNotification { @@ -2179,12 +2089,6 @@ mod tests { let collator = CollatorId::from(Sr25519Keyring::Alice.public()); - let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { - genesis_head: Vec::new().into(), - validation_code: Vec::new().into(), - parachain: is_chain, - }); - new_test_ext(genesis_config).execute_with(|| { assert_eq!(default_config().parathread_cores, 3); @@ -2210,7 +2114,7 @@ mod tests { run_to_block(2, |_| None); assert_eq!(Scheduler::scheduled().len(), 2); - Paras::schedule_para_cleanup(thread_a); + assert_ok!(Paras::schedule_para_cleanup(thread_a)); // start a new session to activate, 5 validators for 5 cores. run_to_block(3, |number| match number { diff --git a/runtime/parachains/src/session_info.rs b/runtime/parachains/src/session_info.rs index 12facbea05d9..bc675c86aec1 100644 --- a/runtime/parachains/src/session_info.rs +++ b/runtime/parachains/src/session_info.rs @@ -166,7 +166,7 @@ mod tests { use super::*; use crate::mock::{ new_test_ext, Configuration, SessionInfo, System, MockGenesisConfig, - Origin, + Origin, Shared, }; use crate::initializer::SessionChangeNotification; use crate::configuration::HostConfiguration; @@ -181,10 +181,16 @@ mod tests { let b = System::block_number(); SessionInfo::initializer_finalize(); + Shared::initializer_finalize(); Configuration::initializer_finalize(); if let Some(notification) = new_session(b + 1) { - Configuration::initializer_on_new_session(¬ification.validators, ¬ification.queued); + Configuration::initializer_on_new_session( + ¬ification.validators, + ¬ification.queued, + ¬ification.session_index, + ); + Shared::initializer_on_new_session(¬ification); SessionInfo::initializer_on_new_session(¬ification); } @@ -194,6 +200,7 @@ mod tests { System::set_block_number(b + 1); Configuration::initializer_initialize(b + 1); + Shared::initializer_initialize(b + 1); SessionInfo::initializer_initialize(b + 1); } } @@ -218,24 +225,13 @@ mod tests { } fn session_changes(n: BlockNumber) -> Option> { - match n { - 100 => Some(SessionChangeNotification { - session_index: 10, + if n % 10 == 0 { + Some(SessionChangeNotification { + session_index: n / 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, + }) + } else { + None } } @@ -249,29 +245,55 @@ mod tests { #[test] fn session_pruning_is_based_on_dispute_period() { new_test_ext(genesis_config()).execute_with(|| { - let default_info = primitives::v1::SessionInfo::default(); - Sessions::insert(9, default_info); + // Dispute period starts at 2 + let config = Configuration::config(); + assert_eq!(config.dispute_period, 2); + + // Move to session 10 run_to_block(100, session_changes); - // but the first session change is not based on dispute_period - assert_eq!(EarliestStoredSession::get(), 10); - // and we didn't prune the last changes + // Earliest stored session is 10 - 2 = 8 + assert_eq!(EarliestStoredSession::get(), 8); + // Pruning works as expected + assert!(Sessions::get(7).is_none()); + assert!(Sessions::get(8).is_some()); assert!(Sessions::get(9).is_some()); // changing dispute_period works let dispute_period = 5; Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap(); + + // Dispute period does not automatically change + let config = Configuration::config(); + assert_eq!(config.dispute_period, 2); + // Two sessions later it will though + run_to_block(120, session_changes); + let config = Configuration::config(); + assert_eq!(config.dispute_period, 5); + run_to_block(200, session_changes); assert_eq!(EarliestStoredSession::get(), 20 - dispute_period); - // we don't have that many sessions stored + // Increase dispute period even more let new_dispute_period = 16; Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap(); + + run_to_block(210, session_changes); + assert_eq!(EarliestStoredSession::get(), 21 - dispute_period); + + // Two sessions later it kicks in + run_to_block(220, session_changes); + let config = Configuration::config(); + assert_eq!(config.dispute_period, 16); + // Earliest session stays the same + assert_eq!(EarliestStoredSession::get(), 21 - dispute_period); + + // We still don't have enough stored sessions to start pruning run_to_block(300, session_changes); - assert_eq!(EarliestStoredSession::get(), 20 - dispute_period); + assert_eq!(EarliestStoredSession::get(), 21 - dispute_period); // now we do - run_to_block(400, session_changes); - assert_eq!(EarliestStoredSession::get(), 40 - new_dispute_period); + run_to_block(420, session_changes); + assert_eq!(EarliestStoredSession::get(), 42 - new_dispute_period); }) } @@ -284,8 +306,9 @@ mod tests { // 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(); + // 2 sessions later + run_to_block(3, new_session_every_block); + let session = Sessions::get(&3).unwrap(); assert_eq!(session.needed_approvals, 42); }) } diff --git a/runtime/parachains/src/shared.rs b/runtime/parachains/src/shared.rs new file mode 100644 index 000000000000..b7b874fbd497 --- /dev/null +++ b/runtime/parachains/src/shared.rs @@ -0,0 +1,79 @@ +// Copyright 2021 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 . + +//! A module for any shared state that other pallets may want access to. +//! +//! To avoid cyclic dependencies, it is important that this module is not +//! dependent on any of the other modules. + +use primitives::v1::SessionIndex; +use frame_support::{ + decl_storage, decl_module, decl_error, + weights::Weight, +}; +use crate::initializer::SessionChangeNotification; + +pub trait Config: frame_system::Config { } + +// `SESSION_DELAY` is used to delay any changes to Paras registration or configurations. +// Wait until the session index is 2 larger then the current index to apply any changes, +// which guarantees that at least one full session has passed before any changes are applied. +pub(crate) const SESSION_DELAY: SessionIndex = 2; + +decl_storage! { + trait Store for Module as ParasShared { + /// The current session index. + CurrentSessionIndex get(fn session_index): SessionIndex; + } +} + +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; + } +} + +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. + /// + /// Returns the list of outgoing paras from the actions queue. + pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification) { + CurrentSessionIndex::set(notification.session_index); + } + + /// Return the session index that should be used for any future scheduled changes. + pub (crate) fn scheduled_session() -> SessionIndex { + Self::session_index().saturating_add(SESSION_DELAY) + } + + #[cfg(test)] + pub(crate) fn set_session_index(index: SessionIndex) { + CurrentSessionIndex::set(index); + } +} diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 6b6ab712b1cb..9ecd1be37959 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -65,6 +65,7 @@ use runtime_common::{paras_sudo_wrapper, paras_registrar}; use runtime_parachains::origin as parachains_origin; use runtime_parachains::configuration as parachains_configuration; +use runtime_parachains::shared as parachains_shared; use runtime_parachains::inclusion as parachains_inclusion; use runtime_parachains::inclusion_inherent as parachains_inclusion_inherent; use runtime_parachains::initializer as parachains_initializer; @@ -187,6 +188,7 @@ construct_runtime! { // Parachains modules. ParachainsOrigin: parachains_origin::{Module, Origin}, ParachainsConfiguration: parachains_configuration::{Module, Call, Storage, Config}, + Shared: parachains_shared::{Module, Call, Storage}, Inclusion: parachains_inclusion::{Module, Call, Storage, Event}, InclusionInherent: parachains_inclusion_inherent::{Module, Call, Storage, Inherent}, Scheduler: parachains_scheduler::{Module, Call, Storage}, @@ -500,6 +502,8 @@ impl parachains_origin::Config for Runtime {} impl parachains_configuration::Config for Runtime {} +impl parachains_shared::Config for Runtime {} + /// Special `RewardValidators` that does nothing ;) pub struct RewardValidators; impl runtime_parachains::inclusion::RewardValidators for RewardValidators { diff --git a/runtime/rococo/src/propose_parachain.rs b/runtime/rococo/src/propose_parachain.rs index b4ac09beb26a..7350d0c0e988 100644 --- a/runtime/rococo/src/propose_parachain.rs +++ b/runtime/rococo/src/propose_parachain.rs @@ -134,6 +134,8 @@ decl_error! { DefinitelyNotWasm, /// Registration requires at least one validator. AtLeastOneValidatorRequired, + /// Couldn't schedule parachain cleanup. + CouldntCleanup, } } @@ -195,11 +197,7 @@ decl_module! { ensure!(validators.len() > 0, Error::::AtLeastOneValidatorRequired); ensure!(!Proposals::::contains_key(¶_id), Error::::ParachainIdAlreadyProposed); ensure!( - !runtime_parachains::paras::Module::::parachains().contains(¶_id), - Error::::ParachainIdAlreadyTaken, - ); - ensure!( - !runtime_parachains::paras::Module::::upcoming_paras().contains(¶_id), + runtime_parachains::paras::Module::::lifecycle(para_id).is_none(), Error::::ParachainIdAlreadyTaken, ); ensure!(validation_code.0.starts_with(runtime_common::WASM_MAGIC), Error::::DefinitelyNotWasm); @@ -289,10 +287,10 @@ decl_module! { if let Some(who) = who { ensure!(who == info.proposer, Error::::NotAuthorized); } + runtime_parachains::schedule_para_cleanup::(para_id).map_err(|_| Error::::CouldntCleanup)?; ParachainInfo::::remove(¶_id); info.validators.into_iter().for_each(|v| ValidatorsToRetire::::append(v)); - runtime_parachains::schedule_para_cleanup::(para_id); pallet_balances::Module::::unreserve(&info.proposer, T::ProposeDeposit::get()); } @@ -354,7 +352,8 @@ impl pallet_session::SessionManager for Module { parachain: true, }; - runtime_parachains::schedule_para_initialize::(*id, genesis); + // Not much we can do if this fails... + let _ = runtime_parachains::schedule_para_initialize::(*id, genesis); validators.extend(proposal.validators); } diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 11e47862c7e7..9d6e28655b22 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -26,6 +26,7 @@ use sp_std::collections::btree_map::BTreeMap; use parity_scale_codec::Encode; use polkadot_runtime_parachains::configuration as parachains_configuration; +use polkadot_runtime_parachains::shared as parachains_shared; use polkadot_runtime_parachains::inclusion as parachains_inclusion; use polkadot_runtime_parachains::inclusion_inherent as parachains_inclusion_inherent; use polkadot_runtime_parachains::initializer as parachains_initializer; @@ -448,6 +449,8 @@ impl pallet_sudo::Config for Runtime { impl parachains_configuration::Config for Runtime {} +impl parachains_shared::Config for Runtime {} + impl parachains_inclusion::Config for Runtime { type Event = Event; type RewardValidators = RewardValidatorsWithEraPoints;