difftreelog
doc: scheduler
in: master
1 file changed
pallets/scheduler-v2/src/lib.rsdiffbeforeafterboth666667// Ensure we're `no_std` when compiling for Wasm.67// Ensure we're `no_std` when compiling for Wasm.68#![cfg_attr(not(feature = "std"), no_std)]68#![cfg_attr(not(feature = "std"), no_std)]69#![deny(missing_docs)]697070#[cfg(feature = "runtime-benchmarks")]71#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72mod benchmarking;106/// The location of a scheduled task that can be used to remove it.107/// The location of a scheduled task that can be used to remove it.107pub type TaskAddress<BlockNumber> = (BlockNumber, u32);108pub type TaskAddress<BlockNumber> = (BlockNumber, u32);108109110/// A an encoded bounded `Call`. Its encoding must be at most 128 bytes.109pub type EncodedCall = BoundedVec<u8, ConstU32<128>>;111pub type EncodedCall = BoundedVec<u8, ConstU32<128>>;110112111#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]113#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]112#[scale_info(skip_type_params(T))]114#[scale_info(skip_type_params(T))]115/// A scheduled call is stored as is or as a preimage hash to lookup.116/// This enum represents both variants.113pub enum ScheduledCall<T: Config> {117pub enum ScheduledCall<T: Config> {118 /// A an encoded bounded `Call`. Its encoding must be at most 128 bytes.114 Inline(EncodedCall),119 Inline(EncodedCall),120121 /// A Blake2-256 hash of the call together with an upper limit for its size.115 PreimageLookup { hash: T::Hash, unbounded_len: u32 },122 PreimageLookup {123 /// A call hash to lookup124 hash: T::Hash,125126 /// The length of the decoded call127 unbounded_len: u32,128 },116}129}117130118impl<T: Config> ScheduledCall<T> {131impl<T: Config> ScheduledCall<T> {132 /// Convert an otherwise unbounded or large value into a type ready for placing in storage.133 ///134 /// NOTE: Once this API is used, you should use either `drop` or `realize`.119 pub fn new(call: <T as Config>::RuntimeCall) -> Result<Self, DispatchError> {135 pub fn new(call: <T as Config>::RuntimeCall) -> Result<Self, DispatchError> {120 let encoded = call.encode();136 let encoded = call.encode();121 let len = encoded.len();137 let len = encoded.len();154 }170 }155 }171 }156172173 // Decodes a runtime call157 fn decode(mut data: &[u8]) -> Result<<T as Config>::RuntimeCall, DispatchError> {174 fn decode(mut data: &[u8]) -> Result<<T as Config>::RuntimeCall, DispatchError> {158 <T as Config>::RuntimeCall::decode(&mut data)175 <T as Config>::RuntimeCall::decode(&mut data)159 .map_err(|_| <Error<T>>::ScheduledCallCorrupted.into())176 .map_err(|_| <Error<T>>::ScheduledCallCorrupted.into())160 }177 }161}178}162179180/// A scheduler's interface for managing preimages to hashes181/// and looking up preimages from their hash on-chain.163pub trait SchedulerPreimages<T: Config>: PreimageRecipient<T::Hash> {182pub trait SchedulerPreimages<T: Config>: PreimageRecipient<T::Hash> {183 /// No longer request that the data for decoding the given `call` is available.164 fn drop(call: &ScheduledCall<T>);184 fn drop(call: &ScheduledCall<T>);165185186 /// Convert the given `call` instance back into its original instance, also returning the187 /// exact size of its encoded form if it needed to be looked-up from a stored preimage.188 ///189 /// NOTE: This does not remove any data needed for realization. If you will no longer use the190 /// `call`, use `realize` instead or use `drop` afterwards.166 fn peek(191 fn peek(167 call: &ScheduledCall<T>,192 call: &ScheduledCall<T>,168 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError>;193 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError>;210 }235 }211}236}212237238/// Scheduler's supported origins.213pub enum ScheduledEnsureOriginSuccess<AccountId> {239pub enum ScheduledEnsureOriginSuccess<AccountId> {240 /// A scheduled transaction has the Root origin.214 Root,241 Root,242243 /// A specific account has signed a scheduled transaction.215 Signed(AccountId),244 Signed(AccountId),216}245}217246247/// An identifier of a scheduled task.218pub type TaskName = [u8; 32];248pub type TaskName = [u8; 32];219249220/// Information regarding an item to be executed in the future.250/// Information regarding an item to be executed in the future.238 _phantom: PhantomData<AccountId>,268 _phantom: PhantomData<AccountId>,239}269}240270271/// Information regarding an item to be executed in the future.241pub type ScheduledOf<T> = Scheduled<272pub type ScheduledOf<T> = Scheduled<242 TaskName,273 TaskName,243 ScheduledCall<T>,274 ScheduledCall<T>,248279249#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)]280#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)]250#[scale_info(skip_type_params(T))]281#[scale_info(skip_type_params(T))]282/// A structure for storing scheduled tasks in a block.283/// The `BlockAgenda` tracks the available free space for a new task in a block.4284///285/// The agenda's maximum amount of tasks is `T::MaxScheduledPerBlock`.251pub struct BlockAgenda<T: Config> {286pub struct BlockAgenda<T: Config> {252 agenda: BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,287 agenda: BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,253 free_places: u32,288 free_places: u32,254}289}255290256impl<T: Config> BlockAgenda<T> {291impl<T: Config> BlockAgenda<T> {292 /// Tries to push a new scheduled task into the block's agenda.293 /// If there is a free place, the new task will take it,294 /// and the `BlockAgenda` will record that the number of free places has decreased.295 ///296 /// An error containing the scheduled task will be returned if there are no free places.297 ///298 /// The complexity of the check for the *existence* of a free place is O(1).299 /// The complexity of *finding* the free slot is O(n).257 fn try_push(&mut self, scheduled: ScheduledOf<T>) -> Result<u32, ScheduledOf<T>> {300 fn try_push(&mut self, scheduled: ScheduledOf<T>) -> Result<u32, ScheduledOf<T>> {258 if self.free_places == 0 {301 if self.free_places == 0 {259 return Err(scheduled);302 return Err(scheduled);276 }319 }277 }320 }278321322 /// Sets a slot by the given index and the slot value.323 ///324 /// ### Panics325 /// If the index is out of range, the function will panic.279 fn set_slot(&mut self, index: u32, slot: Option<ScheduledOf<T>>) {326 fn set_slot(&mut self, index: u32, slot: Option<ScheduledOf<T>>) {280 self.agenda[index as usize] = slot;327 self.agenda[index as usize] = slot;281 }328 }282329330 /// Returns an iterator containing references to the agenda's slots.283 fn iter(&self) -> impl Iterator<Item = &'_ Option<ScheduledOf<T>>> + '_ {331 fn iter(&self) -> impl Iterator<Item = &'_ Option<ScheduledOf<T>>> + '_ {284 self.agenda.iter()332 self.agenda.iter()285 }333 }286334335 /// Returns an immutable reference to a scheduled task if there is one under the given index.336 ///337 /// The function returns `None` if:338 /// * The `index` is out of range339 /// * No scheduled task occupies the agenda slot under the given index.287 fn get(&self, index: u32) -> Option<&ScheduledOf<T>> {340 fn get(&self, index: u32) -> Option<&ScheduledOf<T>> {288 match self.agenda.get(index as usize) {341 match self.agenda.get(index as usize) {289 Some(Some(scheduled)) => Some(scheduled),342 Some(Some(scheduled)) => Some(scheduled),290 _ => None,343 _ => None,291 }344 }292 }345 }293346347 /// Returns a mutable reference to a scheduled task if there is one under the given index.348 ///349 /// The function returns `None` if:350 /// * The `index` is out of range351 /// * No scheduled task occupies the agenda slot under the given index.294 fn get_mut(&mut self, index: u32) -> Option<&mut ScheduledOf<T>> {352 fn get_mut(&mut self, index: u32) -> Option<&mut ScheduledOf<T>> {295 match self.agenda.get_mut(index as usize) {353 match self.agenda.get_mut(index as usize) {296 Some(Some(scheduled)) => Some(scheduled),354 Some(Some(scheduled)) => Some(scheduled),297 _ => None,355 _ => None,298 }356 }299 }357 }300358359 /// Take a scheduled task by the given index.360 ///361 /// If there is a task under the index, the function will:362 /// * Free the corresponding agenda slot.363 /// * Decrease the number of free places.364 /// * Return the scheduled task.365 ///366 /// The function returns `None` if there is no task under the index.301 fn take(&mut self, index: u32) -> Option<ScheduledOf<T>> {367 fn take(&mut self, index: u32) -> Option<ScheduledOf<T>> {302 let removed = self.agenda.get_mut(index as usize)?.take();368 let removed = self.agenda.get_mut(index as usize)?.take();303369320 }386 }321 }387 }322}388}323389/// A structure for tracking the used weight390/// and checking if it does not exceed the weight limit.324struct WeightCounter {391struct WeightCounter {325 used: Weight,392 used: Weight,326 limit: Weight,393 limit: Weight,327}394}328395329impl WeightCounter {396impl WeightCounter {397 /// Checks if the weight `w` can be accommodated by the counter.398 ///399 /// If there is room for the additional weight `w`,400 /// the function will update the used weight and return true.330 fn check_accrue(&mut self, w: Weight) -> bool {401 fn check_accrue(&mut self, w: Weight) -> bool {331 let test = self.used.saturating_add(w);402 let test = self.used.saturating_add(w);332 if test.any_gt(self.limit) {403 if test.any_gt(self.limit) {337 }408 }338 }409 }339410411 /// Checks if the weight `w` can be accommodated by the counter.340 fn can_accrue(&mut self, w: Weight) -> bool {412 fn can_accrue(&mut self, w: Weight) -> bool {341 self.used.saturating_add(w).all_lte(self.limit)413 self.used.saturating_add(w).all_lte(self.limit)342 }414 }343}415}344416345pub(crate) trait MarginalWeightInfo: WeightInfo {417pub(crate) trait MarginalWeightInfo: WeightInfo {418 /// Return the weight of servicing a single task.346 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {419 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {347 let base = Self::service_task_base();420 let base = Self::service_task_base();348 let mut total = match maybe_lookup_len {421 let mut total = match maybe_lookup_len {381454382 #[pallet::config]455 #[pallet::config]383 pub trait Config: frame_system::Config {456 pub trait Config: frame_system::Config {457 /// The overarching event type.384 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;458 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;385459386 /// The aggregated origin which the dispatch will take.460 /// The aggregated origin which the dispatch will take.442 type PrioritySetOrigin: EnsureOrigin<<Self as system::Config>::RuntimeOrigin>;516 type PrioritySetOrigin: EnsureOrigin<<Self as system::Config>::RuntimeOrigin>;443 }517 }444518519 /// It contains the block number from which we should service tasks.520 /// It's used for delaying the servicing of future blocks' agendas if we had overweight tasks.445 #[pallet::storage]521 #[pallet::storage]446 pub type IncompleteSince<T: Config> = StorageValue<_, T::BlockNumber>;522 pub type IncompleteSince<T: Config> = StorageValue<_, T::BlockNumber>;447523461 pub enum Event<T: Config> {537 pub enum Event<T: Config> {462 /// Scheduled some task.538 /// Scheduled some task.463 Scheduled { when: T::BlockNumber, index: u32 },539 Scheduled {540 /// The block number in which the scheduled task should be executed.541 when: T::BlockNumber,542543 /// The index of the block's agenda slot.544 index: u32,545 },464 /// Canceled some task.546 /// Canceled some task.465 Canceled { when: T::BlockNumber, index: u32 },547 Canceled {548 /// The block number in which the canceled task has been.549 when: T::BlockNumber,550551 /// The index of the block's agenda slot that had become available.552 index: u32,553 },466 /// Dispatched some task.554 /// Dispatched some task.467 Dispatched {555 Dispatched {556 /// The task's address - the block number and the block's agenda index.468 task: TaskAddress<T::BlockNumber>,557 task: TaskAddress<T::BlockNumber>,558559 /// The task's name if it is not anonymous.469 id: Option<[u8; 32]>,560 id: Option<[u8; 32]>,561562 /// The task's execution result.470 result: DispatchResult,563 result: DispatchResult,471 },564 },472 /// Scheduled task's priority has changed565 /// Scheduled task's priority has changed473 PriorityChanged {566 PriorityChanged {567 /// The task's address - the block number and the block's agenda index.474 when: T::BlockNumber,568 task: TaskAddress<T::BlockNumber>,475 index: u32,569570 /// The new priority of the task.476 priority: schedule::Priority,571 priority: schedule::Priority,477 },572 },478 /// The call for the provided hash was not found so the task has been aborted.573 /// The call for the provided hash was not found so the task has been aborted.479 CallUnavailable {574 CallUnavailable {575 /// The task's address - the block number and the block's agenda index.480 task: TaskAddress<T::BlockNumber>,576 task: TaskAddress<T::BlockNumber>,577578 /// The task's name if it is not anonymous.481 id: Option<[u8; 32]>,579 id: Option<[u8; 32]>,482 },580 },483 /// The given task can never be executed since it is overweight.581 /// The given task can never be executed since it is overweight.484 PermanentlyOverweight {582 PermanentlyOverweight {583 /// The task's address - the block number and the block's agenda index.485 task: TaskAddress<T::BlockNumber>,584 task: TaskAddress<T::BlockNumber>,585586 /// The task's name if it is not anonymous.486 id: Option<[u8; 32]>,587 id: Option<[u8; 32]>,487 },588 },488 }589 }523 #[pallet::call]624 #[pallet::call]524 impl<T: Config> Pallet<T> {625 impl<T: Config> Pallet<T> {525 /// Anonymously schedule a task.626 /// Anonymously schedule a task.627 ///628 /// Only `T::ScheduleOrigin` is allowed to schedule a task.629 /// Only `T::PrioritySetOrigin` is allowed to set the task's priority.526 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]630 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]527 pub fn schedule(631 pub fn schedule(528 origin: OriginFor<T>,632 origin: OriginFor<T>,549 }653 }550654551 /// Cancel an anonymously scheduled task.655 /// Cancel an anonymously scheduled task.656 ///657 /// The `T::OriginPrivilegeCmp` decides whether the given origin is allowed to cancel the task or not.552 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]658 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]553 pub fn cancel(origin: OriginFor<T>, when: T::BlockNumber, index: u32) -> DispatchResult {659 pub fn cancel(origin: OriginFor<T>, when: T::BlockNumber, index: u32) -> DispatchResult {554 T::ScheduleOrigin::ensure_origin(origin.clone())?;660 T::ScheduleOrigin::ensure_origin(origin.clone())?;558 }664 }559665560 /// Schedule a named task.666 /// Schedule a named task.667 ///668 /// Only `T::ScheduleOrigin` is allowed to schedule a task.669 /// Only `T::PrioritySetOrigin` is allowed to set the task's priority.561 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]670 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]562 pub fn schedule_named(671 pub fn schedule_named(563 origin: OriginFor<T>,672 origin: OriginFor<T>,586 }695 }587696588 /// Cancel a named scheduled task.697 /// Cancel a named scheduled task.698 ///699 /// The `T::OriginPrivilegeCmp` decides whether the given origin is allowed to cancel the task or not.589 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]700 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]590 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {701 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {591 T::ScheduleOrigin::ensure_origin(origin.clone())?;702 T::ScheduleOrigin::ensure_origin(origin.clone())?;625 }736 }626737627 /// Schedule a named task after a delay.738 /// Schedule a named task after a delay.739 ///740 /// Only `T::ScheduleOrigin` is allowed to schedule a task.741 /// Only `T::PrioritySetOrigin` is allowed to set the task's priority.628 ///742 ///629 /// # <weight>743 /// # <weight>630 /// Same as [`schedule_named`](Self::schedule_named).744 /// Same as [`schedule_named`](Self::schedule_named).656 Ok(())770 Ok(())657 }771 }658772773 /// Change a named task's priority.774 ///775 /// Only the `T::PrioritySetOrigin` is allowed to change the task's priority.659 #[pallet::weight(<T as Config>::WeightInfo::change_named_priority(T::MaxScheduledPerBlock::get()))]776 #[pallet::weight(<T as Config>::WeightInfo::change_named_priority(T::MaxScheduledPerBlock::get()))]660 pub fn change_named_priority(777 pub fn change_named_priority(661 origin: OriginFor<T>,778 origin: OriginFor<T>,670}787}671788672impl<T: Config> Pallet<T> {789impl<T: Config> Pallet<T> {790 /// Converts the `DispatchTime` to the `BlockNumber`.791 ///792 /// Returns an error if the block number is in the past.673 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {793 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {674 let now = frame_system::Pallet::<T>::block_number();794 let now = frame_system::Pallet::<T>::block_number();675795687 Ok(when)807 Ok(when)688 }808 }689809810 /// Places the mandatory task.811 ///812 /// It will try to place the task into the block pointed by the `when` parameter.813 ///814 /// If the block has no room for a task,815 /// the function will search for a future block that can accommodate the task.690 fn mandatory_place_task(when: T::BlockNumber, what: ScheduledOf<T>) {816 fn mandatory_place_task(when: T::BlockNumber, what: ScheduledOf<T>) {691 Self::place_task(when, what, true).expect("mandatory place task always succeeds; qed");817 Self::place_task(when, what, true).expect("mandatory place task always succeeds; qed");692 }818 }693819820 /// Tries to place a task `what` into the given block `when`.821 ///822 /// Returns an error if the block has no room for the task.694 fn try_place_task(823 fn try_place_task(695 when: T::BlockNumber,824 when: T::BlockNumber,696 what: ScheduledOf<T>,825 what: ScheduledOf<T>,697 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {826 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {698 Self::place_task(when, what, false)827 Self::place_task(when, what, false)699 }828 }700829830 /// If `is_mandatory` is true, the function behaves like [`mandatory_place_task`](Self::mandatory_place_task);831 /// otherwise it acts like [`try_place_task`](Self::try_place_task).832 ///833 /// The function also updates the `Lookup` storage.701 fn place_task(834 fn place_task(702 mut when: T::BlockNumber,835 mut when: T::BlockNumber,703 what: ScheduledOf<T>,836 what: ScheduledOf<T>,716 Ok(address)849 Ok(address)717 }850 }718851852 /// Pushes the scheduled task into the block's agenda.853 ///854 /// If `is_mandatory` is true, it searches for a block with a free slot for the given task.855 ///856 /// If `is_mandatory` is false and there is no free slot, the function returns an error.719 fn push_to_agenda(857 fn push_to_agenda(720 when: &mut T::BlockNumber,858 when: &mut T::BlockNumber,721 mut what: ScheduledOf<T>,859 mut what: ScheduledOf<T>,8861024887 scheduled.priority = priority;1025 scheduled.priority = priority;888 Self::deposit_event(Event::PriorityChanged {1026 Self::deposit_event(Event::PriorityChanged {889 when,1027 task: (when, index),890 index,891 priority,1028 priority,892 });1029 });