Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Scheduler Module #1162

Merged
merged 40 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
af77a9a
scheduler module skeleton
rphmeier May 28, 2020
d2eb247
update scheduler skeleton to match latest version of guide
rphmeier Jun 2, 2020
631a6ba
better session change notification
rphmeier Jun 2, 2020
cdf114b
add mock randomness and fix test compilation
rphmeier Jun 2, 2020
b2a8c42
shuffle validators into groups
rphmeier Jun 2, 2020
d6d751d
finish implementing session change logic for scheduler
rphmeier Jun 2, 2020
ba8dc9f
tweak core assignment type to track retries of parathread
rphmeier Jun 3, 2020
be800dd
reframe queued parathread core as offset
rphmeier Jun 3, 2020
cad53e6
implement initialzation and finalization routines
rphmeier Jun 3, 2020
eb64044
implement parathread claim queuing
rphmeier Jun 3, 2020
8b4cfd4
implement core_para
rphmeier Jun 3, 2020
ff2e4d7
implement the group_validators routine and fix errors
rphmeier Jun 3, 2020
35a3692
add a reason for freeing cores
rphmeier Jun 3, 2020
adbaa1d
implement `schedule` function
rphmeier Jun 3, 2020
e5c1651
Merge branch 'master' into rh-para-scheduler
rphmeier Jun 6, 2020
6a11337
add some docs to the scheduled function
rphmeier Jun 6, 2020
c63a94b
implement `occupied` helper
rphmeier Jun 6, 2020
1c6d4ab
implement availability predicate
rphmeier Jun 6, 2020
83ba678
fix some warnings
rphmeier Jun 6, 2020
d9df378
integrate scheduler into initializer
rphmeier Jun 6, 2020
3e7ac7b
integrate scheduler into mock module
rphmeier Jun 6, 2020
1bee255
avoid conflict with Substrate's scheduler storage
rphmeier Jun 6, 2020
9096c14
add parathreads index to paras module
rphmeier Jun 6, 2020
242b129
implement parathreads map in paras module
rphmeier Jun 6, 2020
94cb169
add is_parathread to paras
rphmeier Jun 6, 2020
55ad79d
test adding parathread claim
rphmeier Jun 6, 2020
2703687
test that you cannot add claims when no parathread cores exist
rphmeier Jun 6, 2020
f707371
check session change parathread queue pruning
rphmeier Jun 6, 2020
40d39eb
test validator shuffling
rphmeier Jun 6, 2020
19f6d53
add allow_unused to scheduler items
rphmeier Jun 7, 2020
f5ef2fa
add test for scheduling
rphmeier Jun 7, 2020
2d52e05
add some more tests for scheduling logic
rphmeier Jun 7, 2020
56e8669
test core rotation
rphmeier Jun 7, 2020
a909ad8
check parathread claim pruning after retries
rphmeier Jun 7, 2020
89ad0f8
add bound notes
rphmeier Jun 7, 2020
f85ff4e
Apply suggestions from code review
rphmeier Jun 10, 2020
50965e8
more suggestions from review
rphmeier Jun 10, 2020
3918713
test availability predicate, add box to please compiler
rphmeier Jun 11, 2020
3392398
Merge branch 'master' into rh-para-scheduler
rphmeier Jun 11, 2020
63dfb9c
add changes to guide
rphmeier Jun 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 30 additions & 12 deletions roadmap/implementors-guide/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ Storage layout:
```rust
/// All parachains. Ordered ascending by ParaId. Parathreads are not included.
Parachains: Vec<ParaId>,
/// All parathreads.
Parathreads: map ParaId => Option<()>,
/// The head-data of every registered para.
Heads: map ParaId => Option<HeadData>;
/// The validation code of every live para.
Expand Down Expand Up @@ -496,6 +498,7 @@ OutgoingParas: Vec<ParaId>;
1. Clean up outgoing paras. 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` map to reflect changes in registered parathreads.
rphmeier marked this conversation as resolved.
Show resolved Hide resolved

#### Initialization

Expand All @@ -508,6 +511,7 @@ OutgoingParas: Vec<ParaId>;
* `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<BlockNumber>)`: 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.

#### Finalization

