Newer
Older
// Disable the following two lints since they originate from an external macro (namely decl_storage)
#![allow(clippy::string_lit_as_bytes)]
pub use default_combine_data::DefaultCombineData;
decl_error, decl_event, decl_module, decl_storage, ensure,
IterableStorageMap, Parameter,
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, ValidTransaction,
use sp_std::{convert::TryInto, prelude::*, vec};
// FIXME: `pallet/frame-` prefix should be used for all pallet modules, but currently `frame_system`
// would cause compiling error in `decl_module!` and `construct_runtime!`
// #3295 https://github.com/paritytech/substrate/issues/3295
use frame_system::{self as system, ensure_none, ensure_root, ensure_signed};
pub use orml_traits::{CombineData, DataProvider, DataProviderExtended, OnNewData, OnRedundantCall};
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> = <<T as Trait>::Time as Time>::Moment;
pub type TimestampedValueOf<T> = TimestampedValue<<T as Trait>::OracleValue, MomentOf<T>>;
/// Number of blocks before an unconfirmed unsigned transaction expires.
pub const EXTRINSIC_LONGVITY: u32 = 100;
#[derive(Encode, Decode, RuntimeDebug, Eq, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct TimestampedValue<Value, Moment> {
pub value: Value,
pub timestamp: Moment,
}
pub trait Trait: frame_system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self>>;
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;
}
decl_storage! {
trait Store for Module<T: Trait> as Oracle {
/// Raw values for each oracle operators
pub RawValues get(fn raw_values): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T>>;
/// 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>>;
/// 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;
#[repr(u8)]
pub enum ValidityError {
NoPermission,
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
#[weight = (0, DispatchClass::Operational, Pays::No)]
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
#[weight = 10_000_000]
pub fn set_session_key(origin, key: T::AuthorityId) {
ensure!(Self::members().contains(&who), Error::<T>::NoPermission);
SessionKeys::<T>::insert(who, key);
fn on_finalize(_n: T::BlockNumber) {
// cleanup for next block
<HasDispatched<T>>::kill();
}
<T as Trait>::OracleKey,
<T as Trait>::OracleValue,
NewFeedData(AccountId, Vec<(OracleKey, OracleValue)>),
pub fn read_raw_values(key: &T::OracleKey) -> Vec<TimestampedValueOf<T>> {
/// 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>> {
if Self::is_updated(key) {
<Values<T>>::get(key)
} else {
/// 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>> {
pub fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T>>)> {
<Values<T>>::iter()
.map(|(key, _)| key)
.map(|key| {
let v = Self::get_no_op(&key);
(key, v)
})
fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
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)>) {
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
let now = T::Time::now();
for (key, value) in &values {
let timestamped = TimestampedValue {
value: value.clone(),
timestamp: now,
};
RawValues::<T>::insert(&who, &key, timestamped);
IsUpdated::<T>::remove(&key);
T::OnNewData::on_new_data(&who, &key, &value);
}
Self::deposit_event(RawEvent::NewFeedData(who, values));
}
}
impl<T: Trait> InitializeMembers<T::AccountId> for Module<T> {
fn initialize_members(members: &[T::AccountId]) {
if !members.is_empty() {
assert!(Members::<T>::get().0.is_empty(), "Members are already initialized!");
Members::<T>::put(OrderedSet::from_sorted_set(members.into()));
}
}
}
impl<T: Trait> ChangeMembers<T::AccountId> for Module<T> {
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>::remove(removed);
RawValues::<T>::remove_prefix(removed);
Nonces::<T>::remove(removed);
}
Members::<T>::put(OrderedSet::from_sorted_set(new.into()));
// not bothering to track which key needs recompute, just update all
IsUpdated::<T>::remove_all();
}
fn set_prime(_prime: Option<T::AccountId>) {
// nothing
}
}
impl<T: Trait> DataProvider<T::OracleKey, T::OracleValue> for Module<T> {
fn get(key: &T::OracleKey) -> Option<T::OracleValue> {
Self::get(key).map(|timestamped_value| timestamped_value.value)
}
}
impl<T: Trait> DataProviderExtended<T::OracleKey, T::OracleValue, T::AccountId> for Module<T> {
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> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
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_LONGVITY.into() {
return Err(InvalidTransaction::Stale.into());
}
if now < *block {
return Err(InvalidTransaction::Future.into());
}
let members = Module::<T>::members();
let who = members.0.get(*index as usize);
if let Some(who) = who {
let nonce = Module::<T>::nonces(&who);
let signature_valid = Module::<T>::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>::mutate(|set| set.insert(who.clone()));
if !ok {
// we already received a feed for this operator
return Err(InvalidTransaction::Stale.into());
}
Nonces::<T>::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))
.propagate(true)
.build()
} else {
InvalidTransaction::BadProof.into()
}
} else {
InvalidTransaction::Call.into()