mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 20:21:03 +00:00
Coretime Feature branch (relay chain) (#1694)
Also fixes: https://github.com/paritytech/polkadot-sdk/issues/1417 - [x] CoreIndex -> AssignmentProvider mapping will be able to change any time. - [x] Implement - [x] Provide Migrations - [x] Add and fix tests - [x] Implement bulk assigner logic - [x] bulk assigner tests - [x] Port over current assigner to use bulk designer (+ share on-demand with bulk): top-level assigner has core ranges: legacy, bulk - [x] Adjust migrations to reflect new assigner structure - [x] Move migration code to Assignment code directly and make it recursive (make it possible to skip releases) -> follow up ticket. - [x] Test migrations - [x] Add migration PR to runtimes repo -> follow up ticket. - [x] Wire up with actual UMP messages - [x] Write PR docs --------- Co-authored-by: eskimor <eskimor@no-such-url.com> Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com> Co-authored-by: BradleyOlson64 <lotrftw9@gmail.com> Co-authored-by: Anton Vilhelm Ásgeirsson <antonva@users.noreply.github.com> Co-authored-by: antonva <anton.asgeirsson@parity.io> Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Marcin S. <marcin@realemail.net> Co-authored-by: Bastian Köcher <info@kchr.de> Co-authored-by: command-bot <>
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! The Polkadot multiplexing assignment provider.
|
||||
//! Provides blockspace assignments for both bulk and on demand parachains.
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
|
||||
use crate::{
|
||||
configuration, paras,
|
||||
scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig},
|
||||
};
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + configuration::Config + paras::Config {
|
||||
type ParachainsAssignmentProvider: AssignmentProvider<BlockNumberFor<Self>>;
|
||||
type OnDemandAssignmentProvider: AssignmentProvider<BlockNumberFor<Self>>;
|
||||
}
|
||||
}
|
||||
|
||||
// Aliases to make the impl more readable.
|
||||
type ParachainAssigner<T> = <T as Config>::ParachainsAssignmentProvider;
|
||||
type OnDemandAssigner<T> = <T as Config>::OnDemandAssignmentProvider;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
// Helper fn for the AssignmentProvider implementation.
|
||||
// Assumes that the first allocation of cores is to bulk parachains.
|
||||
// This function will return false if there are no cores assigned to the bulk parachain
|
||||
// assigner.
|
||||
fn is_bulk_core(core_idx: &CoreIndex) -> bool {
|
||||
let parachain_cores =
|
||||
<ParachainAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::session_core_count();
|
||||
|
||||
core_idx.0 < parachain_cores
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn session_core_count() -> u32 {
|
||||
let parachain_cores =
|
||||
<ParachainAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::session_core_count();
|
||||
let on_demand_cores =
|
||||
<OnDemandAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::session_core_count();
|
||||
|
||||
parachain_cores.saturating_add(on_demand_cores)
|
||||
}
|
||||
|
||||
/// Pops an `Assignment` from a specified `CoreIndex`
|
||||
fn pop_assignment_for_core(
|
||||
core_idx: CoreIndex,
|
||||
concluded_para: Option<ParaId>,
|
||||
) -> Option<Assignment> {
|
||||
if Pallet::<T>::is_bulk_core(&core_idx) {
|
||||
<ParachainAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::pop_assignment_for_core(
|
||||
core_idx,
|
||||
concluded_para,
|
||||
)
|
||||
} else {
|
||||
<OnDemandAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::pop_assignment_for_core(
|
||||
core_idx,
|
||||
concluded_para,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment) {
|
||||
if Pallet::<T>::is_bulk_core(&core_idx) {
|
||||
<ParachainAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::push_assignment_for_core(
|
||||
core_idx, assignment,
|
||||
)
|
||||
} else {
|
||||
<OnDemandAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::push_assignment_for_core(
|
||||
core_idx, assignment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
|
||||
if Pallet::<T>::is_bulk_core(&core_idx) {
|
||||
<ParachainAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::get_provider_config(
|
||||
core_idx,
|
||||
)
|
||||
} else {
|
||||
<OnDemandAssigner<T> as AssignmentProvider<BlockNumberFor<T>>>::get_provider_config(
|
||||
core_idx,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! Helper functions for tests, also used in runtime-benchmarks.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
mock::MockGenesisConfig,
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
};
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
use primitives::{Balance, HeadData, ValidationCode};
|
||||
|
||||
fn default_genesis_config() -> MockGenesisConfig {
|
||||
MockGenesisConfig {
|
||||
configuration: crate::configuration::GenesisConfig {
|
||||
config: crate::configuration::HostConfiguration { ..Default::default() },
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenesisConfigBuilder {
|
||||
pub on_demand_cores: u32,
|
||||
pub on_demand_base_fee: Balance,
|
||||
pub on_demand_fee_variability: Perbill,
|
||||
pub on_demand_max_queue_size: u32,
|
||||
pub on_demand_target_queue_utilization: Perbill,
|
||||
pub onboarded_on_demand_chains: Vec<primitives::Id>,
|
||||
}
|
||||
|
||||
impl Default for GenesisConfigBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_demand_cores: 10,
|
||||
on_demand_base_fee: 10_000,
|
||||
on_demand_fee_variability: Perbill::from_percent(1),
|
||||
on_demand_max_queue_size: 100,
|
||||
on_demand_target_queue_utilization: Perbill::from_percent(25),
|
||||
onboarded_on_demand_chains: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenesisConfigBuilder {
|
||||
pub(super) fn build(self) -> MockGenesisConfig {
|
||||
let mut genesis = default_genesis_config();
|
||||
let config = &mut genesis.configuration.config;
|
||||
config.coretime_cores = self.on_demand_cores;
|
||||
config.on_demand_base_fee = self.on_demand_base_fee;
|
||||
config.on_demand_fee_variability = self.on_demand_fee_variability;
|
||||
config.on_demand_queue_max_size = self.on_demand_max_queue_size;
|
||||
config.on_demand_target_queue_utilization = self.on_demand_target_queue_utilization;
|
||||
|
||||
let paras = &mut genesis.paras.paras;
|
||||
for para_id in self.onboarded_on_demand_chains {
|
||||
paras.push((
|
||||
para_id,
|
||||
ParaGenesisArgs {
|
||||
genesis_head: HeadData::from(vec![0u8]),
|
||||
validation_code: ValidationCode::from(vec![0u8]),
|
||||
para_kind: ParaKind::Parathread,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
genesis
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,496 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! The parachain coretime assignment module.
|
||||
//!
|
||||
//! Handles scheduling of assignments coming from the coretime/broker chain. For on-demand
|
||||
//! assignments it relies on the separate on-demand assignment provider, where it forwards requests
|
||||
//! to.
|
||||
//!
|
||||
//! `CoreDescriptor` contains pointers to the begin and the end of a list of schedules, together
|
||||
//! with the currently active assignments.
|
||||
|
||||
mod mock_helpers;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
assigner_on_demand, configuration,
|
||||
paras::AssignCoretime,
|
||||
scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig},
|
||||
ParaId,
|
||||
};
|
||||
|
||||
use frame_support::{defensive, pallet_prelude::*};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use pallet_broker::CoreAssignment;
|
||||
use primitives::CoreIndex;
|
||||
use sp_runtime::traits::{One, Saturating};
|
||||
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
/// Fraction expressed as a nominator with an assumed denominator of 57,600.
|
||||
#[derive(RuntimeDebug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, TypeInfo)]
|
||||
pub struct PartsOf57600(u16);
|
||||
|
||||
impl PartsOf57600 {
|
||||
pub const ZERO: Self = Self(0);
|
||||
pub const FULL: Self = Self(57600);
|
||||
|
||||
pub fn new_saturating(v: u16) -> Self {
|
||||
Self::ZERO.saturating_add(Self(v))
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
*self == Self::FULL
|
||||
}
|
||||
|
||||
pub fn saturating_add(self, rhs: Self) -> Self {
|
||||
let inner = self.0.saturating_add(rhs.0);
|
||||
if inner > 57600 {
|
||||
Self(57600)
|
||||
} else {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn saturating_sub(self, rhs: Self) -> Self {
|
||||
Self(self.0.saturating_sub(rhs.0))
|
||||
}
|
||||
|
||||
pub fn checked_add(self, rhs: Self) -> Option<Self> {
|
||||
let inner = self.0.saturating_add(rhs.0);
|
||||
if inner > 57600 {
|
||||
None
|
||||
} else {
|
||||
Some(Self(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assignments as they are scheduled by block number
|
||||
///
|
||||
/// for a particular core.
|
||||
#[derive(Encode, Decode, TypeInfo)]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug))]
|
||||
struct Schedule<N> {
|
||||
// Original assignments
|
||||
assignments: Vec<(CoreAssignment, PartsOf57600)>,
|
||||
/// When do our assignments become invalid, if at all?
|
||||
///
|
||||
/// If this is `Some`, then this `CoreState` will be dropped at that block number. If this is
|
||||
/// `None`, then we will keep serving our core assignments in a circle until a new set of
|
||||
/// assignments is scheduled.
|
||||
end_hint: Option<N>,
|
||||
|
||||
/// The next queued schedule for this core.
|
||||
///
|
||||
/// Schedules are forming a queue.
|
||||
next_schedule: Option<N>,
|
||||
}
|
||||
|
||||
/// Descriptor for a core.
|
||||
///
|
||||
/// Contains pointers to first and last schedule into `CoreSchedules` for that core and keeps track
|
||||
/// of the currently active work as well.
|
||||
#[derive(Encode, Decode, TypeInfo, Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone))]
|
||||
struct CoreDescriptor<N> {
|
||||
/// Meta data about the queued schedules for this core.
|
||||
queue: Option<QueueDescriptor<N>>,
|
||||
/// Currently performed work.
|
||||
current_work: Option<WorkState<N>>,
|
||||
}
|
||||
|
||||
/// Pointers into `CoreSchedules` for a particular core.
|
||||
///
|
||||
/// Schedules in `CoreSchedules` form a queue. `Schedule::next_schedule` always pointing to the next
|
||||
/// item.
|
||||
#[derive(Encode, Decode, TypeInfo, Copy, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug))]
|
||||
struct QueueDescriptor<N> {
|
||||
/// First scheduled item, that is not yet active.
|
||||
first: N,
|
||||
/// Last scheduled item.
|
||||
last: N,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo)]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone))]
|
||||
struct WorkState<N> {
|
||||
/// Assignments with current state.
|
||||
///
|
||||
/// Assignments and book keeping on how much has been served already. We keep track of serviced
|
||||
/// assignments in order to adhere to the specified ratios.
|
||||
assignments: Vec<(CoreAssignment, AssignmentState)>,
|
||||
/// When do our assignments become invalid if at all?
|
||||
///
|
||||
/// If this is `Some`, then this `CoreState` will be dropped at that block number. If this is
|
||||
/// `None`, then we will keep serving our core assignments in a circle until a new set of
|
||||
/// assignments is scheduled.
|
||||
end_hint: Option<N>,
|
||||
/// Position in the assignments we are currently in.
|
||||
///
|
||||
/// Aka which core assignment will be popped next on
|
||||
/// `AssignmentProvider::pop_assignment_for_core`.
|
||||
pos: u16,
|
||||
/// Step width
|
||||
///
|
||||
/// How much we subtract from `AssignmentState::remaining` for a core served.
|
||||
step: PartsOf57600,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo)]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone, Copy))]
|
||||
struct AssignmentState {
|
||||
/// Ratio of the core this assignment has.
|
||||
///
|
||||
/// As initially received via `assign_core`.
|
||||
ratio: PartsOf57600,
|
||||
/// How many parts are remaining in this round?
|
||||
///
|
||||
/// At the end of each round (in preparation for the next), ratio will be added to remaining.
|
||||
/// Then every time we get scheduled we subtract a core worth of points. Once we reach 0 or a
|
||||
/// number lower than what a core is worth (`CoreState::step` size), we move on to the next
|
||||
/// item in the `Vec`.
|
||||
///
|
||||
/// The first round starts with remaining = ratio.
|
||||
remaining: PartsOf57600,
|
||||
}
|
||||
|
||||
impl<N> From<Schedule<N>> for WorkState<N> {
|
||||
fn from(schedule: Schedule<N>) -> Self {
|
||||
let Schedule { assignments, end_hint, next_schedule: _ } = schedule;
|
||||
let step =
|
||||
if let Some(min_step_assignment) = assignments.iter().min_by(|a, b| a.1.cmp(&b.1)) {
|
||||
min_step_assignment.1
|
||||
} else {
|
||||
// Assignments empty, should not exist. In any case step size does not matter here:
|
||||
log::debug!("assignments of a `Schedule` should never be empty.");
|
||||
PartsOf57600(1)
|
||||
};
|
||||
let assignments = assignments
|
||||
.into_iter()
|
||||
.map(|(a, ratio)| (a, AssignmentState { ratio, remaining: ratio }))
|
||||
.collect();
|
||||
|
||||
Self { assignments, end_hint, pos: 0, step }
|
||||
}
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config + configuration::Config + assigner_on_demand::Config
|
||||
{
|
||||
}
|
||||
|
||||
/// Scheduled assignment sets.
|
||||
///
|
||||
/// Assignments as of the given block number. They will go into state once the block number is
|
||||
/// reached (and replace whatever was in there before).
|
||||
#[pallet::storage]
|
||||
pub(super) type CoreSchedules<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox256,
|
||||
(BlockNumberFor<T>, CoreIndex),
|
||||
Schedule<BlockNumberFor<T>>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// Assignments which are currently active.
|
||||
///
|
||||
/// They will be picked from `PendingAssignments` once we reach the scheduled block number in
|
||||
/// `PendingAssignments`.
|
||||
#[pallet::storage]
|
||||
pub(super) type CoreDescriptors<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox256,
|
||||
CoreIndex,
|
||||
CoreDescriptor<BlockNumberFor<T>>,
|
||||
ValueQuery,
|
||||
GetDefault,
|
||||
>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
AssignmentsEmpty,
|
||||
/// Assignments together exceeded 57600.
|
||||
OverScheduled,
|
||||
/// Assignments together less than 57600
|
||||
UnderScheduled,
|
||||
/// assign_core is only allowed to append new assignments at the end of already existing
|
||||
/// ones.
|
||||
DisallowedInsert,
|
||||
/// Tried to insert a schedule for the same core and block number as an existing schedule
|
||||
DuplicateInsert,
|
||||
/// Tried to add an unsorted set of assignments
|
||||
AssignmentsNotSorted,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn pop_assignment_for_core(core_idx: CoreIndex) -> Option<Assignment> {
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
CoreDescriptors::<T>::mutate(core_idx, |core_state| {
|
||||
Self::ensure_workload(now, core_idx, core_state);
|
||||
|
||||
let work_state = core_state.current_work.as_mut()?;
|
||||
|
||||
// Wrap around:
|
||||
work_state.pos = work_state.pos % work_state.assignments.len() as u16;
|
||||
let (a_type, a_state) = &mut work_state
|
||||
.assignments
|
||||
.get_mut(work_state.pos as usize)
|
||||
.expect("We limited pos to the size of the vec one line above. qed");
|
||||
|
||||
// advance for next pop:
|
||||
a_state.remaining = a_state.remaining.saturating_sub(work_state.step);
|
||||
if a_state.remaining < work_state.step {
|
||||
// Assignment exhausted, need to move to the next and credit remaining for
|
||||
// next round.
|
||||
work_state.pos += 1;
|
||||
// Reset to ratio + still remaining "credits":
|
||||
a_state.remaining = a_state.remaining.saturating_add(a_state.ratio);
|
||||
}
|
||||
|
||||
match a_type {
|
||||
CoreAssignment::Idle => None,
|
||||
CoreAssignment::Pool =>
|
||||
assigner_on_demand::Pallet::<T>::pop_assignment_for_core(core_idx),
|
||||
CoreAssignment::Task(para_id) => Some(Assignment::Bulk((*para_id).into())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn report_processed(assignment: Assignment) {
|
||||
match assignment {
|
||||
Assignment::Pool { para_id, core_index } =>
|
||||
assigner_on_demand::Pallet::<T>::report_processed(para_id, core_index),
|
||||
Assignment::Bulk(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Push an assignment back to the front of the queue.
|
||||
///
|
||||
/// The assignment has not been processed yet. Typically used on session boundaries.
|
||||
/// Parameters:
|
||||
/// - `assignment`: The on demand assignment.
|
||||
fn push_back_assignment(assignment: Assignment) {
|
||||
match assignment {
|
||||
Assignment::Pool { para_id, core_index } =>
|
||||
assigner_on_demand::Pallet::<T>::push_back_assignment(para_id, core_index),
|
||||
Assignment::Bulk(_) => {
|
||||
// Session changes are rough. We just drop assignments that did not make it on a
|
||||
// session boundary. This seems sensible as bulk is region based. Meaning, even if
|
||||
// we made the effort catching up on those dropped assignments, this would very
|
||||
// likely lead to other assignments not getting served at the "end" (when our
|
||||
// assignment set gets replaced).
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
AssignmentProviderConfig {
|
||||
max_availability_timeouts: config.on_demand_retries,
|
||||
ttl: config.on_demand_ttl,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
fn get_mock_assignment(_: CoreIndex, para_id: primitives::Id) -> Assignment {
|
||||
// Given that we are not tracking anything in `Bulk` assignments, it is safe to always
|
||||
// return a bulk assignment.
|
||||
Assignment::Bulk(para_id)
|
||||
}
|
||||
|
||||
fn session_core_count() -> u32 {
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
config.coretime_cores
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Ensure given workload for core is up to date.
|
||||
fn ensure_workload(
|
||||
now: BlockNumberFor<T>,
|
||||
core_idx: CoreIndex,
|
||||
descriptor: &mut CoreDescriptor<BlockNumberFor<T>>,
|
||||
) {
|
||||
// Workload expired?
|
||||
if descriptor
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| w.end_hint)
|
||||
.map_or(false, |e| e <= now)
|
||||
{
|
||||
descriptor.current_work = None;
|
||||
}
|
||||
|
||||
let Some(queue) = descriptor.queue else {
|
||||
// No queue.
|
||||
return
|
||||
};
|
||||
|
||||
let mut next_scheduled = queue.first;
|
||||
|
||||
if next_scheduled > now {
|
||||
// Not yet ready.
|
||||
return
|
||||
}
|
||||
|
||||
// Update is needed:
|
||||
let update = loop {
|
||||
let Some(update) = CoreSchedules::<T>::take((next_scheduled, core_idx)) else {
|
||||
break None
|
||||
};
|
||||
// Still good?
|
||||
if update.end_hint.map_or(true, |e| e > now) {
|
||||
break Some(update)
|
||||
}
|
||||
// Move on if possible:
|
||||
if let Some(n) = update.next_schedule {
|
||||
next_scheduled = n;
|
||||
} else {
|
||||
break None
|
||||
}
|
||||
};
|
||||
|
||||
let new_first = update.as_ref().and_then(|u| u.next_schedule);
|
||||
descriptor.current_work = update.map(Into::into);
|
||||
|
||||
descriptor.queue = new_first.map(|new_first| {
|
||||
QueueDescriptor {
|
||||
first: new_first,
|
||||
// `last` stays unaffected, if not empty:
|
||||
last: queue.last,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Append another assignment for a core.
|
||||
///
|
||||
/// Important only appending is allowed. Meaning, all already existing assignments must have a
|
||||
/// begin smaller than the one passed here. This restriction exists, because it makes the
|
||||
/// insertion O(1) and the author could not think of a reason, why this restriction should be
|
||||
/// causing any problems. Inserting arbitrarily causes a `DispatchError::DisallowedInsert`
|
||||
/// error. This restriction could easily be lifted if need be and in fact an implementation is
|
||||
/// available
|
||||
/// [here](https://github.com/paritytech/polkadot-sdk/pull/1694/commits/c0c23b01fd2830910cde92c11960dad12cdff398#diff-0c85a46e448de79a5452395829986ee8747e17a857c27ab624304987d2dde8baR386).
|
||||
/// The problem is that insertion complexity then depends on the size of the existing queue,
|
||||
/// which makes determining weights hard and could lead to issues like overweight blocks (at
|
||||
/// least in theory).
|
||||
pub fn assign_core(
|
||||
core_idx: CoreIndex,
|
||||
begin: BlockNumberFor<T>,
|
||||
assignments: Vec<(CoreAssignment, PartsOf57600)>,
|
||||
end_hint: Option<BlockNumberFor<T>>,
|
||||
) -> Result<(), DispatchError> {
|
||||
// There should be at least one assignment.
|
||||
ensure!(!assignments.is_empty(), Error::<T>::AssignmentsEmpty);
|
||||
|
||||
// Checking for sort and unique manually, since we don't have access to iterator tools.
|
||||
// This way of checking uniqueness only works since we also check sortedness.
|
||||
assignments.iter().map(|x| &x.0).try_fold(None, |prev, cur| {
|
||||
if prev.map_or(false, |p| p >= cur) {
|
||||
Err(Error::<T>::AssignmentsNotSorted)
|
||||
} else {
|
||||
Ok(Some(cur))
|
||||
}
|
||||
})?;
|
||||
|
||||
// Check that the total parts between all assignments are equal to 57600
|
||||
let parts_sum = assignments
|
||||
.iter()
|
||||
.map(|assignment| assignment.1)
|
||||
.try_fold(PartsOf57600::ZERO, |sum, parts| {
|
||||
sum.checked_add(parts).ok_or(Error::<T>::OverScheduled)
|
||||
})?;
|
||||
ensure!(parts_sum.is_full(), Error::<T>::UnderScheduled);
|
||||
|
||||
CoreDescriptors::<T>::mutate(core_idx, |core_descriptor| {
|
||||
let new_queue = match core_descriptor.queue {
|
||||
Some(queue) => {
|
||||
ensure!(begin > queue.last, Error::<T>::DisallowedInsert);
|
||||
|
||||
CoreSchedules::<T>::try_mutate((queue.last, core_idx), |schedule| {
|
||||
if let Some(schedule) = schedule.as_mut() {
|
||||
debug_assert!(schedule.next_schedule.is_none(), "queue.end was supposed to be the end, so the next item must be `None`!");
|
||||
schedule.next_schedule = Some(begin);
|
||||
} else {
|
||||
defensive!("Queue end entry does not exist?");
|
||||
}
|
||||
CoreSchedules::<T>::try_mutate((begin, core_idx), |schedule| {
|
||||
// It should already be impossible to overwrite an existing schedule due
|
||||
// to strictly increasing block number. But we check here for safety and
|
||||
// in case the design changes.
|
||||
ensure!(schedule.is_none(), Error::<T>::DuplicateInsert);
|
||||
*schedule =
|
||||
Some(Schedule { assignments, end_hint, next_schedule: None });
|
||||
Ok::<(), DispatchError>(())
|
||||
})?;
|
||||
Ok::<(), DispatchError>(())
|
||||
})?;
|
||||
|
||||
QueueDescriptor { first: queue.first, last: begin }
|
||||
},
|
||||
None => {
|
||||
// Queue empty, just insert:
|
||||
CoreSchedules::<T>::insert(
|
||||
(begin, core_idx),
|
||||
Schedule { assignments, end_hint, next_schedule: None },
|
||||
);
|
||||
QueueDescriptor { first: begin, last: begin }
|
||||
},
|
||||
};
|
||||
core_descriptor.queue = Some(new_queue);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AssignCoretime for Pallet<T> {
|
||||
fn assign_coretime(id: ParaId) -> DispatchResult {
|
||||
let current_block = frame_system::Pallet::<T>::block_number();
|
||||
|
||||
// Add a new core and assign the para to it.
|
||||
let mut config = <configuration::Pallet<T>>::config();
|
||||
let core = config.coretime_cores;
|
||||
config.coretime_cores.saturating_inc();
|
||||
|
||||
// `assign_coretime` is only called at genesis or by root, so setting the active
|
||||
// config here is fine.
|
||||
configuration::Pallet::<T>::force_set_active_config(config);
|
||||
|
||||
let begin = current_block + One::one();
|
||||
let assignment = vec![(pallet_broker::CoreAssignment::Task(id.into()), PartsOf57600::FULL)];
|
||||
Pallet::<T>::assign_core(CoreIndex(core), begin, assignment, None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,817 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
assigner_coretime::{mock_helpers::GenesisConfigBuilder, pallet::Error, Schedule},
|
||||
initializer::SessionChangeNotification,
|
||||
mock::{
|
||||
new_test_ext, Balances, CoretimeAssigner, OnDemandAssigner, Paras, ParasShared,
|
||||
RuntimeOrigin, Scheduler, System, Test,
|
||||
},
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
scheduler::common::Assignment,
|
||||
};
|
||||
use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency};
|
||||
use pallet_broker::TaskId;
|
||||
use primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
|
||||
fn schedule_blank_para(id: ParaId, parakind: ParaKind) {
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
assert_ok!(Paras::schedule_para_initialize(
|
||||
id,
|
||||
ParaGenesisArgs {
|
||||
genesis_head: Vec::new().into(),
|
||||
validation_code: validation_code.clone(),
|
||||
para_kind: parakind,
|
||||
}
|
||||
));
|
||||
|
||||
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
|
||||
}
|
||||
|
||||
fn run_to_block(
|
||||
to: BlockNumber,
|
||||
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
|
||||
) {
|
||||
while System::block_number() < to {
|
||||
let b = System::block_number();
|
||||
|
||||
Scheduler::initializer_finalize();
|
||||
Paras::initializer_finalize(b);
|
||||
|
||||
if let Some(notification) = new_session(b + 1) {
|
||||
let mut notification_with_session_index = notification;
|
||||
// We will make every session change trigger an action queue. Normally this may require
|
||||
// 2 or more session changes.
|
||||
if notification_with_session_index.session_index == SessionIndex::default() {
|
||||
notification_with_session_index.session_index = ParasShared::scheduled_session();
|
||||
}
|
||||
Paras::initializer_on_new_session(¬ification_with_session_index);
|
||||
Scheduler::initializer_on_new_session(¬ification_with_session_index);
|
||||
}
|
||||
|
||||
System::on_finalize(b);
|
||||
|
||||
System::on_initialize(b + 1);
|
||||
System::set_block_number(b + 1);
|
||||
|
||||
Paras::initializer_initialize(b + 1);
|
||||
Scheduler::initializer_initialize(b + 1);
|
||||
|
||||
// In the real runtime this is expected to be called by the `InclusionInherent` pallet.
|
||||
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_test_assignments() -> Vec<(CoreAssignment, PartsOf57600)> {
|
||||
vec![(CoreAssignment::Idle, PartsOf57600::FULL)]
|
||||
}
|
||||
|
||||
fn default_test_schedule() -> Schedule<BlockNumberFor<Test>> {
|
||||
Schedule { assignments: default_test_assignments(), end_hint: None, next_schedule: None }
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Should create new QueueDescriptor and add new schedule to CoreSchedules
|
||||
fn assign_core_works_with_no_prior_schedule() {
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Call assign_core
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Check CoreSchedules
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(11u32), core_idx)),
|
||||
Some(default_test_schedule())
|
||||
);
|
||||
|
||||
// Check QueueDescriptor
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.queue
|
||||
.as_ref()
|
||||
.and_then(|q| Some(q.first)),
|
||||
Some(BlockNumberFor::<Test>::from(11u32))
|
||||
);
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
|
||||
Some(BlockNumberFor::<Test>::from(11u32))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_hint_is_properly_honored() {
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
vec![(CoreAssignment::Task(1), PartsOf57600::FULL)],
|
||||
Some(15u32),
|
||||
));
|
||||
|
||||
assert!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx).is_none(),
|
||||
"No assignment yet in effect"
|
||||
);
|
||||
|
||||
run_to_block(11, |_| None);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(1.into())),
|
||||
"Assignment should now be present"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(1.into())),
|
||||
"Nothing changed, assignment should still be present"
|
||||
);
|
||||
|
||||
run_to_block(15, |_| None);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
None,
|
||||
"Assignment should now be gone"
|
||||
);
|
||||
|
||||
// Insert assignment that is already dead:
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
vec![(CoreAssignment::Task(1), PartsOf57600::FULL)],
|
||||
Some(15u32),
|
||||
));
|
||||
|
||||
// Core should still be empty:
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
None,
|
||||
"Assignment should now be gone"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Should update last in QueueDescriptor and add new schedule to CoreSchedules
|
||||
fn assign_core_works_with_prior_schedule() {
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
let default_with_next_schedule =
|
||||
Schedule { next_schedule: Some(15u32), ..default_test_schedule() };
|
||||
|
||||
// Call assign_core twice
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(15u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Check CoreSchedules for two entries
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(11u32), core_idx)),
|
||||
Some(default_with_next_schedule)
|
||||
);
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(15u32), core_idx)),
|
||||
Some(default_test_schedule())
|
||||
);
|
||||
|
||||
// Check QueueDescriptor
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.queue
|
||||
.as_ref()
|
||||
.and_then(|q| Some(q.first)),
|
||||
Some(BlockNumberFor::<Test>::from(11u32))
|
||||
);
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
|
||||
Some(BlockNumberFor::<Test>::from(15u32))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Invariants: We assume that CoreSchedules is append only and consumed. In other words new
|
||||
// schedules inserted for a core must have a higher block number than all of the already existing
|
||||
// schedules.
|
||||
fn assign_core_enforces_higher_block_number() {
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Call assign core twice to establish some schedules
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(12u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(15u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Call assign core with block number before QueueDescriptor first, expecting an error
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
),
|
||||
Error::<Test>::DisallowedInsert
|
||||
);
|
||||
|
||||
// Call assign core with block number between already scheduled assignments, expecting an
|
||||
// error
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(13u32),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
),
|
||||
Error::<Test>::DisallowedInsert
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_core_enforces_well_formed_schedule() {
|
||||
let para_id = ParaId::from(1u32);
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
let empty_assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![];
|
||||
let overscheduled = vec![
|
||||
(CoreAssignment::Pool, PartsOf57600::FULL),
|
||||
(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL),
|
||||
];
|
||||
let underscheduled = vec![(CoreAssignment::Pool, PartsOf57600(30000))];
|
||||
let not_unique = vec![
|
||||
(CoreAssignment::Pool, PartsOf57600::FULL / 2),
|
||||
(CoreAssignment::Pool, PartsOf57600::FULL / 2),
|
||||
];
|
||||
let not_sorted = vec![
|
||||
(CoreAssignment::Task(para_id.into()), PartsOf57600(19200)),
|
||||
(CoreAssignment::Pool, PartsOf57600(19200)),
|
||||
(CoreAssignment::Idle, PartsOf57600(19200)),
|
||||
];
|
||||
|
||||
// Attempting assign_core with malformed assignments such that all error cases
|
||||
// are tested
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
empty_assignments,
|
||||
None,
|
||||
),
|
||||
Error::<Test>::AssignmentsEmpty
|
||||
);
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
overscheduled,
|
||||
None,
|
||||
),
|
||||
Error::<Test>::OverScheduled
|
||||
);
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
underscheduled,
|
||||
None,
|
||||
),
|
||||
Error::<Test>::UnderScheduled
|
||||
);
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
not_unique,
|
||||
None,
|
||||
),
|
||||
Error::<Test>::AssignmentsNotSorted
|
||||
);
|
||||
assert_noop!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
not_sorted,
|
||||
None,
|
||||
),
|
||||
Error::<Test>::AssignmentsNotSorted
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_schedule_always_points_to_next_work_plan_item() {
|
||||
let core_idx = CoreIndex(0);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
let start_1 = 15u32;
|
||||
let start_2 = 20u32;
|
||||
let start_3 = 25u32;
|
||||
let start_4 = 30u32;
|
||||
let start_5 = 35u32;
|
||||
|
||||
let expected_schedule_3 =
|
||||
Schedule { next_schedule: Some(start_4), ..default_test_schedule() };
|
||||
let expected_schedule_4 =
|
||||
Schedule { next_schedule: Some(start_5), ..default_test_schedule() };
|
||||
let expected_schedule_5 = default_test_schedule();
|
||||
|
||||
// Call assign_core for each of five schedules
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_1),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_2),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_3),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_4),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_5),
|
||||
default_test_assignments(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Rotate through the first two schedules
|
||||
run_to_block(start_1, |n| if n == start_1 { Some(Default::default()) } else { None });
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx);
|
||||
run_to_block(start_2, |n| if n == start_2 { Some(Default::default()) } else { None });
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx);
|
||||
|
||||
// Use saved starting block numbers to check that schedules chain
|
||||
// together correctly
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_3), core_idx)),
|
||||
Some(expected_schedule_3)
|
||||
);
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_4), core_idx)),
|
||||
Some(expected_schedule_4)
|
||||
);
|
||||
assert_eq!(
|
||||
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_5), core_idx)),
|
||||
Some(expected_schedule_5)
|
||||
);
|
||||
|
||||
// Check QueueDescriptor
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.queue
|
||||
.as_ref()
|
||||
.and_then(|q| Some(q.first)),
|
||||
Some(start_3)
|
||||
);
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
|
||||
Some(start_5)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_workload_works() {
|
||||
let core_idx = CoreIndex(0);
|
||||
let test_assignment_state =
|
||||
AssignmentState { ratio: PartsOf57600::FULL, remaining: PartsOf57600::FULL };
|
||||
|
||||
let empty_descriptor: CoreDescriptor<BlockNumberFor<Test>> =
|
||||
CoreDescriptor { queue: None, current_work: None };
|
||||
let assignments_queued_descriptor = CoreDescriptor {
|
||||
queue: Some(QueueDescriptor {
|
||||
first: BlockNumberFor::<Test>::from(11u32),
|
||||
last: BlockNumberFor::<Test>::from(11u32),
|
||||
}),
|
||||
current_work: None,
|
||||
};
|
||||
let assignments_active_descriptor = CoreDescriptor {
|
||||
queue: None,
|
||||
current_work: Some(WorkState {
|
||||
assignments: vec![(CoreAssignment::Pool, test_assignment_state)],
|
||||
end_hint: Some(BlockNumberFor::<Test>::from(15u32)),
|
||||
pos: 0,
|
||||
step: PartsOf57600::FULL,
|
||||
}),
|
||||
};
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
let mut core_descriptor: CoreDescriptor<BlockNumberFor<Test>> = empty_descriptor.clone();
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Case 1: No new schedule in CoreSchedules for core
|
||||
CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor);
|
||||
assert_eq!(core_descriptor, empty_descriptor);
|
||||
|
||||
// Case 2: New schedule exists in CoreSchedules for core, but new
|
||||
// schedule start is not yet reached.
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
vec![(CoreAssignment::Pool, PartsOf57600::FULL)],
|
||||
Some(BlockNumberFor::<Test>::from(15u32)),
|
||||
));
|
||||
|
||||
// Propagate changes from storage to Core_Descriptor handle. Normally
|
||||
// pop_assignment_for_core would handle this.
|
||||
core_descriptor = CoreDescriptors::<Test>::get(core_idx);
|
||||
|
||||
CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor);
|
||||
assert_eq!(core_descriptor, assignments_queued_descriptor);
|
||||
|
||||
// Case 3: Next schedule exists in CoreSchedules for core. Next starting
|
||||
// block has been reached. Swaps new WorkState into CoreDescriptors from
|
||||
// CoreSchedules.
|
||||
CoretimeAssigner::ensure_workload(11u32, core_idx, &mut core_descriptor);
|
||||
assert_eq!(core_descriptor, assignments_active_descriptor);
|
||||
|
||||
// Case 4: end_hint reached but new schedule start not yet reached. WorkState in
|
||||
// CoreDescriptor is cleared
|
||||
CoretimeAssigner::ensure_workload(15u32, core_idx, &mut core_descriptor);
|
||||
assert_eq!(core_descriptor, empty_descriptor);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_assignment_for_core_works() {
|
||||
let para_id = ParaId::from(1);
|
||||
let core_idx = CoreIndex(0);
|
||||
let alice = 1u64;
|
||||
let amt = 10_000_000u128;
|
||||
|
||||
let assignments_pool = vec![(CoreAssignment::Pool, PartsOf57600::FULL)];
|
||||
let assignments_task = vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)];
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
// Initialize the parathread, wait for it to be ready, then add an
|
||||
// on demand order to later pop with our Coretime assigner.
|
||||
schedule_blank_para(para_id, ParaKind::Parathread);
|
||||
Balances::make_free_balance_be(&alice, amt);
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
assert_ok!(OnDemandAssigner::place_order_allow_death(
|
||||
RuntimeOrigin::signed(alice),
|
||||
amt,
|
||||
para_id
|
||||
));
|
||||
|
||||
// Case 1: Assignment idle
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
default_test_assignments(), // Default is Idle
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
assert_eq!(CoretimeAssigner::pop_assignment_for_core(core_idx), None);
|
||||
|
||||
// Case 2: Assignment pool
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(21u32),
|
||||
assignments_pool,
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(21, |n| if n == 21 { Some(Default::default()) } else { None });
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Pool { para_id, core_index: 0.into() })
|
||||
);
|
||||
|
||||
// Case 3: Assignment task
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(31u32),
|
||||
assignments_task,
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(31, |n| if n == 31 { Some(Default::default()) } else { None });
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(para_id))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignment_proportions_in_core_state_work() {
|
||||
let core_idx = CoreIndex(0);
|
||||
let task_1 = TaskId::from(1u32);
|
||||
let task_2 = TaskId::from(2u32);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Task 1 gets 2/3 core usage, while task 2 gets 1/3
|
||||
let test_assignments = vec![
|
||||
(CoreAssignment::Task(task_1), PartsOf57600::FULL / 3 * 2),
|
||||
(CoreAssignment::Task(task_2), PartsOf57600::FULL / 3),
|
||||
];
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
test_assignments,
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
// Case 1: Current assignment remaining >= step after pop
|
||||
{
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.pos)),
|
||||
Some(0u16)
|
||||
);
|
||||
// Consumed step should be 1/3 of core parts, leaving 1/3 remaining
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.assignments[0].1.remaining)),
|
||||
Some(PartsOf57600::FULL / 3)
|
||||
);
|
||||
}
|
||||
|
||||
// Case 2: Current assignment remaning < step after pop
|
||||
{
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
// Pos should have incremented, as assignment had remaining < step
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.pos)),
|
||||
Some(1u16)
|
||||
);
|
||||
// Remaining should have started at 1/3 of core work parts. We then subtract
|
||||
// step (1/3) and add back ratio (2/3), leaving us with 2/3 of core work parts.
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.assignments[0].1.remaining)),
|
||||
Some(PartsOf57600::FULL / 3 * 2)
|
||||
);
|
||||
}
|
||||
|
||||
// Final check, task 2's turn to be served
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_assignments_served_equally() {
|
||||
let core_idx = CoreIndex(0);
|
||||
let task_1 = TaskId::from(1u32);
|
||||
let task_2 = TaskId::from(2u32);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Tasks 1 and 2 get equal work parts
|
||||
let test_assignments = vec![
|
||||
(CoreAssignment::Task(task_1), PartsOf57600::FULL / 2),
|
||||
(CoreAssignment::Task(task_2), PartsOf57600::FULL / 2),
|
||||
];
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
test_assignments,
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
// Test that popped assignments alternate between tasks 1 and 2
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Checks that core is shared fairly, even in case of `ratio` not being
|
||||
// divisible by `step` (over multiple rounds).
|
||||
fn assignment_proportions_indivisible_by_step_work() {
|
||||
let core_idx = CoreIndex(0);
|
||||
let task_1 = TaskId::from(1u32);
|
||||
let ratio_1 = PartsOf57600::FULL / 5 * 3;
|
||||
let ratio_2 = PartsOf57600::FULL / 5 * 2;
|
||||
let task_2 = TaskId::from(2u32);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
|
||||
// Task 1 gets 3/5 core usage, while task 2 gets 2/5. That way
|
||||
// step is set to 2/5 and task 1 is indivisible by step.
|
||||
let test_assignments =
|
||||
vec![(CoreAssignment::Task(task_1), ratio_1), (CoreAssignment::Task(task_2), ratio_2)];
|
||||
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(11u32),
|
||||
test_assignments,
|
||||
None,
|
||||
));
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
// Pop 5 assignments. Should Result in the the following work ordering:
|
||||
// 1, 2, 1, 1, 2. The remaining parts for each assignment should be same
|
||||
// at the end as in the beginning.
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_1.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoretimeAssigner::pop_assignment_for_core(core_idx),
|
||||
Some(Assignment::Bulk(task_2.into()))
|
||||
);
|
||||
|
||||
// Remaining should equal ratio for both assignments.
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.assignments[0].1.remaining)),
|
||||
Some(ratio_1)
|
||||
);
|
||||
assert_eq!(
|
||||
CoreDescriptors::<Test>::get(core_idx)
|
||||
.current_work
|
||||
.as_ref()
|
||||
.and_then(|w| Some(w.assignments[1].1.remaining)),
|
||||
Some(ratio_2)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl std::ops::Div<u16> for PartsOf57600 {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: u16) -> Self::Output {
|
||||
if rhs == 0 {
|
||||
panic!("Cannot divide by zero!");
|
||||
}
|
||||
|
||||
Self(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl std::ops::Mul<u16> for PartsOf57600 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: u16) -> Self {
|
||||
Self(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parts_of_57600_ops() {
|
||||
assert!(PartsOf57600::new_saturating(57601).is_full());
|
||||
assert!(PartsOf57600::FULL.saturating_add(PartsOf57600(1)).is_full());
|
||||
assert_eq!(PartsOf57600::ZERO.saturating_sub(PartsOf57600(1)), PartsOf57600::ZERO);
|
||||
assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(0)), Some(PartsOf57600::FULL));
|
||||
assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(1)), None);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ where
|
||||
{
|
||||
ParasShared::<T>::set_session_index(SESSION_INDEX);
|
||||
let mut config = HostConfiguration::default();
|
||||
config.on_demand_cores = 1;
|
||||
config.coretime_cores = 1;
|
||||
ConfigurationPallet::<T>::force_set_active_config(config);
|
||||
let mut parachains = ParachainsCache::new();
|
||||
ParasPallet::<T>::initialize_para_now(
|
||||
@@ -70,11 +70,10 @@ mod benchmarks {
|
||||
let para_id = ParaId::from(111u32);
|
||||
init_parathread::<T>(para_id);
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
let assignment = Assignment::new(para_id);
|
||||
let order = EnqueuedOrder::new(para_id);
|
||||
|
||||
for _ in 0..s {
|
||||
Pallet::<T>::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back)
|
||||
.unwrap();
|
||||
Pallet::<T>::add_on_demand_order(order.clone(), QueuePushDirection::Back).unwrap();
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
@@ -88,11 +87,10 @@ mod benchmarks {
|
||||
let para_id = ParaId::from(111u32);
|
||||
init_parathread::<T>(para_id);
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
let assignment = Assignment::new(para_id);
|
||||
let order = EnqueuedOrder::new(para_id);
|
||||
|
||||
for _ in 0..s {
|
||||
Pallet::<T>::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back)
|
||||
.unwrap();
|
||||
Pallet::<T>::add_on_demand_order(order.clone(), QueuePushDirection::Back).unwrap();
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::{
|
||||
|
||||
use primitives::{Balance, HeadData, ValidationCode};
|
||||
|
||||
pub fn default_genesis_config() -> MockGenesisConfig {
|
||||
fn default_genesis_config() -> MockGenesisConfig {
|
||||
MockGenesisConfig {
|
||||
configuration: crate::configuration::GenesisConfig {
|
||||
config: crate::configuration::HostConfiguration { ..Default::default() },
|
||||
@@ -63,7 +63,7 @@ impl GenesisConfigBuilder {
|
||||
pub(super) fn build(self) -> MockGenesisConfig {
|
||||
let mut genesis = default_genesis_config();
|
||||
let config = &mut genesis.configuration.config;
|
||||
config.on_demand_cores = self.on_demand_cores;
|
||||
config.coretime_cores = self.on_demand_cores;
|
||||
config.on_demand_base_fee = self.on_demand_base_fee;
|
||||
config.on_demand_fee_variability = self.on_demand_fee_variability;
|
||||
config.on_demand_queue_max_size = self.on_demand_max_queue_size;
|
||||
|
||||
@@ -32,10 +32,7 @@ mod mock_helpers;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
configuration, paras,
|
||||
scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig},
|
||||
};
|
||||
use crate::{configuration, paras, scheduler::common::Assignment};
|
||||
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
@@ -79,7 +76,7 @@ impl WeightInfo for TestWeightInfo {
|
||||
/// Keeps track of how many assignments a scheduler currently has at a specific `CoreIndex` for a
|
||||
/// specific `ParaId`.
|
||||
#[derive(Encode, Decode, Default, Clone, Copy, TypeInfo)]
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
#[cfg_attr(test, derive(PartialEq, RuntimeDebug))]
|
||||
pub struct CoreAffinityCount {
|
||||
core_idx: CoreIndex,
|
||||
count: u32,
|
||||
@@ -107,6 +104,18 @@ pub enum SpotTrafficCalculationErr {
|
||||
Division,
|
||||
}
|
||||
|
||||
/// Internal representation of an order after it has been enqueued already.
|
||||
#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone)]
|
||||
pub(super) struct EnqueuedOrder {
|
||||
pub para_id: ParaId,
|
||||
}
|
||||
|
||||
impl EnqueuedOrder {
|
||||
pub fn new(para_id: ParaId) -> Self {
|
||||
Self { para_id }
|
||||
}
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
|
||||
@@ -140,7 +149,7 @@ pub mod pallet {
|
||||
|
||||
/// Creates an empty on demand queue if one isn't present in storage already.
|
||||
#[pallet::type_value]
|
||||
pub fn OnDemandQueueOnEmpty<T: Config>() -> VecDeque<Assignment> {
|
||||
pub(super) fn OnDemandQueueOnEmpty<T: Config>() -> VecDeque<EnqueuedOrder> {
|
||||
VecDeque::new()
|
||||
}
|
||||
|
||||
@@ -153,8 +162,8 @@ pub mod pallet {
|
||||
/// The order storage entry. Uses a VecDeque to be able to push to the front of the
|
||||
/// queue from the scheduler on session boundaries.
|
||||
#[pallet::storage]
|
||||
pub type OnDemandQueue<T: Config> =
|
||||
StorageValue<_, VecDeque<Assignment>, ValueQuery, OnDemandQueueOnEmpty<T>>;
|
||||
pub(super) type OnDemandQueue<T: Config> =
|
||||
StorageValue<_, VecDeque<EnqueuedOrder>, ValueQuery, OnDemandQueueOnEmpty<T>>;
|
||||
|
||||
/// Maps a `ParaId` to `CoreIndex` and keeps track of how many assignments the scheduler has in
|
||||
/// it's lookahead. Keeping track of this affinity prevents parallel execution of the same
|
||||
@@ -182,9 +191,6 @@ pub mod pallet {
|
||||
/// The current spot price is higher than the max amount specified in the `place_order`
|
||||
/// call, making it invalid.
|
||||
SpotPriceHigherThanMaxAmount,
|
||||
/// There are no on demand cores available. `place_order` will not add anything to the
|
||||
/// queue.
|
||||
NoOnDemandCores,
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
@@ -248,7 +254,6 @@ pub mod pallet {
|
||||
/// - `InvalidParaId`
|
||||
/// - `QueueFull`
|
||||
/// - `SpotPriceHigherThanMaxAmount`
|
||||
/// - `NoOnDemandCores`
|
||||
///
|
||||
/// Events:
|
||||
/// - `SpotOrderPlaced`
|
||||
@@ -276,7 +281,6 @@ pub mod pallet {
|
||||
/// - `InvalidParaId`
|
||||
/// - `QueueFull`
|
||||
/// - `SpotPriceHigherThanMaxAmount`
|
||||
/// - `NoOnDemandCores`
|
||||
///
|
||||
/// Events:
|
||||
/// - `SpotOrderPlaced`
|
||||
@@ -311,7 +315,6 @@ where
|
||||
/// - `InvalidParaId`
|
||||
/// - `QueueFull`
|
||||
/// - `SpotPriceHigherThanMaxAmount`
|
||||
/// - `NoOnDemandCores`
|
||||
///
|
||||
/// Events:
|
||||
/// - `SpotOrderPlaced`
|
||||
@@ -323,9 +326,6 @@ where
|
||||
) -> DispatchResult {
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
|
||||
// Are there any schedulable cores in this session
|
||||
ensure!(config.on_demand_cores > 0, Error::<T>::NoOnDemandCores);
|
||||
|
||||
// Traffic always falls back to 1.0
|
||||
let traffic = SpotTraffic::<T>::get();
|
||||
|
||||
@@ -344,17 +344,15 @@ where
|
||||
existence_requirement,
|
||||
)?;
|
||||
|
||||
let assignment = Assignment::new(para_id);
|
||||
let order = EnqueuedOrder::new(para_id);
|
||||
|
||||
let res = Pallet::<T>::add_on_demand_assignment(assignment, QueuePushDirection::Back);
|
||||
let res = Pallet::<T>::add_on_demand_order(order, QueuePushDirection::Back);
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
Pallet::<T>::deposit_event(Event::<T>::OnDemandOrderPlaced { para_id, spot_price });
|
||||
return Ok(())
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
if res.is_ok() {
|
||||
Pallet::<T>::deposit_event(Event::<T>::OnDemandOrderPlaced { para_id, spot_price });
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// The spot price multiplier. This is based on the transaction fee calculations defined in:
|
||||
@@ -428,10 +426,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an assignment to the on demand queue.
|
||||
/// Adds an order to the on demand queue.
|
||||
///
|
||||
/// Paramenters:
|
||||
/// - `assignment`: The on demand assignment to add to the queue.
|
||||
/// - `order`: The `EnqueuedOrder` to add to the queue.
|
||||
/// - `location`: Whether to push this entry to the back or the front of the queue. Pushing an
|
||||
/// entry to the front of the queue is only used when the scheduler wants to push back an
|
||||
/// entry it has already popped.
|
||||
@@ -441,12 +439,12 @@ where
|
||||
/// Errors:
|
||||
/// - `InvalidParaId`
|
||||
/// - `QueueFull`
|
||||
pub fn add_on_demand_assignment(
|
||||
assignment: Assignment,
|
||||
fn add_on_demand_order(
|
||||
order: EnqueuedOrder,
|
||||
location: QueuePushDirection,
|
||||
) -> Result<(), DispatchError> {
|
||||
// Only parathreads are valid paraids for on the go parachains.
|
||||
ensure!(<paras::Pallet<T>>::is_parathread(assignment.para_id), Error::<T>::InvalidParaId);
|
||||
ensure!(<paras::Pallet<T>>::is_parathread(order.para_id), Error::<T>::InvalidParaId);
|
||||
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
|
||||
@@ -454,8 +452,8 @@ where
|
||||
// Abort transaction if queue is too large
|
||||
ensure!(Self::queue_size() < config.on_demand_queue_max_size, Error::<T>::QueueFull);
|
||||
match location {
|
||||
QueuePushDirection::Back => queue.push_back(assignment),
|
||||
QueuePushDirection::Front => queue.push_front(assignment),
|
||||
QueuePushDirection::Back => queue.push_back(order),
|
||||
QueuePushDirection::Front => queue.push_front(order),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
@@ -480,7 +478,8 @@ where
|
||||
}
|
||||
|
||||
/// Getter for the order queue.
|
||||
pub fn get_queue() -> VecDeque<Assignment> {
|
||||
#[cfg(test)]
|
||||
fn get_queue() -> VecDeque<EnqueuedOrder> {
|
||||
OnDemandQueue::<T>::get()
|
||||
}
|
||||
|
||||
@@ -528,12 +527,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn session_core_count() -> u32 {
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
config.on_demand_cores
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Take the next queued entry that is available for a given core index.
|
||||
/// Invalidates and removes orders with a `para_id` that is not `ParaLifecycle::Parathread`
|
||||
/// but only in [0..P] range slice of the order queue, where P is the element that is
|
||||
@@ -541,20 +535,8 @@ impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `core_idx`: The core index
|
||||
/// - `previous_paraid`: Which paraid was previously processed on the requested core. Is None if
|
||||
/// nothing was processed on the core.
|
||||
fn pop_assignment_for_core(
|
||||
core_idx: CoreIndex,
|
||||
previous_para: Option<ParaId>,
|
||||
) -> Option<Assignment> {
|
||||
// Only decrease the affinity of the previous para if it exists.
|
||||
// A nonexistant `ParaId` indicates that the scheduler has not processed any
|
||||
// `ParaId` this session.
|
||||
if let Some(previous_para_id) = previous_para {
|
||||
Pallet::<T>::decrease_affinity(previous_para_id, core_idx)
|
||||
}
|
||||
|
||||
let mut queue: VecDeque<Assignment> = OnDemandQueue::<T>::get();
|
||||
pub fn pop_assignment_for_core(core_idx: CoreIndex) -> Option<Assignment> {
|
||||
let mut queue: VecDeque<EnqueuedOrder> = OnDemandQueue::<T>::get();
|
||||
|
||||
let mut invalidated_para_id_indexes: Vec<usize> = vec![];
|
||||
|
||||
@@ -591,28 +573,28 @@ impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
// Write changes to storage.
|
||||
OnDemandQueue::<T>::set(queue);
|
||||
|
||||
popped
|
||||
popped.map(|p| Assignment::Pool { para_id: p.para_id, core_index: core_idx })
|
||||
}
|
||||
|
||||
/// Push an assignment back to the queue.
|
||||
/// Typically used on session boundaries.
|
||||
/// Report that the `para_id` & `core_index` combination was processed.
|
||||
pub fn report_processed(para_id: ParaId, core_index: CoreIndex) {
|
||||
Pallet::<T>::decrease_affinity(para_id, core_index)
|
||||
}
|
||||
|
||||
/// Push an assignment back to the front of the queue.
|
||||
///
|
||||
/// The assignment has not been processed yet. Typically used on session boundaries.
|
||||
/// Parameters:
|
||||
/// - `core_idx`: The core index
|
||||
/// - `assignment`: The on demand assignment.
|
||||
fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment) {
|
||||
Pallet::<T>::decrease_affinity(assignment.para_id, core_idx);
|
||||
pub fn push_back_assignment(para_id: ParaId, core_index: CoreIndex) {
|
||||
Pallet::<T>::decrease_affinity(para_id, core_index);
|
||||
// Skip the queue on push backs from scheduler
|
||||
match Pallet::<T>::add_on_demand_assignment(assignment, QueuePushDirection::Front) {
|
||||
match Pallet::<T>::add_on_demand_order(
|
||||
EnqueuedOrder::new(para_id),
|
||||
QueuePushDirection::Front,
|
||||
) {
|
||||
Ok(_) => {},
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
AssignmentProviderConfig {
|
||||
max_availability_timeouts: config.on_demand_retries,
|
||||
ttl: config.on_demand_ttl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ use crate::{
|
||||
System, Test,
|
||||
},
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
scheduler::common::Assignment,
|
||||
};
|
||||
use frame_support::{assert_noop, assert_ok, error::BadOrigin};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
@@ -75,7 +74,7 @@ fn run_to_block(
|
||||
Scheduler::initializer_initialize(b + 1);
|
||||
|
||||
// In the real runtime this is expected to be called by the `InclusionInherent` pallet.
|
||||
Scheduler::update_claimqueue(BTreeMap::new(), b + 1);
|
||||
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,9 +279,9 @@ fn place_order_keep_alive_keeps_alive() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_on_demand_assignment_works() {
|
||||
fn add_on_demand_order_works() {
|
||||
let para_a = ParaId::from(111);
|
||||
let assignment = Assignment::new(para_a);
|
||||
let order = EnqueuedOrder::new(para_a);
|
||||
|
||||
let mut genesis = GenesisConfigBuilder::default();
|
||||
genesis.on_demand_max_queue_size = 1;
|
||||
@@ -292,10 +291,7 @@ fn add_on_demand_assignment_works() {
|
||||
|
||||
// `para_a` is not onboarded as a parathread yet.
|
||||
assert_noop!(
|
||||
OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment.clone(),
|
||||
QueuePushDirection::Back
|
||||
),
|
||||
OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back),
|
||||
Error::<Test>::InvalidParaId
|
||||
);
|
||||
|
||||
@@ -304,14 +300,11 @@ fn add_on_demand_assignment_works() {
|
||||
assert!(Paras::is_parathread(para_a));
|
||||
|
||||
// `para_a` is now onboarded as a valid parathread.
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment.clone(),
|
||||
QueuePushDirection::Back
|
||||
));
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back));
|
||||
|
||||
// Max queue size is 1, queue should be full.
|
||||
assert_noop!(
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment, QueuePushDirection::Back),
|
||||
OnDemandAssigner::add_on_demand_order(order, QueuePushDirection::Back),
|
||||
Error::<Test>::QueueFull
|
||||
);
|
||||
});
|
||||
@@ -330,29 +323,131 @@ fn spotqueue_push_directions() {
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
let assignment_a = Assignment { para_id: para_a };
|
||||
let assignment_b = Assignment { para_id: para_b };
|
||||
let assignment_c = Assignment { para_id: para_c };
|
||||
let order_a = EnqueuedOrder::new(para_a);
|
||||
let order_b = EnqueuedOrder::new(para_b);
|
||||
let order_c = EnqueuedOrder::new(para_c);
|
||||
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment_a.clone(),
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(
|
||||
order_a.clone(),
|
||||
QueuePushDirection::Front
|
||||
));
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment_b.clone(),
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(
|
||||
order_b.clone(),
|
||||
QueuePushDirection::Front
|
||||
));
|
||||
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment_c.clone(),
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(
|
||||
order_c.clone(),
|
||||
QueuePushDirection::Back
|
||||
));
|
||||
|
||||
assert_eq!(OnDemandAssigner::queue_size(), 3);
|
||||
assert_eq!(OnDemandAssigner::get_queue(), VecDeque::from(vec![order_b, order_a, order_c]))
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_assignment_for_core_works() {
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
let para_a = ParaId::from(111);
|
||||
let para_b = ParaId::from(110);
|
||||
schedule_blank_para(para_a, ParaKind::Parathread);
|
||||
schedule_blank_para(para_b, ParaKind::Parathread);
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
let order_a = EnqueuedOrder::new(para_a);
|
||||
let order_b = EnqueuedOrder::new(para_b);
|
||||
let assignment_a = Assignment::Pool { para_id: para_a, core_index: CoreIndex(0) };
|
||||
let assignment_b = Assignment::Pool { para_id: para_b, core_index: CoreIndex(1) };
|
||||
|
||||
// Pop should return none with empty queue
|
||||
assert_eq!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)), None);
|
||||
|
||||
// Add enough assignments to the order queue.
|
||||
for _ in 0..2 {
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
}
|
||||
|
||||
// Queue should contain orders a, b, a, b
|
||||
{
|
||||
let queue: Vec<EnqueuedOrder> = OnDemandQueue::<Test>::get().into_iter().collect();
|
||||
assert_eq!(
|
||||
queue,
|
||||
vec![order_a.clone(), order_b.clone(), order_a.clone(), order_b.clone()]
|
||||
);
|
||||
}
|
||||
|
||||
// Popped assignments should be for the correct paras and cores
|
||||
assert_eq!(
|
||||
OnDemandAssigner::get_queue(),
|
||||
VecDeque::from(vec![assignment_b, assignment_a, assignment_c])
|
||||
)
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)),
|
||||
Some(assignment_a.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(1)),
|
||||
Some(assignment_b.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)),
|
||||
Some(assignment_a.clone())
|
||||
);
|
||||
|
||||
// Queue should contain one left over order
|
||||
{
|
||||
let queue: Vec<EnqueuedOrder> = OnDemandQueue::<Test>::get().into_iter().collect();
|
||||
assert_eq!(queue, vec![order_b.clone(),]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_back_assignment_works() {
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
let para_a = ParaId::from(111);
|
||||
let para_b = ParaId::from(110);
|
||||
schedule_blank_para(para_a, ParaKind::Parathread);
|
||||
schedule_blank_para(para_b, ParaKind::Parathread);
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
let order_a = EnqueuedOrder::new(para_a);
|
||||
let order_b = EnqueuedOrder::new(para_b);
|
||||
|
||||
// Add enough assignments to the order queue.
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
// Pop order a
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0));
|
||||
|
||||
// Para a should have affinity for core 0
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1);
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().core_idx, CoreIndex(0));
|
||||
|
||||
// Queue should still contain order b
|
||||
{
|
||||
let queue: Vec<EnqueuedOrder> = OnDemandQueue::<Test>::get().into_iter().collect();
|
||||
assert_eq!(queue, vec![order_b.clone()]);
|
||||
}
|
||||
|
||||
// Push back order a
|
||||
OnDemandAssigner::push_back_assignment(para_a, CoreIndex(0));
|
||||
|
||||
// Para a should have no affinity
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).is_none(), true);
|
||||
|
||||
// Queue should contain orders a, b. A in front of b.
|
||||
{
|
||||
let queue: Vec<EnqueuedOrder> = OnDemandQueue::<Test>::get().into_iter().collect();
|
||||
assert_eq!(queue, vec![order_a.clone(), order_b.clone()]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,39 +455,38 @@ fn spotqueue_push_directions() {
|
||||
fn affinity_changes_work() {
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
let para_a = ParaId::from(111);
|
||||
let core_index = CoreIndex(0);
|
||||
schedule_blank_para(para_a, ParaKind::Parathread);
|
||||
|
||||
let order_a = EnqueuedOrder::new(para_a);
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
let assignment_a = Assignment { para_id: para_a };
|
||||
// There should be no affinity before starting.
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
|
||||
// Add enough assignments to the order queue.
|
||||
for _ in 0..10 {
|
||||
OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment_a.clone(),
|
||||
QueuePushDirection::Front,
|
||||
)
|
||||
.expect("Invalid paraid or queue full");
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Front)
|
||||
.expect("Invalid paraid or queue full");
|
||||
}
|
||||
|
||||
// There should be no affinity before the scheduler pops.
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None);
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
|
||||
// Affinity count is 1 after popping.
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1);
|
||||
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
|
||||
// Affinity count is 1 after popping with a previous para.
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1);
|
||||
assert_eq!(OnDemandAssigner::queue_size(), 8);
|
||||
|
||||
for _ in 0..3 {
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None);
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
}
|
||||
|
||||
// Affinity count is 4 after popping 3 times without a previous para.
|
||||
@@ -400,7 +494,8 @@ fn affinity_changes_work() {
|
||||
assert_eq!(OnDemandAssigner::queue_size(), 5);
|
||||
|
||||
for _ in 0..5 {
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
}
|
||||
|
||||
// Affinity count should still be 4 but queue should be empty.
|
||||
@@ -409,12 +504,14 @@ fn affinity_changes_work() {
|
||||
|
||||
// Pop 4 times and get to exactly 0 (None) affinity.
|
||||
for _ in 0..4 {
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
}
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
|
||||
// Decreasing affinity beyond 0 should still be None.
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index);
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
});
|
||||
}
|
||||
@@ -430,28 +527,28 @@ fn affinity_prohibits_parallel_scheduling() {
|
||||
|
||||
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
|
||||
|
||||
let assignment_a = Assignment { para_id: para_a };
|
||||
let assignment_b = Assignment { para_id: para_b };
|
||||
let order_a = EnqueuedOrder::new(para_a);
|
||||
let order_b = EnqueuedOrder::new(para_b);
|
||||
|
||||
// There should be no affinity before starting.
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_b).is_none());
|
||||
|
||||
// Add 2 assignments for para_a for every para_b.
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_b.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
assert_eq!(OnDemandAssigner::queue_size(), 3);
|
||||
|
||||
// Approximate having 1 core.
|
||||
for _ in 0..3 {
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None);
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0));
|
||||
}
|
||||
|
||||
// Affinity on one core is meaningless.
|
||||
@@ -463,24 +560,25 @@ fn affinity_prohibits_parallel_scheduling() {
|
||||
);
|
||||
|
||||
// Clear affinity
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a));
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_b));
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::report_processed(para_b, 0.into());
|
||||
|
||||
// Add 2 assignments for para_a for every para_b.
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
OnDemandAssigner::add_on_demand_assignment(assignment_b.clone(), QueuePushDirection::Back)
|
||||
OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back)
|
||||
.expect("Invalid paraid or queue full");
|
||||
|
||||
// Approximate having 2 cores.
|
||||
// Approximate having 3 cores. CoreIndex 2 should be unable to obtain an assignment
|
||||
for _ in 0..3 {
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None);
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(1), None);
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(0));
|
||||
OnDemandAssigner::pop_assignment_for_core(CoreIndex(1));
|
||||
assert_eq!(None, OnDemandAssigner::pop_assignment_for_core(CoreIndex(2)));
|
||||
}
|
||||
|
||||
// Affinity should be the same as before, but on different cores.
|
||||
@@ -488,38 +586,23 @@ fn affinity_prohibits_parallel_scheduling() {
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_b).unwrap().count, 1);
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().core_idx, CoreIndex(0));
|
||||
assert_eq!(OnDemandAssigner::get_affinity_map(para_b).unwrap().core_idx, CoreIndex(1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_place_order_when_no_on_demand_cores() {
|
||||
let mut genesis = GenesisConfigBuilder::default();
|
||||
genesis.on_demand_cores = 0;
|
||||
let para_id = ParaId::from(10);
|
||||
let alice = 1u64;
|
||||
let amt = 10_000_000u128;
|
||||
// Clear affinity
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::report_processed(para_a, 0.into());
|
||||
OnDemandAssigner::report_processed(para_b, 1.into());
|
||||
|
||||
new_test_ext(genesis.build()).execute_with(|| {
|
||||
schedule_blank_para(para_id, ParaKind::Parathread);
|
||||
Balances::make_free_balance_be(&alice, amt);
|
||||
|
||||
assert!(!Paras::is_parathread(para_id));
|
||||
|
||||
run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None });
|
||||
|
||||
assert!(Paras::is_parathread(para_id));
|
||||
|
||||
assert_noop!(
|
||||
OnDemandAssigner::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id),
|
||||
Error::<Test>::NoOnDemandCores
|
||||
);
|
||||
// There should be no affinity after clearing.
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_a).is_none());
|
||||
assert!(OnDemandAssigner::get_affinity_map(para_b).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() {
|
||||
let para_id = ParaId::from(10);
|
||||
let assignment = Assignment { para_id };
|
||||
let core_index = CoreIndex(0);
|
||||
let order = EnqueuedOrder::new(para_id);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
// Register the para_id as a parathread
|
||||
@@ -530,17 +613,14 @@ fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() {
|
||||
assert!(Paras::is_parathread(para_id));
|
||||
|
||||
// Add two assignments for a para_id with a valid lifecycle.
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment.clone(),
|
||||
QueuePushDirection::Back
|
||||
));
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_assignment(
|
||||
assignment.clone(),
|
||||
QueuePushDirection::Back
|
||||
));
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back));
|
||||
assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back));
|
||||
|
||||
// First pop is fine
|
||||
assert!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None) == Some(assignment));
|
||||
assert!(
|
||||
OnDemandAssigner::pop_assignment_for_core(core_index) ==
|
||||
Some(Assignment::Pool { para_id, core_index })
|
||||
);
|
||||
|
||||
// Deregister para
|
||||
assert_ok!(Paras::schedule_para_cleanup(para_id));
|
||||
@@ -551,6 +631,7 @@ fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() {
|
||||
assert!(!Paras::is_parathread(para_id));
|
||||
|
||||
// Second pop should be None.
|
||||
assert!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_id)) == None);
|
||||
OnDemandAssigner::report_processed(para_id, core_index);
|
||||
assert_eq!(OnDemandAssigner::pop_assignment_for_core(core_index), None);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,13 +17,20 @@
|
||||
//! The bulk (parachain slot auction) blockspace assignment provider.
|
||||
//! This provider is tightly coupled with the configuration and paras modules.
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock_helpers;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
use primitives::CoreIndex;
|
||||
|
||||
use crate::{
|
||||
configuration, paras,
|
||||
scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig},
|
||||
};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
|
||||
pub use pallet::*;
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
@@ -38,23 +45,18 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn session_core_count() -> u32 {
|
||||
paras::Parachains::<T>::decode_len().unwrap_or(0) as u32
|
||||
}
|
||||
|
||||
fn pop_assignment_for_core(
|
||||
core_idx: CoreIndex,
|
||||
_concluded_para: Option<ParaId>,
|
||||
) -> Option<Assignment> {
|
||||
fn pop_assignment_for_core(core_idx: CoreIndex) -> Option<Assignment> {
|
||||
<paras::Pallet<T>>::parachains()
|
||||
.get(core_idx.0 as usize)
|
||||
.copied()
|
||||
.map(|para_id| Assignment::new(para_id))
|
||||
.map(Assignment::Bulk)
|
||||
}
|
||||
|
||||
fn report_processed(_: Assignment) {}
|
||||
|
||||
/// Bulk assignment has no need to push the assignment back on a session change,
|
||||
/// this is a no-op in the case of a bulk assignment slot.
|
||||
fn push_assignment_for_core(_: CoreIndex, _: Assignment) {}
|
||||
fn push_back_assignment(_: Assignment) {}
|
||||
|
||||
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
|
||||
AssignmentProviderConfig {
|
||||
@@ -65,4 +67,13 @@ impl<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
|
||||
ttl: 10u32.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
fn get_mock_assignment(_: CoreIndex, para_id: primitives::Id) -> Assignment {
|
||||
Assignment::Bulk(para_id)
|
||||
}
|
||||
|
||||
fn session_core_count() -> u32 {
|
||||
paras::Parachains::<T>::decode_len().unwrap_or(0) as u32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! Helper functions for tests
|
||||
|
||||
use crate::{
|
||||
mock::MockGenesisConfig,
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
};
|
||||
|
||||
use primitives::{Balance, HeadData, ValidationCode};
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
fn default_genesis_config() -> MockGenesisConfig {
|
||||
MockGenesisConfig {
|
||||
configuration: crate::configuration::GenesisConfig {
|
||||
config: crate::configuration::HostConfiguration { ..Default::default() },
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenesisConfigBuilder {
|
||||
pub on_demand_cores: u32,
|
||||
pub on_demand_base_fee: Balance,
|
||||
pub on_demand_fee_variability: Perbill,
|
||||
pub on_demand_max_queue_size: u32,
|
||||
pub on_demand_target_queue_utilization: Perbill,
|
||||
pub onboarded_on_demand_chains: Vec<primitives::Id>,
|
||||
}
|
||||
|
||||
impl Default for GenesisConfigBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_demand_cores: 10,
|
||||
on_demand_base_fee: 10_000,
|
||||
on_demand_fee_variability: Perbill::from_percent(1),
|
||||
on_demand_max_queue_size: 100,
|
||||
on_demand_target_queue_utilization: Perbill::from_percent(25),
|
||||
onboarded_on_demand_chains: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenesisConfigBuilder {
|
||||
pub(super) fn build(self) -> MockGenesisConfig {
|
||||
let mut genesis = default_genesis_config();
|
||||
let config = &mut genesis.configuration.config;
|
||||
config.coretime_cores = self.on_demand_cores;
|
||||
config.on_demand_base_fee = self.on_demand_base_fee;
|
||||
config.on_demand_fee_variability = self.on_demand_fee_variability;
|
||||
config.on_demand_queue_max_size = self.on_demand_max_queue_size;
|
||||
config.on_demand_target_queue_utilization = self.on_demand_target_queue_utilization;
|
||||
|
||||
let paras = &mut genesis.paras.paras;
|
||||
for para_id in self.onboarded_on_demand_chains {
|
||||
paras.push((
|
||||
para_id,
|
||||
ParaGenesisArgs {
|
||||
genesis_head: HeadData::from(vec![0u8]),
|
||||
validation_code: ValidationCode::from(vec![0u8]),
|
||||
para_kind: ParaKind::Parathread,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
genesis
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
assigner_parachains::mock_helpers::GenesisConfigBuilder,
|
||||
initializer::SessionChangeNotification,
|
||||
mock::{
|
||||
new_test_ext, ParachainsAssigner, Paras, ParasShared, RuntimeOrigin, Scheduler, System,
|
||||
},
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
};
|
||||
use frame_support::{assert_ok, pallet_prelude::*};
|
||||
use primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
|
||||
fn schedule_blank_para(id: ParaId, parakind: ParaKind) {
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
assert_ok!(Paras::schedule_para_initialize(
|
||||
id,
|
||||
ParaGenesisArgs {
|
||||
genesis_head: Vec::new().into(),
|
||||
validation_code: validation_code.clone(),
|
||||
para_kind: parakind,
|
||||
}
|
||||
));
|
||||
|
||||
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
|
||||
}
|
||||
|
||||
fn run_to_block(
|
||||
to: BlockNumber,
|
||||
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
|
||||
) {
|
||||
while System::block_number() < to {
|
||||
let b = System::block_number();
|
||||
|
||||
Scheduler::initializer_finalize();
|
||||
Paras::initializer_finalize(b);
|
||||
|
||||
if let Some(notification) = new_session(b + 1) {
|
||||
let mut notification_with_session_index = notification;
|
||||
// We will make every session change trigger an action queue. Normally this may require
|
||||
// 2 or more session changes.
|
||||
if notification_with_session_index.session_index == SessionIndex::default() {
|
||||
notification_with_session_index.session_index = ParasShared::scheduled_session();
|
||||
}
|
||||
Paras::initializer_on_new_session(¬ification_with_session_index);
|
||||
Scheduler::initializer_on_new_session(¬ification_with_session_index);
|
||||
}
|
||||
|
||||
System::on_finalize(b);
|
||||
|
||||
System::on_initialize(b + 1);
|
||||
System::set_block_number(b + 1);
|
||||
|
||||
Paras::initializer_initialize(b + 1);
|
||||
Scheduler::initializer_initialize(b + 1);
|
||||
|
||||
// In the real runtime this is expected to be called by the `InclusionInherent` pallet.
|
||||
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// This and the scheduler test schedule_schedules_including_just_freed together
|
||||
// ensure that next_up_on_available and next_up_on_time_out will always be
|
||||
// filled with scheduler claims for lease holding parachains. (Removes the need
|
||||
// for two other scheduler tests)
|
||||
#[test]
|
||||
fn parachains_assigner_pop_assignment_is_always_some() {
|
||||
let core_index = CoreIndex(0);
|
||||
let para_id = ParaId::from(10);
|
||||
let expected_assignment = Assignment::Bulk(para_id);
|
||||
|
||||
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
|
||||
// Register the para_id as a lease holding parachain
|
||||
schedule_blank_para(para_id, ParaKind::Parachain);
|
||||
|
||||
assert!(!Paras::is_parachain(para_id));
|
||||
run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None });
|
||||
assert!(Paras::is_parachain(para_id));
|
||||
|
||||
for _ in 0..20 {
|
||||
assert!(
|
||||
ParachainsAssigner::pop_assignment_for_core(core_index) ==
|
||||
Some(expected_assignment.clone())
|
||||
);
|
||||
}
|
||||
|
||||
run_to_block(20, |n| if n == 20 { Some(Default::default()) } else { None });
|
||||
|
||||
for _ in 0..20 {
|
||||
assert!(
|
||||
ParachainsAssigner::pop_assignment_for_core(core_index) ==
|
||||
Some(expected_assignment.clone())
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
paras_inherent,
|
||||
scheduler::{
|
||||
self,
|
||||
common::{Assignment, AssignmentProviderConfig},
|
||||
common::{AssignmentProvider, AssignmentProviderConfig},
|
||||
CoreOccupied, ParasEntry,
|
||||
},
|
||||
session_info, shared,
|
||||
@@ -96,6 +96,8 @@ pub(crate) struct BenchBuilder<T: paras_inherent::Config> {
|
||||
/// Make every candidate include a code upgrade by setting this to `Some` where the interior
|
||||
/// value is the byte length of the new code.
|
||||
code_upgrade: Option<u32>,
|
||||
/// Specifies whether the claimqueue should be filled.
|
||||
fill_claimqueue: bool,
|
||||
_phantom: sp_std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -122,6 +124,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
|
||||
dispute_sessions: Default::default(),
|
||||
backed_and_concluding_cores: Default::default(),
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: true,
|
||||
_phantom: sp_std::marker::PhantomData::<T>,
|
||||
}
|
||||
}
|
||||
@@ -225,6 +228,13 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
|
||||
self.max_validators() / self.max_validators_per_core()
|
||||
}
|
||||
|
||||
/// Set whether the claim queue should be filled.
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
pub(crate) fn set_fill_claimqueue(mut self, f: bool) -> Self {
|
||||
self.fill_claimqueue = f;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the minimum number of validity votes in order for a backed candidate to be included.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub(crate) fn fallback_min_validity_votes() -> u32 {
|
||||
@@ -643,7 +653,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
|
||||
})
|
||||
.collect();
|
||||
|
||||
DisputeStatementSet { candidate_hash: candidate_hash, session, statements }
|
||||
DisputeStatementSet { candidate_hash, session, statements }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -663,14 +673,18 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
|
||||
inclusion::PendingAvailability::<T>::remove_all(None);
|
||||
|
||||
// We don't allow a core to have both disputes and be marked fully available at this block.
|
||||
let cores = self.max_cores();
|
||||
let max_cores = self.max_cores();
|
||||
let used_cores =
|
||||
(self.dispute_sessions.len() + self.backed_and_concluding_cores.len()) as u32;
|
||||
assert!(used_cores <= cores);
|
||||
assert!(used_cores <= max_cores);
|
||||
let fill_claimqueue = self.fill_claimqueue;
|
||||
|
||||
// NOTE: there is an n+2 session delay for these actions to take effect.
|
||||
// We are currently in Session 0, so these changes will take effect in Session 2.
|
||||
Self::setup_para_ids(used_cores);
|
||||
configuration::ActiveConfig::<T>::mutate(|c| {
|
||||
c.coretime_cores = used_cores;
|
||||
});
|
||||
|
||||
let validator_ids = Self::generate_validator_pairs(self.max_validators());
|
||||
let target_session = SessionIndex::from(self.target_session);
|
||||
@@ -702,13 +716,33 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
|
||||
.map(|i| {
|
||||
let AssignmentProviderConfig { ttl, .. } =
|
||||
scheduler::Pallet::<T>::assignment_provider_config(CoreIndex(i));
|
||||
CoreOccupied::Paras(ParasEntry::new(
|
||||
Assignment::new(ParaId::from(i as u32)),
|
||||
now + ttl,
|
||||
))
|
||||
// Load an assignment into provider so that one is present to pop
|
||||
let assignment = <T as scheduler::Config>::AssignmentProvider::get_mock_assignment(
|
||||
CoreIndex(i),
|
||||
ParaId::from(i),
|
||||
);
|
||||
CoreOccupied::Paras(ParasEntry::new(assignment, now + ttl))
|
||||
})
|
||||
.collect();
|
||||
scheduler::AvailabilityCores::<T>::set(cores);
|
||||
if fill_claimqueue {
|
||||
// Add items to claim queue as well:
|
||||
let cores = (0..used_cores)
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let AssignmentProviderConfig { ttl, .. } =
|
||||
scheduler::Pallet::<T>::assignment_provider_config(CoreIndex(i));
|
||||
// Load an assignment into provider so that one is present to pop
|
||||
let assignment =
|
||||
<T as scheduler::Config>::AssignmentProvider::get_mock_assignment(
|
||||
CoreIndex(i),
|
||||
ParaId::from(i),
|
||||
);
|
||||
(CoreIndex(i), [ParasEntry::new(assignment, now + ttl)].into())
|
||||
})
|
||||
.collect();
|
||||
scheduler::ClaimQueue::<T>::set(cores);
|
||||
}
|
||||
|
||||
Bench::<T> {
|
||||
data: ParachainsInherentData {
|
||||
|
||||
@@ -172,8 +172,8 @@ pub struct HostConfiguration<BlockNumber> {
|
||||
/// How long to keep code on-chain, in blocks. This should be sufficiently long that disputes
|
||||
/// have concluded.
|
||||
pub code_retention_period: BlockNumber,
|
||||
/// The amount of execution cores to dedicate to on demand execution.
|
||||
pub on_demand_cores: u32,
|
||||
/// How many cores are managed by the coretime chain.
|
||||
pub coretime_cores: u32,
|
||||
/// The number of retries that a on demand author has to submit their block.
|
||||
pub on_demand_retries: u32,
|
||||
/// The maximum queue size of the pay as you go module.
|
||||
@@ -284,7 +284,7 @@ impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber
|
||||
max_code_size: Default::default(),
|
||||
max_pov_size: Default::default(),
|
||||
max_head_data_size: Default::default(),
|
||||
on_demand_cores: Default::default(),
|
||||
coretime_cores: Default::default(),
|
||||
on_demand_retries: Default::default(),
|
||||
scheduling_lookahead: 1,
|
||||
max_validators_per_core: Default::default(),
|
||||
@@ -665,17 +665,18 @@ pub mod pallet {
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the number of on demand execution cores.
|
||||
/// Set the number of coretime execution cores.
|
||||
///
|
||||
/// Note that this configuration is managed by the coretime chain. Only manually change
|
||||
/// this, if you really know what you are doing!
|
||||
#[pallet::call_index(6)]
|
||||
#[pallet::weight((
|
||||
T::WeightInfo::set_config_with_u32(),
|
||||
DispatchClass::Operational,
|
||||
))]
|
||||
pub fn set_on_demand_cores(origin: OriginFor<T>, new: u32) -> DispatchResult {
|
||||
pub fn set_coretime_cores(origin: OriginFor<T>, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::schedule_config_update(|config| {
|
||||
config.on_demand_cores = new;
|
||||
})
|
||||
Self::set_coretime_cores_unchecked(new)
|
||||
}
|
||||
|
||||
/// Set the number of retries for a particular on demand.
|
||||
@@ -1245,6 +1246,17 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Set coretime cores.
|
||||
///
|
||||
/// To be used if authorization is checked otherwise.
|
||||
pub fn set_coretime_cores_unchecked(new: u32) -> DispatchResult {
|
||||
Self::schedule_config_update(|config| {
|
||||
config.coretime_cores = new;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn integrity_test() {
|
||||
|
||||
@@ -126,7 +126,7 @@ hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channe
|
||||
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
|
||||
hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size,
|
||||
code_retention_period : pre.code_retention_period,
|
||||
on_demand_cores : pre.on_demand_cores,
|
||||
coretime_cores : pre.on_demand_cores,
|
||||
on_demand_retries : pre.on_demand_retries,
|
||||
group_rotation_frequency : pre.group_rotation_frequency,
|
||||
paras_availability_period : pre.paras_availability_period,
|
||||
@@ -222,7 +222,7 @@ mod tests {
|
||||
assert_eq!(v11.n_delay_tranches, 25);
|
||||
assert_eq!(v11.minimum_validation_upgrade_delay, 5);
|
||||
assert_eq!(v11.group_rotation_frequency, 20);
|
||||
assert_eq!(v11.on_demand_cores, 0);
|
||||
assert_eq!(v11.coretime_cores, 0);
|
||||
assert_eq!(v11.on_demand_base_fee, 10_000_000);
|
||||
assert_eq!(v11.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES);
|
||||
assert_eq!(v11.approval_voting_params.max_approval_coalesce_count, 1);
|
||||
@@ -286,7 +286,7 @@ mod tests {
|
||||
assert_eq!(v10.hrmp_max_parachain_inbound_channels , v11.hrmp_max_parachain_inbound_channels);
|
||||
assert_eq!(v10.hrmp_channel_max_message_size , v11.hrmp_channel_max_message_size);
|
||||
assert_eq!(v10.code_retention_period , v11.code_retention_period);
|
||||
assert_eq!(v10.on_demand_cores , v11.on_demand_cores);
|
||||
assert_eq!(v10.coretime_cores , v11.coretime_cores);
|
||||
assert_eq!(v10.on_demand_retries , v11.on_demand_retries);
|
||||
assert_eq!(v10.group_rotation_frequency , v11.group_rotation_frequency);
|
||||
assert_eq!(v10.paras_availability_period , v11.paras_availability_period);
|
||||
|
||||
@@ -283,7 +283,7 @@ fn setting_pending_config_members() {
|
||||
max_code_size: 100_000,
|
||||
max_pov_size: 1024,
|
||||
max_head_data_size: 1_000,
|
||||
on_demand_cores: 2,
|
||||
coretime_cores: 2,
|
||||
on_demand_retries: 5,
|
||||
group_rotation_frequency: 20,
|
||||
paras_availability_period: 10,
|
||||
@@ -342,7 +342,7 @@ fn setting_pending_config_members() {
|
||||
Configuration::set_max_pov_size(RuntimeOrigin::root(), new_config.max_pov_size).unwrap();
|
||||
Configuration::set_max_head_data_size(RuntimeOrigin::root(), new_config.max_head_data_size)
|
||||
.unwrap();
|
||||
Configuration::set_on_demand_cores(RuntimeOrigin::root(), new_config.on_demand_cores)
|
||||
Configuration::set_coretime_cores(RuntimeOrigin::root(), new_config.coretime_cores)
|
||||
.unwrap();
|
||||
Configuration::set_on_demand_retries(RuntimeOrigin::root(), new_config.on_demand_retries)
|
||||
.unwrap();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! On demand assigner pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support::traits::OriginTrait;
|
||||
use pallet_broker::CoreIndex as BrokerCoreIndex;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
use assigner_coretime::PartsOf57600;
|
||||
|
||||
#[benchmark]
|
||||
fn request_core_count() {
|
||||
// Setup
|
||||
let root_origin = <T as frame_system::Config>::RuntimeOrigin::root();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
root_origin as <T as frame_system::Config>::RuntimeOrigin,
|
||||
// random core count
|
||||
100,
|
||||
)
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn assign_core(s: Linear<1, 100>) {
|
||||
// Setup
|
||||
let root_origin = <T as frame_system::Config>::RuntimeOrigin::root();
|
||||
|
||||
// Use parameterized assignment count
|
||||
let mut assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![0u16; s as usize - 1]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, parts)| {
|
||||
(CoreAssignment::Task(index as u32), PartsOf57600::new_saturating(parts))
|
||||
})
|
||||
.collect();
|
||||
// Parts must add up to exactly 57600. Here we add all the parts in one assignment, as
|
||||
// it won't effect the weight and splitting up the parts into even groupings may not
|
||||
// work for every value `s`.
|
||||
assignments.push((CoreAssignment::Task(s as u32), PartsOf57600::FULL));
|
||||
|
||||
let core_index: BrokerCoreIndex = 0;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
root_origin as <T as frame_system::Config>::RuntimeOrigin,
|
||||
core_index,
|
||||
BlockNumberFor::<T>::from(5u32),
|
||||
assignments,
|
||||
Some(BlockNumberFor::<T>::from(20u32)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! Migrations for the Coretime pallet.
|
||||
|
||||
pub use v_coretime::{GetLegacyLease, MigrateToCoretime};
|
||||
|
||||
mod v_coretime {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use crate::scheduler::common::AssignmentProvider;
|
||||
use crate::{
|
||||
assigner_coretime, configuration,
|
||||
coretime::{mk_coretime_call, Config, PartsOf57600, WeightInfo},
|
||||
paras,
|
||||
};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use frame_support::ensure;
|
||||
use frame_support::{
|
||||
traits::{OnRuntimeUpgrade, PalletInfoAccess, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
use pallet_broker::{CoreAssignment, CoreMask, ScheduleItem};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use parity_scale_codec::Decode;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_parachain_primitives::primitives::IsSystem;
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
use sp_arithmetic::traits::SaturatedConversion;
|
||||
use sp_core::Get;
|
||||
use sp_runtime::BoundedVec;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_std::vec::Vec;
|
||||
use sp_std::{iter, prelude::*, result};
|
||||
use xcm::v3::{
|
||||
send_xcm, Instruction, Junction, Junctions, MultiLocation, SendError, WeightLimit, Xcm,
|
||||
};
|
||||
|
||||
/// Return information about a legacy lease of a parachain.
|
||||
pub trait GetLegacyLease<N> {
|
||||
/// If parachain is a lease holding parachain, return the block at which the lease expires.
|
||||
fn get_parachain_lease_in_blocks(para: ParaId) -> Option<N>;
|
||||
}
|
||||
|
||||
/// Migrate a chain to use coretime.
|
||||
///
|
||||
/// This assumes that the `Coretime` and the `AssignerCoretime` pallets are added at the same
|
||||
/// time to a runtime.
|
||||
pub struct MigrateToCoretime<T, SendXcm, LegacyLease>(
|
||||
sp_std::marker::PhantomData<(T, SendXcm, LegacyLease)>,
|
||||
);
|
||||
|
||||
impl<T: Config, SendXcm: xcm::v3::SendXcm, LegacyLease: GetLegacyLease<BlockNumberFor<T>>>
|
||||
MigrateToCoretime<T, SendXcm, LegacyLease>
|
||||
{
|
||||
fn already_migrated() -> bool {
|
||||
// We are using the assigner coretime because the coretime pallet doesn't has any
|
||||
// storage data. But both pallets are introduced at the same time, so this is fine.
|
||||
let name_hash = assigner_coretime::Pallet::<T>::name_hash();
|
||||
let mut next_key = name_hash.to_vec();
|
||||
let storage_version_key = StorageVersion::storage_key::<assigner_coretime::Pallet<T>>();
|
||||
|
||||
loop {
|
||||
match sp_io::storage::next_key(&next_key) {
|
||||
// StorageVersion is initialized before, so we need to ingore it.
|
||||
Some(key) if &key == &storage_version_key => {
|
||||
next_key = key;
|
||||
},
|
||||
// If there is any other key with the prefix of the pallet,
|
||||
// we already have executed the migration.
|
||||
Some(key) if key.starts_with(&name_hash) => {
|
||||
log::info!("`MigrateToCoretime` already executed!");
|
||||
return true
|
||||
},
|
||||
// Any other key/no key means that we did not yet have migrated.
|
||||
None | Some(_) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: Config + crate::dmp::Config,
|
||||
SendXcm: xcm::v3::SendXcm,
|
||||
LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
|
||||
> OnRuntimeUpgrade for MigrateToCoretime<T, SendXcm, LegacyLease>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
if Self::already_migrated() {
|
||||
return Weight::zero()
|
||||
}
|
||||
|
||||
log::info!("Migrating existing parachains to coretime.");
|
||||
migrate_to_coretime::<T, SendXcm, LegacyLease>()
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
|
||||
if Self::already_migrated() {
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
|
||||
let legacy_paras = paras::Parachains::<T>::get();
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
let total_core_count = config.coretime_cores + legacy_paras.len() as u32;
|
||||
|
||||
let dmp_queue_size =
|
||||
crate::dmp::Pallet::<T>::dmq_contents(T::BrokerId::get().into()).len() as u32;
|
||||
|
||||
let total_core_count = total_core_count as u32;
|
||||
|
||||
Ok((total_core_count, dmp_queue_size).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
|
||||
if state.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
log::trace!("Running post_upgrade()");
|
||||
|
||||
let (prev_core_count, prev_dmp_queue_size) =
|
||||
<(u32, u32)>::decode(&mut &state[..]).unwrap();
|
||||
|
||||
let dmp_queue_size =
|
||||
crate::dmp::Pallet::<T>::dmq_contents(T::BrokerId::get().into()).len() as u32;
|
||||
let new_core_count = assigner_coretime::Pallet::<T>::session_core_count();
|
||||
ensure!(new_core_count == prev_core_count, "Total number of cores need to not change.");
|
||||
ensure!(
|
||||
dmp_queue_size == prev_dmp_queue_size + 1,
|
||||
"There should have been enqueued one DMP message."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate to Coretime.
|
||||
//
|
||||
// NOTE: Also migrates coretime_cores config value in configuration::ActiveConfig.
|
||||
fn migrate_to_coretime<
|
||||
T: Config,
|
||||
SendXcm: xcm::v3::SendXcm,
|
||||
LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
|
||||
>() -> Weight {
|
||||
let legacy_paras = paras::Pallet::<T>::parachains();
|
||||
let legacy_count = legacy_paras.len() as u32;
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
for (core, para_id) in legacy_paras.into_iter().enumerate() {
|
||||
let r = assigner_coretime::Pallet::<T>::assign_core(
|
||||
CoreIndex(core as u32),
|
||||
now,
|
||||
vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)],
|
||||
None,
|
||||
);
|
||||
if let Err(err) = r {
|
||||
log::error!(
|
||||
"Creating assignment for existing para failed: {:?}, error: {:?}",
|
||||
para_id,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
// coretime_cores was on_demand_cores until now:
|
||||
for on_demand in 0..config.coretime_cores {
|
||||
let core = CoreIndex(legacy_count.saturating_add(on_demand as _));
|
||||
let r = assigner_coretime::Pallet::<T>::assign_core(
|
||||
core,
|
||||
now,
|
||||
vec![(CoreAssignment::Pool, PartsOf57600::FULL)],
|
||||
None,
|
||||
);
|
||||
if let Err(err) = r {
|
||||
log::error!("Creating assignment for existing on-demand core, failed: {:?}", err);
|
||||
}
|
||||
}
|
||||
let total_cores = config.coretime_cores + legacy_count;
|
||||
configuration::ActiveConfig::<T>::mutate(|c| {
|
||||
c.coretime_cores = total_cores;
|
||||
});
|
||||
|
||||
if let Err(err) = migrate_send_assignments_to_coretime_chain::<T, SendXcm, LegacyLease>() {
|
||||
log::error!("Sending legacy chain data to coretime chain failed: {:?}", err);
|
||||
}
|
||||
|
||||
let single_weight = <T as Config>::WeightInfo::assign_core(1);
|
||||
single_weight
|
||||
.saturating_mul(u64::from(legacy_count.saturating_add(config.coretime_cores)))
|
||||
// Second read from sending assignments to the coretime chain.
|
||||
.saturating_add(T::DbWeight::get().reads_writes(2, 1))
|
||||
}
|
||||
|
||||
fn migrate_send_assignments_to_coretime_chain<
|
||||
T: Config,
|
||||
SendXcm: xcm::v3::SendXcm,
|
||||
LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
|
||||
>() -> result::Result<(), SendError> {
|
||||
let legacy_paras = paras::Pallet::<T>::parachains();
|
||||
let legacy_paras_count = legacy_paras.len();
|
||||
let (system_chains, lease_holding): (Vec<_>, Vec<_>) =
|
||||
legacy_paras.into_iter().partition(IsSystem::is_system);
|
||||
|
||||
let reservations = system_chains.into_iter().map(|p| {
|
||||
let schedule = BoundedVec::truncate_from(vec![ScheduleItem {
|
||||
mask: CoreMask::complete(),
|
||||
assignment: CoreAssignment::Task(p.into()),
|
||||
}]);
|
||||
mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule))
|
||||
});
|
||||
|
||||
let leases = lease_holding.into_iter().filter_map(|p| {
|
||||
log::trace!(target: "coretime-migration", "Preparing sending of lease holding para {:?}", p);
|
||||
let Some(valid_until) = LegacyLease::get_parachain_lease_in_blocks(p) else {
|
||||
log::error!("Lease holding chain with no lease information?!");
|
||||
return None
|
||||
};
|
||||
let valid_until: u32 = match valid_until.try_into() {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
log::error!("Converting block number to u32 failed!");
|
||||
return None
|
||||
},
|
||||
};
|
||||
// We assume the coretime chain set this parameter to the recommened value in RFC-1:
|
||||
const TIME_SLICE_PERIOD: u32 = 80;
|
||||
let round_up = if valid_until % TIME_SLICE_PERIOD > 0 { 1 } else { 0 };
|
||||
let time_slice = valid_until / TIME_SLICE_PERIOD + TIME_SLICE_PERIOD * round_up;
|
||||
log::trace!(target: "coretime-migration", "Sending of lease holding para {:?}, valid_until: {:?}, time_slice: {:?}", p, valid_until, time_slice);
|
||||
Some(mk_coretime_call(crate::coretime::CoretimeCalls::SetLease(p.into(), time_slice)))
|
||||
});
|
||||
|
||||
let core_count: u16 = configuration::Pallet::<T>::config().coretime_cores.saturated_into();
|
||||
let set_core_count = iter::once(mk_coretime_call(
|
||||
crate::coretime::CoretimeCalls::NotifyCoreCount(core_count),
|
||||
));
|
||||
|
||||
let pool = (legacy_paras_count..core_count.into()).map(|_| {
|
||||
let schedule = BoundedVec::truncate_from(vec![ScheduleItem {
|
||||
mask: CoreMask::complete(),
|
||||
assignment: CoreAssignment::Pool,
|
||||
}]);
|
||||
// Reserved cores will come before lease cores, so cores will change their assignments
|
||||
// when coretime chain sends us their assign_core calls -> Good test.
|
||||
mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule))
|
||||
});
|
||||
|
||||
let message_content = iter::once(Instruction::UnpaidExecution {
|
||||
weight_limit: WeightLimit::Unlimited,
|
||||
check_origin: None,
|
||||
})
|
||||
.chain(reservations)
|
||||
.chain(pool)
|
||||
.chain(leases)
|
||||
.chain(set_core_count)
|
||||
.collect();
|
||||
|
||||
let message = Xcm(message_content);
|
||||
|
||||
send_xcm::<SendXcm>(
|
||||
MultiLocation {
|
||||
parents: 0,
|
||||
interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())),
|
||||
},
|
||||
message,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright (C) 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/>.
|
||||
|
||||
//! Extrinsics implementing the relay chain side of the Coretime interface.
|
||||
//!
|
||||
//! <https://github.com/polkadot-fellows/RFCs/blob/main/text/0005-coretime-interface.md>
|
||||
|
||||
use sp_std::{prelude::*, result};
|
||||
|
||||
use frame_support::{pallet_prelude::*, traits::Currency};
|
||||
use frame_system::pallet_prelude::*;
|
||||
pub use pallet::*;
|
||||
use pallet_broker::{CoreAssignment, CoreIndex as BrokerCoreIndex};
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
use sp_arithmetic::traits::SaturatedConversion;
|
||||
use xcm::v3::{
|
||||
send_xcm, Instruction, Junction, Junctions, MultiLocation, OriginKind, SendXcm, Xcm,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assigner_coretime::{self, PartsOf57600},
|
||||
initializer::{OnNewSession, SessionChangeNotification},
|
||||
origin::{ensure_parachain, Origin},
|
||||
};
|
||||
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn request_core_count() -> Weight;
|
||||
//fn request_revenue_info_at() -> Weight;
|
||||
//fn credit_account() -> Weight;
|
||||
fn assign_core(s: u32) -> Weight;
|
||||
}
|
||||
|
||||
/// A weight info that is only suitable for testing.
|
||||
pub struct TestWeightInfo;
|
||||
|
||||
impl WeightInfo for TestWeightInfo {
|
||||
fn request_core_count() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
// TODO: Add real benchmarking functionality for each of these to
|
||||
// benchmarking.rs, then uncomment here and in trait definition.
|
||||
/*fn request_revenue_info_at() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
fn credit_account() -> Weight {
|
||||
Weight::MAX
|
||||
}*/
|
||||
fn assign_core(_s: u32) -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
/// Broker pallet index on the coretime chain. Used to
|
||||
///
|
||||
/// construct remote calls. The codec index must correspond to the index of `Broker` in the
|
||||
/// `construct_runtime` of the coretime chain.
|
||||
#[derive(Encode, Decode)]
|
||||
enum BrokerRuntimePallets {
|
||||
#[codec(index = 50)]
|
||||
Broker(CoretimeCalls),
|
||||
}
|
||||
|
||||
/// Call encoding for the calls needed from the Broker pallet.
|
||||
#[derive(Encode, Decode)]
|
||||
enum CoretimeCalls {
|
||||
#[codec(index = 1)]
|
||||
Reserve(pallet_broker::Schedule),
|
||||
#[codec(index = 3)]
|
||||
SetLease(pallet_broker::TaskId, pallet_broker::Timeslice),
|
||||
#[codec(index = 19)]
|
||||
NotifyCoreCount(u16),
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use crate::configuration;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + assigner_coretime::Config {
|
||||
type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
|
||||
+ Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
/// The runtime's definition of a Currency.
|
||||
type Currency: Currency<Self::AccountId>;
|
||||
/// The ParaId of the broker system parachain.
|
||||
#[pallet::constant]
|
||||
type BrokerId: Get<u32>;
|
||||
/// Something that provides the weight of this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
type SendXcm: SendXcm;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// The broker chain has asked for revenue information for a specific block.
|
||||
RevenueInfoRequested { when: BlockNumberFor<T> },
|
||||
/// A core has received a new assignment from the broker chain.
|
||||
CoreAssigned { core: CoreIndex },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The paraid making the call is not the coretime brokerage system parachain.
|
||||
NotBroker,
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::weight(<T as Config>::WeightInfo::request_core_count())]
|
||||
#[pallet::call_index(1)]
|
||||
pub fn request_core_count(origin: OriginFor<T>, count: u16) -> DispatchResult {
|
||||
// Ignore requests not coming from the broker parachain or root.
|
||||
Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
|
||||
|
||||
configuration::Pallet::<T>::set_coretime_cores_unchecked(u32::from(count))
|
||||
}
|
||||
|
||||
//// TODO Impl me!
|
||||
////#[pallet::weight(<T as Config>::WeightInfo::request_revenue_info_at())]
|
||||
//#[pallet::call_index(2)]
|
||||
//pub fn request_revenue_info_at(
|
||||
// origin: OriginFor<T>,
|
||||
// _when: BlockNumberFor<T>,
|
||||
//) -> DispatchResult {
|
||||
// // Ignore requests not coming from the broker parachain or root.
|
||||
// Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
|
||||
// Ok(())
|
||||
//}
|
||||
|
||||
//// TODO Impl me!
|
||||
////#[pallet::weight(<T as Config>::WeightInfo::credit_account())]
|
||||
//#[pallet::call_index(3)]
|
||||
//pub fn credit_account(
|
||||
// origin: OriginFor<T>,
|
||||
// _who: T::AccountId,
|
||||
// _amount: BalanceOf<T>,
|
||||
//) -> DispatchResult {
|
||||
// // Ignore requests not coming from the broker parachain or root.
|
||||
// Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
|
||||
// Ok(())
|
||||
//}
|
||||
|
||||
/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
|
||||
/// to be used.
|
||||
///
|
||||
/// Parameters:
|
||||
/// -`origin`: The `ExternalBrokerOrigin`, assumed to be the Broker system parachain.
|
||||
/// -`core`: The core that should be scheduled.
|
||||
/// -`begin`: The starting blockheight of the instruction.
|
||||
/// -`assignment`: How the blockspace should be utilised.
|
||||
/// -`end_hint`: An optional hint as to when this particular set of instructions will end.
|
||||
// The broker pallet's `CoreIndex` definition is `u16` but on the relay chain it's `struct
|
||||
// CoreIndex(u32)`
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::assign_core(assignment.len() as u32))]
|
||||
pub fn assign_core(
|
||||
origin: OriginFor<T>,
|
||||
core: BrokerCoreIndex,
|
||||
begin: BlockNumberFor<T>,
|
||||
assignment: Vec<(CoreAssignment, PartsOf57600)>,
|
||||
end_hint: Option<BlockNumberFor<T>>,
|
||||
) -> DispatchResult {
|
||||
// Ignore requests not coming from the broker parachain or root.
|
||||
Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
|
||||
|
||||
let core = u32::from(core).into();
|
||||
|
||||
<assigner_coretime::Pallet<T>>::assign_core(core, begin, assignment, end_hint)?;
|
||||
Self::deposit_event(Event::<T>::CoreAssigned { core });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Ensure the origin is one of Root or the `para` itself.
|
||||
fn ensure_root_or_para(
|
||||
origin: <T as frame_system::Config>::RuntimeOrigin,
|
||||
id: ParaId,
|
||||
) -> DispatchResult {
|
||||
if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone()))
|
||||
{
|
||||
// Check if matching para id...
|
||||
ensure!(caller_id == id, Error::<T>::NotBroker);
|
||||
} else {
|
||||
// Check if root...
|
||||
ensure_root(origin.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
|
||||
let old_core_count = notification.prev_config.coretime_cores;
|
||||
let new_core_count = notification.new_config.coretime_cores;
|
||||
if new_core_count != old_core_count {
|
||||
let core_count: u16 = new_core_count.saturated_into();
|
||||
let message = Xcm(vec![mk_coretime_call(
|
||||
crate::coretime::CoretimeCalls::NotifyCoreCount(core_count),
|
||||
)]);
|
||||
if let Err(err) = send_xcm::<T::SendXcm>(
|
||||
MultiLocation {
|
||||
parents: 0,
|
||||
interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())),
|
||||
},
|
||||
message,
|
||||
) {
|
||||
log::error!("Sending `NotifyCoreCount` to coretime chain failed: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
|
||||
Self::initializer_on_new_session(notification);
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruction<()> {
|
||||
Instruction::Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(1000000000, 200000),
|
||||
call: BrokerRuntimePallets::Broker(call).encode().into(),
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ use test_helpers::{dummy_collator, dummy_collator_signature, dummy_validation_co
|
||||
|
||||
fn default_config() -> HostConfiguration<BlockNumber> {
|
||||
let mut config = HostConfiguration::default();
|
||||
config.on_demand_cores = 1;
|
||||
config.coretime_cores = 1;
|
||||
config.max_code_size = 0b100000;
|
||||
config.max_head_data_size = 0b100000;
|
||||
config.group_rotation_frequency = u32::MAX;
|
||||
@@ -218,7 +218,7 @@ pub(crate) fn run_to_block(
|
||||
}
|
||||
|
||||
pub(crate) fn expected_bits() -> usize {
|
||||
Paras::parachains().len() + Configuration::config().on_demand_cores as usize
|
||||
Paras::parachains().len() + Configuration::config().coretime_cores as usize
|
||||
}
|
||||
|
||||
fn default_bitfield() -> AvailabilityBitfield {
|
||||
|
||||
@@ -60,6 +60,16 @@ pub struct SessionChangeNotification<BlockNumber> {
|
||||
pub session_index: SessionIndex,
|
||||
}
|
||||
|
||||
/// Inform something about a new session.
|
||||
pub trait OnNewSession<N> {
|
||||
/// A new session was started.
|
||||
fn on_new_session(notification: &SessionChangeNotification<N>);
|
||||
}
|
||||
|
||||
impl<N> OnNewSession<N> for () {
|
||||
fn on_new_session(_: &SessionChangeNotification<N>) {}
|
||||
}
|
||||
|
||||
/// Number of validators (not only parachain) in a session.
|
||||
pub type ValidatorSetCount = u32;
|
||||
|
||||
@@ -120,6 +130,10 @@ pub mod pallet {
|
||||
type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
|
||||
/// An origin which is allowed to force updates to parachains.
|
||||
type ForceOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
|
||||
/// Temporary hack to call `Coretime::on_new_session` on chains that support `Coretime` or
|
||||
/// to disable it on the ones that don't support it. Can be removed and replaced by a simple
|
||||
/// bound to `coretime::Config` once all chains support it.
|
||||
type CoretimeOnNewSession: OnNewSession<BlockNumberFor<Self>>;
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
@@ -271,6 +285,7 @@ impl<T: Config> Pallet<T> {
|
||||
T::SlashingHandler::initializer_on_new_session(session_index);
|
||||
dmp::Pallet::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
hrmp::Pallet::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
T::CoretimeOnNewSession::on_new_session(¬ification);
|
||||
}
|
||||
|
||||
/// Should be called when a new session occurs. Buffers the session notification to be applied
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "256")]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod assigner;
|
||||
pub mod assigner_coretime;
|
||||
pub mod assigner_on_demand;
|
||||
pub mod assigner_parachains;
|
||||
pub mod configuration;
|
||||
pub mod coretime;
|
||||
pub mod disputes;
|
||||
pub mod dmp;
|
||||
pub mod hrmp;
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
//! Mocks for all the traits.
|
||||
|
||||
use crate::{
|
||||
assigner, assigner_on_demand, assigner_parachains, configuration, disputes, dmp, hrmp,
|
||||
assigner_coretime, assigner_on_demand, assigner_parachains, configuration, coretime, disputes,
|
||||
dmp, hrmp,
|
||||
inclusion::{self, AggregateMessageOrigin, UmpQueueId},
|
||||
initializer, origin, paras,
|
||||
paras::ParaKind,
|
||||
paras_inherent, scheduler, session_info, shared, ParaId,
|
||||
paras_inherent, scheduler,
|
||||
scheduler::common::{AssignmentProvider, AssignmentProviderConfig},
|
||||
session_info, shared, ParaId,
|
||||
};
|
||||
use frame_support::pallet_prelude::*;
|
||||
use primitives::CoreIndex;
|
||||
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl, parameter_types,
|
||||
@@ -45,7 +50,9 @@ use sp_runtime::{
|
||||
transaction_validity::TransactionPriority,
|
||||
BuildStorage, FixedU128, Perbill, Permill,
|
||||
};
|
||||
use sp_std::collections::vec_deque::VecDeque;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use xcm::v3::{MultiAssets, MultiLocation, SendError, SendResult, SendXcm, Xcm, XcmHash};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlockU32<Test>;
|
||||
@@ -62,9 +69,11 @@ frame_support::construct_runtime!(
|
||||
ParaInclusion: inclusion,
|
||||
ParaInherent: paras_inherent,
|
||||
Scheduler: scheduler,
|
||||
Assigner: assigner,
|
||||
OnDemandAssigner: assigner_on_demand,
|
||||
MockAssigner: mock_assigner,
|
||||
ParachainsAssigner: assigner_parachains,
|
||||
OnDemandAssigner: assigner_on_demand,
|
||||
CoretimeAssigner: assigner_coretime,
|
||||
Coretime: coretime,
|
||||
Initializer: initializer,
|
||||
Dmp: dmp,
|
||||
Hrmp: hrmp,
|
||||
@@ -178,6 +187,7 @@ impl crate::initializer::Config for Test {
|
||||
type Randomness = TestRandomness<Self>;
|
||||
type ForceOrigin = frame_system::EnsureRoot<u64>;
|
||||
type WeightInfo = ();
|
||||
type CoretimeOnNewSession = Coretime;
|
||||
}
|
||||
|
||||
impl crate::configuration::Config for Test {
|
||||
@@ -217,6 +227,7 @@ impl crate::paras::Config for Test {
|
||||
type QueueFootprinter = ParaInclusion;
|
||||
type NextSessionRotation = TestNextSessionRotation;
|
||||
type OnNewHead = ();
|
||||
type AssignCoretime = ();
|
||||
}
|
||||
|
||||
impl crate::dmp::Config for Test {}
|
||||
@@ -288,7 +299,7 @@ impl crate::disputes::SlashingHandler<BlockNumber> for Test {
|
||||
}
|
||||
|
||||
impl crate::scheduler::Config for Test {
|
||||
type AssignmentProvider = Assigner;
|
||||
type AssignmentProvider = MockAssigner;
|
||||
}
|
||||
|
||||
pub struct TestMessageQueueWeight;
|
||||
@@ -342,17 +353,12 @@ impl pallet_message_queue::Config for Test {
|
||||
type ServiceWeight = MessageQueueServiceWeight;
|
||||
}
|
||||
|
||||
impl assigner::Config for Test {
|
||||
type ParachainsAssignmentProvider = ParachainsAssigner;
|
||||
type OnDemandAssignmentProvider = OnDemandAssigner;
|
||||
}
|
||||
|
||||
impl assigner_parachains::Config for Test {}
|
||||
|
||||
parameter_types! {
|
||||
pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1);
|
||||
}
|
||||
|
||||
impl assigner_parachains::Config for Test {}
|
||||
|
||||
impl assigner_on_demand::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
@@ -360,6 +366,37 @@ impl assigner_on_demand::Config for Test {
|
||||
type WeightInfo = crate::assigner_on_demand::TestWeightInfo;
|
||||
}
|
||||
|
||||
impl assigner_coretime::Config for Test {}
|
||||
|
||||
parameter_types! {
|
||||
pub const BrokerId: u32 = 10u32;
|
||||
}
|
||||
|
||||
impl coretime::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
type BrokerId = BrokerId;
|
||||
type WeightInfo = crate::coretime::TestWeightInfo;
|
||||
type SendXcm = DummyXcmSender;
|
||||
}
|
||||
|
||||
pub struct DummyXcmSender;
|
||||
impl SendXcm for DummyXcmSender {
|
||||
type Ticket = ();
|
||||
fn validate(
|
||||
_: &mut Option<MultiLocation>,
|
||||
_: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
Ok(((), MultiAssets::new()))
|
||||
}
|
||||
|
||||
/// Actually carry out the delivery operation for a previously validated message sending.
|
||||
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
Ok([0u8; 32])
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::inclusion::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
@@ -390,6 +427,104 @@ impl ValidatorSetWithIdentification<AccountId> for MockValidatorSet {
|
||||
type IdentificationOf = FoolIdentificationOf;
|
||||
}
|
||||
|
||||
/// A mock assigner which acts as the scheduler's `AssignmentProvider` for tests. The mock
|
||||
/// assigner provides bare minimum functionality to test scheduler internals. Since they
|
||||
/// have no direct effect on scheduler state, AssignmentProvider functions such as
|
||||
/// `push_back_assignment` can be left empty.
|
||||
pub mod mock_assigner {
|
||||
use crate::scheduler::common::Assignment;
|
||||
|
||||
use super::*;
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + configuration::Config + paras::Config {}
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type MockAssignmentQueue<T: Config> =
|
||||
StorageValue<_, VecDeque<Assignment>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type MockProviderConfig<T: Config> =
|
||||
StorageValue<_, AssignmentProviderConfig<BlockNumber>, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type MockCoreCount<T: Config> = StorageValue<_, u32, OptionQuery>;
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Adds a claim to the `MockAssignmentQueue` this claim can later be popped by the
|
||||
/// scheduler when filling the claim queue for tests.
|
||||
pub fn add_test_assignment(assignment: Assignment) {
|
||||
MockAssignmentQueue::<T>::mutate(|queue| queue.push_back(assignment));
|
||||
}
|
||||
|
||||
// This configuration needs to be customized to service `get_provider_config` in
|
||||
// scheduler tests.
|
||||
pub fn set_assignment_provider_config(config: AssignmentProviderConfig<BlockNumber>) {
|
||||
MockProviderConfig::<T>::set(Some(config));
|
||||
}
|
||||
|
||||
// Allows for customized core count in scheduler tests, rather than a core count
|
||||
// derived from on-demand config + parachain count.
|
||||
pub fn set_core_count(count: u32) {
|
||||
MockCoreCount::<T>::set(Some(count));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AssignmentProvider<BlockNumber> for Pallet<T> {
|
||||
// With regards to popping_assignments, the scheduler just needs to be tested under
|
||||
// the following two conditions:
|
||||
// 1. An assignment is provided
|
||||
// 2. No assignment is provided
|
||||
// A simple assignment queue populated to fit each test fulfills these needs.
|
||||
fn pop_assignment_for_core(_core_idx: CoreIndex) -> Option<Assignment> {
|
||||
let mut queue: VecDeque<Assignment> = MockAssignmentQueue::<T>::get();
|
||||
let front = queue.pop_front();
|
||||
// Write changes to storage.
|
||||
MockAssignmentQueue::<T>::set(queue);
|
||||
front
|
||||
}
|
||||
|
||||
// We don't care about core affinity in the test assigner
|
||||
fn report_processed(_assignment: Assignment) {}
|
||||
|
||||
// The results of this are tested in assigner_on_demand tests. No need to represent it
|
||||
// in the mock assigner.
|
||||
fn push_back_assignment(_assignment: Assignment) {}
|
||||
|
||||
// Gets the provider config we set earlier using `set_assignment_provider_config`, falling
|
||||
// back to the on demand parachain configuration if none was set.
|
||||
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumber> {
|
||||
match MockProviderConfig::<T>::get() {
|
||||
Some(config) => config,
|
||||
None => AssignmentProviderConfig {
|
||||
max_availability_timeouts: 1,
|
||||
ttl: BlockNumber::from(5u32),
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
fn get_mock_assignment(_: CoreIndex, para_id: ParaId) -> Assignment {
|
||||
Assignment::Bulk(para_id)
|
||||
}
|
||||
|
||||
fn session_core_count() -> u32 {
|
||||
MockCoreCount::<T>::get().unwrap_or(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mock_assigner::pallet::Config for Test {}
|
||||
|
||||
pub struct FoolIdentificationOf;
|
||||
impl sp_runtime::traits::Convert<AccountId, Option<()>> for FoolIdentificationOf {
|
||||
fn convert(_: AccountId) -> Option<()> {
|
||||
|
||||
@@ -506,6 +506,21 @@ impl OnNewHead for Tuple {
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign coretime to some parachain.
|
||||
///
|
||||
/// This assigns coretime to a parachain without using the coretime chain. Thus, this should only be
|
||||
/// used for testing purposes.
|
||||
pub trait AssignCoretime {
|
||||
/// ONLY USE FOR TESTING OR GENESIS.
|
||||
fn assign_coretime(id: ParaId) -> DispatchResult;
|
||||
}
|
||||
|
||||
impl AssignCoretime for () {
|
||||
fn assign_coretime(_: ParaId) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn force_set_current_code(c: u32) -> Weight;
|
||||
fn force_set_current_head(s: u32) -> Weight;
|
||||
@@ -605,6 +620,13 @@ pub mod pallet {
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Runtime hook for assigning coretime for a given parachain.
|
||||
///
|
||||
/// This is only used at genesis or by root.
|
||||
///
|
||||
/// TODO: Remove once coretime is the standard accross all chains.
|
||||
type AssignCoretime: AssignCoretime;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
@@ -838,6 +860,8 @@ pub mod pallet {
|
||||
panic!("empty validation code is not allowed in genesis");
|
||||
}
|
||||
Pallet::<T>::initialize_para_now(&mut parachains, *id, genesis_args);
|
||||
T::AssignCoretime::assign_coretime(*id)
|
||||
.expect("Assigning coretime works at genesis; qed");
|
||||
}
|
||||
// parachains are flushed on drop
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ use super::*;
|
||||
use crate::{inclusion, ParaId};
|
||||
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use sp_std::{cmp::min, collections::btree_map::BTreeMap};
|
||||
|
||||
use primitives::v6::GroupIndex;
|
||||
|
||||
use crate::builder::BenchBuilder;
|
||||
|
||||
@@ -116,7 +118,9 @@ benchmarks! {
|
||||
// There is 1 backed,
|
||||
assert_eq!(benchmark.backed_candidates.len(), 1);
|
||||
// with `v` validity votes.
|
||||
assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), v as usize);
|
||||
// let votes = v as usize;
|
||||
let votes = min(scheduler::Pallet::<T>::group_validators(GroupIndex::from(0)).unwrap().len(), v as usize);
|
||||
assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), votes);
|
||||
|
||||
benchmark.bitfields.clear();
|
||||
benchmark.disputes.clear();
|
||||
@@ -138,7 +142,7 @@ benchmarks! {
|
||||
let descriptor = backing_validators.0.descriptor();
|
||||
assert_eq!(ParaId::from(para_id), descriptor.para_id);
|
||||
assert_eq!(header.hash(), descriptor.relay_parent);
|
||||
assert_eq!(backing_validators.1.len(), v as usize);
|
||||
assert_eq!(backing_validators.1.len(), votes);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
@@ -167,11 +171,14 @@ benchmarks! {
|
||||
|
||||
let mut benchmark = scenario.data.clone();
|
||||
|
||||
// let votes = BenchBuilder::<T>::fallback_min_validity_votes() as usize;
|
||||
let votes = min(scheduler::Pallet::<T>::group_validators(GroupIndex::from(0)).unwrap().len(), BenchBuilder::<T>::fallback_min_validity_votes() as usize);
|
||||
|
||||
// There is 1 backed
|
||||
assert_eq!(benchmark.backed_candidates.len(), 1);
|
||||
assert_eq!(
|
||||
benchmark.backed_candidates.get(0).unwrap().validity_votes.len() as u32,
|
||||
BenchBuilder::<T>::fallback_min_validity_votes()
|
||||
benchmark.backed_candidates.get(0).unwrap().validity_votes.len(),
|
||||
votes,
|
||||
);
|
||||
|
||||
benchmark.bitfields.clear();
|
||||
@@ -197,8 +204,8 @@ benchmarks! {
|
||||
assert_eq!(ParaId::from(para_id), descriptor.para_id);
|
||||
assert_eq!(header.hash(), descriptor.relay_parent);
|
||||
assert_eq!(
|
||||
backing_validators.1.len() as u32,
|
||||
BenchBuilder::<T>::fallback_min_validity_votes()
|
||||
backing_validators.1.len(),
|
||||
votes,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ impl<T: Config> Pallet<T> {
|
||||
let disputed_bitfield = create_disputed_bitfield(expected_bits, freed_disputed.keys());
|
||||
|
||||
if !freed_disputed.is_empty() {
|
||||
<scheduler::Pallet<T>>::update_claimqueue(freed_disputed.clone(), now);
|
||||
<scheduler::Pallet<T>>::free_cores_and_fill_claimqueue(freed_disputed.clone(), now);
|
||||
}
|
||||
|
||||
let bitfields = sanitize_bitfields::<T>(
|
||||
@@ -580,7 +580,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
||||
|
||||
<scheduler::Pallet<T>>::update_claimqueue(freed, now);
|
||||
<scheduler::Pallet<T>>::free_cores_and_fill_claimqueue(freed, now);
|
||||
let scheduled = <scheduler::Pallet<T>>::scheduled_paras()
|
||||
.map(|(core_idx, para_id)| (para_id, core_idx))
|
||||
.collect();
|
||||
|
||||
@@ -25,7 +25,8 @@ mod enter {
|
||||
use super::*;
|
||||
use crate::{
|
||||
builder::{Bench, BenchBuilder},
|
||||
mock::{new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test},
|
||||
mock::{mock_assigner, new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test},
|
||||
scheduler::common::Assignment,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use frame_support::assert_ok;
|
||||
@@ -39,6 +40,7 @@ mod enter {
|
||||
backed_and_concluding: BTreeMap<u32, u32>,
|
||||
num_validators_per_core: u32,
|
||||
code_upgrade: Option<u32>,
|
||||
fill_claimqueue: bool,
|
||||
}
|
||||
|
||||
fn make_inherent_data(
|
||||
@@ -48,6 +50,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core,
|
||||
code_upgrade,
|
||||
fill_claimqueue,
|
||||
}: TestConfig,
|
||||
) -> Bench<Test> {
|
||||
let builder = BenchBuilder::<Test>::new()
|
||||
@@ -58,7 +61,15 @@ mod enter {
|
||||
.set_max_validators_per_core(num_validators_per_core)
|
||||
.set_dispute_statements(dispute_statements)
|
||||
.set_backed_and_concluding_cores(backed_and_concluding)
|
||||
.set_dispute_sessions(&dispute_sessions[..]);
|
||||
.set_dispute_sessions(&dispute_sessions[..])
|
||||
.set_fill_claimqueue(fill_claimqueue);
|
||||
|
||||
// Setup some assignments as needed:
|
||||
mock_assigner::Pallet::<Test>::set_core_count(builder.max_cores());
|
||||
for core_index in 0..builder.max_cores() {
|
||||
// Core index == para_id in this case
|
||||
mock_assigner::Pallet::<Test>::add_test_assignment(Assignment::Bulk(core_index.into()));
|
||||
}
|
||||
|
||||
if let Some(code_size) = code_upgrade {
|
||||
builder.set_code_upgrade(code_size).build()
|
||||
@@ -88,6 +99,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 1,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
// We expect the scenario to have cores 0 & 1 with pending availability. The backed
|
||||
@@ -238,6 +250,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -308,6 +321,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 6,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -376,6 +390,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 4,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -460,6 +475,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -544,6 +560,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -627,6 +644,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -666,15 +684,9 @@ mod enter {
|
||||
// * 3 disputes.
|
||||
assert_eq!(limit_inherent_data.disputes.len(), 2);
|
||||
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
));
|
||||
|
||||
// TODO [now]: this assertion fails with async backing runtime.
|
||||
assert_eq!(
|
||||
// The length of this vec is equal to the number of candidates, so we know our 2
|
||||
// backed candidates did not get filtered out
|
||||
// The length of this vec is equal to the number of candidates, so we know 1
|
||||
// candidate got filtered out
|
||||
Pallet::<Test>::on_chain_votes().unwrap().backing_validators_per_candidate.len(),
|
||||
1
|
||||
);
|
||||
@@ -684,6 +696,11 @@ mod enter {
|
||||
Pallet::<Test>::on_chain_votes().unwrap().session,
|
||||
2
|
||||
);
|
||||
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -713,6 +730,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -778,6 +796,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -841,6 +860,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
@@ -905,6 +925,7 @@ mod enter {
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
fill_claimqueue: false,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
|
||||
//
|
||||
// At the end of a session we clear the claim queues: Without this update call, nothing would be
|
||||
// scheduled to the client.
|
||||
<scheduler::Pallet<T>>::update_claimqueue(Vec::new(), now);
|
||||
<scheduler::Pallet<T>>::free_cores_and_fill_claimqueue(Vec::new(), now);
|
||||
|
||||
let time_out_for = <scheduler::Pallet<T>>::availability_timeout_predicate();
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ pub mod migration;
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
@@ -99,15 +99,14 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn availability_cores)]
|
||||
pub(crate) type AvailabilityCores<T: Config> =
|
||||
StorageValue<_, Vec<CoreOccupied<BlockNumberFor<T>>>, ValueQuery>;
|
||||
StorageValue<_, Vec<CoreOccupiedType<T>>, ValueQuery>;
|
||||
|
||||
/// Representation of a core in `AvailabilityCores`.
|
||||
///
|
||||
/// This is not to be confused with `CoreState` which is an enriched variant of this and exposed
|
||||
/// to the node side. It also provides information about scheduled/upcoming assignments for
|
||||
/// example and is computed on the fly in the `availability_cores` runtime call.
|
||||
#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)]
|
||||
pub enum CoreOccupied<N> {
|
||||
/// No candidate is waiting availability on this core right now (the core is not occupied).
|
||||
Free,
|
||||
@@ -115,6 +114,9 @@ pub mod pallet {
|
||||
Paras(ParasEntry<N>),
|
||||
}
|
||||
|
||||
/// Conveninece type alias for `CoreOccupied`.
|
||||
pub type CoreOccupiedType<T> = CoreOccupied<BlockNumberFor<T>>;
|
||||
|
||||
impl<N> CoreOccupied<N> {
|
||||
/// Is core free?
|
||||
pub fn is_free(&self) -> bool {
|
||||
@@ -149,16 +151,13 @@ pub mod pallet {
|
||||
/// a block. Runtime APIs should be used to determine scheduled cores/ for the upcoming block.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn claimqueue)]
|
||||
pub(crate) type ClaimQueue<T: Config> = StorageValue<
|
||||
_,
|
||||
BTreeMap<CoreIndex, VecDeque<Option<ParasEntry<BlockNumberFor<T>>>>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(crate) type ClaimQueue<T: Config> =
|
||||
StorageValue<_, BTreeMap<CoreIndex, VecDeque<ParasEntryType<T>>>, ValueQuery>;
|
||||
|
||||
/// Assignments as tracked in the claim queue.
|
||||
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)]
|
||||
pub struct ParasEntry<N = BlockNumber> {
|
||||
/// The underlying `Assignment`
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq, Clone)]
|
||||
pub struct ParasEntry<N> {
|
||||
/// The underlying [`Assignment`].
|
||||
pub assignment: Assignment,
|
||||
/// The number of times the entry has timed out in availability already.
|
||||
pub availability_timeouts: u32,
|
||||
@@ -169,37 +168,18 @@ pub mod pallet {
|
||||
pub ttl: N,
|
||||
}
|
||||
|
||||
impl<N> ParasEntry<N> {
|
||||
/// Return `Id` from the underlying `Assignment`.
|
||||
pub fn para_id(&self) -> ParaId {
|
||||
self.assignment.para_id
|
||||
}
|
||||
/// Convenience type declaration for `ParasEntry`.
|
||||
pub type ParasEntryType<T> = ParasEntry<BlockNumberFor<T>>;
|
||||
|
||||
impl<N> ParasEntry<N> {
|
||||
/// Create a new `ParasEntry`.
|
||||
pub fn new(assignment: Assignment, now: N) -> Self {
|
||||
ParasEntry { assignment, availability_timeouts: 0, ttl: now }
|
||||
}
|
||||
}
|
||||
|
||||
/// How a core is mapped to a backing group and a `ParaId`
|
||||
#[derive(Clone, Encode, Decode, PartialEq, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct CoreAssignment<BlockNumber> {
|
||||
/// The core that is assigned.
|
||||
pub core: CoreIndex,
|
||||
/// The para id and accompanying information needed to collate and back a parablock.
|
||||
pub paras_entry: ParasEntry<BlockNumber>,
|
||||
}
|
||||
|
||||
impl<BlockNumber> CoreAssignment<BlockNumber> {
|
||||
/// Returns the [`ParaId`] of the assignment.
|
||||
/// Return `Id` from the underlying `Assignment`.
|
||||
pub fn para_id(&self) -> ParaId {
|
||||
self.paras_entry.para_id()
|
||||
}
|
||||
|
||||
/// Returns the inner [`ParasEntry`] of the assignment.
|
||||
pub fn to_paras_entry(self) -> ParasEntry<BlockNumber> {
|
||||
self.paras_entry
|
||||
self.assignment.para_id()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,8 +199,6 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
type PositionInClaimqueue = u32;
|
||||
type TimedoutParas<T> = BTreeMap<CoreIndex, ParasEntry<BlockNumberFor<T>>>;
|
||||
type ConcludedParas = BTreeMap<CoreIndex, ParaId>;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Called by the initializer to initialize the scheduler pallet.
|
||||
@@ -253,7 +231,7 @@ impl<T: Config> Pallet<T> {
|
||||
);
|
||||
|
||||
AvailabilityCores::<T>::mutate(|cores| {
|
||||
cores.resize(n_cores as _, CoreOccupied::Free);
|
||||
cores.resize_with(n_cores as _, || CoreOccupied::Free);
|
||||
});
|
||||
|
||||
// shuffle validators into groups.
|
||||
@@ -298,9 +276,8 @@ impl<T: Config> Pallet<T> {
|
||||
/// with the reason for them being freed. Returns a tuple of concluded and timedout paras.
|
||||
fn free_cores(
|
||||
just_freed_cores: impl IntoIterator<Item = (CoreIndex, FreedReason)>,
|
||||
) -> (ConcludedParas, TimedoutParas<T>) {
|
||||
let mut timedout_paras: BTreeMap<CoreIndex, ParasEntry<BlockNumberFor<T>>> =
|
||||
BTreeMap::new();
|
||||
) -> (BTreeMap<CoreIndex, Assignment>, BTreeMap<CoreIndex, ParasEntryType<T>>) {
|
||||
let mut timedout_paras: BTreeMap<CoreIndex, ParasEntryType<T>> = BTreeMap::new();
|
||||
let mut concluded_paras = BTreeMap::new();
|
||||
|
||||
AvailabilityCores::<T>::mutate(|cores| {
|
||||
@@ -310,21 +287,22 @@ impl<T: Config> Pallet<T> {
|
||||
.into_iter()
|
||||
.filter(|(freed_index, _)| (freed_index.0 as usize) < c_len)
|
||||
.for_each(|(freed_index, freed_reason)| {
|
||||
match &cores[freed_index.0 as usize] {
|
||||
match sp_std::mem::replace(
|
||||
&mut cores[freed_index.0 as usize],
|
||||
CoreOccupied::Free,
|
||||
) {
|
||||
CoreOccupied::Free => {},
|
||||
CoreOccupied::Paras(entry) => {
|
||||
match freed_reason {
|
||||
FreedReason::Concluded => {
|
||||
concluded_paras.insert(freed_index, entry.para_id());
|
||||
concluded_paras.insert(freed_index, entry.assignment);
|
||||
},
|
||||
FreedReason::TimedOut => {
|
||||
timedout_paras.insert(freed_index, entry.clone());
|
||||
timedout_paras.insert(freed_index, entry);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
cores[freed_index.0 as usize] = CoreOccupied::Free;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -379,30 +357,36 @@ impl<T: Config> Pallet<T> {
|
||||
for (idx, _) in (0u32..).zip(availability_cores) {
|
||||
let core_idx = CoreIndex(idx);
|
||||
if let Some(core_claimqueue) = cq.get_mut(&core_idx) {
|
||||
let mut dropped_claims: Vec<Option<ParaId>> = vec![];
|
||||
core_claimqueue.retain(|maybe_entry| {
|
||||
if let Some(entry) = maybe_entry {
|
||||
let mut i = 0;
|
||||
let mut num_dropped = 0;
|
||||
while i < core_claimqueue.len() {
|
||||
let maybe_dropped = if let Some(entry) = core_claimqueue.get(i) {
|
||||
if entry.ttl < now {
|
||||
dropped_claims.push(Some(entry.para_id()));
|
||||
return false
|
||||
core_claimqueue.remove(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// For all claims dropped due to TTL, attempt to pop a new entry to
|
||||
// the back of the claimqueue.
|
||||
for drop in dropped_claims {
|
||||
match T::AssignmentProvider::pop_assignment_for_core(core_idx, drop) {
|
||||
Some(assignment) => {
|
||||
let AssignmentProviderConfig { ttl, .. } =
|
||||
T::AssignmentProvider::get_provider_config(core_idx);
|
||||
core_claimqueue.push_back(Some(ParasEntry::new(
|
||||
assignment.clone(),
|
||||
now + ttl,
|
||||
)));
|
||||
},
|
||||
None => (),
|
||||
if let Some(dropped) = maybe_dropped {
|
||||
num_dropped += 1;
|
||||
T::AssignmentProvider::report_processed(dropped.assignment);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..num_dropped {
|
||||
// For all claims dropped due to TTL, attempt to pop a new entry to
|
||||
// the back of the claimqueue.
|
||||
if let Some(assignment) =
|
||||
T::AssignmentProvider::pop_assignment_for_core(core_idx)
|
||||
{
|
||||
let AssignmentProviderConfig { ttl, .. } =
|
||||
T::AssignmentProvider::get_provider_config(core_idx);
|
||||
core_claimqueue.push_back(ParasEntry::new(assignment, now + ttl));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,14 +498,12 @@ impl<T: Config> Pallet<T> {
|
||||
/// Return the next thing that will be scheduled on this core assuming it is currently
|
||||
/// occupied and the candidate occupying it became available.
|
||||
pub(crate) fn next_up_on_available(core: CoreIndex) -> Option<ScheduledCore> {
|
||||
ClaimQueue::<T>::get().get(&core).and_then(|a| {
|
||||
a.iter()
|
||||
.find_map(|e| e.as_ref())
|
||||
.map(|pe| Self::paras_entry_to_scheduled_core(pe))
|
||||
})
|
||||
ClaimQueue::<T>::get()
|
||||
.get(&core)
|
||||
.and_then(|a| a.front().map(|pe| Self::paras_entry_to_scheduled_core(pe)))
|
||||
}
|
||||
|
||||
fn paras_entry_to_scheduled_core(pe: &ParasEntry<BlockNumberFor<T>>) -> ScheduledCore {
|
||||
fn paras_entry_to_scheduled_core(pe: &ParasEntryType<T>) -> ScheduledCore {
|
||||
ScheduledCore { para_id: pe.para_id(), collator: None }
|
||||
}
|
||||
|
||||
@@ -552,35 +534,33 @@ impl<T: Config> Pallet<T> {
|
||||
/// Pushes occupied cores to the assignment provider.
|
||||
fn push_occupied_cores_to_assignment_provider() {
|
||||
AvailabilityCores::<T>::mutate(|cores| {
|
||||
for (core_idx, core) in cores.iter_mut().enumerate() {
|
||||
match core {
|
||||
for core in cores.iter_mut() {
|
||||
match sp_std::mem::replace(core, CoreOccupied::Free) {
|
||||
CoreOccupied::Free => continue,
|
||||
CoreOccupied::Paras(entry) => {
|
||||
let core_idx = CoreIndex::from(core_idx as u32);
|
||||
Self::maybe_push_assignment(core_idx, entry.clone());
|
||||
Self::maybe_push_assignment(entry);
|
||||
},
|
||||
}
|
||||
*core = CoreOccupied::Free;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// on new session
|
||||
fn push_claimqueue_items_to_assignment_provider() {
|
||||
for (core_idx, core_claimqueue) in ClaimQueue::<T>::take() {
|
||||
for (_, claim_queue) in ClaimQueue::<T>::take() {
|
||||
// Push back in reverse order so that when we pop from the provider again,
|
||||
// the entries in the claimqueue are in the same order as they are right now.
|
||||
for para_entry in core_claimqueue.into_iter().flatten().rev() {
|
||||
Self::maybe_push_assignment(core_idx, para_entry);
|
||||
for para_entry in claim_queue.into_iter().rev() {
|
||||
Self::maybe_push_assignment(para_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Push assignments back to the provider on session change unless the paras
|
||||
/// timed out on availability before.
|
||||
fn maybe_push_assignment(core_idx: CoreIndex, pe: ParasEntry<BlockNumberFor<T>>) {
|
||||
fn maybe_push_assignment(pe: ParasEntryType<T>) {
|
||||
if pe.availability_timeouts == 0 {
|
||||
T::AssignmentProvider::push_assignment_for_core(core_idx, pe.assignment);
|
||||
T::AssignmentProvider::push_back_assignment(pe.assignment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,31 +571,8 @@ impl<T: Config> Pallet<T> {
|
||||
<configuration::Pallet<T>>::config().scheduling_lookahead
|
||||
}
|
||||
|
||||
/// Updates the claimqueue by moving it to the next paras and filling empty spots with new
|
||||
/// paras.
|
||||
pub(crate) fn update_claimqueue(
|
||||
just_freed_cores: impl IntoIterator<Item = (CoreIndex, FreedReason)>,
|
||||
now: BlockNumberFor<T>,
|
||||
) {
|
||||
Self::move_claimqueue_forward();
|
||||
Self::free_cores_and_fill_claimqueue(just_freed_cores, now)
|
||||
}
|
||||
|
||||
/// Moves all elements in the claimqueue forward.
|
||||
fn move_claimqueue_forward() {
|
||||
let mut cq = ClaimQueue::<T>::get();
|
||||
for core_queue in cq.values_mut() {
|
||||
// First pop the finished claims from the front.
|
||||
if let Some(None) = core_queue.front() {
|
||||
core_queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
ClaimQueue::<T>::set(cq);
|
||||
}
|
||||
|
||||
/// Frees cores and fills the free claimqueue spots by popping from the `AssignmentProvider`.
|
||||
fn free_cores_and_fill_claimqueue(
|
||||
pub fn free_cores_and_fill_claimqueue(
|
||||
just_freed_cores: impl IntoIterator<Item = (CoreIndex, FreedReason)>,
|
||||
now: BlockNumberFor<T>,
|
||||
) {
|
||||
@@ -651,19 +608,19 @@ impl<T: Config> Pallet<T> {
|
||||
} else {
|
||||
// Consider timed out assignments for on demand parachains as concluded for
|
||||
// the assignment provider
|
||||
let ret = concluded_paras.insert(core_idx, entry.para_id());
|
||||
let ret = concluded_paras.insert(core_idx, entry.assignment);
|
||||
debug_assert!(ret.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
// We consider occupied cores to be part of the claimqueue
|
||||
if let Some(concluded_para) = concluded_paras.remove(&core_idx) {
|
||||
T::AssignmentProvider::report_processed(concluded_para);
|
||||
}
|
||||
// We consider occupied cores to be part of the claimqueue
|
||||
let n_lookahead_used = cq.get(&core_idx).map_or(0, |v| v.len() as u32) +
|
||||
if Self::is_core_occupied(core_idx) { 1 } else { 0 };
|
||||
for _ in n_lookahead_used..n_lookahead {
|
||||
let concluded_para = concluded_paras.remove(&core_idx);
|
||||
if let Some(assignment) =
|
||||
T::AssignmentProvider::pop_assignment_for_core(core_idx, concluded_para)
|
||||
{
|
||||
if let Some(assignment) = T::AssignmentProvider::pop_assignment_for_core(core_idx) {
|
||||
Self::add_to_claimqueue(core_idx, ParasEntry::new(assignment, now + ttl));
|
||||
}
|
||||
}
|
||||
@@ -680,9 +637,9 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntry<BlockNumberFor<T>>) {
|
||||
fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntryType<T>) {
|
||||
ClaimQueue::<T>::mutate(|la| {
|
||||
la.entry(core_idx).or_default().push_back(Some(pe));
|
||||
la.entry(core_idx).or_default().push_back(pe);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -690,19 +647,16 @@ impl<T: Config> Pallet<T> {
|
||||
fn remove_from_claimqueue(
|
||||
core_idx: CoreIndex,
|
||||
para_id: ParaId,
|
||||
) -> Result<(PositionInClaimqueue, ParasEntry<BlockNumberFor<T>>), &'static str> {
|
||||
) -> Result<(PositionInClaimqueue, ParasEntryType<T>), &'static str> {
|
||||
ClaimQueue::<T>::mutate(|cq| {
|
||||
let core_claims = cq.get_mut(&core_idx).ok_or("core_idx not found in lookahead")?;
|
||||
|
||||
let pos = core_claims
|
||||
.iter()
|
||||
.position(|a| a.as_ref().map_or(false, |pe| pe.para_id() == para_id))
|
||||
.position(|pe| pe.para_id() == para_id)
|
||||
.ok_or("para id not found at core_idx lookahead")?;
|
||||
|
||||
let pe = core_claims
|
||||
.remove(pos)
|
||||
.ok_or("remove returned None")?
|
||||
.ok_or("Element in Claimqueue was None.")?;
|
||||
let pe = core_claims.remove(pos).ok_or("remove returned None")?;
|
||||
|
||||
Ok((pos as u32, pe))
|
||||
})
|
||||
@@ -710,16 +664,10 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Paras scheduled next in the claim queue.
|
||||
pub(crate) fn scheduled_paras() -> impl Iterator<Item = (CoreIndex, ParaId)> {
|
||||
Self::scheduled_entries().map(|(core_idx, e)| (core_idx, e.assignment.para_id))
|
||||
}
|
||||
|
||||
/// Internal access to entries at the top of the claim queue.
|
||||
fn scheduled_entries() -> impl Iterator<Item = (CoreIndex, ParasEntry<BlockNumberFor<T>>)> {
|
||||
let claimqueue = ClaimQueue::<T>::get();
|
||||
|
||||
claimqueue
|
||||
.into_iter()
|
||||
.filter_map(|(core_idx, v)| v.front().cloned().flatten().map(|e| (core_idx, e)))
|
||||
.filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.assignment.para_id())))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
|
||||
@@ -16,29 +16,39 @@
|
||||
|
||||
//! Common traits and types used by the scheduler and assignment providers.
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::{
|
||||
codec::{Decode, Encode},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
// Only used to link to configuration documentation.
|
||||
#[allow(unused)]
|
||||
use crate::configuration::HostConfiguration;
|
||||
use primitives::{CoreIndex, Id as ParaId};
|
||||
|
||||
/// An assignment for a parachain scheduled to be backed and included in a relay chain block.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct Assignment {
|
||||
/// Assignment's ParaId
|
||||
pub para_id: ParaId,
|
||||
/// Assignment (ParaId -> CoreIndex).
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, Clone, PartialEq)]
|
||||
pub enum Assignment {
|
||||
/// A pool assignment.
|
||||
Pool {
|
||||
/// The assigned para id.
|
||||
para_id: ParaId,
|
||||
/// The core index the para got assigned to.
|
||||
core_index: CoreIndex,
|
||||
},
|
||||
/// A bulk assignment.
|
||||
Bulk(ParaId),
|
||||
}
|
||||
|
||||
impl Assignment {
|
||||
/// Create a new `Assignment`.
|
||||
pub fn new(para_id: ParaId) -> Self {
|
||||
Self { para_id }
|
||||
/// Returns the [`ParaId`] this assignment is associated to.
|
||||
pub fn para_id(&self) -> ParaId {
|
||||
match self {
|
||||
Self::Pool { para_id, .. } => *para_id,
|
||||
Self::Bulk(para_id) => *para_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo)]
|
||||
/// A set of variables required by the scheduler in order to operate.
|
||||
pub struct AssignmentProviderConfig<BlockNumber> {
|
||||
/// How many times a collation can time out on availability.
|
||||
@@ -51,22 +61,42 @@ pub struct AssignmentProviderConfig<BlockNumber> {
|
||||
}
|
||||
|
||||
pub trait AssignmentProvider<BlockNumber> {
|
||||
/// How many cores are allocated to this provider.
|
||||
fn session_core_count() -> u32;
|
||||
|
||||
/// Pops an [`Assignment`] from the provider for a specified [`CoreIndex`].
|
||||
/// The `concluded_para` field makes the caller report back to the provider
|
||||
/// which [`ParaId`] it processed last on the supplied [`CoreIndex`].
|
||||
fn pop_assignment_for_core(
|
||||
core_idx: CoreIndex,
|
||||
concluded_para: Option<ParaId>,
|
||||
) -> Option<Assignment>;
|
||||
///
|
||||
/// This is where assignments come into existance.
|
||||
fn pop_assignment_for_core(core_idx: CoreIndex) -> Option<Assignment>;
|
||||
|
||||
/// Push back an already popped assignment. Intended for provider implementations
|
||||
/// that need to be able to keep track of assignments over session boundaries,
|
||||
/// such as the on demand assignment provider.
|
||||
fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment);
|
||||
/// A previously popped `Assignment` has been fully processed.
|
||||
///
|
||||
/// Report back to the assignment provider that an assignment is done and no longer present in
|
||||
/// the scheduler.
|
||||
///
|
||||
/// This is one way of the life of an assignment coming to an end.
|
||||
fn report_processed(assignment: Assignment);
|
||||
|
||||
/// Push back a previously popped assignment.
|
||||
///
|
||||
/// If the assignment could not be processed within the current session, it can be pushed back
|
||||
/// to the assignment provider in order to be poppped again later.
|
||||
///
|
||||
/// This is the second way the life of an assignment can come to an end.
|
||||
fn push_back_assignment(assignment: Assignment);
|
||||
|
||||
/// Returns a set of variables needed by the scheduler
|
||||
fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumber>;
|
||||
|
||||
/// Push some assignment for mocking/benchmarks purposes.
|
||||
///
|
||||
/// Useful for benchmarks and testing. The returned assignment is "valid" and can if need be
|
||||
/// passed into `report_processed` for example.
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
fn get_mock_assignment(core_idx: CoreIndex, para_id: ParaId) -> Assignment;
|
||||
|
||||
/// How many cores are allocated to this provider.
|
||||
///
|
||||
/// As the name suggests the core count has to be session buffered:
|
||||
///
|
||||
/// - Core count has to be predetermined for the next session in the current session.
|
||||
/// - Core count must not change during a session.
|
||||
fn session_core_count() -> u32;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,18 @@ use frame_support::{
|
||||
traits::OnRuntimeUpgrade, weights::Weight,
|
||||
};
|
||||
|
||||
/// Old/legacy assignment representation (v0).
|
||||
///
|
||||
/// `Assignment` used to be a concrete type with the same layout V0Assignment, idential on all
|
||||
/// assignment providers. This can be removed once storage has been migrated.
|
||||
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Clone)]
|
||||
struct V0Assignment {
|
||||
pub para_id: ParaId,
|
||||
}
|
||||
|
||||
/// Old scheduler with explicit parathreads and `Scheduled` storage instead of `ClaimQueue`.
|
||||
mod v0 {
|
||||
use super::*;
|
||||
|
||||
use primitives::{CollatorId, Id};
|
||||
|
||||
#[storage_alias]
|
||||
@@ -90,29 +99,123 @@ mod v0 {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
// `ClaimQueue` got introduced.
|
||||
//
|
||||
// - Items are `Option` for some weird reason.
|
||||
// - Assignments only consist of `ParaId`, `Assignment` is a concrete type (Same as V0Assignment).
|
||||
mod v1 {
|
||||
use frame_support::{
|
||||
pallet_prelude::ValueQuery, storage_alias, traits::OnRuntimeUpgrade, weights::Weight,
|
||||
};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
|
||||
use super::*;
|
||||
use crate::scheduler;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type MigrateToV1<T> = VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
UncheckedMigrateToV1<T>,
|
||||
#[storage_alias]
|
||||
pub(super) type ClaimQueue<T: Config> = StorageValue<
|
||||
Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
BTreeMap<CoreIndex, VecDeque<Option<ParasEntry<BlockNumberFor<T>>>>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[deprecated(note = "Use MigrateToV1 instead")]
|
||||
#[storage_alias]
|
||||
pub(super) type AvailabilityCores<T: Config> =
|
||||
StorageValue<Pallet<T>, Vec<CoreOccupied<BlockNumberFor<T>>>, ValueQuery>;
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)]
|
||||
pub(super) enum CoreOccupied<N> {
|
||||
/// No candidate is waiting availability on this core right now (the core is not occupied).
|
||||
Free,
|
||||
/// A para is currently waiting for availability/inclusion on this core.
|
||||
Paras(ParasEntry<N>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)]
|
||||
pub(super) struct ParasEntry<N> {
|
||||
/// The underlying `Assignment`
|
||||
pub(super) assignment: V0Assignment,
|
||||
/// The number of times the entry has timed out in availability already.
|
||||
pub(super) availability_timeouts: u32,
|
||||
/// The block height until this entry needs to be backed.
|
||||
///
|
||||
/// If missed the entry will be removed from the claim queue without ever having occupied
|
||||
/// the core.
|
||||
pub(super) ttl: N,
|
||||
}
|
||||
|
||||
impl<N> ParasEntry<N> {
|
||||
/// Create a new `ParasEntry`.
|
||||
pub(super) fn new(assignment: V0Assignment, now: N) -> Self {
|
||||
ParasEntry { assignment, availability_timeouts: 0, ttl: now }
|
||||
}
|
||||
|
||||
/// Return `Id` from the underlying `Assignment`.
|
||||
pub(super) fn para_id(&self) -> ParaId {
|
||||
self.assignment.para_id
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_claimqueue<T: Config>(core_idx: CoreIndex, pe: ParasEntry<BlockNumberFor<T>>) {
|
||||
ClaimQueue::<T>::mutate(|la| {
|
||||
la.entry(core_idx).or_default().push_back(Some(pe));
|
||||
});
|
||||
}
|
||||
|
||||
/// Migration to V1
|
||||
pub struct UncheckedMigrateToV1<T>(sp_std::marker::PhantomData<T>);
|
||||
#[allow(deprecated)]
|
||||
impl<T: Config> OnRuntimeUpgrade for UncheckedMigrateToV1<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let weight_consumed = migrate_to_v1::<T>();
|
||||
let mut weight: Weight = Weight::zero();
|
||||
|
||||
log::info!(target: scheduler::LOG_TARGET, "Migrating para scheduler storage to v1");
|
||||
v0::ParathreadQueue::<T>::kill();
|
||||
v0::ParathreadClaimIndex::<T>::kill();
|
||||
|
||||
weight_consumed
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
let scheduled = v0::Scheduled::<T>::take();
|
||||
let sched_len = scheduled.len() as u64;
|
||||
for core_assignment in scheduled {
|
||||
let core_idx = core_assignment.core;
|
||||
let assignment = V0Assignment { para_id: core_assignment.para_id };
|
||||
let pe = v1::ParasEntry::new(assignment, now);
|
||||
v1::add_to_claimqueue::<T>(core_idx, pe);
|
||||
}
|
||||
|
||||
let parachains = paras::Pallet::<T>::parachains();
|
||||
let availability_cores = v0::AvailabilityCores::<T>::take();
|
||||
let mut new_availability_cores = Vec::new();
|
||||
|
||||
for (core_index, core) in availability_cores.into_iter().enumerate() {
|
||||
let new_core = if let Some(core) = core {
|
||||
match core {
|
||||
v0::CoreOccupied::Parachain =>
|
||||
v1::CoreOccupied::Paras(v1::ParasEntry::new(
|
||||
V0Assignment { para_id: parachains[core_index] },
|
||||
now,
|
||||
)),
|
||||
v0::CoreOccupied::Parathread(entry) => v1::CoreOccupied::Paras(
|
||||
v1::ParasEntry::new(V0Assignment { para_id: entry.claim.0 }, now),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
v1::CoreOccupied::Free
|
||||
};
|
||||
|
||||
new_availability_cores.push(new_core);
|
||||
}
|
||||
|
||||
v1::AvailabilityCores::<T>::set(new_availability_cores);
|
||||
|
||||
// 2x as once for Scheduled and once for Claimqueue
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(2 * sched_len, 2 * sched_len));
|
||||
// reading parachains + availability_cores, writing AvailabilityCores
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1));
|
||||
// 2x kill
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(2));
|
||||
|
||||
log::info!(target: scheduler::LOG_TARGET, "Migrated para scheduler storage to v1");
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
@@ -138,9 +241,9 @@ pub mod v1 {
|
||||
);
|
||||
|
||||
let expected_len = u32::decode(&mut &state[..]).unwrap();
|
||||
let availability_cores_waiting = super::AvailabilityCores::<T>::get()
|
||||
.iter()
|
||||
.filter(|c| !matches!(c, CoreOccupied::Free))
|
||||
let availability_cores_waiting = v1::AvailabilityCores::<T>::get()
|
||||
.into_iter()
|
||||
.filter(|c| !matches!(c, v1::CoreOccupied::Free))
|
||||
.count();
|
||||
|
||||
ensure!(
|
||||
@@ -154,51 +257,150 @@ pub mod v1 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn migrate_to_v1<T: crate::scheduler::Config>() -> Weight {
|
||||
let mut weight: Weight = Weight::zero();
|
||||
/// Migrate `V0` to `V1` of the storage format.
|
||||
pub type MigrateV0ToV1<T> = VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
v1::UncheckedMigrateToV1<T>,
|
||||
Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
|
||||
v0::ParathreadQueue::<T>::kill();
|
||||
v0::ParathreadClaimIndex::<T>::kill();
|
||||
mod v2 {
|
||||
use super::*;
|
||||
use crate::scheduler;
|
||||
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
let scheduled = v0::Scheduled::<T>::take();
|
||||
let sched_len = scheduled.len() as u64;
|
||||
for core_assignment in scheduled {
|
||||
let core_idx = core_assignment.core;
|
||||
let assignment = Assignment::new(core_assignment.para_id);
|
||||
let pe = ParasEntry::new(assignment, now);
|
||||
Pallet::<T>::add_to_claimqueue(core_idx, pe);
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)]
|
||||
pub(crate) enum CoreOccupied<N> {
|
||||
Free,
|
||||
Paras(ParasEntry<N>),
|
||||
}
|
||||
|
||||
let parachains = paras::Pallet::<T>::parachains();
|
||||
let availability_cores = v0::AvailabilityCores::<T>::take();
|
||||
let mut new_availability_cores = Vec::new();
|
||||
|
||||
for (core_index, core) in availability_cores.into_iter().enumerate() {
|
||||
let new_core = if let Some(core) = core {
|
||||
match core {
|
||||
v0::CoreOccupied::Parachain => CoreOccupied::Paras(ParasEntry::new(
|
||||
Assignment::new(parachains[core_index]),
|
||||
now,
|
||||
)),
|
||||
v0::CoreOccupied::Parathread(entry) =>
|
||||
CoreOccupied::Paras(ParasEntry::new(Assignment::new(entry.claim.0), now)),
|
||||
}
|
||||
} else {
|
||||
CoreOccupied::Free
|
||||
};
|
||||
|
||||
new_availability_cores.push(new_core);
|
||||
#[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)]
|
||||
pub(crate) struct ParasEntry<N> {
|
||||
pub assignment: Assignment,
|
||||
pub availability_timeouts: u32,
|
||||
pub ttl: N,
|
||||
}
|
||||
|
||||
super::AvailabilityCores::<T>::set(new_availability_cores);
|
||||
// V2 (no Option wrapper) and new [`Assignment`].
|
||||
#[storage_alias]
|
||||
pub(crate) type ClaimQueue<T: Config> = StorageValue<
|
||||
Pallet<T>,
|
||||
BTreeMap<CoreIndex, VecDeque<ParasEntry<BlockNumberFor<T>>>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
// 2x as once for Scheduled and once for Claimqueue
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(2 * sched_len, 2 * sched_len));
|
||||
// reading parachains + availability_cores, writing AvailabilityCores
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1));
|
||||
// 2x kill
|
||||
weight = weight.saturating_add(T::DbWeight::get().writes(2));
|
||||
#[storage_alias]
|
||||
pub(crate) type AvailabilityCores<T: Config> =
|
||||
StorageValue<Pallet<T>, Vec<CoreOccupied<BlockNumberFor<T>>>, ValueQuery>;
|
||||
|
||||
weight
|
||||
fn is_bulk<T: Config>(core_index: CoreIndex) -> bool {
|
||||
core_index.0 < paras::Parachains::<T>::decode_len().unwrap_or(0) as u32
|
||||
}
|
||||
|
||||
/// Migration to V2
|
||||
pub struct UncheckedMigrateToV2<T>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config> OnRuntimeUpgrade for UncheckedMigrateToV2<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight: Weight = Weight::zero();
|
||||
|
||||
let old = v1::ClaimQueue::<T>::take();
|
||||
let new = old
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k,
|
||||
v.into_iter()
|
||||
.flatten()
|
||||
.map(|p| {
|
||||
let assignment = if is_bulk::<T>(k) {
|
||||
Assignment::Bulk(p.para_id())
|
||||
} else {
|
||||
Assignment::Pool { para_id: p.para_id(), core_index: k }
|
||||
};
|
||||
|
||||
ParasEntry {
|
||||
assignment,
|
||||
availability_timeouts: p.availability_timeouts,
|
||||
ttl: p.ttl,
|
||||
}
|
||||
})
|
||||
.collect::<VecDeque<_>>(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<CoreIndex, VecDeque<ParasEntry<BlockNumberFor<T>>>>>();
|
||||
|
||||
ClaimQueue::<T>::put(new);
|
||||
|
||||
let old = v1::AvailabilityCores::<T>::get();
|
||||
|
||||
let new = old
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(k, a)| match a {
|
||||
v1::CoreOccupied::Free => CoreOccupied::Free,
|
||||
v1::CoreOccupied::Paras(paras) => {
|
||||
let assignment = if is_bulk::<T>((k as u32).into()) {
|
||||
Assignment::Bulk(paras.para_id())
|
||||
} else {
|
||||
Assignment::Pool {
|
||||
para_id: paras.para_id(),
|
||||
core_index: (k as u32).into(),
|
||||
}
|
||||
};
|
||||
|
||||
CoreOccupied::Paras(ParasEntry {
|
||||
assignment,
|
||||
availability_timeouts: paras.availability_timeouts,
|
||||
ttl: paras.ttl,
|
||||
})
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
AvailabilityCores::<T>::put(new);
|
||||
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
|
||||
log::info!(target: scheduler::LOG_TARGET, "Migrating para scheduler storage to v2");
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
|
||||
log::trace!(
|
||||
target: crate::scheduler::LOG_TARGET,
|
||||
"ClaimQueue before migration: {}",
|
||||
v1::ClaimQueue::<T>::get().len()
|
||||
);
|
||||
|
||||
let bytes = u32::to_be_bytes(v1::ClaimQueue::<T>::get().len() as u32);
|
||||
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
|
||||
log::trace!(target: crate::scheduler::LOG_TARGET, "Running post_upgrade()");
|
||||
|
||||
let old_len = u32::from_be_bytes(state.try_into().unwrap());
|
||||
ensure!(
|
||||
v2::ClaimQueue::<T>::get().len() as u32 == old_len,
|
||||
"Old ClaimQueue completely moved to new ClaimQueue after migration"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate `V1` to `V2` of the storage format.
|
||||
pub type MigrateV1ToV2<T> = VersionedMigration<
|
||||
1,
|
||||
2,
|
||||
v2::UncheckedMigrateToV2<T>,
|
||||
Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ fn run_to_block(
|
||||
|
||||
fn default_config() -> HostConfiguration<BlockNumber> {
|
||||
HostConfiguration {
|
||||
on_demand_cores: 1,
|
||||
coretime_cores: 1,
|
||||
dispute_period: 2,
|
||||
needed_approvals: 3,
|
||||
..Default::default()
|
||||
|
||||
Reference in New Issue
Block a user