diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 36ce03e613..77852437f2 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3512,6 +3512,7 @@ dependencies = [ "pallet-offences", "pallet-randomness-collective-flip", "pallet-recovery", + "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", "pallet-society", @@ -4044,6 +4045,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", + "pallet-scheduler", "parity-scale-codec", "serde", "sp-core", @@ -4077,6 +4079,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", + "pallet-scheduler", "parity-scale-codec", "serde", "sp-core", @@ -4314,6 +4317,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-scheduler" +version = "2.0.0-alpha.5" +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.5" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 39a6ff1f2d..9b30bfd39e 100644 --- a/substrate/Cargo.toml +++ b/substrate/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/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 804a74b8c4..c477471166 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/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/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 07d3b8beda..67e50e53f6 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/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/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 881807ea24..309f4ed024 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -207,6 +207,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 Origin = Origin; + type Call = Call; + type MaximumWeight = MaximumWeight; +} + parameter_types! { pub const EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS; pub const ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK; @@ -262,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; @@ -392,6 +404,7 @@ impl pallet_democracy::Trait for Runtime { type CooloffPeriod = CooloffPeriod; type PreimageByteDeposit = PreimageByteDeposit; type Slash = Treasury; + type Scheduler = Scheduler; } parameter_types! { @@ -670,6 +683,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}, } ); diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 888e23e01b..7886457644 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -21,6 +21,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/substrate/frame/democracy/src/benchmarking.rs b/substrate/frame/democracy/src/benchmarking.rs index 2429edbefd..a483269c43 100644 --- a/substrate/frame/democracy/src/benchmarking.rs +++ b/substrate/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); @@ -57,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) } @@ -89,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)?; } @@ -99,10 +109,10 @@ 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 { + for i in 0 .. s { let seconder = funded_account::("seconder", i); Democracy::::second(RawOrigin::Signed(seconder).into(), 0)?; } @@ -202,7 +212,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 +220,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)); @@ -228,12 +238,9 @@ benchmarks! { }: _(RawOrigin::Root, referendum_index) cancel_queued { - let d in 0 .. 100; + let u in 1 .. MAX_USERS; - 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]); + let referendum_index = add_referendum::(u)?; }: _(RawOrigin::Root, referendum_index) open_proxy { @@ -297,15 +304,17 @@ 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)?; } + }: _(RawOrigin::Root) 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]; @@ -313,51 +322,32 @@ 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 b in 0 .. MAX_BYTES; - 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; 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; - // Length of dispatch queue - let d in 0 .. 100; + let b in 0 .. MAX_BYTES; - 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; 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]; + 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. 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 { diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index 7223b66a4e..5f50ee04da 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/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 + OnUnbalanced, BalanceStatus, schedule::Named as ScheduleNamed } }; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -206,7 +207,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. @@ -273,6 +274,33 @@ pub trait Trait: frame_system::Trait + Sized { /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; + + /// The Scheduler. + type Scheduler: ScheduleNamed; +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum PreimageStatus { + /// 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! { @@ -293,7 +321,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 +334,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>; @@ -816,11 +839,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((DEMOCRACY_ID, which)) + .map_err(|_| Error::::ProposalMissing)?; } fn on_initialize(n: T::BlockNumber) -> Weight { @@ -986,7 +1006,14 @@ decl_module! { T::Currency::reserve(&who, deposit)?; let now = >::block_number(); - >::insert(proposal_hash, (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)); } @@ -1007,13 +1034,19 @@ 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)?; + let expiry = status.to_missing_expiry().ok_or(Error::::DuplicatePreimage)?; let now = >::block_number(); let free = >::zero(); - >::insert(proposal_hash, (encoded_proposal, who.clone(), free, 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)); } @@ -1036,20 +1069,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); - let queue = >::get(); - ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::::Imminent); - - 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. @@ -1222,6 +1257,13 @@ 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. 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) + } } } @@ -1544,28 +1586,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() { @@ -1622,6 +1642,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, @@ -1633,13 +1675,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 { - 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); + 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)), }); + + if T::Scheduler::schedule_named( + (DEMOCRACY_ID, index), + when, + None, + 63, + 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)); @@ -1662,17 +1715,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(()) } } diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 1e2e6dc688..8fca8fa4cf 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/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 { @@ -167,6 +175,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] @@ -215,6 +224,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/substrate/frame/democracy/src/tests/cancellation.rs b/substrate/frame/democracy/src/tests/cancellation.rs index 424ec36dbe..998b0c14d8 100644 --- a/substrate/frame/democracy/src/tests/cancellation.rs +++ b/substrate/frame/democracy/src/tests/cancellation.rs @@ -50,13 +50,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/substrate/frame/democracy/src/tests/preimage.rs b/substrate/frame/democracy/src/tests/preimage.rs index 8d834c319e..7d977b0ba8 100644 --- a/substrate/frame/democracy/src/tests/preimage.rs +++ b/substrate/frame/democracy/src/tests/preimage.rs @@ -152,7 +152,6 @@ fn reaping_imminent_preimage_should_fail() { assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); next_block(); next_block(); - // now imminent. assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); }); } diff --git a/substrate/frame/democracy/src/tests/voting.rs b/substrate/frame/democracy/src/tests/voting.rs index bdb8edb758..43aed29a32 100644 --- a/substrate/frame/democracy/src/tests/voting.rs +++ b/substrate/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/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index 1fbfbc20ce..fb219bbebc 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -21,6 +21,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" } diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml new file mode 100644 index 0000000000..531de95867 --- /dev/null +++ b/substrate/frame/scheduler/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-scheduler" +version = "2.0.0-alpha.5" +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" } +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", + "sp-io/std", + "sp-std/std" +] diff --git a/substrate/frame/scheduler/src/lib.rs b/substrate/frame/scheduler/src/lib.rs new file mode 100644 index 0000000000..8c60df3527 --- /dev/null +++ b/substrate/frame/scheduler/src/lib.rs @@ -0,0 +1,518 @@ +// 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 +//! +//! - \[`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::prelude::*; +use codec::{Encode, Decode}; +use sp_runtime::{RuntimeDebug, traits::{Zero, One}}; +use frame_support::{ + dispatch::{Dispatchable, DispatchResult, Parameter}, decl_module, decl_storage, decl_event, + traits::{Get, schedule}, + weights::{GetDispatchInfo, Weight}, +}; +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. +/// +/// `system::Trait` should always be included in our implied traits. +pub trait Trait: system::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::Origin> + GetDispatchInfo; + + /// The maximum weight that may be scheduled per block for any dispatchables of less priority + /// than `schedule::HARD_DEADLINE`. + type MaximumWeight: Get; +} + +/// Just a simple index for naming period tasks. +pub type PeriodicIndex = u32; +/// 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 { + /// The unique identity for this task, if there is one. + maybe_id: Option>, + /// This task's 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>, +} + +decl_storage! { + trait Store for Module as Scheduler { + /// Items to be executed, indexed by the block number that they should be executed on. + 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. + Lookup: map hasher(twox_64_concat) Vec => Option>; + } +} + +decl_event!( + pub enum Event where ::BlockNumber { + Scheduled(BlockNumber), + Dispatched(TaskAddress, Option>, DispatchResult), + } +); + +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; + + fn on_initialize(now: T::BlockNumber) -> Weight { + let limit = T::MaximumWeight::get(); + 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() + .enumerate() + .scan(0, |cumulative_weight, (order, (index, s))| { + *cumulative_weight += s.call.get_dispatch_info().weight; + Some((order, index, *cumulative_weight, s)) + }) + .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 { + 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(Some(s)) + } + }) + .collect::>(); + if !unused_items.is_empty() { + let next = now + One::one(); + Agenda::::append_or_insert(next, &unused_items[..]); + } + result + } + } +} + +impl schedule::Anon::Call> for Module { + type Address = TaskAddress; + + 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 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: impl Encode, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: ::Call, + ) -> 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_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 }); + Ok(()) + } else { + Err(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use frame_support::{ + 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 + // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. + use sp_runtime::{ + Perbill, + testing::Header, + traits::{BlakeTwo256, 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 {} + } + + 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. + #[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 system::Trait for Test { + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + 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 = (); + type OnNewAccount = (); + type OnKilledAccount = (); + } + impl logger::Trait for Test { + type Event = (); + } + 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 = 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 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 basic_scheduling_works() { + new_test_ext().execute_with(|| { + 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); + assert_eq!(logger::log(), vec![42u32]); + run_to_block(100); + assert_eq!(logger::log(), vec![42u32]); + }); + } + + #[test] + fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + 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); + 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]); + }); + } + + #[test] + fn cancel_named_scheduling_works_with_normal_cancel() { + new_test_ext().execute_with(|| { + // 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); + 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_ok!(Scheduler::cancel_named(1u32)); + run_to_block(100); + assert_eq!(logger::log(), vec![42u32, 69u32]); + }); + } + + #[test] + fn scheduler_respects_weight_limits() { + new_test_ext().execute_with(|| { + 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); + assert_eq!(logger::log(), vec![42u32, 69u32]); + }); + } + + #[test] + fn scheduler_respects_hard_deadlines_more() { + new_test_ext().execute_with(|| { + 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]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering() { + new_test_ext().execute_with(|| { + 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]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + 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); + assert_eq!(logger::log(), vec![2600u32, 69u32, 42u32]); + }); + } + + #[test] + fn initialize_weight_is_correct() { + new_test_ext().execute_with(|| { + 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); + assert_eq!(weight, 10000); + let weight = Scheduler::on_initialize(3); + assert_eq!(weight, 1000); + let weight = Scheduler::on_initialize(4); + assert_eq!(weight, 0); + }); + } +} diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 77e4c679f4..f19d3995ea 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/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, @@ -1144,6 +1144,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: impl Encode, + 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: impl Encode) -> Result<(), ()>; + } +} + #[cfg(test)] mod tests { use super::*;