Implement Runtime APIs (#1411)

* create a README on Runtime APIs

* add ParaId type

* write up runtime APIs

* more preamble

* rename

* rejig runtime APIs

* add occupied_since to `BlockNumber`

* skeleton crate for runtime API subsystem

* improve group_for_core

* improve docs on availability cores runtime API

* guide: freed -> free

* add primitives for runtime APIs

* create a v1 ParachainHost API trait

* guide: make validation code return `Option`al.

* skeleton runtime API helpers

* make parachain-host runtime-generic

* skeleton for most runtime API implementation functions

* guide: add runtime API helper methods

* implement new helpers of the inclusion module

* guide: remove retries check, as it is unneeded

* implement helpers for scheduler module for Runtime APIs

* clean up `validator_groups` implementation

* implement next_rotation_at and last_rotation_at

* guide: more helpers on GroupRotationInfo

* almost finish implementing runtime APIs

* add explicit block parameter to runtime API fns

* guide: generalize number parameter

* guide: add group_responsible to occupied-core

* update primitives due to guide changes

* finishing touches on runtime API implementation; squash warnings

* break out runtime API impl to separate file

* add tests for next_up logic

* test group rotation info

* point to filed TODO

* remove unused TODO [now]

* indentation

* guide: para -> para_id

* rename para field to para_id for core meta

* remove reference to outdated AvailabilityCores type

* add an event in `inclusion` for candidates being included or timing out

* guide: candidate events

* guide: adjust language

* Candidate events type from guide and adjust inclusion event

* implement `candidate_events` runtime API

* fix runtime test compilation

* max -> min

* fix typos

* guide: add `RuntimeAPIRequest::CandidateEvents`
This commit is contained in:
Robert Habermeier
2020-07-18 16:01:51 -04:00
committed by GitHub
parent 5624bd8bf4
commit dddde219a2
18 changed files with 1151 additions and 33 deletions
+115 -11
View File
@@ -25,12 +25,11 @@ use primitives::v1::{
ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt,
CandidateReceipt, HeadData,
};
use frame_support::{
decl_storage, decl_module, decl_error, ensure, dispatch::DispatchResult, IterableStorageMap,
weights::Weight,
traits::Get,
debug,
decl_storage, decl_module, decl_error, decl_event, ensure, debug,
dispatch::DispatchResult, IterableStorageMap, weights::Weight, traits::Get,
};
use codec::{Encode, Decode};
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
@@ -69,7 +68,28 @@ pub struct CandidatePendingAvailability<H, N> {
backed_in_number: N,
}
pub trait Trait: system::Trait + paras::Trait + configuration::Trait { }
impl<H, N> CandidatePendingAvailability<H, N> {
/// Get the availability votes on the candidate.
pub(crate) fn availability_votes(&self) -> &BitVec<BitOrderLsb0, u8> {
&self.availability_votes
}
/// Get the relay-chain block number this was backed in.
pub(crate) fn backed_in_number(&self) -> &N {
&self.backed_in_number
}
/// Get the core index.
pub(crate) fn core_occupied(&self)-> CoreIndex {
self.core.clone()
}
}
pub trait Trait:
system::Trait + paras::Trait + configuration::Trait
{
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
decl_storage! {
trait Store for Module<T: Trait> as ParaInclusion {
@@ -89,7 +109,7 @@ decl_storage! {
Validators get(fn validators) config(validators): Vec<ValidatorId>;
/// The current session index.
CurrentSessionIndex: SessionIndex;
CurrentSessionIndex get(fn session_index): SessionIndex;
}
}
@@ -130,10 +150,25 @@ decl_error! {
}
}
decl_event! {
pub enum Event<T> where <T as system::Trait>::Hash {
/// A candidate was backed.
CandidateBacked(CandidateReceipt<Hash>, HeadData),
/// A candidate was included.
CandidateIncluded(CandidateReceipt<Hash>, HeadData),
/// A candidate timed out.
CandidateTimedOut(CandidateReceipt<Hash>, HeadData),
}
}
decl_module! {
/// The parachain-candidate inclusion module.
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin, system = system {
pub struct Module<T: Trait>
for enum Call where origin: <T as system::Trait>::Origin, system = system
{
type Error = Error<T>;
fn deposit_event() = default;
}
}
@@ -450,7 +485,17 @@ impl<T: Trait> Module<T> {
// initialize all availability votes to 0.
let availability_votes: BitVec<BitOrderLsb0, u8>
= bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()];
let (descriptor, commitments) = (candidate.candidate.descriptor, candidate.candidate.commitments);
Self::deposit_event(Event::<T>::CandidateBacked(
candidate.candidate.to_plain(),
candidate.candidate.commitments.head_data.clone(),
));
let (descriptor, commitments) = (
candidate.candidate.descriptor,
candidate.candidate.commitments,
);
<PendingAvailability<T>>::insert(&para_id, CandidatePendingAvailability {
core,
descriptor,
@@ -468,6 +513,7 @@ impl<T: Trait> Module<T> {
relay_parent_number: T::BlockNumber,
receipt: CommittedCandidateReceipt<T::Hash>,
) -> Weight {
let plain = receipt.to_plain();
let commitments = receipt.commitments;
let config = <configuration::Module<T>>::config();
@@ -481,6 +527,10 @@ impl<T: Trait> Module<T> {
);
}
Self::deposit_event(
Event::<T>::CandidateIncluded(plain, commitments.head_data.clone())
);
weight + <paras::Module<T>>::note_new_head(
receipt.descriptor.para_id,
commitments.head_data,
@@ -506,12 +556,66 @@ impl<T: Trait> Module<T> {
}
for para_id in cleaned_up_ids {
<PendingAvailability<T>>::remove(&para_id);
<PendingAvailabilityCommitments>::remove(&para_id);
let pending = <PendingAvailability<T>>::take(&para_id);
let commitments = <PendingAvailabilityCommitments>::take(&para_id);
if let (Some(pending), Some(commitments)) = (pending, commitments) {
// defensive: this should always be true.
let candidate = CandidateReceipt {
descriptor: pending.descriptor,
commitments_hash: commitments.hash(),
};
Self::deposit_event(Event::<T>::CandidateTimedOut(
candidate,
commitments.head_data,
));
}
}
cleaned_up_cores
}
/// Forcibly enact the candidate with the given ID as though it had been deemed available
/// by bitfields.
///
/// Is a no-op if there is no candidate pending availability for this para-id.
/// This should generally not be used but it is useful during execution of Runtime APIs,
/// where the changes to the state are expected to be discarded directly after.
pub(crate) fn force_enact(para: ParaId) {
let pending = <PendingAvailability<T>>::take(&para);
let commitments = <PendingAvailabilityCommitments>::take(&para);
if let (Some(pending), Some(commitments)) = (pending, commitments) {
let candidate = CommittedCandidateReceipt {
descriptor: pending.descriptor,
commitments,
};
Self::enact_candidate(
pending.relay_parent_number,
candidate,
);
}
}
/// Returns the CommittedCandidateReceipt pending availability for the para provided, if any.
pub(crate) fn candidate_pending_availability(para: ParaId)
-> Option<CommittedCandidateReceipt<T::Hash>>
{
<PendingAvailability<T>>::get(&para)
.map(|p| p.descriptor)
.and_then(|d| <PendingAvailabilityCommitments>::get(&para).map(move |c| (d, c)))
.map(|(d, c)| CommittedCandidateReceipt { descriptor: d, commitments: c })
}
/// Returns the metadata around the candidate pending availability for the
/// para provided, if any.
pub(crate) fn pending_availability(para: ParaId)
-> Option<CandidatePendingAvailability<T::Hash, T::BlockNumber>>
{
<PendingAvailability<T>>::get(&para)
}
}
const fn availability_threshold(n_validators: usize) -> usize {
@@ -527,7 +631,7 @@ mod tests {
use primitives::v1::{BlockNumber, Hash};
use primitives::v1::{
SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId,
CandidateCommitments, SignedStatement, CandidateDescriptor, HeadData, ValidationCode,
CandidateCommitments, SignedStatement, CandidateDescriptor, ValidationCode,
AssignmentKind,
};
use frame_support::traits::{OnFinalize, OnInitialize};
+2
View File
@@ -28,5 +28,7 @@ mod paras;
mod scheduler;
mod validity;
pub mod runtime_api_impl;
#[cfg(test)]
mod mock;
+13 -3
View File
@@ -26,9 +26,10 @@ use sp_runtime::{
};
use primitives::v1::{BlockNumber, Header};
use frame_support::{
impl_outer_origin, impl_outer_dispatch, parameter_types,
impl_outer_origin, impl_outer_dispatch, impl_outer_event, parameter_types,
weights::Weight, traits::Randomness as RandomnessT,
};
use crate::inclusion;
/// A test runtime struct.
#[derive(Clone, Eq, PartialEq)]
@@ -44,6 +45,13 @@ impl_outer_dispatch! {
}
}
impl_outer_event! {
pub enum TestEvent for Test {
system<T>,
inclusion<T>,
}
}
pub struct TestRandomness;
impl RandomnessT<H256> for TestRandomness {
@@ -70,7 +78,7 @@ impl system::Trait for Test {
type AccountId = u64;
type Lookup = IdentityLookup<u64>;
type Header = Header;
type Event = ();
type Event = TestEvent;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type DbWeight = ();
@@ -97,7 +105,9 @@ impl crate::paras::Trait for Test { }
impl crate::scheduler::Trait for Test { }
impl crate::inclusion::Trait for Test { }
impl crate::inclusion::Trait for Test {
type Event = TestEvent;
}
pub type System = system::Module<Test>;
@@ -0,0 +1,23 @@
// Copyright 2020 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 <http://www.gnu.org/licenses/>.
//! Runtime API implementations for Parachains.
//!
//! These are exposed as different modules using different sets of primitives.
//! At the moment there is only a v1 module and it is not completely clear how migration
//! to a v2 would be done.
pub mod v1;
@@ -0,0 +1,287 @@
// Copyright 2020 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.
//! A module exporting runtime API implementation functions for all runtime APIs using v1
//! primitives.
//!
//! Runtimes implementing the v1 runtime API are recommended to forward directly to these
//! functions.
use primitives::v1::{
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule,
Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
GroupIndex, CandidateEvent,
};
use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero};
use frame_support::debug;
use crate::{initializer, inclusion, scheduler, configuration, paras};
/// Implementation for the `validators` function of the runtime API.
pub fn validators<T: initializer::Trait>() -> Vec<ValidatorId> {
<inclusion::Module<T>>::validators()
}
/// Implementation for the `validator_groups` function of the runtime API.
pub fn validator_groups<T: initializer::Trait>() -> (
Vec<Vec<ValidatorIndex>>,
GroupRotationInfo<T::BlockNumber>,
) {
let groups = <scheduler::Module<T>>::validator_groups();
let rotation_info = <scheduler::Module<T>>::group_rotation_info();
(groups, rotation_info)
}
/// Implementation for the `availability_cores` function of the runtime API.
pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumber>> {
let cores = <scheduler::Module<T>>::availability_cores();
let parachains = <paras::Module<T>>::parachains();
let config = <configuration::Module<T>>::config();
let rotation_info = <scheduler::Module<T>>::group_rotation_info();
let time_out_at = |backed_in_number, availability_period| {
let time_out_at = backed_in_number + availability_period;
if rotation_info.group_rotation_frequency == Zero::zero() {
return time_out_at;
}
let current_window = rotation_info.last_rotation_at() + availability_period;
let next_rotation = rotation_info.next_rotation_at();
// If we are within `period` blocks of rotation, timeouts are being checked
// actively. We could even time out this block.
if time_out_at < current_window {
time_out_at
} else if time_out_at <= next_rotation {
// Otherwise, it will time out at the sooner of the next rotation
next_rotation
} else {
// or the scheduled time-out. This is by definition within `period` blocks
// of `next_rotation` and is thus a valid timeout block.
time_out_at
}
};
let group_responsible_for = |backed_in_number, core_index| {
match <scheduler::Module<T>>::group_assigned_to_core(core_index, backed_in_number) {
Some(g) => g,
None => {
debug::warn!("Could not determine the group responsible for core extracted \
from list of cores for some prior block in same session");
GroupIndex(0)
}
}
};
let mut core_states: Vec<_> = cores.into_iter().enumerate().map(|(i, core)| match core {
Some(occupied) => {
CoreState::Occupied(match occupied {
CoreOccupied::Parachain => {
let para_id = parachains[i];
let pending_availability = <inclusion::Module<T>>
::pending_availability(para_id)
.expect("Occupied core always has pending availability; qed");
let backed_in_number = pending_availability.backed_in_number().clone();
OccupiedCore {
para_id,
next_up_on_available: <scheduler::Module<T>>::next_up_on_available(
CoreIndex(i as u32)
),
occupied_since: backed_in_number,
time_out_at: time_out_at(
backed_in_number,
config.chain_availability_period,
),
next_up_on_time_out: <scheduler::Module<T>>::next_up_on_time_out(
CoreIndex(i as u32)
),
availability: pending_availability.availability_votes().clone(),
group_responsible: group_responsible_for(
backed_in_number,
pending_availability.core_occupied(),
),
}
}
CoreOccupied::Parathread(p) => {
let para_id = p.claim.0;
let pending_availability = <inclusion::Module<T>>
::pending_availability(para_id)
.expect("Occupied core always has pending availability; qed");
let backed_in_number = pending_availability.backed_in_number().clone();
OccupiedCore {
para_id,
next_up_on_available: <scheduler::Module<T>>::next_up_on_available(
CoreIndex(i as u32)
),
occupied_since: backed_in_number,
time_out_at: time_out_at(
backed_in_number,
config.thread_availability_period,
),
next_up_on_time_out: <scheduler::Module<T>>::next_up_on_time_out(
CoreIndex(i as u32)
),
availability: pending_availability.availability_votes().clone(),
group_responsible: group_responsible_for(
backed_in_number,
pending_availability.core_occupied(),
),
}
}
})
}
None => CoreState::Free,
}).collect();
// This will overwrite only `Free` cores if the scheduler module is working as intended.
for scheduled in <scheduler::Module<T>>::scheduled() {
core_states[scheduled.core.0 as usize] = CoreState::Scheduled(ScheduledCore {
para_id: scheduled.para_id,
collator: scheduled.required_collator().map(|c| c.clone()),
});
}
core_states
}
/// Implementation for the `global_validation_schedule` function of the runtime API.
pub fn global_validation_schedule<T: initializer::Trait>()
-> GlobalValidationSchedule<T::BlockNumber>
{
let config = <configuration::Module<T>>::config();
GlobalValidationSchedule {
max_code_size: config.max_code_size,
max_head_data_size: config.max_head_data_size,
block_number: <system::Module<T>>::block_number() - One::one(),
}
}
/// Implementation for the `local_validation_data` function of the runtime API.
pub fn local_validation_data<T: initializer::Trait>(
para_id: ParaId,
assumption: OccupiedCoreAssumption,
) -> Option<LocalValidationData<T::BlockNumber>> {
let construct = || {
let relay_parent_number = <system::Module<T>>::block_number() - One::one();
let config = <configuration::Module<T>>::config();
let freq = config.validation_upgrade_frequency;
let delay = config.validation_upgrade_delay;
let last_code_upgrade = <paras::Module<T>>::last_code_upgrade(para_id, true)?;
let can_upgrade_code = last_code_upgrade <= relay_parent_number
&& relay_parent_number.saturating_sub(last_code_upgrade) >= freq;
let code_upgrade_allowed = if can_upgrade_code {
Some(relay_parent_number + delay)
} else {
None
};
Some(LocalValidationData {
parent_head: <paras::Module<T>>::para_head(&para_id)?,
balance: 0,
validation_code_hash: BlakeTwo256::hash_of(
&<paras::Module<T>>::current_code(&para_id)?
),
code_upgrade_allowed,
})
};
match assumption {
OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id);
construct()
}
OccupiedCoreAssumption::TimedOut => {
construct()
}
OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None
} else {
construct()
}
}
}
}
/// Implementation for the `session_index_for_child` function of the runtime API.
pub fn session_index_for_child<T: initializer::Trait>() -> SessionIndex {
// Just returns the session index from `inclusion`. Runtime APIs follow
// initialization so the initializer will have applied any pending session change
// which is expected at the child of the block whose context the runtime API was invoked
// in.
//
// 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".
<inclusion::Module<T>>::session_index()
}
/// Implementation for the `validation_code` function of the runtime API.
pub fn validation_code<T: initializer::Trait>(
para_id: ParaId,
assumption: OccupiedCoreAssumption,
) -> Option<ValidationCode> {
let fetch = || {
<paras::Module<T>>::current_code(&para_id)
};
match assumption {
OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id);
fetch()
}
OccupiedCoreAssumption::TimedOut => {
fetch()
}
OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None
} else {
fetch()
}
}
}
}
/// Implementation for the `candidate_pending_availability` function of the runtime API.
pub fn candidate_pending_availability<T: initializer::Trait>(para_id: ParaId)
-> Option<CommittedCandidateReceipt<T::Hash>>
{
<inclusion::Module<T>>::candidate_pending_availability(para_id)
}
/// Implementation for the `candidate_events` function of the runtime API.
// NOTE: this runs without block initialization, as it accesses events.
// this means it can run in a different session than other runtime APIs at the same block.
pub fn candidate_events<T: initializer::Trait>(
extract_event: impl Fn(<T as system::Trait>::Event) -> Option<inclusion::Event<T>>,
) -> Vec<CandidateEvent<T::Hash>> {
use inclusion::Event as RawEvent;
<system::Module<T>>::events().into_iter()
.filter_map(|record| extract_event(record.event))
.map(|event| match event {
RawEvent::<T>::CandidateBacked(c, h) => CandidateEvent::CandidateBacked(c, h),
RawEvent::<T>::CandidateIncluded(c, h) => CandidateEvent::CandidateIncluded(c, h),
RawEvent::<T>::CandidateTimedOut(c, h) => CandidateEvent::CandidateTimedOut(c, h),
})
.collect()
}
+388 -5
View File
@@ -39,7 +39,7 @@ use sp_std::prelude::*;
use sp_std::convert::TryInto;
use primitives::v1::{
Id as ParaId, ValidatorIndex, CoreAssignment, CoreOccupied, CoreIndex, AssignmentKind,
GroupIndex, ParathreadClaim, ParathreadEntry,
GroupIndex, ParathreadClaim, ParathreadEntry, GroupRotationInfo, ScheduledCore,
};
use frame_support::{
decl_storage, decl_module, decl_error,
@@ -84,11 +84,17 @@ impl ParathreadClaimQueue {
})
}
// Take next queued entry with given core offset, if any.
/// Take next queued entry with given core offset, if any.
fn take_next_on_core(&mut self, core_offset: u32) -> Option<ParathreadEntry> {
let pos = self.queue.iter().position(|queued| queued.core_offset == core_offset);
pos.map(|i| self.queue.remove(i).claim)
}
/// Get the next queued entry with given core offset, if any.
fn get_next_on_core(&self, core_offset: u32) -> Option<&ParathreadEntry> {
let pos = self.queue.iter().position(|queued| queued.core_offset == core_offset);
pos.map(|i| &self.queue[i].claim)
}
}
/// Reasons a core might be freed
@@ -107,7 +113,7 @@ decl_storage! {
///
/// Bound: The number of cores is the sum of the numbers of parachains and parathread multiplexers.
/// Reasonably, 100-1000. The dominant factor is the number of validators: safe upper bound at 10k.
ValidatorGroups: Vec<Vec<ValidatorIndex>>;
ValidatorGroups get(fn validator_groups): Vec<Vec<ValidatorIndex>>;
/// A queue of upcoming claims and which core they should be mapped onto.
///
@@ -120,14 +126,14 @@ decl_storage! {
/// parathread-multiplexers.
///
/// Bounded by the number of cores: one for each parachain and parathread multiplexer.
AvailabilityCores: Vec<Option<CoreOccupied>>;
AvailabilityCores get(fn availability_cores): Vec<Option<CoreOccupied>>;
/// An index used to ensure that only one claim on a parathread exists in the queue or is
/// currently being handled by an occupied core.
///
/// Bounded by the number of parathread cores and scheduling lookahead. Reasonably, 10 * 50 = 500.
ParathreadClaimIndex: Vec<ParaId>;
/// The block number where the session start occurred. Used to track how many group rotations have occurred.
SessionStartBlock: T::BlockNumber;
SessionStartBlock get(fn session_start_block): T::BlockNumber;
/// Currently scheduled cores - free but up to be occupied. Ephemeral storage item that's wiped on finalization.
///
/// Bounded by the number of cores: one for each parachain and parathread multiplexer.
@@ -578,6 +584,86 @@ impl<T: Trait> Module<T> {
}))
}
}
/// Returns a helper for determining group rotation.
pub(crate) fn group_rotation_info() -> GroupRotationInfo<T::BlockNumber> {
let session_start_block = Self::session_start_block();
let now = <system::Module<T>>::block_number();
let group_rotation_frequency = <configuration::Module<T>>::config()
.parachain_rotation_frequency;
GroupRotationInfo {
session_start_block,
now,
group_rotation_frequency,
}
}
/// Return the next thing that will be scheduled on this core assuming it is currently
/// occupied and the candidate occupying it became available.
///
/// For parachains, this is always the ID of the parachain and no specified collator.
/// For parathreads, this is based on the next item in the ParathreadQueue assigned to that
/// core, and is None if there isn't one.
pub(crate) fn next_up_on_available(core: CoreIndex) -> Option<ScheduledCore> {
let parachains = <paras::Module<T>>::parachains();
if (core.0 as usize) < parachains.len() {
Some(ScheduledCore {
para_id: parachains[core.0 as usize],
collator: None,
})
} else {
let queue = ParathreadQueue::get();
let core_offset = (core.0 as usize - parachains.len()) as u32;
queue.get_next_on_core(core_offset).map(|entry| ScheduledCore {
para_id: entry.claim.0,
collator: Some(entry.claim.1.clone()),
})
}
}
/// Return the next thing that will be scheduled on this core assuming it is currently
/// occupied and the candidate occupying it became available.
///
/// For parachains, this is always the ID of the parachain and no specified collator.
/// For parathreads, this is based on the next item in the ParathreadQueue assigned to that
/// core, or if there isn't one, the claim that is currently occupying the core, as long
/// as the claim's retries would not exceed the limit. Otherwise None.
pub(crate) fn next_up_on_time_out(core: CoreIndex) -> Option<ScheduledCore> {
let parachains = <paras::Module<T>>::parachains();
if (core.0 as usize) < parachains.len() {
Some(ScheduledCore {
para_id: parachains[core.0 as usize],
collator: None,
})
} else {
let queue = ParathreadQueue::get();
// This is the next scheduled para on this core.
let core_offset = (core.0 as usize - parachains.len()) as u32;
queue.get_next_on_core(core_offset)
.map(|entry| ScheduledCore {
para_id: entry.claim.0,
collator: Some(entry.claim.1.clone()),
})
.or_else(|| {
// Or, if none, the claim currently occupying the core,
// as it would be put back on the queue after timing out.
let cores = AvailabilityCores::get();
cores.get(core.0 as usize).and_then(|c| c.as_ref()).and_then(|o| {
match o {
CoreOccupied::Parathread(entry) => {
Some(ScheduledCore {
para_id: entry.claim.0,
collator: Some(entry.claim.1.clone()),
})
}
CoreOccupied::Parachain => None, // defensive; not possible.
}
})
})
}
}
}
#[cfg(test)]
@@ -1440,4 +1526,301 @@ mod tests {
assert!(Scheduler::availability_timeout_predicate().is_none());
});
}
#[test]
fn next_up_on_available_uses_next_scheduled_or_none_for_thread() {
let mut config = default_config();
config.parathread_cores = 1;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: config.clone(),
..Default::default()
},
..Default::default()
};
let thread_a = ParaId::from(1);
let thread_b = ParaId::from(2);
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);
// start a new session to activate, 5 validators for 5 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
let thread_claim_a = ParathreadClaim(thread_a, collator.clone());
let thread_claim_b = ParathreadClaim(thread_b, collator.clone());
Scheduler::add_parathread_claim(thread_claim_a.clone());
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::scheduled().len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
Scheduler::occupied(&[CoreIndex(0)]);
let cores = Scheduler::availability_cores();
match cores[0].as_ref().unwrap() {
CoreOccupied::Parathread(entry) => assert_eq!(entry.claim, thread_claim_a),
_ => panic!("with no chains, only core should be a thread core"),
}
assert!(Scheduler::next_up_on_available(CoreIndex(0)).is_none());
Scheduler::add_parathread_claim(thread_claim_b);
let queue = ParathreadQueue::get();
assert_eq!(
queue.get_next_on_core(0).unwrap().claim,
ParathreadClaim(thread_b, collator.clone()),
);
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore {
para_id: thread_b,
collator: Some(collator.clone()),
}
);
}
});
}
#[test]
fn next_up_on_time_out_reuses_claim_if_nothing_queued() {
let mut config = default_config();
config.parathread_cores = 1;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: config.clone(),
..Default::default()
},
..Default::default()
};
let thread_a = ParaId::from(1);
let thread_b = ParaId::from(2);
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);
// start a new session to activate, 5 validators for 5 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
let thread_claim_a = ParathreadClaim(thread_a, collator.clone());
let thread_claim_b = ParathreadClaim(thread_b, collator.clone());
Scheduler::add_parathread_claim(thread_claim_a.clone());
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::scheduled().len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
Scheduler::occupied(&[CoreIndex(0)]);
let cores = Scheduler::availability_cores();
match cores[0].as_ref().unwrap() {
CoreOccupied::Parathread(entry) => assert_eq!(entry.claim, thread_claim_a),
_ => panic!("with no chains, only core should be a thread core"),
}
let queue = ParathreadQueue::get();
assert!(queue.get_next_on_core(0).is_none());
assert_eq!(
Scheduler::next_up_on_time_out(CoreIndex(0)).unwrap(),
ScheduledCore {
para_id: thread_a,
collator: Some(collator.clone()),
}
);
Scheduler::add_parathread_claim(thread_claim_b);
let queue = ParathreadQueue::get();
assert_eq!(
queue.get_next_on_core(0).unwrap().claim,
ParathreadClaim(thread_b, collator.clone()),
);
// Now that there is an earlier next-up, we use that.
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore {
para_id: thread_b,
collator: Some(collator.clone()),
}
);
}
});
}
#[test]
fn next_up_on_available_is_parachain_always() {
let mut config = default_config();
config.parathread_cores = 0;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: config.clone(),
..Default::default()
},
..Default::default()
};
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);
// start a new session to activate, 5 validators for 5 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::scheduled().len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
Scheduler::occupied(&[CoreIndex(0)]);
let cores = Scheduler::availability_cores();
match cores[0].as_ref().unwrap() {
CoreOccupied::Parachain => {},
_ => panic!("with no threads, only core should be a chain core"),
}
// Now that there is an earlier next-up, we use that.
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore {
para_id: chain_a,
collator: None,
}
);
}
});
}
#[test]
fn next_up_on_time_out_is_parachain_always() {
let mut config = default_config();
config.parathread_cores = 0;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: config.clone(),
..Default::default()
},
..Default::default()
};
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);
// start a new session to activate, 5 validators for 5 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::scheduled().len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
Scheduler::occupied(&[CoreIndex(0)]);
let cores = Scheduler::availability_cores();
match cores[0].as_ref().unwrap() {
CoreOccupied::Parachain => {},
_ => panic!("with no threads, only core should be a chain core"),
}
// Now that there is an earlier next-up, we use that.
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore {
para_id: chain_a,
collator: None,
}
);
}
});
}
}