diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 6e5a67387c..af05b34967 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -97,8 +97,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 255, - impl_version: 1, + spec_version: 256, + impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, }; diff --git a/substrate/frame/democracy/src/benchmarking.rs b/substrate/frame/democracy/src/benchmarking.rs index 77d49c80fc..421eb07e32 100644 --- a/substrate/frame/democracy/src/benchmarking.rs +++ b/substrate/frame/democracy/src/benchmarking.rs @@ -22,7 +22,7 @@ use super::*; use frame_benchmarking::{benchmarks, account}; use frame_support::{ IterableStorageMap, - traits::{Currency, Get, EnsureOrigin, OnInitialize, UnfilteredDispatchable}, + traits::{Currency, Get, EnsureOrigin, OnInitialize, UnfilteredDispatchable, schedule::DispatchTime}, }; use frame_system::{RawOrigin, Module as System, self, EventRecord}; use sp_runtime::traits::{Bounded, One}; @@ -76,7 +76,7 @@ fn add_referendum(n: u32) -> Result { let referendum_index: ReferendumIndex = ReferendumCount::get() - 1; T::Scheduler::schedule_named( (DEMOCRACY_ID, referendum_index).encode(), - 1.into(), + DispatchTime::At(1.into()), None, 63, system::RawOrigin::Root.into(), diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index ae256f9d73..4ee8963be5 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/frame/democracy/src/lib.rs @@ -163,7 +163,7 @@ use frame_support::{ weights::{Weight, DispatchClass}, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, - OnUnbalanced, BalanceStatus, schedule::Named as ScheduleNamed, EnsureOrigin + OnUnbalanced, BalanceStatus, schedule::{Named as ScheduleNamed, DispatchTime}, EnsureOrigin }, dispatch::DispatchResultWithPostInfo, }; @@ -1688,7 +1688,7 @@ impl Module { if T::Scheduler::schedule_named( (DEMOCRACY_ID, index).encode(), - when, + DispatchTime::At(when), None, 63, system::RawOrigin::Root.into(), diff --git a/substrate/frame/scheduler/src/benchmarking.rs b/substrate/frame/scheduler/src/benchmarking.rs index 748017829f..847460fe85 100644 --- a/substrate/frame/scheduler/src/benchmarking.rs +++ b/substrate/frame/scheduler/src/benchmarking.rs @@ -39,7 +39,7 @@ fn fill_schedule (when: T::BlockNumber, n: u32) -> Result<(), &'static // Named schedule is strictly heavier than anonymous Scheduler::::do_schedule_named( i.encode(), - when, + DispatchTime::At(when), // Add periodicity Some((T::BlockNumber::one(), 100)), // HARD_DEADLINE priority means it gets executed no matter what diff --git a/substrate/frame/scheduler/src/lib.rs b/substrate/frame/scheduler/src/lib.rs index 0b8c9173a9..12a3997aaf 100644 --- a/substrate/frame/scheduler/src/lib.rs +++ b/substrate/frame/scheduler/src/lib.rs @@ -55,11 +55,11 @@ mod benchmarking; use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow}; use codec::{Encode, Decode, Codec}; -use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin}}; +use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin, Saturating}}; use frame_support::{ decl_module, decl_storage, decl_event, decl_error, IterableStorageMap, dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter}, - traits::{Get, schedule, OriginTrait, EnsureOrigin, IsType}, + traits::{Get, schedule::{self, DispatchTime}, OriginTrait, EnsureOrigin, IsType}, weights::{GetDispatchInfo, Weight}, }; use frame_system::{self as system}; @@ -219,7 +219,7 @@ decl_module! { ) { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::Origin::from(origin); - Self::do_schedule(when, maybe_periodic, priority, origin.caller().clone(), *call)?; + Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?; } /// Cancel an anonymously scheduled task. @@ -259,7 +259,9 @@ decl_module! { ) { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::Origin::from(origin); - Self::do_schedule_named(id, when, maybe_periodic, priority, origin.caller().clone(), *call)?; + Self::do_schedule_named( + id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call + )?; } /// Cancel a named scheduled task. @@ -279,6 +281,45 @@ decl_module! { Self::do_cancel_named(Some(origin.caller().clone()), id)?; } + /// Anonymously schedule a task after a delay. + /// + /// # + /// Same as [`schedule`]. + /// # + #[weight = 25_000_000 + T::DbWeight::get().reads_writes(1, 1)] + fn schedule_after(origin, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule( + DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call + )?; + } + + /// Schedule a named task after a delay. + /// + /// # + /// Same as [`schedule_named`]. + /// # + #[weight = 35_000_000 + T::DbWeight::get().reads_writes(2, 2)] + fn schedule_named_after(origin, + id: Vec, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule_named( + id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call + )?; + } + /// Execute the scheduled calls /// /// # @@ -395,13 +436,20 @@ impl Module { } fn do_schedule( - when: T::BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, call: ::Call ) -> Result, DispatchError> { - if when <= frame_system::Module::::block_number() { + let now = frame_system::Module::::block_number(); + + let when = match when { + DispatchTime::At(x) => x, + DispatchTime::After(x) => now.saturating_add(x) + }; + + if when <= now { return Err(Error::::TargetBlockNumberInPast.into()) } @@ -451,7 +499,7 @@ impl Module { fn do_schedule_named( id: Vec, - when: T::BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, @@ -462,7 +510,14 @@ impl Module { return Err(Error::::FailedToSchedule)? } - if when <= frame_system::Module::::block_number() { + let now = frame_system::Module::::block_number(); + + let when = match when { + DispatchTime::At(x) => x, + DispatchTime::After(x) => now.saturating_add(x) + }; + + if when <= now { return Err(Error::::TargetBlockNumberInPast.into()) } @@ -512,7 +567,7 @@ impl schedule::Anon::Call, T::PalletsOrig type Address = TaskAddress; fn schedule( - when: T::BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, @@ -531,7 +586,7 @@ impl schedule::Named::Call, T::PalletsOri fn schedule_named( id: Vec, - when: T::BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, @@ -716,7 +771,7 @@ mod tests { new_test_ext().execute_with(|| { let call = Call::Logger(logger::Call::log(42, 1000)); assert!(!::BaseCallFilter::filter(&call)); - let _ = Scheduler::do_schedule(4, None, 127, root(), call); + let _ = Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call); run_to_block(3); assert!(logger::log().is_empty()); run_to_block(4); @@ -726,12 +781,28 @@ mod tests { }); } + #[test] + fn schedule_after_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = Call::Logger(logger::Call::log(42, 1000)); + assert!(!::BaseCallFilter::filter(&call)); + let _ = Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call); + run_to_block(4); + assert!(logger::log().is_empty()); + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + #[test] fn periodic_scheduling_works() { new_test_ext().execute_with(|| { // at #4, every 3 blocks, 3 times. let _ = Scheduler::do_schedule( - 4, Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000)) + DispatchTime::At(4), Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000)) ); run_to_block(3); assert!(logger::log().is_empty()); @@ -755,10 +826,10 @@ mod tests { new_test_ext().execute_with(|| { // at #4. Scheduler::do_schedule_named( - 1u32.encode(), 4, None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) + 1u32.encode(), DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) ).unwrap(); let i = Scheduler::do_schedule( - 4, None, 127, root(), Call::Logger(logger::Call::log(42, 1000)) + DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(42, 1000)) ).unwrap(); run_to_block(3); assert!(logger::log().is_empty()); @@ -774,15 +845,25 @@ mod tests { new_test_ext().execute_with(|| { // at #4, every 3 blocks, 3 times. Scheduler::do_schedule_named( - 1u32.encode(), 4, Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000)) + 1u32.encode(), + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Call::Logger(logger::Call::log(42, 1000)) ).unwrap(); // same id results in error. assert!(Scheduler::do_schedule_named( - 1u32.encode(), 4, None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(logger::Call::log(69, 1000)) ).is_err()); // different id is ok. Scheduler::do_schedule_named( - 2u32.encode(), 8, None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) + 2u32.encode(), DispatchTime::At(8), None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) ).unwrap(); run_to_block(3); assert!(logger::log().is_empty()); @@ -799,10 +880,17 @@ mod tests { fn scheduler_respects_weight_limits() { new_test_ext().execute_with(|| { let _ = Scheduler::do_schedule( - 4, None, 127, root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) ); let _ = Scheduler::do_schedule( - 4, None, 127, root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 127, + root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) ); // 69 and 42 do not fit together run_to_block(4); @@ -816,10 +904,18 @@ mod tests { fn scheduler_respects_hard_deadlines_more() { new_test_ext().execute_with(|| { let _ = Scheduler::do_schedule( - 4, None, 0, root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) ); let _ = Scheduler::do_schedule( - 4, None, 0, root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) ); // With base weights, 69 and 42 should not fit together, but do because of hard deadlines run_to_block(4); @@ -831,10 +927,18 @@ mod tests { fn scheduler_respects_priority_ordering() { new_test_ext().execute_with(|| { let _ = Scheduler::do_schedule( - 4, None, 1, root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 1, + root(), + Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) ); let _ = Scheduler::do_schedule( - 4, None, 0, root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) ); run_to_block(4); assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); @@ -845,13 +949,22 @@ mod tests { fn scheduler_respects_priority_ordering_with_soft_deadlines() { new_test_ext().execute_with(|| { let _ = Scheduler::do_schedule( - 4, None, 255, root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) + DispatchTime::At(4), + None, + 255, + root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) ); let _ = Scheduler::do_schedule( - 4, None, 127, root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 127, + root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) ); let _ = Scheduler::do_schedule( - 4, None, 126, root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(4), + None, + 126, + root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2)) ); // 2600 does not fit with 69 or 42, but has higher priority, so will go through @@ -874,21 +987,29 @@ mod tests { // Named assert_ok!( Scheduler::do_schedule_named( - 1u32.encode(), 1, None, 255, root(), + 1u32.encode(), DispatchTime::At(1), None, 255, root(), Call::Logger(logger::Call::log(3, MaximumSchedulerWeight::get() / 3)) ) ); // Anon Periodic let _ = Scheduler::do_schedule( - 1, Some((1000, 3)), 128, root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) + DispatchTime::At(1), + Some((1000, 3)), + 128, + root(), + Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) ); // Anon let _ = Scheduler::do_schedule( - 1, None, 127, root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) + DispatchTime::At(1), + None, + 127, + root(), + Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) ); // Named Periodic assert_ok!(Scheduler::do_schedule_named( - 2u32.encode(), 1, Some((1000, 3)), 126, root(), + 2u32.encode(), DispatchTime::At(1), Some((1000, 3)), 126, root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))) ); diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index e0b2f256f0..ce5b7d0dea 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -1490,6 +1490,15 @@ pub mod schedule { /// higher priority. pub type Priority = u8; + /// The dispatch time of a scheduled task. + #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)] + pub enum DispatchTime { + /// At specified block. + At(BlockNumber), + /// After specified number of blocks. + After(BlockNumber), + } + /// The highest priority. We invert the value so that normal sorting will place the highest /// priority at the beginning of the list. pub const HIGHEST_PRIORITY: Priority = 0; @@ -1510,7 +1519,7 @@ pub mod schedule { /// /// Infallible. fn schedule( - when: BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: Priority, origin: Origin, @@ -1540,7 +1549,7 @@ pub mod schedule { /// - `id`: The identity of the task. This must be unique and will return an error if not. fn schedule_named( id: Vec, - when: BlockNumber, + when: DispatchTime, maybe_periodic: Option>, priority: Priority, origin: Origin,