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<
277 T: SigningTypes + CreateInherent<LocalCall>,
278 C: AppCrypto<T::Public, T::Signature>,
279 LocalCall,
280 > SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAny>
281{
282 type Result = Option<(Account<T>, Result<(), ()>)>;
283
284 fn send_unsigned_transaction<TPayload, F>(
285 &self,
286 f: F,
287 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
288 ) -> Self::Result
289 where
290 F: Fn(&Account<T>) -> TPayload,
291 TPayload: SignedPayload<T>,
292 {
293 self.for_any(|account| {
294 let payload = f(account);
295 let signature = payload.sign::<C>()?;
296 let call = f2(payload, signature);
297 self.submit_unsigned_transaction(call)
298 })
299 }
300}
301
302impl<
303 T: SigningTypes + CreateInherent<LocalCall>,
304 C: AppCrypto<T::Public, T::Signature>,
305 LocalCall,
306 > SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAll>
307{
308 type Result = Vec<(Account<T>, Result<(), ()>)>;
309
310 fn send_unsigned_transaction<TPayload, F>(
311 &self,
312 f: F,
313 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
314 ) -> Self::Result
315 where
316 F: Fn(&Account<T>) -> TPayload,
317 TPayload: SignedPayload<T>,
318 {
319 self.for_all(|account| {
320 let payload = f(account);
321 let signature = payload.sign::<C>()?;
322 let call = f2(payload, signature);
323 self.submit_unsigned_transaction(call)
324 })
325 }
326}
327
328#[derive(RuntimeDebug, PartialEq)]
330pub struct Account<T: SigningTypes> {
331 pub index: usize,
333 pub id: T::AccountId,
335 pub public: T::Public,
337}
338
339impl<T: SigningTypes> Account<T> {
340 pub fn new(index: usize, id: T::AccountId, public: T::Public) -> Self {
342 Self { index, id, public }
343 }
344}
345
346impl<T: SigningTypes> Clone for Account<T>
347where
348 T::AccountId: Clone,
349 T::Public: Clone,
350{
351 fn clone(&self) -> Self {
352 Self { index: self.index, id: self.id.clone(), public: self.public.clone() }
353 }
354}
355
356pub trait AppCrypto<Public, Signature> {
382 type RuntimeAppPublic: RuntimeAppPublic;
384
385 type GenericPublic: From<Self::RuntimeAppPublic>
387 + Into<Self::RuntimeAppPublic>
388 + TryFrom<Public>
389 + Into<Public>;
390
391 type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
393 + Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
394 + TryFrom<Signature>
395 + Into<Signature>;
396
397 fn sign(payload: &[u8], public: Public) -> Option<Signature> {
399 let p: Self::GenericPublic = public.try_into().ok()?;
400 let x = Into::<Self::RuntimeAppPublic>::into(p);
401 x.sign(&payload)
402 .map(|x| {
403 let sig: Self::GenericSignature = x.into();
404 sig
405 })
406 .map(Into::into)
407 }
408
409 fn verify(payload: &[u8], public: Public, signature: Signature) -> bool {
411 let p: Self::GenericPublic = match public.try_into() {
412 Ok(a) => a,
413 _ => return false,
414 };
415 let x = Into::<Self::RuntimeAppPublic>::into(p);
416 let signature: Self::GenericSignature = match signature.try_into() {
417 Ok(a) => a,
418 _ => return false,
419 };
420 let signature =
421 Into::<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>::into(signature);
422
423 x.verify(&payload, &signature)
424 }
425}
426
427pub trait SigningTypes: crate::Config {
434 type Public: Clone
439 + PartialEq
440 + IdentifyAccount<AccountId = Self::AccountId>
441 + core::fmt::Debug
442 + codec::Codec
443 + Ord
444 + scale_info::TypeInfo;
445
446 type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
448}
449
450pub trait CreateTransactionBase<LocalCall> {
452 type Extrinsic: ExtrinsicLike + Encode;
454
455 type RuntimeCall: From<LocalCall> + Encode;
459}
460
461pub trait CreateTransaction<LocalCall>: CreateTransactionBase<LocalCall> {
463 type Extension: TypeInfo;
465
466 fn create_transaction(
468 call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
469 extension: Self::Extension,
470 ) -> Self::Extrinsic;
471}
472
473pub trait CreateSignedTransaction<LocalCall>:
475 CreateTransactionBase<LocalCall> + SigningTypes
476{
477 fn create_signed_transaction<C: AppCrypto<Self::Public, Self::Signature>>(
484 call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
485 public: Self::Public,
486 account: Self::AccountId,
487 nonce: Self::Nonce,
488 ) -> Option<Self::Extrinsic>;
489}
490
491pub trait CreateInherent<LocalCall>: CreateTransactionBase<LocalCall> {
493 fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic;
495}
496
497pub trait SignMessage<T: SigningTypes> {
499 type SignatureData;
503
504 fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
509
510 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
515 where
516 F: Fn(&Account<T>) -> TPayload,
517 TPayload: SignedPayload<T>;
518}
519
520pub trait SendSignedTransaction<
522 T: CreateSignedTransaction<LocalCall>,
523 C: AppCrypto<T::Public, T::Signature>,
524 LocalCall,
525>
526{
527 type Result;
531
532 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
539
540 fn send_single_signed_transaction(
542 &self,
543 account: &Account<T>,
544 call: LocalCall,
545 ) -> Option<Result<(), ()>> {
546 let mut account_data = crate::Account::<T>::get(&account.id);
547 log::debug!(
548 target: "runtime::offchain",
549 "Creating signed transaction from account: {:?} (nonce: {:?})",
550 account.id,
551 account_data.nonce,
552 );
553 let transaction = T::create_signed_transaction::<C>(
554 call.into(),
555 account.public.clone(),
556 account.id.clone(),
557 account_data.nonce,
558 )?;
559
560 let res = SubmitTransaction::<T, LocalCall>::submit_transaction(transaction);
561
562 if res.is_ok() {
563 account_data.nonce += One::one();
566 crate::Account::<T>::insert(&account.id, account_data);
567 }
568
569 Some(res)
570 }
571}
572
573pub trait SendUnsignedTransaction<T: SigningTypes + CreateInherent<LocalCall>, LocalCall> {
575 type Result;
579
580 fn send_unsigned_transaction<TPayload, F>(
587 &self,
588 f: F,
589 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
590 ) -> Self::Result
591 where
592 F: Fn(&Account<T>) -> TPayload,
593 TPayload: SignedPayload<T>;
594
595 fn submit_unsigned_transaction(&self, call: LocalCall) -> Option<Result<(), ()>> {
597 let xt = T::create_inherent(call.into());
598 Some(SubmitTransaction::<T, LocalCall>::submit_transaction(xt))
599 }
600}
601
602pub trait SignedPayload<T: SigningTypes>: Encode {
604 fn public(&self) -> T::Public;
607
608 fn sign<C: AppCrypto<T::Public, T::Signature>>(&self) -> Option<T::Signature> {
612 self.using_encoded(|payload| C::sign(payload, self.public()))
613 }
614
615 fn verify<C: AppCrypto<T::Public, T::Signature>>(&self, signature: T::Signature) -> bool {
619 self.using_encoded(|payload| C::verify(payload, self.public(), signature))
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use crate::mock::{RuntimeCall, Test as TestRuntime, CALL};
627 use codec::Decode;
628 use sp_core::offchain::{testing, TransactionPoolExt};
629 use sp_runtime::testing::{TestSignature, TestXt, UintAuthorityId};
630
631 impl SigningTypes for TestRuntime {
632 type Public = UintAuthorityId;
633 type Signature = TestSignature;
634 }
635
636 type Extrinsic = TestXt<RuntimeCall, ()>;
637
638 impl CreateTransactionBase<RuntimeCall> for TestRuntime {
639 type Extrinsic = Extrinsic;
640 type RuntimeCall = RuntimeCall;
641 }
642
643 impl CreateInherent<RuntimeCall> for TestRuntime {
644 fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic {
645 Extrinsic::new_bare(call)
646 }
647 }
648
649 #[derive(codec::Encode, codec::Decode)]
650 struct SimplePayload {
651 pub public: UintAuthorityId,
652 pub data: Vec<u8>,
653 }
654
655 impl SignedPayload<TestRuntime> for SimplePayload {
656 fn public(&self) -> UintAuthorityId {
657 self.public.clone()
658 }
659 }
660
661 struct DummyAppCrypto;
662 impl AppCrypto<UintAuthorityId, TestSignature> for DummyAppCrypto {
667 type RuntimeAppPublic = UintAuthorityId;
668 type GenericPublic = UintAuthorityId;
669 type GenericSignature = TestSignature;
670 }
671
672 fn assert_account(next: Option<(Account<TestRuntime>, Result<(), ()>)>, index: usize, id: u64) {
673 assert_eq!(next, Some((Account { index, id, public: id.into() }, Ok(()))));
674 }
675
676 #[test]
677 fn should_send_unsigned_with_signed_payload_with_all_accounts() {
678 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
679
680 let mut t = sp_io::TestExternalities::default();
681 t.register_extension(TransactionPoolExt::new(pool));
682
683 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
685
686 t.execute_with(|| {
687 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
689 .send_unsigned_transaction(
690 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
691 |_payload, _signature| CALL.clone(),
692 );
693
694 let mut res = result.into_iter();
696 assert_account(res.next(), 0, 0xf0);
697 assert_account(res.next(), 1, 0xf1);
698 assert_account(res.next(), 2, 0xf2);
699 assert_eq!(res.next(), None);
700
701 let tx1 = pool_state.write().transactions.pop().unwrap();
703 let _tx2 = pool_state.write().transactions.pop().unwrap();
704 let _tx3 = pool_state.write().transactions.pop().unwrap();
705 assert!(pool_state.read().transactions.is_empty());
706 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
707 assert!(tx1.is_inherent());
708 });
709 }
710
711 #[test]
712 fn should_send_unsigned_with_signed_payload_with_any_account() {
713 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
714
715 let mut t = sp_io::TestExternalities::default();
716 t.register_extension(TransactionPoolExt::new(pool));
717
718 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
720
721 t.execute_with(|| {
722 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
724 .send_unsigned_transaction(
725 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
726 |_payload, _signature| CALL.clone(),
727 );
728
729 let mut res = result.into_iter();
731 assert_account(res.next(), 0, 0xf0);
732 assert_eq!(res.next(), None);
733
734 let tx1 = pool_state.write().transactions.pop().unwrap();
736 assert!(pool_state.read().transactions.is_empty());
737 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
738 assert!(tx1.is_inherent());
739 });
740 }
741
742 #[test]
743 fn should_send_unsigned_with_signed_payload_with_all_account_and_filter() {
744 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
745
746 let mut t = sp_io::TestExternalities::default();
747 t.register_extension(TransactionPoolExt::new(pool));
748
749 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
751
752 t.execute_with(|| {
753 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
755 .with_filter(vec![0xf2.into(), 0xf1.into()])
756 .send_unsigned_transaction(
757 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
758 |_payload, _signature| CALL.clone(),
759 );
760
761 let mut res = result.into_iter();
763 assert_account(res.next(), 0, 0xf2);
764 assert_account(res.next(), 1, 0xf1);
765 assert_eq!(res.next(), None);
766
767 let tx1 = pool_state.write().transactions.pop().unwrap();
769 let _tx2 = pool_state.write().transactions.pop().unwrap();
770 assert!(pool_state.read().transactions.is_empty());
771 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
772 assert!(tx1.is_inherent());
773 });
774 }
775
776 #[test]
777 fn should_send_unsigned_with_signed_payload_with_any_account_and_filter() {
778 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
779
780 let mut t = sp_io::TestExternalities::default();
781 t.register_extension(TransactionPoolExt::new(pool));
782
783 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
785
786 t.execute_with(|| {
787 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
789 .with_filter(vec![0xf2.into(), 0xf1.into()])
790 .send_unsigned_transaction(
791 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
792 |_payload, _signature| CALL.clone(),
793 );
794
795 let mut res = result.into_iter();
797 assert_account(res.next(), 0, 0xf2);
798 assert_eq!(res.next(), None);
799
800 let tx1 = pool_state.write().transactions.pop().unwrap();
802 assert!(pool_state.read().transactions.is_empty());
803 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
804 assert!(tx1.is_inherent());
805 });
806 }
807}