difftreelog
feature: add benchmarks for scheduler v2
in: master
3 files changed
pallets/scheduler-v2/src/benchmarking.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler-v2/src/benchmarking.rs
@@ -0,0 +1,315 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network 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.
+
+// Unique Network 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 Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+// Original license:
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Scheduler pallet benchmarking.
+
+use super::*;
+use frame_benchmarking::{account, benchmarks};
+use frame_support::{
+ ensure,
+ traits::{schedule::Priority, PreimageRecipient},
+};
+use frame_system::RawOrigin;
+use sp_std::{prelude::*, vec};
+
+use crate::{Pallet as Scheduler, ScheduledCall, EncodedCall};
+use frame_system::Call as SystemCall;
+
+const SEED: u32 = 0;
+
+const BLOCK_NUMBER: u32 = 2;
+
+type SystemOrigin<T> = <T as frame_system::Config>::Origin;
+
+/// Add `n` items to the schedule.
+///
+/// For `resolved`:
+/// - `
+/// - `None`: aborted (hash without preimage)
+/// - `Some(true)`: hash resolves into call if possible, plain call otherwise
+/// - `Some(false)`: plain call
+fn fill_schedule<T: Config>(when: T::BlockNumber, n: u32) -> Result<(), &'static str> {
+ let t = DispatchTime::At(when);
+ let origin: <T as Config>::PalletsOrigin = frame_system::RawOrigin::Root.into();
+ for i in 0..n {
+ let call = make_call::<T>(None);
+ let period = Some(((i + 100).into(), 100));
+ let name = u32_to_name(i);
+ Scheduler::<T>::do_schedule_named(name, t, period, 0, origin.clone(), call)?;
+ }
+ ensure!(Agenda::<T>::get(when).len() == n as usize, "didn't fill schedule");
+ Ok(())
+}
+
+fn u32_to_name(i: u32) -> TaskName {
+ i.using_encoded(blake2_256)
+}
+
+fn make_task<T: Config>(
+ periodic: bool,
+ named: bool,
+ signed: bool,
+ maybe_lookup_len: Option<u32>,
+ priority: Priority,
+) -> ScheduledOf<T> {
+ let call = make_call::<T>(maybe_lookup_len);
+ let maybe_periodic = match periodic {
+ true => Some((100u32.into(), 100)),
+ false => None,
+ };
+ let maybe_id = match named {
+ true => Some(u32_to_name(0)),
+ false => None,
+ };
+ let origin = make_origin::<T>(signed);
+ Scheduled { maybe_id, priority, call, maybe_periodic, origin, _phantom: PhantomData }
+}
+
+fn bounded<T: Config>(len: u32) -> Option<ScheduledCall<T>> {
+ let call =
+ <<T as Config>::Call>::from(SystemCall::remark { remark: vec![0; len as usize] });
+ ScheduledCall::new(call).ok()
+}
+
+fn make_call<T: Config>(maybe_lookup_len: Option<u32>) -> ScheduledCall<T> {
+ let bound = EncodedCall::bound() as u32;
+ let mut len = match maybe_lookup_len {
+ Some(len) => len.min(<T::Preimages as PreimageRecipient<T::Hash>>::MaxSize::get() - 2).max(bound) - 3,
+ None => bound.saturating_sub(4),
+ };
+
+ loop {
+ let c = match bounded::<T>(len) {
+ Some(x) => x,
+ None => {
+ len -= 1;
+ continue
+ },
+ };
+ if c.lookup_needed() == maybe_lookup_len.is_some() {
+ break c
+ }
+ if maybe_lookup_len.is_some() {
+ len += 1;
+ } else {
+ if len > 0 {
+ len -= 1;
+ } else {
+ break c
+ }
+ }
+ }
+}
+
+fn make_origin<T: Config>(signed: bool) -> <T as Config>::PalletsOrigin {
+ match signed {
+ true => frame_system::RawOrigin::Signed(account("origin", 0, SEED)).into(),
+ false => frame_system::RawOrigin::Root.into(),
+ }
+}
+
+fn dummy_counter() -> WeightCounter {
+ WeightCounter { used: Weight::zero(), limit: Weight::MAX }
+}
+
+benchmarks! {
+ // `service_agendas` when no work is done.
+ service_agendas_base {
+ let now = T::BlockNumber::from(BLOCK_NUMBER);
+ IncompleteSince::<T>::put(now - One::one());
+ }: {
+ Scheduler::<T>::service_agendas(&mut dummy_counter(), now, 0);
+ } verify {
+ assert_eq!(IncompleteSince::<T>::get(), Some(now - One::one()));
+ }
+
+ // `service_agenda` when no work is done.
+ service_agenda_base {
+ let now = BLOCK_NUMBER.into();
+ let s in 0 .. T::MaxScheduledPerBlock::get();
+ fill_schedule::<T>(now, s)?;
+ let mut executed = 0;
+ }: {
+ Scheduler::<T>::service_agenda(&mut dummy_counter(), &mut executed, now, now, 0);
+ } verify {
+ assert_eq!(executed, 0);
+ }
+
+ // `service_task` when the task is a non-periodic, non-named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_base {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::<T>(false, false, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::<T>::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ //assert_eq!(result, Ok(()));
+ }
+
+ // `service_task` when the task is a non-periodic, non-named, fetched call (with a known
+ // preimage length) and which is not dispatched (e.g. due to being overweight).
+ service_task_fetched {
+ let s in (EncodedCall::bound() as u32) .. (<T::Preimages as PreimageRecipient<T::Hash>>::MaxSize::get());
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::<T>(false, false, false, Some(s), 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::<T>::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `service_task` when the task is a non-periodic, named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_named {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::<T>(false, true, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::<T>::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `service_task` when the task is a periodic, non-named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_periodic {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::<T>(true, false, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::<T>::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight.
+ execute_dispatch_signed {
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX };
+ let origin = make_origin::<T>(true);
+ let call = T::Preimages::realize(&make_call::<T>(None)).unwrap().0;
+ }: {
+ assert!(Scheduler::<T>::execute_dispatch(&mut counter, origin, call).is_ok());
+ }
+ verify {
+ }
+
+ // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight.
+ execute_dispatch_unsigned {
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX };
+ let origin = make_origin::<T>(false);
+ let call = T::Preimages::realize(&make_call::<T>(None)).unwrap().0;
+ }: {
+ assert!(Scheduler::<T>::execute_dispatch(&mut counter, origin, call).is_ok());
+ }
+ verify {
+ }
+
+ schedule {
+ let s in 0 .. (T::MaxScheduledPerBlock::get() - 1);
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::<T>::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::<T>(when, s)?;
+ assert_eq!(Agenda::<T>::get(when).len(), s as usize);
+ let schedule_origin = T::ScheduleOrigin::successful_origin();
+ }: _<SystemOrigin<T>>(schedule_origin, when, 0)
+ verify {
+ ensure!(
+ Lookup::<T>::get(u32_to_name(0)).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::<T>::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ schedule_named {
+ let s in 0 .. (T::MaxScheduledPerBlock::get() - 1);
+ let id = u32_to_name(s);
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, id, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::<T>::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel_named {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, u32_to_name(0))
+ verify {
+ ensure!(
+ Lookup::<T>::get(u32_to_name(0)).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::<T>::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ // impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test);
+}
pallets/scheduler-v2/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// This file is part of Substrate.1920// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.21// SPDX-License-Identifier: Apache-2.02223// Licensed under the Apache License, Version 2.0 (the "License");24// you may not use this file except in compliance with the License.25// You may obtain a copy of the License at26//27// http://www.apache.org/licenses/LICENSE-2.028//29// Unless required by applicable law or agreed to in writing, software30// distributed under the License is distributed on an "AS IS" BASIS,31// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.32// See the License for the specific language governing permissions and33// limitations under the License.3435//! # Scheduler36//! A Pallet for scheduling dispatches.37//!38//! - [`Config`]39//! - [`Call`]40//! - [`Pallet`]41//!42//! ## Overview43//!44//! This Pallet exposes capabilities for scheduling dispatches to occur at a45//! specified block number or at a specified period. These scheduled dispatches46//! may be named or anonymous and may be canceled.47//!48//! **NOTE:** The scheduled calls will be dispatched with the default filter49//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin50//! except root which will get no filter. And not the filter contained in origin51//! use to call `fn schedule`.52//!53//! If a call is scheduled using proxy or whatever mecanism which adds filter,54//! then those filter will not be used when dispatching the schedule call.55//!56//! ## Interface57//!58//! ### Dispatchable Functions59//!60//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and61//! with a specified priority.62//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.63//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter64//! that can be used for identification.65//! * `cancel_named` - the named complement to the cancel function.6667// Ensure we're `no_std` when compiling for Wasm.68#![cfg_attr(not(feature = "std"), no_std)]6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72#[cfg(test)]73mod mock;74#[cfg(test)]75mod tests;76pub mod weights;7778use codec::{Codec, Decode, Encode, MaxEncodedLen};79use frame_support::{80 dispatch::{81 DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter, RawOrigin,82 },83 ensure,84 traits::{85 schedule::{self, DispatchTime},86 EnsureOrigin, Get, IsType, OriginTrait,87 PalletInfoAccess, PrivilegeCmp, StorageVersion,88 PreimageProvider, PreimageRecipient, ConstU32,89 },90 weights::Weight,91};9293use frame_system::{self as system};94use scale_info::TypeInfo;95use sp_io::hashing::blake2_256;96use sp_runtime::{97 traits::{BadOrigin, One, Saturating, Zero, Hash},98 BoundedVec, RuntimeDebug,99};100use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};101pub use weights::WeightInfo;102103pub use pallet::*;104105/// Just a simple index for naming period tasks.106pub type PeriodicIndex = u32;107/// The location of a scheduled task that can be used to remove it.108pub type TaskAddress<BlockNumber> = (BlockNumber, u32);109110pub type EncodedCall = BoundedVec<u8, ConstU32<128>>;111112#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]113#[scale_info(skip_type_params(T))]114pub enum ScheduledCall<T: Config> {115 Inline(EncodedCall),116 PreimageLookup {117 hash: T::Hash,118 unbounded_len: u32,119 },120}121122impl<T: Config> ScheduledCall<T> {123 pub fn new(call: <T as Config>::Call) -> Result<Self, DispatchError> {124 let encoded = call.encode();125 let len = encoded.len();126127 match EncodedCall::try_from(encoded.clone()) {128 Ok(bounded) => Ok(Self::Inline(bounded)),129 Err(_) => {130 let hash = <T as system::Config>::Hashing::hash_of(&encoded);131 <T as Config>::Preimages::note_preimage(encoded.try_into().map_err(|_| <Error<T>>::TooBigScheduledCall)?);132133 Ok(Self::PreimageLookup { hash, unbounded_len: len as u32 })134 }135 }136 }137138 /// The maximum length of the lookup that is needed to peek `Self`.139 pub fn lookup_len(&self) -> Option<u32> {140 match self {141 Self::Inline(..) => None,142 Self::PreimageLookup { unbounded_len, .. } => Some(*unbounded_len),143 }144 }145146 fn decode(mut data: &[u8]) -> Result<<T as Config>::Call, DispatchError> {147 <T as Config>::Call::decode(&mut data)148 .map_err(|_| <Error<T>>::ScheduledCallCorrupted.into())149 }150}151152pub trait SchedulerPreimages<T: Config>: PreimageRecipient<T::Hash> {153 fn drop(call: &ScheduledCall<T>);154155 fn peek(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError>;156}157158impl<T: Config, PP: PreimageRecipient<T::Hash>> SchedulerPreimages<T> for PP {159 fn drop(call: &ScheduledCall<T>) {160 match call {161 ScheduledCall::Inline(_) => {},162 ScheduledCall::PreimageLookup { hash, .. } => Self::unrequest_preimage(hash),163 }164 }165166 fn peek(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError> {167 match call {168 ScheduledCall::Inline(data) => Ok((ScheduledCall::<T>::decode(data)?, None)),169 ScheduledCall::PreimageLookup { hash, unbounded_len } => {170 let (preimage, len) = Self::get_preimage(hash)171 .ok_or(<Error<T>>::PreimageNotFound)172 .map(|preimage| (preimage, *unbounded_len))?;173174 Ok((ScheduledCall::<T>::decode(preimage.as_slice())?, Some(len)))175 },176 }177 }178}179180pub type TaskName = [u8; 32];181182/// Information regarding an item to be executed in the future.183#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]184#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]185pub struct Scheduled<Name, Call, BlockNumber, PalletsOrigin, AccountId> {186 /// The unique identity for this task, if there is one.187 maybe_id: Option<Name>,188189 /// This task's priority.190 priority: schedule::Priority,191192 /// The call to be dispatched.193 call: Call,194195 /// If the call is periodic, then this points to the information concerning that.196 maybe_periodic: Option<schedule::Period<BlockNumber>>,197198 /// The origin with which to dispatch the call.199 origin: PalletsOrigin,200 _phantom: PhantomData<AccountId>,201}202203pub type ScheduledOf<T> = Scheduled<204 TaskName,205 ScheduledCall<T>,206 <T as frame_system::Config>::BlockNumber,207 <T as Config>::PalletsOrigin,208 <T as frame_system::Config>::AccountId,209>;210211struct WeightCounter {212 used: Weight,213 limit: Weight,214}215216impl WeightCounter {217 fn check_accrue(&mut self, w: Weight) -> bool {218 let test = self.used.saturating_add(w);219 if test > self.limit {220 false221 } else {222 self.used = test;223 true224 }225 }226227 fn can_accrue(&mut self, w: Weight) -> bool {228 self.used.saturating_add(w) <= self.limit229 }230}231232pub(crate) trait MarginalWeightInfo: WeightInfo {233 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {234 let base = Self::service_task_base();235 let mut total = match maybe_lookup_len {236 None => base,237 Some(l) => Self::service_task_fetched(l as u32),238 };239 if named {240 total.saturating_accrue(Self::service_task_named().saturating_sub(base));241 }242 if periodic {243 total.saturating_accrue(Self::service_task_periodic().saturating_sub(base));244 }245 total246 }247}248249impl<T: WeightInfo> MarginalWeightInfo for T {}250251#[frame_support::pallet]252pub mod pallet {253 use super::*;254 use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*};255 use system::pallet_prelude::*;256257 /// The current storage version.258 const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);259260 #[pallet::pallet]261 #[pallet::generate_store(pub(super) trait Store)]262 #[pallet::storage_version(STORAGE_VERSION)]263 pub struct Pallet<T>(_);264265 #[pallet::config]266 pub trait Config: frame_system::Config {267 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;268269 /// The aggregated origin which the dispatch will take.270 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>271 + From<Self::PalletsOrigin>272 + IsType<<Self as system::Config>::Origin>273 + Clone;274275 /// The caller origin, overarching type of all pallets origins.276 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>>277 + Codec278 + Clone279 + Eq280 + TypeInfo281 + MaxEncodedLen;282283 /// The aggregated call type.284 type Call: Parameter285 + Dispatchable<286 Origin = <Self as Config>::Origin,287 PostInfo = PostDispatchInfo,288 > + GetDispatchInfo289 + From<system::Call<Self>>;290291 /// The maximum weight that may be scheduled per block for any dispatchables.292 #[pallet::constant]293 type MaximumWeight: Get<Weight>;294295 /// Required origin to schedule or cancel calls.296 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;297298 /// Compare the privileges of origins.299 ///300 /// This will be used when canceling a task, to ensure that the origin that tries301 /// to cancel has greater or equal privileges as the origin that created the scheduled task.302 ///303 /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can304 /// be used. This will only check if two given origins are equal.305 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;306307 /// The maximum number of scheduled calls in the queue for a single block.308 #[pallet::constant]309 type MaxScheduledPerBlock: Get<u32>;310311 /// Weight information for extrinsics in this pallet.312 type WeightInfo: WeightInfo;313314 /// The preimage provider with which we look up call hashes to get the call.315 type Preimages: SchedulerPreimages<Self>;316 }317318 #[pallet::storage]319 pub type IncompleteSince<T: Config> = StorageValue<_, T::BlockNumber>;320321 /// Items to be executed, indexed by the block number that they should be executed on.322 #[pallet::storage]323 pub type Agenda<T: Config> = StorageMap<324 _,325 Twox64Concat,326 T::BlockNumber,327 BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,328 ValueQuery,329 >;330331 /// Lookup from a name to the block number and index of the task.332 #[pallet::storage]333 pub(crate) type Lookup<T: Config> =334 StorageMap<_, Twox64Concat, TaskName, TaskAddress<T::BlockNumber>>;335336 /// Events type.337 #[pallet::event]338 #[pallet::generate_deposit(pub(super) fn deposit_event)]339 pub enum Event<T: Config> {340 /// Scheduled some task.341 Scheduled { when: T::BlockNumber, index: u32 },342 /// Canceled some task.343 Canceled { when: T::BlockNumber, index: u32 },344 /// Dispatched some task.345 Dispatched {346 task: TaskAddress<T::BlockNumber>,347 id: Option<[u8; 32]>,348 result: DispatchResult,349 },350 /// The call for the provided hash was not found so the task has been aborted.351 CallUnavailable { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },352 /// The given task was unable to be renewed since the agenda is full at that block.353 PeriodicFailed { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },354 /// The given task can never be executed since it is overweight.355 PermanentlyOverweight { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },356 }357358 #[pallet::error]359 pub enum Error<T> {360 /// Failed to schedule a call361 FailedToSchedule,362 /// There is no place for a new task in the agenda363 AgendaIsExhausted,364 /// Scheduled call is corrupted365 ScheduledCallCorrupted,366 /// Scheduled call preimage is not found367 PreimageNotFound,368 /// Scheduled call is too big369 TooBigScheduledCall,370 /// Cannot find the scheduled call.371 NotFound,372 /// Given target block number is in the past.373 TargetBlockNumberInPast,374 /// Reschedule failed because it does not change scheduled time.375 RescheduleNoChange,376 /// Attempt to use a non-named function on a named task.377 Named,378 }379380 #[pallet::hooks]381 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {382 /// Execute the scheduled calls383 fn on_initialize(now: T::BlockNumber) -> Weight {384 let mut weight_counter =385 WeightCounter { used: Weight::zero(), limit: T::MaximumWeight::get() };386 Self::service_agendas(&mut weight_counter, now, u32::max_value());387 weight_counter.used388 }389 }390391 #[pallet::call]392 impl<T: Config> Pallet<T> {393 /// Anonymously schedule a task.394 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]395 pub fn schedule(396 origin: OriginFor<T>,397 when: T::BlockNumber,398 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,399 priority: schedule::Priority,400 call: Box<<T as Config>::Call>,401 ) -> DispatchResult {402 T::ScheduleOrigin::ensure_origin(origin.clone())?;403 let origin = <T as Config>::Origin::from(origin);404 Self::do_schedule(405 DispatchTime::At(when),406 maybe_periodic,407 priority,408 origin.caller().clone(),409 <ScheduledCall<T>>::new(*call)?,410 )?;411 Ok(())412 }413414 /// Cancel an anonymously scheduled task.415 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]416 pub fn cancel(origin: OriginFor<T>, when: T::BlockNumber, index: u32) -> DispatchResult {417 T::ScheduleOrigin::ensure_origin(origin.clone())?;418 let origin = <T as Config>::Origin::from(origin);419 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;420 Ok(())421 }422423 /// Schedule a named task.424 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]425 pub fn schedule_named(426 origin: OriginFor<T>,427 id: TaskName,428 when: T::BlockNumber,429 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,430 priority: schedule::Priority,431 call: Box<<T as Config>::Call>,432 ) -> DispatchResult {433 T::ScheduleOrigin::ensure_origin(origin.clone())?;434 let origin = <T as Config>::Origin::from(origin);435 Self::do_schedule_named(436 id,437 DispatchTime::At(when),438 maybe_periodic,439 priority,440 origin.caller().clone(),441 <ScheduledCall<T>>::new(*call)?,442 )?;443 Ok(())444 }445446 /// Cancel a named scheduled task.447 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]448 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {449 T::ScheduleOrigin::ensure_origin(origin.clone())?;450 let origin = <T as Config>::Origin::from(origin);451 Self::do_cancel_named(Some(origin.caller().clone()), id)?;452 Ok(())453 }454455 /// Anonymously schedule a task after a delay.456 ///457 /// # <weight>458 /// Same as [`schedule`].459 /// # </weight>460 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]461 pub fn schedule_after(462 origin: OriginFor<T>,463 after: T::BlockNumber,464 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,465 priority: schedule::Priority,466 call: Box<<T as Config>::Call>,467 ) -> DispatchResult {468 T::ScheduleOrigin::ensure_origin(origin.clone())?;469 let origin = <T as Config>::Origin::from(origin);470 Self::do_schedule(471 DispatchTime::After(after),472 maybe_periodic,473 priority,474 origin.caller().clone(),475 <ScheduledCall<T>>::new(*call)?,476 )?;477 Ok(())478 }479480 /// Schedule a named task after a delay.481 ///482 /// # <weight>483 /// Same as [`schedule_named`](Self::schedule_named).484 /// # </weight>485 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]486 pub fn schedule_named_after(487 origin: OriginFor<T>,488 id: TaskName,489 after: T::BlockNumber,490 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,491 priority: schedule::Priority,492 call: Box<<T as Config>::Call>,493 ) -> DispatchResult {494 T::ScheduleOrigin::ensure_origin(origin.clone())?;495 let origin = <T as Config>::Origin::from(origin);496 Self::do_schedule_named(497 id,498 DispatchTime::After(after),499 maybe_periodic,500 priority,501 origin.caller().clone(),502 <ScheduledCall<T>>::new(*call)?,503 )?;504 Ok(())505 }506 }507}508509impl<T: Config> Pallet<T> {510 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {511 let now = frame_system::Pallet::<T>::block_number();512513 let when = match when {514 DispatchTime::At(x) => x,515 // The current block has already completed it's scheduled tasks, so516 // Schedule the task at lest one block after this current block.517 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),518 };519520 if when <= now {521 return Err(Error::<T>::TargetBlockNumberInPast.into())522 }523524 Ok(when)525 }526527 fn place_task(528 when: T::BlockNumber,529 what: ScheduledOf<T>,530 ) -> Result<TaskAddress<T::BlockNumber>, (DispatchError, ScheduledOf<T>)> {531 let maybe_name = what.maybe_id;532 let index = Self::push_to_agenda(when, what)?;533 let address = (when, index);534 if let Some(name) = maybe_name {535 Lookup::<T>::insert(name, address)536 }537 Self::deposit_event(Event::Scheduled { when: address.0, index: address.1 });538 Ok(address)539 }540541 fn push_to_agenda(542 when: T::BlockNumber,543 what: ScheduledOf<T>,544 ) -> Result<u32, (DispatchError, ScheduledOf<T>)> {545 let mut agenda = Agenda::<T>::get(when);546 let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() {547 // will always succeed due to the above check.548 let _ = agenda.try_push(Some(what));549 agenda.len() as u32 - 1550 } else {551 if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) {552 agenda[hole_index] = Some(what);553 hole_index as u32554 } else {555 return Err((<Error<T>>::AgendaIsExhausted.into(), what))556 }557 };558 Agenda::<T>::insert(when, agenda);559 Ok(index)560 }561562 fn do_schedule(563 when: DispatchTime<T::BlockNumber>,564 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,565 priority: schedule::Priority,566 origin: T::PalletsOrigin,567 call: ScheduledCall<T>,568 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {569 let when = Self::resolve_time(when)?;570571 // sanitize maybe_periodic572 let maybe_periodic = maybe_periodic573 .filter(|p| p.1 > 1 && !p.0.is_zero())574 // Remove one from the number of repetitions since we will schedule one now.575 .map(|(p, c)| (p, c - 1));576 let task = Scheduled {577 maybe_id: None,578 priority,579 call,580 maybe_periodic,581 origin,582 _phantom: PhantomData,583 };584 Self::place_task(when, task).map_err(|x| x.0)585 }586587 fn do_cancel(588 origin: Option<T::PalletsOrigin>,589 (when, index): TaskAddress<T::BlockNumber>,590 ) -> Result<(), DispatchError> {591 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {592 agenda.get_mut(index as usize).map_or(593 Ok(None),594 |s| -> Result<Option<Scheduled<_, _, _, _, _>>, DispatchError> {595 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {596 if matches!(597 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),598 Some(Ordering::Less) | None599 ) {600 return Err(BadOrigin.into())601 }602 };603 Ok(s.take())604 },605 )606 })?;607 if let Some(s) = scheduled {608 T::Preimages::drop(&s.call);609610 if let Some(id) = s.maybe_id {611 Lookup::<T>::remove(id);612 }613 Self::deposit_event(Event::Canceled { when, index });614 Ok(())615 } else {616 return Err(Error::<T>::NotFound.into())617 }618 }619620 fn do_schedule_named(621 id: TaskName,622 when: DispatchTime<T::BlockNumber>,623 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,624 priority: schedule::Priority,625 origin: T::PalletsOrigin,626 call: ScheduledCall<T>,627 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {628 // ensure id it is unique629 if Lookup::<T>::contains_key(&id) {630 return Err(Error::<T>::FailedToSchedule.into())631 }632633 let when = Self::resolve_time(when)?;634635 // sanitize maybe_periodic636 let maybe_periodic = maybe_periodic637 .filter(|p| p.1 > 1 && !p.0.is_zero())638 // Remove one from the number of repetitions since we will schedule one now.639 .map(|(p, c)| (p, c - 1));640641 let task = Scheduled {642 maybe_id: Some(id),643 priority,644 call,645 maybe_periodic,646 origin,647 _phantom: Default::default(),648 };649 Self::place_task(when, task).map_err(|x| x.0)650 }651652 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: TaskName) -> DispatchResult {653 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {654 if let Some((when, index)) = lookup.take() {655 let i = index as usize;656 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {657 if let Some(s) = agenda.get_mut(i) {658 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {659 if matches!(660 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),661 Some(Ordering::Less) | None662 ) {663 return Err(BadOrigin.into())664 }665 T::Preimages::drop(&s.call);666 }667 *s = None;668 }669 Ok(())670 })?;671 Self::deposit_event(Event::Canceled { when, index });672 Ok(())673 } else {674 return Err(Error::<T>::NotFound.into())675 }676 })677 }678}679680enum ServiceTaskError {681 /// Could not be executed due to missing preimage.682 Unavailable,683 /// Could not be executed due to weight limitations.684 Overweight,685}686use ServiceTaskError::*;687688impl<T: Config> Pallet<T> {689 /// Service up to `max` agendas queue starting from earliest incompletely executed agenda.690 fn service_agendas(weight: &mut WeightCounter, now: T::BlockNumber, max: u32) {691 if !weight.check_accrue(T::WeightInfo::service_agendas_base()) {692 return693 }694695 let mut incomplete_since = now + One::one();696 let mut when = IncompleteSince::<T>::take().unwrap_or(now);697 let mut executed = 0;698699 let max_items = T::MaxScheduledPerBlock::get();700 let mut count_down = max;701 let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items);702 while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) {703 if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) {704 incomplete_since = incomplete_since.min(when);705 }706 when.saturating_inc();707 count_down.saturating_dec();708 }709 incomplete_since = incomplete_since.min(when);710 if incomplete_since <= now {711 IncompleteSince::<T>::put(incomplete_since);712 }713 }714715 /// Returns `true` if the agenda was fully completed, `false` if it should be revisited at a716 /// later block.717 fn service_agenda(718 weight: &mut WeightCounter,719 executed: &mut u32,720 now: T::BlockNumber,721 when: T::BlockNumber,722 max: u32,723 ) -> bool {724 let mut agenda = Agenda::<T>::get(when);725 let mut ordered = agenda726 .iter()727 .enumerate()728 .filter_map(|(index, maybe_item)| {729 maybe_item.as_ref().map(|item| (index as u32, item.priority))730 })731 .collect::<Vec<_>>();732 ordered.sort_by_key(|k| k.1);733 let within_limit =734 weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32));735 debug_assert!(within_limit, "weight limit should have been checked in advance");736737 // Items which we know can be executed and have postponed for execution in a later block.738 let mut postponed = (ordered.len() as u32).saturating_sub(max);739 // Items which we don't know can ever be executed.740 let mut dropped = 0;741742 for (agenda_index, _) in ordered.into_iter().take(max as usize) {743 let task = match agenda[agenda_index as usize].take() {744 None => continue,745 Some(t) => t,746 };747 let base_weight = T::WeightInfo::service_task(748 task.call.lookup_len().map(|x| x as usize),749 task.maybe_id.is_some(),750 task.maybe_periodic.is_some(),751 );752 if !weight.can_accrue(base_weight) {753 postponed += 1;754 break755 }756 let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task);757 agenda[agenda_index as usize] = match result {758 Err((Unavailable, slot)) => {759 dropped += 1;760 slot761 },762 Err((Overweight, slot)) => {763 postponed += 1;764 slot765 },766 Ok(()) => {767 *executed += 1;768 None769 },770 };771 }772 if postponed > 0 || dropped > 0 {773 Agenda::<T>::insert(when, agenda);774 } else {775 Agenda::<T>::remove(when);776 }777 postponed == 0778 }779780 /// Service (i.e. execute) the given task, being careful not to overflow the `weight` counter.781 ///782 /// This involves:783 /// - removing and potentially replacing the `Lookup` entry for the task.784 /// - realizing the task's call which can include a preimage lookup.785 /// - Rescheduling the task for execution in a later agenda if periodic.786 fn service_task(787 weight: &mut WeightCounter,788 now: T::BlockNumber,789 when: T::BlockNumber,790 agenda_index: u32,791 is_first: bool,792 mut task: ScheduledOf<T>,793 ) -> Result<(), (ServiceTaskError, Option<ScheduledOf<T>>)> {794 if let Some(ref id) = task.maybe_id {795 Lookup::<T>::remove(id);796 }797798 let (call, lookup_len) = match T::Preimages::peek(&task.call) {799 Ok(c) => c,800 Err(_) => return Err((Unavailable, Some(task))),801 };802803 weight.check_accrue(T::WeightInfo::service_task(804 lookup_len.map(|x| x as usize),805 task.maybe_id.is_some(),806 task.maybe_periodic.is_some(),807 ));808809 match Self::execute_dispatch(weight, task.origin.clone(), call) {810 Err(Unavailable) => {811 debug_assert!(false, "Checked to exist with `peek`");812 Self::deposit_event(Event::CallUnavailable {813 task: (when, agenda_index),814 id: task.maybe_id,815 });816 Err((Unavailable, Some(task)))817 },818 Err(Overweight) if is_first => {819 T::Preimages::drop(&task.call);820 Self::deposit_event(Event::PermanentlyOverweight {821 task: (when, agenda_index),822 id: task.maybe_id,823 });824 Err((Unavailable, Some(task)))825 },826 Err(Overweight) => Err((Overweight, Some(task))),827 Ok(result) => {828 Self::deposit_event(Event::Dispatched {829 task: (when, agenda_index),830 id: task.maybe_id,831 result,832 });833 if let &Some((period, count)) = &task.maybe_periodic {834 if count > 1 {835 task.maybe_periodic = Some((period, count - 1));836 } else {837 task.maybe_periodic = None;838 }839 let wake = now.saturating_add(period);840 match Self::place_task(wake, task) {841 Ok(_) => {},842 Err((_, task)) => {843 // TODO: Leave task in storage somewhere for it to be rescheduled844 // manually.845 T::Preimages::drop(&task.call);846 Self::deposit_event(Event::PeriodicFailed {847 task: (when, agenda_index),848 id: task.maybe_id,849 });850 },851 }852 } else {853 T::Preimages::drop(&task.call);854 }855 Ok(())856 },857 }858 }859860 /// Make a dispatch to the given `call` from the given `origin`, ensuring that the `weight`861 /// counter does not exceed its limit and that it is counted accurately (e.g. accounted using862 /// post info if available).863 ///864 /// NOTE: Only the weight for this function will be counted (origin lookup, dispatch and the865 /// call itself).866 fn execute_dispatch(867 weight: &mut WeightCounter,868 origin: T::PalletsOrigin,869 call: <T as Config>::Call,870 ) -> Result<DispatchResult, ServiceTaskError> {871 let dispatch_origin: <T as Config>::Origin = origin.into();872 let base_weight = match dispatch_origin.clone().as_signed() {873 Some(_) => T::WeightInfo::execute_dispatch_signed(),874 _ => T::WeightInfo::execute_dispatch_unsigned(),875 };876 let call_weight = call.get_dispatch_info().weight;877 // We only allow a scheduled call if it cannot push the weight past the limit.878 let max_weight = base_weight.saturating_add(call_weight);879880 if !weight.can_accrue(max_weight) {881 return Err(Overweight)882 }883884 let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) {885 Ok(post_info) => (post_info.actual_weight, Ok(())),886 Err(error_and_info) =>887 (error_and_info.post_info.actual_weight, Err(error_and_info.error)),888 };889 let call_weight = maybe_actual_call_weight.unwrap_or(call_weight);890 weight.check_accrue(base_weight);891 weight.check_accrue(call_weight);892 Ok(result)893 }894}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// This file is part of Substrate.1920// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.21// SPDX-License-Identifier: Apache-2.02223// Licensed under the Apache License, Version 2.0 (the "License");24// you may not use this file except in compliance with the License.25// You may obtain a copy of the License at26//27// http://www.apache.org/licenses/LICENSE-2.028//29// Unless required by applicable law or agreed to in writing, software30// distributed under the License is distributed on an "AS IS" BASIS,31// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.32// See the License for the specific language governing permissions and33// limitations under the License.3435//! # Scheduler36//! A Pallet for scheduling dispatches.37//!38//! - [`Config`]39//! - [`Call`]40//! - [`Pallet`]41//!42//! ## Overview43//!44//! This Pallet exposes capabilities for scheduling dispatches to occur at a45//! specified block number or at a specified period. These scheduled dispatches46//! may be named or anonymous and may be canceled.47//!48//! **NOTE:** The scheduled calls will be dispatched with the default filter49//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin50//! except root which will get no filter. And not the filter contained in origin51//! use to call `fn schedule`.52//!53//! If a call is scheduled using proxy or whatever mecanism which adds filter,54//! then those filter will not be used when dispatching the schedule call.55//!56//! ## Interface57//!58//! ### Dispatchable Functions59//!60//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and61//! with a specified priority.62//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.63//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter64//! that can be used for identification.65//! * `cancel_named` - the named complement to the cancel function.6667// Ensure we're `no_std` when compiling for Wasm.68#![cfg_attr(not(feature = "std"), no_std)]6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72#[cfg(test)]73mod mock;74#[cfg(test)]75mod tests;76pub mod weights;7778use codec::{Codec, Decode, Encode, MaxEncodedLen};79use frame_support::{80 dispatch::{81 DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter, RawOrigin,82 },83 ensure,84 traits::{85 schedule::{self, DispatchTime},86 EnsureOrigin, Get, IsType, OriginTrait,87 PalletInfoAccess, PrivilegeCmp, StorageVersion,88 PreimageProvider, PreimageRecipient, ConstU32,89 },90 weights::Weight,91};9293use frame_system::{self as system};94use scale_info::TypeInfo;95use sp_io::hashing::blake2_256;96use sp_runtime::{97 traits::{BadOrigin, One, Saturating, Zero, Hash},98 BoundedVec, RuntimeDebug,99};100use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};101pub use weights::WeightInfo;102103pub use pallet::*;104105/// Just a simple index for naming period tasks.106pub type PeriodicIndex = u32;107/// The location of a scheduled task that can be used to remove it.108pub type TaskAddress<BlockNumber> = (BlockNumber, u32);109110pub type EncodedCall = BoundedVec<u8, ConstU32<128>>;111112#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]113#[scale_info(skip_type_params(T))]114pub enum ScheduledCall<T: Config> {115 Inline(EncodedCall),116 PreimageLookup {117 hash: T::Hash,118 unbounded_len: u32,119 },120}121122impl<T: Config> ScheduledCall<T> {123 pub fn new(call: <T as Config>::Call) -> Result<Self, DispatchError> {124 let encoded = call.encode();125 let len = encoded.len();126127 match EncodedCall::try_from(encoded.clone()) {128 Ok(bounded) => Ok(Self::Inline(bounded)),129 Err(_) => {130 let hash = <T as system::Config>::Hashing::hash_of(&encoded);131 <T as Config>::Preimages::note_preimage(encoded.try_into().map_err(|_| <Error<T>>::TooBigScheduledCall)?);132133 Ok(Self::PreimageLookup { hash, unbounded_len: len as u32 })134 }135 }136 }137138 /// The maximum length of the lookup that is needed to peek `Self`.139 pub fn lookup_len(&self) -> Option<u32> {140 match self {141 Self::Inline(..) => None,142 Self::PreimageLookup { unbounded_len, .. } => Some(*unbounded_len),143 }144 }145146 /// Returns whether the image will require a lookup to be peeked.147 pub fn lookup_needed(&self) -> bool {148 match self {149 Self::Inline(_) => false,150 Self::PreimageLookup { .. } => true,151 }152 }153154 fn decode(mut data: &[u8]) -> Result<<T as Config>::Call, DispatchError> {155 <T as Config>::Call::decode(&mut data)156 .map_err(|_| <Error<T>>::ScheduledCallCorrupted.into())157 }158}159160pub trait SchedulerPreimages<T: Config>: PreimageRecipient<T::Hash> {161 fn drop(call: &ScheduledCall<T>);162163 fn peek(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError>;164165 /// Convert the given scheduled `call` value back into its original instance. If successful,166 /// `drop` any data backing it. This will not break the realisability of independently167 /// created instances of `ScheduledCall` which happen to have identical data.168 fn realize(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError>;169}170171impl<T: Config, PP: PreimageRecipient<T::Hash>> SchedulerPreimages<T> for PP {172 fn drop(call: &ScheduledCall<T>) {173 match call {174 ScheduledCall::Inline(_) => {},175 ScheduledCall::PreimageLookup { hash, .. } => Self::unrequest_preimage(hash),176 }177 }178179 fn peek(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError> {180 match call {181 ScheduledCall::Inline(data) => Ok((ScheduledCall::<T>::decode(data)?, None)),182 ScheduledCall::PreimageLookup { hash, unbounded_len } => {183 let (preimage, len) = Self::get_preimage(hash)184 .ok_or(<Error<T>>::PreimageNotFound)185 .map(|preimage| (preimage, *unbounded_len))?;186187 Ok((ScheduledCall::<T>::decode(preimage.as_slice())?, Some(len)))188 },189 }190 }191192 fn realize(call: &ScheduledCall<T>) -> Result<(<T as pallet::Config>::Call, Option<u32>), DispatchError> {193 let r = Self::peek(call)?;194 Self::drop(call);195 Ok(r)196 }197}198199pub type TaskName = [u8; 32];200201/// Information regarding an item to be executed in the future.202#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]203#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]204pub struct Scheduled<Name, Call, BlockNumber, PalletsOrigin, AccountId> {205 /// The unique identity for this task, if there is one.206 maybe_id: Option<Name>,207208 /// This task's priority.209 priority: schedule::Priority,210211 /// The call to be dispatched.212 call: Call,213214 /// If the call is periodic, then this points to the information concerning that.215 maybe_periodic: Option<schedule::Period<BlockNumber>>,216217 /// The origin with which to dispatch the call.218 origin: PalletsOrigin,219 _phantom: PhantomData<AccountId>,220}221222pub type ScheduledOf<T> = Scheduled<223 TaskName,224 ScheduledCall<T>,225 <T as frame_system::Config>::BlockNumber,226 <T as Config>::PalletsOrigin,227 <T as frame_system::Config>::AccountId,228>;229230struct WeightCounter {231 used: Weight,232 limit: Weight,233}234235impl WeightCounter {236 fn check_accrue(&mut self, w: Weight) -> bool {237 let test = self.used.saturating_add(w);238 if test > self.limit {239 false240 } else {241 self.used = test;242 true243 }244 }245246 fn can_accrue(&mut self, w: Weight) -> bool {247 self.used.saturating_add(w) <= self.limit248 }249}250251pub(crate) trait MarginalWeightInfo: WeightInfo {252 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {253 let base = Self::service_task_base();254 let mut total = match maybe_lookup_len {255 None => base,256 Some(l) => Self::service_task_fetched(l as u32),257 };258 if named {259 total.saturating_accrue(Self::service_task_named().saturating_sub(base));260 }261 if periodic {262 total.saturating_accrue(Self::service_task_periodic().saturating_sub(base));263 }264 total265 }266}267268impl<T: WeightInfo> MarginalWeightInfo for T {}269270#[frame_support::pallet]271pub mod pallet {272 use super::*;273 use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*};274 use system::pallet_prelude::*;275276 /// The current storage version.277 const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);278279 #[pallet::pallet]280 #[pallet::generate_store(pub(super) trait Store)]281 #[pallet::storage_version(STORAGE_VERSION)]282 pub struct Pallet<T>(_);283284 #[pallet::config]285 pub trait Config: frame_system::Config {286 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;287288 /// The aggregated origin which the dispatch will take.289 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>290 + From<Self::PalletsOrigin>291 + IsType<<Self as system::Config>::Origin>292 + Clone;293294 /// The caller origin, overarching type of all pallets origins.295 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>>296 + Codec297 + Clone298 + Eq299 + TypeInfo300 + MaxEncodedLen;301302 /// The aggregated call type.303 type Call: Parameter304 + Dispatchable<305 Origin = <Self as Config>::Origin,306 PostInfo = PostDispatchInfo,307 > + GetDispatchInfo308 + From<system::Call<Self>>;309310 /// The maximum weight that may be scheduled per block for any dispatchables.311 #[pallet::constant]312 type MaximumWeight: Get<Weight>;313314 /// Required origin to schedule or cancel calls.315 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;316317 /// Compare the privileges of origins.318 ///319 /// This will be used when canceling a task, to ensure that the origin that tries320 /// to cancel has greater or equal privileges as the origin that created the scheduled task.321 ///322 /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can323 /// be used. This will only check if two given origins are equal.324 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;325326 /// The maximum number of scheduled calls in the queue for a single block.327 #[pallet::constant]328 type MaxScheduledPerBlock: Get<u32>;329330 /// Weight information for extrinsics in this pallet.331 type WeightInfo: WeightInfo;332333 /// The preimage provider with which we look up call hashes to get the call.334 type Preimages: SchedulerPreimages<Self>;335 }336337 #[pallet::storage]338 pub type IncompleteSince<T: Config> = StorageValue<_, T::BlockNumber>;339340 /// Items to be executed, indexed by the block number that they should be executed on.341 #[pallet::storage]342 pub type Agenda<T: Config> = StorageMap<343 _,344 Twox64Concat,345 T::BlockNumber,346 BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,347 ValueQuery,348 >;349350 /// Lookup from a name to the block number and index of the task.351 #[pallet::storage]352 pub(crate) type Lookup<T: Config> =353 StorageMap<_, Twox64Concat, TaskName, TaskAddress<T::BlockNumber>>;354355 /// Events type.356 #[pallet::event]357 #[pallet::generate_deposit(pub(super) fn deposit_event)]358 pub enum Event<T: Config> {359 /// Scheduled some task.360 Scheduled { when: T::BlockNumber, index: u32 },361 /// Canceled some task.362 Canceled { when: T::BlockNumber, index: u32 },363 /// Dispatched some task.364 Dispatched {365 task: TaskAddress<T::BlockNumber>,366 id: Option<[u8; 32]>,367 result: DispatchResult,368 },369 /// The call for the provided hash was not found so the task has been aborted.370 CallUnavailable { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },371 /// The given task was unable to be renewed since the agenda is full at that block.372 PeriodicFailed { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },373 /// The given task can never be executed since it is overweight.374 PermanentlyOverweight { task: TaskAddress<T::BlockNumber>, id: Option<[u8; 32]> },375 }376377 #[pallet::error]378 pub enum Error<T> {379 /// Failed to schedule a call380 FailedToSchedule,381 /// There is no place for a new task in the agenda382 AgendaIsExhausted,383 /// Scheduled call is corrupted384 ScheduledCallCorrupted,385 /// Scheduled call preimage is not found386 PreimageNotFound,387 /// Scheduled call is too big388 TooBigScheduledCall,389 /// Cannot find the scheduled call.390 NotFound,391 /// Given target block number is in the past.392 TargetBlockNumberInPast,393 /// Reschedule failed because it does not change scheduled time.394 RescheduleNoChange,395 /// Attempt to use a non-named function on a named task.396 Named,397 }398399 #[pallet::hooks]400 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {401 /// Execute the scheduled calls402 fn on_initialize(now: T::BlockNumber) -> Weight {403 let mut weight_counter =404 WeightCounter { used: Weight::zero(), limit: T::MaximumWeight::get() };405 Self::service_agendas(&mut weight_counter, now, u32::max_value());406 weight_counter.used407 }408 }409410 #[pallet::call]411 impl<T: Config> Pallet<T> {412 /// Anonymously schedule a task.413 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]414 pub fn schedule(415 origin: OriginFor<T>,416 when: T::BlockNumber,417 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,418 priority: schedule::Priority,419 call: Box<<T as Config>::Call>,420 ) -> DispatchResult {421 T::ScheduleOrigin::ensure_origin(origin.clone())?;422 let origin = <T as Config>::Origin::from(origin);423 Self::do_schedule(424 DispatchTime::At(when),425 maybe_periodic,426 priority,427 origin.caller().clone(),428 <ScheduledCall<T>>::new(*call)?,429 )?;430 Ok(())431 }432433 /// Cancel an anonymously scheduled task.434 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]435 pub fn cancel(origin: OriginFor<T>, when: T::BlockNumber, index: u32) -> DispatchResult {436 T::ScheduleOrigin::ensure_origin(origin.clone())?;437 let origin = <T as Config>::Origin::from(origin);438 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;439 Ok(())440 }441442 /// Schedule a named task.443 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]444 pub fn schedule_named(445 origin: OriginFor<T>,446 id: TaskName,447 when: T::BlockNumber,448 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,449 priority: schedule::Priority,450 call: Box<<T as Config>::Call>,451 ) -> DispatchResult {452 T::ScheduleOrigin::ensure_origin(origin.clone())?;453 let origin = <T as Config>::Origin::from(origin);454 Self::do_schedule_named(455 id,456 DispatchTime::At(when),457 maybe_periodic,458 priority,459 origin.caller().clone(),460 <ScheduledCall<T>>::new(*call)?,461 )?;462 Ok(())463 }464465 /// Cancel a named scheduled task.466 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]467 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {468 T::ScheduleOrigin::ensure_origin(origin.clone())?;469 let origin = <T as Config>::Origin::from(origin);470 Self::do_cancel_named(Some(origin.caller().clone()), id)?;471 Ok(())472 }473474 /// Anonymously schedule a task after a delay.475 ///476 /// # <weight>477 /// Same as [`schedule`].478 /// # </weight>479 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]480 pub fn schedule_after(481 origin: OriginFor<T>,482 after: T::BlockNumber,483 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,484 priority: schedule::Priority,485 call: Box<<T as Config>::Call>,486 ) -> DispatchResult {487 T::ScheduleOrigin::ensure_origin(origin.clone())?;488 let origin = <T as Config>::Origin::from(origin);489 Self::do_schedule(490 DispatchTime::After(after),491 maybe_periodic,492 priority,493 origin.caller().clone(),494 <ScheduledCall<T>>::new(*call)?,495 )?;496 Ok(())497 }498499 /// Schedule a named task after a delay.500 ///501 /// # <weight>502 /// Same as [`schedule_named`](Self::schedule_named).503 /// # </weight>504 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]505 pub fn schedule_named_after(506 origin: OriginFor<T>,507 id: TaskName,508 after: T::BlockNumber,509 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,510 priority: schedule::Priority,511 call: Box<<T as Config>::Call>,512 ) -> DispatchResult {513 T::ScheduleOrigin::ensure_origin(origin.clone())?;514 let origin = <T as Config>::Origin::from(origin);515 Self::do_schedule_named(516 id,517 DispatchTime::After(after),518 maybe_periodic,519 priority,520 origin.caller().clone(),521 <ScheduledCall<T>>::new(*call)?,522 )?;523 Ok(())524 }525 }526}527528impl<T: Config> Pallet<T> {529 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {530 let now = frame_system::Pallet::<T>::block_number();531532 let when = match when {533 DispatchTime::At(x) => x,534 // The current block has already completed it's scheduled tasks, so535 // Schedule the task at lest one block after this current block.536 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),537 };538539 if when <= now {540 return Err(Error::<T>::TargetBlockNumberInPast.into())541 }542543 Ok(when)544 }545546 fn place_task(547 when: T::BlockNumber,548 what: ScheduledOf<T>,549 ) -> Result<TaskAddress<T::BlockNumber>, (DispatchError, ScheduledOf<T>)> {550 let maybe_name = what.maybe_id;551 let index = Self::push_to_agenda(when, what)?;552 let address = (when, index);553 if let Some(name) = maybe_name {554 Lookup::<T>::insert(name, address)555 }556 Self::deposit_event(Event::Scheduled { when: address.0, index: address.1 });557 Ok(address)558 }559560 fn push_to_agenda(561 when: T::BlockNumber,562 what: ScheduledOf<T>,563 ) -> Result<u32, (DispatchError, ScheduledOf<T>)> {564 let mut agenda = Agenda::<T>::get(when);565 let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() {566 // will always succeed due to the above check.567 let _ = agenda.try_push(Some(what));568 agenda.len() as u32 - 1569 } else {570 if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) {571 agenda[hole_index] = Some(what);572 hole_index as u32573 } else {574 return Err((<Error<T>>::AgendaIsExhausted.into(), what))575 }576 };577 Agenda::<T>::insert(when, agenda);578 Ok(index)579 }580581 fn do_schedule(582 when: DispatchTime<T::BlockNumber>,583 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,584 priority: schedule::Priority,585 origin: T::PalletsOrigin,586 call: ScheduledCall<T>,587 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {588 let when = Self::resolve_time(when)?;589590 // sanitize maybe_periodic591 let maybe_periodic = maybe_periodic592 .filter(|p| p.1 > 1 && !p.0.is_zero())593 // Remove one from the number of repetitions since we will schedule one now.594 .map(|(p, c)| (p, c - 1));595 let task = Scheduled {596 maybe_id: None,597 priority,598 call,599 maybe_periodic,600 origin,601 _phantom: PhantomData,602 };603 Self::place_task(when, task).map_err(|x| x.0)604 }605606 fn do_cancel(607 origin: Option<T::PalletsOrigin>,608 (when, index): TaskAddress<T::BlockNumber>,609 ) -> Result<(), DispatchError> {610 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {611 agenda.get_mut(index as usize).map_or(612 Ok(None),613 |s| -> Result<Option<Scheduled<_, _, _, _, _>>, DispatchError> {614 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {615 if matches!(616 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),617 Some(Ordering::Less) | None618 ) {619 return Err(BadOrigin.into())620 }621 };622 Ok(s.take())623 },624 )625 })?;626 if let Some(s) = scheduled {627 T::Preimages::drop(&s.call);628629 if let Some(id) = s.maybe_id {630 Lookup::<T>::remove(id);631 }632 Self::deposit_event(Event::Canceled { when, index });633 Ok(())634 } else {635 return Err(Error::<T>::NotFound.into())636 }637 }638639 fn do_schedule_named(640 id: TaskName,641 when: DispatchTime<T::BlockNumber>,642 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,643 priority: schedule::Priority,644 origin: T::PalletsOrigin,645 call: ScheduledCall<T>,646 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {647 // ensure id it is unique648 if Lookup::<T>::contains_key(&id) {649 return Err(Error::<T>::FailedToSchedule.into())650 }651652 let when = Self::resolve_time(when)?;653654 // sanitize maybe_periodic655 let maybe_periodic = maybe_periodic656 .filter(|p| p.1 > 1 && !p.0.is_zero())657 // Remove one from the number of repetitions since we will schedule one now.658 .map(|(p, c)| (p, c - 1));659660 let task = Scheduled {661 maybe_id: Some(id),662 priority,663 call,664 maybe_periodic,665 origin,666 _phantom: Default::default(),667 };668 Self::place_task(when, task).map_err(|x| x.0)669 }670671 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: TaskName) -> DispatchResult {672 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {673 if let Some((when, index)) = lookup.take() {674 let i = index as usize;675 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {676 if let Some(s) = agenda.get_mut(i) {677 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {678 if matches!(679 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),680 Some(Ordering::Less) | None681 ) {682 return Err(BadOrigin.into())683 }684 T::Preimages::drop(&s.call);685 }686 *s = None;687 }688 Ok(())689 })?;690 Self::deposit_event(Event::Canceled { when, index });691 Ok(())692 } else {693 return Err(Error::<T>::NotFound.into())694 }695 })696 }697}698699enum ServiceTaskError {700 /// Could not be executed due to missing preimage.701 Unavailable,702 /// Could not be executed due to weight limitations.703 Overweight,704}705use ServiceTaskError::*;706707impl<T: Config> Pallet<T> {708 /// Service up to `max` agendas queue starting from earliest incompletely executed agenda.709 fn service_agendas(weight: &mut WeightCounter, now: T::BlockNumber, max: u32) {710 if !weight.check_accrue(T::WeightInfo::service_agendas_base()) {711 return712 }713714 let mut incomplete_since = now + One::one();715 let mut when = IncompleteSince::<T>::take().unwrap_or(now);716 let mut executed = 0;717718 let max_items = T::MaxScheduledPerBlock::get();719 let mut count_down = max;720 let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items);721 while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) {722 if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) {723 incomplete_since = incomplete_since.min(when);724 }725 when.saturating_inc();726 count_down.saturating_dec();727 }728 incomplete_since = incomplete_since.min(when);729 if incomplete_since <= now {730 IncompleteSince::<T>::put(incomplete_since);731 }732 }733734 /// Returns `true` if the agenda was fully completed, `false` if it should be revisited at a735 /// later block.736 fn service_agenda(737 weight: &mut WeightCounter,738 executed: &mut u32,739 now: T::BlockNumber,740 when: T::BlockNumber,741 max: u32,742 ) -> bool {743 let mut agenda = Agenda::<T>::get(when);744 let mut ordered = agenda745 .iter()746 .enumerate()747 .filter_map(|(index, maybe_item)| {748 maybe_item.as_ref().map(|item| (index as u32, item.priority))749 })750 .collect::<Vec<_>>();751 ordered.sort_by_key(|k| k.1);752 let within_limit =753 weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32));754 debug_assert!(within_limit, "weight limit should have been checked in advance");755756 // Items which we know can be executed and have postponed for execution in a later block.757 let mut postponed = (ordered.len() as u32).saturating_sub(max);758 // Items which we don't know can ever be executed.759 let mut dropped = 0;760761 for (agenda_index, _) in ordered.into_iter().take(max as usize) {762 let task = match agenda[agenda_index as usize].take() {763 None => continue,764 Some(t) => t,765 };766 let base_weight = T::WeightInfo::service_task(767 task.call.lookup_len().map(|x| x as usize),768 task.maybe_id.is_some(),769 task.maybe_periodic.is_some(),770 );771 if !weight.can_accrue(base_weight) {772 postponed += 1;773 break774 }775 let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task);776 agenda[agenda_index as usize] = match result {777 Err((Unavailable, slot)) => {778 dropped += 1;779 slot780 },781 Err((Overweight, slot)) => {782 postponed += 1;783 slot784 },785 Ok(()) => {786 *executed += 1;787 None788 },789 };790 }791 if postponed > 0 || dropped > 0 {792 Agenda::<T>::insert(when, agenda);793 } else {794 Agenda::<T>::remove(when);795 }796 postponed == 0797 }798799 /// Service (i.e. execute) the given task, being careful not to overflow the `weight` counter.800 ///801 /// This involves:802 /// - removing and potentially replacing the `Lookup` entry for the task.803 /// - realizing the task's call which can include a preimage lookup.804 /// - Rescheduling the task for execution in a later agenda if periodic.805 fn service_task(806 weight: &mut WeightCounter,807 now: T::BlockNumber,808 when: T::BlockNumber,809 agenda_index: u32,810 is_first: bool,811 mut task: ScheduledOf<T>,812 ) -> Result<(), (ServiceTaskError, Option<ScheduledOf<T>>)> {813 if let Some(ref id) = task.maybe_id {814 Lookup::<T>::remove(id);815 }816817 let (call, lookup_len) = match T::Preimages::peek(&task.call) {818 Ok(c) => c,819 Err(_) => return Err((Unavailable, Some(task))),820 };821822 weight.check_accrue(T::WeightInfo::service_task(823 lookup_len.map(|x| x as usize),824 task.maybe_id.is_some(),825 task.maybe_periodic.is_some(),826 ));827828 match Self::execute_dispatch(weight, task.origin.clone(), call) {829 Err(Unavailable) => {830 debug_assert!(false, "Checked to exist with `peek`");831 Self::deposit_event(Event::CallUnavailable {832 task: (when, agenda_index),833 id: task.maybe_id,834 });835 Err((Unavailable, Some(task)))836 },837 Err(Overweight) if is_first => {838 T::Preimages::drop(&task.call);839 Self::deposit_event(Event::PermanentlyOverweight {840 task: (when, agenda_index),841 id: task.maybe_id,842 });843 Err((Unavailable, Some(task)))844 },845 Err(Overweight) => Err((Overweight, Some(task))),846 Ok(result) => {847 Self::deposit_event(Event::Dispatched {848 task: (when, agenda_index),849 id: task.maybe_id,850 result,851 });852 if let &Some((period, count)) = &task.maybe_periodic {853 if count > 1 {854 task.maybe_periodic = Some((period, count - 1));855 } else {856 task.maybe_periodic = None;857 }858 let wake = now.saturating_add(period);859 match Self::place_task(wake, task) {860 Ok(_) => {},861 Err((_, task)) => {862 // TODO: Leave task in storage somewhere for it to be rescheduled863 // manually.864 T::Preimages::drop(&task.call);865 Self::deposit_event(Event::PeriodicFailed {866 task: (when, agenda_index),867 id: task.maybe_id,868 });869 },870 }871 } else {872 T::Preimages::drop(&task.call);873 }874 Ok(())875 },876 }877 }878879 /// Make a dispatch to the given `call` from the given `origin`, ensuring that the `weight`880 /// counter does not exceed its limit and that it is counted accurately (e.g. accounted using881 /// post info if available).882 ///883 /// NOTE: Only the weight for this function will be counted (origin lookup, dispatch and the884 /// call itself).885 fn execute_dispatch(886 weight: &mut WeightCounter,887 origin: T::PalletsOrigin,888 call: <T as Config>::Call,889 ) -> Result<DispatchResult, ServiceTaskError> {890 let dispatch_origin: <T as Config>::Origin = origin.into();891 let base_weight = match dispatch_origin.clone().as_signed() {892 Some(_) => T::WeightInfo::execute_dispatch_signed(),893 _ => T::WeightInfo::execute_dispatch_unsigned(),894 };895 let call_weight = call.get_dispatch_info().weight;896 // We only allow a scheduled call if it cannot push the weight past the limit.897 let max_weight = base_weight.saturating_add(call_weight);898899 if !weight.can_accrue(max_weight) {900 return Err(Overweight)901 }902903 let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) {904 Ok(post_info) => (post_info.actual_weight, Ok(())),905 Err(error_and_info) =>906 (error_and_info.post_info.actual_weight, Err(error_and_info.error)),907 };908 let call_weight = maybe_actual_call_weight.unwrap_or(call_weight);909 weight.check_accrue(base_weight);910 weight.check_accrue(call_weight);911 Ok(result)912 }913}runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -42,6 +42,7 @@
'pallet-inflation/runtime-benchmarks',
'pallet-app-promotion/runtime-benchmarks',
'pallet-unique-scheduler/runtime-benchmarks',
+ 'pallet-unique-scheduler-v2/runtime-benchmarks',
'pallet-xcm/runtime-benchmarks',
'sp-runtime/runtime-benchmarks',
'xcm-builder/runtime-benchmarks',