feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. 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 pezkuwi_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<pezkuwi_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.scheduler_params.num_cores = self.on_demand_cores;
|
||||
config.scheduler_params.on_demand_base_fee = self.on_demand_base_fee;
|
||||
config.scheduler_params.on_demand_fee_variability = self.on_demand_fee_variability;
|
||||
config.scheduler_params.on_demand_queue_max_size = self.on_demand_max_queue_size;
|
||||
config.scheduler_params.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,479 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The teyrchain 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::{
|
||||
configuration, on_demand,
|
||||
paras::AssignCoretime,
|
||||
scheduler::common::{Assignment, AssignmentProvider},
|
||||
ParaId,
|
||||
};
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use frame_support::{defensive, pallet_prelude::*};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use pallet_broker::CoreAssignment;
|
||||
use pezkuwi_primitives::CoreIndex;
|
||||
use sp_runtime::traits::{One, Saturating};
|
||||
|
||||
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,
|
||||
DecodeWithMemTracking,
|
||||
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 + 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,
|
||||
/// assign_core is only allowed to append new assignments at the end of already existing
|
||||
/// ones or update the last entry.
|
||||
DisallowedInsert,
|
||||
}
|
||||
}
|
||||
|
||||
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 => 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 } =>
|
||||
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 } =>
|
||||
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).
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
fn get_mock_assignment(_: CoreIndex, para_id: pezkuwi_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 assignment_duplicated(assignment: &Assignment) {
|
||||
match assignment {
|
||||
Assignment::Pool { para_id, core_index } =>
|
||||
on_demand::Pallet::<T>::assignment_duplicated(*para_id, *core_index),
|
||||
Assignment::Bulk(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 or insertion into the last item. Meaning,
|
||||
/// all already existing assignments must have a `begin` smaller or equal than the one passed
|
||||
/// here.
|
||||
/// Updating the last entry is supported to allow for making a core assignment multiple calls to
|
||||
/// assign_core. Thus if you have too much interlacing for e.g. a single UMP message you can
|
||||
/// split that up into multiple messages, each triggering a call to `assign_core`, together
|
||||
/// forming the total assignment.
|
||||
///
|
||||
/// Inserting arbitrarily causes a `DispatchError::DisallowedInsert` error.
|
||||
// With this restriction this function allows for O(1) complexity. It 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>,
|
||||
mut 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);
|
||||
|
||||
CoreDescriptors::<T>::mutate(core_idx, |core_descriptor| {
|
||||
let new_queue = match core_descriptor.queue {
|
||||
Some(queue) => {
|
||||
ensure!(begin >= queue.last, Error::<T>::DisallowedInsert);
|
||||
|
||||
// Update queue if we are appending:
|
||||
if begin > queue.last {
|
||||
CoreSchedules::<T>::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>::mutate((begin, core_idx), |schedule| {
|
||||
let assignments = if let Some(mut old_schedule) = schedule.take() {
|
||||
old_schedule.assignments.append(&mut assignments);
|
||||
old_schedule.assignments
|
||||
} else {
|
||||
assignments
|
||||
};
|
||||
*schedule = Some(Schedule { assignments, end_hint, next_schedule: None });
|
||||
});
|
||||
|
||||
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::ActiveConfig::<T>::get();
|
||||
let core = config.scheduler_params.num_cores;
|
||||
config.scheduler_params.num_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,785 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. 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, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin, Scheduler,
|
||||
System, Test,
|
||||
},
|
||||
paras::{ParaGenesisArgs, ParaKind},
|
||||
scheduler::common::Assignment,
|
||||
};
|
||||
use frame_support::{assert_noop, assert_ok, pallet_prelude::*};
|
||||
use pallet_broker::TaskId;
|
||||
use pezkuwi_primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};
|
||||
|
||||
fn schedule_blank_para(id: ParaId, parakind: ParaKind) {
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
assert_ok!(Paras::schedule_para_initialize(
|
||||
id,
|
||||
ParaGenesisArgs {
|
||||
genesis_head: Vec::new().into(),
|
||||
validation_code: validation_code.clone(),
|
||||
para_kind: parakind,
|
||||
}
|
||||
));
|
||||
|
||||
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
|
||||
}
|
||||
|
||||
fn run_to_block(
|
||||
to: BlockNumber,
|
||||
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
|
||||
) {
|
||||
while System::block_number() < to {
|
||||
let b = System::block_number();
|
||||
|
||||
Scheduler::initializer_finalize();
|
||||
Paras::initializer_finalize(b);
|
||||
|
||||
if let Some(notification) = new_session(b + 1) {
|
||||
let mut notification_with_session_index = notification;
|
||||
// We will make every session change trigger an action queue. Normally this may require
|
||||
// 2 or more session changes.
|
||||
if notification_with_session_index.session_index == SessionIndex::default() {
|
||||
notification_with_session_index.session_index = ParasShared::scheduled_session();
|
||||
}
|
||||
Paras::initializer_on_new_session(¬ification_with_session_index);
|
||||
Scheduler::initializer_on_new_session(¬ification_with_session_index);
|
||||
}
|
||||
|
||||
System::on_finalize(b);
|
||||
|
||||
System::on_initialize(b + 1);
|
||||
System::set_block_number(b + 1);
|
||||
|
||||
Paras::initializer_initialize(b + 1);
|
||||
Scheduler::initializer_initialize(b + 1);
|
||||
|
||||
// Update the spot traffic and revenue on every block.
|
||||
OnDemand::on_initialize(b + 1);
|
||||
|
||||
// In the real runtime this is expected to be called by the `InclusionInherent` pallet.
|
||||
Scheduler::advance_claim_queue(&Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
fn assign_core_enforces_higher_or_equal_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),
|
||||
vec![(CoreAssignment::Idle, PartsOf57600(28800))],
|
||||
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
|
||||
);
|
||||
// Call assign core again on last entry should work:
|
||||
assert_eq!(
|
||||
CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(15u32),
|
||||
vec![(CoreAssignment::Pool, PartsOf57600(28800))],
|
||||
None,
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_core_enforces_well_formed_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 empty_assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![];
|
||||
|
||||
// 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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[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 = Schedule {
|
||||
next_schedule: None,
|
||||
end_hint: None,
|
||||
assignments: vec![
|
||||
(CoreAssignment::Pool, PartsOf57600(28800)),
|
||||
(CoreAssignment::Idle, PartsOf57600(28800)),
|
||||
],
|
||||
};
|
||||
|
||||
// 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),
|
||||
vec![(CoreAssignment::Pool, PartsOf57600(28800))],
|
||||
None,
|
||||
));
|
||||
// Test updating last entry once more:
|
||||
assert_ok!(CoretimeAssigner::assign_core(
|
||||
core_idx,
|
||||
BlockNumberFor::<Test>::from(start_5),
|
||||
vec![(CoreAssignment::Idle, PartsOf57600(28800))],
|
||||
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);
|
||||
on_demand::Credits::<Test>::insert(&alice, amt);
|
||||
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
|
||||
assert_ok!(OnDemand::place_order_with_credits(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 remaining < 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);
|
||||
}
|
||||
Reference in New Issue
Block a user