Skip to main content

topsoil_core/system/
offchain.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Module helpers for off-chain calls.
8//!
9//! ## Overview
10//!
11//! This module provides transaction related helpers to:
12//! - Submit a raw unsigned transaction
13//! - Submit an unsigned transaction with a signed payload
14//! - Submit a signed transaction.
15//!
16//! ## Usage
17//!
18//! Please refer to [`example-offchain-worker`](../../plant_example_offchain_worker/index.html) for
19//! a concrete example usage of this crate.
20//!
21//! ### Submit a raw unsigned transaction
22//!
23//! To submit a raw unsigned transaction, [`SubmitTransaction`](./struct.SubmitTransaction.html)
24//! can be used.
25//!
26//! ### Signing transactions
27//!
28//! To be able to use signing, the following trait should be implemented:
29//!
30//! - [`AppCrypto`](./trait.AppCrypto.html): where an application-specific key is defined and can be
31//!   used by this module's helpers for signing.
32//! - [`CreateSignedTransaction`](./trait.CreateSignedTransaction.html): where the manner in which
33//!   the transaction is constructed is defined.
34//!
35//! #### Submit an unsigned transaction with a signed payload
36//!
37//! Initially, a payload instance that implements the `SignedPayload` trait should be defined.
38//! See [`PricePayload`](../../plant_example_offchain_worker/struct.PricePayload.html)
39//!
40//! The payload type that is defined defined can then be signed and submitted onchain.
41//!
42//! #### Submit a signed transaction
43//!
44//! [`Signer`](./struct.Signer.html) can be used to sign/verify payloads
45
46#![warn(missing_docs)]
47
48use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
49use codec::Encode;
50use scale_info::TypeInfo;
51use subsoil::runtime::{
52	app_crypto::RuntimeAppPublic,
53	traits::{ExtrinsicLike, IdentifyAccount, One},
54	Debug,
55};
56
57/// Marker struct used to flag using all supported keys to sign a payload.
58pub struct ForAll {}
59/// Marker struct used to flag using any of the supported keys to sign a payload.
60pub struct ForAny {}
61
62/// Provides the ability to directly submit signed and unsigned
63/// transaction onchain.
64///
65/// For submitting unsigned transactions, `submit_unsigned_transaction`
66/// utility function can be used. However, this struct is used by `Signer`
67/// to submit a signed transactions providing the signature along with the call.
68pub struct SubmitTransaction<T: CreateTransactionBase<RuntimeCall>, RuntimeCall> {
69	_phantom: core::marker::PhantomData<(T, RuntimeCall)>,
70}
71
72impl<T, LocalCall> SubmitTransaction<T, LocalCall>
73where
74	T: CreateTransactionBase<LocalCall>,
75{
76	/// A convenience method to submit an extrinsic onchain.
77	pub fn submit_transaction(xt: T::Extrinsic) -> Result<(), ()> {
78		subsoil::io::offchain::submit_transaction(xt.encode())
79	}
80}
81
82/// Provides an implementation for signing transaction payloads.
83///
84/// Keys used for signing are defined when instantiating the signer object.
85/// Signing can be done using:
86///
87/// - All supported keys in the keystore
88/// - Any of the supported keys in the keystore
89/// - An intersection of in-keystore keys and the list of provided keys
90///
91/// The signer is then able to:
92/// - Submit a unsigned transaction with a signed payload
93/// - Submit a signed transaction
94#[derive(Debug)]
95pub struct Signer<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X = ForAny> {
96	accounts: Option<Vec<T::Public>>,
97	_phantom: core::marker::PhantomData<(X, C)>,
98}
99
100impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Default for Signer<T, C, X> {
101	fn default() -> Self {
102		Self { accounts: Default::default(), _phantom: Default::default() }
103	}
104}
105
106impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Signer<T, C, X> {
107	/// Use all available keys for signing.
108	pub fn all_accounts() -> Signer<T, C, ForAll> {
109		Default::default()
110	}
111
112	/// Use any of the available keys for signing.
113	pub fn any_account() -> Signer<T, C, ForAny> {
114		Default::default()
115	}
116
117	/// Use provided `accounts` for signing.
118	///
119	/// Note that not all keys will be necessarily used. The provided
120	/// vector of accounts will be intersected with the supported keys
121	/// in the keystore and the resulting list will be used for signing.
122	pub fn with_filter(mut self, accounts: Vec<T::Public>) -> Self {
123		self.accounts = Some(accounts);
124		self
125	}
126
127	/// Check if there are any keys that could be used for signing.
128	pub fn can_sign(&self) -> bool {
129		self.accounts_from_keys().count() > 0
130	}
131
132	/// Return a vector of the intersection between
133	/// all available accounts and the provided accounts
134	/// in `with_filter`. If no accounts are provided,
135	/// use all accounts by default.
136	pub fn accounts_from_keys<'a>(&'a self) -> Box<dyn Iterator<Item = Account<T>> + 'a> {
137		let keystore_accounts = Self::keystore_accounts();
138		match self.accounts {
139			None => Box::new(keystore_accounts),
140			Some(ref keys) => {
141				let keystore_lookup: BTreeSet<<T as SigningTypes>::Public> =
142					keystore_accounts.map(|account| account.public).collect();
143
144				Box::new(
145					keys.iter()
146						.enumerate()
147						.map(|(index, key)| {
148							let account_id = key.clone().into_account();
149							Account::new(index, account_id, key.clone())
150						})
151						.filter(move |account| keystore_lookup.contains(&account.public)),
152				)
153			},
154		}
155	}
156
157	/// Return all available accounts in keystore.
158	pub fn keystore_accounts() -> impl Iterator<Item = Account<T>> {
159		C::RuntimeAppPublic::all().into_iter().enumerate().map(|(index, key)| {
160			let generic_public = C::GenericPublic::from(key);
161			let public: T::Public = generic_public.into();
162			let account_id = public.clone().into_account();
163			Account::new(index, account_id, public)
164		})
165	}
166}
167
168impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAll> {
169	fn for_all<F, R>(&self, f: F) -> Vec<(Account<T>, R)>
170	where
171		F: Fn(&Account<T>) -> Option<R>,
172	{
173		let accounts = self.accounts_from_keys();
174		accounts
175			.into_iter()
176			.filter_map(|account| f(&account).map(|res| (account, res)))
177			.collect()
178	}
179}
180
181impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAny> {
182	fn for_any<F, R>(&self, f: F) -> Option<(Account<T>, R)>
183	where
184		F: Fn(&Account<T>) -> Option<R>,
185	{
186		let accounts = self.accounts_from_keys();
187		for account in accounts.into_iter() {
188			let res = f(&account);
189			if let Some(res) = res {
190				return Some((account, res));
191			}
192		}
193		None
194	}
195}
196
197impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
198	for Signer<T, C, ForAll>
199{
200	type SignatureData = Vec<(Account<T>, T::Signature)>;
201
202	fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
203		self.for_all(|account| C::sign(message, account.public.clone()))
204	}
205
206	fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
207	where
208		F: Fn(&Account<T>) -> TPayload,
209		TPayload: SignedPayload<T>,
210	{
211		self.for_all(|account| f(account).sign::<C>())
212	}
213}
214
215impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
216	for Signer<T, C, ForAny>
217{
218	type SignatureData = Option<(Account<T>, T::Signature)>;
219
220	fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
221		self.for_any(|account| C::sign(message, account.public.clone()))
222	}
223
224	fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
225	where
226		F: Fn(&Account<T>) -> TPayload,
227		TPayload: SignedPayload<T>,
228	{
229		self.for_any(|account| f(account).sign::<C>())
230	}
231}
232
233impl<
234		T: CreateSignedTransaction<LocalCall> + SigningTypes,
235		C: AppCrypto<T::Public, T::Signature>,
236		LocalCall,
237	> SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAny>
238{
239	type Result = Option<(Account<T>, Result<(), ()>)>;
240
241	fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
242		self.for_any(|account| {
243			let call = f(account);
244			self.send_single_signed_transaction(account, call)
245		})
246	}
247}
248
249impl<
250		T: SigningTypes + CreateSignedTransaction<LocalCall>,
251		C: AppCrypto<T::Public, T::Signature>,
252		LocalCall,
253	> SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAll>
254{
255	type Result = Vec<(Account<T>, Result<(), ()>)>;
256
257	fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
258		self.for_all(|account| {
259			let call = f(account);
260			self.send_single_signed_transaction(account, call)
261		})
262	}
263}
264
265impl<T: SigningTypes + CreateBare<LocalCall>, C: AppCrypto<T::Public, T::Signature>, LocalCall>
266	SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAny>
267{
268	type Result = Option<(Account<T>, Result<(), ()>)>;
269
270	fn send_unsigned_transaction<TPayload, F>(
271		&self,
272		f: F,
273		f2: impl Fn(TPayload, T::Signature) -> LocalCall,
274	) -> Self::Result
275	where
276		F: Fn(&Account<T>) -> TPayload,
277		TPayload: SignedPayload<T>,
278	{
279		self.for_any(|account| {
280			let payload = f(account);
281			let signature = payload.sign::<C>()?;
282			let call = f2(payload, signature);
283			self.submit_unsigned_transaction(call)
284		})
285	}
286}
287
288impl<T: SigningTypes + CreateBare<LocalCall>, C: AppCrypto<T::Public, T::Signature>, LocalCall>
289	SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAll>
290{
291	type Result = Vec<(Account<T>, Result<(), ()>)>;
292
293	fn send_unsigned_transaction<TPayload, F>(
294		&self,
295		f: F,
296		f2: impl Fn(TPayload, T::Signature) -> LocalCall,
297	) -> Self::Result
298	where
299		F: Fn(&Account<T>) -> TPayload,
300		TPayload: SignedPayload<T>,
301	{
302		self.for_all(|account| {
303			let payload = f(account);
304			let signature = payload.sign::<C>()?;
305			let call = f2(payload, signature);
306			self.submit_unsigned_transaction(call)
307		})
308	}
309}
310
311/// Details of an account for which a private key is contained in the keystore.
312#[derive(Debug, PartialEq)]
313pub struct Account<T: SigningTypes> {
314	/// Index on the provided list of accounts or list of all accounts.
315	pub index: usize,
316	/// Runtime-specific `AccountId`.
317	pub id: T::AccountId,
318	/// A runtime-specific `Public` key for that key pair.
319	pub public: T::Public,
320}
321
322impl<T: SigningTypes> Account<T> {
323	/// Create a new Account instance
324	pub fn new(index: usize, id: T::AccountId, public: T::Public) -> Self {
325		Self { index, id, public }
326	}
327}
328
329impl<T: SigningTypes> Clone for Account<T>
330where
331	T::AccountId: Clone,
332	T::Public: Clone,
333{
334	fn clone(&self) -> Self {
335		Self { index: self.index, id: self.id.clone(), public: self.public.clone() }
336	}
337}
338
339/// A type binding runtime-level `Public/Signature` pair with crypto wrapped by `RuntimeAppPublic`.
340///
341/// Implementations of this trait should specify the app-specific public/signature types.
342/// This is merely a wrapper around an existing `RuntimeAppPublic` type, but with
343/// extra non-application-specific crypto type that is being wrapped (e.g. `sr25519`, `ed25519`).
344/// This is needed to later on convert into runtime-specific `Public` key, which might support
345/// multiple different crypto.
346/// The point of this trait is to be able to easily convert between `RuntimeAppPublic`, the wrapped
347/// (generic = non application-specific) crypto types and the `Public` type required by the runtime.
348///
349/// Example (pseudo-)implementation:
350/// ```ignore
351/// // im-online specific crypto
352/// type RuntimeAppPublic = ImOnline(sr25519::Public);
353///
354/// // wrapped "raw" crypto
355/// type GenericPublic = sr25519::Public;
356/// type GenericSignature = sr25519::Signature;
357///
358/// // runtime-specific public key
359/// type Public = MultiSigner: From<sr25519::Public>;
360/// type Signature = MultiSignature: From<sr25519::Signature>;
361/// ```
362// TODO [#5662] Potentially use `IsWrappedBy` types, or find some other way to make it easy to
363// obtain unwrapped crypto (and wrap it back).
364pub trait AppCrypto<Public, Signature> {
365	/// A application-specific crypto.
366	type RuntimeAppPublic: RuntimeAppPublic;
367
368	/// A raw crypto public key wrapped by `RuntimeAppPublic`.
369	type GenericPublic: From<Self::RuntimeAppPublic>
370		+ Into<Self::RuntimeAppPublic>
371		+ TryFrom<Public>
372		+ Into<Public>;
373
374	/// A matching raw crypto `Signature` type.
375	type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
376		+ Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
377		+ TryFrom<Signature>
378		+ Into<Signature>;
379
380	/// Sign payload with the private key to maps to the provided public key.
381	fn sign(payload: &[u8], public: Public) -> Option<Signature> {
382		let p: Self::GenericPublic = public.try_into().ok()?;
383		let x = Into::<Self::RuntimeAppPublic>::into(p);
384		x.sign(&payload)
385			.map(|x| {
386				let sig: Self::GenericSignature = x.into();
387				sig
388			})
389			.map(Into::into)
390	}
391
392	/// Verify signature against the provided public key.
393	fn verify(payload: &[u8], public: Public, signature: Signature) -> bool {
394		let p: Self::GenericPublic = match public.try_into() {
395			Ok(a) => a,
396			_ => return false,
397		};
398		let x = Into::<Self::RuntimeAppPublic>::into(p);
399		let signature: Self::GenericSignature = match signature.try_into() {
400			Ok(a) => a,
401			_ => return false,
402		};
403		let signature =
404			Into::<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>::into(signature);
405
406		x.verify(&payload, &signature)
407	}
408}
409
410/// A wrapper around the types which are used for signing.
411///
412/// This trait adds extra bounds to `Public` and `Signature` types of the runtime
413/// that are necessary to use these types for signing.
414// TODO [#5663] Could this be just `T::Signature as traits::Verify>::Signer`?
415// Seems that this may cause issues with bounds resolution.
416pub trait SigningTypes: crate::system::Config {
417	/// A public key that is capable of identifying `AccountId`s.
418	///
419	/// Usually that's either a raw crypto public key (e.g. `sr25519::Public`) or
420	/// an aggregate type for multiple crypto public keys, like `MultiSigner`.
421	type Public: Clone
422		+ PartialEq
423		+ IdentifyAccount<AccountId = Self::AccountId>
424		+ core::fmt::Debug
425		+ codec::Codec
426		+ Ord
427		+ scale_info::TypeInfo;
428
429	/// A matching `Signature` type.
430	type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
431}
432
433/// Common interface for the `CreateTransaction` trait family to unify the `Call` type.
434pub trait CreateTransactionBase<LocalCall> {
435	/// The extrinsic.
436	type Extrinsic: ExtrinsicLike + Encode;
437
438	/// The runtime's call type.
439	///
440	/// This has additional bound to be able to be created from pallet-local `Call` types.
441	type RuntimeCall: From<LocalCall> + Encode;
442}
443
444/// Interface for creating a transaction.
445pub trait CreateTransaction<LocalCall>: CreateTransactionBase<LocalCall> {
446	/// The extension.
447	type Extension: TypeInfo;
448
449	/// Create a transaction using the call and the desired transaction extension.
450	fn create_transaction(
451		call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
452		extension: Self::Extension,
453	) -> Self::Extrinsic;
454}
455
456/// Interface for creating an old-school signed transaction.
457pub trait CreateSignedTransaction<LocalCall>:
458	CreateTransactionBase<LocalCall> + SigningTypes
459{
460	/// Attempt to create signed extrinsic data that encodes call from given account.
461	///
462	/// Runtime implementation is free to construct the payload to sign and the signature
463	/// in any way it wants.
464	/// Returns `None` if signed extrinsic could not be created (either because signing failed
465	/// or because of any other runtime-specific reason).
466	fn create_signed_transaction<C: AppCrypto<Self::Public, Self::Signature>>(
467		call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
468		public: Self::Public,
469		account: Self::AccountId,
470		nonce: Self::Nonce,
471	) -> Option<Self::Extrinsic>;
472}
473
474/// Interface for creating an inherent; ⚠️  **Deprecated use [`CreateBare`]**.
475///
476/// This is a deprecated type alias for [`CreateBare`].
477///
478/// Doc for [`CreateBare`]:
479#[deprecated(note = "Use `CreateBare` instead")]
480#[doc(inline)]
481pub use CreateBare as CreateInherent;
482
483/// Interface for creating a bare extrinsic.
484///
485/// Bare extrinsic are used for inherent extrinsic and unsigned transaction.
486pub trait CreateBare<LocalCall>: CreateTransactionBase<LocalCall> {
487	/// Create a bare extrinsic.
488	///
489	/// Bare extrinsic are used for inherent extrinsic and unsigned transaction.
490	fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic;
491
492	/// Create an inherent.
493	#[deprecated(note = "Use `create_bare` instead")]
494	fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic {
495		Self::create_bare(call)
496	}
497}
498
499/// A message signer.
500pub trait SignMessage<T: SigningTypes> {
501	/// A signature data.
502	///
503	/// May contain account used for signing and the `Signature` itself.
504	type SignatureData;
505
506	/// Sign a message.
507	///
508	/// Implementation of this method should return
509	/// a result containing the signature.
510	fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
511
512	/// Construct and sign given payload.
513	///
514	/// This method expects `f` to return a `SignedPayload`
515	/// object which is then used for signing.
516	fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
517	where
518		F: Fn(&Account<T>) -> TPayload,
519		TPayload: SignedPayload<T>;
520}
521
522/// Interface for creating a transaction for a call that will be authorized.
523///
524/// Authorized calls are calls that has some specific validation logic execute in the transaction
525/// extension: [`crate::system::AuthorizeCall`].
526/// The authorization logic is defined on the call with the attribute:
527/// [`topsoil_core::pallet_macros::authorize`].
528///
529/// This trait allows the runtime to define the extension to be used when creating an authorized
530/// transaction. It can be used in the offchain worker to create a transaction from a call.
531pub trait CreateAuthorizedTransaction<LocalCall>: CreateTransaction<LocalCall> {
532	/// Create the transaction extension to be used alongside an authorized call.
533	///
534	/// For more information about authorized call see [`topsoil_core::pallet_prelude::authorize`].
535	fn create_extension() -> Self::Extension;
536
537	/// Create a new transaction for an authorized call.
538	///
539	/// For more information about authorized call see [`topsoil_core::pallet_prelude::authorize`].
540	fn create_authorized_transaction(call: Self::RuntimeCall) -> Self::Extrinsic {
541		Self::create_transaction(call, Self::create_extension())
542	}
543}
544
545/// Submit a signed transaction to the transaction pool.
546pub trait SendSignedTransaction<
547	T: CreateSignedTransaction<LocalCall>,
548	C: AppCrypto<T::Public, T::Signature>,
549	LocalCall,
550>
551{
552	/// A submission result.
553	///
554	/// This should contain an indication of success and the account that was used for signing.
555	type Result;
556
557	/// Submit a signed transaction to the local pool.
558	///
559	/// Given `f` closure will be called for every requested account and expects a `Call` object
560	/// to be returned.
561	/// The call is then wrapped into a transaction (see `#CreateSignedTransaction`), signed and
562	/// submitted to the pool.
563	fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
564
565	/// Wraps the call into transaction, signs using given account and submits to the pool.
566	fn send_single_signed_transaction(
567		&self,
568		account: &Account<T>,
569		call: LocalCall,
570	) -> Option<Result<(), ()>> {
571		let mut account_data = crate::system::Account::<T>::get(&account.id);
572		log::debug!(
573			target: "runtime::offchain",
574			"Creating signed transaction from account: {:?} (nonce: {:?})",
575			account.id,
576			account_data.nonce,
577		);
578		let transaction = T::create_signed_transaction::<C>(
579			call.into(),
580			account.public.clone(),
581			account.id.clone(),
582			account_data.nonce,
583		)?;
584
585		let res = SubmitTransaction::<T, LocalCall>::submit_transaction(transaction);
586
587		if res.is_ok() {
588			// increment the nonce. This is fine, since the code should always
589			// be running in off-chain context, so we NEVER persists data.
590			account_data.nonce += One::one();
591			crate::system::Account::<T>::insert(&account.id, account_data);
592		}
593
594		Some(res)
595	}
596}
597
598/// Submit an unsigned transaction onchain with a signed payload
599pub trait SendUnsignedTransaction<T: SigningTypes + CreateBare<LocalCall>, LocalCall> {
600	/// A submission result.
601	///
602	/// Should contain the submission result and the account(s) that signed the payload.
603	type Result;
604
605	/// Send an unsigned transaction with a signed payload.
606	///
607	/// This method takes `f` and `f2` where:
608	/// - `f` is called for every account and is expected to return a `SignedPayload` object.
609	/// - `f2` is then called with the `SignedPayload` returned by `f` and the signature and is
610	/// expected to return a `Call` object to be embedded into transaction.
611	fn send_unsigned_transaction<TPayload, F>(
612		&self,
613		f: F,
614		f2: impl Fn(TPayload, T::Signature) -> LocalCall,
615	) -> Self::Result
616	where
617		F: Fn(&Account<T>) -> TPayload,
618		TPayload: SignedPayload<T>;
619
620	/// Submits an unsigned call to the transaction pool.
621	fn submit_unsigned_transaction(&self, call: LocalCall) -> Option<Result<(), ()>> {
622		let xt = T::create_bare(call.into());
623		Some(SubmitTransaction::<T, LocalCall>::submit_transaction(xt))
624	}
625}
626
627/// Utility trait to be implemented on payloads that can be signed.
628pub trait SignedPayload<T: SigningTypes>: Encode {
629	/// Return a public key that is expected to have a matching key in the keystore,
630	/// which should be used to sign the payload.
631	fn public(&self) -> T::Public;
632
633	/// Sign the payload using the implementor's provided public key.
634	///
635	/// Returns `Some(signature)` if public key is supported.
636	fn sign<C: AppCrypto<T::Public, T::Signature>>(&self) -> Option<T::Signature> {
637		self.using_encoded(|payload| C::sign(payload, self.public()))
638	}
639
640	/// Verify signature against payload.
641	///
642	/// Returns a bool indicating whether the signature is valid or not.
643	fn verify<C: AppCrypto<T::Public, T::Signature>>(&self, signature: T::Signature) -> bool {
644		self.using_encoded(|payload| C::verify(payload, self.public(), signature))
645	}
646}
647
648#[cfg(test)]
649mod tests {
650	use super::*;
651	use crate::system::mock::{RuntimeCall, Test as TestRuntime, CALL};
652	use codec::Decode;
653	use subsoil::core::offchain::{testing, TransactionPoolExt};
654	use subsoil::runtime::testing::{TestSignature, TestXt, UintAuthorityId};
655
656	impl SigningTypes for TestRuntime {
657		type Public = UintAuthorityId;
658		type Signature = TestSignature;
659	}
660
661	type Extrinsic = TestXt<RuntimeCall, ()>;
662
663	impl CreateTransactionBase<RuntimeCall> for TestRuntime {
664		type Extrinsic = Extrinsic;
665		type RuntimeCall = RuntimeCall;
666	}
667
668	impl CreateBare<RuntimeCall> for TestRuntime {
669		fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
670			Extrinsic::new_bare(call)
671		}
672	}
673
674	#[derive(codec::Encode, codec::Decode)]
675	struct SimplePayload {
676		pub public: UintAuthorityId,
677		pub data: Vec<u8>,
678	}
679
680	impl SignedPayload<TestRuntime> for SimplePayload {
681		fn public(&self) -> UintAuthorityId {
682			self.public.clone()
683		}
684	}
685
686	struct DummyAppCrypto;
687	// Bind together the `SigningTypes` with app-crypto and the wrapper types.
688	// here the implementation is pretty dummy, because we use the same type for
689	// both application-specific crypto and the runtime crypto, but in real-life
690	// runtimes it's going to use different types everywhere.
691	impl AppCrypto<UintAuthorityId, TestSignature> for DummyAppCrypto {
692		type RuntimeAppPublic = UintAuthorityId;
693		type GenericPublic = UintAuthorityId;
694		type GenericSignature = TestSignature;
695	}
696
697	fn assert_account(next: Option<(Account<TestRuntime>, Result<(), ()>)>, index: usize, id: u64) {
698		assert_eq!(next, Some((Account { index, id, public: id.into() }, Ok(()))));
699	}
700
701	#[test]
702	fn should_send_unsigned_with_signed_payload_with_all_accounts() {
703		let (pool, pool_state) = testing::TestTransactionPoolExt::new();
704
705		let mut t = subsoil::io::TestExternalities::default();
706		t.register_extension(TransactionPoolExt::new(pool));
707
708		// given
709		UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
710
711		t.execute_with(|| {
712			// when
713			let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
714				.send_unsigned_transaction(
715					|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
716					|_payload, _signature| CALL.clone(),
717				);
718
719			// then
720			let mut res = result.into_iter();
721			assert_account(res.next(), 0, 0xf0);
722			assert_account(res.next(), 1, 0xf1);
723			assert_account(res.next(), 2, 0xf2);
724			assert_eq!(res.next(), None);
725
726			// check the transaction pool content:
727			let tx1 = pool_state.write().transactions.pop().unwrap();
728			let _tx2 = pool_state.write().transactions.pop().unwrap();
729			let _tx3 = pool_state.write().transactions.pop().unwrap();
730			assert!(pool_state.read().transactions.is_empty());
731			let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
732			assert!(tx1.is_inherent());
733		});
734	}
735
736	#[test]
737	fn should_send_unsigned_with_signed_payload_with_any_account() {
738		let (pool, pool_state) = testing::TestTransactionPoolExt::new();
739
740		let mut t = subsoil::io::TestExternalities::default();
741		t.register_extension(TransactionPoolExt::new(pool));
742
743		// given
744		UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
745
746		t.execute_with(|| {
747			// when
748			let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
749				.send_unsigned_transaction(
750					|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
751					|_payload, _signature| CALL.clone(),
752				);
753
754			// then
755			let mut res = result.into_iter();
756			assert_account(res.next(), 0, 0xf0);
757			assert_eq!(res.next(), None);
758
759			// check the transaction pool content:
760			let tx1 = pool_state.write().transactions.pop().unwrap();
761			assert!(pool_state.read().transactions.is_empty());
762			let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
763			assert!(tx1.is_inherent());
764		});
765	}
766
767	#[test]
768	fn should_send_unsigned_with_signed_payload_with_all_account_and_filter() {
769		let (pool, pool_state) = testing::TestTransactionPoolExt::new();
770
771		let mut t = subsoil::io::TestExternalities::default();
772		t.register_extension(TransactionPoolExt::new(pool));
773
774		// given
775		UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
776
777		t.execute_with(|| {
778			// when
779			let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
780				.with_filter(vec![0xf2.into(), 0xf1.into()])
781				.send_unsigned_transaction(
782					|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
783					|_payload, _signature| CALL.clone(),
784				);
785
786			// then
787			let mut res = result.into_iter();
788			assert_account(res.next(), 0, 0xf2);
789			assert_account(res.next(), 1, 0xf1);
790			assert_eq!(res.next(), None);
791
792			// check the transaction pool content:
793			let tx1 = pool_state.write().transactions.pop().unwrap();
794			let _tx2 = pool_state.write().transactions.pop().unwrap();
795			assert!(pool_state.read().transactions.is_empty());
796			let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
797			assert!(tx1.is_inherent());
798		});
799	}
800
801	#[test]
802	fn should_send_unsigned_with_signed_payload_with_any_account_and_filter() {
803		let (pool, pool_state) = testing::TestTransactionPoolExt::new();
804
805		let mut t = subsoil::io::TestExternalities::default();
806		t.register_extension(TransactionPoolExt::new(pool));
807
808		// given
809		UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
810
811		t.execute_with(|| {
812			// when
813			let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
814				.with_filter(vec![0xf2.into(), 0xf1.into()])
815				.send_unsigned_transaction(
816					|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
817					|_payload, _signature| CALL.clone(),
818				);
819
820			// then
821			let mut res = result.into_iter();
822			assert_account(res.next(), 0, 0xf2);
823			assert_eq!(res.next(), None);
824
825			// check the transaction pool content:
826			let tx1 = pool_state.write().transactions.pop().unwrap();
827			assert!(pool_state.read().transactions.is_empty());
828			let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
829			assert!(tx1.is_inherent());
830		});
831	}
832}