Newer
Older
//! # Oracle
//! A module to allow oracle operators to feed external data.
//!
//! - [`Trait`](./trait.Trait.html)
//! - [`Call`](./enum.Call.html)
//! - [`Module`](./struct.Module.html)
//!
//! ## Overview
//!
//! This module exposes capabilities for oracle operators to feed external
//! offchain data. The raw values can be combined to provide an aggregated
//! value.
//! The data are submitted with unsigned transaction so it does not incure a
//! transaction fee. However the data still needs to be signed by a session key
//! to prevent spam and ensure the integrity.
// Disable the following two lints since they originate from an external macro (namely decl_storage)
#![allow(clippy::string_lit_as_bytes)]
mod default_combine_data;
mod mock;
mod tests;
use codec::Encode;
pub use default_combine_data::DefaultCombineData;
decl_error, decl_event, decl_module, decl_storage, ensure,
IterableStorageMap, Parameter,
use frame_system::{ensure_none, ensure_root, ensure_signed};
pub use orml_traits::{CombineData, DataFeeder, DataProvider, DataProviderExtended, OnNewData, TimestampedValue};
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, ValidTransaction,
use sp_std::{convert::TryInto, prelude::*, vec};
use sp_application_crypto::{KeyTypeId, RuntimeAppPublic};
pub const ORACLE: KeyTypeId = KeyTypeId(*b"orac");
mod app_sr25519 {
use sp_application_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, super::ORACLE);
}
sp_application_crypto::with_pair! {
/// An oracle keypair using sr25519 as its crypto.
pub type AuthorityPair = app_sr25519::Pair;
}
/// An oracle signature using sr25519 as its crypto.
pub type AuthoritySignature = app_sr25519::Signature;
/// An oracle identifier using sr25519 as its crypto.
pub type AuthorityId = app_sr25519::Public;
type MomentOf<T, I> = <<T as Trait<I>>::Time as Time>::Moment;
pub type TimestampedValueOf<T, I> = TimestampedValue<<T as Trait<I>>::OracleValue, MomentOf<T, I>>;
/// Number of blocks before an unconfirmed unsigned transaction expires.
pub const EXTRINSIC_LONGEVITY: u32 = 100;
pub trait Trait<I: Instance>: frame_system::Trait {
type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
/// Provide the implementation to combine raw values to produce aggregated
/// value
type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self, I>>;
type OracleKey: Parameter + Member;
type OracleValue: Parameter + Member + Ord;
/// 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<TransactionPriority>;
/// The identifier type for an authority.
type AuthorityId: Member + Parameter + RuntimeAppPublic + Default + Ord;
trait Store for Module<T: Trait<I>, I: Instance> as Oracle {
pub RawValues get(fn raw_values): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T, I>>;
/// True if Self::values(key) is up to date, otherwise the value is stale
pub IsUpdated get(fn is_updated): map hasher(twox_64_concat) T::OracleKey => bool;
/// Combined value, may not be up to date
pub Values get(fn values): map hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T, I>>;
/// If an oracle operator has feed a value in this block
HasDispatched: OrderedSet<T::AccountId>;
// TODO: this shouldn't be required https://github.com/paritytech/substrate/issues/6041
/// The current members of the collective. This is stored sorted (just by value).
pub Members get(fn members) config(): OrderedSet<T::AccountId>;
/// Session key for oracle operators
pub SessionKeys get(fn session_keys) config(): map hasher(twox_64_concat) T::AccountId => Option<T::AuthorityId>;
pub Nonces get(fn nonces): map hasher(twox_64_concat) T::AccountId => u32;
pub enum Error for Module<T: Trait<I>, I: Instance> {
pub struct Module<T: Trait<I>, I: Instance> for enum Call where origin: T::Origin {
type Error = Error<T, I>;
/// Feed the external value.
///
/// Require unsigned. However a valid signature signed by session key is required along with payload.
#[weight = (10_000, DispatchClass::Operational)]
pub fn feed_values(
origin,
values: Vec<(T::OracleKey, T::OracleValue)>,
#[compact] index: u32,
// since signature verification is done in `validate_unsigned`
// we can skip doing it here again.
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
) {
ensure_none(origin.clone()).or_else(|_| ensure_root(origin))?;
// validate_unsigned already unsure index is valid
let who = Self::members().0.get(index as usize)
.expect("`validate_unsigned` ensures index is in bound; qed`")
.clone();
#[weight = 10_000_000]
pub fn set_session_key(origin, key: T::AuthorityId) {
ensure!(Self::members().contains(&who), Error::<T, I>::NoPermission);
SessionKeys::<T, I>::insert(who, key);
/// dummy `on_initialize` to return the weight used in `on_finalize`.
fn on_initialize() -> Weight {
// weight of `on_finalize`
0
}
fn on_finalize(_n: T::BlockNumber) {
// cleanup for next block
<HasDispatched<T, I>>::kill();
pub enum Event<T, I> where
<T as Trait<I>>::OracleKey,
<T as Trait<I>>::OracleValue,
/// New feed data is submitted. [sender, values]
NewFeedData(AccountId, Vec<(OracleKey, OracleValue)>),
impl<T: Trait<I>, I: Instance> Module<T, I> {
pub fn read_raw_values(key: &T::OracleKey) -> Vec<TimestampedValueOf<T, I>> {
/// Returns fresh combined value if has update, or latest combined value.
///
/// Note this will update values storage if has update.
pub fn get(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
<Values<T, I>>::get(key)
<Values<T, I>>::insert(key, timestamped.clone());
IsUpdated::<T, I>::insert(key, true);
/// Returns fresh combined value if has update, or latest combined value.
///
/// This is a no-op function which would not change storage.
pub fn get_no_op(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
#[allow(clippy::complexity)]
pub fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T, I>>)> {
<Values<T, I>>::iter()
.map(|key| {
let v = Self::get_no_op(&key);
(key, v)
})
fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
let values = Self::read_raw_values(key);
T::CombineData::combine_data(key, values, Self::values(key))
}
fn do_feed_values(who: T::AccountId, values: Vec<(T::OracleKey, T::OracleValue)>) {
let now = T::Time::now();
for (key, value) in &values {
let timestamped = TimestampedValue {
value: value.clone(),
timestamp: now,
};
RawValues::<T, I>::insert(&who, &key, timestamped);
IsUpdated::<T, I>::remove(&key);
T::OnNewData::on_new_data(&who, &key, &value);
}
Self::deposit_event(RawEvent::NewFeedData(who, values));
}
}
impl<T: Trait<I>, I: Instance> InitializeMembers<T::AccountId> for Module<T, I> {
fn initialize_members(members: &[T::AccountId]) {
if !members.is_empty() {
assert!(Members::<T, I>::get().0.is_empty(), "Members are already initialized!");
Members::<T, I>::put(OrderedSet::from_sorted_set(members.into()));
impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
fn change_members_sorted(_incoming: &[T::AccountId], outgoing: &[T::AccountId], new: &[T::AccountId]) {
// remove session keys and its values
for removed in outgoing {
SessionKeys::<T, I>::remove(removed);
RawValues::<T, I>::remove_prefix(removed);
Nonces::<T, I>::remove(removed);
Members::<T, I>::put(OrderedSet::from_sorted_set(new.into()));
// not bothering to track which key needs recompute, just update all
IsUpdated::<T, I>::remove_all();
}
fn set_prime(_prime: Option<T::AccountId>) {
// nothing
}
}
impl<T: Trait<I>, I: Instance> DataProvider<T::OracleKey, T::OracleValue> for Module<T, I> {
fn get(key: &T::OracleKey) -> Option<T::OracleValue> {
Self::get(key).map(|timestamped_value| timestamped_value.value)
}
}
impl<T: Trait<I>, I: Instance> DataProviderExtended<T::OracleKey, TimestampedValueOf<T, I>> for Module<T, I> {
fn get_no_op(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
Self::get_no_op(key)
}
#[allow(clippy::complexity)]
fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T, I>>)> {
Self::get_all_values()
}
}
impl<T: Trait<I>, I: Instance> DataFeeder<T::OracleKey, T::OracleValue, T::AccountId> for Module<T, I> {
fn feed_value(who: T::AccountId, key: T::OracleKey, value: T::OracleValue) -> DispatchResult {
Self::do_feed_values(who, vec![(key, value)]);
Ok(())
impl<T: Trait<I>, I: Instance> frame_support::unsigned::ValidateUnsigned for Module<T, I> {
type Call = Call<T, I>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::feed_values(value, index, block, signature) = call {
let now = <frame_system::Module<T>>::block_number();
if now > *block + EXTRINSIC_LONGEVITY.into() {
return Err(InvalidTransaction::Stale.into());
}
if now < *block {
return Err(InvalidTransaction::Future.into());
}
let members = Module::<T, I>::members();
let who = members.0.get(*index as usize);
if let Some(who) = who {
let nonce = Module::<T, I>::nonces(&who);
let signature_valid = Module::<T, I>::session_keys(&who)
.map(|session_key| {
(nonce, block, value).using_encoded(|payload| session_key.verify(&payload, &signature))
})
.unwrap_or(false);
if !signature_valid {
return InvalidTransaction::BadProof.into();
}
// ensure account hasn't dispatched an updated yet
let ok = HasDispatched::<T, I>::mutate(|set| set.insert(who.clone()));
if !ok {
// we already received a feed for this operator
return Err(InvalidTransaction::Stale.into());
}
Nonces::<T, I>::insert(who, nonce + 1);
// make priority less likely to overflow.
// this ensures tx sent later overrides old one
let add_priority = TryInto::<TransactionPriority>::try_into(*block % 1000.into()).unwrap_or(0);
ValidTransaction::with_tag_prefix("orml-oracle")
.priority(T::UnsignedPriority::get().saturating_add(add_priority))
.and_provides((who, nonce))
.longevity(EXTRINSIC_LONGEVITY.into())
.propagate(true)
.build()
} else {
InvalidTransaction::BadProof.into()
}
} else {
InvalidTransaction::Call.into()