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 all 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.

5 changes: 2 additions & 3 deletions roadmap/implementors-guide/src/runtime/inclusioninherent.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ Included: Option<()>,
## Entry Points

* `inclusion`: This entry-point accepts two parameters: [`Bitfields`](/type-definitions.html#signed-availability-bitfield) and [`BackedCandidates`](/type-definitions.html#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.
1. If all of the above succeeds, set `Included` to `Some(())`.
4 changes: 4 additions & 0 deletions roadmap/implementors-guide/src/runtime/paras.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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 @@ -94,6 +96,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` set to reflect changes in registered parathreads.

## Initialization

Expand All @@ -106,6 +109,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
33 changes: 23 additions & 10 deletions roadmap/implementors-guide/src/runtime/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,38 @@ struct ParathreadEntry {

// A queued parathread entry, pre-assigned to a core.
struct QueuedParathread {
claim: ParathreadEntry,
core: CoreIndex,
claim: ParathreadEntry,
/// offset within the set of para-threads ranged `0..config.parathread_cores`.
core_offset: u32,
}

struct ParathreadQueue {
queue: Vec<QueuedParathread>,
// this value is between 0 and config.parathread_cores
next_core: CoreIndex,
queue: Vec<QueuedParathread>,
/// offset within the set of para-threads ranged `0..config.parathread_cores`.
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>,
kind: AssignmentKind,
group_idx: GroupIndex,
}
// reasons a core might be freed.
enum FreedReason {
Concluded,
TimedOut,
}
```

Storage layout:
Expand Down Expand Up @@ -150,6 +161,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 @@ -172,15 +184,16 @@ Actions:
- The core used for the parathread claim is the `next_core` field of the `ParathreadQueue` and adding `Paras::parachains().len()` to it.
- `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 whose reason for freeing was `FreedReason::Concluded` should have the claim removed from the claim index.
- All freed parathread cores whose reason for freeing was `FreedReason::TimedOut` should have the claim added to the parathread queue again without retries incremented
- 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
- 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
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
Loading