mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +00:00
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:
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2465,6 +2465,9 @@ pub mod pallet_macros {
|
||||
/// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and
|
||||
/// submit such tasks via an extrinsic defined in `frame_system` called `do_task`.
|
||||
///
|
||||
/// When submitted as unsigned transactions (for example via an off-chain workder), note
|
||||
/// that the tasks will be executed in a random order.
|
||||
///
|
||||
/// ## Example
|
||||
#[doc = docify::embed!("src/tests/tasks.rs", tasks_example)]
|
||||
/// Now, this can be executed as follows:
|
||||
|
||||
@@ -46,6 +46,10 @@ pub trait Task: Sized + FullCodec + TypeInfo + Clone + Debug + PartialEq + Eq {
|
||||
fn iter() -> Self::Enumeration;
|
||||
|
||||
/// Checks if a particular instance of this `Task` variant is a valid piece of work.
|
||||
///
|
||||
/// This is used to validate tasks for unsigned execution. Hence, it MUST be cheap
|
||||
/// with minimal to no storage reads. Else, it can make the blockchain vulnerable
|
||||
/// to DoS attacks.
|
||||
fn is_valid(&self) -> bool;
|
||||
|
||||
/// Performs the work for this particular `Task` variant.
|
||||
|
||||
@@ -741,9 +741,7 @@ pub mod pallet {
|
||||
#[cfg(feature = "experimental")]
|
||||
#[pallet::call_index(8)]
|
||||
#[pallet::weight(task.weight())]
|
||||
pub fn do_task(origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
|
||||
pub fn do_task(_origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
|
||||
if !task.is_valid() {
|
||||
return Err(Error::<T>::InvalidTask.into())
|
||||
}
|
||||
@@ -1032,6 +1030,18 @@ pub mod pallet {
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "experimental")]
|
||||
if let Call::do_task { ref task } = call {
|
||||
if task.is_valid() {
|
||||
return Ok(ValidTransaction {
|
||||
priority: u64::max_value(),
|
||||
requires: Vec::new(),
|
||||
provides: vec![T::Hashing::hash_of(&task.encode()).as_ref().to_vec()],
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(InvalidTransaction::Call.into())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user