Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bc28c60
Add documentation to signed transactions and actually make them work.
tomusdrw Nov 25, 2019
a645b90
Fix naming and bounds.
tomusdrw Nov 25, 2019
f8c9540
Forgotten import.
tomusdrw Nov 25, 2019
f85b890
Merge branch 'master' into td-signed-transactions
tomusdrw Dec 2, 2019
3f38218
Merge branch 'master' into td-signed-transactions
tomusdrw Dec 3, 2019
d715f64
Remove warning.
tomusdrw Dec 3, 2019
0c4c037
Make accounts optional, fix logic.
tomusdrw Dec 3, 2019
dc8d71c
Split the method to avoid confusing type error message.
tomusdrw Dec 3, 2019
173cf6c
Move executor tests to integration.
tomusdrw Nov 28, 2019
90a00fd
Add submit transactions tests.
tomusdrw Dec 3, 2019
901dbdb
Merge branch 'master' into td-signed-transactions
tomusdrw Dec 10, 2019
d103fa1
Make `submit_transaction` tests compile
HCastano Dec 19, 2019
99c54b7
Remove a file that was accidently committed
HCastano Dec 19, 2019
dbc81dd
Merge branch 'master' into td-signed-transactions
tomusdrw Dec 20, 2019
9eb7a2e
Add can_sign helper function.
tomusdrw Dec 20, 2019
0ccb951
Fix compilation.
tomusdrw Dec 20, 2019
73a936c
Add a key to keystore.
tomusdrw Dec 20, 2019
d776d50
Fix the tests.
tomusdrw Dec 20, 2019
0ac61d7
Remove env_logger.
tomusdrw Dec 20, 2019
8755539
Fix sending multiple transactions.
tomusdrw Dec 20, 2019
fc6b5fe
Remove commented code.
tomusdrw Dec 20, 2019
af1782e
Bring back criterion.
tomusdrw Dec 20, 2019
58673bf
Merge branch 'master' into td-signed-transactions
tomusdrw Dec 23, 2019
bacf48c
Remove stray debug log.
tomusdrw Dec 23, 2019
b3c8726
Merge branch 'master' into td-signed-transactions
gavofyork Jan 3, 2020
da816e2
Apply suggestions from code review
tomusdrw Jan 6, 2020
a2c6d09
Make sure to initialize block correctly.
tomusdrw Jan 8, 2020
af5724c
Initialize block for offchain workers.
tomusdrw Jan 8, 2020
3eda535
Add test for transaction validity.
tomusdrw Jan 8, 2020
765f96f
Merge branch 'master' into td-signed-transactions
tomusdrw Jan 8, 2020
6bafaff
Fix tests.
tomusdrw Jan 8, 2020
021f586
Review suggestions.
tomusdrw Jan 8, 2020
3ac00ff
Remove redundant comment.
tomusdrw Jan 8, 2020
7f38d4c
Make sure to use correct block number of authoring.
tomusdrw Jan 8, 2020
afb99f9
Change the runtime API.
tomusdrw Jan 9, 2020
a945a8c
Support both versions.
tomusdrw Jan 9, 2020
4c66a45
Bump spec version, fix RPC test.
tomusdrw Jan 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add documentation to signed transactions and actually make them work.
  • Loading branch information
tomusdrw committed Nov 25, 2019
commit bc28c60593c30c4f53a76df92f0c4546a29bc901
223 changes: 183 additions & 40 deletions frame/system/src/offchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,52 @@
//! Module helpers for offchain calls.

use codec::Encode;
use sr_primitives::app_crypto::{self, RuntimeAppPublic};
use rstd::convert::TryInto;
use rstd::prelude::Vec;
use sr_primitives::app_crypto::{self, RuntimeAppPublic, AppPublic, AppSignature};
use sr_primitives::traits::{Extrinsic as ExtrinsicT, IdentifyAccount};
use support::debug;

