// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! # Offchain Worker Example Module
//!
//! 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.
//!
//! - \[`pallet_example_offchain_worker::Trait`](./trait.Trait.html)
//! - \[`Call`](./enum.Call.html)
//! - \[`Module`](./struct.Module.html)
//!
//!
//! \## 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 unsigned 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 unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
//! one unsigned transaction floating in the network.
#![cfg_attr(not(feature = "std"), no_std)]
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::{
offchain::{http, Duration, storage::StorageValueRef},
traits::Zero,
transaction_validity::{
InvalidTransaction, ValidTransaction, TransactionValidity, TransactionSource,
TransactionPriority,
},
};
use sp_std::vec::Vec;
use lite_json::json::JsonValue;
#[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_runtime::app_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, KEY_TYPE);
}
/// This pallet's configuration trait
pub trait Trait: frame_system::Trait {
/// The type to sign and submit transactions.
type SubmitSignedTransaction:
offchain::SubmitSignedTransaction::Call>;
/// The type to submit unsigned transactions.
type SubmitUnsignedTransaction:
offchain::SubmitUnsignedTransaction::Call>;
/// The overarching event type.
type Event: From> + Into<::Event>;
/// The overarching dispatch call type.
type Call: From>;
// 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.
type GracePeriod: Get;
/// Number of blocks of cooldown after unsigned transaction is included.
///
/// This ensures that we only accept unsigned transactions once, every `UnsignedInterval` blocks.
type UnsignedInterval: Get;
/// A configuration for base priority of unsigned transactions.
///
/// This is exposed so that it can be tuned for particular runtime, when
/// multiple pallets send unsigned transactions.
type UnsignedPriority: Get;
}
decl_storage! {
trait Store for Module as ExampleOffchainWorker {
/// A vector of recently submitted prices.
///
/// This is used to calculate average price, should have bounded size.
Prices get(fn prices): Vec;
/// Defines the block when next unsigned transaction will be accepted.
///
/// To prevent spam of unsigned (and unpayed!) transactions on the network,
/// we only allow one transaction every `T::UnsignedInterval` blocks.
/// This storage entry defines when new transaction is going to be accepted.
NextUnsignedAt get(fn next_unsigned_at): T::BlockNumber;
}
}
decl_event!(
/// Events generated by the module.
pub enum Event where AccountId = ::AccountId {
/// Event generated when new price is accepted to contribute to the average.
NewPrice(u32, AccountId),
}
);
decl_module! {
/// A public part of the pallet.
pub struct Module for enum Call where origin: T::Origin {
fn deposit_event() = default;
/// 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
/// excesive 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.
#[weight = SimpleDispatchInfo::FixedNormal(MINIMUM_WEIGHT)]
pub fn submit_price(origin, price: u32) -> DispatchResult {
// Retrieve sender of the transaction.
let who = ensure_signed(origin)?;
// Add the price to the on-chain list.
Self::add_price(who, price);
Ok(())
}
/// Submit new price to the list via unsigned 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::UnsignedInterval` blocks.
/// Transactions that call that function are de-duplicated on the pool level
/// via `validate_unsigned` implementation and also are rendered invalid if
/// the function has already been called in current "session".
///
/// It's important to specify `weight` for unsigned 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.
#[weight = SimpleDispatchInfo::FixedNormal(MINIMUM_WEIGHT)]
pub fn submit_price_unsigned(origin, _block_number: T::BlockNumber, price: u32)
-> 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);
// now increment the block number at which we expect next unsigned transaction.
let current_block = >::block_number();
>::put(current_block + T::UnsignedInterval::get());
Ok(())
}
/// Offchain Worker entry point.
///
/// By implementing `fn offchain_worker` within `decl_module!` you declare a new offchain
/// worker.
/// This function will be called when the node is fully synced and a new best block is
/// succesfuly 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: T::BlockNumber) {
// It's a good idea to add logs to your offchain workers.
// Using the `frame_support::debug` module you have access to the same API exposed by
// the `log` crate.
// Note that having logs compiled to WASM may cause the size of the blob to increase
// significantly. You can use `RuntimeDebug` custom derive to hide details of the types
// in WASM or use `debug::native` namespace to produce logs only when the worker is
// running natively.
debug::native::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 - 1.into());
debug::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();
debug::debug!("Current price: {:?}", average);
// For this example we are going to send both signed and unsigned 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::Unsigned => Self::fetch_price_and_send_unsigned(block_number),
TransactionType::None => Ok(()),
};
if let Err(e) = res {
debug::error!("Error: {}", e);
}
}
}
}
enum TransactionType {
Signed,
Unsigned,
None,
}
/// Most of the functions are moved outside of the `decl_module!` macro.
///
/// This greatly helps with error messages, as the ones inside the macro
/// can sometimes be hard to debug.
impl Module {
/// 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: T::BlockNumber) -> 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: Option