From 0a899fc94107637be9551f445fa5696b63104460 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Mar 2020 23:59:59 +0100 Subject: [PATCH 01/28] Initial draft of the logic --- frame/scheduler/Cargo.toml | 37 ++++ frame/scheduler/src/lib.rs | 361 +++++++++++++++++++++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 frame/scheduler/Cargo.toml create mode 100644 frame/scheduler/src/lib.rs diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml new file mode 100644 index 0000000000000..5db9aca45b532 --- /dev/null +++ b/frame/scheduler/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-example" +version = "2.0.0-alpha.4" +authors = ["Parity Technologies "] +edition = "2018" +license = "Unlicense" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example pallet" + +[dependencies] +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +frame-benchmarking = { version = "2.0.0-alpha.4", default-features = false, path = "../benchmarking" } +frame-support = { version = "2.0.0-alpha.4", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.4", default-features = false, path = "../system" } +pallet-balances = { version = "2.0.0-alpha.4", default-features = false, path = "../balances" } +sp-runtime = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/io" } + +[dev-dependencies] +sp-core = { version = "2.0.0-alpha.4", path = "../../primitives/core", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-runtime/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "sp-io/std", + "sp-std/std" +] diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs new file mode 100644 index 0000000000000..3e1c835abf321 --- /dev/null +++ b/frame/scheduler/src/lib.rs @@ -0,0 +1,361 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +//! # Scheduler +//! +//! \# Scheduler +//! +//! - \[`::Trait`](./trait.Trait.html) +//! - \[`Call`](./enum.Call.html) +//! - \[`Module`](./struct.Module.html) +//! +//! \## Overview +//! +//! // Short description of pallet's purpose. +//! // Links to Traits that should be implemented. +//! // What this pallet is for. +//! // What functionality the pallet provides. +//! // When to use the pallet (use case examples). +//! // How it is used. +//! // Inputs it uses and the source of each input. +//! // Outputs it produces. +//! +//! \## Terminology +//! +//! \## Goals +//! +//! \## Interface +//! +//! \### Dispatchable Functions + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::marker::PhantomData; +use frame_support::{ + dispatch::DispatchResult, decl_module, decl_storage, decl_event, + weights::{ + SimpleDispatchInfo, DispatchInfo, DispatchClass, ClassifyDispatch, WeighData, Weight, + PaysFee + }, +}; +use sp_std::prelude::*; +use frame_benchmarking::{benchmarks, account}; +use frame_system::{self as system, ensure_signed, ensure_root, RawOrigin}; +use codec::{Encode, Decode}; +use sp_runtime::{ + traits::{SignedExtension, Bounded, SaturatedConversion}, + transaction_validity::{ + ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity, + }, +}; + +/// A type alias for the balance type from this pallet's point of view. +type BalanceOf = ::Balance; + +/// Our pallet's configuration trait. All our types and constants go in here. If the +/// pallet is dependent on specific other pallets, then their configuration traits +/// should be added to our implied traits list. +/// +/// `frame_system::Trait` should always be included in our implied traits. +pub trait Trait: pallet_balances::Trait { + /// The overarching event type. + type Event: From> + Into<::Event>; + + /// The aggregated origin which the dispatch will take. + type Origin: From>; + + /// The aggregated call type. + type Call: Parameter + Dispatchable; + + /// The maximum weight that may be scheduled per block for any dispatchables of less priority + /// than 255. + type MaximumWeight: Get; +} + +/// Just a simple index for naming period tasks. +pub type PeriodicIndex = u32; +/// Priority with which a call is scheduled. It's just a linear amount with higher values meaning +/// higher priority. +pub type Priority = u8; + +/// The highest priority. We invest the value so that normal sorting will place the highest +/// priority at the beginning of the list. +pub const HIGHEST_PRORITY: Priority = 0; +/// Anything of this value or lower will definitely be scheduled on the block that the ask for, even +/// if it breaches the `MaximumWeight` limitation. +pub const HARD_DEADLINE: Priority = 63; +/// The lowest priority. Most stuff should be around here. +pub const LOWEST_PRORITY: Priority = 255; + +/// Information regarding an item to be executed in the future. +#[derive(Clone, RuntimeDebug, Encode, Decode)] +pub struct Scheduled { + /// This task's priority. + priority: Priority, + /// The call to be dispatched. + call: Call, + /// If the call is periodic, then this points to the information concerning that. + maybe_periodic: Option, +} + +decl_storage! { + trait Store for Module as Scheduler { + /// Any period items. + Periodic: map hasher(twox_64_concat) PeriodicIndex => Option<(T::BlockNumber, u32)>; + + /// Items to be executed, indexed by the block number that they should be executed on. + /// This is ordered by `priority`. + Agenda: map hasher(twox_64_concat) T::BlockNumber + => Vec::Call, ::Origin>>; + + /// The next free periodic index. + NextPeriodicIndex: PeriodicIndex; + } +} + +decl_event!( + pub enum Event where B = ::Balance { + Scheduled(T::BlockNumber), + } +); + +decl_module! { + // Simple declaration of the `Module` type. Lets the macro know what its working on. + pub struct Module for enum Call where origin: T::Origin { + fn deposit_event() = default; + + #[weight = FunctionOf(|| { + let now = frame_system::Module::::block_number(); + let limit = T::MaximumWeight::get(); + let mut queued = Agenda::::get(now).into_iter() + .map(|i| (i.priority, i.call.get_dispatch_info().weight)) + .collect::>(); + queued.sort(); + queued + .scan(0, |a, i| { + *a += i.1; + if i.0 <= HARD_DEADLINE || *a <= limit { Some(a) } else { None } + }) + .last() + }, || DispatchClass::Normal, true)] + fn on_initialize(_n: T::BlockNumber) { + let limit = T::MaximumWeight::get(); + let mut queued = Agenda::::take(now); + queued.sort(); + let unused_items = queued.into_iter() + .scan(0, |cumulative_weight, i| { + *cumulative_weight += i.call.get_dispatch_info().weight; + Some((*cumulative_weight, i)) + }) + .filter_map(|(cumulative_weight, i)| { + if i.priority <= HARD_DEADLINE || cumulative_weight <= limit { + let maybe_reschedule = i.maybe_periodic.map(|p| (p, i.clone())); + let _ = i.call.dispatch(frame_system::RawOrigin::ROOT.into()); + if let Some((periodic_index, i)) = maybe_reschedule { + // Register the next instance of this task + if let Some((period, count)) = Periodic::get(periodic_index) { + if count > 1 { + count -= 1; + Periodic::insert(periodic_index, (period, count)); + } else { + Periodic::remove(periodic_index); + } + Agenda::::append(now + period, i); + } + } + None + } else { + Some(i) + } + }); + .collect::>(); + if !queued_items.is_empty() { + Agenda::::append_or_put(next, queued_items.into_iter()); + } + } + } +} + +/// A type that can be used as a scheduler. +pub trait Schedule { + fn schedule(when: BlockNumber, call: Call); + fn schedule_periodic(when: BlockNumber, period: BlockNumber, count: u32, call: Call); +} + +impl Scheduler for Module { + fn schedule(when: BlockNumber, priority: Priority, call: Call) { + Agenda::::append(when, Scheduled { priority, call, maybe_periodic: None }); + } + + fn schedule_periodic( + when: BlockNumber, + priority: Priority, + period: BlockNumber, + count: u32, + call: Call + ) -> PeriodicIndex { + let index = NextPeriodicIndex::mutate(|i| { let r = *1; *i += 1; *r }); + Periodic::::insert(index, (period, count)); + Agenda::::append(when, Scheduled { priority, call, maybe_periodic: Some(index) }); + index + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use frame_support::{assert_ok, impl_outer_origin, parameter_types, weights::GetDispatchInfo}; + use sp_core::H256; + // The testing primitives are very useful for avoiding having to work with signatures + // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. + use sp_runtime::{ + Perbill, + testing::Header, + traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup}, + }; + + impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} + } + + // For testing the pallet, we construct most of a mock runtime. This means + // first constructing a configuration type (`Test`) which `impl`s each of the + // configuration traits of pallets we want to use. + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + impl frame_system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + } + parameter_types! { + pub const ExistentialDeposit: u64 = 1; + } + impl pallet_balances::Trait for Test { + type Balance = u64; + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + } + impl Trait for Test { + type Event = (); + } + type System = frame_system::Module; + type Scheduler = Module; + + // This function basically just builds a genesis storage key/value store according to + // our desired mockup. + fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + pallet_balances::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); + GenesisConfig::{ + dummy: 42, + // we configure the map with (key, value) pairs. + bar: vec![(1, 2), (2, 3)], + foo: 24, + }.assimilate_storage(&mut t).unwrap(); + t.into() + } + + #[test] + fn it_works_for_optional_value() { + new_test_ext().execute_with(|| { + // Check that GenesisBuilder works properly. + assert_eq!(Scheduler::dummy(), Some(42)); + + // Check that accumulate works when we have Some value in Dummy already. + assert_ok!(Scheduler::accumulate_dummy(Origin::signed(1), 27)); + assert_eq!(Scheduler::dummy(), Some(69)); + + // Check that finalizing the block removes Dummy from storage. + >::on_finalize(1); + assert_eq!(Scheduler::dummy(), None); + + // Check that accumulate works when we Dummy has None in it. + >::on_initialize(2); + assert_ok!(Scheduler::accumulate_dummy(Origin::signed(1), 42)); + assert_eq!(Scheduler::dummy(), Some(42)); + }); + } + + #[test] + fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + assert_eq!(Scheduler::foo(), 24); + assert_ok!(Scheduler::accumulate_foo(Origin::signed(1), 1)); + assert_eq!(Scheduler::foo(), 25); + }); + } + + #[test] + fn signed_ext_watch_dummy_works() { + new_test_ext().execute_with(|| { + let call = >::set_dummy(10); + let info = DispatchInfo::default(); + + assert_eq!( + WatchDummy::(PhantomData).validate(&1, &call, info, 150) + .unwrap() + .priority, + Bounded::max_value(), + ); + assert_eq!( + WatchDummy::(PhantomData).validate(&1, &call, info, 250), + InvalidTransaction::ExhaustsResources.into(), + ); + }) + } + + #[test] + fn weights_work() { + // must have a default weight. + let default_call = >::accumulate_dummy(10); + let info = default_call.get_dispatch_info(); + // aka. `let info = as GetDispatchInfo>::get_dispatch_info(&default_call);` + assert_eq!(info.weight, 10_000); + + // must have a custom weight of `100 * arg = 2000` + let custom_call = >::set_dummy(20); + let info = custom_call.get_dispatch_info(); + assert_eq!(info.weight, 2000); + } +} From 6cd4ccad99b23b6d05f5f59e913478bafdb076a6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Mar 2020 21:37:36 +0100 Subject: [PATCH 02/28] Build and tests --- Cargo.lock | 15 ++ Cargo.toml | 1 + frame/scheduler/Cargo.toml | 4 +- frame/scheduler/src/lib.rs | 371 ++++++++++++++++++++++++------------- 4 files changed, 263 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba96516dd7271..e3a7969637ead 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,6 +4417,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-scheduler" +version = "2.0.0-alpha.4" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-scored-pool" version = "2.0.0-alpha.4" diff --git a/Cargo.toml b/Cargo.toml index d86bab8abd533..2737214c08323 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ members = [ "frame/offences", "frame/randomness-collective-flip", "frame/recovery", + "frame/scheduler", "frame/scored-pool", "frame/session", "frame/session/benchmarking", diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 5db9aca45b532..017518d882df4 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-example" +name = "pallet-scheduler" version = "2.0.0-alpha.4" authors = ["Parity Technologies "] edition = "2018" @@ -14,7 +14,6 @@ codec = { package = "parity-scale-codec", version = "1.2.0", default-features = frame-benchmarking = { version = "2.0.0-alpha.4", default-features = false, path = "../benchmarking" } frame-support = { version = "2.0.0-alpha.4", default-features = false, path = "../support" } frame-system = { version = "2.0.0-alpha.4", default-features = false, path = "../system" } -pallet-balances = { version = "2.0.0-alpha.4", default-features = false, path = "../balances" } sp-runtime = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/std" } sp-io = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/io" } @@ -31,7 +30,6 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "pallet-balances/std", "sp-io/std", "sp-std/std" ] diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 3e1c835abf321..61931d542c7c7 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -44,42 +44,30 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::marker::PhantomData; -use frame_support::{ - dispatch::DispatchResult, decl_module, decl_storage, decl_event, - weights::{ - SimpleDispatchInfo, DispatchInfo, DispatchClass, ClassifyDispatch, WeighData, Weight, - PaysFee - }, -}; use sp_std::prelude::*; -use frame_benchmarking::{benchmarks, account}; -use frame_system::{self as system, ensure_signed, ensure_root, RawOrigin}; use codec::{Encode, Decode}; -use sp_runtime::{ - traits::{SignedExtension, Bounded, SaturatedConversion}, - transaction_validity::{ - ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity, - }, +use sp_runtime::{RuntimeDebug, traits::One}; +use frame_support::{ + dispatch::{Dispatchable, Parameter}, decl_module, decl_storage, decl_event, traits::Get, + weights::{DispatchClass, Weight, FunctionOf, GetDispatchInfo}, }; - -/// A type alias for the balance type from this pallet's point of view. -type BalanceOf = ::Balance; +//use frame_benchmarking::{benchmarks, account}; +use frame_system::{self as system}; /// Our pallet's configuration trait. All our types and constants go in here. If the /// pallet is dependent on specific other pallets, then their configuration traits /// should be added to our implied traits list. /// -/// `frame_system::Trait` should always be included in our implied traits. -pub trait Trait: pallet_balances::Trait { +/// `system::Trait` should always be included in our implied traits. +pub trait Trait: system::Trait { /// The overarching event type. - type Event: From> + Into<::Event>; + type Event: From> + Into<::Event>; /// The aggregated origin which the dispatch will take. - type Origin: From>; + type Origin: From>; /// The aggregated call type. - type Call: Parameter + Dispatchable; + type Call: Parameter + Dispatchable::Origin> + GetDispatchInfo; /// The maximum weight that may be scheduled per block for any dispatchables of less priority /// than 255. @@ -103,7 +91,7 @@ pub const LOWEST_PRORITY: Priority = 255; /// Information regarding an item to be executed in the future. #[derive(Clone, RuntimeDebug, Encode, Decode)] -pub struct Scheduled { +pub struct Scheduled { /// This task's priority. priority: Priority, /// The call to be dispatched. @@ -119,43 +107,44 @@ decl_storage! { /// Items to be executed, indexed by the block number that they should be executed on. /// This is ordered by `priority`. - Agenda: map hasher(twox_64_concat) T::BlockNumber - => Vec::Call, ::Origin>>; + Agenda: map hasher(twox_64_concat) T::BlockNumber => Vec::Call>>; /// The next free periodic index. - NextPeriodicIndex: PeriodicIndex; + LastPeriodicIndex: PeriodicIndex; } } decl_event!( - pub enum Event where B = ::Balance { - Scheduled(T::BlockNumber), + pub enum Event where ::BlockNumber { + Scheduled(BlockNumber), } ); decl_module! { // Simple declaration of the `Module` type. Lets the macro know what its working on. - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: ::Origin { fn deposit_event() = default; - #[weight = FunctionOf(|| { - let now = frame_system::Module::::block_number(); + #[weight = FunctionOf(|_| -> u32 { + let now = system::Module::::block_number(); let limit = T::MaximumWeight::get(); let mut queued = Agenda::::get(now).into_iter() .map(|i| (i.priority, i.call.get_dispatch_info().weight)) .collect::>(); - queued.sort(); - queued + queued.sort_by_key(|i| i.0); + queued.into_iter() .scan(0, |a, i| { *a += i.1; - if i.0 <= HARD_DEADLINE || *a <= limit { Some(a) } else { None } + if i.0 <= HARD_DEADLINE || *a <= limit { Some(*a) } else { None } }) .last() + .unwrap_or(0) + + 10_000 }, || DispatchClass::Normal, true)] - fn on_initialize(_n: T::BlockNumber) { + fn on_initialize(now: T::BlockNumber) { let limit = T::MaximumWeight::get(); let mut queued = Agenda::::take(now); - queued.sort(); + queued.sort_by_key(|i| i.priority); let unused_items = queued.into_iter() .scan(0, |cumulative_weight, i| { *cumulative_weight += i.call.get_dispatch_info().weight; @@ -164,27 +153,27 @@ decl_module! { .filter_map(|(cumulative_weight, i)| { if i.priority <= HARD_DEADLINE || cumulative_weight <= limit { let maybe_reschedule = i.maybe_periodic.map(|p| (p, i.clone())); - let _ = i.call.dispatch(frame_system::RawOrigin::ROOT.into()); + let _ = i.call.dispatch(system::RawOrigin::Root.into()); if let Some((periodic_index, i)) = maybe_reschedule { // Register the next instance of this task - if let Some((period, count)) = Periodic::get(periodic_index) { + if let Some((period, count)) = Periodic::::get(periodic_index) { if count > 1 { - count -= 1; - Periodic::insert(periodic_index, (period, count)); + Periodic::::insert(periodic_index, (period, count - 1)); } else { - Periodic::remove(periodic_index); + Periodic::::remove(periodic_index); } - Agenda::::append(now + period, i); + Agenda::::append_or_insert(now + period, &[i][..]); } } None } else { Some(i) } - }); + }) .collect::>(); - if !queued_items.is_empty() { - Agenda::::append_or_put(next, queued_items.into_iter()); + let next = now + One::one(); + if !unused_items.is_empty() { + Agenda::::append_or_insert(next, &unused_items[..]); } } } @@ -192,26 +181,61 @@ decl_module! { /// A type that can be used as a scheduler. pub trait Schedule { - fn schedule(when: BlockNumber, call: Call); - fn schedule_periodic(when: BlockNumber, period: BlockNumber, count: u32, call: Call); + /// Schedule a one-off dispatch to happen at the beginning of some block in the future. + fn schedule(when: BlockNumber, priority: Priority, call: Call); + + /// Schedule a dispatch to happen periodically into the future for come number of times. + /// + /// An index is returned so that it can be cancelled at some point in the future. + fn schedule_periodic( + when: BlockNumber, + period: BlockNumber, + count: u32, + priority: Priority, + call: Call, + ) -> PeriodicIndex; + + /// Cancel a periodic dispatch. If called during its dispatch, it will not be dispatched + /// any further. If called between dispatches, the next dispatch will happen as scheduled but + /// then it will cease. + fn cancel_periodic(index: PeriodicIndex) -> Result<(), ()>; } -impl Scheduler for Module { - fn schedule(when: BlockNumber, priority: Priority, call: Call) { - Agenda::::append(when, Scheduled { priority, call, maybe_periodic: None }); +impl Schedule::Call> for Module { + fn schedule(when: T::BlockNumber, priority: Priority, call: ::Call) { + let s = Scheduled { priority, call, maybe_periodic: None }; + Agenda::::append_or_insert(when, &[s][..]); } fn schedule_periodic( - when: BlockNumber, - priority: Priority, - period: BlockNumber, + when: T::BlockNumber, + period: T::BlockNumber, count: u32, - call: Call + priority: Priority, + call: ::Call, ) -> PeriodicIndex { - let index = NextPeriodicIndex::mutate(|i| { let r = *1; *i += 1; *r }); - Periodic::::insert(index, (period, count)); - Agenda::::append(when, Scheduled { priority, call, maybe_periodic: Some(index) }); - index + match count { + 0 => 0, + 1 => { + Self::schedule(when, priority, call); + 0 + } + count => { + let index = LastPeriodicIndex::mutate(|i| { *i += 1; *i }); + Periodic::::insert(index, (period, count - 1)); + let s = Scheduled { priority, call, maybe_periodic: Some(index) }; + Agenda::::append_or_insert(when, &[s][..]); + index + } + } + } + + fn cancel_periodic(index: PeriodicIndex) -> Result<(), ()> { + if index == 0 { + Err(()) + } else { + Periodic::::take(index).map(|_| ()).ok_or(()) + } } } @@ -219,7 +243,9 @@ impl Scheduler for Module { mod tests { use super::*; - use frame_support::{assert_ok, impl_outer_origin, parameter_types, weights::GetDispatchInfo}; + use frame_support::{ + impl_outer_event, impl_outer_origin, impl_outer_dispatch, parameter_types + }; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. @@ -228,11 +254,70 @@ mod tests { testing::Header, traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup}, }; + use crate as scheduler; + + mod logger { + use super::*; + use std::cell::RefCell; + use frame_system::ensure_root; + + thread_local! { + static LOG: RefCell> = RefCell::new(Vec::new()); + } + pub fn log() -> Vec { + LOG.with(|log| log.borrow().clone()) + } + pub trait Trait: system::Trait { + type Event: From + Into<::Event>; + } + decl_storage! { + trait Store for Module as Logger { + } + } + decl_event! { + pub enum Event { + Logged(u32, Weight), + } + } + decl_module! { + // Simple declaration of the `Module` type. Lets the macro know what its working on. + pub struct Module for enum Call where origin: ::Origin { + fn deposit_event() = default; + + #[weight = FunctionOf( + |args: (&u32, &Weight)| *args.1, + |_: (&u32, &Weight)| DispatchClass::Normal, + true + )] + fn log(origin, i: u32, weight: Weight) { + ensure_root(origin)?; + Self::deposit_event(Event::Logged(i, weight)); + LOG.with(|log| { + log.borrow_mut().push(i); + }) + } + } + } + } impl_outer_origin! { - pub enum Origin for Test where system = frame_system {} + pub enum Origin for Test where system = frame_system {} + } + + impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + system::System, + logger::Logger, + } } + impl_outer_event! { + pub enum Event for Test { + system, + logger, + scheduler, + } + } // For testing the pallet, we construct most of a mock runtime. This means // first constructing a configuration type (`Test`) which `impl`s each of the // configuration traits of pallets we want to use. @@ -244,12 +329,12 @@ mod tests { pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } - impl frame_system::Trait for Test { + impl system::Trait for Test { type Origin = Origin; + type Call = (); type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = (); type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; @@ -261,101 +346,137 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type AccountData = pallet_balances::AccountData; + type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } - impl pallet_balances::Trait for Test { - type Balance = u64; - type DustRemoval = (); + impl logger::Trait for Test { type Event = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; + } + parameter_types! { + pub const MaximumWeight: Weight = 10_000; } impl Trait for Test { type Event = (); + type Origin = Origin; + type Call = Call; + type MaximumWeight = MaximumWeight; } - type System = frame_system::Module; + type System = system::Module; + type Logger = logger::Module; type Scheduler = Module; // This function basically just builds a genesis storage key/value store according to // our desired mockup. fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - // We use default for brevity, but you can configure as desired if needed. - pallet_balances::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); - GenesisConfig::{ - dummy: 42, - // we configure the map with (key, value) pairs. - bar: vec![(1, 2), (2, 3)], - foo: 24, - }.assimilate_storage(&mut t).unwrap(); + let t = system::GenesisConfig::default().build_storage::().unwrap(); t.into() } + fn run_to_block(n: u64) { + while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + } + } + #[test] - fn it_works_for_optional_value() { + fn basic_scheduling_works() { new_test_ext().execute_with(|| { - // Check that GenesisBuilder works properly. - assert_eq!(Scheduler::dummy(), Some(42)); + Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(42, 1000))); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(100); + assert_eq!(logger::log(), vec![42u32]); + }); + } - // Check that accumulate works when we have Some value in Dummy already. - assert_ok!(Scheduler::accumulate_dummy(Origin::signed(1), 27)); - assert_eq!(Scheduler::dummy(), Some(69)); + #[test] + fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::schedule_periodic(4, 3, 3, 127, Call::Logger(logger::Call::log(42, 1000))); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(6); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(7); + assert_eq!(logger::log(), vec![42u32, 42u32]); + run_to_block(9); + assert_eq!(logger::log(), vec![42u32, 42u32]); + run_to_block(10); + assert_eq!(logger::log(), vec![42u32, 42u32, 42u32]); + run_to_block(100); + assert_eq!(logger::log(), vec![42u32, 42u32, 42u32]); + }); + } - // Check that finalizing the block removes Dummy from storage. - >::on_finalize(1); - assert_eq!(Scheduler::dummy(), None); + #[test] + fn cancel_period_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + let i = Scheduler::schedule_periodic(4, 3, 3, 127, Call::Logger(logger::Call::log(42, 1000))); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(6); + assert_eq!(logger::log(), vec![42u32]); + Scheduler::cancel_periodic(i).unwrap(); + run_to_block(7); + assert_eq!(logger::log(), vec![42u32, 42u32]); + run_to_block(100); + assert_eq!(logger::log(), vec![42u32, 42u32]); + }); + } - // Check that accumulate works when we Dummy has None in it. - >::on_initialize(2); - assert_ok!(Scheduler::accumulate_dummy(Origin::signed(1), 42)); - assert_eq!(Scheduler::dummy(), Some(42)); + #[test] + fn scheduler_respects_weight_limits() { + new_test_ext().execute_with(|| { + Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(69, 6000))); + run_to_block(4); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(5); + assert_eq!(logger::log(), vec![42u32, 69u32]); }); } #[test] - fn it_works_for_default_value() { + fn scheduler_respects_hard_deadlines_more() { new_test_ext().execute_with(|| { - assert_eq!(Scheduler::foo(), 24); - assert_ok!(Scheduler::accumulate_foo(Origin::signed(1), 1)); - assert_eq!(Scheduler::foo(), 25); + Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(69, 6000))); + run_to_block(4); + assert_eq!(logger::log(), vec![42u32, 69u32]); }); } #[test] - fn signed_ext_watch_dummy_works() { + fn scheduler_respects_priority_ordering() { new_test_ext().execute_with(|| { - let call = >::set_dummy(10); - let info = DispatchInfo::default(); - - assert_eq!( - WatchDummy::(PhantomData).validate(&1, &call, info, 150) - .unwrap() - .priority, - Bounded::max_value(), - ); - assert_eq!( - WatchDummy::(PhantomData).validate(&1, &call, info, 250), - InvalidTransaction::ExhaustsResources.into(), - ); - }) + Scheduler::schedule(4, 1, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(69, 6000))); + run_to_block(4); + assert_eq!(logger::log(), vec![69u32, 42u32]); + }); } #[test] - fn weights_work() { - // must have a default weight. - let default_call = >::accumulate_dummy(10); - let info = default_call.get_dispatch_info(); - // aka. `let info = as GetDispatchInfo>::get_dispatch_info(&default_call);` - assert_eq!(info.weight, 10_000); - - // must have a custom weight of `100 * arg = 2000` - let custom_call = >::set_dummy(20); - let info = custom_call.get_dispatch_info(); - assert_eq!(info.weight, 2000); + fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + Scheduler::schedule(4, 255, Call::Logger(logger::Call::log(42, 5000))); + Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(69, 5000))); + Scheduler::schedule(4, 126, Call::Logger(logger::Call::log(2600, 6000))); + run_to_block(4); + assert_eq!(logger::log(), vec![2600u32]); + run_to_block(5); + assert_eq!(logger::log(), vec![2600u32, 69u32, 42u32]); + }); } } From 876e3e59e7fc9b1f8c92f718d5c381eb1bc8af50 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Mar 2020 23:02:16 +0100 Subject: [PATCH 03/28] Make work with new initialize infratructure. --- frame/scheduler/src/lib.rs | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 61931d542c7c7..fa4017192607f 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -49,7 +49,7 @@ use codec::{Encode, Decode}; use sp_runtime::{RuntimeDebug, traits::One}; use frame_support::{ dispatch::{Dispatchable, Parameter}, decl_module, decl_storage, decl_event, traits::Get, - weights::{DispatchClass, Weight, FunctionOf, GetDispatchInfo}, + weights::{GetDispatchInfo, Weight}, }; //use frame_benchmarking::{benchmarks, account}; use frame_system::{self as system}; @@ -125,26 +125,11 @@ decl_module! { pub struct Module for enum Call where origin: ::Origin { fn deposit_event() = default; - #[weight = FunctionOf(|_| -> u32 { - let now = system::Module::::block_number(); - let limit = T::MaximumWeight::get(); - let mut queued = Agenda::::get(now).into_iter() - .map(|i| (i.priority, i.call.get_dispatch_info().weight)) - .collect::>(); - queued.sort_by_key(|i| i.0); - queued.into_iter() - .scan(0, |a, i| { - *a += i.1; - if i.0 <= HARD_DEADLINE || *a <= limit { Some(*a) } else { None } - }) - .last() - .unwrap_or(0) - + 10_000 - }, || DispatchClass::Normal, true)] - fn on_initialize(now: T::BlockNumber) { + fn on_initialize(now: T::BlockNumber) -> Weight { let limit = T::MaximumWeight::get(); let mut queued = Agenda::::take(now); queued.sort_by_key(|i| i.priority); + let mut result = 0; let unused_items = queued.into_iter() .scan(0, |cumulative_weight, i| { *cumulative_weight += i.call.get_dispatch_info().weight; @@ -165,6 +150,7 @@ decl_module! { Agenda::::append_or_insert(now + period, &[i][..]); } } + result = cumulative_weight; None } else { Some(i) @@ -175,6 +161,7 @@ decl_module! { if !unused_items.is_empty() { Agenda::::append_or_insert(next, &unused_items[..]); } + result } } } @@ -244,7 +231,8 @@ mod tests { use super::*; use frame_support::{ - impl_outer_event, impl_outer_origin, impl_outer_dispatch, parameter_types + impl_outer_event, impl_outer_origin, impl_outer_dispatch, parameter_types, + traits::{OnInitialize, OnFinalize}, weights::{DispatchClass, FunctionOf} }; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures @@ -252,7 +240,7 @@ mod tests { use sp_runtime::{ Perbill, testing::Header, - traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, }; use crate as scheduler; @@ -479,4 +467,20 @@ mod tests { assert_eq!(logger::log(), vec![2600u32, 69u32, 42u32]); }); } + + #[test] + fn initialize_weight_is_correct() { + Scheduler::schedule(1, 255, Call::Logger(logger::Call::log(3, 1000))); + Scheduler::schedule(1, 128, Call::Logger(logger::Call::log(42, 5000))); + Scheduler::schedule(1, 127, Call::Logger(logger::Call::log(69, 5000))); + Scheduler::schedule(1, 126, Call::Logger(logger::Call::log(2600, 6000))); + let weight = Scheduler::on_initialize(1); + assert_eq!(weight, 6000); + let weight = Scheduler::on_initialize(2); + assert_eq!(weight, 10000); + let weight = Scheduler::on_initialize(3); + assert_eq!(weight, 1000); + let weight = Scheduler::on_initialize(4); + assert_eq!(weight, 0); + } } From 5bed28dede89e8753990f8fd29c2cc2da5346cde Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 25 Mar 2020 10:46:45 +0100 Subject: [PATCH 04/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 61931d542c7c7..d7ccb3fdaef36 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -76,7 +76,7 @@ pub trait Trait: system::Trait { /// Just a simple index for naming period tasks. pub type PeriodicIndex = u32; -/// Priority with which a call is scheduled. It's just a linear amount with higher values meaning +/// Priority with which a call is scheduled. It's just a linear amount with lowest values meaning /// higher priority. pub type Priority = u8; From 981f27a349f2ab05b462a6ca61b5222fa341ac26 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 25 Mar 2020 10:46:56 +0100 Subject: [PATCH 05/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index d7ccb3fdaef36..28e292eb34dab 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -80,7 +80,7 @@ pub type PeriodicIndex = u32; /// higher priority. pub type Priority = u8; -/// The highest priority. We invest the value so that normal sorting will place the highest +/// 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_PRORITY: Priority = 0; /// Anything of this value or lower will definitely be scheduled on the block that the ask for, even From 6caf97e717245ea664de7e1e37e7c96f88d21218 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 25 Mar 2020 10:47:06 +0100 Subject: [PATCH 06/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 28e292eb34dab..ede71e9b766dc 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -18,7 +18,7 @@ //! //! \# Scheduler //! -//! - \[`::Trait`](./trait.Trait.html) +//! - \[`scheduler::Trait`](./trait.Trait.html) //! - \[`Call`](./enum.Call.html) //! - \[`Module`](./struct.Module.html) //! From ca175a2569cb0a12ceb8f0be743f04b56ac09478 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 25 Mar 2020 10:47:16 +0100 Subject: [PATCH 07/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ede71e9b766dc..6c982867750d3 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -83,7 +83,7 @@ pub type Priority = u8; /// 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_PRORITY: Priority = 0; -/// Anything of this value or lower will definitely be scheduled on the block that the ask for, even +/// Anything of this value or lower will definitely be scheduled on the block that they ask for, even /// if it breaches the `MaximumWeight` limitation. pub const HARD_DEADLINE: Priority = 63; /// The lowest priority. Most stuff should be around here. From eef345d6ef67db2d05b6932acbe38d5fd3173ca6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Mar 2020 10:48:43 +0100 Subject: [PATCH 08/28] Fix test --- frame/scheduler/src/lib.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index fa4017192607f..1f00996eb9b08 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -470,17 +470,19 @@ mod tests { #[test] fn initialize_weight_is_correct() { - Scheduler::schedule(1, 255, Call::Logger(logger::Call::log(3, 1000))); - Scheduler::schedule(1, 128, Call::Logger(logger::Call::log(42, 5000))); - Scheduler::schedule(1, 127, Call::Logger(logger::Call::log(69, 5000))); - Scheduler::schedule(1, 126, Call::Logger(logger::Call::log(2600, 6000))); - let weight = Scheduler::on_initialize(1); - assert_eq!(weight, 6000); - let weight = Scheduler::on_initialize(2); - assert_eq!(weight, 10000); - let weight = Scheduler::on_initialize(3); - assert_eq!(weight, 1000); - let weight = Scheduler::on_initialize(4); - assert_eq!(weight, 0); + new_test_ext().execute_with(|| { + Scheduler::schedule(1, 255, Call::Logger(logger::Call::log(3, 1000))); + Scheduler::schedule(1, 128, Call::Logger(logger::Call::log(42, 5000))); + Scheduler::schedule(1, 127, Call::Logger(logger::Call::log(69, 5000))); + Scheduler::schedule(1, 126, Call::Logger(logger::Call::log(2600, 6000))); + let weight = Scheduler::on_initialize(1); + assert_eq!(weight, 6000); + let weight = Scheduler::on_initialize(2); + assert_eq!(weight, 10000); + let weight = Scheduler::on_initialize(3); + assert_eq!(weight, 1000); + let weight = Scheduler::on_initialize(4); + assert_eq!(weight, 0); + }); } } From fd98570ead9702844619255ae4ebbcb95d9eb08c Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 25 Mar 2020 10:49:34 +0100 Subject: [PATCH 09/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index e660acff71538..253ff2d74a527 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -51,7 +51,6 @@ use frame_support::{ dispatch::{Dispatchable, Parameter}, decl_module, decl_storage, decl_event, traits::Get, weights::{GetDispatchInfo, Weight}, }; -//use frame_benchmarking::{benchmarks, account}; use frame_system::{self as system}; /// Our pallet's configuration trait. All our types and constants go in here. If the From 2985d57924065156152db86f736bccd755892732 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Mar 2020 16:15:28 +0100 Subject: [PATCH 10/28] Rejig interface to make it more useful for democracy. --- frame/democracy/src/lib.rs | 6 +- frame/scheduler/src/lib.rs | 258 ++++++++++++++++++++---------------- frame/support/src/traits.rs | 80 ++++++++++- 3 files changed, 228 insertions(+), 116 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index a4366c4ef23ee..1affba7706856 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -173,7 +173,7 @@ use frame_support::{ weights::{SimpleDispatchInfo, Weight, WeighData}, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, - OnUnbalanced, BalanceStatus + OnUnbalanced, BalanceStatus, schedule } }; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -270,6 +270,9 @@ pub trait Trait: frame_system::Trait + Sized { /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; + + /// The Scheduler. + type Scheduler: schedule::Named; } decl_storage! { @@ -1628,6 +1631,7 @@ impl Module { let _ = Self::enact_proposal(status.proposal_hash, index); } else { let item = (now + status.delay, status.proposal_hash, index); + >::mutate(|queue| { let pos = queue.binary_search_by_key(&item.0, |x| x.0).unwrap_or_else(|e| e); queue.insert(pos, item); diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index e660acff71538..fff9b46cb08a6 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -46,9 +46,10 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; -use sp_runtime::{RuntimeDebug, traits::One}; +use sp_runtime::{RuntimeDebug, traits::{Zero, One}}; use frame_support::{ - dispatch::{Dispatchable, Parameter}, decl_module, decl_storage, decl_event, traits::Get, + dispatch::{Dispatchable, DispatchResult, Parameter}, decl_module, decl_storage, decl_event, + traits::{Get, schedule}, weights::{GetDispatchInfo, Weight}, }; //use frame_benchmarking::{benchmarks, account}; @@ -76,47 +77,37 @@ pub trait Trait: system::Trait { /// Just a simple index for naming period tasks. pub type PeriodicIndex = u32; -/// Priority with which a call is scheduled. It's just a linear amount with lowest values meaning -/// higher priority. -pub type Priority = u8; - -/// 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_PRORITY: Priority = 0; -/// Anything of this value or lower will definitely be scheduled on the block that they ask for, even -/// if it breaches the `MaximumWeight` limitation. -pub const HARD_DEADLINE: Priority = 63; -/// The lowest priority. Most stuff should be around here. -pub const LOWEST_PRORITY: Priority = 255; +/// The location of a scheduled task that can be used to remove it. +pub type TaskAddress = (BlockNumber, u32); /// Information regarding an item to be executed in the future. #[derive(Clone, RuntimeDebug, Encode, Decode)] -pub struct Scheduled { +pub struct Scheduled { + /// The unique identity for this task, if there is one. + maybe_id: Option>, /// This task's priority. - priority: Priority, + priority: schedule::Priority, /// The call to be dispatched. call: Call, /// If the call is periodic, then this points to the information concerning that. - maybe_periodic: Option, + maybe_periodic: Option>, } decl_storage! { trait Store for Module as Scheduler { - /// Any period items. - Periodic: map hasher(twox_64_concat) PeriodicIndex => Option<(T::BlockNumber, u32)>; - /// Items to be executed, indexed by the block number that they should be executed on. - /// This is ordered by `priority`. - Agenda: map hasher(twox_64_concat) T::BlockNumber => Vec::Call>>; + Agenda: map hasher(twox_64_concat) T::BlockNumber + => Vec::Call, T::BlockNumber>>>; - /// The next free periodic index. - LastPeriodicIndex: PeriodicIndex; + /// Lookup from identity to the block number and index of the task. + Lookup: map hasher(twox_64_concat) Vec => Option>; } } decl_event!( pub enum Event where ::BlockNumber { Scheduled(BlockNumber), + Dispatched(TaskAddress, Option>, DispatchResult), } ); @@ -127,38 +118,48 @@ decl_module! { fn on_initialize(now: T::BlockNumber) -> Weight { let limit = T::MaximumWeight::get(); - let mut queued = Agenda::::take(now); - queued.sort_by_key(|i| i.priority); + let mut queued = Agenda::::take(now).into_iter() + .enumerate() + .filter_map(|(index, s)| s.map(|inner| (index as u32, inner))) + .collect::>(); + queued.sort_by_key(|(_, s)| s.priority); let mut result = 0; let unused_items = queued.into_iter() - .scan(0, |cumulative_weight, i| { - *cumulative_weight += i.call.get_dispatch_info().weight; - Some((*cumulative_weight, i)) + .scan(0, |cumulative_weight, (index, s)| { + *cumulative_weight += s.call.get_dispatch_info().weight; + Some((index, *cumulative_weight, s)) }) - .filter_map(|(cumulative_weight, i)| { - if i.priority <= HARD_DEADLINE || cumulative_weight <= limit { - let maybe_reschedule = i.maybe_periodic.map(|p| (p, i.clone())); - let _ = i.call.dispatch(system::RawOrigin::Root.into()); - if let Some((periodic_index, i)) = maybe_reschedule { - // Register the next instance of this task - if let Some((period, count)) = Periodic::::get(periodic_index) { - if count > 1 { - Periodic::::insert(periodic_index, (period, count - 1)); - } else { - Periodic::::remove(periodic_index); - } - Agenda::::append_or_insert(now + period, &[i][..]); + .filter_map(|(index, cumulative_weight, mut s)| { + if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit { + let r = s.call.clone().dispatch(system::RawOrigin::Root.into()); + let maybe_id = s.maybe_id.clone(); + if let &Some((period, count)) = &s.maybe_periodic { + if count > 1 { + s.maybe_periodic = Some((period, count - 1)); + } else { + s.maybe_periodic = None; + } + let next = now + period; + if let Some(ref id) = s.maybe_id { + let next_index = Agenda::::decode_len(now + period).unwrap_or(0) as u32; + Lookup::::insert(id, (next, next_index)); + } + Agenda::::append_or_insert(next, &[Some(s)][..]); + } else { + if let Some(ref id) = s.maybe_id { + Lookup::::remove(id); } } + Self::deposit_event(RawEvent::Dispatched((now, index), maybe_id, r)); result = cumulative_weight; None } else { - Some(i) + Some(Some(s)) } }) .collect::>(); - let next = now + One::one(); if !unused_items.is_empty() { + let next = now + One::one(); Agenda::::append_or_insert(next, &unused_items[..]); } result @@ -166,62 +167,74 @@ decl_module! { } } -/// A type that can be used as a scheduler. -pub trait Schedule { - /// Schedule a one-off dispatch to happen at the beginning of some block in the future. - fn schedule(when: BlockNumber, priority: Priority, call: Call); - - /// Schedule a dispatch to happen periodically into the future for come number of times. - /// - /// An index is returned so that it can be cancelled at some point in the future. - fn schedule_periodic( - when: BlockNumber, - period: BlockNumber, - count: u32, - priority: Priority, - call: Call, - ) -> PeriodicIndex; - - /// Cancel a periodic dispatch. If called during its dispatch, it will not be dispatched - /// any further. If called between dispatches, the next dispatch will happen as scheduled but - /// then it will cease. - fn cancel_periodic(index: PeriodicIndex) -> Result<(), ()>; -} +impl schedule::Anon::Call> for Module { + type Address = TaskAddress; -impl Schedule::Call> for Module { - fn schedule(when: T::BlockNumber, priority: Priority, call: ::Call) { - let s = Scheduled { priority, call, maybe_periodic: None }; + fn schedule( + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: ::Call + ) -> Self::Address { + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + let s = Some(Scheduled { maybe_id: None, priority, call, maybe_periodic }); Agenda::::append_or_insert(when, &[s][..]); + (when, Agenda::::decode_len(when).unwrap_or(1) as u32 - 1) } - fn schedule_periodic( + fn cancel((when, index): Self::Address) -> Result<(), ()> { + if let Some(s) = Agenda::::mutate(when, |agenda| agenda.get_mut(index as usize).and_then(Option::take)) { + if let Some(id) = s.maybe_id { + Lookup::::remove(id) + } + Ok(()) + } else { + Err(()) + } + } +} + +impl schedule::Named::Call> for Module { + type Address = TaskAddress; + + fn schedule_named( + id: Id, when: T::BlockNumber, - period: T::BlockNumber, - count: u32, - priority: Priority, + maybe_periodic: Option>, + priority: schedule::Priority, call: ::Call, - ) -> PeriodicIndex { - match count { - 0 => 0, - 1 => { - Self::schedule(when, priority, call); - 0 - } - count => { - let index = LastPeriodicIndex::mutate(|i| { *i += 1; *i }); - Periodic::::insert(index, (period, count - 1)); - let s = Scheduled { priority, call, maybe_periodic: Some(index) }; - Agenda::::append_or_insert(when, &[s][..]); - index - } + ) -> Result { + // determine id and ensure it is unique + let id = id.encode(); + if Lookup::::contains_key(&id) { + return Err(()) } + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + + let s = Scheduled { maybe_id: Some(id.clone()), priority, call, maybe_periodic }; + Agenda::::append_or_insert(when, &[Some(s)][..]); + let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; + let address = (when, index); + Lookup::::insert(&id, &address); + Ok(address) } - fn cancel_periodic(index: PeriodicIndex) -> Result<(), ()> { - if index == 0 { - Err(()) + fn cancel_named(id: Id) -> Result<(), ()> { + if let Some((when, index)) = id.using_encoded(|d| Lookup::::take(d)) { + let i = index as usize; + Agenda::::mutate(when, |agenda| if let Some(s) = agenda.get_mut(i) { *s = None }); + Ok(()) } else { - Periodic::::take(index).map(|_| ()).ok_or(()) + Err(()) } } } @@ -231,8 +244,9 @@ mod tests { use super::*; use frame_support::{ - impl_outer_event, impl_outer_origin, impl_outer_dispatch, parameter_types, - traits::{OnInitialize, OnFinalize}, weights::{DispatchClass, FunctionOf} + impl_outer_event, impl_outer_origin, impl_outer_dispatch, parameter_types, assert_ok, + traits::{OnInitialize, OnFinalize, schedule::{Anon, Named}}, + weights::{DispatchClass, FunctionOf} }; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures @@ -372,7 +386,7 @@ mod tests { #[test] fn basic_scheduling_works() { new_test_ext().execute_with(|| { - Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(42, 1000))); + Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(42, 1000))); run_to_block(3); assert!(logger::log().is_empty()); run_to_block(4); @@ -386,7 +400,7 @@ mod tests { fn periodic_scheduling_works() { new_test_ext().execute_with(|| { // at #4, every 3 blocks, 3 times. - Scheduler::schedule_periodic(4, 3, 3, 127, Call::Logger(logger::Call::log(42, 1000))); + Scheduler::schedule(4, Some((3, 3)), 127, Call::Logger(logger::Call::log(42, 1000))); run_to_block(3); assert!(logger::log().is_empty()); run_to_block(4); @@ -405,29 +419,45 @@ mod tests { } #[test] - fn cancel_period_scheduling_works() { + fn cancel_named_scheduling_works_with_normal_cancel() { new_test_ext().execute_with(|| { // at #4, every 3 blocks, 3 times. - let i = Scheduler::schedule_periodic(4, 3, 3, 127, Call::Logger(logger::Call::log(42, 1000))); + Scheduler::schedule_named(1u32, 4, None, 127, Call::Logger(logger::Call::log(69, 1000))).unwrap(); + let i = Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(42, 1000))); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(1u32)); + assert_ok!(Scheduler::cancel(i)); + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn cancel_named_periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::schedule_named(1u32, 4, Some((3, 3)), 127, Call::Logger(logger::Call::log(42, 1000))).unwrap(); + // same id results in error. + assert!(Scheduler::schedule_named(1u32, 4, None, 127, Call::Logger(logger::Call::log(69, 1000))).is_err()); + // different id is ok. + Scheduler::schedule_named(2u32, 8, None, 127, Call::Logger(logger::Call::log(69, 1000))).unwrap(); run_to_block(3); assert!(logger::log().is_empty()); run_to_block(4); assert_eq!(logger::log(), vec![42u32]); run_to_block(6); - assert_eq!(logger::log(), vec![42u32]); - Scheduler::cancel_periodic(i).unwrap(); - run_to_block(7); - assert_eq!(logger::log(), vec![42u32, 42u32]); + assert_ok!(Scheduler::cancel_named(1u32)); run_to_block(100); - assert_eq!(logger::log(), vec![42u32, 42u32]); + assert_eq!(logger::log(), vec![42u32, 69u32]); }); } #[test] fn scheduler_respects_weight_limits() { new_test_ext().execute_with(|| { - Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(42, 6000))); - Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(69, 6000))); + Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(69, 6000))); run_to_block(4); assert_eq!(logger::log(), vec![42u32]); run_to_block(5); @@ -438,8 +468,8 @@ mod tests { #[test] fn scheduler_respects_hard_deadlines_more() { new_test_ext().execute_with(|| { - Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(42, 6000))); - Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(69, 6000))); + Scheduler::schedule(4, None, 0, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, None, 0, Call::Logger(logger::Call::log(69, 6000))); run_to_block(4); assert_eq!(logger::log(), vec![42u32, 69u32]); }); @@ -448,8 +478,8 @@ mod tests { #[test] fn scheduler_respects_priority_ordering() { new_test_ext().execute_with(|| { - Scheduler::schedule(4, 1, Call::Logger(logger::Call::log(42, 6000))); - Scheduler::schedule(4, 0, Call::Logger(logger::Call::log(69, 6000))); + Scheduler::schedule(4, None, 1, Call::Logger(logger::Call::log(42, 6000))); + Scheduler::schedule(4, None, 0, Call::Logger(logger::Call::log(69, 6000))); run_to_block(4); assert_eq!(logger::log(), vec![69u32, 42u32]); }); @@ -458,9 +488,9 @@ mod tests { #[test] fn scheduler_respects_priority_ordering_with_soft_deadlines() { new_test_ext().execute_with(|| { - Scheduler::schedule(4, 255, Call::Logger(logger::Call::log(42, 5000))); - Scheduler::schedule(4, 127, Call::Logger(logger::Call::log(69, 5000))); - Scheduler::schedule(4, 126, Call::Logger(logger::Call::log(2600, 6000))); + Scheduler::schedule(4, None, 255, Call::Logger(logger::Call::log(42, 5000))); + Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(69, 5000))); + Scheduler::schedule(4, None, 126, Call::Logger(logger::Call::log(2600, 6000))); run_to_block(4); assert_eq!(logger::log(), vec![2600u32]); run_to_block(5); @@ -471,10 +501,10 @@ mod tests { #[test] fn initialize_weight_is_correct() { new_test_ext().execute_with(|| { - Scheduler::schedule(1, 255, Call::Logger(logger::Call::log(3, 1000))); - Scheduler::schedule(1, 128, Call::Logger(logger::Call::log(42, 5000))); - Scheduler::schedule(1, 127, Call::Logger(logger::Call::log(69, 5000))); - Scheduler::schedule(1, 126, Call::Logger(logger::Call::log(2600, 6000))); + Scheduler::schedule(1, None, 255, Call::Logger(logger::Call::log(3, 1000))); + Scheduler::schedule(1, None, 128, Call::Logger(logger::Call::log(42, 5000))); + Scheduler::schedule(1, None, 127, Call::Logger(logger::Call::log(69, 5000))); + Scheduler::schedule(1, None, 126, Call::Logger(logger::Call::log(2600, 6000))); let weight = Scheduler::on_initialize(1); assert_eq!(weight, 6000); let weight = Scheduler::on_initialize(2); diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index bd1534bac506e..39b1c7571d184 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -19,7 +19,7 @@ //! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module. use sp_std::{prelude::*, result, marker::PhantomData, ops::Div, fmt::Debug}; -use codec::{FullCodec, Codec, Encode, Decode}; +use codec::{FullCodec, Codec, Encode, Decode, EncodeLike}; use sp_core::u32_trait::Value as U32; use sp_runtime::{ RuntimeDebug, @@ -1109,6 +1109,84 @@ pub trait OffchainWorker { fn offchain_worker(_n: BlockNumber) {} } +pub mod schedule { + use super::*; + + /// Information relating to the period of a scheduled task. First item is the length of the + /// period and the second is the number of times it should be executed in total before the task + /// is considered finished and removed. + pub type Period = (BlockNumber, u32); + + /// Priority with which a call is scheduled. It's just a linear amount with lowest values meaning + /// higher priority. + pub type Priority = u8; + + /// 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_PRORITY: Priority = 0; + /// Anything of this value or lower will definitely be scheduled on the block that they ask for, even + /// if it breaches the `MaximumWeight` limitation. + pub const HARD_DEADLINE: Priority = 63; + /// The lowest priority. Most stuff should be around here. + pub const LOWEST_PRORITY: Priority = 255; + + /// A type that can be used as a scheduler. + pub trait Anon { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + Debug; + + /// Schedule a one-off dispatch to happen at the beginning of some block in the future. + /// + /// This is not named. + /// + /// Infallible. + fn schedule( + when: BlockNumber, + maybe_periodic: Option>, + priority: Priority, + call: Call + ) -> Self::Address; + + /// Cancel a scheduled task. If periodic, then it will cancel all further instances of that, + /// also. + /// + /// Will return an error if the `address` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + /// + /// NOTE2: This will not work to cancel periodic tasks after their initial execution. For + /// that, you must name the task explicitly using the `Named` trait. + fn cancel(address: Self::Address) -> Result<(), ()>; + } + + /// A type that can be used as a scheduler. + pub trait Named { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug; + + /// Schedule a one-off dispatch to happen at the beginning of some block in the future. + /// + /// - `id`: The identity of the task. This must be unique and will return an error if not. + fn schedule_named( + id: Id, + when: BlockNumber, + maybe_periodic: Option>, + priority: Priority, + call: Call + ) -> Result; + + /// Cancel a scheduled, named task. If periodic, then it will cancel all further instances + /// of that, also. + /// + /// Will return an error if the `id` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + fn cancel_named(id: Id) -> Result<(), ()>; + } +} + #[cfg(test)] mod tests { use super::*; From 8c119dbbc02216bfb870cf68f33a4acd2200c4db Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Mar 2020 16:48:47 +0100 Subject: [PATCH 11/28] Try to get democraxy module to make use of scheduler. --- frame/democracy/src/lib.rs | 85 ++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 1affba7706856..fbef5f3cd59e1 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -203,7 +203,7 @@ type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: frame_system::Trait + Sized { - type Proposal: Parameter + Dispatchable; + type Proposal: Parameter + Dispatchable + From>; type Event: From> + Into<::Event>; /// Currency type for this module. @@ -275,6 +275,11 @@ pub trait Trait: frame_system::Trait + Sized { type Scheduler: schedule::Named; } +pub enum PreimageStatus { + Missing, + Available((Vec, AccountId, Balance, BlockNumber)), +} + decl_storage! { trait Store for Module as Democracy { // TODO: Refactor public proposal queue into its own pallet. @@ -293,7 +298,7 @@ decl_storage! { // https://github.com/paritytech/substrate/issues/5322 pub Preimages: map hasher(identity) T::Hash - => Option<(Vec, T::AccountId, BalanceOf, T::BlockNumber)>; + => Option, T::BlockNumber>>; /// The next free referendum index, aka the number of referenda started so far. pub ReferendumCount get(fn referendum_count) build(|_| 0 as ReferendumIndex): ReferendumIndex; @@ -306,11 +311,6 @@ decl_storage! { map hasher(twox_64_concat) ReferendumIndex => Option>>; - // TODO: Refactor DispatchQueue into its own pallet. - // https://github.com/paritytech/substrate/issues/5322 - /// Queue of successful referenda to be dispatched. Stored ordered by block number. - pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; - /// All votes for a particular voter. We store the balance for the number of votes that we /// have recorded. The second item is the total amount of delegations, that will be added. pub VotingOf: map hasher(twox_64_concat) T::AccountId => Voting, T::AccountId, T::BlockNumber>; @@ -811,11 +811,8 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_queued(origin, which: ReferendumIndex) { ensure_root(origin)?; - let mut items = >::get(); - let original_len = items.len(); - items.retain(|i| i.2 != which); - ensure!(items.len() < original_len, Error::::ProposalMissing); - >::put(items); + T::Scheduler::cancel_named((b"democracy/dispatch/", ReferendumIndex)) + .map_err(|_| Error::::ProposalMissing) } fn on_initialize(n: T::BlockNumber) -> Weight { @@ -981,7 +978,8 @@ decl_module! { T::Currency::reserve(&who, deposit)?; let now = >::block_number(); - >::insert(proposal_hash, (encoded_proposal, who.clone(), deposit, now)); + let a = PreimageStatus::Available((encoded_proposal, who.clone(), deposit, now)); + >::insert(proposal_hash, a); Self::deposit_event(RawEvent::PreimageNoted(proposal_hash, who, deposit)); } @@ -1002,13 +1000,13 @@ decl_module! { fn note_imminent_preimage(origin, encoded_proposal: Vec) { let who = ensure_signed(origin)?; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - ensure!(!>::contains_key(&proposal_hash), Error::::DuplicatePreimage); - let queue = >::get(); - ensure!(queue.iter().any(|item| &item.1 == &proposal_hash), Error::::NotImminent); + let status = Preimages::::get(&proposal_hash).ok_or(Error::::NotImminent)?; + ensure!(matches!(status, PreimageStatus::Missing), Error::::DuplicatePreimage); let now = >::block_number(); let free = >::zero(); - >::insert(proposal_hash, (encoded_proposal, who.clone(), free, now)); + let a = PreimageStatus::Available((encoded_proposal, who.clone(), deposit, now)); + >::insert(proposal_hash, a); Self::deposit_event(RawEvent::PreimageNoted(proposal_hash, who, free)); } @@ -1039,8 +1037,7 @@ decl_module! { let additional = if who == old { Zero::zero() } else { enactment }; ensure!(now >= then + voting + additional, Error::::TooEarly); - let queue = >::get(); - ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::::Imminent); + //TODO: Consider explicit protection for scheduled proposals. let _ = T::Currency::repatriate_reserved(&old, &who, deposit, BalanceStatus::Free); >::remove(&proposal_hash); @@ -1217,6 +1214,30 @@ decl_module! { let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; Self::try_remove_vote(&target, index, UnvoteScope::Any) } + + /// Enact a proposal from a referendum. + #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] + fn enact_proposal(origin, proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { + ensure_root(origin)?; + if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { + if let Ok(proposal) = T::Proposal::decode(&mut &encoded_proposal[..]) { + let _ = T::Currency::unreserve(&who, amount); + Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, who, amount)); + + let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); + Self::deposit_event(RawEvent::Executed(index, ok)); + + Ok(()) + } else { + T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); + Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); + Err(Error::::PreimageInvalid.into()) + } + } else { + Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); + Err(Error::::PreimageMissing.into()) + } + } } } @@ -1630,12 +1651,17 @@ impl Module { if status.delay.is_zero() { let _ = Self::enact_proposal(status.proposal_hash, index); } else { - let item = (now + status.delay, status.proposal_hash, index); - - >::mutate(|queue| { - let pos = queue.binary_search_by_key(&item.0, |x| x.0).unwrap_or_else(|e| e); - queue.insert(pos, item); + /// Note that we need the preimage now. + Preimages::::mutate_exists(&status.proposal_hash, |pre| if pre.is_none() { + *pre = Some(PreimageStatus::Missing) }); + T::Scheduler::schedule_named( + (b"/democracy/dispatch", index), + now + status.delay, + None, + 63, + Call::enact_proposal(status.proposal_hash, index) + ); } } else { Self::deposit_event(RawEvent::NotPassed(index)); @@ -1658,17 +1684,6 @@ impl Module { let approved = Self::bake_referendum(now, index, info)?; ReferendumInfoOf::::insert(index, ReferendumInfo::Finished { end: now, approved }); } - - let queue = >::get(); - let mut used = 0; - // It's stored in order, so the earliest will always be at the start. - for &(_, proposal_hash, index) in queue.iter().take_while(|x| x.0 == now) { - let _ = Self::enact_proposal(proposal_hash.clone(), index); - used += 1; - } - if used != 0 { - >::put(&queue[used..]); - } Ok(()) } } From 2c783f42b4ebd28393b57e78485a9de6295c2a2b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 11:00:40 +0100 Subject: [PATCH 12/28] Make democracy use scheduler. --- Cargo.lock | 4 +- frame/democracy/Cargo.toml | 1 + frame/democracy/src/lib.rs | 159 +++++++++++++--------- frame/democracy/src/tests.rs | 14 +- frame/democracy/src/tests/cancellation.rs | 6 +- frame/democracy/src/tests/preimage.rs | 1 + frame/democracy/src/tests/voting.rs | 4 +- frame/elections-phragmen/Cargo.toml | 1 + frame/scheduler/Cargo.toml | 2 +- frame/scheduler/src/lib.rs | 10 +- frame/support/src/traits.rs | 6 +- 11 files changed, 121 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bb13ea3b958d..db76dc63a236c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4147,6 +4147,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", + "pallet-scheduler", "parity-scale-codec", "serde", "sp-core", @@ -4180,6 +4181,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", + "pallet-scheduler", "parity-scale-codec", "serde", "sp-core", @@ -4419,7 +4421,7 @@ dependencies = [ [[package]] name = "pallet-scheduler" -version = "2.0.0-alpha.4" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index f7dd0b7f0a3c7..9d31daf559f80 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -20,6 +20,7 @@ frame-system = { version = "2.0.0-alpha.5", default-features = false, path = ".. [dev-dependencies] sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +pallet-scheduler = { version = "2.0.0-alpha.5", path = "../scheduler" } sp-storage = { version = "2.0.0-alpha.5", path = "../../primitives/storage" } hex-literal = "0.2.1" diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index fbef5f3cd59e1..2664e54ef7b99 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -165,15 +165,16 @@ use sp_std::prelude::*; use sp_runtime::{ - DispatchResult, DispatchError, traits::{Zero, EnsureOrigin, Hash, Dispatchable, Saturating}, + DispatchResult, DispatchError, RuntimeDebug, + traits::{Zero, EnsureOrigin, Hash, Dispatchable, Saturating}, }; -use codec::{Ref, Decode}; +use codec::{Ref, Encode, Decode}; use frame_support::{ decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, weights::{SimpleDispatchInfo, Weight, WeighData}, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, - OnUnbalanced, BalanceStatus, schedule + OnUnbalanced, BalanceStatus, schedule::Named as ScheduleNamed } }; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -272,12 +273,31 @@ pub trait Trait: frame_system::Trait + Sized { type Slash: OnUnbalanced>; /// The Scheduler. - type Scheduler: schedule::Named; + type Scheduler: ScheduleNamed; } +#[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum PreimageStatus { - Missing, - Available((Vec, AccountId, Balance, BlockNumber)), + /// The preimage is imminently needed at the argument. + Missing(BlockNumber), + /// The preimage is available. + Available { + data: Vec, + provider: AccountId, + deposit: Balance, + since: BlockNumber, + /// None if it's not imminent. + expiry: Option, + }, +} + +impl PreimageStatus { + fn to_missing_expiry(self) -> Option { + match self { + PreimageStatus::Missing(expiry) => Some(expiry), + _ => None, + } + } } decl_storage! { @@ -811,8 +831,8 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_queued(origin, which: ReferendumIndex) { ensure_root(origin)?; - T::Scheduler::cancel_named((b"democracy/dispatch/", ReferendumIndex)) - .map_err(|_| Error::::ProposalMissing) + T::Scheduler::cancel_named((DEMOCRACY_ID, which)) + .map_err(|_| Error::::ProposalMissing)?; } fn on_initialize(n: T::BlockNumber) -> Weight { @@ -978,7 +998,13 @@ decl_module! { T::Currency::reserve(&who, deposit)?; let now = >::block_number(); - let a = PreimageStatus::Available((encoded_proposal, who.clone(), deposit, now)); + let a = PreimageStatus::Available { + data: encoded_proposal, + provider: who.clone(), + deposit, + since: now, + expiry: None, + }; >::insert(proposal_hash, a); Self::deposit_event(RawEvent::PreimageNoted(proposal_hash, who, deposit)); @@ -1001,11 +1027,17 @@ decl_module! { let who = ensure_signed(origin)?; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let status = Preimages::::get(&proposal_hash).ok_or(Error::::NotImminent)?; - ensure!(matches!(status, PreimageStatus::Missing), Error::::DuplicatePreimage); + let expiry = status.to_missing_expiry().ok_or(Error::::DuplicatePreimage)?; let now = >::block_number(); let free = >::zero(); - let a = PreimageStatus::Available((encoded_proposal, who.clone(), deposit, now)); + let a = PreimageStatus::Available { + data: encoded_proposal, + provider: who.clone(), + deposit: Zero::zero(), + since: now, + expiry: Some(expiry), + }; >::insert(proposal_hash, a); Self::deposit_event(RawEvent::PreimageNoted(proposal_hash, who, free)); @@ -1029,19 +1061,22 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn reap_preimage(origin, proposal_hash: T::Hash) { let who = ensure_signed(origin)?; + let (provider, deposit, since, expiry) = >::get(&proposal_hash) + .and_then(|m| match m { + PreimageStatus::Available { provider, deposit, since, expiry, .. } + => Some((provider, deposit, since, expiry)), + _ => None, + }).ok_or(Error::::PreimageMissing)?; - let (_, old, deposit, then) = >::get(&proposal_hash) - .ok_or(Error::::PreimageMissing)?; let now = >::block_number(); let (voting, enactment) = (T::VotingPeriod::get(), T::EnactmentPeriod::get()); - let additional = if who == old { Zero::zero() } else { enactment }; - ensure!(now >= then + voting + additional, Error::::TooEarly); + let additional = if who == provider { Zero::zero() } else { enactment }; + ensure!(now >= since + voting + additional, Error::::TooEarly); + ensure!(expiry.map_or(true, |e| now > e), Error::::Imminent); - //TODO: Consider explicit protection for scheduled proposals. - - let _ = T::Currency::repatriate_reserved(&old, &who, deposit, BalanceStatus::Free); + let _ = T::Currency::repatriate_reserved(&provider, &who, deposit, BalanceStatus::Free); >::remove(&proposal_hash); - Self::deposit_event(RawEvent::PreimageReaped(proposal_hash, old, deposit, who)); + Self::deposit_event(RawEvent::PreimageReaped(proposal_hash, provider, deposit, who)); } /// Unlock tokens that have an expired lock. @@ -1219,24 +1254,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] fn enact_proposal(origin, proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { ensure_root(origin)?; - if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { - if let Ok(proposal) = T::Proposal::decode(&mut &encoded_proposal[..]) { - let _ = T::Currency::unreserve(&who, amount); - Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, who, amount)); - - let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); - Self::deposit_event(RawEvent::Executed(index, ok)); - - Ok(()) - } else { - T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); - Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); - Err(Error::::PreimageInvalid.into()) - } - } else { - Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); - Err(Error::::PreimageMissing.into()) - } + Self::do_enact_proposal(proposal_hash, index) } } } @@ -1560,28 +1578,6 @@ impl Module { ref_index } - /// Enact a proposal from a referendum. - fn enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { - if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { - if let Ok(proposal) = T::Proposal::decode(&mut &encoded_proposal[..]) { - let _ = T::Currency::unreserve(&who, amount); - Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, who, amount)); - - let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); - Self::deposit_event(RawEvent::Executed(index, ok)); - - Ok(()) - } else { - T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); - Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); - Err(Error::::PreimageInvalid.into()) - } - } else { - Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); - Err(Error::::PreimageMissing.into()) - } - } - /// Table the next waiting proposal for a vote. fn launch_next(now: T::BlockNumber) -> DispatchResult { if LastTabledWasExternal::take() { @@ -1638,6 +1634,28 @@ impl Module { } } + fn do_enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { + let preimage = >::take(&proposal_hash); + if let Some(PreimageStatus::Available { data, provider, deposit, .. }) = preimage { + if let Ok(proposal) = T::Proposal::decode(&mut &data[..]) { + let _ = T::Currency::unreserve(&provider, deposit); + Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, provider, deposit)); + + let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); + Self::deposit_event(RawEvent::Executed(index, ok)); + + Ok(()) + } else { + T::Slash::on_unbalanced(T::Currency::slash_reserved(&provider, deposit).0); + Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); + Err(Error::::PreimageInvalid.into()) + } + } else { + Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); + Err(Error::::PreimageMissing.into()) + } + } + fn bake_referendum( now: T::BlockNumber, index: ReferendumIndex, @@ -1649,19 +1667,24 @@ impl Module { if approved { Self::deposit_event(RawEvent::Passed(index)); if status.delay.is_zero() { - let _ = Self::enact_proposal(status.proposal_hash, index); + let _ = Self::do_enact_proposal(status.proposal_hash, index); } else { - /// Note that we need the preimage now. - Preimages::::mutate_exists(&status.proposal_hash, |pre| if pre.is_none() { - *pre = Some(PreimageStatus::Missing) + let when = now + status.delay; + // Note that we need the preimage now. + Preimages::::mutate_exists(&status.proposal_hash, |maybe_pre| match *maybe_pre { + Some(PreimageStatus::Available { ref mut expiry, .. }) => *expiry = Some(when), + ref mut a => *a = Some(PreimageStatus::Missing(when)), }); - T::Scheduler::schedule_named( - (b"/democracy/dispatch", index), - now + status.delay, + + if T::Scheduler::schedule_named( + (DEMOCRACY_ID, index), + when, None, 63, - Call::enact_proposal(status.proposal_hash, index) - ); + Call::enact_proposal(status.proposal_hash, index).into(), + ).is_err() { + frame_support::print("LOGIC ERROR: bake_referendum/schedule_named failed"); + } } } else { Self::deposit_event(RawEvent::NotPassed(index)); diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index f2544470aa722..86619c677ed48 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -21,7 +21,7 @@ use std::cell::RefCell; use codec::Encode; use frame_support::{ impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, - ord_parameter_types, traits::Contains, weights::Weight, + ord_parameter_types, traits::{Contains, OnInitialize}, weights::Weight, }; use sp_core::H256; use sp_runtime::{ @@ -71,7 +71,7 @@ impl frame_system::Trait for Test { type Origin = Origin; type Index = u64; type BlockNumber = u64; - type Call = (); + type Call = Call; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; @@ -90,6 +90,13 @@ impl frame_system::Trait for Test { } parameter_types! { pub const ExistentialDeposit: u64 = 1; + pub const MaximumWeight: u32 = 1000000; +} +impl pallet_scheduler::Trait for Test { + type Event = (); + type Origin = Origin; + type Call = Call; + type MaximumWeight = MaximumWeight; } impl pallet_balances::Trait for Test { type Balance = u64; @@ -152,6 +159,7 @@ impl super::Trait for Test { type Slash = (); type InstantOrigin = EnsureSignedBy; type InstantAllowed = InstantAllowed; + type Scheduler = Scheduler; } fn new_test_ext() -> sp_io::TestExternalities { @@ -165,6 +173,7 @@ fn new_test_ext() -> sp_io::TestExternalities { type System = frame_system::Module; type Balances = pallet_balances::Module; +type Scheduler = pallet_scheduler::Module; type Democracy = Module; #[test] @@ -213,6 +222,7 @@ fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchRes fn next_block() { System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); } diff --git a/frame/democracy/src/tests/cancellation.rs b/frame/democracy/src/tests/cancellation.rs index c0e1b8b27ae40..217a384bb976e 100644 --- a/frame/democracy/src/tests/cancellation.rs +++ b/frame/democracy/src/tests/cancellation.rs @@ -51,13 +51,11 @@ fn cancel_queued_should_work() { fast_forward_to(4); - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); - assert_eq!(Democracy::dispatch_queue(), vec![]); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); }); } diff --git a/frame/democracy/src/tests/preimage.rs b/frame/democracy/src/tests/preimage.rs index 1fb805f72630d..9f15c35a31465 100644 --- a/frame/democracy/src/tests/preimage.rs +++ b/frame/democracy/src/tests/preimage.rs @@ -159,6 +159,7 @@ fn reaping_imminent_preimage_should_fail() { next_block(); next_block(); // now imminent. + // TODO! assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); }); } diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index 06fde99cbdb5c..8f73907e33b0a 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -85,9 +85,7 @@ fn single_proposal_should_work() { fast_forward_to(4); assert!(Democracy::referendum_status(0).is_err()); - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); // referendum passes and wait another two blocks for enactment. fast_forward_to(6); diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index 97b7dc124314d..20a31828bb33a 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -20,6 +20,7 @@ sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../pr sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } hex-literal = "0.2.1" pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +pallet-scheduler = { version = "2.0.0-alpha.5", path = "../scheduler" } sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } serde = { version = "1.0.101" } diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 017518d882df4..531de95867bbb 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-scheduler" -version = "2.0.0-alpha.4" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "Unlicense" diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 7ccb43b4493bd..ffc417f06c633 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -95,7 +95,7 @@ pub struct Scheduled { decl_storage! { trait Store for Module as Scheduler { /// Items to be executed, indexed by the block number that they should be executed on. - Agenda: map hasher(twox_64_concat) T::BlockNumber + pub Agenda: map hasher(twox_64_concat) T::BlockNumber => Vec::Call, T::BlockNumber>>>; /// Lookup from identity to the block number and index of the task. @@ -129,7 +129,7 @@ decl_module! { Some((index, *cumulative_weight, s)) }) .filter_map(|(index, cumulative_weight, mut s)| { - if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit { + if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || index == 0 { let r = s.call.clone().dispatch(system::RawOrigin::Root.into()); let maybe_id = s.maybe_id.clone(); if let &Some((period, count)) = &s.maybe_periodic { @@ -197,11 +197,11 @@ impl schedule::Anon::Call> for Module } } -impl schedule::Named::Call> for Module { +impl schedule::Named::Call> for Module { type Address = TaskAddress; fn schedule_named( - id: Id, + id: impl Encode, when: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, @@ -227,7 +227,7 @@ impl schedule::Named::Cal Ok(address) } - fn cancel_named(id: Id) -> Result<(), ()> { + fn cancel_named(id: impl Encode) -> Result<(), ()> { if let Some((when, index)) = id.using_encoded(|d| Lookup::::take(d)) { let i = index as usize; Agenda::::mutate(when, |agenda| if let Some(s) = agenda.get_mut(i) { *s = None }); diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 39b1c7571d184..399cc5b0d57c9 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -1161,7 +1161,7 @@ pub mod schedule { } /// A type that can be used as a scheduler. - pub trait Named { + pub trait Named { /// An address which can be used for removing a scheduled task. type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug; @@ -1169,7 +1169,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: Id, + id: impl Encode, when: BlockNumber, maybe_periodic: Option>, priority: Priority, @@ -1183,7 +1183,7 @@ pub mod schedule { /// /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. - fn cancel_named(id: Id) -> Result<(), ()>; + fn cancel_named(id: impl Encode) -> Result<(), ()>; } } From c5de44d394268b313d1ab8a2c5309cc8c9a29b98 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 11:04:37 +0100 Subject: [PATCH 13/28] Use actual max weight for enactent --- frame/democracy/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 2664e54ef7b99..9f6c61e7d2ed3 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1250,8 +1250,8 @@ decl_module! { Self::try_remove_vote(&target, index, UnvoteScope::Any) } - /// Enact a proposal from a referendum. - #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] + /// Enact a proposal from a referendum. For now we just make the weight be the maximum. + #[weight = SimpleDispatchInfo::MaxNormal] fn enact_proposal(origin, proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { ensure_root(origin)?; Self::do_enact_proposal(proposal_hash, index) From e4f914f0584d092690e72b38310c6265553853c4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 11:12:31 +0100 Subject: [PATCH 14/28] Remove TODO --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 3 ++- bin/node/runtime/src/lib.rs | 12 ++++++++++++ frame/democracy/src/tests/preimage.rs | 2 -- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db76dc63a236c..b4d60461bb9a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3585,6 +3585,7 @@ dependencies = [ "pallet-offences", "pallet-randomness-collective-flip", "pallet-recovery", + "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", "pallet-society", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 24ae560607a8d..099db54fa7956 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -61,8 +61,9 @@ pallet-session = { version = "2.0.0-alpha.5", features = ["historical"], path = pallet-session-benchmarking = { version = "2.0.0-alpha.5", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/staking/reward-curve" } -pallet-sudo = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/sudo" } +pallet-scheduler = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/society" } +pallet-sudo = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/sudo" } pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/timestamp" } pallet-treasury = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/treasury" } pallet-utility = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/utility" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7bb230ec0fc67..90a6e190442cd 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -161,6 +161,17 @@ impl pallet_utility::Trait for Runtime { type MaxSignatories = MaxSignatories; } +parameter_types! { + pub const MaximumWeight: Weight = 2_000_000; +} + +impl pallet_scheduler::Trait for Runtime { + type Event = Event; + type Call = Call; + type Origin = Origin; + type MaximumWeight = MaximumWeight; +} + parameter_types! { pub const EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS; pub const ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK; @@ -340,6 +351,7 @@ impl pallet_democracy::Trait for Runtime { type CooloffPeriod = CooloffPeriod; type PreimageByteDeposit = PreimageByteDeposit; type Slash = Treasury; + type Scheduler = Scheduler; } parameter_types! { diff --git a/frame/democracy/src/tests/preimage.rs b/frame/democracy/src/tests/preimage.rs index 9f15c35a31465..e1b5c9665e2a8 100644 --- a/frame/democracy/src/tests/preimage.rs +++ b/frame/democracy/src/tests/preimage.rs @@ -158,8 +158,6 @@ fn reaping_imminent_preimage_should_fail() { assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); next_block(); next_block(); - // now imminent. - // TODO! assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); }); } From fb6f5eaf64d08d501675c6b10d50476947faa048 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 17:04:10 +0100 Subject: [PATCH 15/28] Fix runtime build --- bin/node/runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 90a6e190442cd..f1022ace5fb6a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -670,6 +670,7 @@ construct_runtime!( Society: pallet_society::{Module, Call, Storage, Event, Config}, Recovery: pallet_recovery::{Module, Call, Storage, Event}, Vesting: pallet_vesting::{Module, Call, Storage, Event, Config}, + Scheduler: pallet_scheduler::{Module, Call, Storage, Event}, } ); From a4403eb0446480a8b96c23b9cc50a3f65de3df78 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 19:03:41 +0100 Subject: [PATCH 16/28] Minor cleanup --- bin/node/cli/Cargo.toml | 2 +- bin/node/runtime/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index d45f5d7848baf..dcb2294e5eae1 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/paritytech/substrate/" wasm-opt = false [badges] -travis-ci = { repository = "paritytech/substrate", branch = "master" } +travis-ci = { repository = "paritytech/substrate" } maintenance = { status = "actively-developed" } is-it-maintained-issue-resolution = { repository = "paritytech/substrate" } is-it-maintained-open-issues = { repository = "paritytech/substrate" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 19dd46a4274d2..6e9861fbd20c1 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -213,8 +213,8 @@ parameter_types! { impl pallet_scheduler::Trait for Runtime { type Event = Event; - type Call = Call; type Origin = Origin; + type Call = Call; type MaximumWeight = MaximumWeight; } @@ -273,6 +273,7 @@ impl pallet_transaction_payment::Trait for Runtime { parameter_types! { pub const MinimumPeriod: Moment = SLOT_DURATION / 2; } + impl pallet_timestamp::Trait for Runtime { type Moment = Moment; type OnTimestampSet = Babe; From 7057704edaaead4c398acc2eb5ef2f8fbae7c5d3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 22:19:12 +0100 Subject: [PATCH 17/28] Fix scheduler. --- frame/scheduler/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ffc417f06c633..b60d3777a8099 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -124,12 +124,13 @@ decl_module! { queued.sort_by_key(|(_, s)| s.priority); let mut result = 0; let unused_items = queued.into_iter() - .scan(0, |cumulative_weight, (index, s)| { + .enumerate() + .scan(0, |cumulative_weight, (order, (index, s))| { *cumulative_weight += s.call.get_dispatch_info().weight; - Some((index, *cumulative_weight, s)) + Some((order, index, *cumulative_weight, s)) }) - .filter_map(|(index, cumulative_weight, mut s)| { - if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || index == 0 { + .filter_map(|(order, index, cumulative_weight, mut s)| { + if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 { let r = s.call.clone().dispatch(system::RawOrigin::Root.into()); let maybe_id = s.maybe_id.clone(); if let &Some((period, count)) = &s.maybe_periodic { From a467b6c8a7094fe83a41b72f3e154126fa1e0737 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 22:48:39 +0100 Subject: [PATCH 18/28] Fix benchmarks --- frame/democracy/src/benchmarking.rs | 33 ++++++----------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 2429edbefd8d1..6405b93a789f7 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -228,12 +228,6 @@ benchmarks! { }: _(RawOrigin::Root, referendum_index) cancel_queued { - let d in 0 .. 100; - - let referendum_index = add_referendum::(d)?; - let block_number: T::BlockNumber = 0.into(); - let hash: T::Hash = T::Hashing::hash_of(&d); - >::put(vec![(block_number, hash, referendum_index.clone()); d as usize]); }: _(RawOrigin::Root, referendum_index) open_proxy { @@ -314,19 +308,13 @@ benchmarks! { note_imminent_preimage { // Num of bytes in encoded proposal let b in 0 .. 16_384; - // Length of dispatch queue - let d in 0 .. 100; let mut dispatch_queue = Vec::new(); // d + 1 to include the one we are testing - for i in 0 .. d + 1 { - let encoded_proposal = vec![0; i as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let block_number = T::BlockNumber::zero(); - let referendum_index: ReferendumIndex = 0; - dispatch_queue.push((block_number, proposal_hash, referendum_index)) - } - >::put(dispatch_queue); + let encoded_proposal = vec![0; 0]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + let block_number = T::BlockNumber::one(); + Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number))? let caller = funded_account::("caller", b); let encoded_proposal = vec![0; d as usize]; @@ -335,18 +323,9 @@ benchmarks! { reap_preimage { // Num of bytes in encoded proposal let b in 0 .. 16_384; - // Length of dispatch queue - let d in 0 .. 100; - let mut dispatch_queue = Vec::new(); - for i in 0 .. d { - let encoded_proposal = vec![0; i as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let block_number = T::BlockNumber::zero(); - let referendum_index: ReferendumIndex = 0; - dispatch_queue.push((block_number, proposal_hash, referendum_index)) - } - >::put(dispatch_queue); + let encoded_proposal = vec![0; 0 as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let caller = funded_account::("caller", d); let encoded_proposal = vec![0; d as usize]; From 99644fdf78b8bce01177e9fa465259613224355c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 23:07:08 +0100 Subject: [PATCH 19/28] Fix --- frame/democracy/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 6405b93a789f7..e58981c3d0b70 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -314,7 +314,7 @@ benchmarks! { let encoded_proposal = vec![0; 0]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let block_number = T::BlockNumber::one(); - Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number))? + Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number))?; let caller = funded_account::("caller", b); let encoded_proposal = vec![0; d as usize]; From 87f940a94d843ed367d0ef92427090f9123b56e0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 23:17:10 +0100 Subject: [PATCH 20/28] Fix --- frame/democracy/src/benchmarking.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index e58981c3d0b70..f5d344de2efd9 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -228,6 +228,7 @@ benchmarks! { }: _(RawOrigin::Root, referendum_index) cancel_queued { + let referendum_index = add_referendum::(d)?; }: _(RawOrigin::Root, referendum_index) open_proxy { From 1190fa5b30c189417a40f14464925b71424b69e7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 23:21:16 +0100 Subject: [PATCH 21/28] Fix --- frame/democracy/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index f5d344de2efd9..bf10444ce2958 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -228,7 +228,7 @@ benchmarks! { }: _(RawOrigin::Root, referendum_index) cancel_queued { - let referendum_index = add_referendum::(d)?; + let referendum_index = add_referendum::(0)?; }: _(RawOrigin::Root, referendum_index) open_proxy { From 0f3b6118f8ea32bb95dd4b616609e1a81e7ddb4f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 26 Mar 2020 23:40:08 +0100 Subject: [PATCH 22/28] More bench fixes --- frame/democracy/src/benchmarking.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index bf10444ce2958..b63345dfc937a 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -310,34 +310,30 @@ benchmarks! { // Num of bytes in encoded proposal let b in 0 .. 16_384; - let mut dispatch_queue = Vec::new(); // d + 1 to include the one we are testing - let encoded_proposal = vec![0; 0]; + let encoded_proposal = vec![0; b as usize]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let block_number = T::BlockNumber::one(); Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number))?; let caller = funded_account::("caller", b); - let encoded_proposal = vec![0; d as usize]; + let encoded_proposal = vec![0; b as usize]; }: _(RawOrigin::Signed(caller), encoded_proposal) reap_preimage { // Num of bytes in encoded proposal let b in 0 .. 16_384; - let encoded_proposal = vec![0; 0 as usize]; + let encoded_proposal = vec![0; b as usize]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let caller = funded_account::("caller", d); - let encoded_proposal = vec![0; d as usize]; Democracy::::note_preimage(RawOrigin::Signed(caller.clone()).into(), encoded_proposal.clone())?; // We need to set this otherwise we get `Early` error. let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); System::::set_block_number(block_number.into()); - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - }: _(RawOrigin::Signed(caller), proposal_hash) unlock { From 81623aee4a209f569d36b297b44dd3c5d9187c42 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 27 Mar 2020 00:32:29 +0100 Subject: [PATCH 23/28] Fix --- frame/democracy/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index b63345dfc937a..c31cdabc812aa 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -327,7 +327,7 @@ benchmarks! { let encoded_proposal = vec![0; b as usize]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let caller = funded_account::("caller", d); + let caller = funded_account::("caller", b); Democracy::::note_preimage(RawOrigin::Signed(caller.clone()).into(), encoded_proposal.clone())?; // We need to set this otherwise we get `Early` error. From 4a563fffb83cc96aa787c6f3143607295e83ed30 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 27 Mar 2020 00:48:38 +0100 Subject: [PATCH 24/28] Fix. --- frame/democracy/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index c31cdabc812aa..34d1f969994d5 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -314,7 +314,7 @@ benchmarks! { let encoded_proposal = vec![0; b as usize]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let block_number = T::BlockNumber::one(); - Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number))?; + Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); let caller = funded_account::("caller", b); let encoded_proposal = vec![0; b as usize]; From 6b3ee590d2375fb522fe621f51f56a245cb5375c Mon Sep 17 00:00:00 2001 From: marcio-diaz Date: Fri, 27 Mar 2020 12:24:13 +0100 Subject: [PATCH 25/28] Add more bench constants. --- frame/democracy/src/benchmarking.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 34d1f969994d5..5d40b2e7c98e9 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -29,6 +29,9 @@ const SEED: u32 = 0; const MAX_USERS: u32 = 1000; const MAX_REFERENDUMS: u32 = 100; const MAX_PROPOSALS: u32 = 100; +const MAX_SECONDERS: u32 = 100; +const MAX_VETOERS: u32 = 100; +const MAX_BYTES: u32 = 16_384; fn funded_account(name: &'static str, index: u32) -> T::AccountId { let caller: T::AccountId = account(name, index, SEED); @@ -99,7 +102,7 @@ benchmarks! { }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) second { - let s in 0 .. 100; + let s in 0 .. MAX_SECONDERS; // Create s existing "seconds" for i in 0..s { @@ -202,7 +205,7 @@ benchmarks! { veto_external { // Existing veto-ers - let v in 0 .. 100; + let v in 0 .. MAX_VETOERS; let proposal_hash: T::Hash = T::Hashing::hash_of(&v); @@ -210,7 +213,7 @@ benchmarks! { Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; let mut vetoers: Vec = Vec::new(); - for i in 0..v { + for i in 0 .. v { vetoers.push(account("vetoer", i, SEED)); } Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); @@ -292,7 +295,7 @@ benchmarks! { }: _(RawOrigin::Signed(delegator)) clear_public_proposals { - let p in 0 .. 100; + let p in 0 .. MAX_PROPOSALS; for i in 0 .. p { add_proposal::(i)?; } @@ -300,7 +303,7 @@ benchmarks! { note_preimage { // Num of bytes in encoded proposal - let b in 0 .. 16_384; + let b in 0 .. MAX_BYTES; let caller = funded_account::("caller", b); let encoded_proposal = vec![0; b as usize]; @@ -308,7 +311,7 @@ benchmarks! { note_imminent_preimage { // Num of bytes in encoded proposal - let b in 0 .. 16_384; + let b in 0 .. MAX_BYTES; // d + 1 to include the one we are testing let encoded_proposal = vec![0; b as usize]; @@ -322,7 +325,7 @@ benchmarks! { reap_preimage { // Num of bytes in encoded proposal - let b in 0 .. 16_384; + let b in 0 .. MAX_BYTES; let encoded_proposal = vec![0; b as usize]; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); From 1a407a33011a62ad990925fd037236196ca9a8cd Mon Sep 17 00:00:00 2001 From: marcio-diaz Date: Fri, 27 Mar 2020 16:06:36 +0100 Subject: [PATCH 26/28] Fix cancel_queued bench. --- frame/democracy/src/benchmarking.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 5d40b2e7c98e9..a483269c43d6f 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -60,6 +60,13 @@ fn add_referendum(n: u32) -> Result { 0.into(), ); let referendum_index: ReferendumIndex = ReferendumCount::get() - 1; + let _ = T::Scheduler::schedule_named( + (DEMOCRACY_ID, referendum_index), + 0.into(), + None, + 63, + Call::enact_proposal(proposal_hash, referendum_index).into(), + ); Ok(referendum_index) } @@ -92,7 +99,7 @@ benchmarks! { let p in 1 .. MAX_PROPOSALS; // Add p proposals - for i in 0..p { + for i in 0 .. p { add_proposal::(i)?; } @@ -105,7 +112,7 @@ benchmarks! { let s in 0 .. MAX_SECONDERS; // Create s existing "seconds" - for i in 0..s { + for i in 0 .. s { let seconder = funded_account::("seconder", i); Democracy::::second(RawOrigin::Signed(seconder).into(), 0)?; } @@ -231,7 +238,9 @@ benchmarks! { }: _(RawOrigin::Root, referendum_index) cancel_queued { - let referendum_index = add_referendum::(0)?; + let u in 1 .. MAX_USERS; + + let referendum_index = add_referendum::(u)?; }: _(RawOrigin::Root, referendum_index) open_proxy { @@ -296,9 +305,11 @@ benchmarks! { clear_public_proposals { let p in 0 .. MAX_PROPOSALS; + for i in 0 .. p { add_proposal::(i)?; } + }: _(RawOrigin::Root) note_preimage { From 51210742070638f30429a2a763a41af1cb718d35 Mon Sep 17 00:00:00 2001 From: marcio-diaz Date: Fri, 27 Mar 2020 16:07:24 +0100 Subject: [PATCH 27/28] Fix test comment. --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index b60d3777a8099..3d33008a0caa9 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -421,7 +421,7 @@ mod tests { #[test] fn cancel_named_scheduling_works_with_normal_cancel() { new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. + // at #4. Scheduler::schedule_named(1u32, 4, None, 127, Call::Logger(logger::Call::log(69, 1000))).unwrap(); let i = Scheduler::schedule(4, None, 127, Call::Logger(logger::Call::log(42, 1000))); run_to_block(3); From f1e05f5e12055275b2af937b2a93df25d675d865 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 31 Mar 2020 20:17:45 +0200 Subject: [PATCH 28/28] Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 3d33008a0caa9..8c60df352768a 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -70,7 +70,7 @@ pub trait Trait: system::Trait { type Call: Parameter + Dispatchable::Origin> + GetDispatchInfo; /// The maximum weight that may be scheduled per block for any dispatchables of less priority - /// than 255. + /// than `schedule::HARD_DEADLINE`. type MaximumWeight: Get; }