diff --git a/core/sr-primitives/src/generic/mod.rs b/core/sr-primitives/src/generic/mod.rs index 1511753d2c524..0138a15aee21a 100644 --- a/core/sr-primitives/src/generic/mod.rs +++ b/core/sr-primitives/src/generic/mod.rs @@ -27,7 +27,7 @@ mod digest; #[cfg(test)] mod tests; -pub use self::unchecked_extrinsic::UncheckedExtrinsic; +pub use self::unchecked_extrinsic::{UncheckedExtrinsic, SignedPayload}; pub use self::era::{Era, Phase}; pub use self::checked_extrinsic::CheckedExtrinsic; pub use self::header::Header; diff --git a/core/sr-primitives/src/generic/unchecked_extrinsic.rs b/core/sr-primitives/src/generic/unchecked_extrinsic.rs index cb9330cfaaff0..aebf80edc63ec 100644 --- a/core/sr-primitives/src/generic/unchecked_extrinsic.rs +++ b/core/sr-primitives/src/generic/unchecked_extrinsic.rs @@ -72,12 +72,22 @@ impl Extrinsic { type Call = Call; + type SignaturePayload = ( + Address, + Signature, + Extra, + ); + fn is_signed(&self) -> Option { Some(self.signature.is_some()) } - fn new_unsigned(function: Call) -> Option { - Some(UncheckedExtrinsic::new_unsigned(function)) + fn new(function: Call, signed_data: Option) -> Option { + Some(if let Some((address, signature, extra)) = signed_data { + UncheckedExtrinsic::new_signed(function, address, signature, extra) + } else { + UncheckedExtrinsic::new_unsigned(function) + }) } } @@ -98,21 +108,18 @@ where fn check(self, lookup: &Lookup) -> Result { Ok(match self.signature { Some((signed, signature, extra)) => { - let additional_signed = extra.additional_signed()?; - let raw_payload = (self.function, extra, additional_signed); let signed = lookup.lookup(signed)?; + let raw_payload = SignedPayload::new(self.function, extra)?; if !raw_payload.using_encoded(|payload| { - if payload.len() > 256 { - signature.verify(&blake2_256(payload)[..], &signed) - } else { - signature.verify(payload, &signed) - } + signature.verify(payload, &signed) }) { return Err(crate::BAD_SIGNATURE) } + + let (function, extra, _) = raw_payload.deconstruct(); CheckedExtrinsic { - signed: Some((signed, raw_payload.1)), - function: raw_payload.0, + signed: Some((signed, extra)), + function, } } None => CheckedExtrinsic { @@ -123,6 +130,59 @@ where } } +/// A payload that has been signed for an unchecked extrinsics. +/// +/// Note that the payload that we sign to produce unchecked extrinsic signature +/// is going to be different than the `SignaturePayload` - so the thing the extrinsic +/// actually contains. +pub struct SignedPayload(( + Call, + Extra, + Extra::AdditionalSigned, +)); + +impl SignedPayload where + Call: Encode, + Extra: SignedExtension, +{ + /// Create new `SignedPayload`. + /// + /// This function may fail if `additional_signed` of `Extra` is not available. + pub fn new(call: Call, extra: Extra) -> Result { + let additional_signed = extra.additional_signed()?; + let raw_payload = (call, extra, additional_signed); + Ok(Self(raw_payload)) + } + + /// Create new `SignedPayload` from raw components. + pub fn from_raw(call: Call, extra: Extra, additional_signed: Extra::AdditionalSigned) -> Self { + Self((call, extra, additional_signed)) + } + + /// Deconstruct the payload into it's components. + pub fn deconstruct(self) -> (Call, Extra, Extra::AdditionalSigned) { + self.0 + } +} + +impl Encode for SignedPayload where + Call: Encode, + Extra: SignedExtension, +{ + /// Get an encoded version of this payload. + /// + /// Payloads longer than 256 bytes are going to be `blake2_256`-hashed. + fn using_encoded R>(&self, f: F) -> R { + self.0.using_encoded(|payload| { + if payload.len() > 256 { + f(&blake2_256(payload)[..]) + } else { + f(payload) + } + }) + } +} + impl Decode for UncheckedExtrinsic where diff --git a/core/sr-primitives/src/lib.rs b/core/sr-primitives/src/lib.rs index 41dac41f79b35..454f1cdfb8106 100644 --- a/core/sr-primitives/src/lib.rs +++ b/core/sr-primitives/src/lib.rs @@ -847,12 +847,7 @@ impl<'a> ::serde::Deserialize<'a> for OpaqueExtrinsic { impl traits::Extrinsic for OpaqueExtrinsic { type Call = (); - - fn is_signed(&self) -> Option { - None - } - - fn new_unsigned(_call: Self::Call) -> Option { None } + type SignaturePayload = (); } #[cfg(test)] diff --git a/core/sr-primitives/src/testing.rs b/core/sr-primitives/src/testing.rs index c91c8366c1123..ed5ac7fa45675 100644 --- a/core/sr-primitives/src/testing.rs +++ b/core/sr-primitives/src/testing.rs @@ -187,6 +187,7 @@ pub struct ExtrinsicWrapper(Xt); impl traits::Extrinsic for ExtrinsicWrapper { type Call = (); + type SignaturePayload = (); fn is_signed(&self) -> Option { None @@ -274,13 +275,14 @@ impl Checkable for TestXt traits::Extrinsic for TestXt { type Call = Call; + type SignaturePayload = (u64, Extra); fn is_signed(&self) -> Option { Some(self.0.is_some()) } - fn new_unsigned(_c: Call) -> Option { - None + fn new(c: Call, sig: Option) -> Option { + Some(TestXt(sig, c)) } } diff --git a/core/sr-primitives/src/traits.rs b/core/sr-primitives/src/traits.rs index 3dc1649e495d4..fe3d67268f117 100644 --- a/core/sr-primitives/src/traits.rs +++ b/core/sr-primitives/src/traits.rs @@ -743,13 +743,24 @@ pub trait Extrinsic: Sized { /// The function call. type Call; + /// The payload we carry for signed extrinsics. + /// + /// Usually it will contain a `Signature` and + /// may include some additional data that are specific to signed + /// extrinsics. + type SignaturePayload; + /// Is this `Extrinsic` signed? /// If no information are available about signed/unsigned, `None` should be returned. fn is_signed(&self) -> Option { None } - /// New instance of an unsigned extrinsic aka "inherent". `None` if this is an opaque - /// extrinsic type. - fn new_unsigned(_call: Self::Call) -> Option { None } + /// Create new instance of the extrinsic. + /// + /// Extrinsics can be split into: + /// 1. Inherents (no signature; created by validators during block production) + /// 2. Unsigned Transactions (no signature; represent "system calls" or other special kinds of calls) + /// 3. Signed Transactions (with signature; a regular transactions with known origin) + fn new(_call: Self::Call, _signed_data: Option) -> Option { None } } /// Extract the hashing type for a block. diff --git a/core/test-runtime/src/lib.rs b/core/test-runtime/src/lib.rs index 55b6ad9b5fe14..f023fd89e137d 100644 --- a/core/test-runtime/src/lib.rs +++ b/core/test-runtime/src/lib.rs @@ -141,6 +141,7 @@ impl BlindCheckable for Extrinsic { impl ExtrinsicT for Extrinsic { type Call = Extrinsic; + type SignaturePayload = (); fn is_signed(&self) -> Option { if let Extrinsic::IncludeData(_) = *self { @@ -150,7 +151,7 @@ impl ExtrinsicT for Extrinsic { } } - fn new_unsigned(call: Self::Call) -> Option { + fn new(call: Self::Call, _signature_payload: Option) -> Option { Some(call) } } diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 01cad95c4678f..68b8d63fe0b18 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -289,7 +289,7 @@ mod tests { crypto::Pair as CryptoPair, blake2_256, sr25519::Public as AddressPublic, H256, }; - use sr_primitives::{generic::{BlockId, Era, Digest}, traits::Block, OpaqueExtrinsic}; + use sr_primitives::{generic::{BlockId, Era, Digest, SignedPayload}, traits::Block, OpaqueExtrinsic}; use timestamp; use finality_tracker; use keyring::AccountKeyring; @@ -462,15 +462,17 @@ mod tests { let check_weight = system::CheckWeight::new(); let take_fees = balances::TakeFees::from(0); let extra = (check_version, check_genesis, check_era, check_nonce, check_weight, take_fees); - - let raw_payload = (function, extra.clone(), version, genesis_hash, genesis_hash); - let signature = raw_payload.using_encoded(|payload| if payload.len() > 256 { - signer.sign(&blake2_256(payload)[..]) - } else { + let raw_payload = SignedPayload::from_raw( + function, + extra, + (version, genesis_hash, genesis_hash, (), (), ()) + ); + let signature = raw_payload.using_encoded(|payload| { signer.sign(payload) }); + let (function, extra, _) = raw_payload.deconstruct(); let xt = UncheckedExtrinsic::new_signed( - raw_payload.0, + function, from.into(), signature.into(), extra, diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 6fec9abe7afdd..5dc8d02eccde7 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -39,7 +39,7 @@ use sr_primitives::{ApplyResult, impl_opaque_keys, generic, create_runtime_str, use sr_primitives::transaction_validity::TransactionValidity; use sr_primitives::weights::Weight; use sr_primitives::traits::{ - BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup, + self, BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup, SaturatedConversion, }; use version::RuntimeVersion; use elections::VoteIndex; @@ -48,6 +48,7 @@ use version::NativeVersion; use primitives::OpaqueMetadata; use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; use im_online::sr25519::{AuthorityId as ImOnlineId}; +use system::offchain::TransactionSubmitter; #[cfg(any(feature = "std", test))] pub use sr_primitives::BuildStorage; @@ -80,7 +81,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. spec_version: 154, - impl_version: 157, + impl_version: 158, apis: RUNTIME_API_VERSIONS, }; @@ -392,11 +393,13 @@ impl sudo::Trait for Runtime { type Proposal = Call; } +type SubmitTransaction = TransactionSubmitter; + impl im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Call = Call; type Event = Event; - type UncheckedExtrinsic = UncheckedExtrinsic; + type SubmitTransaction = SubmitTransaction; type ReportUnresponsiveness = Offences; type CurrentElectedSet = staking::CurrentElectedStashAccounts; } @@ -424,6 +427,33 @@ impl finality_tracker::Trait for Runtime { type ReportLatency = ReportLatency; } +impl system::offchain::CreateTransaction for Runtime { + type Signature = Signature; + + fn create_transaction>( + call: Call, + account: AccountId, + index: Index, + ) -> Option<(Call, ::SignaturePayload)> { + let period = 1 << 8; + let current_block = System::block_number().saturated_into::(); + let tip = 0; + let extra: SignedExtra = ( + system::CheckVersion::::new(), + system::CheckGenesis::::new(), + system::CheckEra::::from(generic::Era::mortal(period, current_block)), + system::CheckNonce::::from(index), + system::CheckWeight::::new(), + balances::TakeFees::::from(tip), + ); + let raw_payload = SignedPayload::new(call, extra).ok()?; + let signature = F::sign(account.clone(), &raw_payload)?; + let address = Indices::unlookup(account); + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (address, signature, extra))) + } +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -475,6 +505,8 @@ pub type SignedExtra = ( ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. @@ -609,3 +641,28 @@ impl_runtime_apis! { } } } + +#[cfg(test)] +mod tests { + use super::*; + use sr_primitives::app_crypto::RuntimeAppPublic; + use system::offchain::SubmitSignedTransaction; + + fn is_submit_signed_transaction(_arg: T) where + T: SubmitSignedTransaction< + Runtime, + Call, + Extrinsic=UncheckedExtrinsic, + CreateTransaction=Runtime, + Signer=Signer, + >, + Signer: RuntimeAppPublic + From, + Signer::Signature: Into, + {} + + #[test] + fn validate_bounds() { + let x = SubmitTransaction::default(); + is_submit_signed_transaction(x); + } +} diff --git a/srml/authority-discovery/src/lib.rs b/srml/authority-discovery/src/lib.rs index ffcb6672c8662..74b1c6172f869 100644 --- a/srml/authority-discovery/src/lib.rs +++ b/srml/authority-discovery/src/lib.rs @@ -194,7 +194,11 @@ mod tests { type AuthorityId = AuthorityId; type Call = im_online::Call; type Event = (); - type UncheckedExtrinsic = UncheckedExtrinsic<(), im_online::Call, (), ()>; + type SubmitTransaction = system::offchain::TransactionSubmitter< + (), + im_online::Call, + UncheckedExtrinsic<(), im_online::Call, (), ()>, + >; type ReportUnresponsiveness = (); type CurrentElectedSet = DummyCurrentElectedSet; } diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index e37ce5c4de1c6..289dfbf93f2d7 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1187,7 +1187,6 @@ pub struct TakeFees, I: Instance = DefaultInstance>(#[codec(compact) impl, I: Instance> TakeFees { /// utility constructor. Used only in client/factory code. - #[cfg(feature = "std")] pub fn from(fee: T::Balance) -> Self { Self(fee) } diff --git a/srml/im-online/src/lib.rs b/srml/im-online/src/lib.rs index fe604ca788fa4..c8494ef633ac8 100644 --- a/srml/im-online/src/lib.rs +++ b/srml/im-online/src/lib.rs @@ -75,7 +75,7 @@ use session::historical::IdentificationTuple; use sr_io::Printable; use sr_primitives::{ Perbill, ApplyError, - traits::{Convert, Extrinsic as ExtrinsicT, Member}, + traits::{Convert, Member}, transaction_validity::{TransactionValidity, TransactionLongevity, ValidTransaction}, }; use sr_staking_primitives::{ @@ -87,11 +87,18 @@ use srml_support::{ Parameter, StorageValue, StorageDoubleMap, }; use system::ensure_none; +use system::offchain::SubmitUnsignedTransaction; pub mod sr25519 { mod app_sr25519 { use app_crypto::{app_crypto, key_types::IM_ONLINE, sr25519}; app_crypto!(sr25519, IM_ONLINE); + + impl From for sr_primitives::AnySignature { + fn from(sig: Signature) -> Self { + sr25519::Signature::from(sig).into() + } + } } /// An i'm online keypair using sr25519 as its crypto. @@ -109,6 +116,12 @@ pub mod ed25519 { mod app_ed25519 { use app_crypto::{app_crypto, key_types::IM_ONLINE, ed25519}; app_crypto!(ed25519, IM_ONLINE); + + impl From for sr_primitives::AnySignature { + fn from(sig: Signature) -> Self { + ed25519::Signature::from(sig).into() + } + } } /// An i'm online keypair using ed25519 as its crypto. @@ -141,7 +154,6 @@ struct WorkerStatus { // Error which may occur while executing the off-chain code. enum OffchainErr { DecodeWorkerStatus, - ExtrinsicCreation, FailedSigning, NetworkState, SubmitTransaction, @@ -151,7 +163,6 @@ impl Printable for OffchainErr { fn print(self) { match self { OffchainErr::DecodeWorkerStatus => print("Offchain error: decoding WorkerStatus failed!"), - OffchainErr::ExtrinsicCreation => print("Offchain error: extrinsic creation failed!"), OffchainErr::FailedSigning => print("Offchain error: signing failed!"), OffchainErr::NetworkState => print("Offchain error: fetching network state failed!"), OffchainErr::SubmitTransaction => print("Offchain error: submitting transaction failed!"), @@ -183,9 +194,8 @@ pub trait Trait: system::Trait + session::historical::Trait { /// A dispatchable call type. type Call: From>; - /// A extrinsic right from the external world. This is unchecked and so - /// can contain a signature. - type UncheckedExtrinsic: ExtrinsicT::Call> + Encode + Decode; + /// A transaction submitter. + type SubmitTransaction: SubmitUnsignedTransaction::Call>; /// A type that gives us the ability to submit unresponsiveness offence reports. type ReportUnresponsiveness: @@ -340,9 +350,8 @@ impl Module { let signature = key.sign(&heartbeat_data.encode()).ok_or(OffchainErr::FailedSigning)?; let call = Call::heartbeat(heartbeat_data, signature); - let ex = T::UncheckedExtrinsic::new_unsigned(call.into()) - .ok_or(OffchainErr::ExtrinsicCreation)?; - sr_io::submit_transaction(&ex).map_err(|_| OffchainErr::SubmitTransaction)?; + T::SubmitTransaction::submit_unsigned(call) + .map_err(|_| OffchainErr::SubmitTransaction)?; // once finished we set the worker status without comparing // if the existing value changed in the meantime. this is diff --git a/srml/support/src/inherent.rs b/srml/support/src/inherent.rs index 1b6d8fbdd7ed9..935d3b4e74e4f 100644 --- a/srml/support/src/inherent.rs +++ b/srml/support/src/inherent.rs @@ -55,14 +55,16 @@ macro_rules! impl_outer_inherent { fn create_extrinsics(&self) -> $crate::inherent::Vec<<$block as $crate::inherent::BlockT>::Extrinsic> { use $crate::inherent::ProvideInherent; + use $crate::inherent::Extrinsic; let mut inherents = Vec::new(); $( if let Some(inherent) = $module::create_inherent(self) { - inherents.push($uncheckedextrinsic::new_unsigned( - Call::$call(inherent)) - ); + inherents.push($uncheckedextrinsic::new( + Call::$call(inherent), + None, + ).expect("Runtime UncheckedExtrinsic is not Opaque, so it has to return `Some`; qed")); } )* diff --git a/srml/system/src/lib.rs b/srml/system/src/lib.rs index 91959f43e4e86..133731c8bd6cf 100644 --- a/srml/system/src/lib.rs +++ b/srml/system/src/lib.rs @@ -121,6 +121,8 @@ use runtime_io::{twox_128, TestExternalities, Blake2Hasher}; #[cfg(any(feature = "std", test))] use primitives::ChangesTrieConfiguration; +pub mod offchain; + /// Handler for when a new account has been created. pub trait OnNewAccount { /// A new account `who` has been registered. @@ -443,7 +445,7 @@ decl_storage! { } } -pub struct EnsureRoot(::rstd::marker::PhantomData); +pub struct EnsureRoot(rstd::marker::PhantomData); impl< O: Into, O>> + From>, AccountId, @@ -457,7 +459,7 @@ impl< } } -pub struct EnsureSigned(::rstd::marker::PhantomData); +pub struct EnsureSigned(rstd::marker::PhantomData); impl< O: Into, O>> + From>, AccountId, @@ -471,7 +473,7 @@ impl< } } -pub struct EnsureSignedBy(::rstd::marker::PhantomData<(Who, AccountId)>); +pub struct EnsureSignedBy(rstd::marker::PhantomData<(Who, AccountId)>); impl< O: Into, O>> + From>, Who: Contains, @@ -486,7 +488,7 @@ impl< } } -pub struct EnsureNone(::rstd::marker::PhantomData); +pub struct EnsureNone(rstd::marker::PhantomData); impl< O: Into, O>> + From>, AccountId, @@ -500,7 +502,7 @@ impl< } } -pub struct EnsureNever(::rstd::marker::PhantomData); +pub struct EnsureNever(rstd::marker::PhantomData); impl EnsureOrigin for EnsureNever { type Success = T; fn try_origin(o: O) -> Result { @@ -894,8 +896,7 @@ impl CheckWeight { } } - /// Utility constructor for tests and client code. - #[cfg(feature = "std")] + /// Creates new `SignedExtension` to check weight of the extrinsic. pub fn new() -> Self { Self(PhantomData) } @@ -950,7 +951,6 @@ impl rstd::fmt::Debug for CheckWeight { #[derive(Encode, Decode, Clone, Eq, PartialEq)] pub struct CheckNonce(#[codec(compact)] T::Index); -#[cfg(feature = "std")] impl CheckNonce { /// utility constructor. Used only in client/factory code. pub fn from(nonce: T::Index) -> Self { @@ -1024,7 +1024,6 @@ impl SignedExtension for CheckNonce { #[derive(Encode, Decode, Clone, Eq, PartialEq)] pub struct CheckEra((Era, rstd::marker::PhantomData)); -#[cfg(feature = "std")] impl CheckEra { /// utility constructor. Used only in client/factory code. pub fn from(era: Era) -> Self { @@ -1079,10 +1078,10 @@ impl rstd::fmt::Debug for CheckGenesis { } } -#[cfg(feature = "std")] impl CheckGenesis { + /// Creates new `SignedExtension` to check genesis hash. pub fn new() -> Self { - Self(std::marker::PhantomData) + Self(rstd::marker::PhantomData) } } @@ -1108,10 +1107,10 @@ impl rstd::fmt::Debug for CheckVersion { } } -#[cfg(feature = "std")] impl CheckVersion { + /// Create new `SignedExtension` to check runtime version. pub fn new() -> Self { - Self(std::marker::PhantomData) + Self(rstd::marker::PhantomData) } } @@ -1126,10 +1125,10 @@ impl SignedExtension for CheckVersion { } } -pub struct ChainContext(::rstd::marker::PhantomData); +pub struct ChainContext(rstd::marker::PhantomData); impl Default for ChainContext { fn default() -> Self { - ChainContext(::rstd::marker::PhantomData) + ChainContext(rstd::marker::PhantomData) } } diff --git a/srml/system/src/offchain.rs b/srml/system/src/offchain.rs new file mode 100644 index 0000000000000..d67598f64fe02 --- /dev/null +++ b/srml/system/src/offchain.rs @@ -0,0 +1,135 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Module helpers for offchain calls. + +use codec::Encode; +use sr_primitives::app_crypto::RuntimeAppPublic; +use sr_primitives::traits::Extrinsic as ExtrinsicT; + +/// A trait responsible for signing a payload using given account. +pub trait Signer { + /// 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. + fn sign(account: Account, payload: &Payload) -> Option; +} + +impl Signer for AppPublic where + AppPublic: RuntimeAppPublic + From, + AppPublic::Signature: Into, +{ + fn sign(account: Account, raw_payload: &Payload) -> Option { + raw_payload.using_encoded(|payload| { + AppPublic::from(account).sign(&payload).map(Into::into) + }) + } +} + +/// Creates runtime-specific signed transaction. +pub trait CreateTransaction { + 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>( + call: Extrinsic::Call, + account: T::AccountId, + nonce: T::Index, + ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; +} + +/// A trait to sign and submit transactions in offchain calls. +pub trait SubmitSignedTransaction { + /// Unchecked extrinsic type. + type Extrinsic: ExtrinsicT + codec::Encode; + + /// A runtime-specific type to produce signed data for the extrinsic. + type CreateTransaction: CreateTransaction; + + /// A type used to sign transactions created using `CreateTransaction`. + type Signer: Signer< + T::AccountId, + >::Signature, + >; + + /// Sign given call and submit it to the transaction pool. + /// + /// Returns `Ok` if the transaction was submitted correctly + /// and `Err` if the key for given `id` was not found or the + /// transaction was rejected from the pool. + fn sign_and_submit(call: impl Into, id: T::AccountId) -> Result<(), ()> { + let call = call.into(); + let expected = >::account_nonce(&id); + let (call, signature_data) = Self::CreateTransaction + ::create_transaction::(call, id, expected) + .ok_or(())?; + let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?; + runtime_io::submit_transaction(&xt) + } +} + +/// A trait to submit unsigned transactions in offchain calls. +pub trait SubmitUnsignedTransaction { + /// Unchecked extrinsic type. + type Extrinsic: ExtrinsicT + codec::Encode; + + /// Submit given call to the transaction pool as unsigned transaction. + /// + /// Returns `Ok` if the transaction was submitted correctly + /// and `Err` if transaction was rejected from the pool. + fn submit_unsigned(call: impl Into) -> Result<(), ()> { + let xt = Self::Extrinsic::new(call.into(), None).ok_or(())?; + runtime_io::submit_transaction(&xt) + } +} + +/// A default type used to submit transactions to the pool. +pub struct TransactionSubmitter { + _signer: rstd::marker::PhantomData<(S, C, E)>, +} + +impl Default for TransactionSubmitter { + fn default() -> Self { + Self { + _signer: Default::default(), + } + } +} + +/// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. +impl SubmitSignedTransaction for TransactionSubmitter where + T: crate::Trait, + C: CreateTransaction, + S: Signer>::Signature>, + E: ExtrinsicT + codec::Encode, +{ + type Extrinsic = E; + type CreateTransaction = C; + type Signer = S; +} + +/// A blanket impl to use the same submitter for usigned transactions as well. +impl SubmitUnsignedTransaction for TransactionSubmitter where + T: crate::Trait, + E: ExtrinsicT + codec::Encode, +{ + type Extrinsic = E; +} diff --git a/subkey/src/main.rs b/subkey/src/main.rs index ba9d1a6cce059..e0ac2cb8b6b0f 100644 --- a/subkey/src/main.rs +++ b/subkey/src/main.rs @@ -29,7 +29,7 @@ use primitives::{ use codec::{Encode, Decode}; use sr_primitives::generic::Era; use node_primitives::{Balance, Index, Hash}; -use node_runtime::{Call, UncheckedExtrinsic, BalancesCall, Runtime}; +use node_runtime::{Call, UncheckedExtrinsic, SignedPayload, BalancesCall, Runtime, VERSION}; mod vanity; @@ -182,22 +182,21 @@ fn execute(matches: clap::ArgMatches) where println!("Using a genesis hash of {}", HexDisplay::from(&genesis_hash.as_ref())); - let raw_payload = ( + let raw_payload = SignedPayload::from_raw( function, extra(index, 0), - (&genesis_hash, &genesis_hash), + (VERSION.spec_version as u32, genesis_hash, genesis_hash, (), (), ()), ); - let signature = raw_payload.using_encoded(|payload| if payload.len() > 256 { - signer.sign(&blake2_256(payload)[..]) - } else { + let signature = raw_payload.using_encoded(|payload| { println!("Signing {}", HexDisplay::from(&payload)); signer.sign(payload) }); + let (function, extra, _) = raw_payload.deconstruct(); let extrinsic = UncheckedExtrinsic::new_signed( - raw_payload.0, + function, signer.public().into(), signature.into(), - extra(index, 0), + extra, ); println!("0x{}", hex::encode(&extrinsic.encode())); }