Skip to content

Commit

Permalink
Added a new module-idle-scheduler (AcalaNetwork#1494)
Browse files Browse the repository at this point in the history
* Added a new module-idle-scheduler
Integrated the new module into Mandala and karura and acala runtime

* Addressed some of the PR comments

* Adjusted the minimumWeightRemainInBlock from 10% to 2%

* Addressed some PR comments.

* Fixed a clippy build error

* Addressed some PR comments

Co-authored-by: Roy Yang <[email protected]>
  • Loading branch information
syan095 and Roy Yang authored Oct 20, 2021
1 parent 6db77b2 commit 432236b
Show file tree
Hide file tree
Showing 14 changed files with 669 additions and 7 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions modules/idle-scheduler/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "module-idle-scheduler"
version = "1.5.0"
authors = ["Acala Developers"]
edition = "2018"

[dependencies]
serde = { version = "1.0.124", optional = true }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11", default-features = false }
acala-primitives = { path = "../../primitives", default-features = false }

[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.11" }

[features]
default = ["std"]
std = [
"serde",
"codec/std",
"sp-runtime/std",
"sp-std/std",
"frame-support/std",
"frame-system/std",
"acala-primitives/std",
]
try-runtime = ["frame-support/try-runtime"]
153 changes: 153 additions & 0 deletions modules/idle-scheduler/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// This file is part of Acala.

// Copyright (C) 2020-2021 Acala Foundation.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

//! # Example Module
//!
//! A simple example of a FRAME pallet demonstrating
//! concepts, APIs and structures common to most FRAME runtimes.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
#![allow(unused_must_use)]
use acala_primitives::{
task::{DispatchableTask, IdelScheduler},
Nonce,
};
use codec::{Codec, EncodeLike};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::traits::{One, Zero};
use sp_std::{cmp::PartialEq, fmt::Debug, prelude::*};

mod mock;
mod tests;
mod weights;
pub use module::*;
pub use weights::WeightInfo;

#[frame_support::pallet]
pub mod module {
use super::*;

#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

/// Weight information for the extrinsics in this module.
type WeightInfo: WeightInfo;

/// Dispatchable tasks
type Task: DispatchableTask + Codec + EncodeLike + Debug + Clone + PartialEq + TypeInfo;

/// The minimum weight that should remain before idle tasks are dispatched.
#[pallet::constant]
type MinimumWeightRemainInBlock: Get<Weight>;
}

#[pallet::event]
#[pallet::generate_deposit(pub fn deposit_event)]
pub enum Event<T: Config> {
/// A task has been dispatched on_idle.
/// \[TaskId\]
TaskDispatched(Nonce),
}

/// Some documentation
#[pallet::storage]
#[pallet::getter(fn tasks)]
pub type Tasks<T: Config> = StorageMap<_, Twox64Concat, Nonce, T::Task, OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn next_task_id)]
pub type NextTaskId<T: Config> = StorageValue<_, Nonce, ValueQuery>;

#[pallet::pallet]
pub struct Pallet<T>(_);

#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_idle(_n: T::BlockNumber, remaining_weight: Weight) -> Weight {
Self::do_dispatch_tasks(remaining_weight)
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(< T as Config >::WeightInfo::schedule_task())]
pub fn schedule_task(origin: OriginFor<T>, task: T::Task) -> DispatchResult {
ensure_root(origin)?;
Self::do_schedule_task(task);
Ok(())
}
}
}

impl<T: Config> Pallet<T> {
/// Add the task to the queue to be dispatched later
fn do_schedule_task(task: T::Task) {
let id = Self::get_next_task_id();
Tasks::<T>::insert(id, task);
}

/// Retrieves the next task ID from storage, and increment it by one.
fn get_next_task_id() -> Nonce {
NextTaskId::<T>::mutate(|current| {
let id = *current;
*current = current.saturating_add(One::one());
id
})
}

/// Keep dispatching tasks in Storage, until insufficient weight remains.
pub fn do_dispatch_tasks(total_weight: Weight) -> Weight {
let mut weight_remaining = total_weight;
if weight_remaining <= T::MinimumWeightRemainInBlock::get() {
return Zero::zero();
}

let mut completed_tasks: Vec<Nonce> = vec![];

for (id, task) in Tasks::<T>::iter() {
let result = task.dispatch(weight_remaining);
weight_remaining = weight_remaining.saturating_sub(result.used_weight);
if result.finished {
completed_tasks.push(id);
}

// If remaining weight falls below the minimmum, break from the loop.
if weight_remaining <= T::MinimumWeightRemainInBlock::get() {
break;
}
}

// Deposit event and remove completed tasks.
for id in completed_tasks {
Self::deposit_event(Event::<T>::TaskDispatched(id));
Tasks::<T>::remove(id);
}

total_weight.saturating_sub(weight_remaining)
}
}

impl<T: Config> IdelScheduler<T::Task> for Pallet<T> {
fn schedule(task: T::Task) {
Self::do_schedule_task(task);
}
}
141 changes: 141 additions & 0 deletions modules/idle-scheduler/src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// This file is part of Acala.

// Copyright (C) 2020-2021 Acala Foundation.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

//! Mocks for example module.
#![cfg(test)]

use crate as module_idle_scheduler;
use acala_primitives::{
define_combined_task,
task::{DispatchableTask, TaskResult},
};
use frame_support::weights::Weight;
use frame_support::{construct_runtime, parameter_types, traits::Everything};

use codec::{Decode, Encode};
use scale_info::TypeInfo;

pub const BASE_WEIGHT: Weight = 1_000_000;

parameter_types!(
pub const BlockHashCount: u32 = 250;
);

pub type AccountId = u32;
impl frame_system::Config for Runtime {
type BaseCallFilter = Everything;
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = Call;
type Hash = sp_runtime::testing::H256;
type Hashing = sp_runtime::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
type Header = sp_runtime::testing::Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
}

parameter_types!(
pub const MinimumWeightRemainInBlock: Weight = 100_000_000_000;
);

impl module_idle_scheduler::Config for Runtime {
type Event = Event;
type WeightInfo = ();
type Task = ScheduledTasks;
type MinimumWeightRemainInBlock = MinimumWeightRemainInBlock;
}

// Mock dispatachable tasks
#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
pub enum BalancesTask {
#[codec(index = 0)]
OnIdle,
}
impl DispatchableTask for BalancesTask {
fn dispatch(self, weight: Weight) -> TaskResult {
TaskResult {
used_weight: BASE_WEIGHT,
finished: weight >= BASE_WEIGHT,
}
}
}

#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
pub enum HomaLiteTask {
#[codec(index = 0)]
OnIdle,
}
impl DispatchableTask for HomaLiteTask {
fn dispatch(self, weight: Weight) -> TaskResult {
TaskResult {
used_weight: BASE_WEIGHT,
finished: weight >= BASE_WEIGHT,
}
}
}

define_combined_task! {
pub enum ScheduledTasks {
BalancesTask,
HomaLiteTask,
}
}

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
type Block = frame_system::mocking::MockBlock<Runtime>;

construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: frame_system::{Pallet, Call, Event<T>},
// NOTE: name Example here is needed in order to have same module prefix
IdleScheduler: module_idle_scheduler::{Pallet, Call, Event<T>, Storage},
}
);

#[derive(Default)]
pub struct ExtBuilder;
impl ExtBuilder {
pub fn build(self) -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.unwrap();

let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
Loading

0 comments on commit 432236b

Please sign in to comment.