From 69434d9a32af6103d7cbde9798cf594027fb620e Mon Sep 17 00:00:00 2001 From: eskimor Date: Thu, 21 Dec 2023 19:06:58 +0100 Subject: [PATCH] Coretime Feature branch (relay chain) (#1694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com> Co-authored-by: BradleyOlson64 Co-authored-by: Anton Vilhelm Ásgeirsson Co-authored-by: antonva Co-authored-by: Bastian Köcher Co-authored-by: Marcin S. Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> --- Cargo.lock | 3 + .../coretime/coretime-rococo/src/coretime.rs | 11 - .../coretime/coretime-rococo/src/lib.rs | 2 +- .../src/weights/pallet_broker.rs | 3 + .../core/prospective-parachains/src/tests.rs | 5 +- .../src/runtime/scheduler.md | 1 + polkadot/runtime/common/Cargo.toml | 4 + .../runtime/common/src/assigned_slots/mod.rs | 1 + .../runtime/common/src/integration_tests.rs | 1 + .../runtime/common/src/paras_registrar/mod.rs | 1 + .../runtime/common/src/paras_sudo_wrapper.rs | 11 +- polkadot/runtime/common/src/slots/mod.rs | 20 +- polkadot/runtime/parachains/Cargo.toml | 6 + polkadot/runtime/parachains/src/assigner.rs | 112 --- .../src/assigner_coretime/mock_helpers.rs | 87 ++ .../parachains/src/assigner_coretime/mod.rs | 496 ++++++++++ .../parachains/src/assigner_coretime/tests.rs | 817 +++++++++++++++++ .../src/assigner_on_demand/benchmarking.rs | 12 +- .../src/assigner_on_demand/mock_helpers.rs | 4 +- .../parachains/src/assigner_on_demand/mod.rs | 118 +-- .../src/assigner_on_demand/tests.rs | 253 ++++-- .../parachains/src/assigner_parachains.rs | 35 +- .../src/assigner_parachains/mock_helpers.rs | 83 ++ .../src/assigner_parachains/tests.rs | 112 +++ polkadot/runtime/parachains/src/builder.rs | 50 +- .../runtime/parachains/src/configuration.rs | 28 +- .../src/configuration/migration/v11.rs | 6 +- .../parachains/src/configuration/tests.rs | 4 +- .../parachains/src/coretime/benchmarking.rs | 73 ++ .../parachains/src/coretime/migration.rs | 285 ++++++ .../runtime/parachains/src/coretime/mod.rs | 251 +++++ .../runtime/parachains/src/inclusion/tests.rs | 4 +- .../runtime/parachains/src/initializer.rs | 15 + polkadot/runtime/parachains/src/lib.rs | 3 +- polkadot/runtime/parachains/src/mock.rs | 159 +++- polkadot/runtime/parachains/src/paras/mod.rs | 24 + .../src/paras_inherent/benchmarking.rs | 21 +- .../parachains/src/paras_inherent/mod.rs | 4 +- .../parachains/src/paras_inherent/tests.rs | 41 +- .../parachains/src/runtime_api_impl/v7.rs | 2 +- polkadot/runtime/parachains/src/scheduler.rs | 206 ++--- .../parachains/src/scheduler/common.rs | 86 +- .../parachains/src/scheduler/migration.rs | 310 +++++-- .../runtime/parachains/src/scheduler/tests.rs | 858 ++++++------------ .../parachains/src/session_info/tests.rs | 2 +- polkadot/runtime/rococo/constants/src/lib.rs | 2 + polkadot/runtime/rococo/src/lib.rs | 70 +- polkadot/runtime/rococo/src/weights/mod.rs | 1 + .../weights/runtime_parachains_coretime.rs | 73 ++ polkadot/runtime/rococo/src/xcm_config.rs | 3 + polkadot/runtime/test-runtime/src/lib.rs | 12 +- polkadot/runtime/westend/constants/src/lib.rs | 2 + polkadot/runtime/westend/src/lib.rs | 16 +- polkadot/runtime/westend/src/weights/mod.rs | 2 + .../runtime_parachains_assigner_on_demand.rs | 91 ++ .../weights/runtime_parachains_coretime.rs | 73 ++ ...005-parachains-disputes-past-session.zndsl | 4 + .../smoke/0004-configure-broker.js | 66 ++ .../smoke/0004-configure-relay.js | 43 + .../smoke/0004-coretime-smoke-test.toml | 58 ++ .../smoke/0004-coretime-smoke-test.zndsl | 19 + prdoc/pr_1694.prdoc | 24 + substrate/bin/node/runtime/src/lib.rs | 10 - substrate/client/chain-spec/src/chain_spec.rs | 4 +- substrate/frame/broker/src/benchmarking.rs | 2 +- .../frame/broker/src/coretime_interface.rs | 16 - .../frame/broker/src/dispatchable_impls.rs | 5 + substrate/frame/broker/src/lib.rs | 12 + substrate/frame/broker/src/mock.rs | 10 +- substrate/frame/broker/src/tick_impls.rs | 2 +- substrate/frame/broker/src/weights.rs | 8 + 71 files changed, 4052 insertions(+), 1206 deletions(-) delete mode 100644 polkadot/runtime/parachains/src/assigner.rs create mode 100644 polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs create mode 100644 polkadot/runtime/parachains/src/assigner_coretime/mod.rs create mode 100644 polkadot/runtime/parachains/src/assigner_coretime/tests.rs create mode 100644 polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs create mode 100644 polkadot/runtime/parachains/src/assigner_parachains/tests.rs create mode 100644 polkadot/runtime/parachains/src/coretime/benchmarking.rs create mode 100644 polkadot/runtime/parachains/src/coretime/migration.rs create mode 100644 polkadot/runtime/parachains/src/coretime/mod.rs create mode 100644 polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs create mode 100644 polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs create mode 100644 polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs create mode 100644 polkadot/zombienet_tests/smoke/0004-configure-broker.js create mode 100644 polkadot/zombienet_tests/smoke/0004-configure-relay.js create mode 100644 polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml create mode 100644 polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl create mode 100644 prdoc/pr_1694.prdoc diff --git a/Cargo.lock b/Cargo.lock index 4fcae91e5ba1..ba30e05b5ba1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs index 3cdcff33cfbd..a85d67f7b4cb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -199,23 +199,12 @@ impl CoretimeInterface for CoretimeAllocator { } } - fn check_notify_core_count() -> Option { - let count = CoreCount::get(); - CoreCount::set(&None); - count - } - fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, 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, revenue: Self::Balance) { CoretimeRevenue::set(&Some((when, revenue))); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index ef6eb02b4e2a..e87611a523ec 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -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, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs index c33cc422d11b..3cc51a247f7b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -463,6 +463,9 @@ impl pallet_broker::WeightInfo for WeightInfo { .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) diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 51a5ef622c04..7e369245c0e1 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -101,9 +101,8 @@ fn test_harness>( 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 diff --git a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md index 26058c446cb9..32a7fe652dbc 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md @@ -182,6 +182,7 @@ struct CoreAssignment { core: CoreIndex, para_id: ParaId, kind: AssignmentKind, + group_idx: GroupIndex, } // reasons a core might be freed. enum FreedReason { diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 1af29be0ae8f..c841c0847c0f 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -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", diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index f5e3aaef6324..cb56cb8a118c 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -743,6 +743,7 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl parachains_shared::Config for Test {} diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index b0d277a702d6..4870432d22f9 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -212,6 +212,7 @@ impl paras::Config for Test { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } parameter_types! { diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 12376ae6f1ff..9719f02677dc 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -814,6 +814,7 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl configuration::Config for Test { diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index 0fc2644b2a0b..4735c1763291 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -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 Pallet { /// 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::(id, genesis) .map_err(|_| Error::::ParaAlreadyExists)?; + + T::AssignCoretime::assign_coretime(id)?; + Ok(()) } diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index c3aaf8b51b81..6a8cddd8d914 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -326,6 +326,18 @@ impl Pallet { tracker.into_iter().collect() } + + /// Current lease index and how many blocks we are already in. + pub fn lease_period_index_plus_progress( + b: BlockNumberFor, + ) -> Option<(>>::LeasePeriod, BlockNumberFor)> { + // 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 crate::traits::OnSwap for Pallet { @@ -449,12 +461,8 @@ impl Leaser> for Pallet { } fn lease_period_index(b: BlockNumberFor) -> 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( diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 0bcf5c04c344..1f381400cf53 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -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", diff --git a/polkadot/runtime/parachains/src/assigner.rs b/polkadot/runtime/parachains/src/assigner.rs deleted file mode 100644 index 9e408df61dc1..000000000000 --- a/polkadot/runtime/parachains/src/assigner.rs +++ /dev/null @@ -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 . - -//! 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(_); - - #[pallet::config] - pub trait Config: frame_system::Config + configuration::Config + paras::Config { - type ParachainsAssignmentProvider: AssignmentProvider>; - type OnDemandAssignmentProvider: AssignmentProvider>; - } -} - -// Aliases to make the impl more readable. -type ParachainAssigner = ::ParachainsAssignmentProvider; -type OnDemandAssigner = ::OnDemandAssignmentProvider; - -impl Pallet { - // 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 = - as AssignmentProvider>>::session_core_count(); - - core_idx.0 < parachain_cores - } -} - -impl AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - let parachain_cores = - as AssignmentProvider>>::session_core_count(); - let on_demand_cores = - as AssignmentProvider>>::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, - ) -> Option { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::pop_assignment_for_core( - core_idx, - concluded_para, - ) - } else { - as AssignmentProvider>>::pop_assignment_for_core( - core_idx, - concluded_para, - ) - } - } - - fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment) { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::push_assignment_for_core( - core_idx, assignment, - ) - } else { - as AssignmentProvider>>::push_assignment_for_core( - core_idx, assignment, - ) - } - } - - fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig> { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::get_provider_config( - core_idx, - ) - } else { - as AssignmentProvider>>::get_provider_config( - core_idx, - ) - } - } -} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs new file mode 100644 index 000000000000..71c3f1fa39f7 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs @@ -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 . + +//! 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, +} + +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 + } +} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/mod.rs b/polkadot/runtime/parachains/src/assigner_coretime/mod.rs new file mode 100644 index 000000000000..9da81dc816ca --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mod.rs @@ -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 . + +//! 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 { + 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 { + // 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, + + /// The next queued schedule for this core. + /// + /// Schedules are forming a queue. + next_schedule: Option, +} + +/// 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 { + /// Meta data about the queued schedules for this core. + queue: Option>, + /// Currently performed work. + current_work: Option>, +} + +/// 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 { + /// 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 { + /// 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, + /// 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 From> for WorkState { + fn from(schedule: Schedule) -> 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(_); + + #[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 = StorageMap< + _, + Twox256, + (BlockNumberFor, CoreIndex), + Schedule>, + 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 = StorageMap< + _, + Twox256, + CoreIndex, + CoreDescriptor>, + ValueQuery, + GetDefault, + >; + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::error] + pub enum Error { + 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 AssignmentProvider> for Pallet { + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { + let now = >::block_number(); + + CoreDescriptors::::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::::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::::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::::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> { + let config = >::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 = >::config(); + config.coretime_cores + } +} + +impl Pallet { + /// Ensure given workload for core is up to date. + fn ensure_workload( + now: BlockNumberFor, + core_idx: CoreIndex, + descriptor: &mut CoreDescriptor>, + ) { + // 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::::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, + assignments: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) -> Result<(), DispatchError> { + // There should be at least one assignment. + ensure!(!assignments.is_empty(), Error::::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::::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::::OverScheduled) + })?; + ensure!(parts_sum.is_full(), Error::::UnderScheduled); + + CoreDescriptors::::mutate(core_idx, |core_descriptor| { + let new_queue = match core_descriptor.queue { + Some(queue) => { + ensure!(begin > queue.last, Error::::DisallowedInsert); + + CoreSchedules::::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::::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::::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::::insert( + (begin, core_idx), + Schedule { assignments, end_hint, next_schedule: None }, + ); + QueueDescriptor { first: begin, last: begin } + }, + }; + core_descriptor.queue = Some(new_queue); + Ok(()) + }) + } +} + +impl AssignCoretime for Pallet { + fn assign_coretime(id: ParaId) -> DispatchResult { + let current_block = frame_system::Pallet::::block_number(); + + // Add a new core and assign the para to it. + let mut config = >::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::::force_set_active_config(config); + + let begin = current_block + One::one(); + let assignment = vec![(pallet_broker::CoreAssignment::Task(id.into()), PartsOf57600::FULL)]; + Pallet::::assign_core(CoreIndex(core), begin, assignment, None) + } +} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/tests.rs b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs new file mode 100644 index 000000000000..998e39670f97 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs @@ -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 . + +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>, +) { + while System::block_number() < to { + let b = System::block_number(); + + Scheduler::initializer_finalize(); + Paras::initializer_finalize(b); + + if let Some(notification) = new_session(b + 1) { + let mut notification_with_session_index = notification; + // We will make every session change trigger an action queue. Normally this may require + // 2 or more session changes. + if notification_with_session_index.session_index == SessionIndex::default() { + notification_with_session_index.session_index = ParasShared::scheduled_session(); + } + Paras::initializer_on_new_session(¬ification_with_session_index); + Scheduler::initializer_on_new_session(¬ification_with_session_index); + } + + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + Paras::initializer_initialize(b + 1); + Scheduler::initializer_initialize(b + 1); + + // In the real runtime this is expected to be called by the `InclusionInherent` pallet. + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); + } +} + +fn default_test_assignments() -> Vec<(CoreAssignment, PartsOf57600)> { + vec![(CoreAssignment::Idle, PartsOf57600::FULL)] +} + +fn default_test_schedule() -> Schedule> { + 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::::from(11u32), + default_test_assignments(), + None, + )); + + // Check CoreSchedules + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(11u32), core_idx)), + Some(default_test_schedule()) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(BlockNumberFor::::from(11u32)) + ); + assert_eq!( + CoreDescriptors::::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)), + Some(BlockNumberFor::::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::::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::::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::::from(11u32), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(15u32), + default_test_assignments(), + None, + )); + + // Check CoreSchedules for two entries + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(11u32), core_idx)), + Some(default_with_next_schedule) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(15u32), core_idx)), + Some(default_test_schedule()) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(BlockNumberFor::::from(11u32)) + ); + assert_eq!( + CoreDescriptors::::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)), + Some(BlockNumberFor::::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::::from(12u32), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::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::::from(11u32), + default_test_assignments(), + None, + ), + Error::::DisallowedInsert + ); + + // Call assign core with block number between already scheduled assignments, expecting an + // error + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(13u32), + default_test_assignments(), + None, + ), + Error::::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::::from(11u32), + empty_assignments, + None, + ), + Error::::AssignmentsEmpty + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + overscheduled, + None, + ), + Error::::OverScheduled + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + underscheduled, + None, + ), + Error::::UnderScheduled + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + not_unique, + None, + ), + Error::::AssignmentsNotSorted + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + not_sorted, + None, + ), + Error::::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::::from(start_1), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_2), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_3), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_4), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::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::::get((BlockNumberFor::::from(start_3), core_idx)), + Some(expected_schedule_3) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(start_4), core_idx)), + Some(expected_schedule_4) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(start_5), core_idx)), + Some(expected_schedule_5) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(start_3) + ); + assert_eq!( + CoreDescriptors::::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> = + CoreDescriptor { queue: None, current_work: None }; + let assignments_queued_descriptor = CoreDescriptor { + queue: Some(QueueDescriptor { + first: BlockNumberFor::::from(11u32), + last: BlockNumberFor::::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::::from(15u32)), + pos: 0, + step: PartsOf57600::FULL, + }), + }; + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + let mut core_descriptor: CoreDescriptor> = 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::::from(11u32), + vec![(CoreAssignment::Pool, PartsOf57600::FULL)], + Some(BlockNumberFor::::from(15u32)), + )); + + // Propagate changes from storage to Core_Descriptor handle. Normally + // pop_assignment_for_core would handle this. + core_descriptor = CoreDescriptors::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.assignments[0].1.remaining)), + Some(ratio_1) + ); + assert_eq!( + CoreDescriptors::::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 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 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); +} diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs b/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs index 42ca94d5185f..5a6060cd2b4e 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs @@ -43,7 +43,7 @@ where { ParasShared::::set_session_index(SESSION_INDEX); let mut config = HostConfiguration::default(); - config.on_demand_cores = 1; + config.coretime_cores = 1; ConfigurationPallet::::force_set_active_config(config); let mut parachains = ParachainsCache::new(); ParasPallet::::initialize_para_now( @@ -70,11 +70,10 @@ mod benchmarks { let para_id = ParaId::from(111u32); init_parathread::(para_id); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); for _ in 0..s { - Pallet::::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back) - .unwrap(); + Pallet::::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::(para_id); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); for _ in 0..s { - Pallet::::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back) - .unwrap(); + Pallet::::add_on_demand_order(order.clone(), QueuePushDirection::Back).unwrap(); } #[extrinsic_call] diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs index acfb24cbf194..de30330ac84e 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs @@ -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; diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs index f4214027a6bd..1b746e88694c 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs @@ -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() -> VecDeque { + pub(super) fn OnDemandQueueOnEmpty() -> VecDeque { 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 = - StorageValue<_, VecDeque, ValueQuery, OnDemandQueueOnEmpty>; + pub(super) type OnDemandQueue = + StorageValue<_, VecDeque, ValueQuery, OnDemandQueueOnEmpty>; /// 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 = >::config(); - // Are there any schedulable cores in this session - ensure!(config.on_demand_cores > 0, Error::::NoOnDemandCores); - // Traffic always falls back to 1.0 let traffic = SpotTraffic::::get(); @@ -344,17 +344,15 @@ where existence_requirement, )?; - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); - let res = Pallet::::add_on_demand_assignment(assignment, QueuePushDirection::Back); + let res = Pallet::::add_on_demand_order(order, QueuePushDirection::Back); - match res { - Ok(_) => { - Pallet::::deposit_event(Event::::OnDemandOrderPlaced { para_id, spot_price }); - return Ok(()) - }, - Err(err) => return Err(err), + if res.is_ok() { + Pallet::::deposit_event(Event::::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!(>::is_parathread(assignment.para_id), Error::::InvalidParaId); + ensure!(>::is_parathread(order.para_id), Error::::InvalidParaId); let config = >::config(); @@ -454,8 +452,8 @@ where // Abort transaction if queue is too large ensure!(Self::queue_size() < config.on_demand_queue_max_size, Error::::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 { + #[cfg(test)] + fn get_queue() -> VecDeque { OnDemandQueue::::get() } @@ -528,12 +527,7 @@ where } } -impl AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - let config = >::config(); - config.on_demand_cores - } - +impl Pallet { /// 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 AssignmentProvider> for Pallet { /// /// 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, - ) -> Option { - // 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::::decrease_affinity(previous_para_id, core_idx) - } - - let mut queue: VecDeque = OnDemandQueue::::get(); + pub fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { + let mut queue: VecDeque = OnDemandQueue::::get(); let mut invalidated_para_id_indexes: Vec = vec![]; @@ -591,28 +573,28 @@ impl AssignmentProvider> for Pallet { // Write changes to storage. OnDemandQueue::::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::::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::::decrease_affinity(assignment.para_id, core_idx); + pub fn push_back_assignment(para_id: ParaId, core_index: CoreIndex) { + Pallet::::decrease_affinity(para_id, core_index); // Skip the queue on push backs from scheduler - match Pallet::::add_on_demand_assignment(assignment, QueuePushDirection::Front) { + match Pallet::::add_on_demand_order( + EnqueuedOrder::new(para_id), + QueuePushDirection::Front, + ) { Ok(_) => {}, Err(_) => {}, } } - - fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { - let config = >::config(); - AssignmentProviderConfig { - max_availability_timeouts: config.on_demand_retries, - ttl: config.on_demand_ttl, - } - } } diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs index d07964b69165..8404700780c8 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs @@ -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::::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::::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 = OnDemandQueue::::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 = OnDemandQueue::::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 = OnDemandQueue::::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 = OnDemandQueue::::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; - - 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)); + // Clear affinity + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_b, 1.into()); - assert_noop!( - OnDemandAssigner::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id), - Error::::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); }); } diff --git a/polkadot/runtime/parachains/src/assigner_parachains.rs b/polkadot/runtime/parachains/src/assigner_parachains.rs index 866e8290052a..34b5d3c1ec51 100644 --- a/polkadot/runtime/parachains/src/assigner_parachains.rs +++ b/polkadot/runtime/parachains/src/assigner_parachains.rs @@ -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 AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - paras::Parachains::::decode_len().unwrap_or(0) as u32 - } - - fn pop_assignment_for_core( - core_idx: CoreIndex, - _concluded_para: Option, - ) -> Option { + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { >::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> { AssignmentProviderConfig { @@ -65,4 +67,13 @@ impl AssignmentProvider> for Pallet { 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::::decode_len().unwrap_or(0) as u32 + } } diff --git a/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs new file mode 100644 index 000000000000..e6e9fb074aa9 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs @@ -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 . + +//! 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, +} + +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 + } +} diff --git a/polkadot/runtime/parachains/src/assigner_parachains/tests.rs b/polkadot/runtime/parachains/src/assigner_parachains/tests.rs new file mode 100644 index 000000000000..a110686aaeb0 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_parachains/tests.rs @@ -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 . + +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>, +) { + while System::block_number() < to { + let b = System::block_number(); + + Scheduler::initializer_finalize(); + Paras::initializer_finalize(b); + + if let Some(notification) = new_session(b + 1) { + let mut notification_with_session_index = notification; + // We will make every session change trigger an action queue. Normally this may require + // 2 or more session changes. + if notification_with_session_index.session_index == SessionIndex::default() { + notification_with_session_index.session_index = ParasShared::scheduled_session(); + } + Paras::initializer_on_new_session(¬ification_with_session_index); + Scheduler::initializer_on_new_session(¬ification_with_session_index); + } + + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + Paras::initializer_initialize(b + 1); + Scheduler::initializer_initialize(b + 1); + + // In the real runtime this is expected to be called by the `InclusionInherent` pallet. + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); + } +} + +// This and the scheduler test schedule_schedules_including_just_freed together +// ensure that next_up_on_available and next_up_on_time_out will always be +// filled with scheduler claims for lease holding parachains. (Removes the need +// for two other scheduler tests) +#[test] +fn parachains_assigner_pop_assignment_is_always_some() { + let core_index = CoreIndex(0); + let para_id = ParaId::from(10); + let expected_assignment = Assignment::Bulk(para_id); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // Register the para_id as a lease holding parachain + schedule_blank_para(para_id, ParaKind::Parachain); + + assert!(!Paras::is_parachain(para_id)); + run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); + assert!(Paras::is_parachain(para_id)); + + for _ in 0..20 { + assert!( + ParachainsAssigner::pop_assignment_for_core(core_index) == + Some(expected_assignment.clone()) + ); + } + + run_to_block(20, |n| if n == 20 { Some(Default::default()) } else { None }); + + for _ in 0..20 { + assert!( + ParachainsAssigner::pop_assignment_for_core(core_index) == + Some(expected_assignment.clone()) + ); + } + }); +} diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index 23916bbdc8ad..016b3fca589a 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -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 { /// 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, + /// Specifies whether the claimqueue should be filled. + fill_claimqueue: bool, _phantom: sp_std::marker::PhantomData, } @@ -122,6 +124,7 @@ impl BenchBuilder { dispute_sessions: Default::default(), backed_and_concluding_cores: Default::default(), code_upgrade: None, + fill_claimqueue: true, _phantom: sp_std::marker::PhantomData::, } } @@ -225,6 +228,13 @@ impl BenchBuilder { 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 BenchBuilder { }) .collect(); - DisputeStatementSet { candidate_hash: candidate_hash, session, statements } + DisputeStatementSet { candidate_hash, session, statements } }) .collect() } @@ -663,14 +673,18 @@ impl BenchBuilder { inclusion::PendingAvailability::::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::::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 BenchBuilder { .map(|i| { let AssignmentProviderConfig { ttl, .. } = scheduler::Pallet::::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 = ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + CoreOccupied::Paras(ParasEntry::new(assignment, now + ttl)) }) .collect(); scheduler::AvailabilityCores::::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::::assignment_provider_config(CoreIndex(i)); + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + (CoreIndex(i), [ParasEntry::new(assignment, now + ttl)].into()) + }) + .collect(); + scheduler::ClaimQueue::::set(cores); + } Bench:: { data: ParachainsInherentData { diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 272c227dfefe..4619313590eb 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -172,8 +172,8 @@ pub struct HostConfiguration { /// 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> Default for HostConfiguration, new: u32) -> DispatchResult { + pub fn set_coretime_cores(origin: OriginFor, 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 Pallet { + /// 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 Hooks> for Pallet { fn integrity_test() { diff --git a/polkadot/runtime/parachains/src/configuration/migration/v11.rs b/polkadot/runtime/parachains/src/configuration/migration/v11.rs index b7dec7070f93..f4db9196b1a0 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v11.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v11.rs @@ -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); diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index d88572d3b558..c915eb12a0ca 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -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(); diff --git a/polkadot/runtime/parachains/src/coretime/benchmarking.rs b/polkadot/runtime/parachains/src/coretime/benchmarking.rs new file mode 100644 index 000000000000..d1ac71f580ee --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/benchmarking.rs @@ -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 . + +//! 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 = ::RuntimeOrigin::root(); + + #[extrinsic_call] + _( + root_origin as ::RuntimeOrigin, + // random core count + 100, + ) + } + + #[benchmark] + fn assign_core(s: Linear<1, 100>) { + // Setup + let root_origin = ::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 ::RuntimeOrigin, + core_index, + BlockNumberFor::::from(5u32), + assignments, + Some(BlockNumberFor::::from(20u32)), + ) + } +} diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs new file mode 100644 index 000000000000..64c10f731988 --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -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 . + +//! 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 { + /// If parachain is a lease holding parachain, return the block at which the lease expires. + fn get_parachain_lease_in_blocks(para: ParaId) -> Option; + } + + /// 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( + sp_std::marker::PhantomData<(T, SendXcm, LegacyLease)>, + ); + + impl>> + MigrateToCoretime + { + 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::::name_hash(); + let mut next_key = name_hash.to_vec(); + let storage_version_key = StorageVersion::storage_key::>(); + + 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>, + > OnRuntimeUpgrade for MigrateToCoretime + { + fn on_runtime_upgrade() -> Weight { + if Self::already_migrated() { + return Weight::zero() + } + + log::info!("Migrating existing parachains to coretime."); + migrate_to_coretime::() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + if Self::already_migrated() { + return Ok(Vec::new()) + } + + let legacy_paras = paras::Parachains::::get(); + let config = >::config(); + let total_core_count = config.coretime_cores + legacy_paras.len() as u32; + + let dmp_queue_size = + crate::dmp::Pallet::::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) -> 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::::dmq_contents(T::BrokerId::get().into()).len() as u32; + let new_core_count = assigner_coretime::Pallet::::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>, + >() -> Weight { + let legacy_paras = paras::Pallet::::parachains(); + let legacy_count = legacy_paras.len() as u32; + let now = >::block_number(); + for (core, para_id) in legacy_paras.into_iter().enumerate() { + let r = assigner_coretime::Pallet::::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 = >::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::::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::::mutate(|c| { + c.coretime_cores = total_cores; + }); + + if let Err(err) = migrate_send_assignments_to_coretime_chain::() { + log::error!("Sending legacy chain data to coretime chain failed: {:?}", err); + } + + let single_weight = ::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>, + >() -> result::Result<(), SendError> { + let legacy_paras = paras::Pallet::::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::::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::( + MultiLocation { + parents: 0, + interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())), + }, + message, + )?; + Ok(()) + } +} diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs new file mode 100644 index 000000000000..d5b044c0631e --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -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 . + +//! Extrinsics implementing the relay chain side of the Coretime interface. +//! +//! + +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(_); + + #[pallet::config] + pub trait Config: frame_system::Config + assigner_coretime::Config { + type RuntimeOrigin: From<::RuntimeOrigin> + + Into::RuntimeOrigin>>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The runtime's definition of a Currency. + type Currency: Currency; + /// The ParaId of the broker system parachain. + #[pallet::constant] + type BrokerId: Get; + /// 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 { + /// The broker chain has asked for revenue information for a specific block. + RevenueInfoRequested { when: BlockNumberFor }, + /// A core has received a new assignment from the broker chain. + CoreAssigned { core: CoreIndex }, + } + + #[pallet::error] + pub enum Error { + /// The paraid making the call is not the coretime brokerage system parachain. + NotBroker, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(::WeightInfo::request_core_count())] + #[pallet::call_index(1)] + pub fn request_core_count(origin: OriginFor, count: u16) -> DispatchResult { + // Ignore requests not coming from the broker parachain or root. + Self::ensure_root_or_para(origin, ::BrokerId::get().into())?; + + configuration::Pallet::::set_coretime_cores_unchecked(u32::from(count)) + } + + //// TODO Impl me! + ////#[pallet::weight(::WeightInfo::request_revenue_info_at())] + //#[pallet::call_index(2)] + //pub fn request_revenue_info_at( + // origin: OriginFor, + // _when: BlockNumberFor, + //) -> DispatchResult { + // // Ignore requests not coming from the broker parachain or root. + // Self::ensure_root_or_para(origin, ::BrokerId::get().into())?; + // Ok(()) + //} + + //// TODO Impl me! + ////#[pallet::weight(::WeightInfo::credit_account())] + //#[pallet::call_index(3)] + //pub fn credit_account( + // origin: OriginFor, + // _who: T::AccountId, + // _amount: BalanceOf, + //) -> DispatchResult { + // // Ignore requests not coming from the broker parachain or root. + // Self::ensure_root_or_para(origin, ::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(::WeightInfo::assign_core(assignment.len() as u32))] + pub fn assign_core( + origin: OriginFor, + core: BrokerCoreIndex, + begin: BlockNumberFor, + assignment: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) -> 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(); + + >::assign_core(core, begin, assignment, end_hint)?; + Self::deposit_event(Event::::CoreAssigned { core }); + Ok(()) + } + } +} + +impl Pallet { + /// Ensure the origin is one of Root or the `para` itself. + fn ensure_root_or_para( + origin: ::RuntimeOrigin, + id: ParaId, + ) -> DispatchResult { + if let Ok(caller_id) = ensure_parachain(::RuntimeOrigin::from(origin.clone())) + { + // Check if matching para id... + ensure!(caller_id == id, Error::::NotBroker); + } else { + // Check if root... + ensure_root(origin.clone())?; + } + Ok(()) + } + + pub fn initializer_on_new_session(notification: &SessionChangeNotification>) { + 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::( + MultiLocation { + parents: 0, + interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())), + }, + message, + ) { + log::error!("Sending `NotifyCoreCount` to coretime chain failed: {:?}", err); + } + } + } +} + +impl OnNewSession> for Pallet { + fn on_new_session(notification: &SessionChangeNotification>) { + 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(), + } +} diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 6bb731671f6f..232e65d78ed2 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -47,7 +47,7 @@ use test_helpers::{dummy_collator, dummy_collator_signature, dummy_validation_co fn default_config() -> HostConfiguration { 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 { diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs index b4f8721be518..3c8ab7c4726f 100644 --- a/polkadot/runtime/parachains/src/initializer.rs +++ b/polkadot/runtime/parachains/src/initializer.rs @@ -60,6 +60,16 @@ pub struct SessionChangeNotification { pub session_index: SessionIndex, } +/// Inform something about a new session. +pub trait OnNewSession { + /// A new session was started. + fn on_new_session(notification: &SessionChangeNotification); +} + +impl OnNewSession for () { + fn on_new_session(_: &SessionChangeNotification) {} +} + /// Number of validators (not only parachain) in a session. pub type ValidatorSetCount = u32; @@ -120,6 +130,10 @@ pub mod pallet { type Randomness: Randomness>; /// An origin which is allowed to force updates to parachains. type ForceOrigin: EnsureOrigin<::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>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -271,6 +285,7 @@ impl Pallet { T::SlashingHandler::initializer_on_new_session(session_index); dmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); hrmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); + T::CoretimeOnNewSession::on_new_session(¬ification); } /// Should be called when a new session occurs. Buffers the session notification to be applied diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index 2509edbee3cb..b0dc27b72863 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -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; diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index 222942922f91..fbaab1d24aaf 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -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; type Block = frame_system::mocking::MockBlockU32; @@ -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; type ForceOrigin = frame_system::EnsureRoot; 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 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; + type BrokerId = BrokerId; + type WeightInfo = crate::coretime::TestWeightInfo; + type SendXcm = DummyXcmSender; +} + +pub struct DummyXcmSender; +impl SendXcm for DummyXcmSender { + type Ticket = (); + fn validate( + _: &mut Option, + _: &mut Option>, + ) -> SendResult { + Ok(((), MultiAssets::new())) + } + + /// Actually carry out the delivery operation for a previously validated message sending. + fn deliver(_ticket: Self::Ticket) -> Result { + Ok([0u8; 32]) + } +} + impl crate::inclusion::Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; @@ -390,6 +427,104 @@ impl ValidatorSetWithIdentification 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(_); + + #[pallet::config] + pub trait Config: frame_system::Config + configuration::Config + paras::Config {} + + #[pallet::storage] + pub(super) type MockAssignmentQueue = + StorageValue<_, VecDeque, ValueQuery>; + + #[pallet::storage] + pub(super) type MockProviderConfig = + StorageValue<_, AssignmentProviderConfig, OptionQuery>; + + #[pallet::storage] + pub(super) type MockCoreCount = StorageValue<_, u32, OptionQuery>; + } + + impl Pallet { + /// 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::::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) { + MockProviderConfig::::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::::set(Some(count)); + } + } + + impl AssignmentProvider for Pallet { + // 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 { + let mut queue: VecDeque = MockAssignmentQueue::::get(); + let front = queue.pop_front(); + // Write changes to storage. + MockAssignmentQueue::::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 { + match MockProviderConfig::::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::::get().unwrap_or(5) + } + } +} + +impl mock_assigner::pallet::Config for Test {} + pub struct FoolIdentificationOf; impl sp_runtime::traits::Convert> for FoolIdentificationOf { fn convert(_: AccountId) -> Option<()> { diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index ef9dfedd7350..e97df8e4a2b3 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -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::::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 } diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index 4509c5c3cc72..0f6b23ae1b39 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -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::::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::::fallback_min_validity_votes() as usize; + let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), BenchBuilder::::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::::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::::fallback_min_validity_votes() + backing_validators.1.len(), + votes, ); } diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 8e918d35d5ff..8c33199c0923 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -548,7 +548,7 @@ impl Pallet { let disputed_bitfield = create_disputed_bitfield(expected_bits, freed_disputed.keys()); if !freed_disputed.is_empty() { - >::update_claimqueue(freed_disputed.clone(), now); + >::free_cores_and_fill_claimqueue(freed_disputed.clone(), now); } let bitfields = sanitize_bitfields::( @@ -580,7 +580,7 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); - >::update_claimqueue(freed, now); + >::free_cores_and_fill_claimqueue(freed, now); let scheduled = >::scheduled_paras() .map(|(core_idx, para_id)| (para_id, core_idx)) .collect(); diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 4fc60792e346..e62d1cb68ffe 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -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, num_validators_per_core: u32, code_upgrade: Option, + 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 { let builder = BenchBuilder::::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::::set_core_count(builder.max_cores()); + for core_index in 0..builder.max_cores() { + // Core index == para_id in this case + mock_assigner::Pallet::::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::::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::::on_chain_votes().unwrap().backing_validators_per_candidate.len(), 1 ); @@ -684,6 +696,11 @@ mod enter { Pallet::::on_chain_votes().unwrap().session, 2 ); + + assert_ok!(Pallet::::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(); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs index 4d0bbc6a8960..b3a060e1cb8a 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs @@ -62,7 +62,7 @@ pub fn availability_cores() -> Vec>::update_claimqueue(Vec::new(), now); + >::free_cores_and_fill_claimqueue(Vec::new(), now); let time_out_for = >::availability_timeout_predicate(); diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index dea84c69f52d..08ce656b2b28 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -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 = - StorageValue<_, Vec>>, ValueQuery>; + StorageValue<_, Vec>, 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 { /// 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), } + /// Conveninece type alias for `CoreOccupied`. + pub type CoreOccupiedType = CoreOccupied>; + impl CoreOccupied { /// 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 = StorageValue< - _, - BTreeMap>>>>, - ValueQuery, - >; + pub(crate) type ClaimQueue = + StorageValue<_, BTreeMap>>, ValueQuery>; /// Assignments as tracked in the claim queue. - #[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)] - pub struct ParasEntry { - /// The underlying `Assignment` + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq, Clone)] + pub struct ParasEntry { + /// 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 ParasEntry { - /// Return `Id` from the underlying `Assignment`. - pub fn para_id(&self) -> ParaId { - self.assignment.para_id - } + /// Convenience type declaration for `ParasEntry`. + pub type ParasEntryType = ParasEntry>; + impl ParasEntry { /// 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 { - /// 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, - } - - impl CoreAssignment { - /// 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 { - self.paras_entry + self.assignment.para_id() } } @@ -219,8 +199,6 @@ pub mod pallet { } type PositionInClaimqueue = u32; -type TimedoutParas = BTreeMap>>; -type ConcludedParas = BTreeMap; impl Pallet { /// Called by the initializer to initialize the scheduler pallet. @@ -253,7 +231,7 @@ impl Pallet { ); AvailabilityCores::::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 Pallet { /// with the reason for them being freed. Returns a tuple of concluded and timedout paras. fn free_cores( just_freed_cores: impl IntoIterator, - ) -> (ConcludedParas, TimedoutParas) { - let mut timedout_paras: BTreeMap>> = - BTreeMap::new(); + ) -> (BTreeMap, BTreeMap>) { + let mut timedout_paras: BTreeMap> = BTreeMap::new(); let mut concluded_paras = BTreeMap::new(); AvailabilityCores::::mutate(|cores| { @@ -310,21 +287,22 @@ impl Pallet { .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 Pallet { 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> = 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 } + } else { + None + }; + + if let Some(dropped) = maybe_dropped { + num_dropped += 1; + T::AssignmentProvider::report_processed(dropped.assignment); + } else { + i += 1; } - true - }); - - // 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 => (), + } + + 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 Pallet { /// 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 { - ClaimQueue::::get().get(&core).and_then(|a| { - a.iter() - .find_map(|e| e.as_ref()) - .map(|pe| Self::paras_entry_to_scheduled_core(pe)) - }) + ClaimQueue::::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>) -> ScheduledCore { + fn paras_entry_to_scheduled_core(pe: &ParasEntryType) -> ScheduledCore { ScheduledCore { para_id: pe.para_id(), collator: None } } @@ -552,35 +534,33 @@ impl Pallet { /// Pushes occupied cores to the assignment provider. fn push_occupied_cores_to_assignment_provider() { AvailabilityCores::::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::::take() { + for (_, claim_queue) in ClaimQueue::::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>) { + fn maybe_push_assignment(pe: ParasEntryType) { 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 Pallet { >::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, - now: BlockNumberFor, - ) { - 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::::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::::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, now: BlockNumberFor, ) { @@ -651,19 +608,19 @@ impl Pallet { } 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 Pallet { } } - fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntry>) { + fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntryType) { ClaimQueue::::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 Pallet { fn remove_from_claimqueue( core_idx: CoreIndex, para_id: ParaId, - ) -> Result<(PositionInClaimqueue, ParasEntry>), &'static str> { + ) -> Result<(PositionInClaimqueue, ParasEntryType), &'static str> { ClaimQueue::::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 Pallet { /// Paras scheduled next in the claim queue. pub(crate) fn scheduled_paras() -> impl Iterator { - 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>)> { let claimqueue = ClaimQueue::::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))] diff --git a/polkadot/runtime/parachains/src/scheduler/common.rs b/polkadot/runtime/parachains/src/scheduler/common.rs index 316e8e3b760c..2eb73385803c 100644 --- a/polkadot/runtime/parachains/src/scheduler/common.rs +++ b/polkadot/runtime/parachains/src/scheduler/common.rs @@ -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 { /// How many times a collation can time out on availability. @@ -51,22 +61,42 @@ pub struct AssignmentProviderConfig { } pub trait AssignmentProvider { - /// 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, - ) -> Option; - - /// 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); + /// + /// This is where assignments come into existance. + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option; + + /// 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; + + /// 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; } diff --git a/polkadot/runtime/parachains/src/scheduler/migration.rs b/polkadot/runtime/parachains/src/scheduler/migration.rs index bb9a647e955c..4c0a07d73674 100644 --- a/polkadot/runtime/parachains/src/scheduler/migration.rs +++ b/polkadot/runtime/parachains/src/scheduler/migration.rs @@ -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 = VersionedMigration< - 0, - 1, - UncheckedMigrateToV1, + #[storage_alias] + pub(super) type ClaimQueue = StorageValue< Pallet, - ::DbWeight, + BTreeMap>>>>, + ValueQuery, >; - #[deprecated(note = "Use MigrateToV1 instead")] + #[storage_alias] + pub(super) type AvailabilityCores = + StorageValue, Vec>>, ValueQuery>; + + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(super) enum CoreOccupied { + /// 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), + } + + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(super) struct ParasEntry { + /// 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 ParasEntry { + /// 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(core_idx: CoreIndex, pe: ParasEntry>) { + ClaimQueue::::mutate(|la| { + la.entry(core_idx).or_default().push_back(Some(pe)); + }); + } + + /// Migration to V1 pub struct UncheckedMigrateToV1(sp_std::marker::PhantomData); - #[allow(deprecated)] impl OnRuntimeUpgrade for UncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { - let weight_consumed = migrate_to_v1::(); + let mut weight: Weight = Weight::zero(); + + v0::ParathreadQueue::::kill(); + v0::ParathreadClaimIndex::::kill(); + + let now = >::block_number(); + let scheduled = v0::Scheduled::::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::(core_idx, pe); + } + + let parachains = paras::Pallet::::parachains(); + let availability_cores = v0::AvailabilityCores::::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::::set(new_availability_cores); - log::info!(target: scheduler::LOG_TARGET, "Migrating para scheduler storage to v1"); + // 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)); - weight_consumed + 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::::get() - .iter() - .filter(|c| !matches!(c, CoreOccupied::Free)) + let availability_cores_waiting = v1::AvailabilityCores::::get() + .into_iter() + .filter(|c| !matches!(c, v1::CoreOccupied::Free)) .count(); ensure!( @@ -154,51 +257,150 @@ pub mod v1 { } } -pub fn migrate_to_v1() -> Weight { - let mut weight: Weight = Weight::zero(); +/// Migrate `V0` to `V1` of the storage format. +pub type MigrateV0ToV1 = VersionedMigration< + 0, + 1, + v1::UncheckedMigrateToV1, + Pallet, + ::DbWeight, +>; - v0::ParathreadQueue::::kill(); - v0::ParathreadClaimIndex::::kill(); +mod v2 { + use super::*; + use crate::scheduler; - let now = >::block_number(); - let scheduled = v0::Scheduled::::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::::add_to_claimqueue(core_idx, pe); + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(crate) enum CoreOccupied { + Free, + Paras(ParasEntry), } - let parachains = paras::Pallet::::parachains(); - let availability_cores = v0::AvailabilityCores::::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 - }; + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(crate) struct ParasEntry { + pub assignment: Assignment, + pub availability_timeouts: u32, + pub ttl: N, + } - new_availability_cores.push(new_core); + // V2 (no Option wrapper) and new [`Assignment`]. + #[storage_alias] + pub(crate) type ClaimQueue = StorageValue< + Pallet, + BTreeMap>>>, + ValueQuery, + >; + + #[storage_alias] + pub(crate) type AvailabilityCores = + StorageValue, Vec>>, ValueQuery>; + + fn is_bulk(core_index: CoreIndex) -> bool { + core_index.0 < paras::Parachains::::decode_len().unwrap_or(0) as u32 } - super::AvailabilityCores::::set(new_availability_cores); + /// Migration to V2 + pub struct UncheckedMigrateToV2(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrateToV2 { + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + + let old = v1::ClaimQueue::::take(); + let new = old + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .flatten() + .map(|p| { + let assignment = if is_bulk::(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::>(), + ) + }) + .collect::>>>>(); + + ClaimQueue::::put(new); + + let old = v1::AvailabilityCores::::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::((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::>(); + AvailabilityCores::::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, sp_runtime::DispatchError> { + log::trace!( + target: crate::scheduler::LOG_TARGET, + "ClaimQueue before migration: {}", + v1::ClaimQueue::::get().len() + ); + + let bytes = u32::to_be_bytes(v1::ClaimQueue::::get().len() as u32); - // 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)); + Ok(bytes.to_vec()) + } - weight + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> 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::::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 = VersionedMigration< + 1, + 2, + v2::UncheckedMigrateToV2, + Pallet, + ::DbWeight, +>; diff --git a/polkadot/runtime/parachains/src/scheduler/tests.rs b/polkadot/runtime/parachains/src/scheduler/tests.rs index 108f365d6b5c..9af23ce64bd6 100644 --- a/polkadot/runtime/parachains/src/scheduler/tests.rs +++ b/polkadot/runtime/parachains/src/scheduler/tests.rs @@ -22,24 +22,24 @@ use primitives::{BlockNumber, SessionIndex, ValidationCode, ValidatorId}; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use crate::{ - assigner_on_demand::QueuePushDirection, configuration::HostConfiguration, initializer::SessionChangeNotification, mock::{ - new_test_ext, MockGenesisConfig, OnDemandAssigner, Paras, ParasShared, RuntimeOrigin, + new_test_ext, MockAssigner, MockGenesisConfig, Paras, ParasShared, RuntimeOrigin, Scheduler, System, Test, }, paras::{ParaGenesisArgs, ParaKind}, + scheduler::{common::Assignment, ClaimQueue}, }; -fn schedule_blank_para(id: ParaId, parakind: ParaKind) { +fn schedule_blank_para(id: ParaId) { 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, + para_kind: ParaKind::Parathread, // This most closely mimics our test assigner } )); @@ -78,7 +78,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); } } @@ -103,11 +103,10 @@ fn run_to_end_of_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_cores: 3, + coretime_cores: 3, group_rotation_frequency: 10, paras_availability_period: 3, scheduling_lookahead: 2, - on_demand_retries: 1, // This field does not affect anything that scheduler does. However, `HostConfiguration` // is still a subject to consistency test. It requires that // `minimum_validation_upgrade_delay` is greater than `chain_availability_period` and @@ -124,29 +123,16 @@ fn genesis_config(config: &HostConfiguration) -> MockGenesisConfig } } -pub(crate) fn claimqueue_contains_only_none() -> bool { - let mut cq = Scheduler::claimqueue(); - for (_, v) in cq.iter_mut() { - v.retain(|e| e.is_some()); - } - - cq.values().map(|v| v.len()).sum::() == 0 -} - -pub(crate) fn claimqueue_contains_para_ids(pids: Vec) -> bool { +fn claimqueue_contains_para_ids(pids: Vec) -> bool { let set: BTreeSet = ClaimQueue::::get() .into_iter() - .flat_map(|(_, assignments)| { - assignments - .into_iter() - .filter_map(|assignment| assignment.and_then(|pe| Some(pe.para_id()))) - }) + .flat_map(|(_, paras_entries)| paras_entries.into_iter().map(|pe| pe.assignment.para_id())) .collect(); pids.into_iter().all(|pid| set.contains(&pid)) } -pub(crate) fn availability_cores_contains_para_ids(pids: Vec) -> bool { +fn availability_cores_contains_para_ids(pids: Vec) -> bool { let set: BTreeSet = AvailabilityCores::::get() .into_iter() .filter_map(|core| match core { @@ -158,6 +144,14 @@ pub(crate) fn availability_cores_contains_para_ids(pids: Vec) pids.into_iter().all(|pid| set.contains(&pid)) } +/// Internal access to entries at the top of the claim queue. +fn scheduled_entries() -> impl Iterator>)> { + let claimqueue = ClaimQueue::::get(); + claimqueue + .into_iter() + .filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.clone()))) +} + #[test] fn claimqueue_ttl_drop_fn_works() { let mut config = default_config(); @@ -169,13 +163,14 @@ fn claimqueue_ttl_drop_fn_works() { let mut now = 10; new_test_ext(genesis_config).execute_with(|| { - assert!(default_config().on_demand_ttl == 5); + let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl; + assert!(assignment_provider_ttl == 5); // Register and run to a blockheight where the para is in a valid state. - schedule_blank_para(para_id, ParaKind::Parathread); - run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); + schedule_blank_para(para_id); + run_to_block(now, |n| if n == now { Some(Default::default()) } else { None }); // Add a claim on core 0 with a ttl in the past. - let paras_entry = ParasEntry::new(Assignment::new(para_id), now - 5); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now - 5 as u32); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue prior to call. @@ -186,7 +181,7 @@ fn claimqueue_ttl_drop_fn_works() { assert!(!claimqueue_contains_para_ids::(vec![para_id])); // Add a claim on core 0 with a ttl in the future (15). - let paras_entry = ParasEntry::new(Assignment::new(para_id), now + 5); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now + 5); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue post call. @@ -201,7 +196,7 @@ fn claimqueue_ttl_drop_fn_works() { assert!(!claimqueue_contains_para_ids::(vec![para_id])); // Add a claim on core 0 with a ttl == now (16) - let paras_entry = ParasEntry::new(Assignment::new(para_id), now); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue post call. @@ -215,8 +210,8 @@ fn claimqueue_ttl_drop_fn_works() { Scheduler::drop_expired_claims_from_claimqueue(); // Add a claim on core 0 with a ttl == now (17) - let paras_entry_non_expired = ParasEntry::new(Assignment::new(para_id), now); - let paras_entry_expired = ParasEntry::new(Assignment::new(para_id), now - 2); + let paras_entry_non_expired = ParasEntry::new(Assignment::Bulk(para_id), now); + let paras_entry_expired = ParasEntry::new(Assignment::Bulk(para_id), now - 2); // ttls = [17, 15, 17] Scheduler::add_to_claimqueue(core_idx, paras_entry_non_expired.clone()); Scheduler::add_to_claimqueue(core_idx, paras_entry_expired.clone()); @@ -224,18 +219,10 @@ fn claimqueue_ttl_drop_fn_works() { let cq = Scheduler::claimqueue(); assert!(cq.get(&core_idx).unwrap().len() == 3); - // Add claims to on demand assignment provider. - let assignment = Assignment::new(para_id); + // Add a claim to the test assignment provider. + let assignment = Assignment::Bulk(para_id); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - )); - - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment, - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment.clone()); // Drop expired claim. Scheduler::drop_expired_claims_from_claimqueue(); @@ -248,58 +235,25 @@ fn claimqueue_ttl_drop_fn_works() { // The first 2 claims in the queue should have a ttl of 17, // being the ones set up prior in this test as claims 1 and 3. // The third claim is popped from the assignment provider and - // has a new ttl set by the scheduler of now + config.on_demand_ttl. - // ttls = [17, 17, 22] + // has a new ttl set by the scheduler of now + + // assignment_provider_ttl. ttls = [17, 17, 22] assert!(cqc.iter().enumerate().all(|(index, entry)| { match index { - 0 | 1 => return entry.clone().unwrap().ttl == 17, - 2 => return entry.clone().unwrap().ttl == 22, - _ => return false, + 0 | 1 => entry.clone().ttl == 17, + 2 => entry.clone().ttl == 22, + _ => false, } })) }); } -// Pretty useless here. Should be on parathread assigner... if at all -#[test] -fn add_parathread_claim_works() { - let genesis_config = genesis_config(&default_config()); - - let thread_id = ParaId::from(10); - let core_index = CoreIndex::from(0); - let entry_ttl = 10_000; - - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_id, ParaKind::Parathread); - - assert!(!Paras::is_parathread(thread_id)); - - run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); - - assert!(Paras::is_parathread(thread_id)); - - let pe = ParasEntry::new(Assignment::new(thread_id), entry_ttl); - Scheduler::add_to_claimqueue(core_index, pe.clone()); - - let cq = Scheduler::claimqueue(); - assert_eq!(Scheduler::claimqueue_len(), 1); - assert_eq!(*(cq.get(&core_index).unwrap().front().unwrap()), Some(pe)); - }) -} - #[test] fn session_change_shuffles_validators() { let genesis_config = genesis_config(&default_config()); - assert_eq!(default_config().on_demand_cores, 3); new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - + // Need five cores for this test + MockAssigner::set_core_count(5); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -336,7 +290,6 @@ fn session_change_shuffles_validators() { fn session_change_takes_only_max_per_core() { let config = { let mut config = default_config(); - config.on_demand_cores = 0; config.max_validators_per_core = Some(1); config }; @@ -344,14 +297,8 @@ fn session_change_takes_only_max_per_core() { let genesis_config = genesis_config(&config); new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - let chain_c = ParaId::from(3_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - schedule_blank_para(chain_c, ParaKind::Parathread); + // Simulate 2 cores between all usage types + MockAssigner::set_core_count(2); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -374,7 +321,7 @@ fn session_change_takes_only_max_per_core() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 7); - // Every validator gets its own group, even though there are 2 paras. + // Every validator gets its own group, even though there are 2 cores. for i in 0..7 { assert_eq!(groups[i].len(), 1); } @@ -385,31 +332,25 @@ fn session_change_takes_only_max_per_core() { fn fill_claimqueue_fills() { let genesis_config = genesis_config(&default_config()); - let lookahead = genesis_config.configuration.config.scheduling_lookahead as usize; - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - let thread_a = ParaId::from(3_u32); - let thread_b = ParaId::from(4_u32); - let thread_c = ParaId::from(5_u32); + let para_a = ParaId::from(3_u32); + let para_b = ParaId::from(4_u32); + let para_c = ParaId::from(5_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; - let assignment_c = Assignment { para_id: thread_c }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(2); + let AssignmentProviderConfig { ttl: config_ttl, .. } = + MockAssigner::get_provider_config(CoreIndex(0)); - // register 2 lease holding parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add 3 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); - // and 3 parathreads (on-demand parachains) - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); - schedule_blank_para(thread_c, ParaKind::Parathread); - - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -417,107 +358,47 @@ fn fill_claimqueue_fills() { ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), _ => None, }); - { - assert_eq!(Scheduler::claimqueue_len(), 2 * lookahead); - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); - - // Cannot assert on indices anymore as they depend on the assignment providers - assert!(claimqueue_contains_para_ids::(vec![chain_a, chain_b])); - - assert_eq!( - scheduled.get(&CoreIndex(0)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - ); - - assert_eq!( - scheduled.get(&CoreIndex(1)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - ); - } - - // add a couple of parathread assignments. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_c, - QueuePushDirection::Back - )); + // add some para assignments. + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); run_to_block(2, |_| None); - // cores 0 and 1 should be occupied. mark them as such. - Scheduler::occupied( - vec![(CoreIndex(0), chain_a), (CoreIndex(1), chain_b)].into_iter().collect(), - ); - - run_to_block(3, |_| None); { - assert_eq!(Scheduler::claimqueue_len(), 5); - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); - - assert_eq!( - scheduled.get(&CoreIndex(0)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - ); - assert_eq!( - scheduled.get(&CoreIndex(1)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - ); + assert_eq!(Scheduler::claimqueue_len(), 3); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); // Was added a block later, note the TTL. assert_eq!( - scheduled.get(&CoreIndex(2)).unwrap(), + scheduled.get(&CoreIndex(0)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_a }, + assignment: assignment_a.clone(), availability_timeouts: 0, - ttl: 7 + ttl: 2 + config_ttl }, ); - // Sits on the same core as `thread_a` + // Sits on the same core as `para_a` assert_eq!( - Scheduler::claimqueue().get(&CoreIndex(2)).unwrap()[1], - Some(ParasEntry { - assignment: Assignment { para_id: thread_b }, + Scheduler::claimqueue().get(&CoreIndex(0)).unwrap()[1], + ParasEntry { + assignment: assignment_b.clone(), availability_timeouts: 0, - ttl: 7 - }) + ttl: 2 + config_ttl + } ); assert_eq!( - scheduled.get(&CoreIndex(3)).unwrap(), + scheduled.get(&CoreIndex(1)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_c }, + assignment: assignment_c.clone(), availability_timeouts: 0, - ttl: 7 + ttl: 2 + config_ttl }, ); } @@ -532,36 +413,29 @@ fn schedule_schedules_including_just_freed() { config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - let thread_a = ParaId::from(3_u32); - let thread_b = ParaId::from(4_u32); - let thread_c = ParaId::from(5_u32); - let thread_d = ParaId::from(6_u32); - let thread_e = ParaId::from(7_u32); + let para_a = ParaId::from(3_u32); + let para_b = ParaId::from(4_u32); + let para_c = ParaId::from(5_u32); + let para_d = ParaId::from(6_u32); + let para_e = ParaId::from(7_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; - let assignment_c = Assignment { para_id: thread_c }; - let assignment_d = Assignment { para_id: thread_d }; - let assignment_e = Assignment { para_id: thread_e }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); + let assignment_d = Assignment::Bulk(para_d); + let assignment_e = Assignment::Bulk(para_e); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(3); - // register 2 lease holding parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // add 5 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); + schedule_blank_para(para_d); + schedule_blank_para(para_e); - // and 5 parathreads (on-demand parachains) - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); - schedule_blank_para(thread_c, ParaKind::Parathread); - schedule_blank_para(thread_d, ParaKind::Parathread); - schedule_blank_para(thread_e, ParaKind::Parathread); - - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -569,153 +443,113 @@ fn schedule_schedules_including_just_freed() { ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), _ => None, }); - // add a couple of parathread claims now that the parathreads are live. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_c, - QueuePushDirection::Back - )); + // add a couple of para claims now that paras are live + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); let mut now = 2; run_to_block(now, |_| None); - assert_eq!(Scheduler::scheduled_paras().collect::>().len(), 4); + assert_eq!(Scheduler::scheduled_paras().collect::>().len(), 2); - // cores 0, 1, 2, and 3 should be occupied. mark them as such. + // cores 0, 1 should be occupied. mark them as such. let mut occupied_map: BTreeMap = BTreeMap::new(); - occupied_map.insert(CoreIndex(0), chain_a); - occupied_map.insert(CoreIndex(1), chain_b); - occupied_map.insert(CoreIndex(2), thread_a); - occupied_map.insert(CoreIndex(3), thread_c); + occupied_map.insert(CoreIndex(0), para_a); + occupied_map.insert(CoreIndex(1), para_c); Scheduler::occupied(occupied_map); { let cores = AvailabilityCores::::get(); - // cores 0, 1, 2, and 3 are all `CoreOccupied::Paras(ParasEntry...)` + // cores 0, 1 are `CoreOccupied::Paras(ParasEntry...)` assert!(cores[0] != CoreOccupied::Free); assert!(cores[1] != CoreOccupied::Free); - assert!(cores[2] != CoreOccupied::Free); - assert!(cores[3] != CoreOccupied::Free); - // core 4 is free - assert!(cores[4] == CoreOccupied::Free); + // core 2 is free + assert!(cores[2] == CoreOccupied::Free); assert!(Scheduler::scheduled_paras().collect::>().is_empty()); - // All core index entries in the claimqueue should have `None` in them. - Scheduler::claimqueue().iter().for_each(|(_core_idx, core_queue)| { - assert!(core_queue.iter().all(|claim| claim.is_none())) - }) + // All `core_queue`s should be empty + Scheduler::claimqueue() + .iter() + .for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0)) } - // add a couple more parathread claims - the claim on `b` will go to the 3rd parathread core - // (4) and the claim on `d` will go back to the 1st parathread core (2). The claim on `e` - // then will go for core `3`. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_d, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_e.clone(), - QueuePushDirection::Back - )); + // add a couple more para claims - the claim on `b` will go to the 3rd core + // (2) and the claim on `d` will go back to the 1st para core (0). The claim on `e` + // then will go for core `1`. + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_d.clone()); + MockAssigner::add_test_assignment(assignment_e.clone()); now = 3; run_to_block(now, |_| None); { - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); - // cores 0 and 1 are occupied by lease holding parachains. cores 2 and 3 are occupied by - // on-demand parachain claims. core 4 was free. + // cores 0 and 1 are occupied by claims. core 2 was free. assert_eq!(scheduled.len(), 1); assert_eq!( - scheduled.get(&CoreIndex(4)).unwrap(), + scheduled.get(&CoreIndex(2)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_b }, + assignment: Assignment::Bulk(para_b), availability_timeouts: 0, ttl: 8 }, ); } - // now note that cores 0, 2, and 3 were freed. + // now note that cores 0 and 1 were freed. let just_updated: BTreeMap = vec![ (CoreIndex(0), FreedReason::Concluded), - (CoreIndex(2), FreedReason::Concluded), - (CoreIndex(3), FreedReason::TimedOut), // should go back on queue. + (CoreIndex(1), FreedReason::TimedOut), // should go back on queue. ] .into_iter() .collect(); - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); { - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); - // 1 thing scheduled before, + 3 cores freed. - assert_eq!(scheduled.len(), 4); + // 1 thing scheduled before, + 2 cores freed. + assert_eq!(scheduled.len(), 3); assert_eq!( scheduled.get(&CoreIndex(0)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 8 - }, - ); - assert_eq!( - scheduled.get(&CoreIndex(2)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: thread_d }, + assignment: Assignment::Bulk(para_d), availability_timeouts: 0, ttl: 8 }, ); - // Although C was descheduled, the core `4` was occupied so C goes back to the queue. + // Although C was descheduled, the core `2` was occupied so C goes back to the queue. assert_eq!( - scheduled.get(&CoreIndex(3)).unwrap(), + scheduled.get(&CoreIndex(1)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_c }, + assignment: Assignment::Bulk(para_c), availability_timeouts: 1, ttl: 8 }, ); assert_eq!( - scheduled.get(&CoreIndex(4)).unwrap(), + scheduled.get(&CoreIndex(2)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_b }, + assignment: Assignment::Bulk(para_b), availability_timeouts: 0, ttl: 8 }, ); - // The only assignment yet to be popped on to the claim queue is `thread_e`. - // This is due to `thread_c` timing out. - let order_queue = OnDemandAssigner::get_queue(); - assert!(order_queue.len() == 1); - assert!(order_queue[0] == assignment_e); - - // Chain B's core was not marked concluded or timed out, it should be on an - // availability core - assert!(availability_cores_contains_para_ids::(vec![chain_b])); - // Thread A claim should have been wiped, but thread C claim should remain. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(claimqueue_contains_para_ids::(vec![thread_c])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a, thread_c])); + // Para A claim should have been wiped, but para C claim should remain. + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(claimqueue_contains_para_ids::(vec![para_c])); + assert!(!availability_cores_contains_para_ids::(vec![para_a, para_c])); } }); } @@ -726,28 +560,35 @@ fn schedule_clears_availability_cores() { config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - let chain_c = ParaId::from(3_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); + let para_c = ParaId::from(3_u32); + + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(3); + + // register 3 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); - // register 3 parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - schedule_blank_para(chain_c, ParaKind::Parachain); + // Adding assignments then running block to populate claim queue + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: default_config(), + new_config: config.clone(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), @@ -760,7 +601,7 @@ fn schedule_clears_availability_cores() { // cores 0, 1, and 2 should be occupied. mark them as such. Scheduler::occupied( - vec![(CoreIndex(0), chain_a), (CoreIndex(1), chain_b), (CoreIndex(2), chain_c)] + vec![(CoreIndex(0), para_a), (CoreIndex(1), para_b), (CoreIndex(2), para_c)] .into_iter() .collect(), ); @@ -772,9 +613,16 @@ fn schedule_clears_availability_cores() { assert_eq!(cores[1].is_free(), false); assert_eq!(cores[2].is_free(), false); - assert!(claimqueue_contains_only_none()); + // All `core_queue`s should be empty + Scheduler::claimqueue() + .iter() + .for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0)) } + // Add more assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); + run_to_block(3, |_| None); // now note that cores 0 and 2 were freed. @@ -786,20 +634,18 @@ fn schedule_clears_availability_cores() { ); { - let claimqueue = Scheduler::claimqueue(); + let claimqueue = ClaimQueue::::get(); let claimqueue_0 = claimqueue.get(&CoreIndex(0)).unwrap().clone(); let claimqueue_2 = claimqueue.get(&CoreIndex(2)).unwrap().clone(); let entry_ttl = 8; assert_eq!(claimqueue_0.len(), 1); assert_eq!(claimqueue_2.len(), 1); - assert_eq!( - claimqueue_0, - vec![Some(ParasEntry::new(Assignment::new(chain_a), entry_ttl))], - ); - assert_eq!( - claimqueue_2, - vec![Some(ParasEntry::new(Assignment::new(chain_c), entry_ttl))], - ); + let queue_0_expectation: VecDeque> = + vec![ParasEntry::new(assignment_a, entry_ttl as u32)].into_iter().collect(); + let queue_2_expectation: VecDeque> = + vec![ParasEntry::new(assignment_c, entry_ttl as u32)].into_iter().collect(); + assert_eq!(claimqueue_0, queue_0_expectation); + assert_eq!(claimqueue_2, queue_2_expectation); // The freed cores should be `Free` in `AvailabilityCores`. let cores = AvailabilityCores::::get(); @@ -813,32 +659,28 @@ fn schedule_clears_availability_cores() { fn schedule_rotates_groups() { let config = { let mut config = default_config(); - - // make sure on demand requests don't retry-out - config.on_demand_retries = config.group_rotation_frequency * 3; - config.on_demand_cores = 2; config.scheduling_lookahead = 1; config }; let rotation_frequency = config.group_rotation_frequency; - let on_demand_cores = config.on_demand_cores; + let on_demand_cores = 2; let genesis_config = genesis_config(&config); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(on_demand_cores); - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: config.clone(), @@ -854,14 +696,8 @@ fn schedule_rotates_groups() { let session_start_block = Scheduler::session_start_block(); assert_eq!(session_start_block, 1); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); let mut now = 2; run_to_block(now, |_| None); @@ -909,16 +745,20 @@ fn on_demand_claims_are_pruned_after_timing_out() { let max_retries = 20; let mut config = default_config(); config.scheduling_lookahead = 1; - config.on_demand_cores = 2; - config.on_demand_retries = max_retries; let genesis_config = genesis_config(&config); - let thread_a = ParaId::from(1_u32); + let para_a = ParaId::from(1_u32); - let assignment_a = Assignment { para_id: thread_a }; + let assignment_a = Assignment::Bulk(para_a); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); + MockAssigner::set_core_count(2); + // Need more timeouts for this test + MockAssigner::set_assignment_provider_config(AssignmentProviderConfig { + max_availability_timeouts: max_retries, + ttl: BlockNumber::from(5u32), + }); + schedule_blank_para(para_a); // #1 let mut now = 1; @@ -934,23 +774,20 @@ fn on_demand_claims_are_pruned_after_timing_out() { _ => None, }); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); // #2 now += 1; run_to_block(now, |_| None); assert_eq!(Scheduler::claimqueue().len(), 1); // ParaId a is in the claimqueue. - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); - Scheduler::occupied(vec![(CoreIndex(0), thread_a)].into_iter().collect()); + Scheduler::occupied(vec![(CoreIndex(0), para_a)].into_iter().collect()); // ParaId a is no longer in the claimqueue. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); // It is in availability cores. - assert!(availability_cores_contains_para_ids::(vec![thread_a])); + assert!(availability_cores_contains_para_ids::(vec![para_a])); // #3 now += 1; @@ -966,36 +803,32 @@ fn on_demand_claims_are_pruned_after_timing_out() { ] .into_iter() .collect(); - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); // ParaId a exists in the claim queue until max_retries is reached. if n < max_retries + now { - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); } else { - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); } let core_assignments = Scheduler::scheduled_paras().collect(); - // Occupy the cores based on the result of update_claimqueue. Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after // threshold has been reached. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(!availability_cores_contains_para_ids::(vec![para_a])); // #25 now += max_retries + 2; // Add assignment back to the mix. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); run_to_block(now, |_| None); - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); // #26 now += 1; @@ -1017,24 +850,23 @@ fn on_demand_claims_are_pruned_after_timing_out() { } } - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); // ParaId a exists in the claim queue until groups are rotated. if n < 31 { - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); } else { - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); } let core_assignments = Scheduler::scheduled_paras().collect(); - // Occupy the cores based on the result of update_claimqueue. Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after // being concluded - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(!availability_cores_contains_para_ids::(vec![para_a])); }); } @@ -1047,40 +879,7 @@ fn availability_predicate_works() { assert!(paras_availability_period < group_rotation_frequency); - let chain_a = ParaId::from(1_u32); - let thread_a = ParaId::from(2_u32); - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(thread_a, ParaKind::Parathread); - - // start a new session with our chain registered. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: default_config(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Bob.public()), - ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - // assign some availability cores. - { - let entry_ttl = 10_000; - AvailabilityCores::::mutate(|cores| { - cores[0] = - CoreOccupied::Paras(ParasEntry::new(Assignment::new(chain_a), entry_ttl)); - cores[1] = - CoreOccupied::Paras(ParasEntry::new(Assignment::new(thread_a), entry_ttl)); - }); - } - run_to_block(1 + paras_availability_period, |_| None); assert!(!Scheduler::availability_timeout_check_required()); @@ -1103,29 +902,25 @@ fn availability_predicate_works() { // check the threshold is exact. assert!(!pred(would_be_timed_out + 1).timed_out); } - - run_to_block(1 + group_rotation_frequency + paras_availability_period, |_| None); }); } #[test] -fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { - let mut config = default_config(); - config.on_demand_cores = 1; - - let genesis_config = genesis_config(&config); +fn next_up_on_available_uses_next_scheduled_or_none() { + let genesis_config = genesis_config(&default_config()); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + MockAssigner::set_core_count(1); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: config.clone(), + new_config: default_config(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Eve.public()), @@ -1135,18 +930,18 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { _ => None, }); - let thread_entry_a = ParasEntry { - assignment: Assignment { para_id: thread_a }, - availability_timeouts: 0, - ttl: 5, + let entry_a = ParasEntry { + assignment: Assignment::Bulk(para_a), + availability_timeouts: 0 as u32, + ttl: 5 as u32, }; - let thread_entry_b = ParasEntry { - assignment: Assignment { para_id: thread_b }, - availability_timeouts: 0, - ttl: 5, + let entry_b = ParasEntry { + assignment: Assignment::Bulk(para_b), + availability_timeouts: 0 as u32, + ttl: 5 as u32, }; - Scheduler::add_to_claimqueue(CoreIndex(0), thread_entry_a.clone()); + Scheduler::add_to_claimqueue(CoreIndex(0), entry_a.clone()); run_to_block(2, |_| None); @@ -1155,22 +950,22 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { assert_eq!(Scheduler::availability_cores().len(), 1); let mut map = BTreeMap::new(); - map.insert(CoreIndex(0), thread_a); + map.insert(CoreIndex(0), para_a); Scheduler::occupied(map); let cores = Scheduler::availability_cores(); match &cores[0] { - CoreOccupied::Paras(entry) => assert_eq!(entry, &thread_entry_a), - _ => panic!("with no chains, only core should be a thread core"), + CoreOccupied::Paras(entry) => assert_eq!(entry, &entry_a), + _ => panic!("There should only be one test assigner core"), } assert!(Scheduler::next_up_on_available(CoreIndex(0)).is_none()); - Scheduler::add_to_claimqueue(CoreIndex(0), thread_entry_b); + Scheduler::add_to_claimqueue(CoreIndex(0), entry_b); assert_eq!( Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_b, collator: None } + ScheduledCore { para_id: para_b, collator: None } ); } }); @@ -1178,25 +973,23 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { #[test] fn next_up_on_time_out_reuses_claim_if_nothing_queued() { - let mut config = default_config(); - config.on_demand_cores = 1; - - let genesis_config = genesis_config(&config); + let genesis_config = genesis_config(&default_config()); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + MockAssigner::set_core_count(1); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: config.clone(), + new_config: default_config(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Eve.public()), @@ -1206,10 +999,7 @@ fn next_up_on_time_out_reuses_claim_if_nothing_queued() { _ => None, }); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); run_to_block(2, |_| None); @@ -1218,150 +1008,62 @@ fn next_up_on_time_out_reuses_claim_if_nothing_queued() { assert_eq!(Scheduler::availability_cores().len(), 1); let mut map = BTreeMap::new(); - map.insert(CoreIndex(0), thread_a); + map.insert(CoreIndex(0), para_a); Scheduler::occupied(map); let cores = Scheduler::availability_cores(); match cores.get(0).unwrap() { - CoreOccupied::Paras(entry) => assert_eq!(entry.assignment, assignment_a.clone()), - _ => panic!("with no chains, only core should be a thread core"), + CoreOccupied::Paras(entry) => { + assert_eq!(entry.assignment, assignment_a.clone()); + }, + _ => panic!("There should only be a single test assigner core"), } // There's nothing more to pop for core 0 from the assignment provider. - assert!( - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(thread_a)).is_none() - ); + assert!(MockAssigner::pop_assignment_for_core(CoreIndex(0)).is_none()); assert_eq!( Scheduler::next_up_on_time_out(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_a, collator: None } + ScheduledCore { para_id: para_a, collator: None } ); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_b.clone()); // Pop assignment_b into the claimqueue - Scheduler::update_claimqueue(BTreeMap::new(), 2); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 2); //// Now that there is an earlier next-up, we use that. assert_eq!( Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_b, collator: None } + ScheduledCore { para_id: para_b, collator: None } ); } }); } #[test] -fn next_up_on_available_is_parachain_always() { +fn session_change_requires_reschedule_dropping_removed_paras() { let mut config = default_config(); - config.on_demand_cores = 0; + config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - - // start a new session to activate, 5 validators for 5 cores. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: config.clone(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - run_to_block(2, |_| None); - - { - assert_eq!(Scheduler::claimqueue().len(), 1); - assert_eq!(Scheduler::availability_cores().len(), 1); - - Scheduler::occupied(vec![(CoreIndex(0), chain_a)].into_iter().collect()); - - let cores = Scheduler::availability_cores(); - match &cores[0] { - CoreOccupied::Paras(pe) if pe.para_id() == chain_a => {}, - _ => panic!("with no threads, only core should be a chain core"), - } - // Now that there is an earlier next-up, we use that. - assert_eq!( - Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: chain_a, collator: None } - ); - } - }); -} + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); -#[test] -fn next_up_on_time_out_is_parachain_always() { - let mut config = default_config(); - config.on_demand_cores = 0; - - let genesis_config = genesis_config(&config); - - let chain_a = ParaId::from(1_u32); + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - - // start a new session to activate, 5 validators for 5 cores. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: config.clone(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - run_to_block(2, |_| None); + // Setting explicit core count + MockAssigner::set_core_count(5); + let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl; - { - assert_eq!(Scheduler::claimqueue().len(), 1); - assert_eq!(Scheduler::availability_cores().len(), 1); - - Scheduler::occupied(vec![(CoreIndex(0), chain_a)].into_iter().collect()); - - let cores = Scheduler::availability_cores(); - match &cores[0] { - CoreOccupied::Paras(pe) if pe.para_id() == chain_a => {}, - _ => panic!("Core should be occupied by chain_a ParaId"), - } - - // Now that there is an earlier next-up, we use that. - assert_eq!( - Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: chain_a, collator: None } - ); - } - }); -} + schedule_blank_para(para_a); + schedule_blank_para(para_b); -#[test] -fn session_change_requires_reschedule_dropping_removed_paras() { - let mut config = default_config(); - config.scheduling_lookahead = 1; - let genesis_config = genesis_config(&config); - - assert_eq!(default_config().on_demand_cores, 3); - new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -1386,7 +1088,11 @@ fn session_change_requires_reschedule_dropping_removed_paras() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 5); - assert_ok!(Paras::schedule_para_cleanup(chain_b)); + assert_ok!(Paras::schedule_para_cleanup(para_b)); + + // Add assignment + MockAssigner::add_test_assignment(assignment_a.clone()); + run_to_end_of_block(2, |number| match number { 2 => Some(SessionChangeNotification { new_config: default_config(), @@ -1405,17 +1111,17 @@ fn session_change_requires_reschedule_dropping_removed_paras() { _ => None, }); - Scheduler::update_claimqueue(BTreeMap::new(), 3); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 3); assert_eq!( Scheduler::claimqueue(), vec![( CoreIndex(0), - vec![Some(ParasEntry::new( - Assignment::new(chain_a), + vec![ParasEntry::new( + Assignment::Bulk(para_a), // At end of block 2 - config.on_demand_ttl + 2 - ))] + assignment_provider_ttl + 2 + )] .into_iter() .collect() )] @@ -1423,8 +1129,12 @@ fn session_change_requires_reschedule_dropping_removed_paras() { .collect() ); - // Add parachain back - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add para back + schedule_blank_para(para_b); + + // Add assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); run_to_block(3, |number| match number { 3 => Some(SessionChangeNotification { @@ -1449,28 +1159,28 @@ fn session_change_requires_reschedule_dropping_removed_paras() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 5); - Scheduler::update_claimqueue(BTreeMap::new(), 4); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 4); assert_eq!( Scheduler::claimqueue(), vec![ ( CoreIndex(0), - vec![Some(ParasEntry::new( - Assignment::new(chain_a), + vec![ParasEntry::new( + Assignment::Bulk(para_a), // At block 3 - config.on_demand_ttl + 3 - ))] + assignment_provider_ttl + 3 + )] .into_iter() .collect() ), ( CoreIndex(1), - vec![Some(ParasEntry::new( - Assignment::new(chain_b), + vec![ParasEntry::new( + Assignment::Bulk(para_b), // At block 3 - config.on_demand_ttl + 3 - ))] + assignment_provider_ttl + 3 + )] .into_iter() .collect() ), diff --git a/polkadot/runtime/parachains/src/session_info/tests.rs b/polkadot/runtime/parachains/src/session_info/tests.rs index 727b7c79fbae..92a50575deda 100644 --- a/polkadot/runtime/parachains/src/session_info/tests.rs +++ b/polkadot/runtime/parachains/src/session_info/tests.rs @@ -62,7 +62,7 @@ fn run_to_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_cores: 1, + coretime_cores: 1, dispute_period: 2, needed_approvals: 3, ..Default::default() diff --git a/polkadot/runtime/rococo/constants/src/lib.rs b/polkadot/runtime/rococo/constants/src/lib.rs index 4e728421b67b..9209045364c2 100644 --- a/polkadot/runtime/rococo/constants/src/lib.rs +++ b/polkadot/runtime/rococo/constants/src/lib.rs @@ -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; diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index acc3f723cddf..a15911c21f65 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -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; + 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; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; + type CoretimeOnNewSession = Coretime; } impl parachains_disputes::Config for Runtime { @@ -1405,15 +1421,16 @@ construct_runtime! { ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event} = 62, ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 63, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 64, - ParaAssignmentProvider: parachains_assigner::{Pallet, Storage} = 65, OnDemandAssignmentProvider: parachains_assigner_on_demand::{Pallet, Call, Storage, Event} = 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, Config} = 70, Slots: slots::{Pallet, Call, Storage, Event} = 71, Auctions: auctions::{Pallet, Call, Storage, Event} = 72, Crowdloan: crowdloan::{Pallet, Call, Storage, Event} = 73, + Coretime: coretime::{Pallet, Call, Event} = 74, // Pallet for sending XCM. XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 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 for GetLegacyLeaseImpl { + fn get_parachain_lease_in_blocks(para: ParaId) -> Option { + let now = frame_system::Pallet::::block_number(); + let mut leases = slots::Pallet::::lease(para).into_iter(); + let initial_sum = if let Some(Some(_)) = leases.next() { + let (_, progress) = + slots::Pallet::::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::::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, parachains_configuration::migration::v7::MigrateToV7, assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, paras_registrar::migration::MigrateToV1, @@ -1633,6 +1680,8 @@ pub mod migrations { // Remove `im-online` pallet on-chain storage frame_support::migrations::RemovePallet::DbWeight>, parachains_configuration::migration::v11::MigrateToV11, + // This needs to come after the `parachains_configuration` above as we are reading the configuration. + coretime::migration::MigrateToCoretime, ); } @@ -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] diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index bd2079ce8277..3613fb4305ba 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -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; diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs new file mode 100644 index 000000000000..d9f2d45207b9 --- /dev/null +++ b/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs @@ -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 . + +//! 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(PhantomData); +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + fn request_core_count() -> Weight { + ::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)) + } +} diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index e5edba2570c3..4f9ab0d66111 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -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, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, ); match_types! { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index cd9590796ae0..f472b619ba75 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -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; type ForceOrigin = frame_system::EnsureRoot; 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 {} diff --git a/polkadot/runtime/westend/constants/src/lib.rs b/polkadot/runtime/westend/constants/src/lib.rs index c2bce3a1791b..3b44684c203b 100644 --- a/polkadot/runtime/westend/constants/src/lib.rs +++ b/polkadot/runtime/westend/constants/src/lib.rs @@ -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; diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index e958a660e6ec..fb54bec509b3 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -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; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; + 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} = 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, Config} = 60, @@ -1636,7 +1644,7 @@ pub mod migrations { parachains_configuration::migration::v7::MigrateToV7, pallet_staking::migrations::v14::MigrateToV14, assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, paras_registrar::migration::MigrateToV1, diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index 3841579088a9..d8a2ae5d2da6 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -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; diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs new file mode 100644 index 000000000000..ac0f05301b48 --- /dev/null +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs @@ -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 . + +//! 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(PhantomData); +impl runtime_parachains::assigner_on_demand::WeightInfo for WeightInfo { + /// 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())) + } +} diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs new file mode 100644 index 000000000000..d9f2d45207b9 --- /dev/null +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs @@ -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 . + +//! 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(PhantomData); +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + fn request_core_count() -> Weight { + ::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)) + } +} diff --git a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl index a3f1f0669ac9..d92820391d53 100644 --- a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl +++ b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl @@ -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 diff --git a/polkadot/zombienet_tests/smoke/0004-configure-broker.js b/polkadot/zombienet_tests/smoke/0004-configure-broker.js new file mode 100644 index 000000000000..a4939ffe1cb8 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-broker.js @@ -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 }; diff --git a/polkadot/zombienet_tests/smoke/0004-configure-relay.js b/polkadot/zombienet_tests/smoke/0004-configure-relay.js new file mode 100644 index 000000000000..9ca23d86a561 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-relay.js @@ -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 }; diff --git a/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml new file mode 100644 index 000000000000..3bcd0bee3c71 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml @@ -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" diff --git a/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl new file mode 100644 index 000000000000..45e000e0bf85 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl @@ -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 diff --git a/prdoc/pr_1694.prdoc b/prdoc/pr_1694.prdoc new file mode 100644 index 000000000000..24797630efc9 --- /dev/null +++ b/prdoc/pr_1694.prdoc @@ -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 diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 849bc7ca6fb9..4d409a791ba2 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1993,7 +1993,6 @@ impl OnUnbalanced> for IntoAuthor { } parameter_types! { - pub storage CoreCount: Option = None; pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; } @@ -2012,21 +2011,12 @@ impl CoretimeInterface for CoretimeProvider { _end_hint: Option, ) { } - fn check_notify_core_count() -> Option { - 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))); } diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index 8d97d9410229..fe8fcfda216e 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -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))) diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index 2cb4826f4acc..c57c4ccb8ce4 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -705,7 +705,7 @@ mod benches { let core_count = n.try_into().unwrap(); - ::ensure_notify_core_count(core_count); + CoreCountInbox::::put(core_count); let mut status = Status::::get().ok_or(BenchmarkError::Weightless)?; diff --git a/substrate/frame/broker/src/coretime_interface.rs b/substrate/frame/broker/src/coretime_interface.rs index 9f23561ce945..9e853e8f3fe0 100644 --- a/substrate/frame/broker/src/coretime_interface.rs +++ b/substrate/frame/broker/src/coretime_interface.rs @@ -107,11 +107,6 @@ pub trait CoretimeInterface { end_hint: Option>, ); - /// 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; - /// 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::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>, ) { } - fn check_notify_core_count() -> Option { - None - } fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, 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, _revenue: Self::Balance) {} } diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs index 0b08a7b665b7..b04e15b169bc 100644 --- a/substrate/frame/broker/src/dispatchable_impls.rs +++ b/substrate/frame/broker/src/dispatchable_impls.rs @@ -37,6 +37,11 @@ impl Pallet { Ok(()) } + pub(crate) fn do_notify_core_count(core_count: CoreIndex) -> DispatchResult { + CoreCountInbox::::put(core_count); + Ok(()) + } + pub(crate) fn do_reserve(workload: Schedule) -> DispatchResult { let mut r = Reservations::::get(); let index = r.len() as u32; diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs index ee3501d56071..38a504900546 100644 --- a/substrate/frame/broker/src/lib.rs +++ b/substrate/frame/broker/src/lib.rs @@ -159,6 +159,10 @@ pub mod pallet { pub type InstaPoolHistory = StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf>; + /// Received core count change from the relay chain. + #[pallet::storage] + pub type CoreCountInbox = StorageValue<_, CoreIndex, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -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, core_count: CoreIndex) -> DispatchResult { + T::AdminOrigin::ensure_origin_or_root(origin)?; + Self::do_notify_core_count(core_count)?; + Ok(()) + } } } diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index 7444040ec9b7..19c72340353c 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -70,7 +70,6 @@ parameter_types! { pub static CoretimeWorkplan: BTreeMap<(u32, CoreIndex), Vec<(CoreAssignment, PartsOf57600)>> = Default::default(); pub static CoretimeUsage: BTreeMap> = Default::default(); pub static CoretimeInPool: CoreMaskBitCount = 0; - pub static NotifyCoreCount: Vec = 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::::put(count); } fn request_revenue_info_at(when: RCBlockNumberOf) { if when > RCBlockNumberProviderOf::::current_block_number() { @@ -126,17 +125,10 @@ impl CoretimeInterface for TestCoretimeProvider { ); CoretimeTrace::mutate(|v| v.push(item)); } - fn check_notify_core_count() -> Option { - NotifyCoreCount::mutate(|s| s.pop()) - } fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, 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, revenue: Self::Balance) { NotifyRevenueInfo::mutate(|s| s.push((when as u32, revenue))); } diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 5f2be268b228..8b7860c8e3af 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -87,7 +87,7 @@ impl Pallet { } 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::::take() { status.core_count = core_count; Self::deposit_event(Event::::CoreCountChanged { core_count }); return true diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs index b3a151c6062c..a8f50eeee6e6 100644 --- a/substrate/frame/broker/src/weights.rs +++ b/substrate/frame/broker/src/weights.rs @@ -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 WeightInfo for SubstrateWeight { // 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)