referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/
scheduler.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The scheduler module for parachains and parathreads.
18//!
19//! This module is responsible for two main tasks:
20//!   - Partitioning validators into groups and assigning groups to parachains and parathreads
21//!   - Scheduling parachains and parathreads
22//!
23//! It aims to achieve these tasks with these goals in mind:
24//! - It should be possible to know at least a block ahead-of-time, ideally more, which validators
25//!   are going to be assigned to which parachains.
26//! - Parachains that have a candidate pending availability in this fork of the chain should not be
27//!   assigned.
28//! - Validator assignments should not be gameable. Malicious cartels should not be able to
29//!   manipulate the scheduler to assign themselves as desired.
30//! - High or close to optimal throughput of parachains and parathreads. Work among validator groups
31//!   should be balanced.
32//!
33//! The Scheduler manages resource allocation using the concept of "Availability Cores".
34//! There will be one availability core for each parachain, and a fixed number of cores
35//! used for multiplexing parathreads. Validators will be partitioned into groups, with the same
36//! number of groups as availability cores. Validator groups will be assigned to different
37//! availability cores over time.
38
39use crate::{configuration, initializer::SessionChangeNotification, paras::AssignCoretime};
40use alloc::{
41	collections::{btree_map::BTreeMap, vec_deque::VecDeque},
42	vec,
43	vec::Vec,
44};
45use frame_support::{pallet_prelude::*, traits::Defensive};
46use frame_system::pallet_prelude::BlockNumberFor;
47use polkadot_primitives::{CoreIndex, GroupIndex, GroupRotationInfo, Id as ParaId, ValidatorIndex};
48use sp_runtime::traits::{One, Saturating};
49
50const LOG_TARGET: &str = "runtime::parachains::scheduler";
51
52pub use assigner_coretime::{
53	AssignmentState, CoreAssignment, CoreDescriptor, PartsOf57600, QueueDescriptor, Schedule,
54	WorkState,
55};
56pub use pallet::*;
57pub use polkadot_core_primitives::v2::BlockNumber;
58
59#[cfg(test)]
60mod tests;
61
62/// Implements core assignments as coming from the Coretime chain.
63///
64/// Depends on the ondemand pallet to assign pool cores.
65mod assigner_coretime;
66
67/// Storage migrations for the scheduler pallet.
68pub mod migration;
69
70#[frame_support::pallet]
71pub mod pallet {
72
73	use crate::on_demand;
74
75	use super::*;
76
77	const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
78
79	#[pallet::pallet]
80	#[pallet::without_storage_info]
81	#[pallet::storage_version(STORAGE_VERSION)]
82	pub struct Pallet<T>(_);
83
84	#[pallet::config]
85	pub trait Config: frame_system::Config + configuration::Config + on_demand::Config {}
86
87	#[pallet::error]
88	pub enum Error<T> {
89		/// assign_core was called with no assignments.
90		AssignmentsEmpty,
91		/// assign_core with non allowed insertion.
92		DisallowedInsert,
93	}
94
95	impl<T> From<assigner_coretime::Error> for Error<T> {
96		fn from(e: assigner_coretime::Error) -> Self {
97			match e {
98				assigner_coretime::Error::AssignmentsEmpty => Error::AssignmentsEmpty,
99				assigner_coretime::Error::DisallowedInsert => Error::DisallowedInsert,
100			}
101		}
102	}
103
104	/// All the validator groups. One for each core. Indices are into `ActiveValidators` - not the
105	/// broader set of Polkadot validators, but instead just the subset used for parachains during
106	/// this session.
107	///
108	/// Bound: The number of cores is the sum of the numbers of parachains and parathread
109	/// multiplexers. Reasonably, 100-1000. The dominant factor is the number of validators: safe
110	/// upper bound at 10k.
111	#[pallet::storage]
112	pub type ValidatorGroups<T> = StorageValue<_, Vec<Vec<ValidatorIndex>>, ValueQuery>;
113
114	/// The block number where the session start occurred. Used to track how many group rotations
115	/// have occurred.
116	///
117	/// Note that in the context of parachains modules the session change is signaled during
118	/// the block and enacted at the end of the block (at the finalization stage, to be exact).
119	/// Thus for all intents and purposes the effect of the session change is observed at the
120	/// block following the session change, block number of which we save in this storage value.
121	#[pallet::storage]
122	pub type SessionStartBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
123
124	/// Scheduled assignment sets for coretime cores.
125	///
126	/// Assignments as of the given block number. They will go into state once the block number is
127	/// reached (and replace whatever was in there before).
128	///
129	/// Managed by the `assigner_coretime` submodule.
130	#[pallet::storage]
131	pub type CoreSchedules<T: Config> = StorageMap<
132		_,
133		Twox64Concat,
134		(BlockNumberFor<T>, CoreIndex),
135		assigner_coretime::Schedule<BlockNumberFor<T>>,
136		OptionQuery,
137	>;
138
139	/// Assignments which are currently active for each core.
140	///
141	/// They will be picked from `CoreSchedules` once we reach the scheduled block number.
142	///
143	/// Managed by the `assigner_coretime` submodule.
144	#[pallet::storage]
145	pub type CoreDescriptors<T: Config> = StorageValue<
146		_,
147		BTreeMap<CoreIndex, assigner_coretime::CoreDescriptor<BlockNumberFor<T>>>,
148		ValueQuery,
149	>;
150
151	/// Availability timeout status of a core.
152	pub(crate) struct AvailabilityTimeoutStatus<BlockNumber> {
153		/// Is the core already timed out?
154		///
155		/// If this is true the core will be freed at this block.
156		pub timed_out: bool,
157
158		/// When does this core timeout.
159		///
160		/// The block number the core times out. If `timed_out` is true, this will correspond to
161		/// now (current block number).
162		pub live_until: BlockNumber,
163	}
164}
165
166impl<T: Config> AssignCoretime for Pallet<T> {
167	// Only for testing purposes.
168	fn assign_coretime(id: ParaId) -> DispatchResult {
169		let current_block = frame_system::Pallet::<T>::block_number();
170
171		// Add a new core and assign the para to it.
172		let mut config = configuration::ActiveConfig::<T>::get();
173		let core = config.scheduler_params.num_cores;
174		config.scheduler_params.num_cores.saturating_inc();
175
176		// `assign_coretime` is only called at genesis or by root, so setting the active
177		// config here is fine.
178		configuration::Pallet::<T>::force_set_active_config(config);
179
180		let begin = current_block + One::one();
181		let assignment = vec![(pallet_broker::CoreAssignment::Task(id.into()), PartsOf57600::FULL)];
182		assigner_coretime::assign_core::<T>(CoreIndex(core), begin, assignment, None)
183			.map_err(Error::<T>::from)?;
184		Ok(())
185	}
186}
187
188impl<T: Config> Pallet<T> {
189	/// Assign a particular core ala Coretime.
190	pub(crate) fn assign_core(
191		core: CoreIndex,
192		begin: BlockNumberFor<T>,
193		assignment: Vec<(CoreAssignment, PartsOf57600)>,
194		end_hint: Option<BlockNumberFor<T>>,
195	) -> DispatchResult {
196		assigner_coretime::assign_core::<T>(core, begin, assignment, end_hint)
197			.map_err(Error::<T>::from)?;
198		Ok(())
199	}
200
201	/// Advance claim queue.
202	///
203	/// Parameters:
204	/// - is_blocked: Inform whether a given core is currently blocked (schedules can not be
205	/// served).
206	///
207	/// Returns: The `ParaId`s that had been scheduled next, blocked ones are filtered out.
208	pub(crate) fn advance_claim_queue<F: Fn(CoreIndex) -> bool>(
209		is_blocked: F,
210	) -> BTreeMap<CoreIndex, ParaId> {
211		let mut assignments = assigner_coretime::advance_assignments::<T, F>(is_blocked);
212		assignments.split_off(&CoreIndex(Self::num_availability_cores() as _));
213		assignments
214	}
215
216	/// Retrieve upcoming claims for each core.
217	///
218	/// To be called from runtime APIs.
219	pub(crate) fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
220		// Since this is being called from a runtime API, we need to workaround for #64.
221		if Self::on_chain_storage_version() == StorageVersion::new(3) {
222			return migration::v3::ClaimQueue::<T>::get()
223				.into_iter()
224				.map(|(core_index, paras)| {
225					(core_index, paras.into_iter().map(|e| e.para_id()).collect())
226				})
227				.collect();
228		}
229
230		let config = configuration::ActiveConfig::<T>::get();
231		let lookahead = config.scheduler_params.lookahead;
232		let mut queue = assigner_coretime::peek_next_block::<T>(lookahead);
233		queue.split_off(&CoreIndex(Self::num_availability_cores() as _));
234		queue
235	}
236
237	/// Called by the initializer to initialize the scheduler pallet.
238	pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
239		Weight::zero()
240	}
241
242	/// Called by the initializer to finalize the scheduler pallet.
243	pub(crate) fn initializer_finalize() {}
244
245	/// Called by the initializer to note that a new session has started.
246	pub(crate) fn initializer_on_new_session(
247		notification: &SessionChangeNotification<BlockNumberFor<T>>,
248	) {
249		let SessionChangeNotification { validators, new_config, .. } = notification;
250		let config = new_config;
251		let assigner_cores = config.scheduler_params.num_cores;
252
253		let n_cores = core::cmp::max(
254			assigner_cores,
255			match config.scheduler_params.max_validators_per_core {
256				Some(x) if x != 0 => validators.len() as u32 / x,
257				_ => 0,
258			},
259		);
260
261		// shuffle validators into groups.
262		if n_cores == 0 || validators.is_empty() {
263			ValidatorGroups::<T>::set(Vec::new());
264		} else {
265			let group_base_size = validators
266				.len()
267				.checked_div(n_cores as usize)
268				.defensive_proof("n_cores should not be 0")
269				.unwrap_or(0);
270			let n_larger_groups = validators
271				.len()
272				.checked_rem(n_cores as usize)
273				.defensive_proof("n_cores should not be 0")
274				.unwrap_or(0);
275
276			// Groups contain indices into the validators from the session change notification,
277			// which are already shuffled.
278
279			let mut groups: Vec<Vec<ValidatorIndex>> = Vec::new();
280			for i in 0..n_larger_groups {
281				let offset = (group_base_size + 1) * i;
282				groups.push(
283					(0..group_base_size + 1)
284						.map(|j| offset + j)
285						.map(|j| ValidatorIndex(j as _))
286						.collect(),
287				);
288			}
289
290			for i in 0..(n_cores as usize - n_larger_groups) {
291				let offset = (n_larger_groups * (group_base_size + 1)) + (i * group_base_size);
292				groups.push(
293					(0..group_base_size)
294						.map(|j| offset + j)
295						.map(|j| ValidatorIndex(j as _))
296						.collect(),
297				);
298			}
299
300			ValidatorGroups::<T>::set(groups);
301		}
302		let now = frame_system::Pallet::<T>::block_number() + One::one();
303		SessionStartBlock::<T>::set(now);
304	}
305
306	/// Get the validators in the given group, if the group index is valid for this session.
307	pub(crate) fn group_validators(group_index: GroupIndex) -> Option<Vec<ValidatorIndex>> {
308		ValidatorGroups::<T>::get().get(group_index.0 as usize).map(|g| g.clone())
309	}
310
311	/// Get the number of cores.
312	pub(crate) fn num_availability_cores() -> usize {
313		ValidatorGroups::<T>::decode_len().unwrap_or(0)
314	}
315
316	/// Get the group assigned to a specific core by index at the current block number. Result
317	/// undefined if the core index is unknown or the block number is less than the session start
318	/// index.
319	pub(crate) fn group_assigned_to_core(
320		core: CoreIndex,
321		at: BlockNumberFor<T>,
322	) -> Option<GroupIndex> {
323		let config = configuration::ActiveConfig::<T>::get();
324		let session_start_block = SessionStartBlock::<T>::get();
325
326		if at < session_start_block {
327			return None;
328		}
329
330		let validator_groups = ValidatorGroups::<T>::get();
331
332		if core.0 as usize >= validator_groups.len() {
333			return None;
334		}
335
336		let rotations_since_session_start: BlockNumberFor<T> =
337			(at - session_start_block) / config.scheduler_params.group_rotation_frequency;
338
339		let rotations_since_session_start =
340			<BlockNumberFor<T> as TryInto<u32>>::try_into(rotations_since_session_start)
341				.unwrap_or(0);
342		// Error case can only happen if rotations occur only once every u32::max(),
343		// so functionally no difference in behavior.
344
345		let group_idx =
346			(core.0 as usize + rotations_since_session_start as usize) % validator_groups.len();
347		Some(GroupIndex(group_idx as u32))
348	}
349
350	/// Returns a predicate that should be used for timing out occupied cores.
351	///
352	/// This only ever times out cores that have been occupied across a group rotation boundary.
353	pub(crate) fn availability_timeout_predicate(
354	) -> impl Fn(BlockNumberFor<T>) -> AvailabilityTimeoutStatus<BlockNumberFor<T>> {
355		let config = configuration::ActiveConfig::<T>::get();
356		let now = frame_system::Pallet::<T>::block_number();
357		let rotation_info = Self::group_rotation_info(now);
358
359		let next_rotation = rotation_info.next_rotation_at();
360
361		let times_out = Self::availability_timeout_check_required();
362
363		move |pending_since| {
364			let time_out_at = if times_out {
365				// We are at the beginning of the rotation, here availability period is relevant.
366				// Note: blocks backed in this rotation will never time out here as backed_in +
367				// config.paras_availability_period will always be > now for these blocks, as
368				// otherwise above condition would not be true.
369				pending_since + config.scheduler_params.paras_availability_period
370			} else {
371				next_rotation + config.scheduler_params.paras_availability_period
372			};
373
374			AvailabilityTimeoutStatus { timed_out: time_out_at <= now, live_until: time_out_at }
375		}
376	}
377
378	/// Is evaluation of `availability_timeout_predicate` necessary at the current block?
379	///
380	/// This can be used to avoid calling `availability_timeout_predicate` for each core in case
381	/// this function returns false.
382	pub(crate) fn availability_timeout_check_required() -> bool {
383		let config = configuration::ActiveConfig::<T>::get();
384		let now = frame_system::Pallet::<T>::block_number() + One::one();
385		let rotation_info = Self::group_rotation_info(now);
386
387		let current_window =
388			rotation_info.last_rotation_at() + config.scheduler_params.paras_availability_period;
389		now < current_window
390	}
391
392	/// Returns a helper for determining group rotation.
393	pub(crate) fn group_rotation_info(
394		now: BlockNumberFor<T>,
395	) -> GroupRotationInfo<BlockNumberFor<T>> {
396		let session_start_block = SessionStartBlock::<T>::get();
397		let group_rotation_frequency = configuration::ActiveConfig::<T>::get()
398			.scheduler_params
399			.group_rotation_frequency;
400
401		GroupRotationInfo { session_start_block, now, group_rotation_frequency }
402	}
403
404	#[cfg(test)]
405	fn claim_queue_len() -> usize {
406		Self::claim_queue().iter().map(|la_vec| la_vec.1.len()).sum()
407	}
408
409	#[cfg(test)]
410	#[allow(dead_code)]
411	pub(crate) fn claim_queue_is_empty() -> bool {
412		Self::claim_queue_len() == 0
413	}
414
415	#[cfg(test)]
416	pub(crate) fn set_validator_groups(validator_groups: Vec<Vec<ValidatorIndex>>) {
417		ValidatorGroups::<T>::set(validator_groups);
418	}
419}