polkadot_runtime_parachains/
scheduler.rs1use 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
62mod assigner_coretime;
66
67pub 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 AssignmentsEmpty,
91 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 #[pallet::storage]
112 pub type ValidatorGroups<T> = StorageValue<_, Vec<Vec<ValidatorIndex>>, ValueQuery>;
113
114 #[pallet::storage]
122 pub type SessionStartBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
123
124 #[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 #[pallet::storage]
145 pub type CoreDescriptors<T: Config> = StorageValue<
146 _,
147 BTreeMap<CoreIndex, assigner_coretime::CoreDescriptor<BlockNumberFor<T>>>,
148 ValueQuery,
149 >;
150
151 pub(crate) struct AvailabilityTimeoutStatus<BlockNumber> {
153 pub timed_out: bool,
157
158 pub live_until: BlockNumber,
163 }
164}
165
166impl<T: Config> AssignCoretime for Pallet<T> {
167 fn assign_coretime(id: ParaId) -> DispatchResult {
169 let current_block = frame_system::Pallet::<T>::block_number();
170
171 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 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 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 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 pub(crate) fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
220 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 pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
239 Weight::zero()
240 }
241
242 pub(crate) fn initializer_finalize() {}
244
245 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 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 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 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 pub(crate) fn num_availability_cores() -> usize {
313 ValidatorGroups::<T>::decode_len().unwrap_or(0)
314 }
315
316 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 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 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 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 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 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}