1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768#![cfg_attr(not(feature = "std"), no_std)]69#![deny(missing_docs)]7071#[cfg(feature = "runtime-benchmarks")]72mod benchmarking;73#[cfg(test)]74mod mock;75#[cfg(test)]76mod tests;77pub mod weights;7879use codec::{Codec, Decode, Encode, MaxEncodedLen};80use frame_support::{81 dispatch::{82 DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter, PostDispatchInfo,83 },84 traits::{85 schedule::{self, DispatchTime, LOWEST_PRIORITY},86 EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp, StorageVersion, PreimageRecipient,87 ConstU32, UnfilteredDispatchable,88 },89 weights::Weight,90 unsigned::TransactionValidityError,91};9293use frame_system::{self as system};94use scale_info::TypeInfo;95use sp_runtime::{96 traits::{BadOrigin, One, Saturating, Zero, Hash},97 BoundedVec, RuntimeDebug, DispatchErrorWithPostInfo,98};99use sp_core::H160;100use sp_std::{cmp::Ordering, marker::PhantomData, prelude::*};101pub use weights::WeightInfo;102103pub use pallet::*;104105106pub type PeriodicIndex = u32;107108pub type TaskAddress<BlockNumber> = (BlockNumber, u32);109110111pub type EncodedCall = BoundedVec<u8, ConstU32<128>>;112113#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]114#[scale_info(skip_type_params(T))]115116117pub enum ScheduledCall<T: Config> {118 119 Inline(EncodedCall),120121 122 PreimageLookup {123 124 hash: T::Hash,125126 127 unbounded_len: u32,128 },129}130131impl<T: Config> ScheduledCall<T> {132 133 134 135 pub fn new(call: <T as Config>::RuntimeCall) -> Result<Self, DispatchError> {136 let encoded = call.encode();137 let len = encoded.len();138139 match EncodedCall::try_from(encoded.clone()) {140 Ok(bounded) => Ok(Self::Inline(bounded)),141 Err(_) => {142 let hash = <T as system::Config>::Hashing::hash_of(&encoded);143 <T as Config>::Preimages::note_preimage(144 encoded145 .try_into()146 .map_err(|_| <Error<T>>::TooBigScheduledCall)?,147 );148149 Ok(Self::PreimageLookup {150 hash,151 unbounded_len: len as u32,152 })153 }154 }155 }156157 158 pub fn lookup_len(&self) -> Option<u32> {159 match self {160 Self::Inline(..) => None,161 Self::PreimageLookup { unbounded_len, .. } => Some(*unbounded_len),162 }163 }164165 166 pub fn lookup_needed(&self) -> bool {167 match self {168 Self::Inline(_) => false,169 Self::PreimageLookup { .. } => true,170 }171 }172173 174 fn decode(mut data: &[u8]) -> Result<<T as Config>::RuntimeCall, DispatchError> {175 <T as Config>::RuntimeCall::decode(&mut data)176 .map_err(|_| <Error<T>>::ScheduledCallCorrupted.into())177 }178}179180181pub trait SchedulerPreimagesWeightInfo<W: WeightInfo> {182 183 fn service_task_fetched(call_length: u32) -> Weight;184}185186impl<W: WeightInfo> SchedulerPreimagesWeightInfo<W> for () {187 fn service_task_fetched(_call_length: u32) -> Weight {188 W::service_task_base()189 }190}191192193194pub trait SchedulerPreimages<T: Config>:195 PreimageRecipient<T::Hash> + SchedulerPreimagesWeightInfo<T::WeightInfo>196{197 198 fn drop(call: &ScheduledCall<T>);199200 201 202 203 204 205 fn peek(206 call: &ScheduledCall<T>,207 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError>;208209 210 211 212 fn realize(213 call: &ScheduledCall<T>,214 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError>;215}216217impl<T: Config, PP: PreimageRecipient<T::Hash> + SchedulerPreimagesWeightInfo<T::WeightInfo>>218 SchedulerPreimages<T> for PP219{220 fn drop(call: &ScheduledCall<T>) {221 match call {222 ScheduledCall::Inline(_) => {}223 ScheduledCall::PreimageLookup { hash, .. } => Self::unrequest_preimage(hash),224 }225 }226227 fn peek(228 call: &ScheduledCall<T>,229 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError> {230 match call {231 ScheduledCall::Inline(data) => Ok((ScheduledCall::<T>::decode(data)?, None)),232 ScheduledCall::PreimageLookup {233 hash,234 unbounded_len,235 } => {236 let (preimage, len) = Self::get_preimage(hash)237 .ok_or(<Error<T>>::PreimageNotFound)238 .map(|preimage| (preimage, *unbounded_len))?;239240 Ok((ScheduledCall::<T>::decode(preimage.as_slice())?, Some(len)))241 }242 }243 }244245 fn realize(246 call: &ScheduledCall<T>,247 ) -> Result<(<T as pallet::Config>::RuntimeCall, Option<u32>), DispatchError> {248 let r = Self::peek(call)?;249 Self::drop(call);250 Ok(r)251 }252}253254255pub enum ScheduledEnsureOriginSuccess<AccountId> {256 257 Root,258259 260 Signed(AccountId),261}262263264pub type TaskName = [u8; 32];265266267#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]268#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]269pub struct Scheduled<Name, Call, BlockNumber, PalletsOrigin, AccountId> {270 271 maybe_id: Option<Name>,272273 274 priority: schedule::Priority,275276 277 call: Call,278279 280 maybe_periodic: Option<schedule::Period<BlockNumber>>,281282 283 origin: PalletsOrigin,284 _phantom: PhantomData<AccountId>,285}286287288pub type ScheduledOf<T> = Scheduled<289 TaskName,290 ScheduledCall<T>,291 <T as frame_system::Config>::BlockNumber,292 <T as Config>::PalletsOrigin,293 <T as frame_system::Config>::AccountId,294>;295296#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)]297#[scale_info(skip_type_params(T))]298299300301302pub struct BlockAgenda<T: Config> {303 agenda: BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,304 free_places: u32,305}306307impl<T: Config> BlockAgenda<T> {308 309 310 311 312 313 314 315 316 fn try_push(&mut self, scheduled: ScheduledOf<T>) -> Result<u32, ScheduledOf<T>> {317 if self.free_places == 0 {318 return Err(scheduled);319 }320321 self.free_places = self.free_places.saturating_sub(1);322323 if (self.agenda.len() as u32) < T::MaxScheduledPerBlock::get() {324 325 let _ = self.agenda.try_push(Some(scheduled));326 Ok((self.agenda.len() - 1) as u32)327 } else {328 match self.agenda.iter().position(|i| i.is_none()) {329 Some(hole_index) => {330 self.agenda[hole_index] = Some(scheduled);331 Ok(hole_index as u32)332 }333 None => unreachable!("free_places was greater than 0; qed"),334 }335 }336 }337338 339 340 341 342 fn set_slot(&mut self, index: u32, slot: Option<ScheduledOf<T>>) {343 self.agenda[index as usize] = slot;344 }345346 347 fn iter(&self) -> impl Iterator<Item = &'_ Option<ScheduledOf<T>>> + '_ {348 self.agenda.iter()349 }350351 352 353 354 355 356 fn get(&self, index: u32) -> Option<&ScheduledOf<T>> {357 match self.agenda.get(index as usize) {358 Some(Some(scheduled)) => Some(scheduled),359 _ => None,360 }361 }362363 364 365 366 367 368 fn get_mut(&mut self, index: u32) -> Option<&mut ScheduledOf<T>> {369 match self.agenda.get_mut(index as usize) {370 Some(Some(scheduled)) => Some(scheduled),371 _ => None,372 }373 }374375 376 377 378 379 380 381 382 383 fn take(&mut self, index: u32) -> Option<ScheduledOf<T>> {384 let removed = self.agenda.get_mut(index as usize)?.take();385386 if removed.is_some() {387 self.free_places = self.free_places.saturating_add(1);388 }389390 removed391 }392}393394impl<T: Config> Default for BlockAgenda<T> {395 fn default() -> Self {396 let agenda = Default::default();397 let free_places = T::MaxScheduledPerBlock::get();398399 Self {400 agenda,401 free_places,402 }403 }404}405406407struct WeightCounter {408 used: Weight,409 limit: Weight,410}411412impl WeightCounter {413 414 415 416 417 fn check_accrue(&mut self, w: Weight) -> bool {418 let test = self.used.saturating_add(w);419 if test.any_gt(self.limit) {420 false421 } else {422 self.used = test;423 true424 }425 }426427 428 fn can_accrue(&mut self, w: Weight) -> bool {429 self.used.saturating_add(w).all_lte(self.limit)430 }431}432433pub(crate) struct MarginalWeightInfo<T: Config>(sp_std::marker::PhantomData<T>);434435impl<T: Config> MarginalWeightInfo<T> {436 437 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {438 let base = T::WeightInfo::service_task_base();439 let mut total = match maybe_lookup_len {440 None => base,441 Some(l) => T::Preimages::service_task_fetched(l as u32),442 };443 if named {444 total.saturating_accrue(T::WeightInfo::service_task_named().saturating_sub(base));445 }446 if periodic {447 total.saturating_accrue(T::WeightInfo::service_task_periodic().saturating_sub(base));448 }449 total450 }451}452453#[frame_support::pallet]454pub mod pallet {455 use super::*;456 use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*};457 use system::pallet_prelude::*;458459 460 const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);461462 #[pallet::pallet]463 #[pallet::generate_store(pub(super) trait Store)]464 #[pallet::storage_version(STORAGE_VERSION)]465 pub struct Pallet<T>(_);466467 #[pallet::config]468 pub trait Config: frame_system::Config {469 470 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;471472 473 type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>474 + From<Self::PalletsOrigin>475 + IsType<<Self as system::Config>::RuntimeOrigin>476 + Clone;477478 479 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>>480 + Codec481 + Clone482 + Eq483 + TypeInfo484 + MaxEncodedLen;485486 487 type RuntimeCall: Parameter488 + Dispatchable<489 RuntimeOrigin = <Self as Config>::RuntimeOrigin,490 PostInfo = PostDispatchInfo,491 > + UnfilteredDispatchable<RuntimeOrigin = <Self as system::Config>::RuntimeOrigin>492 + GetDispatchInfo493 + From<system::Call<Self>>;494495 496 #[pallet::constant]497 type MaximumWeight: Get<Weight>;498499 500 type ScheduleOrigin: EnsureOrigin<501 <Self as system::Config>::RuntimeOrigin,502 Success = ScheduledEnsureOriginSuccess<Self::AccountId>,503 >;504505 506 507 508 509 510 511 512 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;513514 515 #[pallet::constant]516 type MaxScheduledPerBlock: Get<u32>;517518 519 type WeightInfo: WeightInfo;520521 522 type Preimages: SchedulerPreimages<Self>;523524 525 type CallExecutor: DispatchCall<Self, H160>;526527 528 type PrioritySetOrigin: EnsureOrigin<<Self as system::Config>::RuntimeOrigin>;529 }530531 532 533 #[pallet::storage]534 pub type IncompleteSince<T: Config> = StorageValue<_, T::BlockNumber>;535536 537 #[pallet::storage]538 pub type Agenda<T: Config> =539 StorageMap<_, Twox64Concat, T::BlockNumber, BlockAgenda<T>, ValueQuery>;540541 542 #[pallet::storage]543 pub(crate) type Lookup<T: Config> =544 StorageMap<_, Twox64Concat, TaskName, TaskAddress<T::BlockNumber>>;545546 547 #[pallet::event]548 #[pallet::generate_deposit(pub(super) fn deposit_event)]549 pub enum Event<T: Config> {550 551 Scheduled {552 553 when: T::BlockNumber,554555 556 index: u32,557 },558 559 Canceled {560 561 when: T::BlockNumber,562563 564 index: u32,565 },566 567 Dispatched {568 569 task: TaskAddress<T::BlockNumber>,570571 572 id: Option<[u8; 32]>,573574 575 result: DispatchResult,576 },577 578 PriorityChanged {579 580 task: TaskAddress<T::BlockNumber>,581582 583 priority: schedule::Priority,584 },585 586 CallUnavailable {587 588 task: TaskAddress<T::BlockNumber>,589590 591 id: Option<[u8; 32]>,592 },593 594 PermanentlyOverweight {595 596 task: TaskAddress<T::BlockNumber>,597598 599 id: Option<[u8; 32]>,600 },601 }602603 #[pallet::error]604 pub enum Error<T> {605 606 FailedToSchedule,607 608 AgendaIsExhausted,609 610 ScheduledCallCorrupted,611 612 PreimageNotFound,613 614 TooBigScheduledCall,615 616 NotFound,617 618 TargetBlockNumberInPast,619 620 Named,621 }622623 #[pallet::hooks]624 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {625 626 fn on_initialize(now: T::BlockNumber) -> Weight {627 let mut weight_counter = WeightCounter {628 used: Weight::zero(),629 limit: T::MaximumWeight::get(),630 };631 Self::service_agendas(&mut weight_counter, now, u32::max_value());632 weight_counter.used633 }634 }635636 #[pallet::call]637 impl<T: Config> Pallet<T> {638 639 640 641 642 #[pallet::call_index(0)]643 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]644 pub fn schedule(645 origin: OriginFor<T>,646 when: T::BlockNumber,647 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,648 priority: Option<schedule::Priority>,649 call: Box<<T as Config>::RuntimeCall>,650 ) -> DispatchResult {651 T::ScheduleOrigin::ensure_origin(origin.clone())?;652653 if priority.is_some() {654 T::PrioritySetOrigin::ensure_origin(origin.clone())?;655 }656657 let origin = <T as Config>::RuntimeOrigin::from(origin);658 Self::do_schedule(659 DispatchTime::At(when),660 maybe_periodic,661 priority.unwrap_or(LOWEST_PRIORITY),662 origin.caller().clone(),663 <ScheduledCall<T>>::new(*call)?,664 )?;665 Ok(())666 }667668 669 670 671 #[pallet::call_index(1)]672 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]673 pub fn cancel(origin: OriginFor<T>, when: T::BlockNumber, index: u32) -> DispatchResult {674 T::ScheduleOrigin::ensure_origin(origin.clone())?;675 let origin = <T as Config>::RuntimeOrigin::from(origin);676 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;677 Ok(())678 }679680 681 682 683 684 #[pallet::call_index(2)]685 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]686 pub fn schedule_named(687 origin: OriginFor<T>,688 id: TaskName,689 when: T::BlockNumber,690 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,691 priority: Option<schedule::Priority>,692 call: Box<<T as Config>::RuntimeCall>,693 ) -> DispatchResult {694 T::ScheduleOrigin::ensure_origin(origin.clone())?;695696 if priority.is_some() {697 T::PrioritySetOrigin::ensure_origin(origin.clone())?;698 }699700 let origin = <T as Config>::RuntimeOrigin::from(origin);701 Self::do_schedule_named(702 id,703 DispatchTime::At(when),704 maybe_periodic,705 priority.unwrap_or(LOWEST_PRIORITY),706 origin.caller().clone(),707 <ScheduledCall<T>>::new(*call)?,708 )?;709 Ok(())710 }711712 713 714 715 #[pallet::call_index(3)]716 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]717 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {718 T::ScheduleOrigin::ensure_origin(origin.clone())?;719 let origin = <T as Config>::RuntimeOrigin::from(origin);720 Self::do_cancel_named(Some(origin.caller().clone()), id)?;721 Ok(())722 }723724 725 726 727 728 729 #[pallet::call_index(4)]730 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]731 pub fn schedule_after(732 origin: OriginFor<T>,733 after: T::BlockNumber,734 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,735 priority: Option<schedule::Priority>,736 call: Box<<T as Config>::RuntimeCall>,737 ) -> DispatchResult {738 T::ScheduleOrigin::ensure_origin(origin.clone())?;739740 if priority.is_some() {741 T::PrioritySetOrigin::ensure_origin(origin.clone())?;742 }743744 let origin = <T as Config>::RuntimeOrigin::from(origin);745 Self::do_schedule(746 DispatchTime::After(after),747 maybe_periodic,748 priority.unwrap_or(LOWEST_PRIORITY),749 origin.caller().clone(),750 <ScheduledCall<T>>::new(*call)?,751 )?;752 Ok(())753 }754755 756 757 758 759 760 761 762 763 #[pallet::call_index(5)]764 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]765 pub fn schedule_named_after(766 origin: OriginFor<T>,767 id: TaskName,768 after: T::BlockNumber,769 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,770 priority: Option<schedule::Priority>,771 call: Box<<T as Config>::RuntimeCall>,772 ) -> DispatchResult {773 T::ScheduleOrigin::ensure_origin(origin.clone())?;774775 if priority.is_some() {776 T::PrioritySetOrigin::ensure_origin(origin.clone())?;777 }778779 let origin = <T as Config>::RuntimeOrigin::from(origin);780 Self::do_schedule_named(781 id,782 DispatchTime::After(after),783 maybe_periodic,784 priority.unwrap_or(LOWEST_PRIORITY),785 origin.caller().clone(),786 <ScheduledCall<T>>::new(*call)?,787 )?;788 Ok(())789 }790791 792 793 794 #[pallet::call_index(6)]795 #[pallet::weight(<T as Config>::WeightInfo::change_named_priority(T::MaxScheduledPerBlock::get()))]796 pub fn change_named_priority(797 origin: OriginFor<T>,798 id: TaskName,799 priority: schedule::Priority,800 ) -> DispatchResult {801 T::PrioritySetOrigin::ensure_origin(origin.clone())?;802 let origin = <T as Config>::RuntimeOrigin::from(origin);803 Self::do_change_named_priority(origin.caller().clone(), id, priority)804 }805 }806}807808impl<T: Config> Pallet<T> {809 810 811 812 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {813 let now = frame_system::Pallet::<T>::block_number();814815 let when = match when {816 DispatchTime::At(x) => x,817 818 819 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),820 };821822 if when <= now {823 return Err(Error::<T>::TargetBlockNumberInPast.into());824 }825826 Ok(when)827 }828829 830 831 832 833 834 835 fn mandatory_place_task(when: T::BlockNumber, what: ScheduledOf<T>) {836 Self::place_task(when, what, true).expect("mandatory place task always succeeds; qed");837 }838839 840 841 842 fn try_place_task(843 when: T::BlockNumber,844 what: ScheduledOf<T>,845 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {846 Self::place_task(when, what, false)847 }848849 850 851 852 853 fn place_task(854 mut when: T::BlockNumber,855 what: ScheduledOf<T>,856 is_mandatory: bool,857 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {858 let maybe_name = what.maybe_id;859 let index = Self::push_to_agenda(&mut when, what, is_mandatory)?;860 let address = (when, index);861 if let Some(name) = maybe_name {862 Lookup::<T>::insert(name, address)863 }864 Self::deposit_event(Event::Scheduled {865 when: address.0,866 index: address.1,867 });868 Ok(address)869 }870871 872 873 874 875 876 fn push_to_agenda(877 when: &mut T::BlockNumber,878 mut what: ScheduledOf<T>,879 is_mandatory: bool,880 ) -> Result<u32, DispatchError> {881 let mut agenda;882883 let index = loop {884 agenda = Agenda::<T>::get(*when);885886 match agenda.try_push(what) {887 Ok(index) => break index,888 Err(returned_what) if is_mandatory => {889 what = returned_what;890 when.saturating_inc();891 }892 Err(_) => return Err(<Error<T>>::AgendaIsExhausted.into()),893 }894 };895896 Agenda::<T>::insert(when, agenda);897 Ok(index)898 }899900 fn do_schedule(901 when: DispatchTime<T::BlockNumber>,902 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,903 priority: schedule::Priority,904 origin: T::PalletsOrigin,905 call: ScheduledCall<T>,906 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {907 let when = Self::resolve_time(when)?;908909 910 let maybe_periodic = maybe_periodic911 .filter(|p| p.1 > 1 && !p.0.is_zero())912 913 .map(|(p, c)| (p, c - 1));914 let task = Scheduled {915 maybe_id: None,916 priority,917 call,918 maybe_periodic,919 origin,920 _phantom: PhantomData,921 };922 Self::try_place_task(when, task)923 }924925 fn do_cancel(926 origin: Option<T::PalletsOrigin>,927 (when, index): TaskAddress<T::BlockNumber>,928 ) -> Result<(), DispatchError> {929 let scheduled = Agenda::<T>::try_mutate(930 when,931 |agenda| -> Result<Option<Scheduled<_, _, _, _, _>>, DispatchError> {932 let scheduled = match agenda.get(index) {933 Some(scheduled) => scheduled,934 None => return Ok(None),935 };936937 if let Some(ref o) = origin {938 if matches!(939 T::OriginPrivilegeCmp::cmp_privilege(o, &scheduled.origin),940 Some(Ordering::Less) | None941 ) {942 return Err(BadOrigin.into());943 }944 }945946 Ok(agenda.take(index))947 },948 )?;949 if let Some(s) = scheduled {950 T::Preimages::drop(&s.call);951952 if let Some(id) = s.maybe_id {953 Lookup::<T>::remove(id);954 }955 Self::deposit_event(Event::Canceled { when, index });956 Ok(())957 } else {958 Err(Error::<T>::NotFound.into())959 }960 }961962 fn do_schedule_named(963 id: TaskName,964 when: DispatchTime<T::BlockNumber>,965 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,966 priority: schedule::Priority,967 origin: T::PalletsOrigin,968 call: ScheduledCall<T>,969 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {970 971 if Lookup::<T>::contains_key(&id) {972 return Err(Error::<T>::FailedToSchedule.into());973 }974975 let when = Self::resolve_time(when)?;976977 978 let maybe_periodic = maybe_periodic979 .filter(|p| p.1 > 1 && !p.0.is_zero())980 981 .map(|(p, c)| (p, c - 1));982983 let task = Scheduled {984 maybe_id: Some(id),985 priority,986 call,987 maybe_periodic,988 origin,989 _phantom: Default::default(),990 };991 Self::try_place_task(when, task)992 }993994 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: TaskName) -> DispatchResult {995 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {996 if let Some((when, index)) = lookup.take() {997 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {998 let scheduled = match agenda.get(index) {999 Some(scheduled) => scheduled,1000 None => return Ok(()),1001 };10021003 if let Some(ref o) = origin {1004 if matches!(1005 T::OriginPrivilegeCmp::cmp_privilege(o, &scheduled.origin),1006 Some(Ordering::Less) | None1007 ) {1008 return Err(BadOrigin.into());1009 }1010 T::Preimages::drop(&scheduled.call);1011 }10121013 agenda.take(index);10141015 Ok(())1016 })?;1017 Self::deposit_event(Event::Canceled { when, index });1018 Ok(())1019 } else {1020 Err(Error::<T>::NotFound.into())1021 }1022 })1023 }10241025 fn do_change_named_priority(1026 origin: T::PalletsOrigin,1027 id: TaskName,1028 priority: schedule::Priority,1029 ) -> DispatchResult {1030 match Lookup::<T>::get(id) {1031 Some((when, index)) => Agenda::<T>::try_mutate(when, |agenda| {1032 let scheduled = match agenda.get_mut(index) {1033 Some(scheduled) => scheduled,1034 None => return Ok(()),1035 };10361037 if matches!(1038 T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin),1039 Some(Ordering::Less) | None1040 ) {1041 return Err(BadOrigin.into());1042 }10431044 scheduled.priority = priority;1045 Self::deposit_event(Event::PriorityChanged {1046 task: (when, index),1047 priority,1048 });10491050 Ok(())1051 }),1052 None => Err(Error::<T>::NotFound.into()),1053 }1054 }1055}10561057enum ServiceTaskError {1058 1059 Unavailable,1060 1061 Overweight,1062}1063use ServiceTaskError::*;106410651066pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {1067 1068 fn dispatch_call(1069 signer: Option<T::AccountId>,1070 function: <T as Config>::RuntimeCall,1071 ) -> Result<1072 Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,1073 TransactionValidityError,1074 >;1075}10761077impl<T: Config> Pallet<T> {1078 1079 fn service_agendas(weight: &mut WeightCounter, now: T::BlockNumber, max: u32) {1080 if !weight.check_accrue(T::WeightInfo::service_agendas_base()) {1081 return;1082 }10831084 let mut incomplete_since = now + One::one();1085 let mut when = IncompleteSince::<T>::take().unwrap_or(now);1086 let mut executed = 0;10871088 let max_items = T::MaxScheduledPerBlock::get();1089 let mut count_down = max;1090 let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items);1091 while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) {1092 if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) {1093 incomplete_since = incomplete_since.min(when);1094 }1095 when.saturating_inc();1096 count_down.saturating_dec();1097 }1098 incomplete_since = incomplete_since.min(when);1099 if incomplete_since <= now {1100 IncompleteSince::<T>::put(incomplete_since);1101 }1102 }11031104 1105 1106 fn service_agenda(1107 weight: &mut WeightCounter,1108 executed: &mut u32,1109 now: T::BlockNumber,1110 when: T::BlockNumber,1111 max: u32,1112 ) -> bool {1113 let mut agenda = Agenda::<T>::get(when);1114 let mut ordered = agenda1115 .iter()1116 .enumerate()1117 .filter_map(|(index, maybe_item)| {1118 maybe_item1119 .as_ref()1120 .map(|item| (index as u32, item.priority))1121 })1122 .collect::<Vec<_>>();1123 ordered.sort_by_key(|k| k.1);1124 let within_limit =1125 weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32));1126 debug_assert!(1127 within_limit,1128 "weight limit should have been checked in advance"1129 );11301131 1132 let mut postponed = (ordered.len() as u32).saturating_sub(max);1133 1134 let mut dropped = 0;11351136 for (agenda_index, _) in ordered.into_iter().take(max as usize) {1137 let task = match agenda.take(agenda_index).take() {1138 None => continue,1139 Some(t) => t,1140 };1141 let base_weight = MarginalWeightInfo::<T>::service_task(1142 task.call.lookup_len().map(|x| x as usize),1143 task.maybe_id.is_some(),1144 task.maybe_periodic.is_some(),1145 );1146 if !weight.can_accrue(base_weight) {1147 postponed += 1;1148 break;1149 }1150 let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task);1151 match result {1152 Err((Unavailable, slot)) => {1153 dropped += 1;1154 agenda.set_slot(agenda_index, slot);1155 }1156 Err((Overweight, slot)) => {1157 postponed += 1;1158 agenda.set_slot(agenda_index, slot);1159 }1160 Ok(()) => {1161 *executed += 1;1162 }1163 };1164 }1165 if postponed > 0 || dropped > 0 {1166 Agenda::<T>::insert(when, agenda);1167 } else {1168 Agenda::<T>::remove(when);1169 }1170 postponed == 01171 }11721173 1174 1175 1176 1177 1178 1179 fn service_task(1180 weight: &mut WeightCounter,1181 now: T::BlockNumber,1182 when: T::BlockNumber,1183 agenda_index: u32,1184 is_first: bool,1185 mut task: ScheduledOf<T>,1186 ) -> Result<(), (ServiceTaskError, Option<ScheduledOf<T>>)> {1187 let (call, lookup_len) = match T::Preimages::peek(&task.call) {1188 Ok(c) => c,1189 Err(_) => {1190 if let Some(ref id) = task.maybe_id {1191 Lookup::<T>::remove(id);1192 }11931194 return Err((Unavailable, Some(task)));1195 }1196 };11971198 weight.check_accrue(MarginalWeightInfo::<T>::service_task(1199 lookup_len.map(|x| x as usize),1200 task.maybe_id.is_some(),1201 task.maybe_periodic.is_some(),1202 ));12031204 match Self::execute_dispatch(weight, task.origin.clone(), call) {1205 Err(Unavailable) => {1206 debug_assert!(false, "Checked to exist with `peek`");12071208 if let Some(ref id) = task.maybe_id {1209 Lookup::<T>::remove(id);1210 }12111212 Self::deposit_event(Event::CallUnavailable {1213 task: (when, agenda_index),1214 id: task.maybe_id,1215 });1216 Err((Unavailable, Some(task)))1217 }1218 Err(Overweight) if is_first && !Self::is_runtime_upgraded() => {1219 T::Preimages::drop(&task.call);12201221 if let Some(ref id) = task.maybe_id {1222 Lookup::<T>::remove(id);1223 }12241225 Self::deposit_event(Event::PermanentlyOverweight {1226 task: (when, agenda_index),1227 id: task.maybe_id,1228 });1229 Err((Unavailable, Some(task)))1230 }1231 Err(Overweight) => {1232 1233 Err((Overweight, Some(task)))1234 }1235 Ok(result) => {1236 Self::deposit_event(Event::Dispatched {1237 task: (when, agenda_index),1238 id: task.maybe_id,1239 result,1240 });12411242 let is_canceled = task1243 .maybe_id1244 .as_ref()1245 .map(|id| !Lookup::<T>::contains_key(id))1246 .unwrap_or(false);12471248 match &task.maybe_periodic {1249 &Some((period, count)) if !is_canceled => {1250 if count > 1 {1251 task.maybe_periodic = Some((period, count - 1));1252 } else {1253 task.maybe_periodic = None;1254 }1255 let wake = now.saturating_add(period);1256 Self::mandatory_place_task(wake, task);1257 }1258 _ => {1259 if let Some(ref id) = task.maybe_id {1260 Lookup::<T>::remove(id);1261 }12621263 T::Preimages::drop(&task.call)1264 }1265 }1266 Ok(())1267 }1268 }1269 }12701271 fn is_runtime_upgraded() -> bool {1272 let last = system::LastRuntimeUpgrade::<T>::get();1273 let current = T::Version::get();12741275 last.map(|v| v.was_upgraded(¤t)).unwrap_or(true)1276 }12771278 1279 1280 1281 1282 1283 1284 fn execute_dispatch(1285 weight: &mut WeightCounter,1286 origin: T::PalletsOrigin,1287 call: <T as Config>::RuntimeCall,1288 ) -> Result<DispatchResult, ServiceTaskError> {1289 let dispatch_origin: <T as Config>::RuntimeOrigin = origin.into();1290 let base_weight = match dispatch_origin.clone().as_signed() {1291 Some(_) => T::WeightInfo::execute_dispatch_signed(),1292 _ => T::WeightInfo::execute_dispatch_unsigned(),1293 };1294 let call_weight = call.get_dispatch_info().weight;1295 1296 let max_weight = base_weight.saturating_add(call_weight);12971298 if !weight.can_accrue(max_weight) {1299 return Err(Overweight);1300 }13011302 let ensured_origin = T::ScheduleOrigin::ensure_origin(dispatch_origin.into());13031304 let r = match ensured_origin {1305 Ok(ScheduledEnsureOriginSuccess::Root) => {1306 Ok(call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()))1307 }1308 Ok(ScheduledEnsureOriginSuccess::Signed(sender)) => {1309 1310 1311 T::CallExecutor::dispatch_call(Some(sender), call)1312 }1313 Err(e) => Ok(Err(e.into())),1314 };13151316 let (maybe_actual_call_weight, result) = match r {1317 Ok(result) => match result {1318 Ok(post_info) => (post_info.actual_weight, Ok(())),1319 Err(error_and_info) => (1320 error_and_info.post_info.actual_weight,1321 Err(error_and_info.error),1322 ),1323 },1324 Err(_) => {1325 log::error!(1326 target: "runtime::scheduler",1327 "Warning: Scheduler has failed to execute a post-dispatch transaction. \1328 This block might have become invalid.");1329 (None, Err(DispatchError::CannotLookup))1330 }1331 };1332 let call_weight = maybe_actual_call_weight.unwrap_or(call_weight);1333 weight.check_accrue(base_weight);1334 weight.check_accrue(call_weight);1335 Ok(result)1336 }1337}