Files
pezkuwi-subxt/substrate/frame/system/src/offchain.rs
T
Andrew Jones 49b6dfd2e5 Enrich metadata with type information (#8615)
* Cargo.lock after merge

* Restore scale-info feature

* Fully qualify TypeInfo derive

* Skip PendingSwap T

* Add missing skip_type_params attr

* metadata docs features

* Reduce pallet event attribute to struct

* Cargo.lock

* Update frame/balances/src/tests_composite.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Line widths check

* Cargo.lock

* Add scale-info/std

* Update frame/system/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Use `skip_type_params` to remove `TypeInfo` requirements on checks

* Revert "Remove unused Call metadata stuff"

This reverts commit 41311f85

* Skip BalanceSwapAction type parameter

* Remove unused event metadata macro

* Update frame-metadata

* Update primitives/npos-elections/compact/src/codec.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Manual TypeInfo for Header

* Remove TypeInfo requirement for consts in BoundedVec etc.

* Another TypeInfo bound removed

* review: fix indentation

* TypeInfo impls for Identity types

* Add some todos to add custom TypeInfo impls

* Update frame/support/procedural/src/pallet/expand/pallet_struct.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Add some todos to add custom TypeInfo impls

* Add a test for manual Data TypeInfo impl

* Add custom TypeInfo impl for Vote

* Era custom TypeInfo crimes

* Revert finality-grandpa version to 0.14.z

* review: renamed module to pallet_constants_metadata

* New line at end of file

* Add missing scale-info/std

* Update frame/support/src/storage/types/mod.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Remove StorageEntryType::Map unused flag

* Add missing scale-info dependency after merge

* SignedExtension::AdditionalSigned metadata

* Update frame-metadata, use abbreviated docs and args fields

* Update frame/example/Cargo.toml

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Add scale_info/std and remove unused scale-info dependency

* Remove scale-info dependency

* Remove treasury pallet::metadata

* Remove redundant Event test

* Add back scale-info as dev dependency

* fix error metadata when no error defined in decl_module

* Add Module3 to tests

* Fix metadata test

* Add docs feature to frame-support test

* WIP fixing pallet metadata test

* Remove redundant FunctionMetadata, FunctionArgumentMetadata as per https://github.com/paritytech/frame-metadata/pull/20

* Use main branch of frame-metadata

* Use patch of scale-info for latest changes

* Use latest patched scale-info

* Manual TypeInfo for DigestItem

* Manual TypeInfo for DigestItem

* Update scale-info

* Skip __Ignore variants for Error, depends on https://github.com/paritytech/scale-info/pull/117

* Named fields for FRAME v2 pallet Call variants

* Named fields for FRAME v1 pallet Call variants

* Add missing scale-info dependency

* WIP expand benchmark call variant

* fix benchmark with new function

create a new function for each variant of a pallet call.
This function is called by benchmarking macro in order not to break call
creation with unnamed argument

* fix tests

* more fix

* Fix staking tests

* Fix offchain workers calls

* Cherry pick rustfmt.toml from master

* cargo +nightly-2021-06-22 fmt --all

* Update to new call variant structs

* More call variant struct updates

* Remove unused import

* More call variant structs

* More call variant structs

* Even more call variant structs

* Mooar variant structs

* Evermore variant structs

* Call variant structs ad infinitum

* Fmt

* More call variants

* Last call variant

* Call variants all done?

* Fix SS58Prefix type

* Potential workaround for BitFlags<IdentityFields> TypeInfo

* Enable docs capturing for Call, Event, and Error types

* Fix IdentityFields TypeInfo

* Remove metadata-docs feature

* Add capture_docs = true for legacy Call, Event and Error types

* Fmt

* Fix metadata test type

* Update benchmarks with call struct variants

* Fmt

* More test fixes

* Fmt

* Fix benches

* Use latest capture_docs attr

* Latest scale_info

* Fmt

* review: change &Vec to &[]

* Remove pallet metadata attr

* review: remove commented out test code

* review: skip_type_params trailing comma suggestion

* Update to scale-info 0.10.0

* Update construct_runtime ui tests, different because of metadata TypeInfo impls

* Add some TypeInfo derives for UI tests

* Update storage ensure span ui stderrs

* Update call argument bound ui tests

Possibly changed because change from tuple to struct variants?

* Add scale-info dev dependency

* Update to latest finality-grandpa release

* review: missing newline

* review: missing scale-info/std

* review: remove duplicate scale-info/std

* review: remove fully qualified TypeInfo

* review: add missing scale-info/std

* review: remove unnecessary imports.

* Fmt

* Use crates.io RC version of frame-metadata

* Remove scale-info/std because it is a dev dependency

* Add missing scale_info dev-dependency for test

* Delete empty metadata folder

* Fix sp_std import

* review: improve manual UncheckedExtrinsic TypeInfo impl

* review: use full scale-info for dev-dependency

* Remove DefaultByteGetter impl

* review: derive TypeInfo for generic header

* Fmt

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update bin/node/executor/Cargo.toml

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/identity/src/types.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/dispatch.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Remove redundant derive

* Simplify scale-info dependency

* Strip underscore prefix from call variant struct names

* Another underscore field

* More underscore fields

* Another underscore field

* Update to frame-metadata 14.0.0-rc.2 with combined StorageEntryType::Map

* Fmt

* Revert weights formatting

* Fix up some tests

* Fix up some tests for StorageEntryTypeMetadata

* scale-info dev dependency

* Fix test error

* Add missing TypeInfo derives

* Add back missing scale-info dependency

* Add back missing scale-info dependency

* Fix npos compact impls

* Cargo.lock

* Fmt

* Fix errors

* Fmt

* Fix renamed raw_solution field

* Fix error

* Fmt

* Fix some benchmarks

* Fmt

* Stray R

* Fix

* Add missing TypeInfos

* ui test fix

* Fix line widths

* Revert "ui test fix"

This reverts commit 2d15ec058a216e3f92d713f1174603a2bb1eac65.

* Upgrade to scale-info 0.11.0

* Revert "Upgrade to scale-info 0.11.0"

This reverts commit 047bb179085a0059c36cd20ab405f55cf0867e28.

* Add Runtime type

* Update to scale-info 0.12

* Update to scale-info 1.0

* Update frame-metadata to version 14.0.0

* Patch finality-grandpa until release available

* Fix metadata tests

* Fix metadata tests

* Fmt

* Remove patched finality-grandpa

* Fix tests, use scale_info imports

* Fix pallet tests

* Add BlockNumber TypeInfo bound

* ui test fix

* Cargo.lock

* Remove pallet metadata

* Cargo.lock

* Add missing scale-info dependency

* Remove pallet event metadata

* Fix error

* Fix collective errors

* Semicolol

* Fmt

* Remove another metadata attribute

* Add new variant to custom digest TypeInfo

* Fmt

* Cargo.lock from master

* Remove comma lol

* Fix example call error

* Fix example call error properly

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
2021-09-15 11:40:41 +00:00

800 lines
26 KiB
Rust

// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Module helpers for off-chain calls.
//!
//! ## Overview
//!
//! This module provides transaction related helpers to:
//! - Submit a raw unsigned transaction
//! - Submit an unsigned transaction with a signed payload
//! - Submit a signed transction.
//!
//! ## Usage
//!
//! Please refer to [`example-offchain-worker`](../../pallet_example_offchain_worker/index.html) for
//! a concrete example usage of this crate.
//!
//! ### Submit a raw unsigned transaction
//!
//! To submit a raw unsigned transaction, [`SubmitTransaction`](./struct.SubmitTransaction.html)
//! can be used.
//!
//! ### Signing transactions
//!
//! To be able to use signing, the following trait should be implemented:
//!
//! - [`AppCrypto`](./trait.AppCrypto.html): where an application-specific key is defined and can be
//! used by this module's helpers for signing.
//! - [`CreateSignedTransaction`](./trait.CreateSignedTransaction.html): where the manner in which
//! the transaction is constructed is defined.
//!
//! #### Submit an unsigned transaction with a signed payload
//!
//! Initially, a payload instance that implements the `SignedPayload` trait should be defined.
//! See [`PricePayload`](../../pallet_example_offchain_worker/struct.PricePayload.html)
//!
//! The payload type that is defined defined can then be signed and submitted onchain.
//!
//! #### Submit a signed transaction
//!
//! [`Signer`](./struct.Signer.html) can be used to sign/verify payloads
#![warn(missing_docs)]
use codec::Encode;
use frame_support::RuntimeDebug;
use sp_runtime::{
app_crypto::RuntimeAppPublic,
traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One},
};
use sp_std::{
collections::btree_set::BTreeSet,
convert::{TryFrom, TryInto},
prelude::{Box, Vec},
};
/// Marker struct used to flag using all supported keys to sign a payload.
pub struct ForAll {}
/// Marker struct used to flag using any of the supported keys to sign a payload.
pub struct ForAny {}
/// Provides the ability to directly submit signed and unsigned
/// transaction onchain.
///
/// For submitting unsigned transactions, `submit_unsigned_transaction`
/// utility function can be used. However, this struct is used by `Signer`
/// to submit a signed transactions providing the signature along with the call.
pub struct SubmitTransaction<T: SendTransactionTypes<OverarchingCall>, OverarchingCall> {
_phantom: sp_std::marker::PhantomData<(T, OverarchingCall)>,
}
impl<T, LocalCall> SubmitTransaction<T, LocalCall>
where
T: SendTransactionTypes<LocalCall>,
{
/// Submit transaction onchain by providing the call and an optional signature
pub fn submit_transaction(
call: <T as SendTransactionTypes<LocalCall>>::OverarchingCall,
signature: Option<<T::Extrinsic as ExtrinsicT>::SignaturePayload>,
) -> Result<(), ()> {
let xt = T::Extrinsic::new(call.into(), signature).ok_or(())?;
sp_io::offchain::submit_transaction(xt.encode())
}
/// A convenience method to submit an unsigned transaction onchain.
pub fn submit_unsigned_transaction(
call: <T as SendTransactionTypes<LocalCall>>::OverarchingCall,
) -> Result<(), ()> {
SubmitTransaction::<T, LocalCall>::submit_transaction(call, None)
}
}
/// Provides an implementation for signing transaction payloads.
///
/// Keys used for signing are defined when instantiating the signer object.
/// Signing can be done using:
///
/// - All supported keys in the keystore
/// - Any of the supported keys in the keystore
/// - An intersection of in-keystore keys and the list of provided keys
///
/// The signer is then able to:
/// - Submit a unsigned transaction with a signed payload
/// - Submit a signed transaction
#[derive(RuntimeDebug)]
pub struct Signer<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X = ForAny> {
accounts: Option<Vec<T::Public>>,
_phantom: sp_std::marker::PhantomData<(X, C)>,
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Default for Signer<T, C, X> {
fn default() -> Self {
Self { accounts: Default::default(), _phantom: Default::default() }
}
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Signer<T, C, X> {
/// Use all available keys for signing.
pub fn all_accounts() -> Signer<T, C, ForAll> {
Default::default()
}
/// Use any of the available keys for signing.
pub fn any_account() -> Signer<T, C, ForAny> {
Default::default()
}
/// Use provided `accounts` for signing.
///
/// Note that not all keys will be necessarily used. The provided
/// vector of accounts will be intersected with the supported keys
/// in the keystore and the resulting list will be used for signing.
pub fn with_filter(mut self, accounts: Vec<T::Public>) -> Self {
self.accounts = Some(accounts);
self
}
/// Check if there are any keys that could be used for signing.
pub fn can_sign(&self) -> bool {
self.accounts_from_keys().count() > 0
}
/// Return a vector of the intersection between
/// all available accounts and the provided accounts
/// in `with_filter`. If no accounts are provided,
/// use all accounts by default.
fn accounts_from_keys<'a>(&'a self) -> Box<dyn Iterator<Item = Account<T>> + 'a> {
let keystore_accounts = self.keystore_accounts();
match self.accounts {
None => Box::new(keystore_accounts),
Some(ref keys) => {
let keystore_lookup: BTreeSet<<T as SigningTypes>::Public> =
keystore_accounts.map(|account| account.public).collect();
Box::new(
keys.into_iter()
.enumerate()
.map(|(index, key)| {
let account_id = key.clone().into_account();
Account::new(index, account_id, key.clone())
})
.filter(move |account| keystore_lookup.contains(&account.public)),
)
},
}
}
fn keystore_accounts(&self) -> impl Iterator<Item = Account<T>> {
C::RuntimeAppPublic::all().into_iter().enumerate().map(|(index, key)| {
let generic_public = C::GenericPublic::from(key);
let public: T::Public = generic_public.into();
let account_id = public.clone().into_account();
Account::new(index, account_id, public)
})
}
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAll> {
fn for_all<F, R>(&self, f: F) -> Vec<(Account<T>, R)>
where
F: Fn(&Account<T>) -> Option<R>,
{
let accounts = self.accounts_from_keys();
accounts
.into_iter()
.filter_map(|account| f(&account).map(|res| (account, res)))
.collect()
}
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAny> {
fn for_any<F, R>(&self, f: F) -> Option<(Account<T>, R)>
where
F: Fn(&Account<T>) -> Option<R>,
{
let accounts = self.accounts_from_keys();
for account in accounts.into_iter() {
let res = f(&account);
if let Some(res) = res {
return Some((account, res))
}
}
None
}
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
for Signer<T, C, ForAll>
{
type SignatureData = Vec<(Account<T>, T::Signature)>;
fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
self.for_all(|account| C::sign(message, account.public.clone()))
}
fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>,
{
self.for_all(|account| f(account).sign::<C>())
}
}
impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
for Signer<T, C, ForAny>
{
type SignatureData = Option<(Account<T>, T::Signature)>;
fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
self.for_any(|account| C::sign(message, account.public.clone()))
}
fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>,
{
self.for_any(|account| f(account).sign::<C>())
}
}
impl<
T: CreateSignedTransaction<LocalCall> + SigningTypes,
C: AppCrypto<T::Public, T::Signature>,
LocalCall,
> SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAny>
{
type Result = Option<(Account<T>, Result<(), ()>)>;
fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
self.for_any(|account| {
let call = f(account);
self.send_single_signed_transaction(account, call)
})
}
}
impl<
T: SigningTypes + CreateSignedTransaction<LocalCall>,
C: AppCrypto<T::Public, T::Signature>,
LocalCall,
> SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAll>
{
type Result = Vec<(Account<T>, Result<(), ()>)>;
fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
self.for_all(|account| {
let call = f(account);
self.send_single_signed_transaction(account, call)
})
}
}
impl<
T: SigningTypes + SendTransactionTypes<LocalCall>,
C: AppCrypto<T::Public, T::Signature>,
LocalCall,
> SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAny>
{
type Result = Option<(Account<T>, Result<(), ()>)>;
fn send_unsigned_transaction<TPayload, F>(
&self,
f: F,
f2: impl Fn(TPayload, T::Signature) -> LocalCall,
) -> Self::Result
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>,
{
self.for_any(|account| {
let payload = f(account);
let signature = payload.sign::<C>()?;
let call = f2(payload, signature);
self.submit_unsigned_transaction(call)
})
}
}
impl<
T: SigningTypes + SendTransactionTypes<LocalCall>,
C: AppCrypto<T::Public, T::Signature>,
LocalCall,
> SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAll>
{
type Result = Vec<(Account<T>, Result<(), ()>)>;
fn send_unsigned_transaction<TPayload, F>(
&self,
f: F,
f2: impl Fn(TPayload, T::Signature) -> LocalCall,
) -> Self::Result
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>,
{
self.for_all(|account| {
let payload = f(account);
let signature = payload.sign::<C>()?;
let call = f2(payload, signature);
self.submit_unsigned_transaction(call)
})
}
}
/// Details of an account for which a private key is contained in the keystore.
#[derive(RuntimeDebug, PartialEq)]
pub struct Account<T: SigningTypes> {
/// Index on the provided list of accounts or list of all accounts.
pub index: usize,
/// Runtime-specific `AccountId`.
pub id: T::AccountId,
/// A runtime-specific `Public` key for that key pair.
pub public: T::Public,
}
impl<T: SigningTypes> Account<T> {
/// Create a new Account instance
pub fn new(index: usize, id: T::AccountId, public: T::Public) -> Self {
Self { index, id, public }
}
}
impl<T: SigningTypes> Clone for Account<T>
where
T::AccountId: Clone,
T::Public: Clone,
{
fn clone(&self) -> Self {
Self { index: self.index, id: self.id.clone(), public: self.public.clone() }
}
}
/// A type binding runtime-level `Public/Signature` pair with crypto wrapped by `RuntimeAppPublic`.
///
/// Implementations of this trait should specify the app-specific public/signature types.
/// This is merely a wrapper around an existing `RuntimeAppPublic` type, but with
/// extra non-application-specific crypto type that is being wrapped (e.g. `sr25519`, `ed25519`).
/// This is needed to later on convert into runtime-specific `Public` key, which might support
/// multiple different crypto.
/// The point of this trait is to be able to easily convert between `RuntimeAppPublic`, the wrapped
/// (generic = non application-specific) crypto types and the `Public` type required by the runtime.
///
/// Example (pseudo-)implementation:
/// ```ignore
/// // im-online specific crypto
/// type RuntimeAppPublic = ImOnline(sr25519::Public);
///
/// // wrapped "raw" crypto
/// type GenericPublic = sr25519::Public;
/// type GenericSignature = sr25519::Signature;
///
/// // runtime-specific public key
/// type Public = MultiSigner: From<sr25519::Public>;
/// type Signature = MulitSignature: From<sr25519::Signature>;
/// ```
// TODO [#5662] Potentially use `IsWrappedBy` types, or find some other way to make it easy to
// obtain unwrapped crypto (and wrap it back).
pub trait AppCrypto<Public, Signature> {
/// A application-specific crypto.
type RuntimeAppPublic: RuntimeAppPublic;
/// A raw crypto public key wrapped by `RuntimeAppPublic`.
type GenericPublic: From<Self::RuntimeAppPublic>
+ Into<Self::RuntimeAppPublic>
+ TryFrom<Public>
+ Into<Public>;
/// A matching raw crypto `Signature` type.
type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
+ Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
+ TryFrom<Signature>
+ Into<Signature>;
/// Sign payload with the private key to maps to the provided public key.
fn sign(payload: &[u8], public: Public) -> Option<Signature> {
let p: Self::GenericPublic = public.try_into().ok()?;
let x = Into::<Self::RuntimeAppPublic>::into(p);
x.sign(&payload)
.map(|x| {
let sig: Self::GenericSignature = x.into();
sig
})
.map(Into::into)
}
/// Verify signature against the provided public key.
fn verify(payload: &[u8], public: Public, signature: Signature) -> bool {
let p: Self::GenericPublic = match public.try_into() {
Ok(a) => a,
_ => return false,
};
let x = Into::<Self::RuntimeAppPublic>::into(p);
let signature: Self::GenericSignature = match signature.try_into() {
Ok(a) => a,
_ => return false,
};
let signature =
Into::<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>::into(signature);
x.verify(&payload, &signature)
}
}
/// A wrapper around the types which are used for signing.
///
/// This trait adds extra bounds to `Public` and `Signature` types of the runtime
/// that are necessary to use these types for signing.
// TODO [#5663] Could this be just `T::Signature as traits::Verify>::Signer`?
// Seems that this may cause issues with bounds resolution.
pub trait SigningTypes: crate::Config {
/// A public key that is capable of identifying `AccountId`s.
///
/// Usually that's either a raw crypto public key (e.g. `sr25519::Public`) or
/// an aggregate type for multiple crypto public keys, like `MulitSigner`.
type Public: Clone
+ PartialEq
+ IdentifyAccount<AccountId = Self::AccountId>
+ core::fmt::Debug
+ codec::Codec
+ Ord
+ scale_info::TypeInfo;
/// A matching `Signature` type.
type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
}
/// A definition of types required to submit transactions from within the runtime.
pub trait SendTransactionTypes<LocalCall> {
/// The extrinsic type expected by the runtime.
type Extrinsic: ExtrinsicT<Call = Self::OverarchingCall> + codec::Encode;
/// The runtime's call type.
///
/// This has additional bound to be able to be created from pallet-local `Call` types.
type OverarchingCall: From<LocalCall> + codec::Encode;
}
/// Create signed transaction.
///
/// This trait is meant to be implemented by the runtime and is responsible for constructing
/// a payload to be signed and contained within the extrinsic.
/// This will most likely include creation of `SignedExtra` (a set of `SignedExtensions`).
/// Note that the result can be altered by inspecting the `Call` (for instance adjusting
/// fees, or mortality depending on the `pallet` being called).
pub trait CreateSignedTransaction<LocalCall>:
SendTransactionTypes<LocalCall> + SigningTypes
{
/// Attempt to create signed extrinsic data that encodes call from given account.
///
/// Runtime implementation is free to construct the payload to sign and the signature
/// in any way it wants.
/// Returns `None` if signed extrinsic could not be created (either because signing failed
/// or because of any other runtime-specific reason).
fn create_transaction<C: AppCrypto<Self::Public, Self::Signature>>(
call: Self::OverarchingCall,
public: Self::Public,
account: Self::AccountId,
nonce: Self::Index,
) -> Option<(Self::OverarchingCall, <Self::Extrinsic as ExtrinsicT>::SignaturePayload)>;
}
/// A message signer.
pub trait SignMessage<T: SigningTypes> {
/// A signature data.
///
/// May contain account used for signing and the `Signature` itself.
type SignatureData;
/// Sign a message.
///
/// Implementation of this method should return
/// a result containing the signature.
fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
/// Construct and sign given payload.
///
/// This method expects `f` to return a `SignedPayload`
/// object which is then used for signing.
fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>;
}
/// Submit a signed transaction to the transaction pool.
pub trait SendSignedTransaction<
T: SigningTypes + CreateSignedTransaction<LocalCall>,
C: AppCrypto<T::Public, T::Signature>,
LocalCall,
>
{
/// A submission result.
///
/// This should contain an indication of success and the account that was used for signing.
type Result;
/// Submit a signed transaction to the local pool.
///
/// Given `f` closure will be called for every requested account and expects a `Call` object
/// to be returned.
/// The call is then wrapped into a transaction (see `#CreateSignedTransaction`), signed and
/// submitted to the pool.
fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
/// Wraps the call into transaction, signs using given account and submits to the pool.
fn send_single_signed_transaction(
&self,
account: &Account<T>,
call: LocalCall,
) -> Option<Result<(), ()>> {
let mut account_data = crate::Account::<T>::get(&account.id);
log::debug!(
target: "runtime::offchain",
"Creating signed transaction from account: {:?} (nonce: {:?})",
account.id,
account_data.nonce,
);
let (call, signature) = T::create_transaction::<C>(
call.into(),
account.public.clone(),
account.id.clone(),
account_data.nonce,
)?;
let res = SubmitTransaction::<T, LocalCall>::submit_transaction(call, Some(signature));
if res.is_ok() {
// increment the nonce. This is fine, since the code should always
// be running in off-chain context, so we NEVER persists data.
account_data.nonce += One::one();
crate::Account::<T>::insert(&account.id, account_data);
}
Some(res)
}
}
/// Submit an unsigned transaction onchain with a signed payload
pub trait SendUnsignedTransaction<T: SigningTypes + SendTransactionTypes<LocalCall>, LocalCall> {
/// A submission result.
///
/// Should contain the submission result and the account(s) that signed the payload.
type Result;
/// Send an unsigned transaction with a signed payload.
///
/// This method takes `f` and `f2` where:
/// - `f` is called for every account and is expected to return a `SignedPayload` object.
/// - `f2` is then called with the `SignedPayload` returned by `f` and the signature and is
/// expected to return a `Call` object to be embedded into transaction.
fn send_unsigned_transaction<TPayload, F>(
&self,
f: F,
f2: impl Fn(TPayload, T::Signature) -> LocalCall,
) -> Self::Result
where
F: Fn(&Account<T>) -> TPayload,
TPayload: SignedPayload<T>;
/// Submits an unsigned call to the transaction pool.
fn submit_unsigned_transaction(&self, call: LocalCall) -> Option<Result<(), ()>> {
Some(SubmitTransaction::<T, LocalCall>::submit_unsigned_transaction(call.into()))
}
}
/// Utility trait to be implemented on payloads that can be signed.
pub trait SignedPayload<T: SigningTypes>: Encode {
/// Return a public key that is expected to have a matching key in the keystore,
/// which should be used to sign the payload.
fn public(&self) -> T::Public;
/// Sign the payload using the implementor's provided public key.
///
/// Returns `Some(signature)` if public key is supported.
fn sign<C: AppCrypto<T::Public, T::Signature>>(&self) -> Option<T::Signature> {
self.using_encoded(|payload| C::sign(payload, self.public()))
}
/// Verify signature against payload.
///
/// Returns a bool indicating whether the signature is valid or not.
fn verify<C: AppCrypto<T::Public, T::Signature>>(&self, signature: T::Signature) -> bool {
self.using_encoded(|payload| C::verify(payload, self.public(), signature))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{Call, Test as TestRuntime, CALL};
use codec::Decode;
use sp_core::offchain::{testing, TransactionPoolExt};
use sp_runtime::testing::{TestSignature, TestXt, UintAuthorityId};
impl SigningTypes for TestRuntime {
type Public = UintAuthorityId;
type Signature = TestSignature;
}
type Extrinsic = TestXt<Call, ()>;
impl SendTransactionTypes<Call> for TestRuntime {
type Extrinsic = Extrinsic;
type OverarchingCall = Call;
}
#[derive(codec::Encode, codec::Decode)]
struct SimplePayload {
pub public: UintAuthorityId,
pub data: Vec<u8>,
}
impl SignedPayload<TestRuntime> for SimplePayload {
fn public(&self) -> UintAuthorityId {
self.public.clone()
}
}
struct DummyAppCrypto;
// Bind together the `SigningTypes` with app-crypto and the wrapper types.
// here the implementation is pretty dummy, because we use the same type for
// both application-specific crypto and the runtime crypto, but in real-life
// runtimes it's going to use different types everywhere.
impl AppCrypto<UintAuthorityId, TestSignature> for DummyAppCrypto {
type RuntimeAppPublic = UintAuthorityId;
type GenericPublic = UintAuthorityId;
type GenericSignature = TestSignature;
}
fn assert_account(next: Option<(Account<TestRuntime>, Result<(), ()>)>, index: usize, id: u64) {
assert_eq!(next, Some((Account { index, id, public: id.into() }, Ok(()))));
}
#[test]
fn should_send_unsigned_with_signed_payload_with_all_accounts() {
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(TransactionPoolExt::new(pool));
// given
UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
t.execute_with(|| {
// when
let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
.send_unsigned_transaction(
|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
|_payload, _signature| CALL.clone(),
);
// then
let mut res = result.into_iter();
assert_account(res.next(), 0, 0xf0);
assert_account(res.next(), 1, 0xf1);
assert_account(res.next(), 2, 0xf2);
assert_eq!(res.next(), None);
// check the transaction pool content:
let tx1 = pool_state.write().transactions.pop().unwrap();
let _tx2 = pool_state.write().transactions.pop().unwrap();
let _tx3 = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
assert_eq!(tx1.signature, None);
});
}
#[test]
fn should_send_unsigned_with_signed_payload_with_any_account() {
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(TransactionPoolExt::new(pool));
// given
UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
t.execute_with(|| {
// when
let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
.send_unsigned_transaction(
|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
|_payload, _signature| CALL.clone(),
);
// then
let mut res = result.into_iter();
assert_account(res.next(), 0, 0xf0);
assert_eq!(res.next(), None);
// check the transaction pool content:
let tx1 = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
assert_eq!(tx1.signature, None);
});
}
#[test]
fn should_send_unsigned_with_signed_payload_with_all_account_and_filter() {
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(TransactionPoolExt::new(pool));
// given
UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
t.execute_with(|| {
// when
let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
.with_filter(vec![0xf2.into(), 0xf1.into()])
.send_unsigned_transaction(
|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
|_payload, _signature| CALL.clone(),
);
// then
let mut res = result.into_iter();
assert_account(res.next(), 0, 0xf2);
assert_account(res.next(), 1, 0xf1);
assert_eq!(res.next(), None);
// check the transaction pool content:
let tx1 = pool_state.write().transactions.pop().unwrap();
let _tx2 = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
assert_eq!(tx1.signature, None);
});
}
#[test]
fn should_send_unsigned_with_signed_payload_with_any_account_and_filter() {
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(TransactionPoolExt::new(pool));
// given
UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
t.execute_with(|| {
// when
let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
.with_filter(vec![0xf2.into(), 0xf1.into()])
.send_unsigned_transaction(
|account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
|_payload, _signature| CALL.clone(),
);
// then
let mut res = result.into_iter();
assert_account(res.next(), 0, 0xf2);
assert_eq!(res.next(), None);
// check the transaction pool content:
let tx1 = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
assert_eq!(tx1.signature, None);
});
}
}