diff --git a/polkadot/roadmap/implementers-guide/src/runtime/paras.md b/polkadot/roadmap/implementers-guide/src/runtime/paras.md index 0958c88d51..acf7e319ff 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/paras.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/paras.md @@ -1,12 +1,16 @@ # 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 of bits required for the availability bitfields does not change except at session boundaries. +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. -It's also responsible for managing parachain validation code upgrades as well as maintaining availability of old parachain code and its pruning. +It's also responsible for managing parachain validation code upgrades as well as maintaining +availability of old parachain code and its pruning. ## Storage -Utility structs: +### Utility Structs ```rust // the two key times necessary to track for every code replacement. @@ -49,15 +53,67 @@ struct ParaGenesisArgs { /// True if parachain, false if parathread. parachain: bool, } + +/// The possible states of a para, to take into account delayed lifecycle changes. +pub enum ParaLifecycle { + /// A Para is new and is onboarding. + Onboarding, + /// Para is a Parathread. + Parathread, + /// Para is a Parachain. + Parachain, + /// Para is a Parathread which is upgrading to a Parachain. + UpgradingToParachain, + /// Para is a Parachain which is downgrading to a Parathread. + DowngradingToParathread, + /// Parathread is being offboarded. + OutgoingParathread, + /// Parachain is being offboarded. + OutgoingParachain, +} ``` -Storage layout: +#### Para Lifecycle + +Because the state of parachains and parathreads are delayed by a session, we track the specific +state of the para using the `ParaLifecycle` enum. + +``` +None Parathread Parachain + + + + + | | | + | (Session Delay) | | + | | | + +----------------------->+ | + | Onboarding | | + | | | + +-------------------------------------------------->+ + | Onboarding | | + | | | + | +------------------------->+ + | | UpgradingToParachain | + | | | + | +<-------------------------+ + | | DowngradingToParathread | + | | | + |<-----------------------+ | + | OutgoingParathread | | + | | | + +<--------------------------------------------------+ + | | OutgoingParachain | + | | | + + + + +``` + +During the transition period, the para object is still considered in its existing state. + +### Storage Layout ```rust /// All parachains. Ordered ascending by ParaId. Parathreads are not included. Parachains: Vec, -/// All parathreads. -Parathreads: map ParaId => Option<()>, +/// The current lifecycle state of all known Para Ids. +ParaLifecycle: map ParaId => Option, /// The head-data of every registered para. Heads: map ParaId => Option; /// The validation code of every live para. @@ -83,38 +139,75 @@ FutureCodeUpgrades: map ParaId => Option; 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. +/// entry in the upcoming-genesis map. Ordered ascending by ParaId. UpcomingParas: Vec; /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map ParaId => Option; -/// Paras that are to be cleaned up at the end of the session. +/// 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 to reflect changes in registered parachains. -1. Amend the `Parathreads` set to reflect changes in registered parathreads. + 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`. ## Initialization -1. Do pruning based on all entries in `PastCodePruning` with `BlockNumber <= now`. Update the corresponding `PastCodeMeta` and `PastCode` accordingly. +1. Do pruning based on all entries in `PastCodePruning` with `BlockNumber <= now`. Update the + corresponding `PastCodeMeta` and `PastCode` accordingly. ## Routines -* `schedule_para_initialize(ParaId, ParaGenesisArgs)`: schedule a para to be initialized at the next session. -* `schedule_para_cleanup(ParaId)`: schedule a para to be cleaned up at the next session. -* `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`. -* `note_new_head(ParaId, HeadData, BlockNumber)`: note that a para has progressed to a new head, where the new head was executed in the context of a relay-chain block with given number. This will apply pending code upgrades based on the block number provided. -* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: Fetches the validation code to be used when validating a block in the context of the given relay-chain height. A second block number parameter may be used to tell the lookup to proceed as if an intermediate parablock has been included at the given relay-chain height. This may return past, 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`. -* `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread. -* `is_valid_para(ParaId) -> bool`: Returns true if the para ID references either a live parathread or live parachain. - -* `last_code_upgrade(id: ParaId, include_future: bool) -> Option`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number. -* `persisted_validation_data(id: ParaId) -> Option`: Get the PersistedValidationData of the given para, assuming the context is the parent block. Returns `None` if the para is not known. +* `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_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`. +* `note_new_head(ParaId, HeadData, BlockNumber)`: note that a para has progressed to a new head, + where the new head was executed in the context of a relay-chain block with given number. This will + apply pending code upgrades based on the block number provided. +* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: Fetches + the validation code to be used when validating a block in the context of the given relay-chain + height. A second block number parameter may be used to tell the lookup to proceed as if an + intermediate parablock has been included at the given relay-chain height. This may return past, + 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_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 + or live parachain. +* `last_code_upgrade(id: ParaId, include_future: bool) -> Option`: The block number of + the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. + This is the `expected_at` number, not the `activated_at` number. +* `persisted_validation_data(id: ParaId) -> Option`: Get the + PersistedValidationData of the given para, assuming the context is the parent block. Returns + `None` if the para is not known. ## Finalization diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index f7cf7cf335..e2afff71d6 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -43,6 +43,7 @@ mod util; mod mock; pub use origin::{Origin, ensure_parachain}; +pub use paras::ParaLifecycle; /// Schedule a para to be initialized at the start of the next session with the given genesis data. pub fn schedule_para_initialize( diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs index a0d5ad7d27..0e1b968ac4 100644 --- a/polkadot/runtime/parachains/src/paras.rs +++ b/polkadot/runtime/parachains/src/paras.rs @@ -92,6 +92,51 @@ enum UseCodeAt { ReplacedAt(N), } +/// The possible states of a para, to take into account delayed lifecycle changes. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub enum ParaLifecycle { + /// Para is new and is onboarding as a Parathread or Parachain. + Onboarding, + /// Para is a Parathread. + Parathread, + /// Para is a Parachain. + Parachain, + /// Para is a Parathread which is upgrading to a Parachain. + UpgradingToParachain, + /// Para is a Parachain which is downgrading to a Parathread. + DowngradingToParathread, + /// Parathread is being offboarded. + OutgoingParathread, + /// Parachain is being offboarded. + OutgoingParachain, +} + +impl ParaLifecycle { + pub fn is_onboarding(&self) -> bool { + matches!(self, ParaLifecycle::Onboarding) + } + + pub fn is_stable(&self) -> bool { + matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain) + } + + pub fn is_parachain(&self) -> bool { + matches!(self, ParaLifecycle::Parachain) + } + + pub fn is_parathread(&self) -> bool { + matches!(self, ParaLifecycle::Parathread) + } + + pub fn is_outgoing(&self) -> bool { + matches!(self, ParaLifecycle::OutgoingParathread | ParaLifecycle::OutgoingParachain) + } + + pub fn is_transitioning(&self) -> bool { + !Self::is_stable(self) + } +} + impl ParaPastCodeMeta { // note a replacement has occurred at a given block number. fn note_replacement(&mut self, expected_at: N, activated_at: N) { @@ -180,8 +225,8 @@ decl_storage! { trait Store for Module as Paras { /// All parachains. Ordered ascending by ParaId. Parathreads are not included. Parachains get(fn parachains): Vec; - /// All parathreads. - Parathreads: map hasher(twox_64_concat) ParaId => Option<()>; + /// The current lifecycle of a all known Para IDs. + ParaLifecycles: map hasher(twox_64_concat) ParaId => Option; /// The head-data of every registered para. Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option; /// The validation code of every live para. @@ -208,13 +253,16 @@ decl_storage! { 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. + /// entry in the upcoming-genesis map. Ordered ascending by ParaId. UpcomingParas get(fn upcoming_paras): 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. + /// 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)>; @@ -240,6 +288,11 @@ fn build(config: &GenesisConfig) { for (id, genesis_args) in &config.paras { as Store>::CurrentCode::insert(&id, &genesis_args.validation_code); as Store>::Heads::insert(&id, &genesis_args.genesis_head); + if genesis_args.parachain { + ParaLifecycles::insert(&id, ParaLifecycle::Parachain); + } else { + ParaLifecycles::insert(&id, ParaLifecycle::Parathread); + } } } @@ -268,6 +321,8 @@ impl Module { let now = >::block_number(); let mut parachains = Self::clean_up_outgoing(now); Self::apply_incoming(&mut parachains); + Self::apply_upgrades(&mut parachains); + Self::apply_downgrades(&mut parachains); ::Parachains::set(parachains); } @@ -277,15 +332,24 @@ impl Module { 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); - } else { - ::Parathreads::remove(&outgoing_para); } ::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 { @@ -300,6 +364,10 @@ impl Module { 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, @@ -309,8 +377,9 @@ impl Module { if let Err(i) = parachains.binary_search(&upcoming_para) { parachains.insert(i, upcoming_para); } + ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parachain); } else { - ::Parathreads::insert(&upcoming_para, ()); + ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parathread); } ::Heads::insert(&upcoming_para, genesis_data.genesis_head); @@ -318,6 +387,36 @@ impl Module { } } + /// 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) { + 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) { + if let Ok(i) = parachains.binary_search(¶) { + parachains.remove(i); + } + *state = Some(ParaLifecycle::Parathread); + } + }); + } + } + // note replacement of the code of para with given `id`, which occured in the // context of the given relay-chain block number. provide the replaced code. // @@ -396,7 +495,17 @@ impl Module { } /// 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); + + // 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; + } + let dup = UpcomingParas::mutate(|v| { match v.binary_search(&id) { Ok(_) => true, @@ -406,42 +515,174 @@ impl Module { } } }); + 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 + } + + /// 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) + }, + 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) + }, + None | + Some(ParaLifecycle::OutgoingParathread) | + Some(ParaLifecycle::OutgoingParachain) + => { T::DbWeight::get().reads(1) }, + } + } + + /// Schedule a parathread to be upgraded to a parachain. + /// + /// Noop 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; } - UpcomingParasGenesis::insert(&id, &genesis); - - T::DbWeight::get().reads_writes(1, 2) - } - - /// Schedule a para to be cleaned up at the start of the next session. - pub(crate) fn schedule_para_cleanup(id: ParaId) -> Weight { - let upcoming_weight = UpcomingParas::mutate(|v| { + let dup = UpcomingUpgrades::mutate(|v| { match v.binary_search(&id) { - Ok(i) => { - v.remove(i); - UpcomingParasGenesis::remove(id); - // If a para was only in the pending state it should not be moved to `Outgoing` - return T::DbWeight::get().reads_writes(2, 2); - } - Err(_) => T::DbWeight::get().reads_writes(1, 0), - } - }); - - let outgoing_weight = OutgoingParas::mutate(|v| { - match v.binary_search(&id) { - Ok(_) => T::DbWeight::get().reads_writes(1, 0), + Ok(_) => true, Err(i) => { v.insert(i, id); - T::DbWeight::get().reads_writes(1, 1) + false } } }); - outgoing_weight + upcoming_weight + 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) + } + + /// 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; + } + + let dup = UpcomingDowngrades::mutate(|v| { + match v.binary_search(&id) { + Ok(_) => true, + Err(i) => { + v.insert(i, id); + false + } + } + }); + + 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) } /// Schedule a future code upgrade of the given parachain, to be applied after inclusion @@ -541,15 +782,40 @@ impl Module { } } + /// Returns the current lifecycle state of the para. + pub fn lifecycle(id: ParaId) -> Option { + ParaLifecycles::get(&id) + } + /// Returns whether the given ID refers to a valid para. pub fn is_valid_para(id: ParaId) -> bool { - Self::parachains().binary_search(&id).is_ok() - || Self::is_parathread(id) + if let Some(state) = ParaLifecycles::get(&id) { + !state.is_onboarding() && !state.is_outgoing() + } else { + false + } + } + + /// Whether a para ID corresponds to any live parachain. + /// + /// Includes parachains which will downgrade to a parathread in the future. + pub fn is_parachain(id: ParaId) -> bool { + if let Some(state) = ParaLifecycles::get(&id) { + state.is_parachain() + } else { + false + } } /// Whether a para ID corresponds to any live parathread. - pub(crate) fn is_parathread(id: ParaId) -> bool { - Parathreads::get(&id).is_some() + /// + /// Includes parathreads which will upgrade to parachains in the future. + pub fn is_parathread(id: ParaId) -> bool { + if let Some(state) = ParaLifecycles::get(&id) { + state.is_parathread() + } else { + false + } } /// The block number of the last scheduled upgrade of the requested para. Includes future upgrades @@ -1122,15 +1388,22 @@ mod tests { ); assert_eq!(::UpcomingParas::get(), vec![c, b, a]); - assert!(::Parathreads::get(&a).is_none()); + // 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]); - assert!(::Parathreads::get(&a).is_none()); + + // 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(3, Some(vec![3])); @@ -1138,7 +1411,10 @@ mod tests { assert_eq!(Paras::parachains(), vec![c, b]); assert_eq!(::UpcomingParas::get(), Vec::new()); - assert!(::Parathreads::get(&a).is_some()); + // 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), Some(ParaLifecycle::Parachain)); assert_eq!(Paras::current_code(&a), Some(vec![2].into())); assert_eq!(Paras::current_code(&b), Some(vec![1].into())); @@ -1183,7 +1459,11 @@ mod tests { ); assert_eq!(::UpcomingParas::get(), vec![c, b, a]); - assert!(::Parathreads::get(&a).is_none()); + + // 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. @@ -1191,7 +1471,11 @@ mod tests { assert_eq!(Paras::parachains(), Vec::new()); assert_eq!(::UpcomingParas::get(), vec![c, b, a]); - assert!(::Parathreads::get(&a).is_none()); + + // 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); @@ -1202,7 +1486,10 @@ mod tests { assert_eq!(::UpcomingParas::get(), Vec::new()); assert!(::UpcomingParasGenesis::get(a).is_none()); - assert!(::Parathreads::get(&a).is_some()); + // 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()));