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:
eskimor
2023-12-21 19:06:58 +01:00
committed by GitHub
parent 18d53dbf91
commit 69434d9a32
71 changed files with 4059 additions and 1213 deletions
Generated
+3
View File
@@ -12993,6 +12993,7 @@ dependencies = [
"pallet-authorship",
"pallet-babe",
"pallet-balances",
"pallet-broker",
"pallet-election-provider-multi-phase",
"pallet-fast-unstake",
"pallet-identity",
@@ -13063,6 +13064,7 @@ dependencies = [
"pallet-authorship",
"pallet-babe",
"pallet-balances",
"pallet-broker",
"pallet-message-queue",
"pallet-session",
"pallet-staking",
@@ -13083,6 +13085,7 @@ dependencies = [
"serde_json",
"sp-api",
"sp-application-crypto",
"sp-arithmetic",
"sp-core",
"sp-inherents",
"sp-io",
@@ -199,23 +199,12 @@ impl CoretimeInterface for CoretimeAllocator {
}
}
fn check_notify_core_count() -> Option<u16> {
let count = CoreCount::get();
CoreCount::set(&None);
count
}
fn check_notify_revenue_info() -> Option<(RCBlockNumberOf<Self>, Self::Balance)> {
let revenue = CoretimeRevenue::get();
CoretimeRevenue::set(&None);
revenue
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_core_count(count: u16) {
CoreCount::set(&Some(count));
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_revenue_info(when: RCBlockNumberOf<Self>, revenue: Self::Balance) {
CoretimeRevenue::set(&Some((when, revenue)));
@@ -121,7 +121,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("coretime-rococo"),
impl_name: create_runtime_str!("coretime-rococo"),
authoring_version: 1,
spec_version: 1_005_000,
spec_version: 1_005_001,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 0,
@@ -463,6 +463,9 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(2))
}
fn notify_core_count() -> Weight {
T::DbWeight::get().reads_writes(1, 1)
}
/// Storage: `Broker::Status` (r:1 w:1)
/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
/// Storage: `Broker::Configuration` (r:1 w:0)
@@ -101,9 +101,8 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
let mut view = View::new();
let subsystem = async move {
match run_iteration(&mut context, &mut view, &Metrics(None)).await {
Ok(()) => {},
Err(e) => panic!("{:?}", e),
if let Err(e) = run_iteration(&mut context, &mut view, &Metrics(None)).await {
panic!("{:?}", e);
}
view
@@ -182,6 +182,7 @@ struct CoreAssignment {
core: CoreIndex,
para_id: ParaId,
kind: AssignmentKind,
group_idx: GroupIndex,
}
// reasons a core might be freed.
enum FreedReason {
+4
View File
@@ -32,6 +32,7 @@ sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", def
pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false }
pallet-balances = { path = "../../../substrate/frame/balances", default-features = false }
pallet-broker = { path = "../../../substrate/frame/broker", default-features = false }
pallet-fast-unstake = { path = "../../../substrate/frame/fast-unstake", default-features = false }
pallet-identity = { path = "../../../substrate/frame/identity", default-features = false }
pallet-session = { path = "../../../substrate/frame/session", default-features = false }
@@ -87,6 +88,7 @@ std = [
"pallet-asset-rate?/std",
"pallet-authorship/std",
"pallet-balances/std",
"pallet-broker/std",
"pallet-election-provider-multi-phase/std",
"pallet-fast-unstake/std",
"pallet-identity/std",
@@ -127,6 +129,7 @@ runtime-benchmarks = [
"pallet-asset-rate/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-broker/runtime-benchmarks",
"pallet-election-provider-multi-phase/runtime-benchmarks",
"pallet-fast-unstake/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
@@ -151,6 +154,7 @@ try-runtime = [
"pallet-authorship/try-runtime",
"pallet-babe?/try-runtime",
"pallet-balances/try-runtime",
"pallet-broker/try-runtime",
"pallet-election-provider-multi-phase/try-runtime",
"pallet-fast-unstake/try-runtime",
"pallet-identity/try-runtime",
@@ -743,6 +743,7 @@ mod tests {
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
}
impl parachains_shared::Config for Test {}
@@ -212,6 +212,7 @@ impl paras::Config for Test {
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
}
parameter_types! {
@@ -814,6 +814,7 @@ mod tests {
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
}
impl configuration::Config for Test {
@@ -23,7 +23,7 @@ use parity_scale_codec::Encode;
use primitives::Id as ParaId;
use runtime_parachains::{
configuration, dmp, hrmp,
paras::{self, ParaGenesisArgs},
paras::{self, AssignCoretime, ParaGenesisArgs},
ParaLifecycle,
};
use sp_std::boxed::Box;
@@ -58,6 +58,8 @@ pub mod pallet {
CannotUpgrade,
/// Cannot downgrade lease holding parachain to on-demand.
CannotDowngrade,
/// There are more cores than supported by the runtime.
TooManyCores,
}
#[pallet::hooks]
@@ -66,6 +68,10 @@ pub mod pallet {
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Schedule a para to be initialized at the start of the next session.
///
/// This should only be used for TESTING and not on PRODUCTION chains. It automatically
/// assigns Coretime to the chain and increases the number of cores. Thus, there is no
/// running coretime chain required.
#[pallet::call_index(0)]
#[pallet::weight((1_000, DispatchClass::Operational))]
pub fn sudo_schedule_para_initialize(
@@ -76,6 +82,9 @@ pub mod pallet {
ensure_root(origin)?;
runtime_parachains::schedule_para_initialize::<T>(id, genesis)
.map_err(|_| Error::<T>::ParaAlreadyExists)?;
T::AssignCoretime::assign_coretime(id)?;
Ok(())
}
+14 -6
View File
@@ -326,6 +326,18 @@ impl<T: Config> Pallet<T> {
tracker.into_iter().collect()
}
/// Current lease index and how many blocks we are already in.
pub fn lease_period_index_plus_progress(
b: BlockNumberFor<T>,
) -> Option<(<Self as Leaser<BlockNumberFor<T>>>::LeasePeriod, BlockNumberFor<T>)> {
// Note that blocks before `LeaseOffset` do not count as any lease period.
let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?;
let lease_period = offset_block_now / T::LeasePeriod::get();
let in_lease = offset_block_now % T::LeasePeriod::get();
Some((lease_period, in_lease))
}
}
impl<T: Config> crate::traits::OnSwap for Pallet<T> {
@@ -449,12 +461,8 @@ impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> {
}
fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> {
// Note that blocks before `LeaseOffset` do not count as any lease period.
let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?;
let lease_period = offset_block_now / T::LeasePeriod::get();
let first_block = (offset_block_now % T::LeasePeriod::get()).is_zero();
Some((lease_period, first_block))
Self::lease_period_index_plus_progress(b)
.map(|(period, progress)| (period, progress.is_zero()))
}
fn already_leased(
+6
View File
@@ -31,11 +31,13 @@ sp-core = { path = "../../../substrate/primitives/core", default-features = fals
sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true }
sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false, optional = true }
sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false, optional = true }
sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false }
pallet-authority-discovery = { path = "../../../substrate/frame/authority-discovery", default-features = false }
pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false }
pallet-balances = { path = "../../../substrate/frame/balances", default-features = false }
pallet-babe = { path = "../../../substrate/frame/babe", default-features = false }
pallet-broker = { path = "../../../substrate/frame/broker", default-features = false }
pallet-message-queue = { path = "../../../substrate/frame/message-queue", default-features = false }
pallet-session = { path = "../../../substrate/frame/session", default-features = false }
pallet-staking = { path = "../../../substrate/frame/staking", default-features = false }
@@ -82,6 +84,7 @@ std = [
"pallet-authorship/std",
"pallet-babe/std",
"pallet-balances/std",
"pallet-broker/std",
"pallet-message-queue/std",
"pallet-session/std",
"pallet-staking/std",
@@ -99,6 +102,7 @@ std = [
"serde/std",
"sp-api/std",
"sp-application-crypto?/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-keystore",
@@ -115,6 +119,7 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-broker/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"pallet-staking/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
@@ -135,6 +140,7 @@ try-runtime = [
"pallet-authorship/try-runtime",
"pallet-babe/try-runtime",
"pallet-balances/try-runtime",
"pallet-broker/try-runtime",
"pallet-message-queue/try-runtime",
"pallet-session/try-runtime",
"pallet-staking/try-runtime",
-112
View File
@@ -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(&notification_with_session_index);
Scheduler::initializer_on_new_session(&notification_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(&notification_with_session_index);
Scheduler::initializer_on_new_session(&notification_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())
);
}
});
}
+42 -8
View File
@@ -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(&notification, &outgoing_paras);
hrmp::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
T::CoretimeOnNewSession::on_new_session(&notification);
}
/// Should be called when a new session occurs. Buffers the session notification to be applied
+2 -1
View File
@@ -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;
+147 -12
View File
@@ -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();
+77 -129
View File
@@ -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()
@@ -116,6 +116,8 @@ pub mod system_parachain {
pub const PEOPLE_ID: u32 = 1004;
/// BridgeHub parachain ID.
pub const BRIDGE_HUB_ID: u32 = 1013;
/// Brokerage parachain ID.
pub const BROKER_ID: u32 = 1005;
/// All system parachains of Rococo.
pub type SystemParachains = IsChildSystemParachain<Id>;
+60 -10
View File
@@ -31,6 +31,7 @@ use primitives::{
OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionInfo, Signature,
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
};
use rococo_runtime_constants::system_parachain::BROKER_ID;
use runtime_common::{
assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights,
impls::{
@@ -43,9 +44,10 @@ use scale_info::TypeInfo;
use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*};
use runtime_parachains::{
assigner as parachains_assigner, assigner_on_demand as parachains_assigner_on_demand,
assigner_coretime as parachains_assigner_coretime,
assigner_on_demand as parachains_assigner_on_demand,
assigner_parachains as parachains_assigner_parachains,
configuration as parachains_configuration, disputes as parachains_disputes,
configuration as parachains_configuration, coretime, disputes as parachains_disputes,
disputes::slashing as parachains_slashing,
dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion,
inclusion::{AggregateMessageOrigin, UmpQueueId},
@@ -150,7 +152,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("rococo"),
impl_name: create_runtime_str!("parity-rococo-v2.0"),
authoring_version: 0,
spec_version: 1_005_000,
spec_version: 1_005_001,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 24,
@@ -935,6 +937,7 @@ impl parachains_paras::Config for Runtime {
type QueueFootprinter = ParaInclusion;
type NextSessionRotation = Babe;
type OnNewHead = Registrar;
type AssignCoretime = CoretimeAssignmentProvider;
}
parameter_types! {
@@ -1001,7 +1004,22 @@ impl parachains_paras_inherent::Config for Runtime {
}
impl parachains_scheduler::Config for Runtime {
type AssignmentProvider = ParaAssignmentProvider;
// If you change this, make sure the `Assignment` type of the new provider is binary compatible,
// otherwise provide a migration.
type AssignmentProvider = CoretimeAssignmentProvider;
}
parameter_types! {
pub const BrokerId: u32 = BROKER_ID;
}
impl coretime::Config for Runtime {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type BrokerId = BrokerId;
type WeightInfo = weights::runtime_parachains_coretime::WeightInfo<Runtime>;
type SendXcm = crate::xcm_config::XcmRouter;
}
parameter_types! {
@@ -1017,15 +1035,13 @@ impl parachains_assigner_on_demand::Config for Runtime {
impl parachains_assigner_parachains::Config for Runtime {}
impl parachains_assigner::Config for Runtime {
type OnDemandAssignmentProvider = OnDemandAssignmentProvider;
type ParachainsAssignmentProvider = ParachainsAssignmentProvider;
}
impl parachains_assigner_coretime::Config for Runtime {}
impl parachains_initializer::Config for Runtime {
type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
type ForceOrigin = EnsureRoot<AccountId>;
type WeightInfo = weights::runtime_parachains_initializer::WeightInfo<Runtime>;
type CoretimeOnNewSession = Coretime;
}
impl parachains_disputes::Config for Runtime {
@@ -1405,15 +1421,16 @@ construct_runtime! {
ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event<T>} = 62,
ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 63,
MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>} = 64,
ParaAssignmentProvider: parachains_assigner::{Pallet, Storage} = 65,
OnDemandAssignmentProvider: parachains_assigner_on_demand::{Pallet, Call, Storage, Event<T>} = 66,
ParachainsAssignmentProvider: parachains_assigner_parachains::{Pallet} = 67,
CoretimeAssignmentProvider: parachains_assigner_coretime::{Pallet, Storage} = 68,
// Parachain Onboarding Pallets. Start indices at 70 to leave room.
Registrar: paras_registrar::{Pallet, Call, Storage, Event<T>, Config<T>} = 70,
Slots: slots::{Pallet, Call, Storage, Event<T>} = 71,
Auctions: auctions::{Pallet, Call, Storage, Event<T>} = 72,
Crowdloan: crowdloan::{Pallet, Call, Storage, Event<T>} = 73,
Coretime: coretime::{Pallet, Call, Event<T>} = 74,
// Pallet for sending XCM.
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>, Origin, Config<T>} = 99,
@@ -1477,9 +1494,39 @@ pub mod migrations {
use frame_support::traits::LockIdentifier;
use frame_system::pallet_prelude::BlockNumberFor;
use sp_arithmetic::traits::Zero;
#[cfg(feature = "try-runtime")]
use sp_core::crypto::ByteArray;
pub struct GetLegacyLeaseImpl;
impl coretime::migration::GetLegacyLease<BlockNumber> for GetLegacyLeaseImpl {
fn get_parachain_lease_in_blocks(para: ParaId) -> Option<BlockNumber> {
let now = frame_system::Pallet::<Runtime>::block_number();
let mut leases = slots::Pallet::<Runtime>::lease(para).into_iter();
let initial_sum = if let Some(Some(_)) = leases.next() {
let (_, progress) =
slots::Pallet::<Runtime>::lease_period_index_plus_progress(now)?;
LeasePeriod::get().saturating_sub(progress)
} else {
// The parachain lease did not yet start
Zero::zero()
};
log::trace!(
target: "coretime-migration",
"Getting lease info for para {:?}:\n LEASE_PERIOD: {:?}, initial_sum: {:?}, number of leases: {:?}",
para,
LeasePeriod::get(),
initial_sum,
slots::Pallet::<Runtime>::lease(para).len(),
);
Some(leases.into_iter().fold(initial_sum, |sum, lease| {
// If the parachain lease did not yet start, we ignore them by multiplying by `0`.
sum + LeasePeriod::get() * lease.map_or(0, |_| 1)
}))
}
}
parameter_types! {
pub const DemocracyPalletName: &'static str = "Democracy";
pub const CouncilPalletName: &'static str = "Council";
@@ -1602,7 +1649,7 @@ pub mod migrations {
pallet_society::migrations::MigrateToV2<Runtime, (), ()>,
parachains_configuration::migration::v7::MigrateToV7<Runtime>,
assigned_slots::migration::v1::MigrateToV1<Runtime>,
parachains_scheduler::migration::v1::MigrateToV1<Runtime>,
parachains_scheduler::migration::MigrateV1ToV2<Runtime>,
parachains_configuration::migration::v8::MigrateToV8<Runtime>,
parachains_configuration::migration::v9::MigrateToV9<Runtime>,
paras_registrar::migration::MigrateToV1<Runtime, ()>,
@@ -1633,6 +1680,8 @@ pub mod migrations {
// Remove `im-online` pallet on-chain storage
frame_support::migrations::RemovePallet<ImOnlinePalletName, <Runtime as frame_system::Config>::DbWeight>,
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
// This needs to come after the `parachains_configuration` above as we are reading the configuration.
coretime::migration::MigrateToCoretime<Runtime, crate::xcm_config::XcmRouter, GetLegacyLeaseImpl>,
);
}
@@ -1681,6 +1730,7 @@ mod benches {
// the that path resolves correctly in the generated file.
[runtime_common::assigned_slots, AssignedSlots]
[runtime_common::auctions, Auctions]
[runtime_common::coretime, Coretime]
[runtime_common::crowdloan, Crowdloan]
[runtime_common::claims, Claims]
[runtime_common::identity_migrator, IdentityMigrator]
@@ -51,6 +51,7 @@ pub mod runtime_common_paras_registrar;
pub mod runtime_common_slots;
pub mod runtime_parachains_assigner_on_demand;
pub mod runtime_parachains_configuration;
pub mod runtime_parachains_coretime;
pub mod runtime_parachains_disputes;
pub mod runtime_parachains_hrmp;
pub mod runtime_parachains_inclusion;
@@ -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/>.
//! Autogenerated weights for `runtime_parachains::coretime`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-12-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
// Executed Command:
// target/production/polkadot
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
// --pallet=runtime_common::coretime
// --chain=rococo-dev
// --header=./polkadot/file_header.txt
// --output=./polkadot/runtime/rococo/src/weights/
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::Weight};
use core::marker::PhantomData;
use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo};
/// Weight functions for `runtime_common::coretime`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config + configuration::Config> runtime_parachains::coretime::WeightInfo for WeightInfo<T> {
fn request_core_count() -> Weight {
<T as configuration::Config>::WeightInfo::set_config_with_u32()
}
/// Storage: `CoreTimeAssignmentProvider::CoreDescriptors` (r:1 w:1)
/// Proof: `CoreTimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `CoreTimeAssignmentProvider::CoreSchedules` (r:0 w:1)
/// Proof: `CoreTimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 100]`.
fn assign_core(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `3541`
// Minimum execution time: 6_275_000 picoseconds.
Weight::from_parts(6_883_543, 0)
.saturating_add(Weight::from_parts(0, 3541))
// Standard Error: 202
.saturating_add(Weight::from_parts(15_028, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(2))
}
}
@@ -120,6 +120,7 @@ parameter_types! {
pub const Contracts: MultiLocation = Parachain(CONTRACTS_ID).into_location();
pub const Encointer: MultiLocation = Parachain(ENCOINTER_ID).into_location();
pub const BridgeHub: MultiLocation = Parachain(BRIDGE_HUB_ID).into_location();
pub const Broker: MultiLocation = Parachain(BROKER_ID).into_location();
pub const Tick: MultiLocation = Parachain(100).into_location();
pub const Trick: MultiLocation = Parachain(110).into_location();
pub const Track: MultiLocation = Parachain(120).into_location();
@@ -130,6 +131,7 @@ parameter_types! {
pub const RocForContracts: (MultiAssetFilter, MultiLocation) = (Roc::get(), Contracts::get());
pub const RocForEncointer: (MultiAssetFilter, MultiLocation) = (Roc::get(), Encointer::get());
pub const RocForBridgeHub: (MultiAssetFilter, MultiLocation) = (Roc::get(), BridgeHub::get());
pub const RocForBroker: (MultiAssetFilter, MultiLocation) = (Roc::get(), Broker::get());
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
}
@@ -141,6 +143,7 @@ pub type TrustedTeleporters = (
xcm_builder::Case<RocForContracts>,
xcm_builder::Case<RocForEncointer>,
xcm_builder::Case<RocForBridgeHub>,
xcm_builder::Case<RocForBroker>,
);
match_types! {
+11 -1
View File
@@ -74,7 +74,7 @@ use sp_runtime::{
SaturatedConversion, StaticLookup, Verify,
},
transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity},
ApplyExtrinsicResult, KeyTypeId, Perbill,
ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill,
};
use sp_staking::SessionIndex;
#[cfg(any(feature = "std", test))]
@@ -520,6 +520,7 @@ impl parachains_initializer::Config for Runtime {
type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type WeightInfo = ();
type CoretimeOnNewSession = ();
}
impl parachains_session_info::Config for Runtime {
@@ -537,6 +538,15 @@ impl parachains_paras::Config for Runtime {
type QueueFootprinter = ParaInclusion;
type NextSessionRotation = Babe;
type OnNewHead = ();
type AssignCoretime = ();
}
parameter_types! {
pub const BrokerId: u32 = 10u32;
}
parameter_types! {
pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1);
}
impl parachains_dmp::Config for Runtime {}
@@ -107,6 +107,8 @@ pub mod system_parachain {
pub const COLLECTIVES_ID: u32 = 1001;
/// BridgeHub parachain ID.
pub const BRIDGE_HUB_ID: u32 = 1002;
/// Brokerage parachain ID.
pub const BROKER_ID: u32 = 1005;
/// All system parachains of Westend.
pub type SystemParachains = IsChildSystemParachain<Id>;
+12 -4
View File
@@ -115,7 +115,7 @@ use sp_runtime::traits::Get;
pub use sp_runtime::BuildStorage;
/// Constant values used within the runtime.
use westend_runtime_constants::{currency::*, fee::*, time::*};
use westend_runtime_constants::{currency::*, fee::*, system_parachain::BROKER_ID, time::*};
mod bag_thresholds;
mod weights;
@@ -1149,6 +1149,7 @@ impl parachains_paras::Config for Runtime {
type QueueFootprinter = ParaInclusion;
type NextSessionRotation = Babe;
type OnNewHead = ();
type AssignCoretime = ();
}
parameter_types! {
@@ -1215,7 +1216,13 @@ impl parachains_paras_inherent::Config for Runtime {
}
impl parachains_scheduler::Config for Runtime {
type AssignmentProvider = ParaAssignmentProvider;
// If you change this, make sure the `Assignment` type of the new provider is binary compatible,
// otherwise provide a migration.
type AssignmentProvider = ParachainsAssignmentProvider;
}
parameter_types! {
pub const BrokerId: u32 = BROKER_ID;
}
impl parachains_assigner_parachains::Config for Runtime {}
@@ -1224,6 +1231,7 @@ impl parachains_initializer::Config for Runtime {
type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
type ForceOrigin = EnsureRoot<AccountId>;
type WeightInfo = weights::runtime_parachains_initializer::WeightInfo<Runtime>;
type CoretimeOnNewSession = ();
}
impl paras_sudo_wrapper::Config for Runtime {}
@@ -1485,7 +1493,7 @@ construct_runtime! {
ParaSessionInfo: parachains_session_info::{Pallet, Storage} = 52,
ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event<T>} = 53,
ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 54,
ParaAssignmentProvider: parachains_assigner_parachains::{Pallet, Storage} = 55,
ParachainsAssignmentProvider: parachains_assigner_parachains::{Pallet} = 55,
// Parachain Onboarding Pallets. Start indices at 60 to leave room.
Registrar: paras_registrar::{Pallet, Call, Storage, Event<T>, Config<T>} = 60,
@@ -1636,7 +1644,7 @@ pub mod migrations {
parachains_configuration::migration::v7::MigrateToV7<Runtime>,
pallet_staking::migrations::v14::MigrateToV14<Runtime>,
assigned_slots::migration::v1::MigrateToV1<Runtime>,
parachains_scheduler::migration::v1::MigrateToV1<Runtime>,
parachains_scheduler::migration::MigrateV1ToV2<Runtime>,
parachains_configuration::migration::v8::MigrateToV8<Runtime>,
parachains_configuration::migration::v9::MigrateToV9<Runtime>,
paras_registrar::migration::MigrateToV1<Runtime, ()>,
@@ -49,7 +49,9 @@ pub mod runtime_common_crowdloan;
pub mod runtime_common_identity_migrator;
pub mod runtime_common_paras_registrar;
pub mod runtime_common_slots;
pub mod runtime_parachains_assigner_on_demand;
pub mod runtime_parachains_configuration;
pub mod runtime_parachains_coretime;
pub mod runtime_parachains_disputes;
pub mod runtime_parachains_disputes_slashing;
pub mod runtime_parachains_hrmp;
@@ -0,0 +1,91 @@
// 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/>.
//! Autogenerated weights for `runtime_parachains::assigner_on_demand`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-fljshgub-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
// Executed Command:
// target/production/polkadot
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json
// --pallet=runtime_parachains::assigner_on_demand
// --chain=rococo-dev
// --header=./file_header.txt
// --output=./runtime/rococo/src/weights/
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::Weight};
use core::marker::PhantomData;
/// Weight functions for `runtime_parachains::assigner_on_demand`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> runtime_parachains::assigner_on_demand::WeightInfo for WeightInfo<T> {
/// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Paras::ParaLifecycles` (r:1 w:0)
/// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 9999]`.
fn place_order_keep_alive(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `297 + s * (4 ±0)`
// Estimated: `3762 + s * (4 ±0)`
// Minimum execution time: 33_522_000 picoseconds.
Weight::from_parts(35_436_835, 0)
.saturating_add(Weight::from_parts(0, 3762))
// Standard Error: 129
.saturating_add(Weight::from_parts(14_041, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
}
/// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Paras::ParaLifecycles` (r:1 w:0)
/// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 9999]`.
fn place_order_allow_death(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `297 + s * (4 ±0)`
// Estimated: `3762 + s * (4 ±0)`
// Minimum execution time: 33_488_000 picoseconds.
Weight::from_parts(34_848_934, 0)
.saturating_add(Weight::from_parts(0, 3762))
// Standard Error: 143
.saturating_add(Weight::from_parts(14_215, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
}
}
@@ -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/>.
//! Autogenerated weights for `runtime_parachains::coretime`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-12-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
// Executed Command:
// target/production/polkadot
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
// --pallet=runtime_common::coretime
// --chain=rococo-dev
// --header=./polkadot/file_header.txt
// --output=./polkadot/runtime/rococo/src/weights/
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::Weight};
use core::marker::PhantomData;
use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo};
/// Weight functions for `runtime_common::coretime`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config + configuration::Config> runtime_parachains::coretime::WeightInfo for WeightInfo<T> {
fn request_core_count() -> Weight {
<T as configuration::Config>::WeightInfo::set_config_with_u32()
}
/// Storage: `CoreTimeAssignmentProvider::CoreDescriptors` (r:1 w:1)
/// Proof: `CoreTimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `CoreTimeAssignmentProvider::CoreSchedules` (r:0 w:1)
/// Proof: `CoreTimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 100]`.
fn assign_core(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `3541`
// Minimum execution time: 6_275_000 picoseconds.
Weight::from_parts(6_883_543, 0)
.saturating_add(Weight::from_parts(0, 3541))
// Standard Error: 202
.saturating_add(Weight::from_parts(15_028, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(2))
}
}
@@ -32,6 +32,10 @@ honest-flaky-validator-1: reports parachain_candidate_disputes_total is at least
honest-flaky-validator-1: pause
# Wait for 1 full session to pass after the last unconcluded dispute.
#
# TODO: replace with assertion for "New session detected" in logs. I think that
# would match on previous log lines, so we may need to programmatically wait for
# a specific session, requiring zombienet v2.
sleep 110 seconds
# Now resume flaky validators
@@ -0,0 +1,66 @@
const assert = require("assert");
async function run(nodeName, networkInfo, _jsArgs) {
const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
await zombie.util.cryptoWaitReady();
// account to submit tx
const keyring = new zombie.Keyring({ type: "sr25519" });
const alice = keyring.addFromUri("//Alice");
const calls = [
// Default broker configuration
api.tx.broker.configure({
advanceNotice: 2,
interludeLength: 1,
leadinLength: 1,
regionLength: 3,
idealBulkProportion: 100,
limitCoresOffered: null,
renewalBump: 10,
contributionTimeout: 5,
}),
// Make reservation for ParaId 100 (adder-a) every other block
// and ParaId 101 (adder-b) every other block.
api.tx.broker.reserve([
{
mask: [255, 0, 255, 0, 255, 0, 255, 0, 255, 0],
assignment: { Task: 100 },
},
{
mask: [0, 255, 0, 255, 0, 255, 0, 255, 0, 255],
assignment: { Task: 101 },
},
]),
// Start sale with 1 core starting at 1 planck
api.tx.broker.startSales(1, 1),
];
const sudo_batch = api.tx.sudo.sudo(api.tx.utility.batch(calls));
await new Promise(async (resolve, reject) => {
const unsub = await sudo_batch.signAndSend(alice, (result) => {
console.log(`Current status is ${result.status}`);
if (result.status.isInBlock) {
console.log(
`Transaction included at blockHash ${result.status.asInBlock}`
);
} else if (result.status.isFinalized) {
console.log(
`Transaction finalized at blockHash ${result.status.asFinalized}`
);
unsub();
return resolve();
} else if (result.isError) {
console.log(`Transaction Error`);
unsub();
return reject();
}
});
});
return 0;
}
module.exports = { run };
@@ -0,0 +1,43 @@
const assert = require("assert");
async function run(nodeName, networkInfo, _jsArgs) {
const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
await zombie.util.cryptoWaitReady();
// account to submit tx
const keyring = new zombie.Keyring({ type: "sr25519" });
const alice = keyring.addFromUri("//Alice");
const calls = [
api.tx.configuration.setCoretimeCores({ new: 1 }),
api.tx.coretime.assignCore(0, 20,[[ { task: 1005 }, 57600 ]], null)
];
const sudo_batch = api.tx.sudo.sudo(api.tx.utility.batch(calls));
await new Promise(async (resolve, reject) => {
const unsub = await sudo_batch.signAndSend(alice, (result) => {
console.log(`Current status is ${result.status}`);
if (result.status.isInBlock) {
console.log(
`Transaction included at blockHash ${result.status.asInBlock}`
);
} else if (result.status.isFinalized) {
console.log(
`Transaction finalized at blockHash ${result.status.asFinalized}`
);
unsub();
return resolve();
} else if (result.isError) {
console.log(`Transaction Error`);
unsub();
return reject();
}
});
});
return 0;
}
module.exports = { run };
@@ -0,0 +1,58 @@
[settings]
timeout = 1000
[relaychain]
default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}"
chain = "rococo-local"
command = "polkadot"
[[relaychain.nodes]]
name = "alice"
args = ["-lruntime=debug,parachain=trace" ]
[[relaychain.nodes]]
name = "bob"
args = ["-lruntime=debug,parachain=trace" ]
[[relaychain.nodes]]
name = "charlie"
args = ["-lruntime=debug,parachain=trace" ]
[[parachains]]
id = 1005
chain = "coretime-rococo-local"
[parachains.collator]
name = "coretime-collator"
image = "{{COL_IMAGE}}"
command = "polkadot-parachain"
args = [ "-lruntime=debug,parachain=trace" ]
[[parachains]]
id = 100
add_to_genesis = false
register_para = true
onboard_as_parachain = false
[parachains.collator]
name = "adder-a"
image = "{{COL_IMAGE}}"
command = "adder-collator"
args = [ "-lruntime=debug,parachain=trace" ]
[[parachains]]
id = 101
add_to_genesis = false
register_para = true
onboard_as_parachain = false
[parachains.collator]
name = "adder-b"
image = "{{COL_IMAGE}}"
command = "adder-collator"
args = [ "-lruntime=debug,parachain=trace" ]
[types.Header]
number = "u64"
parent_hash = "Hash"
post_state = "Hash"
@@ -0,0 +1,19 @@
Description: Bulk core assignment Smoke
Network: ./0004-coretime-smoke-test.toml
Creds: config
alice: is up
coretime-collator: is up
alice: reports block height is at least 3 within 30 seconds
# configure relay chain
alice: js-script ./0004-configure-relay.js with "" return is 0 within 600 secs
# Wait 2 sessions. The parachain doesn't start block production immediately.
alice: log line contains "New session detected session_index=2" within 600 seconds
# configure broker chain
coretime-collator: js-script ./0004-configure-broker.js with "" return is 0 within 600 secs
# TODO: Fix this
# alice: parachain 100 block height is at least 10 within 600 seconds
+24
View File
@@ -0,0 +1,24 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: Agile Coretime Base Relaychain Functionality
doc:
- audience: Runtime User
description: |
The relay chain is now capable of receiving assignments from the coretime
chain and will schedule parachains and on-demand orders accordingly.
Existing leases and system chains are preserved. They get a reserved
coretime core via a migration.
migrations:
db: []
runtime:
- reference: polkadot-runtime-parachains
description: |
Claim queue in scheduler now no longer contains Option values and
assignments now contain information necessary to accomodate for coretime
features. Also all existing parachains are converted to coretime
assignments.
crates:
- name: polkadot-runtime-parachains
-10
View File
@@ -1993,7 +1993,6 @@ impl OnUnbalanced<Credit<AccountId, Balances>> for IntoAuthor {
}
parameter_types! {
pub storage CoreCount: Option<CoreIndex> = None;
pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None;
}
@@ -2012,21 +2011,12 @@ impl CoretimeInterface for CoretimeProvider {
_end_hint: Option<u32>,
) {
}
fn check_notify_core_count() -> Option<u16> {
let count = CoreCount::get();
CoreCount::set(&None);
count
}
fn check_notify_revenue_info() -> Option<(u32, Self::Balance)> {
let revenue = CoretimeRevenue::get();
CoretimeRevenue::set(&None);
revenue
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_core_count(count: u16) {
CoreCount::set(&Some(count));
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_revenue_info(when: u32, revenue: Self::Balance) {
CoretimeRevenue::set(&Some((when, revenue)));
}
@@ -784,9 +784,7 @@ fn json_eval_value_at_key(
path: &mut VecDeque<&str>,
fun: &dyn Fn(&json::Value) -> bool,
) -> bool {
let Some(key) = path.pop_front() else {
return false;
};
let Some(key) = path.pop_front() else { return false };
if path.is_empty() {
doc.as_object().map_or(false, |o| o.get(key).map_or(false, |v| fun(v)))
+1 -1
View File
@@ -705,7 +705,7 @@ mod benches {
let core_count = n.try_into().unwrap();
<T::Coretime as CoretimeInterface>::ensure_notify_core_count(core_count);
CoreCountInbox::<T>::put(core_count);
let mut status = Status::<T>::get().ok_or(BenchmarkError::Weightless)?;
@@ -107,11 +107,6 @@ pub trait CoretimeInterface {
end_hint: Option<RCBlockNumberOf<Self>>,
);
/// Indicate that from this block onwards, the range of acceptable values of the `core`
/// parameter of `assign_core` message is `[0, count)`. `assign_core` will be a no-op if
/// provided with a value for `core` outside of this range.
fn check_notify_core_count() -> Option<u16>;
/// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain
/// block number `last_until` to `until`, not including `until` itself. `last_until` is defined
/// as being the `until` argument of the last `notify_revenue` message sent, or zero for the
@@ -123,12 +118,6 @@ pub trait CoretimeInterface {
/// single revenue information destination exists.
fn check_notify_revenue_info() -> Option<(RCBlockNumberOf<Self>, Self::Balance)>;
/// Ensure that core count is updated to the provided value.
///
/// This is only used for benchmarking.
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_core_count(count: u16);
/// Ensure that revenue information is updated to the provided value.
///
/// This is only used for benchmarking.
@@ -151,14 +140,9 @@ impl CoretimeInterface for () {
_end_hint: Option<RCBlockNumberOf<Self>>,
) {
}
fn check_notify_core_count() -> Option<u16> {
None
}
fn check_notify_revenue_info() -> Option<(RCBlockNumberOf<Self>, Self::Balance)> {
None
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_core_count(_count: u16) {}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_revenue_info(_when: RCBlockNumberOf<Self>, _revenue: Self::Balance) {}
}
@@ -37,6 +37,11 @@ impl<T: Config> Pallet<T> {
Ok(())
}
pub(crate) fn do_notify_core_count(core_count: CoreIndex) -> DispatchResult {
CoreCountInbox::<T>::put(core_count);
Ok(())
}
pub(crate) fn do_reserve(workload: Schedule) -> DispatchResult {
let mut r = Reservations::<T>::get();
let index = r.len() as u32;
+12
View File
@@ -159,6 +159,10 @@ pub mod pallet {
pub type InstaPoolHistory<T> =
StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf<T>>;
/// Received core count change from the relay chain.
#[pallet::storage]
pub type CoreCountInbox<T> = StorageValue<_, CoreIndex, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
@@ -774,5 +778,13 @@ pub mod pallet {
Self::do_request_core_count(core_count)?;
Ok(())
}
#[pallet::call_index(19)]
#[pallet::weight(T::WeightInfo::notify_core_count())]
pub fn notify_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
T::AdminOrigin::ensure_origin_or_root(origin)?;
Self::do_notify_core_count(core_count)?;
Ok(())
}
}
}
+1 -9
View File
@@ -70,7 +70,6 @@ parameter_types! {
pub static CoretimeWorkplan: BTreeMap<(u32, CoreIndex), Vec<(CoreAssignment, PartsOf57600)>> = Default::default();
pub static CoretimeUsage: BTreeMap<CoreIndex, Vec<(CoreAssignment, PartsOf57600)>> = Default::default();
pub static CoretimeInPool: CoreMaskBitCount = 0;
pub static NotifyCoreCount: Vec<u16> = Default::default();
pub static NotifyRevenueInfo: Vec<(u32, u64)> = Default::default();
}
@@ -80,7 +79,7 @@ impl CoretimeInterface for TestCoretimeProvider {
type Balance = u64;
type RealyChainBlockNumberProvider = System;
fn request_core_count(count: CoreIndex) {
NotifyCoreCount::mutate(|s| s.insert(0, count));
CoreCountInbox::<Test>::put(count);
}
fn request_revenue_info_at(when: RCBlockNumberOf<Self>) {
if when > RCBlockNumberProviderOf::<Self>::current_block_number() {
@@ -126,17 +125,10 @@ impl CoretimeInterface for TestCoretimeProvider {
);
CoretimeTrace::mutate(|v| v.push(item));
}
fn check_notify_core_count() -> Option<u16> {
NotifyCoreCount::mutate(|s| s.pop())
}
fn check_notify_revenue_info() -> Option<(RCBlockNumberOf<Self>, Self::Balance)> {
NotifyRevenueInfo::mutate(|s| s.pop()).map(|v| (v.0 as _, v.1))
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_core_count(count: u16) {
NotifyCoreCount::mutate(|s| s.insert(0, count));
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_notify_revenue_info(when: RCBlockNumberOf<Self>, revenue: Self::Balance) {
NotifyRevenueInfo::mutate(|s| s.push((when as u32, revenue)));
}
+1 -1
View File
@@ -87,7 +87,7 @@ impl<T: Config> Pallet<T> {
}
pub(crate) fn process_core_count(status: &mut StatusRecord) -> bool {
if let Some(core_count) = T::Coretime::check_notify_core_count() {
if let Some(core_count) = CoreCountInbox::<T>::take() {
status.core_count = core_count;
Self::deposit_event(Event::<T>::CoreCountChanged { core_count });
return true
+8
View File
@@ -74,6 +74,7 @@ pub trait WeightInfo {
fn process_pool() -> Weight;
fn process_core_schedule() -> Weight;
fn request_revenue_info_at() -> Weight;
fn notify_core_count() -> Weight;
fn do_tick_base() -> Weight;
}
@@ -447,6 +448,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Minimum execution time: 147_000 picoseconds.
Weight::from_parts(184_000, 0)
}
fn notify_core_count() -> Weight {
T::DbWeight::get().reads_writes(1, 1)
}
/// Storage: `Broker::Status` (r:1 w:1)
/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
/// Storage: `Broker::Configuration` (r:1 w:0)
@@ -835,6 +839,10 @@ impl WeightInfo for () {
// Minimum execution time: 147_000 picoseconds.
Weight::from_parts(184_000, 0)
}
fn notify_core_count() -> Weight {
RocksDbWeight::get().reads(1)
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `Broker::Status` (r:1 w:1)
/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
/// Storage: `Broker::Configuration` (r:1 w:0)