mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 21:21:11 +00:00
Introduce scheduler and use it for the democracy dispatch queue (#5412)
* Initial draft of the logic * Build and tests * Make work with new initialize infratructure. * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> * Fix test * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> * Rejig interface to make it more useful for democracy. * Try to get democraxy module to make use of scheduler. * Make democracy use scheduler. * Use actual max weight for enactent * Remove TODO * Fix runtime build * Minor cleanup * Fix scheduler. * Fix benchmarks * Fix * Fix * Fix * More bench fixes * Fix * Fix. * Add more bench constants. * Fix cancel_queued bench. * Fix test comment. * Update frame/scheduler/src/lib.rs Co-Authored-By: Marcio Diaz <marcio.diaz@gmail.com> Co-authored-by: Marcio Diaz <marcio.diaz@gmail.com>
This commit is contained in:
Generated
+18
@@ -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"
|
||||
|
||||
@@ -86,6 +86,7 @@ members = [
|
||||
"frame/offences",
|
||||
"frame/randomness-collective-flip",
|
||||
"frame/recovery",
|
||||
"frame/scheduler",
|
||||
"frame/scored-pool",
|
||||
"frame/session",
|
||||
"frame/session/benchmarking",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<T>, Config<T>},
|
||||
Recovery: pallet_recovery::{Module, Call, Storage, Event<T>},
|
||||
Vesting: pallet_vesting::{Module, Call, Storage, Event<T>, Config<T>},
|
||||
Scheduler: pallet_scheduler::{Module, Call, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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<T: Trait>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let caller: T::AccountId = account(name, index, SEED);
|
||||
@@ -57,6 +60,13 @@ fn add_referendum<T: Trait>(n: u32) -> Result<ReferendumIndex, &'static str> {
|
||||
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::<T>(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::<T>("seconder", i);
|
||||
Democracy::<T>::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::<T>::external_propose_default(origin_propose, proposal_hash.clone())?;
|
||||
|
||||
let mut vetoers: Vec<T::AccountId> = Vec::new();
|
||||
for i in 0..v {
|
||||
for i in 0 .. v {
|
||||
vetoers.push(account("vetoer", i, SEED));
|
||||
}
|
||||
Blacklist::<T>::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::<T>(d)?;
|
||||
let block_number: T::BlockNumber = 0.into();
|
||||
let hash: T::Hash = T::Hashing::hash_of(&d);
|
||||
<DispatchQueue<T>>::put(vec![(block_number, hash, referendum_index.clone()); d as usize]);
|
||||
let referendum_index = add_referendum::<T>(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::<T>(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::<T>("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))
|
||||
}
|
||||
<DispatchQueue<T>>::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::<T>::insert(&proposal_hash, PreimageStatus::Missing(block_number));
|
||||
|
||||
let caller = funded_account::<T>("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))
|
||||
}
|
||||
<DispatchQueue<T>>::put(dispatch_queue);
|
||||
let encoded_proposal = vec![0; b as usize];
|
||||
let proposal_hash = T::Hashing::hash(&encoded_proposal[..]);
|
||||
|
||||
let caller = funded_account::<T>("caller", d);
|
||||
let encoded_proposal = vec![0; d as usize];
|
||||
let caller = funded_account::<T>("caller", b);
|
||||
Democracy::<T>::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::<T>::set_block_number(block_number.into());
|
||||
|
||||
let proposal_hash = T::Hashing::hash(&encoded_proposal[..]);
|
||||
|
||||
}: _(RawOrigin::Signed(caller), proposal_hash)
|
||||
|
||||
unlock {
|
||||
|
||||
@@ -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<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
|
||||
|
||||
pub trait Trait: frame_system::Trait + Sized {
|
||||
type Proposal: Parameter + Dispatchable<Origin=Self::Origin>;
|
||||
type Proposal: Parameter + Dispatchable<Origin=Self::Origin> + From<Call<Self>>;
|
||||
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::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<NegativeImbalanceOf<Self>>;
|
||||
|
||||
/// The Scheduler.
|
||||
type Scheduler: ScheduleNamed<Self::BlockNumber, Self::Proposal>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub enum PreimageStatus<AccountId, Balance, BlockNumber> {
|
||||
/// The preimage is imminently needed at the argument.
|
||||
Missing(BlockNumber),
|
||||
/// The preimage is available.
|
||||
Available {
|
||||
data: Vec<u8>,
|
||||
provider: AccountId,
|
||||
deposit: Balance,
|
||||
since: BlockNumber,
|
||||
/// None if it's not imminent.
|
||||
expiry: Option<BlockNumber>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<AccountId, Balance, BlockNumber> PreimageStatus<AccountId, Balance, BlockNumber> {
|
||||
fn to_missing_expiry(self) -> Option<BlockNumber> {
|
||||
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<u8>, T::AccountId, BalanceOf<T>, T::BlockNumber)>;
|
||||
=> Option<PreimageStatus<T::AccountId, BalanceOf<T>, 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<ReferendumInfo<T::BlockNumber, T::Hash, BalanceOf<T>>>;
|
||||
|
||||
// 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<BalanceOf<T>, 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 = <DispatchQueue<T>>::get();
|
||||
let original_len = items.len();
|
||||
items.retain(|i| i.2 != which);
|
||||
ensure!(items.len() < original_len, Error::<T>::ProposalMissing);
|
||||
<DispatchQueue<T>>::put(items);
|
||||
T::Scheduler::cancel_named((DEMOCRACY_ID, which))
|
||||
.map_err(|_| Error::<T>::ProposalMissing)?;
|
||||
}
|
||||
|
||||
fn on_initialize(n: T::BlockNumber) -> Weight {
|
||||
@@ -986,7 +1006,14 @@ decl_module! {
|
||||
T::Currency::reserve(&who, deposit)?;
|
||||
|
||||
let now = <frame_system::Module<T>>::block_number();
|
||||
<Preimages<T>>::insert(proposal_hash, (encoded_proposal, who.clone(), deposit, now));
|
||||
let a = PreimageStatus::Available {
|
||||
data: encoded_proposal,
|
||||
provider: who.clone(),
|
||||
deposit,
|
||||
since: now,
|
||||
expiry: None,
|
||||
};
|
||||
<Preimages<T>>::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<u8>) {
|
||||
let who = ensure_signed(origin)?;
|
||||
let proposal_hash = T::Hashing::hash(&encoded_proposal[..]);
|
||||
ensure!(!<Preimages<T>>::contains_key(&proposal_hash), Error::<T>::DuplicatePreimage);
|
||||
let queue = <DispatchQueue<T>>::get();
|
||||
ensure!(queue.iter().any(|item| &item.1 == &proposal_hash), Error::<T>::NotImminent);
|
||||
let status = Preimages::<T>::get(&proposal_hash).ok_or(Error::<T>::NotImminent)?;
|
||||
let expiry = status.to_missing_expiry().ok_or(Error::<T>::DuplicatePreimage)?;
|
||||
|
||||
let now = <frame_system::Module<T>>::block_number();
|
||||
let free = <BalanceOf<T>>::zero();
|
||||
<Preimages<T>>::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),
|
||||
};
|
||||
<Preimages<T>>::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) = <Preimages<T>>::get(&proposal_hash)
|
||||
.and_then(|m| match m {
|
||||
PreimageStatus::Available { provider, deposit, since, expiry, .. }
|
||||
=> Some((provider, deposit, since, expiry)),
|
||||
_ => None,
|
||||
}).ok_or(Error::<T>::PreimageMissing)?;
|
||||
|
||||
let (_, old, deposit, then) = <Preimages<T>>::get(&proposal_hash)
|
||||
.ok_or(Error::<T>::PreimageMissing)?;
|
||||
let now = <frame_system::Module<T>>::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::<T>::TooEarly);
|
||||
let additional = if who == provider { Zero::zero() } else { enactment };
|
||||
ensure!(now >= since + voting + additional, Error::<T>::TooEarly);
|
||||
ensure!(expiry.map_or(true, |e| now > e), Error::<T>::Imminent);
|
||||
|
||||
let queue = <DispatchQueue<T>>::get();
|
||||
ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::<T>::Imminent);
|
||||
|
||||
let _ = T::Currency::repatriate_reserved(&old, &who, deposit, BalanceStatus::Free);
|
||||
let _ = T::Currency::repatriate_reserved(&provider, &who, deposit, BalanceStatus::Free);
|
||||
<Preimages<T>>::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::<T>::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<T: Trait> Module<T> {
|
||||
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, _)) = <Preimages<T>>::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::<T>::PreimageInvalid.into())
|
||||
}
|
||||
} else {
|
||||
Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index));
|
||||
Err(Error::<T>::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<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn do_enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult {
|
||||
let preimage = <Preimages<T>>::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::<T>::PreimageInvalid.into())
|
||||
}
|
||||
} else {
|
||||
Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index));
|
||||
Err(Error::<T>::PreimageMissing.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn bake_referendum(
|
||||
now: T::BlockNumber,
|
||||
index: ReferendumIndex,
|
||||
@@ -1633,13 +1675,24 @@ impl<T: Trait> Module<T> {
|
||||
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);
|
||||
<DispatchQueue<T>>::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::<T>::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<T: Trait> Module<T> {
|
||||
let approved = Self::bake_referendum(now, index, info)?;
|
||||
ReferendumInfoOf::<T>::insert(index, ReferendumInfo::Finished { end: now, approved });
|
||||
}
|
||||
|
||||
let queue = <DispatchQueue<T>>::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 {
|
||||
<DispatchQueue<T>>::put(&queue[used..]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Six, u64>;
|
||||
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<Test>;
|
||||
type Balances = pallet_balances::Module<Test>;
|
||||
type Scheduler = pallet_scheduler::Module<Test>;
|
||||
type Democracy = Module<Test>;
|
||||
|
||||
#[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(()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<Test>::get(6)[0].is_some());
|
||||
|
||||
assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::<Test>::ProposalMissing);
|
||||
assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0));
|
||||
assert_eq!(Democracy::dispatch_queue(), vec![]);
|
||||
assert!(pallet_scheduler::Agenda::<Test>::get(6)[0].is_none());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<Test>::Imminent);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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::<Test>::get(6)[0].is_some());
|
||||
|
||||
// referendum passes and wait another two blocks for enactment.
|
||||
fast_forward_to(6);
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "pallet-scheduler"
|
||||
version = "2.0.0-alpha.5"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
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"
|
||||
]
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # 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<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// The aggregated origin which the dispatch will take.
|
||||
type Origin: From<system::RawOrigin<Self::AccountId>>;
|
||||
|
||||
/// The aggregated call type.
|
||||
type Call: Parameter + Dispatchable<Origin=<Self as Trait>::Origin> + GetDispatchInfo;
|
||||
|
||||
/// The maximum weight that may be scheduled per block for any dispatchables of less priority
|
||||
/// than `schedule::HARD_DEADLINE`.
|
||||
type MaximumWeight: Get<Weight>;
|
||||
}
|
||||
|
||||
/// 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> = (BlockNumber, u32);
|
||||
|
||||
/// Information regarding an item to be executed in the future.
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode)]
|
||||
pub struct Scheduled<Call, BlockNumber> {
|
||||
/// The unique identity for this task, if there is one.
|
||||
maybe_id: Option<Vec<u8>>,
|
||||
/// 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<schedule::Period<BlockNumber>>,
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> 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<Option<Scheduled<<T as Trait>::Call, T::BlockNumber>>>;
|
||||
|
||||
/// Lookup from identity to the block number and index of the task.
|
||||
Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where <T as system::Trait>::BlockNumber {
|
||||
Scheduled(BlockNumber),
|
||||
Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
// Simple declaration of the `Module` type. Lets the macro know what its working on.
|
||||
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
|
||||
fn deposit_event() = default;
|
||||
|
||||
fn on_initialize(now: T::BlockNumber) -> Weight {
|
||||
let limit = T::MaximumWeight::get();
|
||||
let mut queued = Agenda::<T>::take(now).into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))
|
||||
.collect::<Vec<_>>();
|
||||
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::<T>::decode_len(now + period).unwrap_or(0) as u32;
|
||||
Lookup::<T>::insert(id, (next, next_index));
|
||||
}
|
||||
Agenda::<T>::append_or_insert(next, &[Some(s)][..]);
|
||||
} else {
|
||||
if let Some(ref id) = s.maybe_id {
|
||||
Lookup::<T>::remove(id);
|
||||
}
|
||||
}
|
||||
Self::deposit_event(RawEvent::Dispatched((now, index), maybe_id, r));
|
||||
result = cumulative_weight;
|
||||
None
|
||||
} else {
|
||||
Some(Some(s))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !unused_items.is_empty() {
|
||||
let next = now + One::one();
|
||||
Agenda::<T>::append_or_insert(next, &unused_items[..]);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> schedule::Anon<T::BlockNumber, <T as Trait>::Call> for Module<T> {
|
||||
type Address = TaskAddress<T::BlockNumber>;
|
||||
|
||||
fn schedule(
|
||||
when: T::BlockNumber,
|
||||
maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
|
||||
priority: schedule::Priority,
|
||||
call: <T as Trait>::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::<T>::append_or_insert(when, &[s][..]);
|
||||
(when, Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1)
|
||||
}
|
||||
|
||||
fn cancel((when, index): Self::Address) -> Result<(), ()> {
|
||||
if let Some(s) = Agenda::<T>::mutate(when, |agenda| agenda.get_mut(index as usize).and_then(Option::take)) {
|
||||
if let Some(id) = s.maybe_id {
|
||||
Lookup::<T>::remove(id)
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> schedule::Named<T::BlockNumber, <T as Trait>::Call> for Module<T> {
|
||||
type Address = TaskAddress<T::BlockNumber>;
|
||||
|
||||
fn schedule_named(
|
||||
id: impl Encode,
|
||||
when: T::BlockNumber,
|
||||
maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
|
||||
priority: schedule::Priority,
|
||||
call: <T as Trait>::Call,
|
||||
) -> Result<Self::Address, ()> {
|
||||
// determine id and ensure it is unique
|
||||
let id = id.encode();
|
||||
if Lookup::<T>::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::<T>::append_or_insert(when, &[Some(s)][..]);
|
||||
let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
|
||||
let address = (when, index);
|
||||
Lookup::<T>::insert(&id, &address);
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
fn cancel_named(id: impl Encode) -> Result<(), ()> {
|
||||
if let Some((when, index)) = id.using_encoded(|d| Lookup::<T>::take(d)) {
|
||||
let i = index as usize;
|
||||
Agenda::<T>::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<Vec<u32>> = RefCell::new(Vec::new());
|
||||
}
|
||||
pub fn log() -> Vec<u32> {
|
||||
LOG.with(|log| log.borrow().clone())
|
||||
}
|
||||
pub trait Trait: system::Trait {
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
}
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> 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<T: Trait> for enum Call where origin: <T as system::Trait>::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<T>,
|
||||
logger,
|
||||
scheduler<T>,
|
||||
}
|
||||
}
|
||||
// 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<Self::AccountId>;
|
||||
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<Test>;
|
||||
type Logger = logger::Module<Test>;
|
||||
type Scheduler = Module<Test>;
|
||||
|
||||
// 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::<Test>().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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<BlockNumber> {
|
||||
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> = (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<BlockNumber, Call> {
|
||||
/// 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<Period<BlockNumber>>,
|
||||
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<BlockNumber, Call> {
|
||||
/// 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<Period<BlockNumber>>,
|
||||
priority: Priority,
|
||||
call: Call
|
||||
) -> Result<Self::Address, ()>;
|
||||
|
||||
/// 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::*;
|
||||
|
||||
Reference in New Issue
Block a user