diff --git a/substrate/bin/node/executor/tests/common.rs b/substrate/bin/node/executor/tests/common.rs index 6b6ef272f8a35bac1140e77ed1411adc52f0e8cf..5a51e4312c5e80315c3b05fc5a82bc72a6842fb2 100644 --- a/substrate/bin/node/executor/tests/common.rs +++ b/substrate/bin/node/executor/tests/common.rs @@ -15,10 +15,21 @@ // along with Substrate. If not, see <http://www.gnu.org/licenses/>. use codec::{Encode, Decode}; +use frame_system::offchain::AppCrypto; use frame_support::Hashable; use sp_state_machine::TestExternalities as CoreTestExternalities; -use sp_core::{NeverNativeValue, NativeOrEncoded, traits::{CodeExecutor, RuntimeCode}}; -use sp_runtime::{ApplyExtrinsicResult, traits::{Header as HeaderT, BlakeTwo256}}; +use sp_core::{ + NeverNativeValue, NativeOrEncoded, + crypto::KeyTypeId, + sr25519::Signature, + traits::{CodeExecutor, RuntimeCode}, +}; +use sp_runtime::{ + ApplyExtrinsicResult, + MultiSigner, + MultiSignature, + traits::{Header as HeaderT, BlakeTwo256}, +}; use sc_executor::{NativeExecutor, WasmExecutionMethod}; use sc_executor::error::Result; @@ -31,6 +42,25 @@ use node_primitives::{Hash, BlockNumber}; use node_testing::keyring::*; use sp_externalities::Externalities; +pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); + +pub mod sr25519 { + mod app_sr25519 { + use sp_application_crypto::{app_crypto, sr25519}; + use super::super::TEST_KEY_TYPE_ID; + app_crypto!(sr25519, TEST_KEY_TYPE_ID); + } + + pub type AuthorityId = app_sr25519::Public; +} + +pub struct TestAuthorityId; +impl AppCrypto<MultiSigner, MultiSignature> for TestAuthorityId { + type RuntimeAppPublic = sr25519::AuthorityId; + type GenericSignature = Signature; + type GenericPublic = sp_core::sr25519::Public; +} + /// The wasm runtime code. /// /// `compact` since it is after post-processing with wasm-gc which performs tree-shaking thus diff --git a/substrate/bin/node/executor/tests/submit_transaction.rs b/substrate/bin/node/executor/tests/submit_transaction.rs index d92f3e3202f6e7cbcfa0743238b0774330244990..3a41c3483c280d7393c87e1f8f57e29f6cb2a4ff 100644 --- a/substrate/bin/node/executor/tests/submit_transaction.rs +++ b/substrate/bin/node/executor/tests/submit_transaction.rs @@ -15,24 +15,29 @@ // along with Substrate. If not, see <http://www.gnu.org/licenses/>. use node_runtime::{ - Call, Executive, Indices, Runtime, TransactionSubmitterOf, UncheckedExtrinsic, + Executive, Indices, Runtime, UncheckedExtrinsic, }; use sp_application_crypto::AppKey; use sp_core::testing::KeyStore; -use sp_core::traits::KeystoreExt; -use sp_core::offchain::{ - TransactionPoolExt, - testing::TestTransactionPoolExt, +use sp_core::{ + offchain::{ + TransactionPoolExt, + testing::TestTransactionPoolExt, + }, + traits::KeystoreExt, +}; +use frame_system::{ + offchain::{ + Signer, + SubmitTransaction, + SendSignedTransaction, + } }; -use frame_system::offchain::{SubmitSignedTransaction, SubmitUnsignedTransaction}; -use pallet_im_online::sr25519::AuthorityPair as Key; use codec::Decode; pub mod common; use self::common::*; -type SubmitTransaction = TransactionSubmitterOf<pallet_im_online::sr25519::AuthorityId>; - #[test] fn should_submit_unsigned_transaction() { let mut t = new_test_ext(COMPACT_CODE, false); @@ -49,8 +54,7 @@ fn should_submit_unsigned_transaction() { }; let call = pallet_im_online::Call::heartbeat(heartbeat_data, signature); - <SubmitTransaction as SubmitUnsignedTransaction<Runtime, Call>> - ::submit_unsigned(call) + SubmitTransaction::<Runtime, pallet_im_online::Call<Runtime>>::submit_unsigned_transaction(call.into()) .unwrap(); assert_eq!(state.read().transactions.len(), 1) @@ -66,23 +70,16 @@ fn should_submit_signed_transaction() { t.register_extension(TransactionPoolExt::new(pool)); let keystore = KeyStore::new(); - keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); - keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter2", PHRASE))).unwrap(); - keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter3", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter3", PHRASE))).unwrap(); t.register_extension(KeystoreExt(keystore)); t.execute_with(|| { - let keys = <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>> - ::find_all_local_keys(); - assert_eq!(keys.len(), 3, "Missing keys: {:?}", keys); - - let can_sign = <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>> - ::can_sign(); - assert!(can_sign, "Since there are keys, `can_sign` should return true"); - - let call = pallet_balances::Call::transfer(Default::default(), Default::default()); - let results = - <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>>::submit_signed(call); + let results = Signer::<Runtime, TestAuthorityId>::all_accounts() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); let len = results.len(); assert_eq!(len, 3); @@ -98,27 +95,26 @@ fn should_submit_signed_twice_from_the_same_account() { t.register_extension(TransactionPoolExt::new(pool)); let keystore = KeyStore::new(); - keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))).unwrap(); t.register_extension(KeystoreExt(keystore)); t.execute_with(|| { - let call = pallet_balances::Call::transfer(Default::default(), Default::default()); - let results = - <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>>::submit_signed(call); + let result = Signer::<Runtime, TestAuthorityId>::any_account() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); - let len = results.len(); - assert_eq!(len, 1); - assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert!(result.is_some()); assert_eq!(state.read().transactions.len(), 1); // submit another one from the same account. The nonce should be incremented. - let call = pallet_balances::Call::transfer(Default::default(), Default::default()); - let results = - <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>>::submit_signed(call); + let result = Signer::<Runtime, TestAuthorityId>::any_account() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); - let len = results.len(); - assert_eq!(len, 1); - assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert!(result.is_some()); assert_eq!(state.read().transactions.len(), 2); // now check that the transaction nonces are not equal @@ -136,6 +132,60 @@ fn should_submit_signed_twice_from_the_same_account() { }); } +#[test] +fn should_submit_signed_twice_from_all_accounts() { + let mut t = new_test_ext(COMPACT_CODE, false); + let (pool, state) = TestTransactionPoolExt::new(); + t.register_extension(TransactionPoolExt::new(pool)); + + let keystore = KeyStore::new(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))).unwrap(); + t.register_extension(KeystoreExt(keystore)); + + t.execute_with(|| { + let results = Signer::<Runtime, TestAuthorityId>::all_accounts() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); + + let len = results.len(); + assert_eq!(len, 2); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert_eq!(state.read().transactions.len(), 2); + + // submit another one from the same account. The nonce should be incremented. + let results = Signer::<Runtime, TestAuthorityId>::all_accounts() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); + + let len = results.len(); + assert_eq!(len, 2); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert_eq!(state.read().transactions.len(), 4); + + // now check that the transaction nonces are not equal + let s = state.read(); + fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> { + let extra = tx.signature.unwrap().2; + extra.3 + } + let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); + let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); + let nonce3 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[2]).unwrap()); + let nonce4 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[3]).unwrap()); + assert!( + nonce1 != nonce3, + "Transactions should have different nonces. Got: 1st tx nonce: {:?}, 2nd nonce: {:?}", nonce1, nonce3 + ); + assert!( + nonce2 != nonce4, + "Transactions should have different nonces. Got: 1st tx nonce: {:?}, 2nd tx nonce: {:?}", nonce2, nonce4 + ); + }); +} + #[test] fn submitted_transaction_should_be_valid() { use codec::Encode; @@ -148,13 +198,14 @@ fn submitted_transaction_should_be_valid() { t.register_extension(TransactionPoolExt::new(pool)); let keystore = KeyStore::new(); - keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); t.register_extension(KeystoreExt(keystore)); t.execute_with(|| { - let call = pallet_balances::Call::transfer(Default::default(), Default::default()); - let results = - <SubmitTransaction as SubmitSignedTransaction<Runtime, Call>>::submit_signed(call); + let results = Signer::<Runtime, TestAuthorityId>::all_accounts() + .send_signed_transaction(|_| { + pallet_balances::Call::transfer(Default::default(), Default::default()) + }); let len = results.len(); assert_eq!(len, 1); assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 337242f884ddc0e01f7a9e90df74f84049f48310..b1797fffb33211570cf27c6273ccac5786e9285f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -46,11 +46,10 @@ use sp_version::NativeVersion; use sp_core::OpaqueMetadata; use pallet_grandpa::AuthorityList as GrandpaAuthorityList; use pallet_grandpa::fg_primitives; -use pallet_im_online::sr25519::{AuthorityId as ImOnlineId}; +use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use pallet_contracts_rpc_runtime_api::ContractExecResult; -use frame_system::offchain::TransactionSubmitter; use sp_inherents::{InherentData, CheckInherentsResult}; #[cfg(any(feature = "std", test))] @@ -60,6 +59,7 @@ pub use pallet_balances::Call as BalancesCall; pub use pallet_contracts::Gas; pub use frame_support::StorageValue; pub use pallet_staking::StakerStatus; +use codec::Encode; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; @@ -73,50 +73,6 @@ use constants::{time::*, currency::*}; #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -/// A transaction submitter with the given key type. -pub type TransactionSubmitterOf<KeyType> = TransactionSubmitter<KeyType, Runtime, UncheckedExtrinsic>; - -/// Submits transaction with the node's public and signature type. Adheres to the signed extension -/// format of the chain. -impl frame_system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime { - type Public = <Signature as traits::Verify>::Signer; - type Signature = Signature; - - fn create_transaction<TSigner: frame_system::offchain::Signer<Self::Public, Self::Signature>>( - call: Call, - public: Self::Public, - account: AccountId, - index: Index, - ) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> { - // take the biggest period possible. - let period = BlockHashCount::get() - .checked_next_power_of_two() - .map(|c| c / 2) - .unwrap_or(2) as u64; - let current_block = System::block_number() - .saturated_into::<u64>() - // The `System::block_number` is initialized with `n+1`, - // so the actual block number is `n`. - .saturating_sub(1); - let tip = 0; - let extra: SignedExtra = ( - frame_system::CheckVersion::<Runtime>::new(), - frame_system::CheckGenesis::<Runtime>::new(), - frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)), - frame_system::CheckNonce::<Runtime>::from(index), - frame_system::CheckWeight::<Runtime>::new(), - pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), - Default::default(), - ); - let raw_payload = SignedPayload::new(call, extra).map_err(|e| { - debug::warn!("Unable to create signed payload: {:?}", e); - }).ok()?; - let signature = TSigner::sign(public, &raw_payload)?; - let address = Indices::unlookup(account); - let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature, extra))) - } -} /// Runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { @@ -127,7 +83,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 243, + spec_version: 244, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -362,7 +318,6 @@ impl pallet_staking::Trait for Runtime { type NextNewSession = Session; type ElectionLookahead = ElectionLookahead; type Call = Call; - type SubmitTransaction = TransactionSubmitterOf<()>; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = StakingUnsignedPriority; } @@ -549,11 +504,63 @@ parameter_types! { pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; } + +impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime where + Call: From<LocalCall>, +{ + fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>( + call: Call, + public: <Signature as traits::Verify>::Signer, + account: AccountId, + nonce: Index, + ) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> { + // take the biggest period possible. + let period = BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let current_block = System::block_number() + .saturated_into::<u64>() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); + let tip = 0; + let extra: SignedExtra = ( + frame_system::CheckVersion::<Runtime>::new(), + frame_system::CheckGenesis::<Runtime>::new(), + frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::<Runtime>::from(nonce), + frame_system::CheckWeight::<Runtime>::new(), + pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), + Default::default(), + ); + let raw_payload = SignedPayload::new(call, extra).map_err(|e| { + debug::warn!("Unable to create signed payload: {:?}", e); + }).ok()?; + let signature = raw_payload.using_encoded(|payload| { + C::sign(payload, public) + })?; + let address = Indices::unlookup(account); + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (address, signature.into(), extra))) + } +} + +impl frame_system::offchain::SigningTypes for Runtime { + type Public = <Signature as traits::Verify>::Signer; + type Signature = Signature; +} + +impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime where + Call: From<C>, +{ + type OverarchingCall = Call; + type Extrinsic = UncheckedExtrinsic; +} + impl pallet_im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Event = Event; - type Call = Call; - type SubmitTransaction = TransactionSubmitterOf<Self::AuthorityId>; type SessionDuration = SessionDuration; type ReportUnresponsiveness = Offences; type UnsignedPriority = ImOnlineUnsignedPriority; @@ -924,28 +931,14 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use super::*; - use frame_system::offchain::{SignAndSubmitTransaction, SubmitSignedTransaction}; + use frame_system::offchain::CreateSignedTransaction; #[test] fn validate_transaction_submitter_bounds() { fn is_submit_signed_transaction<T>() where - T: SubmitSignedTransaction< - Runtime, - Call, - >, - {} - - fn is_sign_and_submit_transaction<T>() where - T: SignAndSubmitTransaction< - Runtime, - Call, - Extrinsic=UncheckedExtrinsic, - CreateTransaction=Runtime, - Signer=ImOnlineId, - >, + T: CreateSignedTransaction<Call>, {} - is_submit_signed_transaction::<TransactionSubmitterOf<ImOnlineId>>(); - is_sign_and_submit_transaction::<TransactionSubmitterOf<ImOnlineId>>(); + is_submit_signed_transaction::<Runtime>(); } } diff --git a/substrate/frame/example-offchain-worker/src/lib.rs b/substrate/frame/example-offchain-worker/src/lib.rs index 29a4859c78e212ad6d074c4aa6cdc6c461b3c99c..d2ebd1159e228ddb396e0011c859fc31a52399d7 100644 --- a/substrate/frame/example-offchain-worker/src/lib.rs +++ b/substrate/frame/example-offchain-worker/src/lib.rs @@ -22,12 +22,12 @@ //! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's //! documentation. //! -//! - \[`pallet_example_offchain_worker::Trait`](./trait.Trait.html) -//! - \[`Call`](./enum.Call.html) -//! - \[`Module`](./struct.Module.html) +//! - [`pallet_example_offchain_worker::Trait`](./trait.Trait.html) +//! - [`Call`](./enum.Call.html) +//! - [`Module`](./struct.Module.html) //! //! -//! \## Overview +//! ## Overview //! //! In this example we are going to build a very simplistic, naive and definitely NOT //! production-ready oracle for BTC/USD price. @@ -40,15 +40,24 @@ //! one unsigned transaction floating in the network. #![cfg_attr(not(feature = "std"), no_std)] +use frame_system::{ + self as system, + ensure_signed, + ensure_none, + offchain::{ + AppCrypto, CreateSignedTransaction, SendUnsignedTransaction, + SignedPayload, SigningTypes, Signer, SubmitTransaction, + } +}; use frame_support::{ debug, dispatch::DispatchResult, decl_module, decl_storage, decl_event, traits::Get, weights::{SimpleDispatchInfo, MINIMUM_WEIGHT}, }; -use frame_system::{self as system, ensure_signed, ensure_none, offchain}; use sp_core::crypto::KeyTypeId; use sp_runtime::{ + RuntimeDebug, offchain::{http, Duration, storage::StorageValueRef}, traits::Zero, transaction_validity::{ @@ -56,6 +65,7 @@ use sp_runtime::{ TransactionPriority, }, }; +use codec::{Encode, Decode}; use sp_std::vec::Vec; use lite_json::json::JsonValue; @@ -76,18 +86,25 @@ pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!"); /// the types with this pallet-specific identifier. pub mod crypto { use super::KEY_TYPE; - use sp_runtime::app_crypto::{app_crypto, sr25519}; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + }; + use sp_core::sr25519::Signature as Sr25519Signature; app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature> for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } } /// This pallet's configuration trait -pub trait Trait: frame_system::Trait { - /// The type to sign and submit transactions. - type SubmitSignedTransaction: - offchain::SubmitSignedTransaction<Self, <Self as Trait>::Call>; - /// The type to submit unsigned transactions. - type SubmitUnsignedTransaction: - offchain::SubmitUnsignedTransaction<Self, <Self as Trait>::Call>; +pub trait Trait: CreateSignedTransaction<Call<Self>> { + /// The identifier type for an offchain worker. + type AuthorityId: AppCrypto<Self::Public, Self::Signature>; /// The overarching event type. type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>; @@ -115,6 +132,21 @@ pub trait Trait: frame_system::Trait { type UnsignedPriority: Get<TransactionPriority>; } +/// Payload used by this example crate to hold price +/// data required to submit a transaction. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct PricePayload<Public, BlockNumber> { + block_number: BlockNumber, + price: u32, + public: Public, +} + +impl<T: SigningTypes> SignedPayload<T> for PricePayload<T::Public, T::BlockNumber> { + fn public(&self) -> T::Public { + self.public.clone() + } +} + decl_storage! { trait Store for Module<T: Trait> as ExampleOffchainWorker { /// A vector of recently submitted prices. @@ -196,6 +228,22 @@ decl_module! { Ok(()) } + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + pub fn submit_price_unsigned_with_signed_payload( + origin, + price_payload: PricePayload<T::Public, T::BlockNumber>, + _signature: T::Signature, + ) -> DispatchResult { + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the price to the on-chain list, but mark it as coming from an empty address. + Self::add_price(Default::default(), price_payload.price); + // now increment the block number at which we expect next unsigned transaction. + let current_block = <system::Module<T>>::block_number(); + <NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get()); + Ok(()) + } + /// Offchain Worker entry point. /// /// By implementing `fn offchain_worker` within `decl_module!` you declare a new offchain @@ -236,7 +284,9 @@ decl_module! { let should_send = Self::choose_transaction_type(block_number); let res = match should_send { TransactionType::Signed => Self::fetch_price_and_send_signed(), - TransactionType::Unsigned => Self::fetch_price_and_send_unsigned(block_number), + TransactionType::UnsignedForAny => Self::fetch_price_and_send_unsigned_for_any_account(block_number), + TransactionType::UnsignedForAll => Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), + TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), TransactionType::None => Ok(()), }; if let Err(e) = res { @@ -248,7 +298,9 @@ decl_module! { enum TransactionType { Signed, - Unsigned, + UnsignedForAny, + UnsignedForAll, + Raw, None, } @@ -311,12 +363,11 @@ impl<T: Trait> Module<T> { // transactions in a row. If a strict order is desired, it's better to use // the storage entry for that. (for instance store both block number and a flag // indicating the type of next transaction to send). - let send_signed = block_number % 2.into() == Zero::zero(); - if send_signed { - TransactionType::Signed - } else { - TransactionType::Unsigned - } + let transaction_type = block_number % 3.into(); + if transaction_type == Zero::zero() { TransactionType::Signed } + else if transaction_type == T::BlockNumber::from(1) { TransactionType::UnsignedForAny } + else if transaction_type == T::BlockNumber::from(2) { TransactionType::UnsignedForAll } + else { TransactionType::Raw } }, // We are in the grace period, we should not send a transaction this time. Err(RECENTLY_SENT) => TransactionType::None, @@ -331,44 +382,43 @@ impl<T: Trait> Module<T> { /// A helper function to fetch the price and send signed transaction. fn fetch_price_and_send_signed() -> Result<(), &'static str> { - use system::offchain::SubmitSignedTransaction; - // Firstly we check if there are any accounts in the local keystore that are capable of - // signing the transaction. - // If not it doesn't even make sense to make external HTTP requests, since we won't be able - // to put the results back on-chain. - if !T::SubmitSignedTransaction::can_sign() { + use frame_system::offchain::SendSignedTransaction; + + let signer = Signer::<T, T::AuthorityId>::all_accounts(); + if !signer.can_sign() { return Err( "No local accounts available. Consider adding one via `author_insertKey` RPC." )? } - // Make an external HTTP request to fetch the current price. // Note this call will block until response is received. let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - // Received price is wrapped into a call to `submit_price` public function of this pallet. - // This means that the transaction, when executed, will simply call that function passing - // `price` as an argument. - let call = Call::submit_price(price); - - // Using `SubmitSignedTransaction` associated type we create and submit a transaction + // Using `send_signed_transaction` associated type we create and submit a transaction // representing the call, we've just created. // Submit signed will return a vector of results for all accounts that were found in the // local keystore with expected `KEY_TYPE`. - let results = T::SubmitSignedTransaction::submit_signed(call); + let results = signer.send_signed_transaction( + |_account| { + // Received price is wrapped into a call to `submit_price` public function of this pallet. + // This means that the transaction, when executed, will simply call that function passing + // `price` as an argument. + Call::submit_price(price) + } + ); + for (acc, res) in &results { match res { - Ok(()) => debug::info!("[{:?}] Submitted price of {} cents", acc, price), - Err(e) => debug::error!("[{:?}] Failed to submit transaction: {:?}", acc, e), + Ok(()) => debug::info!("[{:?}] Submitted price of {} cents", acc.id, price), + Err(e) => debug::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), } } Ok(()) } - /// A helper function to fetch the price and send unsigned transaction. - fn fetch_price_and_send_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> { - use system::offchain::SubmitUnsignedTransaction; + /// A helper function to fetch the price and send a raw unsigned transaction. + fn fetch_price_and_send_raw_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. let next_unsigned_at = <NextUnsignedAt<T>>::get(); @@ -385,14 +435,101 @@ impl<T: Trait> Module<T> { // passing `price` as an argument. let call = Call::submit_price_unsigned(block_number, price); - // Now let's create an unsigned transaction out of this call and submit it to the pool. + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // // By default unsigned transactions are disallowed, so we need to whitelist this case // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefuly // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam // attack vectors. See validation logic docs for more details. - T::SubmitUnsignedTransaction::submit_unsigned(call) - .map_err(|()| "Unable to submit unsigned transaction.".into()) + // + SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_any_account(block_number: T::BlockNumber) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = <NextUnsignedAt<T>>::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `price` as an argument. + let call = Call::submit_price_unsigned(block_number, price); + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction with a signed payload + SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + + // -- Sign using any account + let (_, result) = Signer::<T, T::AuthorityId>::any_account().send_unsigned_transaction( + |account| PricePayload { + price, + block_number, + public: account.public.clone() + }, + |payload, signature| { + Call::submit_price_unsigned_with_signed_payload(payload, signature) + } + ).ok_or("No local accounts accounts available.")?; + result.map_err(|()| "Unable to submit transaction")?; + + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_all_accounts(block_number: T::BlockNumber) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = <NextUnsignedAt<T>>::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `price` as an argument. + let call = Call::submit_price_unsigned(block_number, price); + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction with a signed payload + SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + + // -- Sign using all accounts + let transaction_results = Signer::<T, T::AuthorityId>::all_accounts() + .send_unsigned_transaction( + |account| PricePayload { + price, + block_number, + public: account.public.clone() + }, + |payload, signature| { + Call::submit_price_unsigned_with_signed_payload(payload, signature) + } + ); + for (_account_id, result) in transaction_results.into_iter() { + if result.is_err() { + return Err("Unable to submit transaction"); + } + } + + Ok(()) } /// Fetch current price and return the result in cents. @@ -507,6 +644,58 @@ impl<T: Trait> Module<T> { Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32) } } + + fn validate_transaction_parameters( + block_number: &T::BlockNumber, + new_price: &u32, + ) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at = <NextUnsignedAt<T>>::get(); + if &next_unsigned_at > block_number { + return InvalidTransaction::Stale.into(); + } + // Let's make sure to reject transactions from the future. + let current_block = <system::Module<T>>::block_number(); + if ¤t_block < block_number { + return InvalidTransaction::Future.into(); + } + + // We prioritize transactions that are more far away from current average. + // + // Note this doesn't make much sense when building an actual oracle, but this example + // is here mostly to show off offchain workers capabilities, not about building an + // oracle. + let avg_price = Self::average_price() + .map(|price| if &price > new_price { price - new_price } else { new_price - price }) + .unwrap_or(0); + + ValidTransaction::with_tag_prefix("ExampleOffchainWorker") + // We set base priority to 2**20 and hope it's included before any other + // transactions in the pool. Next we tweak the priority depending on how much + // it differs from the current average. (the more it differs the more priority it + // has). + .priority(T::UnsignedPriority::get().saturating_add(avg_price as _)) + // This transaction does not require anything else to go before into the pool. + // In theory we could require `previous_unsigned_at` transaction to go first, + // but it's not necessary in our case. + //.and_requires() + // We set the `provides` tag to be the same as `next_unsigned_at`. This makes + // sure only one transaction produced after `next_unsigned_at` will ever + // get to the transaction pool and will end up in the block. + // We can still have multiple transactions compete for the same "spot", + // and the one with higher priority will replace other one in the pool. + .and_provides(next_unsigned_at) + // The transaction is only valid for next 5 blocks. After that it's + // going to be revalidated by the pool. + .longevity(5) + // It's fine to propagate that transaction to other peers, which means it can be + // created even by nodes that don't produce blocks. + // Note that sometimes it's better to keep it for yourself (if you are the block + // producer), since for instance in some schemes others may copy your solution and + // claim a reward. + .propagate(true) + .build() + } } #[allow(deprecated)] // ValidateUnsigned @@ -523,54 +712,16 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> { call: &Self::Call, ) -> TransactionValidity { // Firstly let's check that we call the right function. - if let Call::submit_price_unsigned(block_number, new_price) = call { - // Now let's check if the transaction has any chance to succeed. - let next_unsigned_at = <NextUnsignedAt<T>>::get(); - if &next_unsigned_at > block_number { - return InvalidTransaction::Stale.into(); - } - // Let's make sure to reject transactions from the future. - let current_block = <system::Module<T>>::block_number(); - if ¤t_block < block_number { - return InvalidTransaction::Future.into(); + if let Call::submit_price_unsigned_with_signed_payload( + ref payload, ref signature + ) = call { + let signature_valid = SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone()); + if !signature_valid { + return InvalidTransaction::BadProof.into(); } - - // We prioritize transactions that are more far away from current average. - // - // Note this doesn't make much sense when building an actual oracle, but this example - // is here mostly to show off offchain workers capabilities, not about building an - // oracle. - let avg_price = Self::average_price() - .map(|price| if &price > new_price { price - new_price } else { new_price - price }) - .unwrap_or(0); - - ValidTransaction::with_tag_prefix("ExampleOffchainWorker") - // We set base priority to 2**20 to make sure it's included before any other - // transactions in the pool. Next we tweak the priority depending on how much - // it differs from the current average. (the more it differs the more priority it - // has). - .priority(T::UnsignedPriority::get().saturating_add(avg_price as _)) - // This transaction does not require anything else to go before into the pool. - // In theory we could require `previous_unsigned_at` transaction to go first, - // but it's not necessary in our case. - //.and_requires() - - // We set the `provides` tag to be the same as `next_unsigned_at`. This makes - // sure only one transaction produced after `next_unsigned_at` will ever - // get to the transaction pool and will end up in the block. - // We can still have multiple transactions compete for the same "spot", - // and the one with higher priority will replace other one in the pool. - .and_provides(next_unsigned_at) - // The transaction is only valid for next 5 blocks. After that it's - // going to be revalidated by the pool. - .longevity(5) - // It's fine to propagate that transaction to other peers, which means it can be - // created even by nodes that don't produce blocks. - // Note that sometimes it's better to keep it for yourself (if you are the block - // producer), since for instance in some schemes others may copy your solution and - // claim a reward. - .propagate(true) - .build() + Self::validate_transaction_parameters(&payload.block_number, &payload.price) + } else if let Call::submit_price_unsigned(block_number, new_price) = call { + Self::validate_transaction_parameters(block_number, new_price) } else { InvalidTransaction::Call.into() } diff --git a/substrate/frame/example-offchain-worker/src/tests.rs b/substrate/frame/example-offchain-worker/src/tests.rs index 279de7ef4a3820c6ca3e5425430306ccce5bf62c..aebcbde451b151b2f8e94ac5df7766a7a6238905 100644 --- a/substrate/frame/example-offchain-worker/src/tests.rs +++ b/substrate/frame/example-offchain-worker/src/tests.rs @@ -16,7 +16,7 @@ use crate::*; -use codec::Decode; +use codec::{Encode, Decode}; use frame_support::{ assert_ok, impl_outer_origin, parameter_types, weights::Weight, @@ -24,13 +24,17 @@ use frame_support::{ use sp_core::{ H256, offchain::{OffchainExt, TransactionPoolExt, testing}, + sr25519::Signature, testing::KeyStore, traits::KeystoreExt, }; use sp_runtime::{ Perbill, RuntimeAppPublic, testing::{Header, TestXt}, - traits::{BlakeTwo256, IdentityLookup, Extrinsic as ExtrinsicsT}, + traits::{ + BlakeTwo256, IdentityLookup, Extrinsic as ExtrinsicT, + IdentifyAccount, Verify, + }, }; impl_outer_origin! { @@ -40,7 +44,7 @@ impl_outer_origin! { // For testing the module, we construct most of a mock runtime. This means // first constructing a configuration type (`Test`) which `impl`s each of the // configuration traits of modules we want to use. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq, Encode, Decode)] pub struct Test; parameter_types! { pub const BlockHashCount: u64 = 250; @@ -72,22 +76,29 @@ impl frame_system::Trait for Test { } type Extrinsic = TestXt<Call<Test>, ()>; -type SubmitTransaction = frame_system::offchain::TransactionSubmitter< - crypto::Public, - Test, - Extrinsic ->; - -impl frame_system::offchain::CreateTransaction<Test, Extrinsic> for Test { - type Public = sp_core::sr25519::Public; - type Signature = sp_core::sr25519::Signature; - - fn create_transaction<F: frame_system::offchain::Signer<Self::Public, Self::Signature>>( - call: <Extrinsic as ExtrinsicsT>::Call, - _public: Self::Public, - _account: <Test as frame_system::Trait>::AccountId, - nonce: <Test as frame_system::Trait>::Index, - ) -> Option<(<Extrinsic as ExtrinsicsT>::Call, <Extrinsic as ExtrinsicsT>::SignaturePayload)> { +type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId; + +impl frame_system::offchain::SigningTypes for Test { + type Public = <Signature as Verify>::Signer; + type Signature = Signature; +} + +impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Test where + Call<Test>: From<LocalCall>, +{ + type OverarchingCall = Call<Test>; + type Extrinsic = Extrinsic; +} + +impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test where + Call<Test>: From<LocalCall>, +{ + fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>( + call: Call<Test>, + _public: <Signature as Verify>::Signer, + _account: AccountId, + nonce: u64, + ) -> Option<(Call<Test>, <Extrinsic as ExtrinsicT>::SignaturePayload)> { Some((call, (nonce, ()))) } } @@ -100,9 +111,8 @@ parameter_types! { impl Trait for Test { type Event = (); + type AuthorityId = crypto::TestAuthId; type Call = Call<Test>; - type SubmitSignedTransaction = SubmitTransaction; - type SubmitUnsignedTransaction = SubmitTransaction; type GracePeriod = GracePeriod; type UnsignedInterval = UnsignedInterval; type UnsignedPriority = UnsignedPriority; @@ -172,18 +182,128 @@ fn should_submit_signed_transaction_on_chain() { } #[test] -fn should_submit_unsigned_transaction_on_chain() { +fn should_submit_unsigned_transaction_on_chain_for_any_account() { + const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + + let keystore = KeyStore::new(); + + keystore.write().sr25519_generate_new( + crate::crypto::Public::ID, + Some(&format!("{}/hunter1", PHRASE)) + ).unwrap(); + let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); + t.register_extension(KeystoreExt(keystore.clone())); + + price_oracle_response(&mut offchain_state.write()); + + let public_key = keystore.read() + .sr25519_public_keys(crate::crypto::Public::ID) + .get(0) + .unwrap() + .clone(); + + let price_payload = PricePayload { + block_number: 1, + price: 15523, + public: <Test as SigningTypes>::Public::from(public_key), + }; + + // let signature = price_payload.sign::<crypto::TestAuthId>().unwrap(); + t.execute_with(|| { + // when + Example::fetch_price_and_send_unsigned_for_any_account(1).unwrap(); + // then + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + assert_eq!(tx.signature, None); + if let Call::submit_price_unsigned_with_signed_payload(body, signature) = tx.call { + assert_eq!(body, price_payload); + + let signature_valid = <PricePayload< + <Test as SigningTypes>::Public, + <Test as frame_system::Trait>::BlockNumber + > as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature); + + assert!(signature_valid); + } + }); +} + +#[test] +fn should_submit_unsigned_transaction_on_chain_for_all_accounts() { + const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; + let (offchain, offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + + let keystore = KeyStore::new(); + + keystore.write().sr25519_generate_new( + crate::crypto::Public::ID, + Some(&format!("{}/hunter1", PHRASE)) + ).unwrap(); + + let mut t = sp_io::TestExternalities::default(); + t.register_extension(OffchainExt::new(offchain)); + t.register_extension(TransactionPoolExt::new(pool)); + t.register_extension(KeystoreExt(keystore.clone())); + + price_oracle_response(&mut offchain_state.write()); + + let public_key = keystore.read() + .sr25519_public_keys(crate::crypto::Public::ID) + .get(0) + .unwrap() + .clone(); + + let price_payload = PricePayload { + block_number: 1, + price: 15523, + public: <Test as SigningTypes>::Public::from(public_key), + }; + + // let signature = price_payload.sign::<crypto::TestAuthId>().unwrap(); + t.execute_with(|| { + // when + Example::fetch_price_and_send_unsigned_for_all_accounts(1).unwrap(); + // then + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + assert_eq!(tx.signature, None); + if let Call::submit_price_unsigned_with_signed_payload(body, signature) = tx.call { + assert_eq!(body, price_payload); + + let signature_valid = <PricePayload< + <Test as SigningTypes>::Public, + <Test as frame_system::Trait>::BlockNumber + > as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature); + + assert!(signature_valid); + } + }); +} + +#[test] +fn should_submit_raw_unsigned_transaction_on_chain() { + let (offchain, offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + + let keystore = KeyStore::new(); + + let mut t = sp_io::TestExternalities::default(); + t.register_extension(OffchainExt::new(offchain)); + t.register_extension(TransactionPoolExt::new(pool)); + t.register_extension(KeystoreExt(keystore)); price_oracle_response(&mut offchain_state.write()); t.execute_with(|| { // when - Example::fetch_price_and_send_unsigned(1).unwrap(); + Example::fetch_price_and_send_raw_unsigned(1).unwrap(); // then let tx = pool_state.write().transactions.pop().unwrap(); assert!(pool_state.read().transactions.is_empty()); diff --git a/substrate/frame/im-online/src/lib.rs b/substrate/frame/im-online/src/lib.rs index 1137fc2699fe3f13e60cb7e220092cf43913c184..813b11bbc9360c5a07a95aa67857dca5f9cb5b11 100644 --- a/substrate/frame/im-online/src/lib.rs +++ b/substrate/frame/im-online/src/lib.rs @@ -98,7 +98,10 @@ use frame_support::{ weights::{SimpleDispatchInfo, MINIMUM_WEIGHT}, }; use frame_system::{self as system, ensure_none}; -use frame_system::offchain::SubmitUnsignedTransaction; +use frame_system::offchain::{ + SendTransactionTypes, + SubmitTransaction, +}; pub mod sr25519 { mod app_sr25519 { @@ -221,19 +224,13 @@ pub struct Heartbeat<BlockNumber> pub authority_index: AuthIndex, } -pub trait Trait: frame_system::Trait + pallet_session::historical::Trait { +pub trait Trait: SendTransactionTypes<Call<Self>> + pallet_session::historical::Trait { /// The identifier type for an authority. type AuthorityId: Member + Parameter + RuntimeAppPublic + Default + Ord; /// The overarching event type. type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>; - /// A dispatchable call type. - type Call: From<Call<Self>>; - - /// A transaction submitter. - type SubmitTransaction: SubmitUnsignedTransaction<Self, <Self as Trait>::Call>; - /// An expected duration of the session. /// /// This parameter is used to determine the longevity of `heartbeat` transaction @@ -444,6 +441,7 @@ impl<T: Trait> Module<T> { } let session_index = <pallet_session::Module<T>>::current_index(); + Ok(Self::local_authority_keys() .map(move |(authority_index, key)| Self::send_single_heartbeat(authority_index, key, session_index, block_number) @@ -467,7 +465,9 @@ impl<T: Trait> Module<T> { session_index, authority_index, }; + let signature = key.sign(&heartbeat_data.encode()).ok_or(OffchainErr::FailedSigning)?; + Ok(Call::heartbeat(heartbeat_data, signature)) }; @@ -492,7 +492,7 @@ impl<T: Trait> Module<T> { call, ); - T::SubmitTransaction::submit_unsigned(call) + SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) .map_err(|_| OffchainErr::SubmitTransaction)?; Ok(()) @@ -501,9 +501,18 @@ impl<T: Trait> Module<T> { } fn local_authority_keys() -> impl Iterator<Item=(u32, T::AuthorityId)> { - // we run only when a local authority key is configured + // on-chain storage + // + // At index `idx`: + // 1. A (ImOnline) public key to be used by a validator at index `idx` to send im-online + // heartbeats. let authorities = Keys::<T>::get(); + + // local keystore + // + // All `ImOnline` public (+private) keys currently in the local keystore. let mut local_keys = T::AuthorityId::all(); + local_keys.sort(); authorities.into_iter() @@ -565,6 +574,11 @@ impl<T: Trait> Module<T> { Keys::<T>::put(keys); } } + + #[cfg(test)] + fn set_keys(keys: Vec<T::AuthorityId>) { + Keys::<T>::put(&keys) + } } impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> { diff --git a/substrate/frame/im-online/src/mock.rs b/substrate/frame/im-online/src/mock.rs index d620bb51b7436be9005d8d48ac1d65962456b3c1..e9b5ef95ef4a02be212ce3758b21d44471c94cfb 100644 --- a/substrate/frame/im-online/src/mock.rs +++ b/substrate/frame/im-online/src/mock.rs @@ -27,7 +27,6 @@ use sp_runtime::testing::{Header, UintAuthorityId, TestXt}; use sp_runtime::traits::{IdentityLookup, BlakeTwo256, ConvertInto}; use sp_core::H256; use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types, weights::Weight}; - use frame_system as system; impl_outer_origin!{ pub enum Origin for Runtime {} @@ -40,7 +39,11 @@ impl_outer_dispatch! { } thread_local! { - pub static VALIDATORS: RefCell<Option<Vec<u64>>> = RefCell::new(Some(vec![1, 2, 3])); + pub static VALIDATORS: RefCell<Option<Vec<u64>>> = RefCell::new(Some(vec![ + 1, + 2, + 3, + ])); } pub struct TestSessionManager; @@ -68,7 +71,6 @@ impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager /// An extrinsic type used for tests. pub type Extrinsic = TestXt<Call, ()>; -type SubmitTransaction = frame_system::offchain::TransactionSubmitter<(), Call, Extrinsic>; type IdentificationTuple = (u64, u64); type Offence = crate::UnresponsivenessOffence<IdentificationTuple>; @@ -90,7 +92,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } - #[derive(Clone, PartialEq, Eq, Debug)] pub struct Runtime; @@ -168,13 +169,18 @@ parameter_types! { impl Trait for Runtime { type AuthorityId = UintAuthorityId; type Event = (); - type Call = Call; - type SubmitTransaction = SubmitTransaction; type ReportUnresponsiveness = OffenceHandler; type SessionDuration = Period; type UnsignedPriority = UnsignedPriority; } +impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime where + Call: From<LocalCall>, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + /// Im Online module. pub type ImOnline = Module<Runtime>; pub type System = frame_system::Module<Runtime>; @@ -184,5 +190,7 @@ pub fn advance_session() { let now = System::block_number().max(1); System::set_block_number(now + 1); Session::rotate_session(); + let keys = Session::validators().into_iter().map(UintAuthorityId).collect(); + ImOnline::set_keys(keys); assert_eq!(Session::current_index(), (now / Period::get()) as u32); } diff --git a/substrate/frame/im-online/src/tests.rs b/substrate/frame/im-online/src/tests.rs index c7bf2afcca629f74765c1519abdd063e63dae1f1..e49f28f4896e068d58adbfcaa3062a9482996497 100644 --- a/substrate/frame/im-online/src/tests.rs +++ b/substrate/frame/im-online/src/tests.rs @@ -61,15 +61,15 @@ fn should_report_offline_validators() { let block = 1; System::set_block_number(block); // buffer new validators - Session::rotate_session(); + advance_session(); // enact the change and buffer another one let validators = vec![1, 2, 3, 4, 5, 6]; VALIDATORS.with(|l| *l.borrow_mut() = Some(validators.clone())); - Session::rotate_session(); + advance_session(); // when // we end current session and start the next one - Session::rotate_session(); + advance_session(); // then let offences = OFFENCES.with(|l| l.replace(vec![])); @@ -89,7 +89,7 @@ fn should_report_offline_validators() { for (idx, v) in validators.into_iter().take(4).enumerate() { let _ = heartbeat(block, 3, idx as u32, v.into()).unwrap(); } - Session::rotate_session(); + advance_session(); // then let offences = OFFENCES.with(|l| l.replace(vec![])); @@ -174,7 +174,7 @@ fn late_heartbeat_should_fail() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 4, 4, 5, 6])); + VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); assert_eq!(Session::validators(), Vec::<u64>::new()); // enact the change and buffer another one advance_session(); @@ -315,7 +315,7 @@ fn should_not_send_a_report_if_already_online() { ImOnline::note_uncle(3, 0); // when - UintAuthorityId::set_all_keys(vec![0]); // all authorities use pallet_session key 0 + UintAuthorityId::set_all_keys(vec![1, 2, 3]); // we expect error, since the authority is already online. let mut res = ImOnline::send_heartbeats(4).unwrap(); assert_eq!(res.next().unwrap().unwrap(), ()); diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 4c022eb8b89b6cb3ee710cd27e3c498635d297ec..c3863e16bbbad244a4541a4147dd32f9f11dd29b 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -151,11 +151,13 @@ parameter_types! { } pub type Extrinsic = sp_runtime::testing::TestXt<Call, ()>; -type SubmitTransaction = frame_system::offchain::TransactionSubmitter< - sp_runtime::testing::UintAuthorityId, - Test, - Extrinsic, ->; + +impl<C> frame_system::offchain::SendTransactionTypes<C> for Test where + Call: From<C>, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} impl pallet_staking::Trait for Test { type Currency = Balances; @@ -174,7 +176,6 @@ impl pallet_staking::Trait for Test { type NextNewSession = Session; type ElectionLookahead = (); type Call = Call; - type SubmitTransaction = SubmitTransaction; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = UnsignedPriority; } diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 40fce5b0d4439934c3e8d48b7c62f0f499d79e08..25d9a10709d2128b204316c3b6d6623fbef6076e 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -320,7 +320,7 @@ use sp_staking::{ use sp_runtime::{Serialize, Deserialize}; use frame_system::{ self as system, ensure_signed, ensure_root, ensure_none, - offchain::SubmitUnsignedTransaction, + offchain::SendTransactionTypes, }; use sp_phragmen::{ ExtendedBalance, Assignment, PhragmenScore, PhragmenResult, build_support_map, evaluate_support, @@ -743,7 +743,7 @@ impl<T: Trait> SessionInterface<<T as frame_system::Trait>::AccountId> for T whe } } -pub trait Trait: frame_system::Trait { +pub trait Trait: frame_system::Trait + SendTransactionTypes<Call<Self>> { /// The staking balance. type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>; @@ -804,10 +804,7 @@ pub trait Trait: frame_system::Trait { /// The overarching call type. type Call: Dispatchable + From<Call<Self>> + IsSubType<Module<Self>, Self> + Clone; - /// A transaction submitter. - type SubmitTransaction: SubmitUnsignedTransaction<Self, <Self as Trait>::Call>; - - /// The maximum number of nominators rewarded for each validator. + /// The maximum number of nominator rewarded for each validator. /// /// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim /// their reward. This used to limit the i/o cost for the nominator payout. diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 20ec6f46a6b8b270665c2a6351512942c2f8c07f..cd943abfa3974d54b8079f0429196464f8145a91 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -29,7 +29,6 @@ use frame_support::{ traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, weights::Weight, }; -use frame_system::offchain::TransactionSubmitter; use sp_io; use sp_phragmen::{ build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore, @@ -309,13 +308,18 @@ impl Trait for Test { type NextNewSession = Session; type ElectionLookahead = ElectionLookahead; type Call = Call; - type SubmitTransaction = SubmitTransaction; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = UnsignedPriority; } +impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Test where + Call: From<LocalCall>, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + pub type Extrinsic = TestXt<Call, ()>; -type SubmitTransaction = TransactionSubmitter<(), Test, Extrinsic>; pub struct ExtBuilder { session_length: BlockNumber, diff --git a/substrate/frame/staking/src/offchain_election.rs b/substrate/frame/staking/src/offchain_election.rs index 4d8ccc6f25cca09f5fca272a2e898621f91cf602..c2383d1eebb69b2ab2a5427f1441b29d74c64211 100644 --- a/substrate/frame/staking/src/offchain_election.rs +++ b/substrate/frame/staking/src/offchain_election.rs @@ -19,7 +19,7 @@ use crate::{ Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex, }; -use frame_system::offchain::SubmitUnsignedTransaction; +use frame_system::offchain::SubmitTransaction; use sp_phragmen::{ build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult, PhragmenScore, @@ -117,14 +117,14 @@ pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElecti let current_era = <Module<T>>::current_era().unwrap_or_default(); // send it. - let call: <T as Trait>::Call = Call::submit_election_solution_unsigned( + let call = Call::submit_election_solution_unsigned( winners, compact, score, current_era, ).into(); - T::SubmitTransaction::submit_unsigned(call) + SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call) .map_err(|_| OffchainElectionError::PoolSubmissionFailed) } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index d1bc3c1a51eab13935447f0a1e1e0a3227f57554..49c404c02209971751a6b0a1e8938c8ef766133a 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -1591,7 +1591,7 @@ impl<T: Trait> Lookup for ChainContext<T> { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use sp_std::cell::RefCell; use sp_core::H256; @@ -1602,7 +1602,7 @@ mod tests { pub enum Origin for Test where system = super {} } - #[derive(Clone, Eq, PartialEq)] + #[derive(Clone, Eq, PartialEq, Debug)] pub struct Test; parameter_types! { @@ -1630,8 +1630,9 @@ mod tests { fn on_killed_account(who: &u64) { KILLED.with(|r| r.borrow_mut().push(*who)) } } - #[derive(Debug)] - pub struct Call {} + #[derive(Debug, codec::Encode, codec::Decode)] + pub struct Call; + impl Dispatchable for Call { type Origin = (); type Trait = (); @@ -1679,7 +1680,7 @@ mod tests { type System = Module<Test>; - const CALL: &<Test as Trait>::Call = &Call {}; + const CALL: &<Test as Trait>::Call = &Call; fn new_test_ext() -> sp_io::TestExternalities { GenesisConfig::default().build_storage::<Test>().unwrap().into() diff --git a/substrate/frame/system/src/offchain.rs b/substrate/frame/system/src/offchain.rs index a3fe3e00ca4be879ed6074cc6b13f75e0fbae83f..04cd3001e43a3899cdbdafaa7fb3a1eeb9bed9db 100644 --- a/substrate/frame/system/src/offchain.rs +++ b/substrate/frame/system/src/offchain.rs @@ -15,353 +15,832 @@ // along with Substrate. If not, see <http://www.gnu.org/licenses/>. //! 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 sp_std::convert::TryInto; -use sp_std::prelude::Vec; -use sp_runtime::app_crypto::{RuntimeAppPublic, AppPublic, AppSignature}; +use sp_std::collections::btree_set::BTreeSet; +use sp_std::convert::{TryInto, TryFrom}; +use sp_std::prelude::{Box, Vec}; +use sp_runtime::app_crypto::RuntimeAppPublic; use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One}; -use frame_support::{debug, storage::StorageMap}; +use frame_support::{debug, storage::StorageMap, RuntimeDebug}; -/// Creates runtime-specific signed transaction. +/// 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. /// -/// This trait should be implemented by your `Runtime` to be able -/// to submit `SignedTransaction`s` to the pool from off-chain code. -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; +/// 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)> +} - /// 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)>; +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) + } } -/// A trait responsible for signing a payload using given account. +/// Provides an implementation for signing transaction payloads. /// -/// This trait is usually going to represent a local public key -/// that has ability to sign arbitrary `Payloads`. +/// Keys used for signing are defined when instantiating the signer object. +/// Signing can be done using: /// -/// 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. +/// - 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 /// -/// 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. +/// 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. /// - /// 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>; + /// 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 = generic_public.into(); + let account_id = public.clone().into_account(); + Account::new(index, account_id, public.clone()) + }) + } } -/// A `Signer` implementation for any `AppPublic` type. -/// -/// This implementation additionally 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, TAnyAppPublic> Signer<Public, Signature> for TAnyAppPublic where - TAnyAppPublic: RuntimeAppPublic - + AppPublic - + From<<TAnyAppPublic as AppPublic>::Generic>, - <TAnyAppPublic as RuntimeAppPublic>::Signature: AppSignature, - Signature: From< - <<TAnyAppPublic as RuntimeAppPublic>::Signature as AppSignature>::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()?; - TAnyAppPublic::from(public).sign(&payload) - .map( - <<TAnyAppPublic as RuntimeAppPublic>::Signature as AppSignature> - ::Generic::from - ) - .map(Signature::from) + +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) }) } } -/// Retrieves a public key type for given `SignAndSubmitTransaction`. -pub type PublicOf<T, Call, X> = -< - <X as SignAndSubmitTransaction<T, Call>>::CreateTransaction - as - CreateTransaction<T, <X as SignAndSubmitTransaction<T, Call>>::Extrinsic> ->::Public; +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 trait to sign and submit transactions in off-chain calls. +/// A type binding runtime-level `Public/Signature` pair with crypto wrapped by `RuntimeAppPublic`. /// -/// NOTE: Most likely you should not implement this trait yourself. -/// There is an implementation for -/// [`TransactionSubmitter`] type, which -/// you should use. -pub trait SignAndSubmitTransaction<T: crate::Trait, Call> { - /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT<Call=Call> + Encode; - - /// A runtime-specific type to produce signed data for the extrinsic. - type CreateTransaction: CreateTransaction<T, Self::Extrinsic>; - - /// A type used to sign transactions created using `CreateTransaction`. - type Signer: Signer< - PublicOf<T, Call, Self>, - <Self::CreateTransaction as CreateTransaction<T, Self::Extrinsic>>::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<Call>, public: PublicOf<T, Call, Self>) -> Result<(), ()> { - let call = call.into(); - let id = public.clone().into_account(); - let mut account = super::Account::<T>::get(&id); - debug::native::debug!( - target: "offchain", - "Creating signed transaction from account: {:?} (nonce: {:?})", - id, - account.nonce, - ); - let (call, signature_data) = Self::CreateTransaction - ::create_transaction::<Self::Signer>(call, public, id.clone(), account.nonce) - .ok_or(())?; - // increment the nonce. This is fine, since the code should always - // be running in off-chain context, so we NEVER persists data. - account.nonce += One::one(); - super::Account::<T>::insert(&id, account); - - let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?; - sp_io::offchain::submit_transaction(xt.encode()) +/// 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. +/// +/// TODO [#5662] Potentially use `IsWrappedBy` types, or find some other way to make it easy to +/// obtain unwrapped crypto (and wrap it back). +/// +/// 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>; +/// ``` +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 trait to submit unsigned transactions in off-chain calls. +/// 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. /// -/// 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> + Encode; - - /// Submit given call to the transaction pool as unsigned transaction. +/// 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::Trait { + /// A public key that is capable of identifing `AccountId`s. /// - /// Returns `Ok` if the transaction was submitted correctly - /// and `Err` if transaction was rejected from the pool. - fn submit_unsigned(call: impl Into<Call>) -> Result<(), ()> { - let xt = Self::Extrinsic::new(call.into(), None).ok_or(())?; - let encoded_xt = xt.encode(); - sp_io::offchain::submit_transaction(encoded_xt) - } + /// 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; + + /// A matching `Signature` type. + type Signature: Clone + + PartialEq + + core::fmt::Debug + + codec::Codec; } -/// A utility trait to easily create signed transactions -/// from accounts in node's local keystore. +/// 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>; +} + +/// Create signed transaction. /// -/// 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> { - /// A `SignAndSubmitTransaction` implementation. - type SignAndSubmit: SignAndSubmitTransaction<T, Call>; - - /// Find local keys that match given list of accounts. +/// 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. /// - /// Technically it finds an intersection between given list of `AccountId`s - /// and accounts that are represented by public keys in local keystore. - /// If `None` is passed it returns all accounts in the keystore. + /// 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. /// - /// 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: Option<impl IntoIterator<Item = T::AccountId>>) -> Vec<( - T::AccountId, - PublicOf<T, Call, Self::SignAndSubmit>, - )>; - - /// Find all available local keys. + /// May contain account used for signing and the `Signature` itself. + type SignatureData; + + /// Sign a message. /// - /// This is equivalent of calling `find_local_keys(None)`. - fn find_all_local_keys() -> Vec<(T::AccountId, PublicOf<T, Call, Self::SignAndSubmit>)> { - Self::find_local_keys(None as Option<Vec<_>>) - } + /// Implementation of this method should return + /// a result containing the signature. + fn sign_message(&self, message: &[u8]) -> Self::SignatureData; - /// Check if there are keys for any of given accounts that could be used to send a transaction. + /// Construct and sign given payload. /// - /// This check can be used as an early-exit condition to avoid doing too - /// much work, before we actually realise that there are no accounts that you - /// we could use for signing. - fn can_sign_with(accounts: Option<impl IntoIterator<Item = T::AccountId>>) -> bool { - !Self::find_local_keys(accounts).is_empty() - } + /// 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>, + ; +} - /// Check if there are any keys that could be used for signing. +/// 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 is equivalent of calling `can_sign_with(None)`. - fn can_sign() -> bool { - Self::can_sign_with(None as Option<Vec<_>>) + /// 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); + debug::native::debug!( + target: "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) } +} - /// Create and submit signed transactions from supported accounts. +/// Submit an unsigned transaction onchain with a signed payload +pub trait SendUnsignedTransaction< + T: SigningTypes + SendTransactionTypes<LocalCall>, + LocalCall, +> { + /// A submission result. /// - /// This method should intersect given list of accounts with the ones - /// supported locally and submit signed transaction containing given `Call` - /// with every of them. + /// Should contain the submission result and the account(s) that signed the payload. + type Result; + + /// Send an unsigned transaction with a signed payload. /// - /// Returns a vector of results and account ids that were supported. - #[must_use] - fn submit_signed_from( - call: impl Into<Call> + Clone, - accounts: impl IntoIterator<Item = T::AccountId>, - ) -> Vec<(T::AccountId, Result<(), ()>)> { - let keys = Self::find_local_keys(Some(accounts)); - keys.into_iter().map(|(account, pub_key)| { - let call = call.clone().into(); - ( - account, - Self::SignAndSubmit::sign_and_submit(call, pub_key) - ) - }).collect() - } - - /// Create and submit signed transactions from all local accounts. + /// 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. /// - /// This method submits a signed transaction from all local accounts - /// for given application crypto. + /// 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 vector of results and account ids that were supported. - #[must_use] - fn submit_signed( - call: impl Into<Call> + Clone, - ) -> Vec<(T::AccountId, Result<(), ()>)> { - let keys = Self::find_all_local_keys(); - keys.into_iter().map(|(account, pub_key)| { - let call = call.clone().into(); - ( - account, - Self::SignAndSubmit::sign_and_submit(call, pub_key) - ) - }).collect() + /// 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)) } } -/// A default type used to submit transactions to the pool. -/// -/// This is passed into each runtime as an opaque associated type that can have either of: -/// - [`SignAndSubmitTransaction`] -/// - [`SubmitUnsignedTransaction`] -/// - [`SubmitSignedTransaction`] -/// 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: sp_std::marker::PhantomData<(Signer, CreateTransaction, Extrinsic)>, -} -impl<S, C, E> Default for TransactionSubmitter<S, C, E> { - fn default() -> Self { - Self { - _signer: Default::default(), - } +#[cfg(test)] +mod tests { + use super::*; + use codec::Decode; + use crate::tests::{Test as TestRuntime, Call}; + use sp_core::offchain::{testing, TransactionPoolExt}; + use sp_runtime::testing::{UintAuthorityId, TestSignature, TestXt}; + + impl SigningTypes for TestRuntime { + type Public = UintAuthorityId; + type Signature = TestSignature; } -} -/// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. -impl<T, E, S, C, Call> SignAndSubmitTransaction<T, Call> for TransactionSubmitter<S, C, E> where - T: crate::Trait, - C: CreateTransaction<T, E>, - S: Signer<<C as CreateTransaction<T, E>>::Public, <C as CreateTransaction<T, E>>::Signature>, - E: ExtrinsicT<Call=Call> + Encode, -{ - type Extrinsic = E; - type CreateTransaction = C; - type Signer = S; -} + type Extrinsic = TestXt<Call, ()>; -/// 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> + Encode, -{ - type Extrinsic = E; -} + impl SendTransactionTypes<Call> for TestRuntime { + type Extrinsic = Extrinsic; + type OverarchingCall = Call; + } -/// A blanket implementation to support local keystore of application-crypto types. -impl<T, C, E, S, Call> SubmitSignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where - T: crate::Trait, - C: CreateTransaction<T, E>, - E: ExtrinsicT<Call=Call> + Encode, - S: Signer<<C as CreateTransaction<T, E>>::Public, <C as CreateTransaction<T, E>>::Signature>, - // Make sure we can unwrap the app crypto key. - S: RuntimeAppPublic + AppPublic + Into<<S as AppPublic>::Generic>, - // Make sure we can convert from wrapped crypto to public key (e.g. `MultiSigner`) - S::Generic: Into<PublicOf<T, Call, Self>>, - // For simplicity we require the same trait to implement `SignAndSubmitTransaction` too. - Self: SignAndSubmitTransaction<T, Call, Signer = S, Extrinsic = E, CreateTransaction = C>, -{ - type SignAndSubmit = Self; - - fn find_local_keys(accounts: Option<impl IntoIterator<Item = T::AccountId>>) -> Vec<( - T::AccountId, - PublicOf<T, Call, Self::SignAndSubmit>, - )> { - // Convert app-specific keys into generic ones. - let local_accounts_and_keys = S::all() - .into_iter() - .map(|app_key| { - // unwrap app-crypto - let generic_pub_key: <S as AppPublic>::Generic = app_key.into(); - // convert to expected public key type (might be MultiSigner) - let signer_pub_key: PublicOf<T, Call, Self::SignAndSubmit> = generic_pub_key.into(); - // lookup accountid for that pubkey - let account = signer_pub_key.clone().into_account(); - (account, signer_pub_key) - }).collect::<Vec<_>>(); - - if let Some(accounts) = accounts { - let mut local_accounts_and_keys = local_accounts_and_keys; - // sort by accountId to allow bin-search. - local_accounts_and_keys.sort_by(|a, b| a.0.cmp(&b.0)); - - // get all the matching accounts - accounts.into_iter().filter_map(|acc| { - let idx = local_accounts_and_keys.binary_search_by(|a| a.0.cmp(&acc)).ok()?; - local_accounts_and_keys.get(idx).cloned() - }).collect() - } else { - // just return all account ids and keys - local_accounts_and_keys - } + #[derive(codec::Encode, codec::Decode)] + struct SimplePayload { + pub public: UintAuthorityId, + pub data: Vec<u8>, } - fn can_sign_with(accounts: Option<impl IntoIterator<Item = T::AccountId>>) -> bool { - // early exit if we care about any account. - if accounts.is_none() { - !S::all().is_empty() - } else { - !Self::find_local_keys(accounts).is_empty() + 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 + } + ); + + // 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 + } + ); + + // 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 + } + ); + + // 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 + } + ); + + // 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); + }); + } + } diff --git a/substrate/primitives/application-crypto/src/lib.rs b/substrate/primitives/application-crypto/src/lib.rs index 79572eb49d1e60d38a06b13356481c25a23803cf..8bad474ede81e1281648953b035be1dbd1cc63ee 100644 --- a/substrate/primitives/application-crypto/src/lib.rs +++ b/substrate/primitives/application-crypto/src/lib.rs @@ -25,7 +25,7 @@ pub use sp_core::{self, crypto::{CryptoType, CryptoTypePublicPair, Public, Deriv #[doc(hidden)] #[cfg(feature = "full_crypto")] pub use sp_core::crypto::{SecretStringError, DeriveJunction, Ss58Codec, Pair}; -pub use sp_core::crypto::{CryptoTypeId, KeyTypeId, key_types}; +pub use sp_core::crypto::{KeyTypeId, key_types}; #[doc(hidden)] pub use codec; diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index 4aae575b2c15a7ff7281cb35659d60b60490d65b..4eb96ff960bcc72d65fd12d0567bd7206945d131 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -320,29 +320,10 @@ mod tests { use super::*; use sp_io::hashing::blake2_256; use crate::codec::{Encode, Decode}; - use crate::traits::{SignedExtension, IdentifyAccount, IdentityLookup}; - use serde::{Serialize, Deserialize}; + use crate::traits::{SignedExtension, IdentityLookup}; + use crate::testing::TestSignature as TestSig; type TestContext = IdentityLookup<u64>; - - #[derive(Eq, PartialEq, Clone, Copy, Debug, Serialize, Deserialize, Encode, Decode)] - pub struct TestSigner(pub u64); - impl From<u64> for TestSigner { fn from(x: u64) -> Self { Self(x) } } - impl From<TestSigner> for u64 { fn from(x: TestSigner) -> Self { x.0 } } - impl IdentifyAccount for TestSigner { - type AccountId = u64; - fn into_account(self) -> u64 { self.into() } - } - - #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)] - struct TestSig(u64, Vec<u8>); - impl traits::Verify for TestSig { - type Signer = TestSigner; - fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &u64) -> bool { - signer == &self.0 && msg.get() == &self.1[..] - } - } - type TestAccountId = u64; type TestCall = Vec<u8>; diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs index 0f609b005001858a4efbe594b7da97b16728e257..a5b3e71edcde3d9f1d258730f58ba3749daf2f46 100644 --- a/substrate/primitives/runtime/src/lib.rs +++ b/substrate/primitives/runtime/src/lib.rs @@ -182,18 +182,39 @@ impl From<ed25519::Signature> for MultiSignature { } } +impl TryFrom<MultiSignature> for ed25519::Signature { + type Error = (); + fn try_from(m: MultiSignature) -> Result<Self, Self::Error> { + if let MultiSignature::Ed25519(x) = m { Ok(x) } else { Err(()) } + } +} + impl From<sr25519::Signature> for MultiSignature { fn from(x: sr25519::Signature) -> Self { MultiSignature::Sr25519(x) } } +impl TryFrom<MultiSignature> for sr25519::Signature { + type Error = (); + fn try_from(m: MultiSignature) -> Result<Self, Self::Error> { + if let MultiSignature::Sr25519(x) = m { Ok(x) } else { Err(()) } + } +} + impl From<ecdsa::Signature> for MultiSignature { fn from(x: ecdsa::Signature) -> Self { MultiSignature::Ecdsa(x) } } +impl TryFrom<MultiSignature> for ecdsa::Signature { + type Error = (); + fn try_from(m: MultiSignature) -> Result<Self, Self::Error> { + if let MultiSignature::Ecdsa(x) = m { Ok(x) } else { Err(()) } + } +} + impl Default for MultiSignature { fn default() -> Self { MultiSignature::Ed25519(Default::default()) diff --git a/substrate/primitives/runtime/src/testing.rs b/substrate/primitives/runtime/src/testing.rs index 1414a5f4f0a753d02abc6ed2c25c574bfbe531f7..40b4e23e3fa0a6b94bb340a1d640f6461c543ca5 100644 --- a/substrate/primitives/runtime/src/testing.rs +++ b/substrate/primitives/runtime/src/testing.rs @@ -17,7 +17,7 @@ //! Testing utilities. use serde::{Serialize, Serializer, Deserialize, de::Error as DeError, Deserializer}; -use std::{fmt::Debug, ops::Deref, fmt, cell::RefCell}; +use std::{fmt::{self, Debug}, ops::Deref, cell::RefCell}; use crate::codec::{Codec, Encode, Decode}; use crate::traits::{ self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, @@ -29,7 +29,12 @@ pub use sp_core::{H256, sr25519}; use sp_core::{crypto::{CryptoType, Dummy, key_types, Public}, U256}; use crate::transaction_validity::{TransactionValidity, TransactionValidityError, TransactionSource}; -/// Authority Id +/// A dummy type which can be used instead of regular cryptographic primitives. +/// +/// 1. Wraps a `u64` `AccountId` and is able to `IdentifyAccount`. +/// 2. Can be converted to any `Public` key. +/// 3. Implements `RuntimeAppPublic` so it can be used instead of regular application-specific +/// crypto. #[derive(Default, PartialEq, Eq, Clone, Encode, Decode, Debug, Hash, Serialize, Deserialize, PartialOrd, Ord)] pub struct UintAuthorityId(pub u64); @@ -82,7 +87,7 @@ impl UintAuthorityId { impl sp_application_crypto::RuntimeAppPublic for UintAuthorityId { const ID: KeyTypeId = key_types::DUMMY; - type Signature = u64; + type Signature = TestSignature; fn all() -> Vec<Self> { ALL_KEYS.with(|l| l.borrow().clone()) @@ -94,25 +99,11 @@ impl sp_application_crypto::RuntimeAppPublic for UintAuthorityId { } fn sign<M: AsRef<[u8]>>(&self, msg: &M) -> Option<Self::Signature> { - let mut signature = [0u8; 8]; - msg.as_ref().iter() - .chain(std::iter::repeat(&42u8)) - .take(8) - .enumerate() - .for_each(|(i, v)| { signature[i] = *v; }); - - Some(u64::from_le_bytes(signature)) + Some(TestSignature(self.0, msg.as_ref().to_vec())) } fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool { - let mut msg_signature = [0u8; 8]; - msg.as_ref().iter() - .chain(std::iter::repeat(&42)) - .take(8) - .enumerate() - .for_each(|(i, v)| { msg_signature[i] = *v; }); - - u64::from_le_bytes(msg_signature) == *signature + traits::Verify::verify(signature, msg.as_ref(), &self.0) } fn to_raw_vec(&self) -> Vec<u8> { @@ -140,6 +131,26 @@ impl crate::BoundToRuntimeAppPublic for UintAuthorityId { type Public = Self; } +impl traits::IdentifyAccount for UintAuthorityId { + type AccountId = u64; + + fn into_account(self) -> Self::AccountId { + self.0 + } +} + +/// A dummy signature type, to match `UintAuthorityId`. +#[derive(Eq, PartialEq, Clone, Debug, Hash, Serialize, Deserialize, Encode, Decode)] +pub struct TestSignature(pub u64, pub Vec<u8>); + +impl traits::Verify for TestSignature { + type Signer = UintAuthorityId; + + fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &u64) -> bool { + signer == &self.0 && msg.get() == &self.1[..] + } +} + /// Digest item pub type DigestItem = generic::DigestItem<H256>; @@ -332,6 +343,7 @@ impl<Call: Codec + Sync + Send, Context, Extra> Checkable<Context> for TestXt<Ca type Checked = Self; fn check(self, _: &Context) -> Result<Self::Checked, TransactionValidityError> { Ok(self) } } + impl<Call: Codec + Sync + Send, Extra> traits::Extrinsic for TestXt<Call, Extra> { type Call = Call; type SignaturePayload = (u64, Extra);