mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 07:01:03 +00:00
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:
committed by
GitHub
parent
5624bd8bf4
commit
dddde219a2
@@ -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(¶_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(¶_id);
|
||||
<PendingAvailabilityCommitments>::remove(¶_id);
|
||||
let pending = <PendingAvailability<T>>::take(¶_id);
|
||||
let commitments = <PendingAvailabilityCommitments>::take(¶_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(¶);
|
||||
let commitments = <PendingAvailabilityCommitments>::take(¶);
|
||||
|
||||
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(¶)
|
||||
.map(|p| p.descriptor)
|
||||
.and_then(|d| <PendingAvailabilityCommitments>::get(¶).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(¶)
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
|
||||
@@ -28,5 +28,7 @@ mod paras;
|
||||
mod scheduler;
|
||||
mod validity;
|
||||
|
||||
pub mod runtime_api_impl;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
@@ -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(¶_id)?,
|
||||
balance: 0,
|
||||
validation_code_hash: BlakeTwo256::hash_of(
|
||||
&<paras::Module<T>>::current_code(¶_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(¶_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()
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user