1#![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
57pub struct ForAll {}
59pub struct ForAny {}
61
62pub 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 pub fn submit_transaction(xt: T::Extrinsic) -> Result<(), ()> {
78 subsoil::io::offchain::submit_transaction(xt.encode())
79 }
80}
81
82#[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 pub fn all_accounts() -> Signer<T, C, ForAll> {
109 Default::default()
110 }
111
112 pub fn any_account() -> Signer<T, C, ForAny> {
114 Default::default()
115 }
116
117 pub fn with_filter(mut self, accounts: Vec<T::Public>) -> Self {
123 self.accounts = Some(accounts);
124 self
125 }
126
127 pub fn can_sign(&self) -> bool {
129 self.accounts_from_keys().count() > 0
130 }
131
132 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 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#[derive(Debug, PartialEq)]
313pub struct Account<T: SigningTypes> {
314 pub index: usize,
316 pub id: T::AccountId,
318 pub public: T::Public,
320}
321
322impl<T: SigningTypes> Account<T> {
323 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
339pub trait AppCrypto<Public, Signature> {
365 type RuntimeAppPublic: RuntimeAppPublic;
367
368 type GenericPublic: From<Self::RuntimeAppPublic>
370 + Into<Self::RuntimeAppPublic>
371 + TryFrom<Public>
372 + Into<Public>;
373
374 type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
376 + Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
377 + TryFrom<Signature>
378 + Into<Signature>;
379
380 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 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
410pub trait SigningTypes: crate::system::Config {
417 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 type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
431}
432
433pub trait CreateTransactionBase<LocalCall> {
435 type Extrinsic: ExtrinsicLike + Encode;
437
438 type RuntimeCall: From<LocalCall> + Encode;
442}
443
444pub trait CreateTransaction<LocalCall>: CreateTransactionBase<LocalCall> {
446 type Extension: TypeInfo;
448
449 fn create_transaction(
451 call: <Self as CreateTransactionBase<LocalCall>>::RuntimeCall,
452 extension: Self::Extension,
453 ) -> Self::Extrinsic;
454}
455
456pub trait CreateSignedTransaction<LocalCall>:
458 CreateTransactionBase<LocalCall> + SigningTypes
459{
460 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#[deprecated(note = "Use `CreateBare` instead")]
480#[doc(inline)]
481pub use CreateBare as CreateInherent;
482
483pub trait CreateBare<LocalCall>: CreateTransactionBase<LocalCall> {
487 fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic;
491
492 #[deprecated(note = "Use `create_bare` instead")]
494 fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic {
495 Self::create_bare(call)
496 }
497}
498
499pub trait SignMessage<T: SigningTypes> {
501 type SignatureData;
505
506 fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
511
512 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
517 where
518 F: Fn(&Account<T>) -> TPayload,
519 TPayload: SignedPayload<T>;
520}
521
522pub trait CreateAuthorizedTransaction<LocalCall>: CreateTransaction<LocalCall> {
532 fn create_extension() -> Self::Extension;
536
537 fn create_authorized_transaction(call: Self::RuntimeCall) -> Self::Extrinsic {
541 Self::create_transaction(call, Self::create_extension())
542 }
543}
544
545pub trait SendSignedTransaction<
547 T: CreateSignedTransaction<LocalCall>,
548 C: AppCrypto<T::Public, T::Signature>,
549 LocalCall,
550>
551{
552 type Result;
556
557 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
564
565 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 account_data.nonce += One::one();
591 crate::system::Account::<T>::insert(&account.id, account_data);
592 }
593
594 Some(res)
595 }
596}
597
598pub trait SendUnsignedTransaction<T: SigningTypes + CreateBare<LocalCall>, LocalCall> {
600 type Result;
604
605 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 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
627pub trait SignedPayload<T: SigningTypes>: Encode {
629 fn public(&self) -> T::Public;
632
633 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 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 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 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
710
711 t.execute_with(|| {
712 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 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 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 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
745
746 t.execute_with(|| {
747 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 let mut res = result.into_iter();
756 assert_account(res.next(), 0, 0xf0);
757 assert_eq!(res.next(), None);
758
759 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 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
776
777 t.execute_with(|| {
778 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 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 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 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
810
811 t.execute_with(|| {
812 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 let mut res = result.into_iter();
822 assert_account(res.next(), 0, 0xf2);
823 assert_eq!(res.next(), None);
824
825 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}