pallet_example_offchain_worker/lib.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: MIT-0
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of
7// this software and associated documentation files (the "Software"), to deal in
8// the Software without restriction, including without limitation the rights to
9// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10// of the Software, and to permit persons to whom the Software is furnished to do
11// so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24//! <!-- markdown-link-check-disable -->
25//! # Offchain Worker Example Pallet
26//!
27//! The Offchain Worker Example: A simple pallet demonstrating
28//! concepts, APIs and structures common to most offchain workers.
29//!
30//! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's
31//! documentation.
32//!
33//! - [`Config`]
34//! - [`Call`]
35//! - [`Pallet`]
36//!
37//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to
38//! be used in production.**
39//!
40//! ## Overview
41//!
42//! In this example we are going to build a very simplistic, naive and definitely NOT
43//! production-ready oracle for BTC/USD price.
44//! Offchain Worker (OCW) will be triggered after every block, fetch the current price
45//! and prepare either signed or unsigned transaction to feed the result back on chain.
46//! The on-chain logic will simply aggregate the results and store last `64` values to compute
47//! the average price.
48//! Additional logic in OCW is put in place to prevent spamming the network with both signed
49//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
50//! one unsigned transaction floating in the network.
51
52#![cfg_attr(not(feature = "std"), no_std)]
53
54extern crate alloc;
55
56use alloc::vec::Vec;
57use codec::{Decode, DecodeWithMemTracking, Encode};
58use frame_support::traits::Get;
59use frame_system::{
60 self as system,
61 offchain::{
62 AppCrypto, CreateBare, CreateSignedTransaction, SendSignedTransaction,
63 SendUnsignedTransaction, SignedPayload, Signer, SigningTypes, SubmitTransaction,
64 },
65 pallet_prelude::BlockNumberFor,
66};
67use lite_json::json::JsonValue;
68use sp_core::crypto::KeyTypeId;
69use sp_runtime::{
70 offchain::{
71 http,
72 storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
73 Duration,
74 },
75 traits::Zero,
76 transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
77 RuntimeDebug,
78};
79
80#[cfg(test)]
81mod tests;
82
83/// Defines application identifier for crypto keys of this module.
84///
85/// Every module that deals with signatures needs to declare its unique identifier for
86/// its crypto keys.
87/// When offchain worker is signing transactions it's going to request keys of type
88/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction.
89/// The keys can be inserted manually via RPC (see `author_insertKey`).
90pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!");
91
92/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers.
93/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment
94/// the types with this pallet-specific identifier.
95pub mod crypto {
96 use super::KEY_TYPE;
97 use sp_core::sr25519::Signature as Sr25519Signature;
98 use sp_runtime::{
99 app_crypto::{app_crypto, sr25519},
100 traits::Verify,
101 MultiSignature, MultiSigner,
102 };
103 app_crypto!(sr25519, KEY_TYPE);
104
105 pub struct TestAuthId;
106
107 impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for TestAuthId {
108 type RuntimeAppPublic = Public;
109 type GenericSignature = sp_core::sr25519::Signature;
110 type GenericPublic = sp_core::sr25519::Public;
111 }
112
113 // implemented for mock runtime in test
114 impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>
115 for TestAuthId
116 {
117 type RuntimeAppPublic = Public;
118 type GenericSignature = sp_core::sr25519::Signature;
119 type GenericPublic = sp_core::sr25519::Public;
120 }
121}
122
123pub use pallet::*;
124
125#[frame_support::pallet]
126pub mod pallet {
127 use super::*;
128 use frame_support::pallet_prelude::*;
129 use frame_system::pallet_prelude::*;
130
131 /// This pallet's configuration trait
132 #[pallet::config]
133 pub trait Config:
134 CreateSignedTransaction<Call<Self>> + CreateBare<Call<Self>> + frame_system::Config
135 {
136 /// The identifier type for an offchain worker.
137 type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
138
139 // Configuration parameters
140
141 /// A grace period after we send transaction.
142 ///
143 /// To avoid sending too many transactions, we only attempt to send one
144 /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate
145 /// sending between distinct runs of this offchain worker.
146 #[pallet::constant]
147 type GracePeriod: Get<BlockNumberFor<Self>>;
148
149 /// Number of blocks of cooldown after unsigned transaction is included.
150 ///
151 /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval`
152 /// blocks.
153 #[pallet::constant]
154 type UnsignedInterval: Get<BlockNumberFor<Self>>;
155
156 /// A configuration for base priority of unsigned transactions.
157 ///
158 /// This is exposed so that it can be tuned for particular runtime, when
159 /// multiple pallets send unsigned transactions.
160 #[pallet::constant]
161 type UnsignedPriority: Get<TransactionPriority>;
162
163 /// Maximum number of prices.
164 #[pallet::constant]
165 type MaxPrices: Get<u32>;
166 }
167
168 #[pallet::pallet]
169 pub struct Pallet<T>(_);
170
171 #[pallet::hooks]
172 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
173 /// Offchain Worker entry point.
174 ///
175 /// By implementing `fn offchain_worker` you declare a new offchain worker.
176 /// This function will be called when the node is fully synced and a new best block is
177 /// successfully imported.
178 /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might
179 /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs),
180 /// so the code should be able to handle that.
181 /// You can use `Local Storage` API to coordinate runs of the worker.
182 fn offchain_worker(block_number: BlockNumberFor<T>) {
183 // Note that having logs compiled to WASM may cause the size of the blob to increase
184 // significantly. You can use `RuntimeDebug` custom derive to hide details of the types
185 // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable
186 // all logging and thus, remove any logging from the WASM.
187 log::info!("Hello World from offchain workers!");
188
189 // Since off-chain workers are just part of the runtime code, they have direct access
190 // to the storage and other included pallets.
191 //
192 // We can easily import `frame_system` and retrieve a block hash of the parent block.
193 let parent_hash = <system::Pallet<T>>::block_hash(block_number - 1u32.into());
194 log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash);
195
196 // It's a good practice to keep `fn offchain_worker()` function minimal, and move most
197 // of the code to separate `impl` block.
198 // Here we call a helper function to calculate current average price.
199 // This function reads storage entries of the current state.
200 let average: Option<u32> = Self::average_price();
201 log::debug!("Current price: {:?}", average);
202
203 // For this example we are going to send both signed and unsigned transactions
204 // depending on the block number.
205 // Usually it's enough to choose one or the other.
206 let should_send = Self::choose_transaction_type(block_number);
207 let res = match should_send {
208 TransactionType::Signed => Self::fetch_price_and_send_signed(),
209 TransactionType::UnsignedForAny =>
210 Self::fetch_price_and_send_unsigned_for_any_account(block_number),
211 TransactionType::UnsignedForAll =>
212 Self::fetch_price_and_send_unsigned_for_all_accounts(block_number),
213 TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number),
214 TransactionType::None => Ok(()),
215 };
216 if let Err(e) = res {
217 log::error!("Error: {}", e);
218 }
219 }
220 }
221
222 /// A public part of the pallet.
223 #[pallet::call]
224 impl<T: Config> Pallet<T> {
225 /// Submit new price to the list.
226 ///
227 /// This method is a public function of the module and can be called from within
228 /// a transaction. It appends given `price` to current list of prices.
229 /// In our example the `offchain worker` will create, sign & submit a transaction that
230 /// calls this function passing the price.
231 ///
232 /// The transaction needs to be signed (see `ensure_signed`) check, so that the caller
233 /// pays a fee to execute it.
234 /// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting
235 /// excessive transactions, but note that it doesn't ensure the price oracle is actually
236 /// working and receives (and provides) meaningful data.
237 /// This example is not focused on correctness of the oracle itself, but rather its
238 /// purpose is to showcase offchain worker capabilities.
239 #[pallet::call_index(0)]
240 #[pallet::weight({0})]
241 pub fn submit_price(origin: OriginFor<T>, price: u32) -> DispatchResultWithPostInfo {
242 // Retrieve sender of the transaction.
243 let who = ensure_signed(origin)?;
244 // Add the price to the on-chain list.
245 Self::add_price(Some(who), price);
246 Ok(().into())
247 }
248
249 /// Submit new price to the list via unsigned transaction.
250 ///
251 /// Works exactly like the `submit_price` function, but since we allow sending the
252 /// transaction without a signature, and hence without paying any fees,
253 /// we need a way to make sure that only some transactions are accepted.
254 /// This function can be called only once every `T::UnsignedInterval` blocks.
255 /// Transactions that call that function are de-duplicated on the pool level
256 /// via `validate_unsigned` implementation and also are rendered invalid if
257 /// the function has already been called in current "session".
258 ///
259 /// It's important to specify `weight` for unsigned calls as well, because even though
260 /// they don't charge fees, we still don't want a single block to contain unlimited
261 /// number of such transactions.
262 ///
263 /// This example is not focused on correctness of the oracle itself, but rather its
264 /// purpose is to showcase offchain worker capabilities.
265 #[pallet::call_index(1)]
266 #[pallet::weight({0})]
267 pub fn submit_price_unsigned(
268 origin: OriginFor<T>,
269 _block_number: BlockNumberFor<T>,
270 price: u32,
271 ) -> DispatchResultWithPostInfo {
272 // This ensures that the function can only be called via unsigned transaction.
273 ensure_none(origin)?;
274 // Add the price to the on-chain list, but mark it as coming from an empty address.
275 Self::add_price(None, price);
276 // now increment the block number at which we expect next unsigned transaction.
277 let current_block = <system::Pallet<T>>::block_number();
278 <NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
279 Ok(().into())
280 }
281
282 #[pallet::call_index(2)]
283 #[pallet::weight({0})]
284 pub fn submit_price_unsigned_with_signed_payload(
285 origin: OriginFor<T>,
286 price_payload: PricePayload<T::Public, BlockNumberFor<T>>,
287 _signature: T::Signature,
288 ) -> DispatchResultWithPostInfo {
289 // This ensures that the function can only be called via unsigned transaction.
290 ensure_none(origin)?;
291 // Add the price to the on-chain list, but mark it as coming from an empty address.
292 Self::add_price(None, price_payload.price);
293 // now increment the block number at which we expect next unsigned transaction.
294 let current_block = <system::Pallet<T>>::block_number();
295 <NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
296 Ok(().into())
297 }
298 }
299
300 /// Events for the pallet.
301 #[pallet::event]
302 #[pallet::generate_deposit(pub(super) fn deposit_event)]
303 pub enum Event<T: Config> {
304 /// Event generated when new price is accepted to contribute to the average.
305 NewPrice { price: u32, maybe_who: Option<T::AccountId> },
306 }
307
308 #[pallet::validate_unsigned]
309 impl<T: Config> ValidateUnsigned for Pallet<T> {
310 type Call = Call<T>;
311
312 /// Validate unsigned call to this module.
313 ///
314 /// By default unsigned transactions are disallowed, but implementing the validator
315 /// here we make sure that some particular calls (the ones produced by offchain worker)
316 /// are being whitelisted and marked as valid.
317 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
318 // Firstly let's check that we call the right function.
319 if let Call::submit_price_unsigned_with_signed_payload {
320 price_payload: ref payload,
321 ref signature,
322 } = call
323 {
324 let signature_valid =
325 SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());
326 if !signature_valid {
327 return InvalidTransaction::BadProof.into()
328 }
329 Self::validate_transaction_parameters(&payload.block_number, &payload.price)
330 } else if let Call::submit_price_unsigned { block_number, price: new_price } = call {
331 Self::validate_transaction_parameters(block_number, new_price)
332 } else {
333 InvalidTransaction::Call.into()
334 }
335 }
336 }
337
338 /// A vector of recently submitted prices.
339 ///
340 /// This is used to calculate average price, should have bounded size.
341 #[pallet::storage]
342 pub(super) type Prices<T: Config> = StorageValue<_, BoundedVec<u32, T::MaxPrices>, ValueQuery>;
343
344 /// Defines the block when next unsigned transaction will be accepted.
345 ///
346 /// To prevent spam of unsigned (and unpaid!) transactions on the network,
347 /// we only allow one transaction every `T::UnsignedInterval` blocks.
348 /// This storage entry defines when new transaction is going to be accepted.
349 #[pallet::storage]
350 pub(super) type NextUnsignedAt<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
351}
352
353/// Payload used by this example crate to hold price
354/// data required to submit a transaction.
355#[derive(
356 Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo,
357)]
358pub struct PricePayload<Public, BlockNumber> {
359 block_number: BlockNumber,
360 price: u32,
361 public: Public,
362}
363
364impl<T: SigningTypes> SignedPayload<T> for PricePayload<T::Public, BlockNumberFor<T>> {
365 fn public(&self) -> T::Public {
366 self.public.clone()
367 }
368}
369
370enum TransactionType {
371 Signed,
372 UnsignedForAny,
373 UnsignedForAll,
374 Raw,
375 None,
376}
377
378impl<T: Config> Pallet<T> {
379 /// Chooses which transaction type to send.
380 ///
381 /// This function serves mostly to showcase `StorageValue` helper
382 /// and local storage usage.
383 ///
384 /// Returns a type of transaction that should be produced in current run.
385 fn choose_transaction_type(block_number: BlockNumberFor<T>) -> TransactionType {
386 /// A friendlier name for the error that is going to be returned in case we are in the grace
387 /// period.
388 const RECENTLY_SENT: () = ();
389
390 // Start off by creating a reference to Local Storage value.
391 // Since the local storage is common for all offchain workers, it's a good practice
392 // to prepend your entry with the module name.
393 let val = StorageValueRef::persistent(b"example_ocw::last_send");
394 // The Local Storage is persisted and shared between runs of the offchain workers,
395 // and offchain workers may run concurrently. We can use the `mutate` function, to
396 // write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set`
397 // low-level method of local storage API, which means that only one worker
398 // will be able to "acquire a lock" and send a transaction if multiple workers
399 // happen to be executed concurrently.
400 let res =
401 val.mutate(|last_send: Result<Option<BlockNumberFor<T>>, StorageRetrievalError>| {
402 match last_send {
403 // If we already have a value in storage and the block number is recent enough
404 // we avoid sending another transaction at this time.
405 Ok(Some(block)) if block_number < block + T::GracePeriod::get() =>
406 Err(RECENTLY_SENT),
407 // In every other case we attempt to acquire the lock and send a transaction.
408 _ => Ok(block_number),
409 }
410 });
411
412 // The result of `mutate` call will give us a nested `Result` type.
413 // The first one matches the return of the closure passed to `mutate`, i.e.
414 // if we return `Err` from the closure, we get an `Err` here.
415 // In case we return `Ok`, here we will have another (inner) `Result` that indicates
416 // if the value has been set to the storage correctly - i.e. if it wasn't
417 // written to in the meantime.
418 match res {
419 // The value has been set correctly, which means we can safely send a transaction now.
420 Ok(block_number) => {
421 // We will send different transactions based on a random number.
422 // Note that this logic doesn't really guarantee that the transactions will be sent
423 // in an alternating fashion (i.e. fairly distributed). Depending on the execution
424 // order and lock acquisition, we may end up for instance sending two `Signed`
425 // transactions in a row. If a strict order is desired, it's better to use
426 // the storage entry for that. (for instance store both block number and a flag
427 // indicating the type of next transaction to send).
428 let transaction_type = block_number % 4u32.into();
429 if transaction_type == Zero::zero() {
430 TransactionType::Signed
431 } else if transaction_type == BlockNumberFor::<T>::from(1u32) {
432 TransactionType::UnsignedForAny
433 } else if transaction_type == BlockNumberFor::<T>::from(2u32) {
434 TransactionType::UnsignedForAll
435 } else {
436 TransactionType::Raw
437 }
438 },
439 // We are in the grace period, we should not send a transaction this time.
440 Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None,
441 // We wanted to send a transaction, but failed to write the block number (acquire a
442 // lock). This indicates that another offchain worker that was running concurrently
443 // most likely executed the same logic and succeeded at writing to storage.
444 // Thus we don't really want to send the transaction, knowing that the other run
445 // already did.
446 Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None,
447 }
448 }
449
450 /// A helper function to fetch the price and send signed transaction.
451 fn fetch_price_and_send_signed() -> Result<(), &'static str> {
452 let signer = Signer::<T, T::AuthorityId>::all_accounts();
453 if !signer.can_sign() {
454 return Err(
455 "No local accounts available. Consider adding one via `author_insertKey` RPC.",
456 )
457 }
458 // Make an external HTTP request to fetch the current price.
459 // Note this call will block until response is received.
460 let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
461
462 // Using `send_signed_transaction` associated type we create and submit a transaction
463 // representing the call, we've just created.
464 // Submit signed will return a vector of results for all accounts that were found in the
465 // local keystore with expected `KEY_TYPE`.
466 let results = signer.send_signed_transaction(|_account| {
467 // Received price is wrapped into a call to `submit_price` public function of this
468 // pallet. This means that the transaction, when executed, will simply call that
469 // function passing `price` as an argument.
470 Call::submit_price { price }
471 });
472
473 for (acc, res) in &results {
474 match res {
475 Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price),
476 Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e),
477 }
478 }
479
480 Ok(())
481 }
482
483 /// A helper function to fetch the price and send a raw unsigned transaction.
484 fn fetch_price_and_send_raw_unsigned(
485 block_number: BlockNumberFor<T>,
486 ) -> Result<(), &'static str> {
487 // Make sure we don't fetch the price if unsigned transaction is going to be rejected
488 // anyway.
489 let next_unsigned_at = NextUnsignedAt::<T>::get();
490 if next_unsigned_at > block_number {
491 return Err("Too early to send unsigned transaction")
492 }
493
494 // Make an external HTTP request to fetch the current price.
495 // Note this call will block until response is received.
496 let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
497
498 // Received price is wrapped into a call to `submit_price_unsigned` public function of this
499 // pallet. This means that the transaction, when executed, will simply call that function
500 // passing `price` as an argument.
501 let call = Call::submit_price_unsigned { block_number, price };
502
503 // Now let's create a transaction out of this call and submit it to the pool.
504 // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw)
505 //
506 // By default unsigned transactions are disallowed, so we need to whitelist this case
507 // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully
508 // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam
509 // attack vectors. See validation logic docs for more details.
510 //
511 let xt = T::create_bare(call.into());
512 SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
513 .map_err(|()| "Unable to submit unsigned transaction.")?;
514
515 Ok(())
516 }
517
518 /// A helper function to fetch the price, sign payload and send an unsigned transaction
519 fn fetch_price_and_send_unsigned_for_any_account(
520 block_number: BlockNumberFor<T>,
521 ) -> Result<(), &'static str> {
522 // Make sure we don't fetch the price if unsigned transaction is going to be rejected
523 // anyway.
524 let next_unsigned_at = NextUnsignedAt::<T>::get();
525 if next_unsigned_at > block_number {
526 return Err("Too early to send unsigned transaction")
527 }
528
529 // Make an external HTTP request to fetch the current price.
530 // Note this call will block until response is received.
531 let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
532
533 // -- Sign using any account
534 let (_, result) = Signer::<T, T::AuthorityId>::any_account()
535 .send_unsigned_transaction(
536 |account| PricePayload { price, block_number, public: account.public.clone() },
537 |payload, signature| Call::submit_price_unsigned_with_signed_payload {
538 price_payload: payload,
539 signature,
540 },
541 )
542 .ok_or("No local accounts accounts available.")?;
543 result.map_err(|()| "Unable to submit transaction")?;
544
545 Ok(())
546 }
547
548 /// A helper function to fetch the price, sign payload and send an unsigned transaction
549 fn fetch_price_and_send_unsigned_for_all_accounts(
550 block_number: BlockNumberFor<T>,
551 ) -> Result<(), &'static str> {
552 // Make sure we don't fetch the price if unsigned transaction is going to be rejected
553 // anyway.
554 let next_unsigned_at = NextUnsignedAt::<T>::get();
555 if next_unsigned_at > block_number {
556 return Err("Too early to send unsigned transaction")
557 }
558
559 // Make an external HTTP request to fetch the current price.
560 // Note this call will block until response is received.
561 let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
562
563 // -- Sign using all accounts
564 let transaction_results = Signer::<T, T::AuthorityId>::all_accounts()
565 .send_unsigned_transaction(
566 |account| PricePayload { price, block_number, public: account.public.clone() },
567 |payload, signature| Call::submit_price_unsigned_with_signed_payload {
568 price_payload: payload,
569 signature,
570 },
571 );
572 for (_account_id, result) in transaction_results.into_iter() {
573 if result.is_err() {
574 return Err("Unable to submit transaction")
575 }
576 }
577
578 Ok(())
579 }
580
581 /// Fetch current price and return the result in cents.
582 fn fetch_price() -> Result<u32, http::Error> {
583 // We want to keep the offchain worker execution time reasonable, so we set a hard-coded
584 // deadline to 2s to complete the external call.
585 // You can also wait indefinitely for the response, however you may still get a timeout
586 // coming from the host machine.
587 let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000));
588 // Initiate an external HTTP GET request.
589 // This is using high-level wrappers from `sp_runtime`, for the low-level calls that
590 // you can find in `sp_io`. The API is trying to be similar to `request`, but
591 // since we are running in a custom WASM execution environment we can't simply
592 // import the library here.
593 let request =
594 http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD");
595 // We set the deadline for sending of the request, note that awaiting response can
596 // have a separate deadline. Next we send the request, before that it's also possible
597 // to alter request headers or stream body content in case of non-GET requests.
598 let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?;
599
600 // The request is already being processed by the host, we are free to do anything
601 // else in the worker (we can send multiple concurrent requests too).
602 // At some point however we probably want to check the response though,
603 // so we can block current thread and wait for it to finish.
604 // Note that since the request is being driven by the host, we don't have to wait
605 // for the request to have it complete, we will just not read the response.
606 let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??;
607 // Let's check the status code before we proceed to reading the response.
608 if response.code != 200 {
609 log::warn!("Unexpected status code: {}", response.code);
610 return Err(http::Error::Unknown)
611 }
612
613 // Next we want to fully read the response body and collect it to a vector of bytes.
614 // Note that the return object allows you to read the body in chunks as well
615 // with a way to control the deadline.
616 let body = response.body().collect::<Vec<u8>>();
617
618 // Create a str slice from the body.
619 let body_str = alloc::str::from_utf8(&body).map_err(|_| {
620 log::warn!("No UTF8 body");
621 http::Error::Unknown
622 })?;
623
624 let price = match Self::parse_price(body_str) {
625 Some(price) => Ok(price),
626 None => {
627 log::warn!("Unable to extract price from the response: {:?}", body_str);
628 Err(http::Error::Unknown)
629 },
630 }?;
631
632 log::warn!("Got price: {} cents", price);
633
634 Ok(price)
635 }
636
637 /// Parse the price from the given JSON string using `lite-json`.
638 ///
639 /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful.
640 fn parse_price(price_str: &str) -> Option<u32> {
641 let val = lite_json::parse_json(price_str);
642 let price = match val.ok()? {
643 JsonValue::Object(obj) => {
644 let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
645 match v {
646 JsonValue::Number(number) => number,
647 _ => return None,
648 }
649 },
650 _ => return None,
651 };
652
653 let exp = price.fraction_length.saturating_sub(2);
654 Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
655 }
656
657 /// Add new price to the list.
658 fn add_price(maybe_who: Option<T::AccountId>, price: u32) {
659 log::info!("Adding to the average: {}", price);
660 <Prices<T>>::mutate(|prices| {
661 if prices.try_push(price).is_err() {
662 prices[(price % T::MaxPrices::get()) as usize] = price;
663 }
664 });
665
666 let average = Self::average_price()
667 .expect("The average is not empty, because it was just mutated; qed");
668 log::info!("Current average price is: {}", average);
669 // here we are raising the NewPrice event
670 Self::deposit_event(Event::NewPrice { price, maybe_who });
671 }
672
673 /// Calculate current average price.
674 fn average_price() -> Option<u32> {
675 let prices = Prices::<T>::get();
676 if prices.is_empty() {
677 None
678 } else {
679 Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32)
680 }
681 }
682
683 fn validate_transaction_parameters(
684 block_number: &BlockNumberFor<T>,
685 new_price: &u32,
686 ) -> TransactionValidity {
687 // Now let's check if the transaction has any chance to succeed.
688 let next_unsigned_at = NextUnsignedAt::<T>::get();
689 if &next_unsigned_at > block_number {
690 return InvalidTransaction::Stale.into()
691 }
692 // Let's make sure to reject transactions from the future.
693 let current_block = <system::Pallet<T>>::block_number();
694 if ¤t_block < block_number {
695 return InvalidTransaction::Future.into()
696 }
697
698 // We prioritize transactions that are more far away from current average.
699 //
700 // Note this doesn't make much sense when building an actual oracle, but this example
701 // is here mostly to show off offchain workers capabilities, not about building an
702 // oracle.
703 let avg_price = Self::average_price()
704 .map(|price| if &price > new_price { price - new_price } else { new_price - price })
705 .unwrap_or(0);
706
707 ValidTransaction::with_tag_prefix("ExampleOffchainWorker")
708 // We set base priority to 2**20 and hope it's included before any other
709 // transactions in the pool. Next we tweak the priority depending on how much
710 // it differs from the current average. (the more it differs the more priority it
711 // has).
712 .priority(T::UnsignedPriority::get().saturating_add(avg_price as _))
713 // This transaction does not require anything else to go before into the pool.
714 // In theory we could require `previous_unsigned_at` transaction to go first,
715 // but it's not necessary in our case.
716 //.and_requires()
717 // We set the `provides` tag to be the same as `next_unsigned_at`. This makes
718 // sure only one transaction produced after `next_unsigned_at` will ever
719 // get to the transaction pool and will end up in the block.
720 // We can still have multiple transactions compete for the same "spot",
721 // and the one with higher priority will replace other one in the pool.
722 .and_provides(next_unsigned_at)
723 // The transaction is only valid for next 5 blocks. After that it's
724 // going to be revalidated by the pool.
725 .longevity(5)
726 // It's fine to propagate that transaction to other peers, which means it can be
727 // created even by nodes that don't produce blocks.
728 // Note that sometimes it's better to keep it for yourself (if you are the block
729 // producer), since for instance in some schemes others may copy your solution and
730 // claim a reward.
731 .propagate(true)
732 .build()
733 }
734}