Tasks: general system for recognizing and executing service work (#1343)

`polkadot-sdk` version of original tasks PR located here:
https://github.com/paritytech/substrate/pull/14329

Fixes #206

## Status
- [x] Generic `Task` trait
- [x] `RuntimeTask` aggregated enum, compatible with
`construct_runtime!`
- [x] Casting between `Task` and `RuntimeTask` without needing `dyn` or
`Box`
- [x] Tasks Example pallet
- [x] Runtime tests for Tasks example pallet
- [x] Parsing for task-related macros
- [x] Retrofit parsing to make macros optional
- [x] Expansion for task-related macros
- [x] Adds support for args in tasks
- [x] Retrofit tasks example pallet to use macros instead of manual
syntax
- [x] Weights
- [x] Cleanup
- [x] UI tests
- [x] Docs

## Target Syntax
Adapted from
https://github.com/paritytech/polkadot-sdk/issues/206#issue-1865172283

```rust
// NOTE: this enum is optional and is auto-generated by the other macros if not present
#[pallet::task]
pub enum Task<T: Config> {
    AddNumberIntoTotal {
        i: u32,
    }
}

/// Some running total.
#[pallet::storage]
pub(super) type Total<T: Config<I>, I: 'static = ()> =
StorageValue<_, (u32, u32), ValueQuery>;

/// Numbers to be added into the total.
#[pallet::storage]
pub(super) type Numbers<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;

#[pallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
	/// Add a pair of numbers into the totals and remove them.
	#[pallet::task_list(Numbers::<T, I>::iter_keys())]
	#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
	#[pallet::task_index(0)]
	pub fn add_number_into_total(i: u32) -> DispatchResult {
		let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
		Total::<T, I>::mutate(|(total_keys, total_values)| {
			*total_keys += i;
			*total_values += v;
		});
		Ok(())
	}
}
```

---------

Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com>
Co-authored-by: kianenigma <kian@parity.io>
Co-authored-by: Nikhil Gupta <>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
This commit is contained in:
Sam Johnson
2023-12-08 00:40:26 -05:00
committed by GitHub
parent 34c991e2cf
commit ac3f14d23b
75 changed files with 3516 additions and 24 deletions
@@ -26,6 +26,7 @@ mod metadata;
mod origin;
mod outer_enums;
mod slash_reason;
mod task;
mod unsigned;
pub use call::expand_outer_dispatch;
@@ -38,4 +39,5 @@ pub use metadata::expand_runtime_metadata;
pub use origin::expand_outer_origin;
pub use outer_enums::{expand_outer_enum, OuterEnumType};
pub use slash_reason::expand_outer_slash_reason;
pub use task::expand_outer_task;
pub use unsigned::expand_outer_validate_unsigned;
@@ -0,0 +1,131 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
use crate::construct_runtime::Pallet;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::quote;
/// Expands aggregate `RuntimeTask` enum.
pub fn expand_outer_task(
runtime_name: &Ident,
pallet_decls: &[Pallet],
scrate: &TokenStream2,
) -> TokenStream2 {
let mut from_impls = Vec::new();
let mut task_variants = Vec::new();
let mut variant_names = Vec::new();
let mut task_paths = Vec::new();
for decl in pallet_decls {
if decl.find_part("Task").is_none() {
continue;
}
let variant_name = &decl.name;
let path = &decl.path;
let index = decl.index;
from_impls.push(quote! {
impl From<#path::Task<#runtime_name>> for RuntimeTask {
fn from(hr: #path::Task<#runtime_name>) -> Self {
RuntimeTask::#variant_name(hr)
}
}
impl TryInto<#path::Task<#runtime_name>> for RuntimeTask {
type Error = ();
fn try_into(self) -> Result<#path::Task<#runtime_name>, Self::Error> {
match self {
RuntimeTask::#variant_name(hr) => Ok(hr),
_ => Err(()),
}
}
}
});
task_variants.push(quote! {
#[codec(index = #index)]
#variant_name(#path::Task<#runtime_name>),
});
variant_names.push(quote!(#variant_name));
task_paths.push(quote!(#path::Task));
}
let prelude = quote!(#scrate::traits::tasks::__private);
const INCOMPLETE_MATCH_QED: &'static str =
"cannot have an instantiated RuntimeTask without some Task variant in the runtime. QED";
let output = quote! {
/// An aggregation of all `Task` enums across all pallets included in the current runtime.
#[derive(
Clone, Eq, PartialEq,
#scrate::__private::codec::Encode,
#scrate::__private::codec::Decode,
#scrate::__private::scale_info::TypeInfo,
#scrate::__private::RuntimeDebug,
)]
pub enum RuntimeTask {
#( #task_variants )*
}
#[automatically_derived]
impl #scrate::traits::Task for RuntimeTask {
type Enumeration = #prelude::IntoIter<RuntimeTask>;
fn is_valid(&self) -> bool {
match self {
#(RuntimeTask::#variant_names(val) => val.is_valid(),)*
_ => unreachable!(#INCOMPLETE_MATCH_QED),
}
}
fn run(&self) -> Result<(), #scrate::traits::tasks::__private::DispatchError> {
match self {
#(RuntimeTask::#variant_names(val) => val.run(),)*
_ => unreachable!(#INCOMPLETE_MATCH_QED),
}
}
fn weight(&self) -> #scrate::pallet_prelude::Weight {
match self {
#(RuntimeTask::#variant_names(val) => val.weight(),)*
_ => unreachable!(#INCOMPLETE_MATCH_QED),
}
}
fn task_index(&self) -> u32 {
match self {
#(RuntimeTask::#variant_names(val) => val.task_index(),)*
_ => unreachable!(#INCOMPLETE_MATCH_QED),
}
}
fn iter() -> Self::Enumeration {
let mut all_tasks = Vec::new();
#(all_tasks.extend(#task_paths::iter().map(RuntimeTask::from).collect::<Vec<_>>());)*
all_tasks.into_iter()
}
}
#( #from_impls )*
};
output
}
@@ -386,6 +386,7 @@ fn construct_runtime_final_expansion(
let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate);
let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
let metadata = expand::expand_runtime_metadata(
&name,
&pallets,
@@ -475,6 +476,8 @@ fn construct_runtime_final_expansion(
#dispatch
#tasks
#metadata
#outer_config
@@ -42,6 +42,7 @@ mod keyword {
syn::custom_keyword!(ValidateUnsigned);
syn::custom_keyword!(FreezeReason);
syn::custom_keyword!(HoldReason);
syn::custom_keyword!(Task);
syn::custom_keyword!(LockId);
syn::custom_keyword!(SlashReason);
syn::custom_keyword!(exclude_parts);
@@ -404,6 +405,7 @@ pub enum PalletPartKeyword {
ValidateUnsigned(keyword::ValidateUnsigned),
FreezeReason(keyword::FreezeReason),
HoldReason(keyword::HoldReason),
Task(keyword::Task),
LockId(keyword::LockId),
SlashReason(keyword::SlashReason),
}
@@ -434,6 +436,8 @@ impl Parse for PalletPartKeyword {
Ok(Self::FreezeReason(input.parse()?))
} else if lookahead.peek(keyword::HoldReason) {
Ok(Self::HoldReason(input.parse()?))
} else if lookahead.peek(keyword::Task) {
Ok(Self::Task(input.parse()?))
} else if lookahead.peek(keyword::LockId) {
Ok(Self::LockId(input.parse()?))
} else if lookahead.peek(keyword::SlashReason) {
@@ -459,6 +463,7 @@ impl PalletPartKeyword {
Self::ValidateUnsigned(_) => "ValidateUnsigned",
Self::FreezeReason(_) => "FreezeReason",
Self::HoldReason(_) => "HoldReason",
Self::Task(_) => "Task",
Self::LockId(_) => "LockId",
Self::SlashReason(_) => "SlashReason",
}
@@ -471,7 +476,7 @@ impl PalletPartKeyword {
/// Returns the names of all pallet parts that allow to have a generic argument.
fn all_generic_arg() -> &'static [&'static str] {
&["Event", "Error", "Origin", "Config"]
&["Event", "Error", "Origin", "Config", "Task"]
}
}
@@ -489,6 +494,7 @@ impl ToTokens for PalletPartKeyword {
Self::ValidateUnsigned(inner) => inner.to_tokens(tokens),
Self::FreezeReason(inner) => inner.to_tokens(tokens),
Self::HoldReason(inner) => inner.to_tokens(tokens),
Self::Task(inner) => inner.to_tokens(tokens),
Self::LockId(inner) => inner.to_tokens(tokens),
Self::SlashReason(inner) => inner.to_tokens(tokens),
}