/// Creates runtime-specific signed transaction.
///
/// This trait should be implemented by your `Runtime` to be able
/// to submit `SignedTransaction`s` to the pool from offchain code.
pub trait CreateTransaction<T: crate::Trait, Extrinsic: ExtrinsicT> {
/// A `Public` key representing a particular `AccountId`.
type Public: Clone + IdentifyAccount<AccountId=T::AccountId>;
/// A `Signature` generated by the `Signer`.
type Signature;

/// 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<F: Signer<Self::Public, Self::Signature>>(
call: Extrinsic::Call,
public: Self::Public,
account: T::AccountId,
nonce: T::Index,
) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>;
}

/// A trait responsible for signing a payload using given account.
///
/// This trait is usually going to represent a local public key
/// that has ability to sign arbitrary `Payloads`.
///
/// NOTE: Most likely you don't need to implement this trait manually.
/// It has a blanket implementation for all `RuntimeAppPublic` types,
/// so it's enough to pass an application-specific crypto type.
///
/// To easily create `SignedTransaction`s have a look at the
/// `TransactionSubmitter` type.
pub trait Signer<Public, Signature> {
/// Sign any encodable payload with given account and produce a signature.
///
/// Returns `Some` if signing succeeded and `None` in case the `account` couldn't be used.
/// Returns `Some` if signing succeeded and `None` in case the `account` couldn't
/// be used (for instance we couldn't convert it to required application specific crypto).
fn sign<Payload: Encode>(public: Public, payload: &Payload) -> Option<Signature>;
}

Expand All @@ -33,58 +71,42 @@ pub trait Signer<Public, Signature> {
/// This implementation additionaly supports conversion to/from multi-signature/multi-signer
/// wrappers.
/// If the wrapped crypto doesn't match `AppPublic`s crypto `None` is returned.
impl<Public, Signature, AppPublic> Signer<Public, Signature> for AppPublic where
AppPublic: RuntimeAppPublic
+ app_crypto::AppPublic
+ From<<AppPublic as app_crypto::AppPublic>::Generic>,
<AppPublic as RuntimeAppPublic>::Signature: app_crypto::AppSignature,
impl<Public, Signature, TAnyAppPublic> Signer<Public, Signature> for TAnyAppPublic where
TAnyAppPublic: RuntimeAppPublic
+ AppPublic
+ From<<TAnyAppPublic as AppPublic>::Generic>,
<TAnyAppPublic as RuntimeAppPublic>::Signature: AppSignature,
Signature: From<
<<AppPublic as RuntimeAppPublic>::Signature as app_crypto::AppSignature>::Generic
<<TAnyAppPublic as RuntimeAppPublic>::Signature as AppSignature>::Generic
>,
Public: rstd::convert::TryInto<<AppPublic as app_crypto::AppPublic>::Generic>
Public: TryInto<<TAnyAppPublic as AppPublic>::Generic>
{
fn sign<Payload: Encode>(public: Public, raw_payload: &Payload) -> Option<Signature> {
raw_payload.using_encoded(|payload| {
let public = public.try_into().ok()?;
AppPublic::from(public).sign(&payload)
TAnyAppPublic::from(public).sign(&payload)
.map(
<<AppPublic as RuntimeAppPublic>::Signature as app_crypto::AppSignature>
<<TAnyAppPublic as RuntimeAppPublic>::Signature as AppSignature>
::Generic::from
)
.map(Signature::from)
})
}
}

/// Creates runtime-specific signed transaction.
pub trait CreateTransaction<T: crate::Trait, Extrinsic: ExtrinsicT> {
/// A `Public` key representing a particular `AccountId`.
type Public: IdentifyAccount<AccountId=T::AccountId> + Clone;
/// A `Signature` generated by the `Signer`.
type Signature;

/// 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<F: Signer<Self::Public, Self::Signature>>(
call: Extrinsic::Call,
public: Self::Public,
account: T::AccountId,
nonce: T::Index,
) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>;
}

