mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 21:41:12 +00:00
Offchain signing (#5182)
* New approach to offchain signing. * Use in im-online * Rewrite to use Account<T> * DRY signing. * Implement send_raw_unsigned_transaction * WiP * Expunge LocalCall * Expunge LocalCall * Fix compilation. * Solve call. * Make it compile again. * Finalize implementation. * Change CreateTransaction * Clear CreateTransaction. * Add price payload * Send raw transaction * Submit signed payload / unsigned transaction (WIP) * Supertrait requirements on T::Signature * Validate signature of payload on an unsigned transaction * Fix encoding - part 1 * Make it compile. * Fix compilation of unsigned validator. * Pass price payload to the transaction * Make block number part of the signed payload * Send signed transaction * Implement all_accounts, any_account * Fix formatting * Implement submit_transaction * Submit signed transaction (ForAll, ForAny) * Fix formatting * Implement CreateSignedTransaction * Move sign and verify to AppCrypto * Sign transaction * Call `use_encoded` * Remove SubmitAndSignTransaction * Implement runtime using new SigningTypes * Adapt offchain example to changes * Fix im-online pallet * Quick fix: rename AuthorityId2 * Fix offchain example tests * Add a comment on why keystore is required in unsigned transaction test * Use UintAuthorityId instead of u64 * WIP * Remove IdentifyAccount from UintAuthorityId * Implement PublicWrapper type * Fix im-online tests * Fix runtime test * Bump spec version * Fix executor tests * Rename ImOnlineAuthId -> ImOnlineAuthorityId and formatting * Fix merge * Documentation * Revert u64 -> UintAuthorityId conversion * Fix string errors * Document public members in offchain module * Introduce SubmitTransaction * Update pallets to use SubmitTransaction * WIP * Use SubmitTransaction in offchain * Use `submit_unsigned_transaction` * Fix tests * Update docs * Remove SigningTypes requirement from `SendTransactionTypes` * Fix tests * Update frame/system/src/offchain.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/system/src/offchain.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/example-offchain-worker/src/tests.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/system/src/offchain.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/system/src/offchain.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Remove leftover from previous iterations * Change enum to struct * Remove public * Move mock to node/executor/tests * Cleanup test-helpers * Make `application-crypto` `std` feature internal The macros should not generate code that requires that the calling crate has a feature with the name `std` defined. * Revert cargo lock update * Use TestAuthorityId from common * Restore members of account to public * Tidy up imports * Fix benchmarking pallet * Add tests demonstrating ForAll, ForAny on signer * Move definition of AppCrypto in example-offchain-worker from tests to mod::crypto * Cleanup stray comment * Fix ValidTransaction * Re-fix CreateSignedTransaction * Address PR feedback * Add can_sign method to signer * Propagate error * Improve documentation * Fix vec! macro not available * Document SendTransactiontypes * Add some docs. * Split signing examples * Add tests for signing examples * WIP can_sign - PR feedback * WIP * Split for_any / for_all into different calls * Verify payload and signature in test * Fix can_sign implementation * Fix impl_version * Import Box from sp_std * Create issues for TODOs * Ignore doctest. * Add test directly to system. Adjust UintTypes. * Add some tests to account filtering. * Remove code samples and point to example offchain worker * Fix doc links * Fix im-online tests using signatures. Co-authored-by: Tomasz Drwięga <tomasz@parity.io> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Bastian Köcher <git@kchr.de>
This commit is contained in:
@@ -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();
|
||||
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();
|
||||
}
|
||||
// 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 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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
>;
|
||||
type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
|
||||
|
||||
impl frame_system::offchain::CreateTransaction<Test, Extrinsic> for Test {
|
||||
type Public = sp_core::sr25519::Public;
|
||||
type Signature = sp_core::sr25519::Signature;
|
||||
impl frame_system::offchain::SigningTypes for Test {
|
||||
type Public = <Signature as Verify>::Signer;
|
||||
type Signature = 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)> {
|
||||
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());
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(), ());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user