Adds ability to trigger tasks via unsigned transactions (#4075)

This PR updates the `validate_unsigned` hook for `frame_system` to allow
valid tasks to be submitted as unsigned transactions. It also updates
the task example to be able to submit such transactions via an off-chain
worker.

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
This commit is contained in:
gupnik
2024-04-24 11:25:54 +05:30
committed by GitHub
parent ffbce2a817
commit 0a56d071c7
7 changed files with 126 additions and 5 deletions
+36 -2
View File
@@ -19,6 +19,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::dispatch::DispatchResult;
use frame_system::offchain::SendTransactionTypes;
#[cfg(feature = "experimental")]
use frame_system::offchain::SubmitTransaction;
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
@@ -31,10 +34,14 @@ mod benchmarking;
pub mod weights;
pub use weights::*;
#[cfg(feature = "experimental")]
const LOG_TARGET: &str = "pallet-example-tasks";
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::error]
pub enum Error<T> {
@@ -59,9 +66,36 @@ pub mod pallet {
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
#[cfg(feature = "experimental")]
fn offchain_worker(_block_number: BlockNumberFor<T>) {
if let Some(key) = Numbers::<T>::iter_keys().next() {
// Create a valid task
let task = Task::<T>::AddNumberIntoTotal { i: key };
let runtime_task = <T as Config>::RuntimeTask::from(task);
let call = frame_system::Call::<T>::do_task { task: runtime_task.into() };
// Submit the task as an unsigned transaction
let res =
SubmitTransaction::<T, frame_system::Call<T>>::submit_unsigned_transaction(
call.into(),
);
match res {
Ok(_) => log::info!(target: LOG_TARGET, "Submitted the task."),
Err(e) => log::error!(target: LOG_TARGET, "Error submitting task: {:?}", e),
}
}
}
}
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeTask: frame_support::traits::Task;
pub trait Config:
SendTransactionTypes<frame_system::Call<Self>> + frame_system::Config
{
type RuntimeTask: frame_support::traits::Task
+ IsType<<Self as frame_system::Config>::RuntimeTask>
+ From<Task<Self>>;
type WeightInfo: WeightInfo;
}
@@ -20,6 +20,7 @@
use crate::{self as tasks_example};
use frame_support::derive_impl;
use sp_runtime::testing::TestXt;
pub type AccountId = u32;
pub type Balance = u32;
@@ -32,12 +33,32 @@ frame_support::construct_runtime!(
}
);
pub type Extrinsic = TestXt<RuntimeCall, ()>;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
}
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type OverarchingCall = RuntimeCall;
type Extrinsic = Extrinsic;
}
impl tasks_example::Config for Runtime {
type RuntimeTask = RuntimeTask;
type WeightInfo = ();
}
pub fn advance_to(b: u64) {
#[cfg(feature = "experimental")]
use frame_support::traits::Hooks;
while System::block_number() < b {
System::set_block_number(System::block_number() + 1);
#[cfg(feature = "experimental")]
TasksExample::offchain_worker(System::block_number());
}
}
@@ -19,7 +19,11 @@
#![cfg(test)]
use crate::{mock::*, Numbers};
#[cfg(feature = "experimental")]
use codec::Decode;
use frame_support::traits::Task;
#[cfg(feature = "experimental")]
use sp_core::offchain::{testing, OffchainWorkerExt, TransactionPoolExt};
use sp_runtime::BuildStorage;
#[cfg(feature = "experimental")]
@@ -130,3 +134,29 @@ fn task_execution_fails_for_invalid_task() {
);
});
}
#[cfg(feature = "experimental")]
#[test]
fn task_with_offchain_worker() {
let (offchain, _offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.execute_with(|| {
advance_to(1);
assert!(pool_state.read().transactions.is_empty());
Numbers::<Runtime>::insert(0, 10);
assert_eq!(crate::Total::<Runtime>::get(), (0, 0));
advance_to(2);
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature, None);
});
}