-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Scheduler Module #1162
Scheduler Module #1162
Changes from 38 commits
af77a9a
d2eb247
631a6ba
cdf114b
b2a8c42
d6d751d
ba8dc9f
be800dd
cad53e6
eb64044
8b4cfd4
ff2e4d7
35a3692
adbaa1d
e5c1651
6a11337
c63a94b
1c6d4ab
83ba678
d9df378
3e7ac7b
1bee255
9096c14
242b129
94cb169
55ad79d
2703687
f707371
40d39eb
19f6d53
f5ef2fa
2d52e05
56e8669
a909ad8
89ad0f8
f85ff4e
50965e8
3918713
3392398
63dfb9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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` set to reflect changes in registered parathreads. | ||
|
||
#### Initialization | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -605,25 +609,37 @@ struct ParathreadEntry { | |
// A queued parathread entry, pre-assigned to a core. | ||
struct QueuedParathread { | ||
claim: ParathreadEntry, | ||
core: CoreIndex, | ||
/// offset within the set of para-threads ranged `0..config.parathread_cores`. | ||
core_offset: u32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In several places in this document, you remove a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
|
||
struct ParathreadQueue { | ||
queue: Vec<QueuedParathread>, | ||
// this value is between 0 and config.parathread_cores | ||
next_core: CoreIndex, | ||
/// 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>, | ||
group_idx: GroupIndex, | ||
core: CoreIndex, | ||
para_id: ParaId, | ||
kind: AssignmentKind, | ||
group_idx: GroupIndex, | ||
} | ||
|
||
// reasons a core might be freed. | ||
enum FreedReason { | ||
Concluded, | ||
TimedOut, | ||
} | ||
``` | ||
|
||
|
@@ -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. | ||
|
@@ -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 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we'd ever run with There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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(); | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we care that There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(¬ification); | ||
scheduler::Module::<T>::initializer_on_new_session(¬ification); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
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//