1#![warn(missing_docs)]
58
59use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
60use codec::Encode;
61use scale_info::TypeInfo;
62use sp_runtime::{
63 app_crypto::RuntimeAppPublic,
64 traits::{ExtrinsicLike, IdentifyAccount, One},
65 RuntimeDebug,
66};
67
68pub struct ForAll {}
70pub struct ForAny {}
72
73pub struct SubmitTransaction<T: CreateTransactionBase<RuntimeCall>, RuntimeCall> {
80 _phantom: core::marker::PhantomData<(T, RuntimeCall)>,
81}
82
83impl<T, LocalCall> SubmitTransaction<T, LocalCall>
84where
85 T: CreateTransactionBase<LocalCall>,
86{
87 pub fn submit_transaction(xt: T::Extrinsic) -> Result<(), ()> {
89 sp_io::offchain::submit_transaction(xt.encode())
90 }
91}
92
93#[derive(RuntimeDebug)]
106pub struct Signer<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X = ForAny> {
107 accounts: Option<Vec<T::Public>>,
108 _phantom: core::marker::PhantomData<(X, C)>,
109}
110
111impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Default for Signer<T, C, X> {
112 fn default() -> Self {
113 Self { accounts: Default::default(), _phantom: Default::default() }
114 }
115}
116
117impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Signer<T, C, X> {
118 pub fn all_accounts() -> Signer<T, C, ForAll> {
120 Default::default()
121 }
122
123 pub fn any_account() -> Signer<T, C, ForAny> {
125 Default::default()
126 }
127
128 pub fn with_filter(mut self, accounts: Vec<T::Public>) -> Self {
134 self.accounts = Some(accounts);
135 self
136 }
137
138 pub fn can_sign(&self) -> bool {
140 self.accounts_from_keys().count() > 0
141 }
142
143 pub fn accounts_from_keys<'a>(&'a self) -> Box<dyn Iterator<Item = Account<T>> + 'a> {
148 let keystore_accounts = Self::keystore_accounts();
149 match self.accounts {
150 None => Box::new(keystore_accounts),
151 Some(ref keys) => {
152 let keystore_lookup: BTreeSet<<T as SigningTypes>::Public> =
153 keystore_accounts.map(|account| account.public).collect();
154
155 Box::new(
156 keys.iter()
157 .enumerate()
158 .map(|(index, key)| {
159 let account_id = key.clone().into_account();
160 Account::new(index, account_id, key.clone())
161 })
162 .filter(move |account| keystore_lookup.contains(&account.public)),
163 )
164 },
165 }
166 }
167
168 pub fn keystore_accounts() -> impl Iterator<Item = Account<T>> {
170 C::RuntimeAppPublic::all().into_iter().enumerate().map(|(index, key)| {
171 let generic_public = C::GenericPublic::from(key);
172 let public: T::Public = generic_public.into();
173 let account_id = public.clone().into_account();
174 Account::new(index, account_id, public)
175 })
176 }
177}
178
179impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAll> {
180 fn for_all<F, R>(&self, f: F) -> Vec<(Account<T>, R)>
181 where
182 F: Fn(&Account<T>) -> Option<R>,
183 {
184 let accounts = self.accounts_from_keys();
185 accounts
186 .into_iter()
187 .filter_map(|account| f(&account).map(|res| (account, res)))
188 .collect()
189 }
190}
191
192impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAny> {
193 fn for_any<F, R>(&self, f: F) -> Option<(Account<T>, R)>
194 where
195 F: Fn(&Account<T>) -> Option<R>,
196 {
197 let accounts = self.accounts_from_keys();
198 for account in accounts.into_iter() {
199 let res = f(&account);
200 if let Some(res) = res {
201 return Some((account, res))
202 }
203 }
204 None
205 }
206}
207
208impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
209 for Signer<T, C, ForAll>
210{
211 type SignatureData = Vec<(Account<T>, T::Signature)>;
212
213 fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
214 self.for_all(|account| C::sign(message, account.public.clone()))
215 }
216
217 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
218 where
219 F: Fn(&Account<T>) -> TPayload,
220 TPayload: SignedPayload<T>,
221 {
222 self.for_all(|account| f(account).sign::<C>())
223 }
224}
225
226impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
227 for Signer<T, C, ForAny>
228{
229 type SignatureData = Option<(Account<T>, T::Signature)>;
230
231 fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
232 self.for_any(|account| C::sign(message, account.public.clone()))
233 }
234
235 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
236 where
237 F: Fn(&Account<T>) -> TPayload,
238 TPayload: SignedPayload<T>,
239 {
240 self.for_any(|account| f(account).sign::<C>())
241 }
242}
243
244impl<
245 T: CreateSignedTransaction<LocalCall> + SigningTypes,
246 C: AppCrypto<T::Public, T::Signature>,
247 LocalCall,
248 > SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAny>
249{
250 type Result = Option<(Account<T>, Result<(), ()>)>;
251
252 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
253 self.for_any(|account| {
254 let call = f(account);
255 self.send_single_signed_transaction(account, call)
256 })
257 }
258}
259
260impl<
261 T: SigningTypes + CreateSignedTransaction<LocalCall>,
262 C: AppCrypto<T::Public, T::Signature>,
263 LocalCall,
264 > SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAll>
265{
266 type Result = Vec<(Account<T>, Result<(), ()>)>;
267
268 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
269 self.for_all(|account| {
270 let call = f(account);
271 self.send_single_signed_transaction(account, call)
272 })
273 }
274}
275
276impl<T: SigningTypes + CreateBare<LocalCall>, C: AppCrypto<T::Public, T::Signature>, LocalCall>
277 SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAny>
278{
279 type Result = Option<(Account<T>, Result<(), ()>)>;
280
281 fn send_unsigned_transaction<TPayload, F>(
282 &self,
283 f: F,
284 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
285 ) -> Self::Result
286 where
287 F: Fn(&Account<T>) -> TPayload,
288 TPayload: SignedPayload<T>,
289 {
290 self.for_any(|account| {
291 let payload = f(account);
292 let signature = payload.sign::<C>()?;
293 let call = f2(payload, signature);
294 self.submit_unsigned_transaction(call)
295 })
296 }
297}
298
299impl<T: SigningTypes + CreateBare<LocalCall>, C: AppCrypto<T::Public, T::Signature>, LocalCall>
300 SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAll>
301{
302 type Result = Vec<(Account<T>, Result<(), ()>)>;
303
304 fn send_unsigned_transaction<TPayload, F>(
305 &self,
306 f: F,
307 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
308 ) -> Self::Result
309 where
310 F: Fn(&Account<T>) -> TPayload,
311 TPayload: SignedPayload<T>,
312 {
313 self.for_all(|account| {
314 let payload = f(account);
315 let signature = payload.sign::<C>()?;
316 let call = f2(payload, signature);
317 self.submit_unsigned_transaction(call)
318 })
319 }
320}
321
322#[derive(RuntimeDebug, PartialEq)]
324pub struct Account<T: SigningTypes> {
325 pub index: usize,
327 pub id: T::AccountId,
329 pub public: T::Public,
331}
332
333impl<T: SigningTypes> Account<T> {
334 pub fn new(index: usize, id: T::AccountId, public: T::Public) -> Self {
336 Self { index, id, public }
337 }
338}
339
340impl<T: SigningTypes> Clone for Account<T>
341where
342 T::AccountId: Clone,
343 T::Public: Clone,
344{
345 fn clone(&self) -> Self {
346 Self { index: self.index, id: self.id.clone(), public: self.public.clone() }
347 }
348}
349
350pub trait AppCrypto<Public, Signature> {
376 type RuntimeAppPublic: RuntimeAppPublic;
378
379 type GenericPublic: From<Self::RuntimeAppPublic>
381 + Into<Self::RuntimeAppPublic>
382 + TryFrom<Public>
383 + Into<Public>;
384
385 type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
387 + Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
388 + TryFrom<Signature>
389 + Into<Signature>;
390
391 fn sign(payload: &[u8], public: Public) -> Option<Signature> {
393 let p: Self::GenericPublic = public.try_into().ok()?;
394 let x = Into::<Self::RuntimeAppPublic>::into(p);
395 x.sign(&payload)
396 .map(|x| {
397 let sig: Self::GenericSignature = x.into();
398 sig
399 })
400 .map(Into::into)
401 }
402
403 fn verify(payload: &[u8], public: Public, signature: Signature) -> bool {
405 let p: Self::GenericPublic = match public.try_into() {
406 Ok(a) => a,
407 _ => return false,
408 };
409 let x = Into::<Self::RuntimeAppPublic>::into(p);
410 let signature: Self::GenericSignature = match signature.try_into() {
411 Ok(a) => a,
412 _ => return false,
413 };
414 let signature =
415 Into::<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>::into(signature);
416
417 x.verify(&payload, &signature)
418 }
419}
420
421pub trait SigningTypes: crate::Config {
428 type Public: Clone
433 + PartialEq
434 + IdentifyAccount<AccountId = Self::AccountId>
435 + core::fmt::Debug
436 + codec::Codec
437 + Ord
438 + scale_info::TypeInfo;
439
440 type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
442}
443
444pub trait CreateTransactionBase<LocalCall> {
446 type Extrinsic: ExtrinsicLike + Encode;
448
449 type RuntimeCall: From<LocalCall> + Encode;
453}
454
455pub trait CreateTransaction<LocalCall>: CreateTransactionBase<LocalCall> {
457 type Extension: TypeInfo;
459
460 fn create_transaction(
462 call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
463 extension: Self::Extension,
464 ) -> Self::Extrinsic;
465}
466
467pub trait CreateSignedTransaction<LocalCall>:
469 CreateTransactionBase<LocalCall> + SigningTypes
470{
471 fn create_signed_transaction<C: AppCrypto<Self::Public, Self::Signature>>(
478 call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
479 public: Self::Public,
480 account: Self::AccountId,
481 nonce: Self::Nonce,
482 ) -> Option<Self::Extrinsic>;
483}
484
485#[deprecated(note = "Use `CreateBare` instead")]
491#[doc(inline)]
492pub use CreateBare as CreateInherent;
493
494pub trait CreateBare<LocalCall>: CreateTransactionBase<LocalCall> {
498 fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic;
502
503 #[deprecated(note = "Use `create_bare` instead")]
505 fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic {
506 Self::create_bare(call)
507 }
508}
509
510pub trait SignMessage<T: SigningTypes> {
512 type SignatureData;
516
517 fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
522
523 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
528 where
529 F: Fn(&Account<T>) -> TPayload,
530 TPayload: SignedPayload<T>;
531}
532
533pub trait CreateAuthorizedTransaction<LocalCall>: CreateTransaction<LocalCall> {
543 fn create_extension() -> Self::Extension;
547
548 fn create_authorized_transaction(call: Self::RuntimeCall) -> Self::Extrinsic {
552 Self::create_transaction(call, Self::create_extension())
553 }
554}
555
556pub trait SendSignedTransaction<
558 T: CreateSignedTransaction<LocalCall>,
559 C: AppCrypto<T::Public, T::Signature>,
560 LocalCall,
561>
562{
563 type Result;
567
568 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
575
576 fn send_single_signed_transaction(
578 &self,
579 account: &Account<T>,
580 call: LocalCall,
581 ) -> Option<Result<(), ()>> {
582 let mut account_data = crate::Account::<T>::get(&account.id);
583 log::debug!(
584 target: "runtime::offchain",
585 "Creating signed transaction from account: {:?} (nonce: {:?})",
586 account.id,
587 account_data.nonce,
588 );
589 let transaction = T::create_signed_transaction::<C>(
590 call.into(),
591 account.public.clone(),
592 account.id.clone(),
593 account_data.nonce,
594 )?;
595
596 let res = SubmitTransaction::<T, LocalCall>::submit_transaction(transaction);
597
598 if res.is_ok() {
599 account_data.nonce += One::one();
602 crate::Account::<T>::insert(&account.id, account_data);
603 }
604
605 Some(res)
606 }
607}
608
609pub trait SendUnsignedTransaction<T: SigningTypes + CreateBare<LocalCall>, LocalCall> {
611 type Result;
615
616 fn send_unsigned_transaction<TPayload, F>(
623 &self,
624 f: F,
625 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
626 ) -> Self::Result
627 where
628 F: Fn(&Account<T>) -> TPayload,
629 TPayload: SignedPayload<T>;
630
631 fn submit_unsigned_transaction(&self, call: LocalCall) -> Option<Result<(), ()>> {
633 let xt = T::create_bare(call.into());
634 Some(SubmitTransaction::<T, LocalCall>::submit_transaction(xt))
635 }
636}
637
638pub trait SignedPayload<T: SigningTypes>: Encode {
640 fn public(&self) -> T::Public;
643
644 fn sign<C: AppCrypto<T::Public, T::Signature>>(&self) -> Option<T::Signature> {
648 self.using_encoded(|payload| C::sign(payload, self.public()))
649 }
650
651 fn verify<C: AppCrypto<T::Public, T::Signature>>(&self, signature: T::Signature) -> bool {
655 self.using_encoded(|payload| C::verify(payload, self.public(), signature))
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use crate::mock::{RuntimeCall, Test as TestRuntime, CALL};
663 use codec::Decode;
664 use sp_core::offchain::{testing, TransactionPoolExt};
665 use sp_runtime::testing::{TestSignature, TestXt, UintAuthorityId};
666
667 impl SigningTypes for TestRuntime {
668 type Public = UintAuthorityId;
669 type Signature = TestSignature;
670 }
671
672 type Extrinsic = TestXt<RuntimeCall, ()>;
673
674 impl CreateTransactionBase<RuntimeCall> for TestRuntime {
675 type Extrinsic = Extrinsic;
676 type RuntimeCall = RuntimeCall;
677 }
678
679 impl CreateBare<RuntimeCall> for TestRuntime {
680 fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
681 Extrinsic::new_bare(call)
682 }
683 }
684
685 #[derive(codec::Encode, codec::Decode)]
686 struct SimplePayload {
687 pub public: UintAuthorityId,
688 pub data: Vec<u8>,
689 }
690
691 impl SignedPayload<TestRuntime> for SimplePayload {
692 fn public(&self) -> UintAuthorityId {
693 self.public.clone()
694 }
695 }
696
697 struct DummyAppCrypto;
698 impl AppCrypto<UintAuthorityId, TestSignature> for DummyAppCrypto {
703 type RuntimeAppPublic = UintAuthorityId;
704 type GenericPublic = UintAuthorityId;
705 type GenericSignature = TestSignature;
706 }
707
708 fn assert_account(next: Option<(Account<TestRuntime>, Result<(), ()>)>, index: usize, id: u64) {
709 assert_eq!(next, Some((Account { index, id, public: id.into() }, Ok(()))));
710 }
711
712 #[test]
713 fn should_send_unsigned_with_signed_payload_with_all_accounts() {
714 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
715
716 let mut t = sp_io::TestExternalities::default();
717 t.register_extension(TransactionPoolExt::new(pool));
718
719 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
721
722 t.execute_with(|| {
723 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
725 .send_unsigned_transaction(
726 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
727 |_payload, _signature| CALL.clone(),
728 );
729
730 let mut res = result.into_iter();
732 assert_account(res.next(), 0, 0xf0);
733 assert_account(res.next(), 1, 0xf1);
734 assert_account(res.next(), 2, 0xf2);
735 assert_eq!(res.next(), None);
736
737 let tx1 = pool_state.write().transactions.pop().unwrap();
739 let _tx2 = pool_state.write().transactions.pop().unwrap();
740 let _tx3 = pool_state.write().transactions.pop().unwrap();
741 assert!(pool_state.read().transactions.is_empty());
742 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
743 assert!(tx1.is_inherent());
744 });
745 }
746
747 #[test]
748 fn should_send_unsigned_with_signed_payload_with_any_account() {
749 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
750
751 let mut t = sp_io::TestExternalities::default();
752 t.register_extension(TransactionPoolExt::new(pool));
753
754 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
756
757 t.execute_with(|| {
758 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
760 .send_unsigned_transaction(
761 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
762 |_payload, _signature| CALL.clone(),
763 );
764
765 let mut res = result.into_iter();
767 assert_account(res.next(), 0, 0xf0);
768 assert_eq!(res.next(), None);
769
770 let tx1 = pool_state.write().transactions.pop().unwrap();
772 assert!(pool_state.read().transactions.is_empty());
773 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
774 assert!(tx1.is_inherent());
775 });
776 }
777
778 #[test]
779 fn should_send_unsigned_with_signed_payload_with_all_account_and_filter() {
780 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
781
782 let mut t = sp_io::TestExternalities::default();
783 t.register_extension(TransactionPoolExt::new(pool));
784
785 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
787
788 t.execute_with(|| {
789 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
791 .with_filter(vec![0xf2.into(), 0xf1.into()])
792 .send_unsigned_transaction(
793 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
794 |_payload, _signature| CALL.clone(),
795 );
796
797 let mut res = result.into_iter();
799 assert_account(res.next(), 0, 0xf2);
800 assert_account(res.next(), 1, 0xf1);
801 assert_eq!(res.next(), None);
802
803 let tx1 = pool_state.write().transactions.pop().unwrap();
805 let _tx2 = pool_state.write().transactions.pop().unwrap();
806 assert!(pool_state.read().transactions.is_empty());
807 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
808 assert!(tx1.is_inherent());
809 });
810 }
811
812 #[test]
813 fn should_send_unsigned_with_signed_payload_with_any_account_and_filter() {
814 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
815
816 let mut t = sp_io::TestExternalities::default();
817 t.register_extension(TransactionPoolExt::new(pool));
818
819 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
821
822 t.execute_with(|| {
823 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
825 .with_filter(vec![0xf2.into(), 0xf1.into()])
826 .send_unsigned_transaction(
827 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
828 |_payload, _signature| CALL.clone(),
829 );
830
831 let mut res = result.into_iter();
833 assert_account(res.next(), 0, 0xf2);
834 assert_eq!(res.next(), None);
835
836 let tx1 = pool_state.write().transactions.pop().unwrap();
838 assert!(pool_state.read().transactions.is_empty());
839 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
840 assert!(tx1.is_inherent());
841 });
842 }
843}