// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: MIT-0 // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! //! # Offchain Worker Example Pallet //! //! The Offchain Worker Example: A simple pallet demonstrating //! concepts, APIs and structures common to most offchain workers. //! //! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's //! documentation. //! //! - [`Config`] //! - [`Call`] //! - [`Pallet`] //! //! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to //! be used in production.** //! //! ## Overview //! //! In this example we are going to build a very simplistic, naive and definitely NOT //! production-ready oracle for BTC/USD price. //! Offchain Worker (OCW) will be triggered after every block, fetch the current price //! and prepare either signed or general transaction to feed the result back on chain. //! The on-chain logic will simply aggregate the results and store last `64` values to compute //! the average price. //! Additional logic in OCW is put in place to prevent spamming the network with both signed //! and general transactions. The pallet uses the `#[pallet::authorize]` attribute to validate //! general transactions, ensuring that only one general transaction can be accepted per //! `AuthorizedTxInterval` blocks. #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; use alloc::vec::Vec; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::{traits::Get, weights::Weight}; use frame_system::{ self as system, offchain::{ AppCrypto, CreateAuthorizedTransaction, CreateSignedTransaction, SendSignedTransaction, SignedPayload, Signer, SigningTypes, SubmitTransaction, }, pallet_prelude::BlockNumberFor, }; use lite_json::json::JsonValue; use sp_core::crypto::KeyTypeId; use sp_runtime::{ offchain::{ http, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, Duration, }, traits::Zero, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityWithRefund, ValidTransaction, }, Debug, }; #[cfg(test)] mod tests; /// Defines application identifier for crypto keys of this module. /// /// Every module that deals with signatures needs to declare its unique identifier for /// its crypto keys. /// When offchain worker is signing transactions it's going to request keys of type /// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction. /// The keys can be inserted manually via RPC (see `author_insertKey`). pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!"); /// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers. /// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment /// the types with this pallet-specific identifier. pub mod crypto { use super::KEY_TYPE; use sp_core::sr25519::Signature as Sr25519Signature; use sp_runtime::{ app_crypto::{app_crypto, sr25519}, traits::Verify, MultiSignature, MultiSigner, }; app_crypto!(sr25519, KEY_TYPE); pub struct TestAuthId; impl frame_system::offchain::AppCrypto for TestAuthId { type RuntimeAppPublic = Public; type GenericSignature = sp_core::sr25519::Signature; type GenericPublic = sp_core::sr25519::Public; } // implemented for mock runtime in test impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> for TestAuthId { type RuntimeAppPublic = Public; type GenericSignature = sp_core::sr25519::Signature; type GenericPublic = sp_core::sr25519::Public; } } pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; /// This pallet's configuration trait /// /// # Requirements /// /// This pallet requires `frame_system::AuthorizeCall` to be included in the runtime's /// transaction extension pipeline. /// The integrity test will verify this at runtime. #[pallet::config] pub trait Config: CreateSignedTransaction> + CreateAuthorizedTransaction> + frame_system::Config { /// The identifier type for an offchain worker. type AuthorityId: AppCrypto; // Configuration parameters /// A grace period after we send transaction. /// /// To avoid sending too many transactions, we only attempt to send one /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate /// sending between distinct runs of this offchain worker. #[pallet::constant] type GracePeriod: Get>; /// Number of blocks of cooldown after an authorized transaction is included. /// /// This ensures that we only accept authorized transactions once, every /// `AuthorizedTxInterval` blocks. #[pallet::constant] type AuthorizedTxInterval: Get>; /// A configuration for base priority of authorized transactions. /// /// This is exposed so that it can be tuned for particular runtime, when /// multiple pallets send authorized transactions. #[pallet::constant] type AuthorizedTxPriority: Get; /// Maximum number of prices. #[pallet::constant] type MaxPrices: Get; } #[pallet::pallet] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { /// Integrity test to ensure AuthorizeCall is configured in the runtime. /// /// This pallet uses the `#[pallet::authorize]` attribute on calls that require /// authorization validation. For these calls to work properly, the runtime must /// include `frame_system::AuthorizeCall` as part of its transaction extension pipeline. /// /// This test validates that the Block type's Extrinsic includes the necessary /// transaction extension structure by checking the type name. fn integrity_test() { use sp_runtime::traits::Block as BlockT; // Get the full type name of the Block's Extrinsic type let extrinsic_type_name = core::any::type_name::<::Extrinsic>(); let extension_type_name = core::any::type_name::>(); // Verify that AuthorizeCall is present in the extrinsic type // The extrinsic should contain the AuthorizeCall extension in its structure assert!( extrinsic_type_name.contains(extension_type_name), "The runtime must include `frame_system::AuthorizeCall` in its transaction extension \ pipeline for this pallet to work correctly. The pallet uses `#[pallet::authorize]` \ which requires AuthorizeCall to validate authorized transactions. \ Current extrinsic type: {}", extrinsic_type_name ); } /// Offchain Worker entry point. /// /// By implementing `fn offchain_worker` you declare a new offchain worker. /// This function will be called when the node is fully synced and a new best block is /// successfully imported. /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs), /// so the code should be able to handle that. /// You can use `Local Storage` API to coordinate runs of the worker. fn offchain_worker(block_number: BlockNumberFor) { // Note that having logs compiled to WASM may cause the size of the blob to increase // significantly. You can use `Debug` custom derive to hide details of the types // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable // all logging and thus, remove any logging from the WASM. log::info!("Hello World from offchain workers!"); // Since off-chain workers are just part of the runtime code, they have direct access // to the storage and other included pallets. // // We can easily import `frame_system` and retrieve a block hash of the parent block. let parent_hash = >::block_hash(block_number - 1u32.into()); log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash); // It's a good practice to keep `fn offchain_worker()` function minimal, and move most // of the code to separate `impl` block. // Here we call a helper function to calculate current average price. // This function reads storage entries of the current state. let average: Option = Self::average_price(); log::debug!("Current price: {:?}", average); // For this example we are going to send both signed and general transactions // depending on the block number. // Usually it's enough to choose one or the other. let should_send = Self::choose_transaction_type(block_number); let res = match should_send { TransactionType::Signed => Self::fetch_price_and_send_signed(), TransactionType::AuthorizedForAny => { Self::fetch_price_and_send_authorized_tx_for_any_account(block_number) }, TransactionType::AuthorizedForAll => { Self::fetch_price_and_send_authorized_tx_for_all_accounts(block_number) }, TransactionType::Raw => Self::fetch_price_and_send_raw_authorized(block_number), TransactionType::None => Ok(()), }; if let Err(e) = res { log::error!("Error: {}", e); } } } /// A public part of the pallet. #[pallet::call] impl Pallet { /// Submit new price to the list. /// /// This method is a public function of the module and can be called from within /// a transaction. It appends given `price` to current list of prices. /// In our example the `offchain worker` will create, sign & submit a transaction that /// calls this function passing the price. /// /// The transaction needs to be signed (see `ensure_signed`) check, so that the caller /// pays a fee to execute it. /// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting /// excessive transactions, but note that it doesn't ensure the price oracle is actually /// working and receives (and provides) meaningful data. /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(0)] #[pallet::weight({0})] pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. let who = ensure_signed(origin)?; // Add the price to the on-chain list. Self::add_price(Some(who), price); Ok(().into()) } /// Submit new price to the list via general transaction. /// /// Works exactly like the `submit_price` function, but since we allow sending the /// transaction without a signature, and hence without paying any fees, /// we need a way to make sure that only some transactions are accepted. /// This function can be called only once every `T::AuthorizedTxInterval` blocks. /// /// Transactions are de-duplicated on the pool level via the `provides` tag in the /// validation logic (`validate_transaction_parameters`), which returns /// `next_authorized_at`. This ensures only one transaction per interval can be included /// in a block. /// /// It's important to specify `weight` for authorized calls as well, because even though /// they don't charge fees, we still don't want a single block to contain unlimited /// number of such transactions. /// /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(1)] #[pallet::weight({0})] // Minimal weight since validation is lightweight. But in real-world scenarios, this should // be benchmarked. #[pallet::weight_of_authorize(Weight::from_parts(5_000, 0))] #[pallet::authorize(| _source: TransactionSource, block_number: &BlockNumberFor, new_price: &u32, | -> TransactionValidityWithRefund { Pallet::::validate_transaction_parameters(block_number, new_price) .map(|v| (v, /* no refund */ Weight::zero())) })] pub fn submit_price_authorized( origin: OriginFor, _block_number: BlockNumberFor, price: u32, ) -> DispatchResultWithPostInfo { ensure_authorized(origin)?; // Add the price to the on-chain list, but mark it as coming from an empty address. Self::add_price(None, price); // now increment the block number at which we expect next authorized transaction. let current_block = >::block_number(); >::put(current_block + T::AuthorizedTxInterval::get()); Ok(().into()) } #[pallet::call_index(2)] #[pallet::weight({0})] // Minimal weight since validation is lightweight. But in real-world scenarios, this should // be benchmarked. #[pallet::weight_of_authorize(Weight::from_parts(5_000, 0))] #[pallet::authorize(| _source: TransactionSource, price_payload: &PricePayload>, signature: &T::Signature, | -> TransactionValidityWithRefund { let signature_valid = SignedPayload::::verify::(price_payload, signature.clone()); if !signature_valid { return Err(InvalidTransaction::BadProof.into()) } Pallet::::validate_transaction_parameters(&price_payload.block_number, &price_payload.price) .map(|v| (v, /* no refund */ Weight::zero())) })] pub fn submit_price_authorized_with_signed_payload( origin: OriginFor, price_payload: PricePayload>, _signature: T::Signature, ) -> DispatchResultWithPostInfo { ensure_authorized(origin)?; // Add the price to the on-chain list, but mark it as coming from an empty address. Self::add_price(None, price_payload.price); // now increment the block number at which we expect next authorized transaction. let current_block = >::block_number(); >::put(current_block + T::AuthorizedTxInterval::get()); Ok(().into()) } } /// Events for the pallet. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event generated when new price is accepted to contribute to the average. NewPrice { price: u32, maybe_who: Option }, } /// A vector of recently submitted prices. /// /// This is used to calculate average price, should have bounded size. #[pallet::storage] pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>; /// Defines the block when next authorized transaction will be accepted. /// /// To prevent spam of authorized (and unpaid!) transactions on the network, /// we only allow one transaction every `T::AuthorizedTxInterval` blocks. /// This storage entry defines when new transaction is going to be accepted. #[pallet::storage] pub(super) type NextAuthorizedAt = StorageValue<_, BlockNumberFor, ValueQuery>; } /// Payload used by this example crate to hold price /// data required to submit a transaction. #[derive( Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, scale_info::TypeInfo, )] pub struct PricePayload { block_number: BlockNumber, price: u32, public: Public, } impl SignedPayload for PricePayload> { fn public(&self) -> T::Public { self.public.clone() } } enum TransactionType { Signed, AuthorizedForAny, AuthorizedForAll, Raw, None, } impl Pallet { /// Chooses which transaction type to send. /// /// This function serves mostly to showcase `StorageValue` helper /// and local storage usage. /// /// Returns a type of transaction that should be produced in current run. fn choose_transaction_type(block_number: BlockNumberFor) -> TransactionType { /// A friendlier name for the error that is going to be returned in case we are in the grace /// period. const RECENTLY_SENT: () = (); // Start off by creating a reference to Local Storage value. // Since the local storage is common for all offchain workers, it's a good practice // to prepend your entry with the module name. let val = StorageValueRef::persistent(b"example_ocw::last_send"); // The Local Storage is persisted and shared between runs of the offchain workers, // and offchain workers may run concurrently. We can use the `mutate` function, to // write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set` // low-level method of local storage API, which means that only one worker // will be able to "acquire a lock" and send a transaction if multiple workers // happen to be executed concurrently. let res = val.mutate(|last_send: Result>, StorageRetrievalError>| { match last_send { // If we already have a value in storage and the block number is recent enough // we avoid sending another transaction at this time. Ok(Some(block)) if block_number < block + T::GracePeriod::get() => { Err(RECENTLY_SENT) }, // In every other case we attempt to acquire the lock and send a transaction. _ => Ok(block_number), } }); // The result of `mutate` call will give us a nested `Result` type. // The first one matches the return of the closure passed to `mutate`, i.e. // if we return `Err` from the closure, we get an `Err` here. // In case we return `Ok`, here we will have another (inner) `Result` that indicates // if the value has been set to the storage correctly - i.e. if it wasn't // written to in the meantime. match res { // The value has been set correctly, which means we can safely send a transaction now. Ok(block_number) => { // We will send different transactions based on a random number. // Note that this logic doesn't really guarantee that the transactions will be sent // in an alternating fashion (i.e. fairly distributed). Depending on the execution // order and lock acquisition, we may end up for instance sending two `Signed` // 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 transaction_type = block_number % 4u32.into(); if transaction_type == Zero::zero() { TransactionType::Signed } else if transaction_type == BlockNumberFor::::from(1u32) { TransactionType::AuthorizedForAny } else if transaction_type == BlockNumberFor::::from(2u32) { TransactionType::AuthorizedForAll } else { TransactionType::Raw } }, // We are in the grace period, we should not send a transaction this time. Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None, // We wanted to send a transaction, but failed to write the block number (acquire a // lock). This indicates that another offchain worker that was running concurrently // most likely executed the same logic and succeeded at writing to storage. // Thus we don't really want to send the transaction, knowing that the other run // already did. Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None, } } /// A helper function to fetch the price and send signed transaction. fn fetch_price_and_send_signed() -> Result<(), &'static str> { let signer = Signer::::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")?; // 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 = 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(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price), Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), } } Ok(()) } /// A helper function to fetch the price and send a raw authorized transaction. fn fetch_price_and_send_raw_authorized( block_number: BlockNumberFor, ) -> Result<(), &'static str> { // Make sure we don't fetch the price if the authorized transaction is going to be rejected // anyway. let next_authorized_at = NextAuthorizedAt::::get(); if next_authorized_at > block_number { return Err("Too early to send authorized 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_authorized` 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_authorized { 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 a general transaction / unsigned payload (raw) // // By default general transactions start the transaction extension pipeline with the origin // `None`. We define custom validation logic using the `#[pallet::authorize]` attribute. // This custom validation is executed by the transaction extension `AuthorizeCall`, which // will change the origin to `frame_system::Origin::Authorized` if validation succeeds. // Note that it's EXTREMELY important to carefully implement custom validation logic, as // any mistakes can lead to opening DoS or spam attack vectors. See validation logic docs // for more details. // let xt = T::create_authorized_transaction(call.into()); SubmitTransaction::>::submit_transaction(xt) .map_err(|()| "Unable to submit transaction.")?; Ok(()) } /// A helper function to fetch the price, sign payload and send an authorized transaction fn fetch_price_and_send_authorized_tx_for_any_account( block_number: BlockNumberFor, ) -> Result<(), &'static str> { // Make sure we don't fetch the price if the authorized transaction is going to be rejected // anyway. let next_authorized_at = NextAuthorizedAt::::get(); if next_authorized_at > block_number { return Err("Too early to send authorized 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")?; // Sign using any account and create an authorized transaction let signer = Signer::::any_account(); // Get the first available account let account = signer.accounts_from_keys().next().ok_or("No local accounts available.")?; // Create the payload to sign let payload = PricePayload { price, block_number, public: account.public.clone() }; // Sign the payload let signature = ::Public, BlockNumberFor, > as SignedPayload>::sign::(&payload) .ok_or("Failed to sign payload")?; // Create the call with the signed payload let call = Call::submit_price_authorized_with_signed_payload { price_payload: payload, signature }; // Create an authorized transaction and submit it let xt = T::create_authorized_transaction(call.into()); SubmitTransaction::>::submit_transaction(xt) .map_err(|()| "Unable to submit transaction")?; Ok(()) } /// A helper function to fetch the price, sign payload and send an authorized transaction fn fetch_price_and_send_authorized_tx_for_all_accounts( block_number: BlockNumberFor, ) -> Result<(), &'static str> { // Make sure we don't fetch the price if the authorized transaction is going to be rejected // anyway. let next_authorized_at = NextAuthorizedAt::::get(); if next_authorized_at > block_number { return Err("Too early to send authorized 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")?; // -- Sign using all accounts and create authorized transactions let signer = Signer::::all_accounts(); // Iterate over all available accounts for account in signer.accounts_from_keys() { // Create the payload to sign let payload = PricePayload { price, block_number, public: account.public.clone() }; // Sign the payload let signature = ::Public, BlockNumberFor> as SignedPayload< T, >>::sign::(&payload) .ok_or("Failed to sign payload")?; // Create the call with the signed payload let call = Call::submit_price_authorized_with_signed_payload { price_payload: payload, signature, }; // Create an authorized transaction and submit it let xt = T::create_authorized_transaction(call.into()); SubmitTransaction::>::submit_transaction(xt) .map_err(|()| "Unable to submit transaction")?; } Ok(()) } /// Fetch current price and return the result in cents. fn fetch_price() -> Result { // We want to keep the offchain worker execution time reasonable, so we set a hard-coded // deadline to 2s to complete the external call. // You can also wait indefinitely for the response, however you may still get a timeout // coming from the host machine. let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); // Initiate an external HTTP GET request. // This is using high-level wrappers from `sp_runtime`, for the low-level calls that // you can find in `sp_io`. The API is trying to be similar to `request`, but // since we are running in a custom WASM execution environment we can't simply // import the library here. let request = http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD"); // We set the deadline for sending of the request, note that awaiting response can // have a separate deadline. Next we send the request, before that it's also possible // to alter request headers or stream body content in case of non-GET requests. let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; // The request is already being processed by the host, we are free to do anything // else in the worker (we can send multiple concurrent requests too). // At some point however we probably want to check the response though, // so we can block current thread and wait for it to finish. // Note that since the request is being driven by the host, we don't have to wait // for the request to have it complete, we will just not read the response. let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; // Let's check the status code before we proceed to reading the response. if response.code != 200 { log::warn!("Unexpected status code: {}", response.code); return Err(http::Error::Unknown); } // Next we want to fully read the response body and collect it to a vector of bytes. // Note that the return object allows you to read the body in chunks as well // with a way to control the deadline. let body = response.body().collect::>(); // Create a str slice from the body. let body_str = alloc::str::from_utf8(&body).map_err(|_| { log::warn!("No UTF8 body"); http::Error::Unknown })?; let price = match Self::parse_price(body_str) { Some(price) => Ok(price), None => { log::warn!("Unable to extract price from the response: {:?}", body_str); Err(http::Error::Unknown) }, }?; log::warn!("Got price: {} cents", price); Ok(price) } /// Parse the price from the given JSON string using `lite-json`. /// /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. fn parse_price(price_str: &str) -> Option { let val = lite_json::parse_json(price_str); let price = match val.ok()? { JsonValue::Object(obj) => { let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?; match v { JsonValue::Number(number) => number, _ => return None, } }, _ => return None, }; let exp = price.fraction_length.saturating_sub(2); Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) } /// Add new price to the list. fn add_price(maybe_who: Option, price: u32) { log::info!("Adding to the average: {}", price); >::mutate(|prices| { if prices.try_push(price).is_err() { prices[(price % T::MaxPrices::get()) as usize] = price; } }); let average = Self::average_price() .expect("The average is not empty, because it was just mutated; qed"); log::info!("Current average price is: {}", average); // here we are raising the NewPrice event Self::deposit_event(Event::NewPrice { price, maybe_who }); } /// Calculate current average price. fn average_price() -> Option { let prices = Prices::::get(); if prices.is_empty() { None } else { Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32) } } fn validate_transaction_parameters( block_number: &BlockNumberFor, new_price: &u32, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. let next_authorized_at = NextAuthorizedAt::::get(); if &next_authorized_at > block_number { return InvalidTransaction::Stale.into(); } // Let's make sure to reject transactions from the future. let current_block = >::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::AuthorizedTxPriority::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_authorized_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_authorized_at`. This makes // sure only one transaction produced after `next_authorized_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_authorized_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() } }