type PublicOf<T, Call, X> = <
<X as SubmitSignedTransaction<T, Call>>::CreateTransaction as CreateTransaction<
T,
<X as SubmitSignedTransaction<T, Call>>::Extrinsic,
>
/// Retrieves a public key type for given `SubmitSignedTransaction`.
pub type PublicOf<T, Call, X> =
<
<X as SubmitSignedTransaction<T, Call>>::CreateTransaction
as
CreateTransaction<T, <X as SubmitSignedTransaction<T, Call>>::Extrinsic>
>::Public;

/// A trait to sign and submit transactions in offchain calls.
///
/// NOTE: Most likely you should not implement this trait yourself.
/// There is an implementation for `TransactionSubmitter` type, which
/// you should use.
pub trait SubmitSignedTransaction<T: crate::Trait, Call> {
/// Unchecked extrinsic type.
type Extrinsic: ExtrinsicT<Call=Call> + codec::Encode;
Expand All @@ -107,6 +129,12 @@ pub trait SubmitSignedTransaction<T: crate::Trait, Call> {
let call = call.into();
let id = public.clone().into_account();
let expected = <crate::Module<T>>::account_nonce(&id);
debug::native::debug!(
target: "offchain",
"Creating signed transaction from account: {:?} (nonce: {:?})",
id,
expected,
);
let (call, signature_data) = Self::CreateTransaction
::create_transaction::<Self::Signer>(call, public, id, expected)
.ok_or(())?;
Expand All @@ -116,6 +144,10 @@ pub trait SubmitSignedTransaction<T: crate::Trait, Call> {
}

/// A trait to submit unsigned transactions in offchain calls.
///
/// NOTE: Most likely you should not implement this trait yourself.
/// There is an implementation for `TransactionSubmitter` type, which
/// you should use.
pub trait SubmitUnsignedTransaction<T: crate::Trait, Call> {
/// Unchecked extrinsic type.
type Extrinsic: ExtrinsicT<Call=Call> + codec::Encode;
Expand All @@ -130,9 +162,70 @@ pub trait SubmitUnsignedTransaction<T: crate::Trait, Call> {
}
}

/// A utility trait to easily create signed transactions
/// from accounts in node's local keystore.
///
/// NOTE: Most likely you should not implement this trait yourself.
/// There is an implementation for `TransactionSubmitter` type, which
/// you should use.
pub trait SigningAccountFinder<T: crate::Trait, Call> {
/// A `SubmitSignedTransaction` implementation.
type SubmitTransaction: SubmitSignedTransaction<T, Call>;

/// Find local keys that match given list of accounts.
///
/// Technically it finds an intersection between given list of `AccountId`s
/// and accounts that are represented by public keys in local keystore.
///
/// Returns both public keys and `AccountId`s of accounts that are available.
/// Such accounts can later be used to sign a payload or send signed transactions.
fn find_local_keys(accounts: impl IntoIterator<Item = T::AccountId>) -> Vec<(
T::AccountId,
PublicOf<T, Call, Self::SubmitTransaction>,
)>;

/// Create and submit signed transactions from supported accounts.
///
/// This method should intersect given list of accounts with the ones
/// supported locally and submit signed transaction containing given `Call`
/// with every of them.
///
/// Returns a vector of results and account ids that were supported.
fn submit_signed(
accounts: impl IntoIterator<Item= T::AccountId>,
call: impl Into<Call> + Clone,
) -> Vec<(T::AccountId, Result<(), ()>)> {
let keys = Self::find_local_keys(accounts);

keys.into_iter().map(|(account, pub_key)| {
let call = call.clone().into();
(
account,
Self::SubmitTransaction::sign_and_submit(call, pub_key)
)
}).collect()
}
}


/// A default type used to submit transactions to the pool.
pub struct TransactionSubmitter<S, C, E> {
_signer: rstd::marker::PhantomData<(S, C, E)>,
///
/// This is passed into each runtime as an opaque associated type that can have either of:
/// - [`SubmitSignedTransaction`]
/// - [`SubmitUnsignedTransaction`]
/// - [`SigningAccountFinder`]
/// and used accordingly.
///
/// This struct should be constructed by providing the following generic parameters:
/// * `Signer` - Usually the application specific key type (see `app_crypto`).
/// * `CreateTransaction` - A type that is able to produce signed transactions,
/// usually it's going to be the entire `Runtime` object.
/// * `Extrinsic` - A runtime-specific type for in-block extrinsics.
///
/// If you only need the ability to submit unsigned transactions,
/// you may substitute both `Signer` and `CreateTransaction` with any type.
pub struct TransactionSubmitter<Signer, CreateTransaction, Extrinsic> {
_signer: rstd::marker::PhantomData<(Signer, CreateTransaction, Extrinsic)>,
}

impl<S, C, E> Default for TransactionSubmitter<S, C, E> {
Expand All @@ -155,10 +248,60 @@ impl<T, E, S, C, Call> SubmitSignedTransaction<T, Call> for TransactionSubmitter
type Signer = S;
}

/// A blanket impl to use the same submitter for usigned transactions as well.
/// A blanket implementation to use the same submitter for unsigned transactions as well.
impl<T, E, S, C, Call> SubmitUnsignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where
T: crate::Trait,
E: ExtrinsicT<Call=Call> + codec::Encode,
{
type Extrinsic = E;
}

/// A blanket implementation to support local keystore of application-crypto types.
impl<T, C, E, S, Call> SigningAccountFinder<T, Call> for TransactionSubmitter<S, C, E> where
T: crate::Trait,
C: CreateTransaction<T, E>,
E: ExtrinsicT<Call=Call> + codec::Encode,
S: Signer<<C as CreateTransaction<T, E>>::Public, <C as CreateTransaction<T, E>>::Signature>,
S: RuntimeAppPublic
+ AppPublic
// TODO remove AppPublic?
+ Into<<S as AppPublic>::Generic>
+ From<<S as AppPublic>::Generic>,
S::Generic: Into<PublicOf<T, Call, Self>>,
PublicOf<T, Call, Self>: TryInto<<S as AppPublic>::Generic>,
<S as RuntimeAppPublic>::Signature: AppSignature,
// <C as CreateTransaction<T, E>>::Signature: From<
// <<S as RuntimeAppPublic>::Signature as app_crypto::AppSignature>::Generic
// >,
<<TransactionSubmitter<S, C, E> as SubmitSignedTransaction<T, Call>>::CreateTransaction as CreateTransaction<T, <TransactionSubmitter<S, C, E> as SubmitSignedTransaction<T, Call>>::Extrinsic>>::Signature: From<<<S as RuntimeAppPublic>::Signature as app_crypto::AppSignature>::Generic>,
Self: SubmitSignedTransaction<T, Call, Signer = S>,
{
type SubmitTransaction = Self;

fn find_local_keys(accounts: impl IntoIterator<Item = T::AccountId>) -> Vec<(
T::AccountId,
PublicOf<T, Call, Self::SubmitTransaction>,
)> {
// Convert app-specific keys into generic ones.
let local_keys = S::all().into_iter().map(|app_key| {
app_key.into()
}).collect::<Vec<<S as app_crypto::AppPublic>::Generic>>();

// lookup accountids for the pub keys.
let mut local_accounts = local_keys.clone().into_iter().map(|pub_key| {
pub_key.into().into_account()
}).collect::<Vec<_>>();
// sort to allow bin-search.
local_accounts.sort();

// get all the matching accounts
accounts.into_iter().filter_map(|acc| {
let idx = local_accounts.binary_search(&acc).ok()?;
Some((
local_accounts.get(idx)?.clone(),
local_keys.get(idx)?.clone().into(),
))
}).collect()
}
}