Expand Down Expand Up @@ -605,25 +609,37 @@ struct ParathreadEntry {
// A queued parathread entry, pre-assigned to a core.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use doc comments /// instead of //

struct QueuedParathread {
claim: ParathreadEntry,
core: CoreIndex,
// this value is between 0 and config.parathread_cores.
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
core_offset: u32,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In several places in this document, you remove a CoreIndex and insert something named *_offset: u32. However, down in scheduler.rs, you define a CoreIndex type anyway. This leaves me confused: is the type still necessary, just not in these places, or have the documentation and implementation gotten out of sync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are offsets, not core indices, so it would break an invariant that CoreIndex always refers to an actual core index, if that makes sense. I believe the implementation and guide are coherent on this.

}

struct ParathreadQueue {
queue: Vec<QueuedParathread>,
// this value is between 0 and config.parathread_cores
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
next_core: CoreIndex,
next_core_offset: u32,
}

enum CoreOccupied {
Parathread(ParathreadEntry), // claim & retries
Parachain,
}

enum AssignmentKind {
Parachain,
Parathread(CollatorId, u32),
}

struct CoreAssignment {
core: CoreIndex,
para_id: ParaId,
collator: Option<CollatorId>,
group_idx: GroupIndex,
core: CoreIndex,
para_id: ParaId,
kind: AssignmentKind,
group_idx: GroupIndex,
}

// reasons a core might be freed.
enum FreedReason {
Concluded,
TimedOut,
}
```

Expand Down Expand Up @@ -662,6 +678,7 @@ Actions:
- First, we obtain "shuffled validators" `SV` by shuffling the validators using the `SessionChangeNotification`'s random seed.
- The groups are selected by partitioning `SV`. The first V % N groups will have (V / N) + 1 members, while the remaining groups will have (V / N) members each.
1. Prune the parathread queue to remove all retries beyond `configuration.parathread_retries`.
- Also prune all parathread claims corresponding to de-registered parathreads.
- all pruned claims should have their entry removed from the parathread index.
- assign all non-pruned claims to new cores if the number of parathread cores has changed between the `new_config` and `old_config` of the `SessionChangeNotification`.
- Assign claims in equal balance across all cores if rebalancing, and set the `next_core` of the `ParathreadQueue` by incrementing the relative index of the last assigned core and taking it modulo the number of parathread cores.
Expand All @@ -684,15 +701,16 @@ Actions:
- `next_core` is then updated by adding 1 and taking it modulo `config.parathread_cores`.
- The claim is then added to the claim index.

* `schedule(Vec<CoreIndex>)`: schedule new core assignments, with a parameter indicating previously-occupied cores which are to be considered returned.
* `schedule(Vec<(CoreIndex, FreedReason)>)`: schedule new core assignments, with a parameter indicating previously-occupied cores which are to be considered returned and why they are being returned.
- All freed parachain cores should be assigned to their respective parachain
- All freed parathread cores should have the claim removed from the claim index.
- All freed parathread cores should have the claim removed from the claim index, if the reason for freeing was `FreedReason::Concluded`
- All freed parathread cores should have the claim added to the parathread queue again without retries incremented, if the reason for freeing was `FreedReason::TimedOut`.
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
- All freed parathread cores should take the next parathread entry from the queue.
- The i'th validator group will be assigned to the `(i+k)%n`'th core at any point in time, where `k` is the number of rotations that have occurred in the session, and `n` is the total number of cores. This makes upcoming rotations within the same session predictable.
* `scheduled() -> Vec<CoreAssignment>`: Get currently scheduled core assignments.
* `occupied(Vec<CoreIndex>). Note that the given cores have become occupied.
- Fails if any given cores were not scheduled.
- Fails if the given cores are not sorted ascending by core index
- Behavior undefined if any given cores were not scheduled.
- Behavior undefined if the given cores are not sorted ascending by core index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that we may not wish to pay for runtime checks of these properties, but it feels like a warning flag when we introduce the possibility of UB; those tend to be the difficult bugs to discover, when they occur. Maybe we could introduce a compilation feature like ub-checks which does do the appropriate runtime checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends what the runtime checks are.

When within the runtime, panicking is pretty much the worst thing you can do. Contrary to typical software, you don't want to fail fast and fail hard - you want to limp along in broken state and get fixed by governance.

Checks that print to console and return as no-op would be welcome.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we'd ever run with ub-checks enabled on a "real" node. Instead, they'd be a debugging tool, maybe in CI, just to ensure that the properties we expect haven't been violated.

Copy link
Contributor Author

@rphmeier rphmeier Jun 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, although I don't perceive this as a blocker for the PR, better as a follow-on that would make a good first issue for someone

- This clears them from `Scheduled` and marks each corresponding `core` in the `AvailabilityCores` as occupied.
- Since both the availability cores and the newly-occupied cores lists are sorted ascending, this method can be implemented efficiently.
* `core_para(CoreIndex) -> ParaId`: return the currently-scheduled or occupied ParaId for the given core.
Expand Down Expand Up @@ -792,8 +810,8 @@ Included: Option<()>,
#### Entry Points

* `inclusion`: This entry-point accepts two parameters: [`Bitfields`](#Signed-Availability-Bitfield) and [`BackedCandidates`](#Backed-Candidate).
1. The `Bitfields` are first forwarded to the `process_bitfields` routine, returning a set of freed cores. Provide a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine.
1. If `Scheduler::availability_timeout_predicate` is `Some`, invoke `Inclusion::collect_pending` using it, and add timed-out cores to the free cores.
1. The `Bitfields` are first forwarded to the `process_bitfields` routine, returning a set of freed cores. Provide a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine. Annotate each of these freed cores with `FreedReason::Concluded`.
1. If `Scheduler::availability_timeout_predicate` is `Some`, invoke `Inclusion::collect_pending` using it, and add timed-out cores to the free cores, annotated with `FreedReason::TimedOut`.
1. Invoke `Scheduler::schedule(freed)`
1. Pass the `BackedCandidates` along with the output of `Scheduler::scheduled` to the `Inclusion::process_candidates` routine, getting a list of all newly-occupied cores.
1. Call `Scheduler::occupied` for all scheduled cores where a backed candidate was submitted.
Expand Down
3 changes: 3 additions & 0 deletions runtime/parachains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch =
primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false }
libsecp256k1 = { version = "0.3.2", default-features = false, optional = true }

rand = { version = "0.7", default-features = false }
rand_chacha = { version = "0.2.2", default-features = false }

[dev-dependencies]
hex-literal = "0.2.1"
keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate", branch = "master" }
Expand Down
4 changes: 2 additions & 2 deletions runtime/parachains/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use system::ensure_root;
/// All configuration of the runtime with respect to parachains and parathreads.
#[derive(Clone, Encode, Decode, PartialEq, Default)]
#[cfg_attr(test, derive(Debug))]
pub struct HostConfiguration<BlockNumber: Default> {
pub struct HostConfiguration<BlockNumber> {
/// The minimum frequency at which parachains can update their validation code.
pub validation_upgrade_frequency: BlockNumber,
/// The delay, in blocks, before a validation upgrade is applied.
Expand All @@ -49,7 +49,7 @@ pub struct HostConfiguration<BlockNumber: Default> {
pub parathread_cores: u32,
/// The number of retries that a parathread author has to submit their block.
pub parathread_retries: u32,
/// How often parachain groups should be rotated across parachains.
/// How often parachain groups should be rotated across parachains. Must be non-zero.
pub parachain_rotation_frequency: BlockNumber,
/// The availability period, in blocks, for parachains. This is the amount of blocks
/// after inclusion that validators have to make the block available and signal its availability to
Expand Down
56 changes: 51 additions & 5 deletions runtime/parachains/src/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,29 @@ use primitives::{
parachain::{ValidatorId},
};
use frame_support::{
decl_storage, decl_module, decl_error,
decl_storage, decl_module, decl_error, traits::Randomness,
};
use crate::{configuration, paras};
use crate::{configuration::{self, HostConfiguration}, paras, scheduler};

/// Information about a session change that has just occurred.
#[derive(Default, Clone)]
pub struct SessionChangeNotification<BlockNumber> {
/// The new validators in the session.
pub validators: Vec<ValidatorId>,
/// The qeueud validators for the following session.
pub queued: Vec<ValidatorId>,
/// The configuration before handling the session change
pub prev_config: HostConfiguration<BlockNumber>,
/// The configuration after handling the session change.
pub new_config: HostConfiguration<BlockNumber>,
/// A secure random seed for the session, gathered from BABE.
pub random_seed: [u8; 32],
}

pub trait Trait: system::Trait + configuration::Trait + paras::Trait { }
pub trait Trait: system::Trait + configuration::Trait + paras::Trait + scheduler::Trait {
/// A randomness beacon.
type Randomness: Randomness<Self::Hash>;
}

decl_storage! {
trait Store for Module<T: Trait> as Initializer {
Expand Down Expand Up @@ -62,14 +80,18 @@ decl_module! {
// - Inclusion
// - Validity
let total_weight = configuration::Module::<T>::initializer_initialize(now) +
paras::Module::<T>::initializer_initialize(now);
paras::Module::<T>::initializer_initialize(now) +
scheduler::Module::<T>::initializer_initialize(now);

HasInitialized::set(Some(()));

total_weight
}

fn on_finalize() {
// reverse initialization order.

scheduler::Module::<T>::initializer_finalize();
paras::Module::<T>::initializer_finalize();
configuration::Module::<T>::initializer_finalize();
HasInitialized::take();
Expand All @@ -90,8 +112,32 @@ impl<T: Trait> Module<T> {
let validators: Vec<_> = validators.map(|(_, v)| v).collect();
let queued: Vec<_> = queued.map(|(_, v)| v).collect();

let prev_config = <configuration::Module<T>>::config();

let random_seed = {
let mut buf = [0u8; 32];
let random_hash = T::Randomness::random(&b"paras"[..]);
let len = sp_std::cmp::min(32, random_hash.as_ref().len());
buf[..len].copy_from_slice(&random_hash.as_ref()[..len]);
buf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care that buf always has 32-len trailing 0s?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope! In practice hash is always 32 bytes, and 0-padding it is pretty much the best option if the hash is smaller in some case. It doesn't lose us any security in any case.

In the case that the hash type is larger (again, unlikely), 32 random bytes are more than enough for our purposes.

};

// 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::<T>::initializer_on_new_session(&validators, &queued);
paras::Module::<T>::initializer_on_new_session(&validators, &queued);

let new_config = <configuration::Module<T>>::config();

let notification = SessionChangeNotification {
validators,
queued,
prev_config,
new_config,
random_seed,
};

paras::Module::<T>::initializer_on_new_session(&notification);
scheduler::Module::<T>::initializer_on_new_session(&notification);
}
}

Expand Down
19 changes: 17 additions & 2 deletions runtime/parachains/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use primitives::{
};
use frame_support::{
impl_outer_origin, impl_outer_dispatch, parameter_types,
weights::Weight,
weights::Weight, traits::Randomness as RandomnessT,
};

/// A test runtime struct.
Expand All @@ -47,6 +47,14 @@ impl_outer_dispatch! {
}
}

pub struct TestRandomness;

impl RandomnessT<H256> for TestRandomness {
fn random(_subject: &[u8]) -> H256 {
Default::default()
}
}

parameter_types! {
pub const BlockHashCount: u32 = 250;
pub const MaximumBlockWeight: Weight = 4 * 1024 * 1024;
Expand Down Expand Up @@ -80,12 +88,16 @@ impl system::Trait for Test {
type OnKilledAccount = ();
}

impl crate::initializer::Trait for Test { }
impl crate::initializer::Trait for Test {
type Randomness = TestRandomness;
}

impl crate::configuration::Trait for Test { }

impl crate::paras::Trait for Test { }

impl crate::scheduler::Trait for Test { }

pub type System = system::Module<Test>;

/// Mocked initializer.
Expand All @@ -97,6 +109,9 @@ pub type Configuration = crate::configuration::Module<Test>;
/// Mocked paras.
pub type Paras = crate::paras::Module<Test>;

/// Mocked scheduler.
pub type Scheduler = crate::scheduler::Module<Test>;

/// Create a new set of test externalities.
pub fn new_test_ext(state: GenesisConfig) -> TestExternalities {
let mut t = state.system.build_storage::<Test>().unwrap();
Expand Down
31 changes: 24 additions & 7 deletions runtime/parachains/src/paras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ use sp_std::prelude::*;
use sp_std::marker::PhantomData;
use sp_runtime::traits::One;
use primitives::{
parachain::{ValidatorId, Id as ParaId, ValidationCode, HeadData},
parachain::{Id as ParaId, ValidationCode, HeadData},
};
use frame_support::{
decl_storage, decl_module, decl_error,
traits::Get,
weights::Weight,
};
use codec::{Encode, Decode};
use crate::configuration;
use crate::{configuration, initializer::SessionChangeNotification};

#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
Expand Down Expand Up @@ -159,18 +159,20 @@ impl<N: Ord + Copy> ParaPastCodeMeta<N> {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct ParaGenesisArgs {
/// The initial head data to use.
genesis_head: HeadData,
pub genesis_head: HeadData,
/// The initial validation code to use.
validation_code: ValidationCode,
pub validation_code: ValidationCode,
/// True if parachain, false if parathread.
parachain: bool,
pub parachain: bool,
}


decl_storage! {
trait Store for Module<T: Trait> as Paras {
/// All parachains. Ordered ascending by ParaId. Parathreads are not included.
Parachains get(fn parachains): Vec<ParaId>;
/// All parathreads.
Parathreads: map hasher(twox_64_concat) ParaId => Option<()>;
/// The head-data of every registered para.
Heads get(fn parachain_head): map hasher(twox_64_concat) ParaId => Option<HeadData>;
/// The validation code of every live para.
Expand Down Expand Up @@ -253,7 +255,7 @@ impl<T: Trait> Module<T> {
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]) {
pub(crate) fn initializer_on_new_session(_notification: &SessionChangeNotification<T::BlockNumber>) {
let now = <system::Module<T>>::block_number();
let mut parachains = Self::clean_up_outgoing(now);
Self::apply_incoming(&mut parachains);
Expand All @@ -268,6 +270,8 @@ impl<T: Trait> Module<T> {
for outgoing_para in outgoing {
if let Ok(i) = parachains.binary_search(&outgoing_para) {
parachains.remove(i);
} else {
<Self as Store>::Parathreads::remove(&outgoing_para);
}

<Self as Store>::Heads::remove(&outgoing_para);
Expand Down Expand Up @@ -296,6 +300,8 @@ impl<T: Trait> Module<T> {
if let Err(i) = parachains.binary_search(&upcoming_para) {
parachains.insert(i, upcoming_para);
}
} else {
<Self as Store>::Parathreads::insert(&upcoming_para, ());
}

<Self as Store>::Heads::insert(&upcoming_para, genesis_data.genesis_head);
Expand Down Expand Up @@ -515,6 +521,11 @@ impl<T: Trait> Module<T> {
}
}
}

/// Whether a para ID corresponds to any live parathread.
pub(crate) fn is_parathread(id: &ParaId) -> bool {
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
Parathreads::get(&id).is_some()
}
}

#[cfg(test)]
Expand All @@ -536,7 +547,7 @@ mod tests {
System::set_block_number(b + 1);

if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
Paras::initializer_on_new_session(&[], &[]);
Paras::initializer_on_new_session(&Default::default());
}
Paras::initializer_initialize(b + 1);
}
Expand Down Expand Up @@ -1040,18 +1051,24 @@ mod tests {
);

assert_eq!(<Paras as Store>::UpcomingParas::get(), vec![c, b, a]);
assert!(<Paras as Store>::Parathreads::get(&a).is_none());


// run to block without session change.
run_to_block(2, None);

assert_eq!(Paras::parachains(), Vec::new());
assert_eq!(<Paras as Store>::UpcomingParas::get(), vec![c, b, a]);
assert!(<Paras as Store>::Parathreads::get(&a).is_none());


run_to_block(3, Some(vec![3]));

assert_eq!(Paras::parachains(), vec![c, b]);
assert_eq!(<Paras as Store>::UpcomingParas::get(), Vec::new());

assert!(<Paras as Store>::Parathreads::get(&a).is_some());

assert_eq!(Paras::current_code(&a), Some(vec![2].into()));
assert_eq!(Paras::current_code(&b), Some(vec![1].into()));
assert_eq!(Paras::current_code(&c), Some(vec![3].into()));
Expand Down
Loading