diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index f7a69e401e..7f8f7bd555 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3195,6 +3195,7 @@ dependencies = [ "parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "sr-std 2.0.0", + "substrate-offchain 2.0.0", "substrate-primitives 2.0.0", "substrate-state-machine 2.0.0", "substrate-trie 2.0.0", @@ -4235,6 +4236,7 @@ dependencies = [ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", "substrate-client 2.0.0", "substrate-consensus-common 2.0.0", diff --git a/substrate/core/client/src/call_executor.rs b/substrate/core/client/src/call_executor.rs index 9a47d1ac21..13ee96400f 100644 --- a/substrate/core/client/src/call_executor.rs +++ b/substrate/core/client/src/call_executor.rs @@ -26,9 +26,7 @@ use state_machine::{ use executor::{RuntimeVersion, RuntimeInfo, NativeVersion}; use hash_db::Hasher; use trie::MemoryDB; -use primitives::{ - H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue, OffchainExt -}; +use primitives::{offchain, H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue}; use crate::runtime_api::{ProofRecorder, InitializeBlock}; use crate::backend; @@ -48,7 +46,7 @@ where /// /// No changes are made. fn call< - O: OffchainExt, + O: offchain::Externalities, >( &self, id: &BlockId, @@ -65,7 +63,7 @@ where /// of the execution context. fn contextual_call< 'a, - O: OffchainExt, + O: offchain::Externalities, IB: Fn() -> error::Result<()>, EM: Fn( Result, Self::Error>, @@ -96,7 +94,7 @@ where /// /// No changes are made. fn call_at_state< - O: OffchainExt, + O: offchain::Externalities, S: state_machine::Backend, F: FnOnce( Result, Self::Error>, @@ -181,7 +179,7 @@ where { type Error = E::Error; - fn call( + fn call( &self, id: &BlockId, method: &str, @@ -211,7 +209,7 @@ where fn contextual_call< 'a, - O: OffchainExt, + O: offchain::Externalities, IB: Fn() -> error::Result<()>, EM: Fn( Result, Self::Error>, @@ -300,7 +298,7 @@ where } fn call_at_state< - O: OffchainExt, + O: offchain::Externalities, S: state_machine::Backend, F: FnOnce( Result, Self::Error>, diff --git a/substrate/core/client/src/light/call_executor.rs b/substrate/core/client/src/light/call_executor.rs index 9959d29a1b..5fb7e7308a 100644 --- a/substrate/core/client/src/light/call_executor.rs +++ b/substrate/core/client/src/light/call_executor.rs @@ -24,7 +24,7 @@ use std::{ use futures::{IntoFuture, Future}; use parity_codec::{Encode, Decode}; -use primitives::{H256, Blake2Hasher, convert_hash, NativeOrEncoded, OffchainExt}; +use primitives::{offchain, H256, Blake2Hasher, convert_hash, NativeOrEncoded}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{One, Block as BlockT, Header as HeaderT}; use state_machine::{ @@ -87,7 +87,7 @@ where type Error = ClientError; fn call< - O: OffchainExt, + O: offchain::Externalities, >( &self, id: &BlockId, @@ -111,7 +111,7 @@ where fn contextual_call< 'a, - O: OffchainExt, + O: offchain::Externalities, IB: Fn() -> ClientResult<()>, EM: Fn( Result, Self::Error>, @@ -154,7 +154,7 @@ where } fn call_at_state< - O: OffchainExt, + O: offchain::Externalities, S: StateBackend, FF: FnOnce( Result, Self::Error>, @@ -230,7 +230,7 @@ impl CallExecutor for type Error = ClientError; fn call< - O: OffchainExt, + O: offchain::Externalities, >( &self, id: &BlockId, @@ -247,7 +247,7 @@ impl CallExecutor for fn contextual_call< 'a, - O: OffchainExt, + O: offchain::Externalities, IB: Fn() -> ClientResult<()>, EM: Fn( Result, Self::Error>, @@ -327,7 +327,7 @@ impl CallExecutor for } fn call_at_state< - O: OffchainExt, + O: offchain::Externalities, S: StateBackend, FF: FnOnce( Result, Self::Error>, diff --git a/substrate/core/client/src/runtime_api.rs b/substrate/core/client/src/runtime_api.rs index f5c8a59a90..987138e69a 100644 --- a/substrate/core/client/src/runtime_api.rs +++ b/substrate/core/client/src/runtime_api.rs @@ -31,7 +31,7 @@ pub use runtime_primitives::{ generic::BlockId, transaction_validity::TransactionValidity, }; #[doc(hidden)] -pub use primitives::{ExecutionContext, OffchainExt}; +pub use primitives::{offchain, ExecutionContext}; #[doc(hidden)] pub use runtime_version::{ApiId, RuntimeVersion, ApisVec, create_apis_vec}; #[doc(hidden)] diff --git a/substrate/core/executor/src/wasm_executor.rs b/substrate/core/executor/src/wasm_executor.rs index 464531a5a1..6a3c92b755 100644 --- a/substrate/core/executor/src/wasm_executor.rs +++ b/substrate/core/executor/src/wasm_executor.rs @@ -17,6 +17,8 @@ //! Rust implementation of Substrate contracts. use std::collections::HashMap; +use std::convert::TryFrom; +use std::str; use tiny_keccak; use secp256k1; @@ -29,6 +31,7 @@ use state_machine::{Externalities, ChildStorageKey}; use crate::error::{Error, Result}; use crate::wasm_utils::UserError; use primitives::{blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Pair}; +use primitives::offchain; use primitives::hexdisplay::HexDisplay; use primitives::sandbox as sandbox_primitives; use primitives::{H256, Blake2Hasher}; @@ -113,6 +116,24 @@ impl ReadPrimitive for MemoryInstance { } } +fn deadline_to_timestamp(deadline: u64) -> Option { + if deadline == 0 { + None + } else { + Some(offchain::Timestamp::from_unix_millis(deadline)) + } +} + +fn u32_to_key(key: u32) -> std::result::Result, ()> { + if key > u16::max_value() as u32 { + Err(()) + } else if key == 0 { + Ok(None) + } else { + Ok(Some(offchain::CryptoKeyId(key as u16))) + } +} + impl_function_executor!(this: FunctionExecutor<'e, E>, ext_print_utf8(utf8_data: *const u8, utf8_len: u32) => { if let Ok(utf8) = this.memory.get(utf8_data, utf8_len as usize) { @@ -459,7 +480,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, let key = this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_twox_64"))?; let hashed_key = twox_64(&key); debug_trace!(target: "xxhash", "XXhash: {} -> {}", - if let Ok(_skey) = ::std::str::from_utf8(&key) { + if let Ok(_skey) = str::from_utf8(&key) { _skey } else { &format!("{}", HexDisplay::from(&key)) @@ -483,7 +504,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, let key = this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_twox_128"))?; let hashed_key = twox_128(&key); debug_trace!(target: "xxhash", "XXhash: {} -> {}", - &if let Ok(_skey) = ::std::str::from_utf8(&key) { + &if let Ok(_skey) = str::from_utf8(&key) { *_skey } else { format!("{}", HexDisplay::from(&key)) @@ -590,15 +611,345 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, Ok(0) }, - ext_submit_extrinsic(msg_data: *const u8, len: u32) => { + ext_submit_transaction(msg_data: *const u8, len: u32) -> u32 => { let extrinsic = this.memory.get(msg_data, len as usize) - .map_err(|_| UserError("OOB while ext_submit_extrinsic: wasm"))?; + .map_err(|_| UserError("OOB while ext_submit_transaction: wasm"))?; - this.ext.submit_extrinsic(extrinsic) - .map_err(|_| UserError("Calling unavailable API ext_submit_extrinsic: wasm"))?; + let res = this.ext.offchain() + .map(|api| api.submit_transaction(extrinsic)) + .ok_or_else(|| UserError("Calling unavailable API ext_submit_transaction: wasm"))?; + + Ok(if res.is_ok() { 0 } else { 1 }) + }, + ext_new_crypto_key(crypto: u32) -> u32 => { + let kind = offchain::CryptoKind::try_from(crypto) + .map_err(|_| UserError("crypto kind OOB while ext_new_crypto_key: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.new_crypto_key(kind)) + .ok_or_else(|| UserError("Calling unavailable API ext_new_crypto_key: wasm"))?; + + match res { + Ok(key_id) => Ok(key_id.0 as u32), + Err(()) => Ok(u32::max_value()), + } + }, + ext_encrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8 => { + let key = u32_to_key(key) + .map_err(|_| UserError("key OOB while ext_encrypt: wasm"))?; + let message = this.memory.get(data, data_len as usize) + .map_err(|_| UserError("OOB while ext_encrypt: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.encrypt(key, &*message)) + .ok_or_else(|| UserError("Calling unavailable API ext_encrypt: wasm"))?; + + let (offset,len) = match res { + Ok(encrypted) => { + let len = encrypted.len() as u32; + let offset = this.heap.allocate(len)? as u32; + this.memory.set(offset, &encrypted) + .map_err(|_| UserError("Invalid attempt to set memory in ext_encrypt"))?; + (offset, len) + }, + Err(()) => (0, u32::max_value()), + }; + + this.memory.write_primitive(msg_len, len) + .map_err(|_| UserError("Invalid attempt to write msg_len in ext_encrypt"))?; + + Ok(offset) + }, + ext_decrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8 => { + let key = u32_to_key(key) + .map_err(|_| UserError("key OOB while ext_decrypt: wasm"))?; + let message = this.memory.get(data, data_len as usize) + .map_err(|_| UserError("OOB while ext_decrypt: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.decrypt(key, &*message)) + .ok_or_else(|| UserError("Calling unavailable API ext_decrypt: wasm"))?; + + let (offset,len) = match res { + Ok(decrypted) => { + let len = decrypted.len() as u32; + let offset = this.heap.allocate(len)? as u32; + this.memory.set(offset, &decrypted) + .map_err(|_| UserError("Invalid attempt to set memory in ext_decrypt"))?; + (offset, len) + }, + Err(()) => (0, u32::max_value()), + }; + + this.memory.write_primitive(msg_len, len) + .map_err(|_| UserError("Invalid attempt to write msg_len in ext_decrypt"))?; + + Ok(offset) + }, + ext_sign(key: u32, data: *const u8, data_len: u32, sig_data_len: *mut u32) -> *mut u8 => { + let key = u32_to_key(key) + .map_err(|_| UserError("key OOB while ext_sign: wasm"))?; + let message = this.memory.get(data, data_len as usize) + .map_err(|_| UserError("OOB while ext_sign: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.sign(key, &*message)) + .ok_or_else(|| UserError("Calling unavailable API ext_sign: wasm"))?; + + let (offset,len) = match res { + Ok(signature) => { + let len = signature.len() as u32; + let offset = this.heap.allocate(len)? as u32; + this.memory.set(offset, &signature) + .map_err(|_| UserError("Invalid attempt to set memory in ext_sign"))?; + (offset, len) + }, + Err(()) => (0, u32::max_value()), + }; + + this.memory.write_primitive(sig_data_len, len) + .map_err(|_| UserError("Invalid attempt to write sig_data_len in ext_sign"))?; + + Ok(offset) + }, + ext_verify( + key: u32, + msg: *const u8, + msg_len: u32, + signature: *const u8, + signature_len: u32 + ) -> u32 => { + let key = u32_to_key(key) + .map_err(|_| UserError("key OOB while ext_verify: wasm"))?; + let message = this.memory.get(msg, msg_len as usize) + .map_err(|_| UserError("OOB while ext_verify: wasm"))?; + let signature = this.memory.get(signature, signature_len as usize) + .map_err(|_| UserError("OOB while ext_verify: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.verify(key, &*message, &*signature)) + .ok_or_else(|| UserError("Calling unavailable API ext_verify: wasm"))?; + + match res { + Ok(true) => Ok(0), + Ok(false) => Ok(1), + Err(()) => Ok(u32::max_value()), + } + }, + ext_timestamp() -> u64 => { + let timestamp = this.ext.offchain() + .map(|api| api.timestamp()) + .ok_or_else(|| UserError("Calling unavailable API ext_timestamp: wasm"))?; + Ok(timestamp.unix_millis()) + }, + ext_sleep_until(deadline: u64) => { + this.ext.offchain() + .map(|api| api.sleep_until(offchain::Timestamp::from_unix_millis(deadline))) + .ok_or_else(|| UserError("Calling unavailable API ext_sleep_until: wasm"))?; + Ok(()) + }, + ext_random_seed(seed_data: *mut u8) => { + // NOTE the runtime as assumptions about seed size. + let seed: [u8; 32] = this.ext.offchain() + .map(|api| api.random_seed()) + .ok_or_else(|| UserError("Calling unavailable API ext_random_seed: wasm"))?; + + this.memory.set(seed_data, &seed) + .map_err(|_| UserError("Invalid attempt to set value in ext_random_seed"))?; + Ok(()) + }, + ext_local_storage_set(key: *const u8, key_len: u32, value: *const u8, value_len: u32) => { + let key = this.memory.get(key, key_len as usize) + .map_err(|_| UserError("OOB while ext_local_storage_set: wasm"))?; + let value = this.memory.get(value, value_len as usize) + .map_err(|_| UserError("OOB while ext_local_storage_set: wasm"))?; + + this.ext.offchain() + .map(|api| api.local_storage_set(&key, &value)) + .ok_or_else(|| UserError("Calling unavailable API ext_local_storage_set: wasm"))?; Ok(()) }, + ext_local_storage_get(key: *const u8, key_len: u32, value_len: *mut u32) -> *mut u8 => { + let key = this.memory.get(key, key_len as usize) + .map_err(|_| UserError("OOB while ext_local_storage_get: wasm"))?; + + let maybe_value = this.ext.offchain() + .map(|api| api.local_storage_get(&key)) + .ok_or_else(|| UserError("Calling unavailable API ext_local_storage_get: wasm"))?; + + let (offset, len) = if let Some(value) = maybe_value { + let offset = this.heap.allocate(value.len() as u32)? as u32; + this.memory.set(offset, &value) + .map_err(|_| UserError("Invalid attempt to set memory in ext_local_storage_get"))?; + (offset, value.len() as u32) + } else { + (0, u32::max_value()) + }; + + this.memory.write_primitive(value_len, len) + .map_err(|_| UserError("Invalid attempt to write value_len in ext_local_storage_get"))?; + + Ok(offset) + }, + ext_http_request_start( + method: *const u8, + method_len: u32, + url: *const u8, + url_len: u32, + meta: *const u8, + meta_len: u32 + ) -> u32 => { + let method = this.memory.get(method, method_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_start: wasm"))?; + let url = this.memory.get(url, url_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_start: wasm"))?; + let meta = this.memory.get(meta, meta_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_start: wasm"))?; + + let method_str = str::from_utf8(&method) + .map_err(|_| UserError("invalid str while ext_http_request_start: wasm"))?; + let url_str = str::from_utf8(&url) + .map_err(|_| UserError("invalid str while ext_http_request_start: wasm"))?; + + let id = this.ext.offchain() + .map(|api| api.http_request_start(method_str, url_str, &*meta)) + .ok_or_else(|| UserError("Calling unavailable API ext_http_request_start: wasm"))?; + + if let Ok(id) = id { + Ok(id.0 as u32) + } else { + Ok(u32::max_value()) + } + }, + ext_http_request_add_header( + request_id: u32, + name: *const u8, + name_len: u32, + value: *const u8, + value_len: u32 + ) -> u32 => { + let name = this.memory.get(name, name_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_add_header: wasm"))?; + let value = this.memory.get(value, value_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_add_header: wasm"))?; + + let name_str = str::from_utf8(&name) + .map_err(|_| UserError("invalid str while ext_http_request_add_header: wasm"))?; + let value_str = str::from_utf8(&value) + .map_err(|_| UserError("invalid str while ext_http_request_add_header: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.http_request_add_header( + offchain::HttpRequestId(request_id as u16), + &name_str, + &value_str, + )) + .ok_or_else(|| UserError("Calling unavailable API ext_http_request_add_header: wasm"))?; + + Ok(if res.is_ok() { 0 } else { 1 }) + }, + ext_http_request_write_body( + request_id: u32, + chunk: *const u8, + chunk_len: u32, + deadline: u64 + ) -> u32 => { + let chunk = this.memory.get(chunk, chunk_len as usize) + .map_err(|_| UserError("OOB while ext_http_request_write_body: wasm"))?; + + let res = this.ext.offchain() + .map(|api| api.http_request_write_body( + offchain::HttpRequestId(request_id as u16), + &chunk, + deadline_to_timestamp(deadline) + )) + .ok_or_else(|| UserError("Calling unavailable API ext_http_request_write_body: wasm"))?; + + Ok(match res { + Ok(()) => 0, + Err(e) => e as u8 as u32, + }) + }, + ext_http_response_wait( + ids: *const u32, + ids_len: u32, + statuses: *mut u32, + deadline: u64 + ) => { + let ids = (0..ids_len) + .map(|i| + this.memory.read_primitive(ids + i * 4) + .map(|id: u32| offchain::HttpRequestId(id as u16)) + .map_err(|_| UserError("OOB while ext_http_response_wait: wasm")) + ) + .collect::<::std::result::Result, _>>()?; + + let res = this.ext.offchain() + .map(|api| api.http_response_wait(&ids, deadline_to_timestamp(deadline))) + .ok_or_else(|| UserError("Calling unavailable API ext_http_response_wait: wasm"))? + .into_iter() + .map(|status| status.into()) + .enumerate() + // make sure to take up to `ids_len` to avoid exceeding the mem. + .take(ids_len as usize); + + for (i, status) in res { + this.memory.write_primitive(statuses + i as u32 * 4, status) + .map_err(|_| UserError("Invalid attempt to set memory in ext_http_response_wait"))?; + } + + Ok(()) + }, + ext_http_response_headers( + request_id: u32, + written_out: *mut u32 + ) -> *mut u8 => { + use parity_codec::Encode; + + let headers = this.ext.offchain() + .map(|api| api.http_response_headers(offchain::HttpRequestId(request_id as u16))) + .ok_or_else(|| UserError("Calling unavailable API ext_http_response_headers: wasm"))?; + + let encoded = headers.encode(); + let len = encoded.len() as u32; + let offset = this.heap.allocate(len)? as u32; + this.memory.set(offset, &encoded) + .map_err(|_| UserError("Invalid attempt to set memory in ext_http_response_headers"))?; + this.memory.write_primitive(written_out, len) + .map_err(|_| UserError("Invalid attempt to write written_out in ext_http_response_headers"))?; + + Ok(offset) + }, + ext_http_response_read_body( + request_id: u32, + buffer: *mut u8, + buffer_len: u32, + deadline: u64 + ) -> u32 => { + let mut internal_buffer = Vec::with_capacity(buffer_len as usize); + internal_buffer.resize(buffer_len as usize, 0); + + let res = this.ext.offchain() + .map(|api| api.http_response_read_body( + offchain::HttpRequestId(request_id as u16), + &mut internal_buffer, + deadline_to_timestamp(deadline), + )) + .ok_or_else(|| UserError("Calling unavailable API ext_http_response_read_body: wasm"))?; + + Ok(match res { + Ok(read) => { + this.memory.set(buffer, &internal_buffer[..read]) + .map_err(|_| UserError("Invalid attempt to set memory in ext_http_response_read_body"))?; + + read as u32 + }, + Err(err) => { + u32::max_value() - err as u8 as u32 + 1 + } + }) + }, ext_sandbox_instantiate( dispatch_thunk_idx: usize, wasm_ptr: *const u8, diff --git a/substrate/core/offchain/Cargo.toml b/substrate/core/offchain/Cargo.toml index 14341e2458..da2aeb4334 100644 --- a/substrate/core/offchain/Cargo.toml +++ b/substrate/core/offchain/Cargo.toml @@ -13,6 +13,7 @@ futures = "0.1.25" log = "0.4" offchain-primitives = { package = "substrate-offchain-primitives", path = "./primitives" } parity-codec = { version = "3.3", features = ["derive"] } +parking_lot = "0.7.1" primitives = { package = "substrate-primitives", path = "../../core/primitives" } runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" } tokio = "0.1.7" diff --git a/substrate/core/offchain/src/api.rs b/substrate/core/offchain/src/api.rs index 25a8a2ebaa..d2c7630c24 100644 --- a/substrate/core/offchain/src/api.rs +++ b/substrate/core/offchain/src/api.rs @@ -16,9 +16,13 @@ use std::sync::Arc; use futures::{Stream, Future, sync::mpsc}; -use log::{info, debug, warn}; +use log::{info, debug, warn, error}; use parity_codec::Decode; -use primitives::OffchainExt; +use primitives::offchain::{ + Timestamp, HttpRequestId, HttpRequestStatus, HttpError, + Externalities as OffchainExt, + CryptoKind, CryptoKeyId, +}; use runtime_primitives::{ generic::BlockId, traits::{self, Extrinsic}, @@ -35,9 +39,122 @@ enum ExtMessage { /// NOTE this is done to prevent recursive calls into the runtime (which are not supported currently). pub(crate) struct AsyncApi(mpsc::UnboundedSender); +fn unavailable_yet(name: &str) -> R { + error!("This {:?} API is not available for offchain workers yet. Follow + https://github.com/paritytech/substrate/issues/1458 for details", name); + Default::default() +} + impl OffchainExt for AsyncApi { - fn submit_extrinsic(&mut self, ext: Vec) { - let _ = self.0.unbounded_send(ExtMessage::SubmitExtrinsic(ext)); + fn submit_transaction(&mut self, ext: Vec) -> Result<(), ()> { + self.0.unbounded_send(ExtMessage::SubmitExtrinsic(ext)) + .map(|_| ()) + .map_err(|_| ()) + } + + fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result { + unavailable_yet::<()>("new_crypto_key"); + Err(()) + } + + fn encrypt(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unavailable_yet::<()>("encrypt"); + Err(()) + } + + fn decrypt(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unavailable_yet::<()>("decrypt"); + Err(()) + } + + fn sign(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unavailable_yet::<()>("sign"); + Err(()) + } + + fn verify(&mut self, _key: Option, _msg: &[u8], _signature: &[u8]) -> Result { + unavailable_yet::<()>("verify"); + Err(()) + } + + fn timestamp(&mut self) -> Timestamp { + unavailable_yet("timestamp") + } + + fn sleep_until(&mut self, _deadline: Timestamp) { + unavailable_yet::<()>("sleep_until") + } + + fn random_seed(&mut self) -> [u8; 32] { + unavailable_yet("random_seed") + } + + fn local_storage_set(&mut self, _key: &[u8], _value: &[u8]) { + unavailable_yet("local_storage_set") + } + + fn local_storage_compare_and_set(&mut self, _key: &[u8], _old_value: &[u8], _new_value: &[u8]) { + unavailable_yet("local_storage_compare_and_set") + } + + fn local_storage_get(&mut self, _key: &[u8]) -> Option> { + unavailable_yet("local_storage_get") + } + + fn http_request_start( + &mut self, + _method: &str, + _uri: &str, + _meta: &[u8] + ) -> Result { + unavailable_yet::<()>("http_request_start"); + Err(()) + } + + fn http_request_add_header( + &mut self, + _request_id: HttpRequestId, + _name: &str, + _value: &str + ) -> Result<(), ()> { + unavailable_yet::<()>("http_request_add_header"); + Err(()) + } + + fn http_request_write_body( + &mut self, + _request_id: HttpRequestId, + _chunk: &[u8], + _deadline: Option + ) -> Result<(), HttpError> { + unavailable_yet::<()>("http_request_write_body"); + Err(HttpError::IoError) + } + + fn http_response_wait( + &mut self, + ids: &[HttpRequestId], + _deadline: Option + ) -> Vec { + unavailable_yet::<()>("http_response_wait"); + ids.iter().map(|_| HttpRequestStatus::Unknown).collect() + } + + fn http_response_headers( + &mut self, + _request_id: HttpRequestId + ) -> Vec<(Vec, Vec)> { + unavailable_yet("http_response_headers") + } + + fn http_response_read_body( + &mut self, + _request_id: HttpRequestId, + _buffer: &mut [u8], + _deadline: Option + ) -> Result { + unavailable_yet::<()>("http_response_read_body"); + Err(HttpError::IoError) } } diff --git a/substrate/core/offchain/src/lib.rs b/substrate/core/offchain/src/lib.rs index f176fadd0d..376f41b445 100644 --- a/substrate/core/offchain/src/lib.rs +++ b/substrate/core/offchain/src/lib.rs @@ -50,6 +50,8 @@ use transaction_pool::txpool::{Pool, ChainApi}; mod api; +pub mod testing; + pub use offchain_primitives::OffchainWorkerApi; /// An offchain workers manager. diff --git a/substrate/core/offchain/src/testing.rs b/substrate/core/offchain/src/testing.rs new file mode 100644 index 0000000000..3419665d0a --- /dev/null +++ b/substrate/core/offchain/src/testing.rs @@ -0,0 +1,244 @@ +// Copyright 2019 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 Externalities implementation for tests. + +use std::{ + collections::BTreeMap, + sync::Arc, +}; +use parking_lot::RwLock; +use primitives::offchain::{ + self, + HttpError, + HttpRequestId as RequestId, + HttpRequestStatus as RequestStatus, + Timestamp, + CryptoKind, + CryptoKeyId, +}; + +/// Pending request. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct PendingRequest { + /// HTTP method + pub method: String, + /// URI + pub uri: String, + /// Encoded Metadata + pub meta: Vec, + /// Request headers + pub headers: Vec<(String, String)>, + /// Request body + pub body: Vec, + /// Has the request been sent already. + pub sent: bool, + /// Response body + pub response: Vec, + /// Number of bytes already read from the response body. + pub read: usize, + /// Response headers + pub response_headers: Vec<(String, String)>, +} + +/// Internal state of the externalities. +/// +/// This can be used in tests to respond or assert stuff about interactions. +#[derive(Debug, Default)] +pub struct State { + /// A list of pending requests. + pub requests: BTreeMap, +} + +impl State { + /// Asserts that pending request has been submitted and fills it's response. + pub fn fulfill_pending_request( + &mut self, + id: u16, + expected: PendingRequest, + response: impl Into>, + response_headers: impl IntoIterator, + ) { + match self.requests.get_mut(&RequestId(id)) { + None => { + panic!("Missing expected request: {:?}.\n\nAll: {:?}", id, self.requests); + } + Some(req) => { + assert_eq!( + *req, + expected, + ); + req.response = response.into(); + req.response_headers = response_headers.into_iter().collect(); + } + } + } +} + +/// Implementation of offchain externalities used for tests. +#[derive(Clone, Default, Debug)] +pub struct TestOffchainExt(pub Arc>); + +impl offchain::Externalities for TestOffchainExt { + fn submit_transaction(&mut self, _ex: Vec) -> Result<(), ()> { + unimplemented!("not needed in tests so far") + } + + fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result { + unimplemented!("not needed in tests so far") + } + + fn encrypt(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unimplemented!("not needed in tests so far") + } + + fn decrypt(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unimplemented!("not needed in tests so far") + } + + fn sign(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unimplemented!("not needed in tests so far") + } + + fn verify(&mut self, _key: Option, _msg: &[u8], _signature: &[u8]) -> Result { + unimplemented!("not needed in tests so far") + } + + fn timestamp(&mut self) -> Timestamp { + unimplemented!("not needed in tests so far") + } + + fn sleep_until(&mut self, _deadline: Timestamp) { + unimplemented!("not needed in tests so far") + } + + fn random_seed(&mut self) -> [u8; 32] { + unimplemented!("not needed in tests so far") + } + + fn local_storage_set(&mut self, _key: &[u8], _value: &[u8]) { + unimplemented!("not needed in tests so far") + } + + fn local_storage_compare_and_set( + &mut self, + _key: &[u8], + _old_value: &[u8], + _new_value: &[u8] + ) { + unimplemented!("not needed in tests so far") + } + + fn local_storage_get(&mut self, _key: &[u8]) -> Option> { + unimplemented!("not needed in tests so far") + } + + fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result { + let mut state = self.0.write(); + let id = RequestId(state.requests.len() as u16); + state.requests.insert(id.clone(), PendingRequest { + method: method.into(), + uri: uri.into(), + meta: meta.into(), + ..Default::default() + }); + Ok(id) + } + + fn http_request_add_header( + &mut self, + request_id: RequestId, + name: &str, + value: &str, + ) -> Result<(), ()> { + let mut state = self.0.write(); + if let Some(req) = state.requests.get_mut(&request_id) { + req.headers.push((name.into(), value.into())); + Ok(()) + } else { + Err(()) + } + } + + fn http_request_write_body( + &mut self, + request_id: RequestId, + chunk: &[u8], + _deadline: Option + ) -> Result<(), HttpError> { + let mut state = self.0.write(); + if let Some(req) = state.requests.get_mut(&request_id) { + if chunk.is_empty() { + req.sent = true; + } + req.body.extend(chunk); + Ok(()) + } else { + Err(HttpError::IoError) + } + } + + fn http_response_wait( + &mut self, + ids: &[RequestId], + _deadline: Option, + ) -> Vec { + let state = self.0.read(); + + ids.iter().map(|id| match state.requests.get(id) { + Some(req) if req.response.is_empty() => RequestStatus::DeadlineReached, + None => RequestStatus::Unknown, + _ => RequestStatus::Finished(200), + }).collect() + } + + fn http_response_headers(&mut self, request_id: RequestId) -> Vec<(Vec, Vec)> { + let state = self.0.read(); + if let Some(req) = state.requests.get(&request_id) { + req.response_headers + .clone() + .into_iter() + .map(|(k, v)| (k.into_bytes(), v.into_bytes())) + .collect() + } else { + Default::default() + } + } + + fn http_response_read_body( + &mut self, + request_id: RequestId, + buffer: &mut [u8], + _deadline: Option + ) -> Result { + let mut state = self.0.write(); + if let Some(req) = state.requests.get_mut(&request_id) { + if req.read >= req.response.len() { + // Remove the pending request as per spec. + state.requests.remove(&request_id); + Ok(0) + } else { + let read = std::cmp::min(buffer.len(), req.response[req.read..].len()); + buffer[0..read].copy_from_slice(&req.response[req.read..read]); + req.read += read; + Ok(read) + } + } else { + Err(HttpError::IoError) + } + } +} + diff --git a/substrate/core/primitives/src/lib.rs b/substrate/core/primitives/src/lib.rs index c9008171df..69b9f1a960 100644 --- a/substrate/core/primitives/src/lib.rs +++ b/substrate/core/primitives/src/lib.rs @@ -56,6 +56,7 @@ pub mod ed25519; pub mod sr25519; pub mod hash; mod hasher; +pub mod offchain; pub mod sandbox; pub mod storage; pub mod uint; @@ -85,25 +86,11 @@ pub enum ExecutionContext { /// Context used for block construction. BlockConstruction, /// Offchain worker context. - OffchainWorker(Box), + OffchainWorker(Box), /// Context used for other calls. Other, } -/// An extended externalities for offchain workers. -pub trait OffchainExt { - /// Submits an extrinsics. - /// - /// The extrinsic will either go to the pool (signed) - /// or to the next produced block (inherent). - fn submit_extrinsic(&mut self, extrinsic: Vec); -} -impl OffchainExt for Box { - fn submit_extrinsic(&mut self, ex: Vec) { - (&mut **self).submit_extrinsic(ex) - } -} - /// Hex-serialized shim for `Vec`. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Hash, PartialOrd, Ord))] diff --git a/substrate/core/primitives/src/offchain.rs b/substrate/core/primitives/src/offchain.rs new file mode 100644 index 0000000000..764837d7b4 --- /dev/null +++ b/substrate/core/primitives/src/offchain.rs @@ -0,0 +1,410 @@ +// Copyright 2019 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 workers types + +use rstd::prelude::{Vec, Box}; +use rstd::convert::TryFrom; + +/// A type of supported crypto. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(C)] +pub enum CryptoKind { + /// SR25519 crypto (Schnorrkel) + Sr25519 = 1, + /// ED25519 crypto (Edwards) + Ed25519 = 2, +} + +impl TryFrom for CryptoKind { + type Error = (); + + fn try_from(kind: u32) -> Result { + match kind { + e if e == CryptoKind::Sr25519 as u8 as u32 => Ok(CryptoKind::Sr25519), + e if e == CryptoKind::Ed25519 as u8 as u32 => Ok(CryptoKind::Ed25519), + _ => Err(()) + } + } +} + +/// Opaque type for created crypto keys. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct CryptoKeyId(pub u16); + +/// Opaque type for offchain http requests. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct HttpRequestId(pub u16); + +/// An error enum returned by some http methods. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(C)] +pub enum HttpError { + /// The requested action couldn't been completed within a deadline. + DeadlineReached = 1, + /// There was an IO Error while processing the request. + IoError = 2, +} + +impl TryFrom for HttpError { + type Error = (); + + fn try_from(error: u32) -> Result { + match error { + e if e == HttpError::DeadlineReached as u8 as u32 => Ok(HttpError::DeadlineReached), + e if e == HttpError::IoError as u8 as u32 => Ok(HttpError::IoError), + _ => Err(()) + } + } +} + +/// Status of the HTTP request +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum HttpRequestStatus { + /// Deadline was reached while we waited for this request to finish. + /// + /// Note the deadline is controlled by the calling part, it not necessarily means + /// that the request has timed out. + DeadlineReached, + /// Request timed out. + /// + /// This means that the request couldn't be completed by the host environment + /// within a reasonable time (according to the host), has now been terminated + /// and is considered finished. + /// To retry the request you need to construct it again. + Timeout, + /// Request status of this ID is not known. + Unknown, + /// The request has finished with given status code. + Finished(u16), +} + +impl From for u32 { + fn from(status: HttpRequestStatus) -> Self { + match status { + HttpRequestStatus::Unknown => 0, + HttpRequestStatus::DeadlineReached => 10, + HttpRequestStatus::Timeout => 20, + HttpRequestStatus::Finished(code) => code as u32, + } + } +} + +impl TryFrom for HttpRequestStatus { + type Error = (); + + fn try_from(status: u32) -> Result { + match status { + 0 => Ok(HttpRequestStatus::Unknown), + 10 => Ok(HttpRequestStatus::DeadlineReached), + 20 => Ok(HttpRequestStatus::Timeout), + 100...999 => Ok(HttpRequestStatus::Finished(status as u16)), + _ => Err(()), + } + } +} + +/// Opaque timestamp type +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Timestamp(u64); + +/// Duration type +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Duration(u64); + +impl Duration { + /// Create new duration representing given number of milliseconds. + pub fn from_millis(millis: u64) -> Self { + Duration(millis) + } + + /// Returns number of milliseconds this Duration represents. + pub fn millis(&self) -> u64 { + self.0 + } +} + +impl Timestamp { + /// Creates new `Timestamp` given unix timestamp in miliseconds. + pub fn from_unix_millis(millis: u64) -> Self { + Timestamp(millis) + } + + /// Increase the timestamp by given `Duration`. + pub fn add(&self, duration: Duration) -> Timestamp { + Timestamp(self.0.saturating_add(duration.0)) + } + + /// Decrease the timestamp by given `Duration` + pub fn sub(&self, duration: Duration) -> Timestamp { + Timestamp(self.0.saturating_sub(duration.0)) + } + + /// Returns a saturated difference (Duration) between two Timestamps. + pub fn diff(&self, other: &Self) -> Duration { + Duration(self.0.saturating_sub(other.0)) + } + + /// Return number of milliseconds since UNIX epoch. + pub fn unix_millis(&self) -> u64 { + self.0 + } +} + +/// An extended externalities for offchain workers. +pub trait Externalities { + /// Submit transaction. + /// + /// The transaction will end up in the pool and be propagated to others. + fn submit_transaction(&mut self, extrinsic: Vec) -> Result<(), ()>; + + /// Create new key(pair) for signing/encryption/decryption. + /// + /// Returns an error if given crypto kind is not supported. + fn new_crypto_key(&mut self, crypto: CryptoKind) -> Result; + + /// Encrypt a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if `key` is not available or does not exist. + fn encrypt(&mut self, key: Option, data: &[u8]) -> Result, ()>; + + /// Decrypt a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if data cannot be decrypted or the `key` is not available or does not exist. + fn decrypt(&mut self, key: Option, data: &[u8]) -> Result, ()>; + + /// Sign a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if `key` is not available or does not exist. + fn sign(&mut self, key: Option, data: &[u8]) -> Result, ()>; + + /// Verifies that `signature` for `msg` matches given `key`. + /// + /// Returns an `Ok` with `true` in case it does, `false` in case it doesn't. + /// Returns an error in case the key is not available or does not exist or the parameters + /// lengths are incorrect. + fn verify(&mut self, key: Option, msg: &[u8], signature: &[u8]) -> Result; + + /// Returns current UNIX timestamp (in millis) + fn timestamp(&mut self) -> Timestamp; + + /// Pause the execution until `deadline` is reached. + fn sleep_until(&mut self, deadline: Timestamp); + + /// Returns a random seed. + /// + /// This is a trully random non deterministic seed generated by host environment. + /// Obviously fine in the off-chain worker context. + fn random_seed(&mut self) -> [u8; 32]; + + /// Sets a value in the local storage. + /// + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_set(&mut self, key: &[u8], value: &[u8]); + + /// Sets a value in the local storage if it matches current value. + /// + /// Since multiple offchain workers may be running concurrently, to prevent + /// data races use CAS to coordinate between them. + /// + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_compare_and_set(&mut self, key: &[u8], old_value: &[u8], new_value: &[u8]); + + /// Gets a value from the local storage. + /// + /// If the value does not exist in the storage `None` will be returned. + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_get(&mut self, key: &[u8]) -> Option>; + + /// Initiaties a http request given HTTP verb and the URL. + /// + /// Meta is a future-reserved field containing additional, parity-codec encoded parameters. + /// Returns the id of newly started request. + fn http_request_start( + &mut self, + method: &str, + uri: &str, + meta: &[u8] + ) -> Result; + + /// Append header to the request. + fn http_request_add_header( + &mut self, + request_id: HttpRequestId, + name: &str, + value: &str + ) -> Result<(), ()>; + + /// Write a chunk of request body. + /// + /// Writing an empty chunks finalises the request. + /// Passing `None` as deadline blocks forever. + /// + /// Returns an error in case deadline is reached or the chunk couldn't be written. + fn http_request_write_body( + &mut self, + request_id: HttpRequestId, + chunk: &[u8], + deadline: Option + ) -> Result<(), HttpError>; + + /// Block and wait for the responses for given requests. + /// + /// Returns a vector of request statuses (the len is the same as ids). + /// Note that if deadline is not provided the method will block indefinitely, + /// otherwise unready responses will produce `DeadlineReached` status. + /// + /// Passing `None` as deadline blocks forever. + fn http_response_wait( + &mut self, + ids: &[HttpRequestId], + deadline: Option + ) -> Vec; + + /// Read all response headers. + /// + /// Returns a vector of pairs `(HeaderKey, HeaderValue)`. + fn http_response_headers( + &mut self, + request_id: HttpRequestId + ) -> Vec<(Vec, Vec)>; + + /// Read a chunk of body response to given buffer. + /// + /// Returns the number of bytes written or an error in case a deadline + /// is reached or server closed the connection. + /// Passing `None` as a deadline blocks forever. + fn http_response_read_body( + &mut self, + request_id: HttpRequestId, + buffer: &mut [u8], + deadline: Option + ) -> Result; + +} +impl Externalities for Box { + fn submit_transaction(&mut self, ex: Vec) -> Result<(), ()> { + (&mut **self).submit_transaction(ex) + } + + fn new_crypto_key(&mut self, crypto: CryptoKind) -> Result { + (&mut **self).new_crypto_key(crypto) + } + + fn encrypt(&mut self, key: Option, data: &[u8]) -> Result, ()> { + (&mut **self).encrypt(key, data) + } + + fn decrypt(&mut self, key: Option, data: &[u8]) -> Result, ()> { + (&mut **self).decrypt(key, data) + } + + fn sign(&mut self, key: Option, data: &[u8]) -> Result, ()> { + (&mut **self).sign(key, data) + } + + fn verify(&mut self, key: Option, msg: &[u8], signature: &[u8]) -> Result { + (&mut **self).verify(key, msg, signature) + } + + fn timestamp(&mut self) -> Timestamp { + (&mut **self).timestamp() + } + + fn sleep_until(&mut self, deadline: Timestamp) { + (&mut **self).sleep_until(deadline) + } + + fn random_seed(&mut self) -> [u8; 32] { + (&mut **self).random_seed() + } + + fn local_storage_set(&mut self, key: &[u8], value: &[u8]) { + (&mut **self).local_storage_set(key, value) + } + + fn local_storage_compare_and_set(&mut self, key: &[u8], old_value: &[u8], new_value: &[u8]) { + (&mut **self).local_storage_compare_and_set(key, old_value, new_value) + } + + fn local_storage_get(&mut self, key: &[u8]) -> Option> { + (&mut **self).local_storage_get(key) + } + + fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result { + (&mut **self).http_request_start(method, uri, meta) + } + + fn http_request_add_header(&mut self, request_id: HttpRequestId, name: &str, value: &str) -> Result<(), ()> { + (&mut **self).http_request_add_header(request_id, name, value) + } + + fn http_request_write_body( + &mut self, + request_id: HttpRequestId, + chunk: &[u8], + deadline: Option + ) -> Result<(), HttpError> { + (&mut **self).http_request_write_body(request_id, chunk, deadline) + } + + fn http_response_wait(&mut self, ids: &[HttpRequestId], deadline: Option) -> Vec { + (&mut **self).http_response_wait(ids, deadline) + } + + fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec, Vec)> { + (&mut **self).http_response_headers(request_id) + } + + fn http_response_read_body( + &mut self, + request_id: HttpRequestId, + buffer: &mut [u8], + deadline: Option + ) -> Result { + (&mut **self).http_response_read_body(request_id, buffer, deadline) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn timestamp_ops() { + let t = Timestamp(5); + assert_eq!(t.add(Duration::from_millis(10)), Timestamp(15)); + assert_eq!(t.sub(Duration::from_millis(10)), Timestamp(0)); + assert_eq!(t.diff(&Timestamp(3)), Duration(2)); + } +} diff --git a/substrate/core/sr-io/Cargo.toml b/substrate/core/sr-io/Cargo.toml index dbe699b4b5..0c97ba8050 100644 --- a/substrate/core/sr-io/Cargo.toml +++ b/substrate/core/sr-io/Cargo.toml @@ -19,6 +19,9 @@ environmental = { version = "1.0.1", optional = true } substrate-state-machine = { path = "../state-machine", optional = true } trie = { package = "substrate-trie", path = "../trie", optional = true } +[dev-dependencies] +substrate-offchain = { path = "../offchain" } + [features] default = ["std"] std = [ diff --git a/substrate/core/sr-io/src/lib.rs b/substrate/core/sr-io/src/lib.rs index 314aa3b61e..cd9b43798b 100644 --- a/substrate/core/sr-io/src/lib.rs +++ b/substrate/core/sr-io/src/lib.rs @@ -33,6 +33,7 @@ use rstd::vec::Vec; pub use codec; pub use primitives::Blake2Hasher; +use primitives::offchain::{Timestamp, HttpRequestId, HttpRequestStatus, HttpError, CryptoKind, CryptoKeyId}; /// Error verifying ECDSA signature pub enum EcdsaVerifyError { @@ -44,6 +45,8 @@ pub enum EcdsaVerifyError { BadSignature, } +pub mod offchain; + /// Trait for things which can be printed. pub trait Printable { /// Print the object. @@ -226,12 +229,140 @@ export_api! { export_api! { pub(crate) trait OffchainApi { - /// Submit extrinsic from the runtime. + /// Submit transaction to the pool. /// - /// Depending on the kind of extrinsic it will either be: - /// 1. scheduled to be included in the next produced block (inherent) - /// 2. added to the pool and propagated (transaction) - fn submit_extrinsic(data: &T); + /// The transaction will end up in the pool. + fn submit_transaction(data: &T) -> Result<(), ()>; + + /// Create new key(pair) for signing/encryption/decryption. + /// + /// Returns an error if given crypto kind is not supported. + fn new_crypto_key(crypto: CryptoKind) -> Result; + + /// Encrypt a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if `key` is not available or does not exist. + fn encrypt(key: Option, data: &[u8]) -> Result, ()>; + + /// Decrypt a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if data cannot be decrypted or the `key` is not available or does not exist. + fn decrypt(key: Option, data: &[u8]) -> Result, ()>; + + /// Sign a piece of data using given crypto key. + /// + /// If `key` is `None`, it will attempt to use current authority key. + /// + /// Returns an error if `key` is not available or does not exist. + fn sign(key: Option, data: &[u8]) -> Result, ()>; + + /// Verifies that `signature` for `msg` matches given `key`. + /// + /// Returns an `Ok` with `true` in case it does, `false` in case it doesn't. + /// Returns an error in case the key is not available or does not exist or the parameters + /// lengths are incorrect. + fn verify(key: Option, msg: &[u8], signature: &[u8]) -> Result; + + /// Returns current UNIX timestamp (in millis) + fn timestamp() -> Timestamp; + + /// Pause the execution until `deadline` is reached. + fn sleep_until(deadline: Timestamp); + + /// Returns a random seed. + /// + /// This is a trully random non deterministic seed generated by host environment. + /// Obviously fine in the off-chain worker context. + fn random_seed() -> [u8; 32]; + + /// Sets a value in the local storage. + /// + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_set(key: &[u8], value: &[u8]); + + /// Sets a value in the local storage if it matches current value. + /// + /// Since multiple offchain workers may be running concurrently, to prevent + /// data races use CAS to coordinate between them. + /// + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_compare_and_set(key: &[u8], old_value: &[u8], new_value: &[u8]); + + /// Gets a value from the local storage. + /// + /// If the value does not exist in the storage `None` will be returned. + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_get(key: &[u8]) -> Option>; + + /// Initiaties a http request given HTTP verb and the URL. + /// + /// Meta is a future-reserved field containing additional, parity-codec encoded parameters. + /// Returns the id of newly started request. + fn http_request_start( + method: &str, + uri: &str, + meta: &[u8] + ) -> Result; + + /// Append header to the request. + fn http_request_add_header( + request_id: HttpRequestId, + name: &str, + value: &str + ) -> Result<(), ()>; + + /// Write a chunk of request body. + /// + /// Writing an empty chunks finalises the request. + /// Passing `None` as deadline blocks forever. + /// + /// Returns an error in case deadline is reached or the chunk couldn't be written. + fn http_request_write_body( + request_id: HttpRequestId, + chunk: &[u8], + deadline: Option + ) -> Result<(), HttpError>; + + /// Block and wait for the responses for given requests. + /// + /// Returns a vector of request statuses (the len is the same as ids). + /// Note that if deadline is not provided the method will block indefinitely, + /// otherwise unready responses will produce `DeadlineReached` status. + /// + /// Passing `None` as deadline blocks forever. + fn http_response_wait( + ids: &[HttpRequestId], + deadline: Option + ) -> Vec; + + /// Read all response headers. + /// + /// Returns a vector of pairs `(HeaderKey, HeaderValue)`. + /// NOTE response headers have to be read before response body. + fn http_response_headers( + request_id: HttpRequestId + ) -> Vec<(Vec, Vec)>; + + /// Read a chunk of body response to given buffer. + /// + /// Returns the number of bytes written or an error in case a deadline + /// is reached or server closed the connection. + /// If `0` is returned it means that the response has been fully consumed + /// and the `request_id` is now invalid. + /// NOTE this implies that response headers must be read before draining the body. + /// Passing `None` as a deadline blocks forever. + fn http_response_read_body( + request_id: HttpRequestId, + buffer: &mut [u8], + deadline: Option + ) -> Result; } } diff --git a/substrate/core/sr-io/src/offchain/http.rs b/substrate/core/sr-io/src/offchain/http.rs new file mode 100644 index 0000000000..0708f83717 --- /dev/null +++ b/substrate/core/sr-io/src/offchain/http.rs @@ -0,0 +1,571 @@ +// Copyright 2019 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 . + +//! A non-std set of HTTP types. + +use rstd::str; +use rstd::prelude::Vec; +#[cfg(not(feature = "std"))] +use rstd::prelude::vec; +use primitives::offchain::{ + Timestamp, + HttpRequestId as RequestId, + HttpRequestStatus as RequestStatus, + HttpError, +}; + +/// Request method (HTTP verb) +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Method { + /// GET request + Get, + /// POST request + Post, + /// PUT request + Put, + /// PATCH request + Patch, + /// DELETE request + Delete, + /// Custom verb + Other(&'static str), +} + +impl AsRef for Method { + fn as_ref(&self) -> &str { + match *self { + Method::Get => "GET", + Method::Post => "POST", + Method::Put => "PUT", + Method::Patch => "PATCH", + Method::Delete => "DELETE", + Method::Other(m) => m, + } + } +} + +mod header { + use super::*; + + /// A header type. + #[derive(Clone, PartialEq, Eq)] + #[cfg_attr(feature = "std", derive(Debug))] + pub struct Header { + name: Vec, + value: Vec, + } + + impl Header { + /// Creates new header given it's name and value. + pub fn new(name: &str, value: &str) -> Self { + Header { + name: name.as_bytes().to_vec(), + value: value.as_bytes().to_vec(), + } + } + + /// Returns the name of this header. + pub fn name(&self) -> &str { + // Header keys are always produced from `&str` so this is safe. + // we don't store them as `Strings` to avoid bringing `alloc::String` to rstd + // or here. + unsafe { str::from_utf8_unchecked(&self.name) } + } + + /// Returns the value of this header. + pub fn value(&self) -> &str { + // Header values are always produced from `&str` so this is safe. + // we don't store them as `Strings` to avoid bringing `alloc::String` to rstd + // or here. + unsafe { str::from_utf8_unchecked(&self.value) } + } + } +} + +/// An HTTP request builder. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Request<'a, T = Vec<&'static [u8]>> { + /// Request method + pub method: Method, + /// Request URL + pub url: &'a str, + /// Body of the request + pub body: T, + /// Deadline to finish sending the request + pub deadline: Option, + /// Request list of headers. + headers: Vec, +} + +impl Default for Request<'static, T> { + fn default() -> Self { + Request { + method: Method::Get, + url: "http://localhost", + headers: Vec::new(), + body: Default::default(), + deadline: None, + } + } +} + +impl<'a> Request<'a> { + /// Start a simple GET request + pub fn get(url: &'a str) -> Self { + Self::new(url) + } +} + +impl<'a, T> Request<'a, T> { + /// Create new POST request with given body. + pub fn post(url: &'a str, body: T) -> Self { + let req: Request = Request::default(); + + Request { + url, + body, + method: Method::Post, + headers: req.headers, + deadline: req.deadline, + } + } +} + +impl<'a, T: Default> Request<'a, T> { + /// Create new Request builder with given URL and body. + pub fn new(url: &'a str) -> Self { + Request::default().url(url) + } + + /// Change the method of the request + pub fn method(mut self, method: Method) -> Self { + self.method = method; + self + } + + /// Change the URL of the request. + pub fn url(mut self, url: &'a str) -> Self { + self.url = url; + self + } + + /// Set the body of the request. + pub fn body(mut self, body: T) -> Self { + self.body = body; + self + } + + /// Add a header. + pub fn add_header(mut self, name: &str, value: &str) -> Self { + self.headers.push(header::Header::new(name, value)); + self + } + + /// Set the deadline of the request. + pub fn deadline(mut self, deadline: Timestamp) -> Self { + self.deadline = Some(deadline); + self + } +} + +impl<'a, I: AsRef<[u8]>, T: IntoIterator> Request<'a, T> { + /// Send the request and return a handle. + /// + /// Err is returned in case the deadline is reached + /// or the request timeouts. + pub fn send(self) -> Result { + let meta = &[]; + + // start an http request. + let id = crate::http_request_start(self.method.as_ref(), self.url, meta).map_err(|_| HttpError::IoError)?; + + // add custom headers + for header in &self.headers { + crate::http_request_add_header( + id, + header.name(), + header.value(), + ).map_err(|_| HttpError::IoError)? + } + + // write body + for chunk in self.body { + crate::http_request_write_body(id, chunk.as_ref(), self.deadline)?; + } + + // finalise the request + crate::http_request_write_body(id, &[], self.deadline)?; + + Ok(PendingRequest { + id, + }) + } +} + +/// A request error +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Error { + /// Deadline has been reached. + DeadlineReached, + /// Request had timed out. + Timeout, + /// Unknown error has been ecountered. + Unknown, +} + +/// A struct representing an uncompleted http request. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct PendingRequest { + /// Request ID + pub id: RequestId, +} + +/// A result of waiting for a pending request. +pub type HttpResult = Result; + +impl PendingRequest { + /// Wait for the request to complete. + /// + /// NOTE this waits for the request indefinitely. + pub fn wait(self) -> HttpResult { + match self.try_wait(None) { + Ok(res) => res, + Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"), + } + } + + /// Attempts to wait for the request to finish, + /// but will return `Err` in case the deadline is reached. + pub fn try_wait(self, deadline: impl Into>) -> Result { + Self::try_wait_all(vec![self], deadline).pop().expect("One request passed, one status received; qed") + } + + /// Wait for all provided requests. + pub fn wait_all(requests: Vec) -> Vec { + Self::try_wait_all(requests, None) + .into_iter() + .map(|r| match r { + Ok(r) => r, + Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"), + }) + .collect() + } + + /// Attempt to wait for all provided requests, but up to given deadline. + /// + /// Requests that are complete will resolve to an `Ok` others will return a `DeadlineReached` error. + pub fn try_wait_all( + requests: Vec, + deadline: impl Into> + ) -> Vec> { + let ids = requests.iter().map(|r| r.id).collect::>(); + let statuses = crate::http_response_wait(&ids, deadline.into()); + + statuses + .into_iter() + .zip(requests.into_iter()) + .map(|(status, req)| match status { + RequestStatus::DeadlineReached => Err(req), + RequestStatus::Timeout => Ok(Err(Error::Timeout)), + RequestStatus::Unknown => Ok(Err(Error::Unknown)), + RequestStatus::Finished(code) => Ok(Ok(Response::new(req.id, code))), + }) + .collect() + } +} + +/// A HTTP response. +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Response { + /// Request id + pub id: RequestId, + /// Response status code + pub code: u16, + /// A collection of headers. + headers: Option, +} + +impl Response { + fn new(id: RequestId, code: u16) -> Self { + Self { + id, + code, + headers: None, + } + } + + /// Retrieve the headers for this response. + pub fn headers(&mut self) -> &Headers { + if self.headers.is_none() { + self.headers = Some(Headers { raw: crate::http_response_headers(self.id) }); + } + self.headers.as_ref().expect("Headers were just set; qed") + } + + /// Retrieve the body of this response. + pub fn body(&self) -> ResponseBody { + ResponseBody::new(self.id) + } +} + +/// A buffered byte iterator over response body. +/// +/// Note that reading the body may return `None` in following cases: +/// 1. Either the deadline you've set is reached (check via `#error`; +/// In such case you can resume the reader by setting a new deadline) +/// 2. Or because of IOError. In such case the reader is not resumable and will keep +/// returning `None`. +/// 3. The body has been returned. The reader will keep returning `None`. +#[derive(Clone)] +pub struct ResponseBody { + id: RequestId, + error: Option, + buffer: [u8; 4096], + filled_up_to: Option, + position: usize, + deadline: Option, +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for ResponseBody { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("ResponseBody") + .field("id", &self.id) + .field("error", &self.error) + .field("buffer", &self.buffer.len()) + .field("filled_up_to", &self.filled_up_to) + .field("position", &self.position) + .field("deadline", &self.deadline) + .finish() + } +} + +impl ResponseBody { + fn new(id: RequestId) -> Self { + ResponseBody { + id, + error: None, + buffer: [0_u8; 4096], + filled_up_to: None, + position: 0, + deadline: None, + } + } + + /// Set the deadline for reading the body. + pub fn deadline(&mut self, deadline: impl Into>) { + self.deadline = deadline.into(); + self.error = None; + } + + /// Return an error that caused the iterator to return `None`. + /// + /// If the error is `DeadlineReached` you can resume the iterator by setting + /// a new deadline. + pub fn error(&self) -> &Option { + &self.error + } +} + +impl Iterator for ResponseBody { + type Item = u8; + + fn next(&mut self) -> Option { + if self.error.is_some() { + return None; + } + + if self.filled_up_to.is_none() { + let result = crate::http_response_read_body(self.id, &mut self.buffer, self.deadline); + match result { + Err(e) => { + self.error = Some(e); + return None; + } + Ok(0) => { + return None; + } + Ok(size) => { + self.position = 0; + self.filled_up_to = Some(size); + } + } + } + + if Some(self.position) == self.filled_up_to { + self.filled_up_to = None; + return self.next(); + } + + let result = self.buffer[self.position]; + self.position += 1; + Some(result) + } +} + +/// A collection of Headers in the response. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Headers { + /// Raw headers + pub raw: Vec<(Vec, Vec)>, +} + +impl Headers { + /// Retrieve a single header from the list of headers. + /// + /// Note this method is linearly looking from all the headers + /// comparing them with the needle byte-by-byte. + /// If you want to consume multiple headers it's better to iterate + /// and collect them on your own. + pub fn find(&self, name: &str) -> Option<&str> { + let raw = name.as_bytes(); + for &(ref key, ref val) in &self.raw { + if &**key == raw { + return str::from_utf8(&val).ok() + } + } + None + } + + /// Convert this headers into an iterator. + pub fn into_iter(&self) -> HeadersIterator { + HeadersIterator { collection: &self.raw, index: None } + } +} + +/// A custom iterator traversing all the headers. +#[derive(Clone)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct HeadersIterator<'a> { + collection: &'a [(Vec, Vec)], + index: Option, +} + +impl<'a> HeadersIterator<'a> { + /// Move the iterator to the next position. + /// + /// Returns `true` is `current` has been set by this call. + pub fn next(&mut self) -> bool { + let index = self.index.map(|x| x + 1).unwrap_or(0); + self.index = Some(index); + index < self.collection.len() + } + + /// Returns current element (if any). + /// + /// Note that you have to call `next` prior to calling this + pub fn current(&self) -> Option<(&str, &str)> { + self.collection.get(self.index?) + .map(|val| (str::from_utf8(&val.0).unwrap_or(""), str::from_utf8(&val.1).unwrap_or(""))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{TestExternalities, with_externalities}; + use substrate_offchain::testing; + + #[test] + fn should_send_a_basic_request_and_get_response() { + let offchain = testing::TestOffchainExt::default(); + let mut t = TestExternalities::default(); + let state = offchain.0.clone(); + t.set_offchain_externalities(offchain); + + with_externalities(&mut t, || { + let request: Request = Request::get("http://localhost:1234"); + let pending = request + .add_header("X-Auth", "hunter2") + .send() + .unwrap(); + // make sure it's sent correctly + state.write().fulfill_pending_request( + 0, + testing::PendingRequest { + method: "GET".into(), + uri: "http://localhost:1234".into(), + headers: vec![("X-Auth".into(), "hunter2".into())], + sent: true, + ..Default::default() + }, + b"1234".to_vec(), + None, + ); + + // wait + let mut response = pending.wait().unwrap(); + + // then check the response + let mut headers = response.headers().into_iter(); + assert_eq!(headers.current(), None); + assert_eq!(headers.next(), false); + assert_eq!(headers.current(), None); + + let body = response.body(); + assert_eq!(body.clone().collect::>(), b"1234".to_vec()); + assert_eq!(body.error(), &None); + }) + } + + #[test] + fn should_send_a_post_request() { + let offchain = testing::TestOffchainExt::default(); + let mut t = TestExternalities::default(); + let state = offchain.0.clone(); + t.set_offchain_externalities(offchain); + + with_externalities(&mut t, || { + let pending = Request::default() + .method(Method::Post) + .url("http://localhost:1234") + .body(vec![b"1234"]) + .send() + .unwrap(); + // make sure it's sent correctly + state.write().fulfill_pending_request( + 0, + testing::PendingRequest { + method: "POST".into(), + uri: "http://localhost:1234".into(), + body: b"1234".to_vec(), + sent: true, + ..Default::default() + }, + b"1234".to_vec(), + Some(("Test".to_owned(), "Header".to_owned())), + ); + + // wait + let mut response = pending.wait().unwrap(); + + // then check the response + let mut headers = response.headers().into_iter(); + assert_eq!(headers.current(), None); + assert_eq!(headers.next(), true); + assert_eq!(headers.current(), Some(("Test", "Header"))); + + let body = response.body(); + assert_eq!(body.clone().collect::>(), b"1234".to_vec()); + assert_eq!(body.error(), &None); + }) + } +} diff --git a/substrate/core/sr-io/src/offchain/mod.rs b/substrate/core/sr-io/src/offchain/mod.rs new file mode 100644 index 0000000000..6b82f77111 --- /dev/null +++ b/substrate/core/sr-io/src/offchain/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2019 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 . + +//! A collection of higher lever helpers for offchain workers. + +pub mod http; diff --git a/substrate/core/sr-io/with_std.rs b/substrate/core/sr-io/with_std.rs index 1bc0141c1d..01e7614de6 100644 --- a/substrate/core/sr-io/with_std.rs +++ b/substrate/core/sr-io/with_std.rs @@ -28,7 +28,7 @@ pub use substrate_state_machine::{ }; use environmental::environmental; -use primitives::{hexdisplay::HexDisplay, H256}; +use primitives::{offchain, hexdisplay::HexDisplay, H256}; #[cfg(feature = "std")] use std::collections::HashMap; @@ -251,12 +251,142 @@ impl HashingApi for () { } } +fn with_offchain(f: impl FnOnce(&mut offchain::Externalities) -> R, msg: &'static str) -> R { + ext::with(|ext| ext + .offchain() + .map(|ext| f(ext)) + .expect(msg) + ).expect("offchain-worker functions cannot be called outside of an Externalities-provided environment.") +} + impl OffchainApi for () { - fn submit_extrinsic(data: &T) { - ext::with(|ext| ext - .submit_extrinsic(codec::Encode::encode(data)) - .expect("submit_extrinsic can be called only in offchain worker context") - ).expect("submit_extrinsic cannot be called outside of an Externalities-provided environment.") + fn submit_transaction(data: &T) -> Result<(), ()> { + with_offchain(|ext| { + ext.submit_transaction(codec::Encode::encode(data)) + }, "submit_transaction can be called only in the offchain worker context") + } + + fn new_crypto_key(crypto: offchain::CryptoKind) -> Result { + with_offchain(|ext| { + ext.new_crypto_key(crypto) + }, "new_crypto_key can be called only in the offchain worker context") + } + + fn encrypt(key: Option, data: &[u8]) -> Result, ()> { + with_offchain(|ext| { + ext.encrypt(key, data) + }, "encrypt can be called only in the offchain worker context") + } + + fn decrypt(key: Option, data: &[u8]) -> Result, ()> { + with_offchain(|ext| { + ext.decrypt(key, data) + }, "decrypt can be called only in the offchain worker context") + } + + fn sign(key: Option, data: &[u8]) -> Result, ()> { + with_offchain(|ext| { + ext.sign(key, data) + }, "sign can be called only in the offchain worker context") + } + + fn verify(key: Option, msg: &[u8], signature: &[u8]) -> Result { + with_offchain(|ext| { + ext.verify(key, msg, signature) + }, "verify can be called only in the offchain worker context") + } + + fn timestamp() -> offchain::Timestamp { + with_offchain(|ext| { + ext.timestamp() + }, "timestamp can be called only in the offchain worker context") + } + + fn sleep_until(deadline: Timestamp) { + with_offchain(|ext| { + ext.sleep_until(deadline) + }, "sleep_until can be called only in the offchain worker context") + } + + fn random_seed() -> [u8; 32] { + with_offchain(|ext| { + ext.random_seed() + }, "random_seed can be called only in the offchain worker context") + } + + fn local_storage_set(key: &[u8], value: &[u8]) { + with_offchain(|ext| { + ext.local_storage_set(key, value) + }, "local_storage_set can be called only in the offchain worker context") + } + + fn local_storage_compare_and_set(key: &[u8], old_value: &[u8], new_value: &[u8]) { + with_offchain(|ext| { + ext.local_storage_compare_and_set(key, old_value, new_value) + }, "local_storage_compare_and_set can be called only in the offchain worker context") + } + + fn local_storage_get(key: &[u8]) -> Option> { + with_offchain(|ext| { + ext.local_storage_get(key) + }, "local_storage_get can be called only in the offchain worker context") + } + + fn http_request_start( + method: &str, + uri: &str, + meta: &[u8] + ) -> Result { + with_offchain(|ext| { + ext.http_request_start(method, uri, meta) + }, "http_request_start can be called only in the offchain worker context") + } + + fn http_request_add_header( + request_id: offchain::HttpRequestId, + name: &str, + value: &str + ) -> Result<(), ()> { + with_offchain(|ext| { + ext.http_request_add_header(request_id, name, value) + }, "http_request_add_header can be called only in the offchain worker context") + } + + fn http_request_write_body( + request_id: offchain::HttpRequestId, + chunk: &[u8], + deadline: Option + ) -> Result<(), offchain::HttpError> { + with_offchain(|ext| { + ext.http_request_write_body(request_id, chunk, deadline) + }, "http_request_write_body can be called only in the offchain worker context") + } + + fn http_response_wait( + ids: &[offchain::HttpRequestId], + deadline: Option + ) -> Vec { + with_offchain(|ext| { + ext.http_response_wait(ids, deadline) + }, "http_response_wait can be called only in the offchain worker context") + } + + fn http_response_headers( + request_id: offchain::HttpRequestId + ) -> Vec<(Vec, Vec)> { + with_offchain(|ext| { + ext.http_response_headers(request_id) + }, "http_response_headers can be called only in the offchain worker context") + } + + fn http_response_read_body( + request_id: offchain::HttpRequestId, + buffer: &mut [u8], + deadline: Option + ) -> Result { + with_offchain(|ext| { + ext.http_response_read_body(request_id, buffer, deadline) + }, "http_response_read_body can be called only in the offchain worker context") } } diff --git a/substrate/core/sr-io/without_std.rs b/substrate/core/sr-io/without_std.rs index de87aeddbd..84e717ffae 100644 --- a/substrate/core/sr-io/without_std.rs +++ b/substrate/core/sr-io/without_std.rs @@ -19,8 +19,8 @@ pub use rstd; pub use rstd::{mem, slice}; use core::{intrinsics, panic::PanicInfo}; -use rstd::{vec::Vec, cell::Cell}; -use primitives::Blake2Hasher; +use rstd::{vec::Vec, cell::Cell, convert::TryInto}; +use primitives::{offchain, Blake2Hasher}; #[cfg(not(feature = "no_panic_handler"))] #[panic_handler] @@ -219,7 +219,6 @@ pub mod ext { /// # Returns /// /// - `0` if no value exists to the given key. `written_out` is set to `u32::max_value()`. - /// /// - Otherwise, pointer to the value in memory. `written_out` contains the length of the value. fn ext_get_allocated_storage(key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8; /// Gets the value of the given key from storage. @@ -331,8 +330,206 @@ pub mod ext { // Offchain-worker Context //================================ - /// Submit extrinsic. - fn ext_submit_extrinsic(data: *const u8, len: u32); + /// Submit transaction. + /// + /// # Returns + /// + /// - 0 if it was successfuly added to the pool + /// - nonzero otherwise. + fn ext_submit_transaction(data: *const u8, len: u32) -> u32; + + /// Create new key(pair) for signing/encryption/decryption. + /// + /// # Returns + /// + /// - A crypto key id (if the value is less than u16::max_value) + /// - `u32::max_value` in case the crypto is not supported + fn ext_new_crypto_key(crypto: u32) -> u32; + + /// Encrypt a piece of data using given crypto key. + /// + /// If `key` is `0`, it will attempt to use current authority key. + /// + /// # Returns + /// + /// - `0` in case the key is invalid, `msg_len` is set to `u32::max_value` + /// - Otherwise, pointer to the encrypted message in memory, + /// `msg_len` contains the length of the message. + fn ext_encrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8; + + /// Decrypt a piece of data using given crypto key. + /// + /// If `key `is `0`, it will attempt to use current authority key. + /// + /// # Returns + /// + /// - `0` in case the key is invalid or data couldn't be decrypted, + /// `msg_len` is set to `u32::max_value` + /// - Otherwise, pointer to the decrypted message in memory, + /// `msg_len` contains the length of the message. + fn ext_decrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8; + + /// Sign a piece of data using given crypto key. + /// + /// If `key` is `0`, it will attempt to use current authority key. + /// + /// # Returns + /// + /// - `0` in case the key is invalid, + /// `sig_data_len` is set to `u32::max_value` + /// - Otherwise, pointer to the signature in memory, + /// `sig_data_len` contains the length of the signature. + fn ext_sign(key: u32, data: *const u8, data_len: u32, sig_data_len: *mut u32) -> *mut u8; + + /// Verifies that `signature` for `msg` matches given `key`. + /// + /// If `key` is `0`, it will attempt to use current authority key. + /// + /// # Returns + /// - `0` in case the signature is correct + /// - `1` in case it doesn't match the key + /// - `u32::max_value` if the key is invalid. + fn ext_verify( + key: u32, + msg: *const u8, + msg_len: u32, + signature: *const u8, + signature_len: u32 + ) -> u32; + + /// Returns current UNIX timestamp (milliseconds) + fn ext_timestamp() -> u64; + + /// Pause execution until given timestamp (milliseconds; `deadline`) is reached. + /// + /// The deadline is obtained by querying the current timestamp via `ext_timestamp` + /// and then adding some time to it. + fn ext_sleep_until(deadline: u64); + + /// Generate a random seed + /// + /// `data` has to be a pointer to a slice of 32 bytes. + fn ext_random_seed(data: *mut u8); + + /// Write a value to local storage. + fn ext_local_storage_set(key: *const u8, key_len: u32, value: *const u8, value_len: u32); + + /// Write a value to local storage in atomic fashion. + fn ext_local_storage_compare_and_set( + key: *const u8, + key_len: u32, + old_value: *const u8, + old_value_len: u32, + new_value: *const u8, + new_value_len: u32 + ); + + /// Read a value from local storage. + /// + /// + /// # Returns + /// + /// - 0 if the value has not been found, the `value_len` is set to `u32::max_value`. + /// - Otherwise, pointer to the value in memory. `value_len` contains the length of the value. + fn ext_local_storage_get(key: *const u8, key_len: u32, value_len: *mut u32) -> *mut u8; + + /// Initiaties a http request. + /// + /// `meta` is parity-codec encoded additional parameters to the request (like redirection policy, + /// timeouts, certificates policy, etc). The format is not yet specified and the field is currently + /// only reserved for future use. + /// + /// # Returns + /// + /// `RequestId(u16)` of initiated request, any value beyond `u16::max_value` + /// signifies an error. + fn ext_http_request_start( + method: *const u8, + method_len: u32, + url: *const u8, + url_len: u32, + meta: *const u8, + meta_len: u32 + ) -> u32; + + /// Add a header to the request. + /// + /// # Returns + /// + /// - `0` if successful (and the request id exists) + /// - nonzero otherwise + fn ext_http_request_add_header( + request_id: u32, + name: *const u8, + name_len: u32, + value: *const u8, + value_len: u32 + ) -> u32; + + /// Write a chunk of request body. + /// + /// Writing an empty chunks finalises the request. + /// Passing `0` as deadline blocks forever. + /// + /// # Returns + /// + /// - `0` if successful, + /// - nonzero otherwise (see HttpError for the codes) + fn ext_http_request_write_body( + request_id: u32, + chunk: *const u8, + chunk_len: u32, + deadline: u64 + ) -> u32; + + /// Block and wait for the responses for given requests. + /// + /// Note that if deadline is 0 the method will block indefinitely, + /// otherwise unready responses will produce `DeadlineReached` status. + /// (see #primitives::offchain::HttpRequestStatus) + /// + /// Make sure that `statuses` have the same length as ids. + fn ext_http_response_wait( + ids: *const u32, + ids_len: u32, + statuses: *mut u32, + deadline: u64 + ); + + /// Read all response headers. + /// + /// Note the headers are only available before response body is fully consumed. + /// + /// # Returns + /// + /// - A pointer to parity-codec encoded vector of pairs `(HeaderKey, HeaderValue)`. + /// - In case invalid `id` is passed it returns a pointer to parity-encoded empty vector. + fn ext_http_response_headers( + id: u32, + written_out: *mut u32 + ) -> *mut u8; + + /// Read a chunk of body response to given buffer. + /// + /// Passing `0` as deadline blocks forever. + /// + /// # Returns + /// + /// The number of bytes written if successful, + /// - if it's `0` it means response has been fully consumed, + /// - if it's greater than `u32::max_value() - 255` it means reading body failed. + /// + /// In case of failure, the error code should be mapped to `HttpError` + /// in a following manner: + /// - `u32::max_value()` HttpError code 1 (DeadlineReached) + /// - `u32::max_value() - 1` HttpError code 2 (IoError) + /// The rest is reserved for potential future errors. + fn ext_http_response_read_body( + id: u32, + buffer: *mut u8, + buffer_len: u32, + deadline: u64 + ) -> u32; } } @@ -343,14 +540,7 @@ impl StorageApi for () { let mut length: u32 = 0; unsafe { let ptr = ext_get_allocated_storage.get()(key.as_ptr(), key.len() as u32, &mut length); - if length == u32::max_value() { - None - } else { - // Invariants required by Vec::from_raw_parts are not formally fulfilled. - // We don't allocate via String/Vec, but use a custom allocator instead. - // See #300 for more details. - Some(>::from_raw_parts(ptr, length as usize, length as usize)) - } + from_raw_parts(ptr, length) } } @@ -364,14 +554,7 @@ impl StorageApi for () { key.len() as u32, &mut length ); - if length == u32::max_value() { - None - } else { - // Invariants required by Vec::from_raw_parts are not formally fulfilled. - // We don't allocate via String/Vec, but use a custom allocator instead. - // See #300 for more details. - Some(>::from_raw_parts(ptr, length as usize, length as usize)) - } + from_raw_parts(ptr, length) } } @@ -491,10 +674,7 @@ impl StorageApi for () { storage_key.len() as u32, &mut length ); - // Invariants required by Vec::from_raw_parts are not formally fulfilled. - // We don't allocate via String/Vec, but use a custom allocator instead. - // See #300 for more details. - >::from_raw_parts(ptr, length as usize, length as usize) + from_raw_parts(ptr, length).expect("ext_child_storage_root never returns u32::max_value; qed") } } @@ -624,12 +804,273 @@ impl CryptoApi for () { } impl OffchainApi for () { - fn submit_extrinsic(data: &T) { + fn submit_transaction(data: &T) -> Result<(), ()> { let encoded_data = codec::Encode::encode(data); - unsafe { - ext_submit_extrinsic.get()(encoded_data.as_ptr(), encoded_data.len() as u32) + let ret = unsafe { + ext_submit_transaction.get()(encoded_data.as_ptr(), encoded_data.len() as u32) + }; + + if ret == 0 { + Ok(()) + } else { + Err(()) } } + + fn new_crypto_key(crypto: offchain::CryptoKind) -> Result { + let crypto = crypto as u8 as u32; + let ret = unsafe { + ext_new_crypto_key.get()(crypto) + }; + + if ret > u16::max_value() as u32 { + Err(()) + } else { + Ok(offchain::CryptoKeyId(ret as u16)) + } + } + + fn encrypt(key: Option, data: &[u8]) -> Result, ()> { + let key = key.map(|x| x.0 as u32).unwrap_or(0); + let mut len = 0_u32; + unsafe { + let ptr = ext_encrypt.get()(key, data.as_ptr(), data.len() as u32, &mut len); + + from_raw_parts(ptr, len).ok_or(()) + } + } + + fn decrypt(key: Option, data: &[u8]) -> Result, ()> { + let key = key.map(|x| x.0 as u32).unwrap_or(0); + let mut len = 0_u32; + unsafe { + let ptr = ext_decrypt.get()(key, data.as_ptr(), data.len() as u32, &mut len); + + from_raw_parts(ptr, len).ok_or(()) + } + } + + fn sign(key: Option, data: &[u8]) -> Result, ()> { + let key = key.map(|x| x.0 as u32).unwrap_or(0); + let mut len = 0_u32; + unsafe { + let ptr = ext_sign.get()(key, data.as_ptr(), data.len() as u32, &mut len); + + from_raw_parts(ptr, len).ok_or(()) + } + } + + fn verify(key: Option, msg: &[u8], signature: &[u8]) -> Result { + let key = key.map(|x| x.0 as u32).unwrap_or(0); + let val = unsafe { + ext_verify.get()( + key, + msg.as_ptr(), + msg.len() as u32, + signature.as_ptr(), + signature.len() as u32, + ) + }; + + match val { + 0 => Ok(true), + 1 => Ok(false), + _ => Err(()), + } + } + + fn timestamp() -> offchain::Timestamp { + offchain::Timestamp::from_unix_millis(unsafe { + ext_timestamp.get()() + }) + } + + fn sleep_until(deadline: Timestamp) { + unsafe { + ext_sleep_until.get()(deadline.unix_millis()) + } + } + + fn random_seed() -> [u8; 32] { + let mut result = [0_u8; 32]; + unsafe { + ext_random_seed.get()(result.as_mut_ptr()) + } + result + } + + fn local_storage_set(key: &[u8], value: &[u8]) { + unsafe { + ext_local_storage_set.get()( + key.as_ptr(), + key.len() as u32, + value.as_ptr(), + value.len() as u32, + ) + } + } + + fn local_storage_compare_and_set(key: &[u8], old_value: &[u8], new_value: &[u8]) { + unsafe { + ext_local_storage_compare_and_set.get()( + key.as_ptr(), + key.len() as u32, + old_value.as_ptr(), + old_value.len() as u32, + new_value.as_ptr(), + new_value.len() as u32, + ) + } + } + + fn local_storage_get(key: &[u8]) -> Option> { + let mut len = 0u32; + unsafe { + let ptr = ext_local_storage_get.get()( + key.as_ptr(), + key.len() as u32, + &mut len, + ); + + from_raw_parts(ptr, len) + } + } + + fn http_request_start(method: &str, url: &str, meta: &[u8]) -> Result { + let method = method.as_bytes(); + let url = url.as_bytes(); + + let result = unsafe { + ext_http_request_start.get()( + method.as_ptr(), + method.len() as u32, + url.as_ptr(), + url.len() as u32, + meta.as_ptr(), + meta.len() as u32, + ) + }; + + if result > u16::max_value() as u32 { + Err(()) + } else { + Ok(offchain::HttpRequestId(result as u16)) + } + } + + fn http_request_add_header(request_id: offchain::HttpRequestId, name: &str, value: &str) -> Result<(), ()> { + let name = name.as_bytes(); + let value = value.as_bytes(); + + let result = unsafe { + ext_http_request_add_header.get()( + request_id.0 as u32, + name.as_ptr(), + name.len() as u32, + value.as_ptr(), + value.len() as u32, + ) + }; + + if result == 0 { + Ok(()) + } else { + Err(()) + } + } + + fn http_request_write_body( + request_id: offchain::HttpRequestId, + chunk: &[u8], + deadline: Option + ) -> Result<(), offchain::HttpError> { + let res = unsafe { + ext_http_request_write_body.get()( + request_id.0 as u32, + chunk.as_ptr(), + chunk.len() as u32, + deadline.map_or(0, |x| x.unix_millis()), + ) + }; + + if res == 0 { + Ok(()) + } else { + Err(res.try_into().unwrap_or(offchain::HttpError::IoError)) + } + } + + fn http_response_wait( + ids: &[offchain::HttpRequestId], + deadline: Option + ) -> Vec { + let ids = ids.iter().map(|x| x.0 as u32).collect::>(); + let mut statuses = Vec::new(); + statuses.resize(ids.len(), 0u32); + + unsafe { + ext_http_response_wait.get()( + ids.as_ptr(), + ids.len() as u32, + statuses.as_mut_ptr(), + deadline.map_or(0, |x| x.unix_millis()), + ) + } + + statuses + .into_iter() + .map(|status| status.try_into().unwrap_or(offchain::HttpRequestStatus::Unknown)) + .collect() + } + + fn http_response_headers( + request_id: offchain::HttpRequestId, + ) -> Vec<(Vec, Vec)> { + let mut len = 0u32; + let raw_result = unsafe { + let ptr = ext_http_response_headers.get()( + request_id.0 as u32, + &mut len, + ); + + from_raw_parts(ptr, len).expect("ext_http_response_headers never return u32::max_value; qed") + }; + + codec::Decode::decode(&mut &*raw_result).unwrap_or_default() + } + + fn http_response_read_body( + request_id: offchain::HttpRequestId, + buffer: &mut [u8], + deadline: Option, + ) -> Result { + let res = unsafe { + ext_http_response_read_body.get()( + request_id.0 as u32, + buffer.as_mut_ptr(), + buffer.len() as u32, + deadline.map_or(0, |x| x.unix_millis()), + ) + }; + + if res >= u32::max_value() - 255 { + let code = (u32::max_value() - res) + 1; + code.try_into().map_err(|_| offchain::HttpError::IoError) + } else { + Ok(res as usize) + } + } +} + +unsafe fn from_raw_parts(ptr: *mut u8, len: u32) -> Option> { + if len == u32::max_value() { + None + } else { + // Invariants required by Vec::from_raw_parts are not formally fulfilled. + // We don't allocate via String/Vec, but use a custom allocator instead. + // See #300 for more details. + Some(>::from_raw_parts(ptr, len as usize, len as usize)) + } } impl Api for () {} diff --git a/substrate/core/sr-primitives/src/generic/era.rs b/substrate/core/sr-primitives/src/generic/era.rs index 22f47b6769..c41d3eedfc 100644 --- a/substrate/core/sr-primitives/src/generic/era.rs +++ b/substrate/core/sr-primitives/src/generic/era.rs @@ -21,7 +21,10 @@ use serde::{Serialize, Deserialize}; use crate::codec::{Decode, Encode, Input, Output}; +/// Era period pub type Period = u64; + +/// Era phase pub type Phase = u64; /// An era to describe the longevity of a transaction. diff --git a/substrate/core/sr-std/with_std.rs b/substrate/core/sr-std/with_std.rs index d71b9dcb69..5824e26241 100644 --- a/substrate/core/sr-std/with_std.rs +++ b/substrate/core/sr-std/with_std.rs @@ -19,6 +19,8 @@ pub use std::boxed; pub use std::cell; pub use std::clone; pub use std::cmp; +pub use std::convert; +pub use std::default; pub use std::fmt; pub use std::hash; pub use std::iter; @@ -28,11 +30,10 @@ pub use std::num; pub use std::ops; pub use std::ptr; pub use std::rc; -pub use std::slice; -pub use std::vec; -pub use std::default; pub use std::result; -pub use std::convert; +pub use std::slice; +pub use std::str; +pub use std::vec; pub mod collections { pub use std::collections::btree_map; diff --git a/substrate/core/sr-std/without_std.rs b/substrate/core/sr-std/without_std.rs index 9214a0ed2f..db81372c2f 100755 --- a/substrate/core/sr-std/without_std.rs +++ b/substrate/core/sr-std/without_std.rs @@ -53,6 +53,8 @@ pub use core::borrow; pub use core::cell; pub use core::clone; pub use core::cmp; +pub use core::convert; +pub use core::default; pub use core::hash; pub use core::intrinsics; pub use core::iter; @@ -61,10 +63,10 @@ pub use core::mem; pub use core::num; pub use core::ops; pub use core::ptr; -pub use core::slice; -pub use core::default; pub use core::result; -pub use core::convert; +pub use core::slice; +// Allow intepreting vectors of bytes as strings, but not constructing them. +pub use core::str; // We are trying to avoid certain things here, such as `core::string` // (if you need `String` you most probably doing something wrong, since // runtime doesn't require anything human readable). diff --git a/substrate/core/state-machine/src/basic.rs b/substrate/core/state-machine/src/basic.rs index 6cb3efd267..0eb0b84b27 100644 --- a/substrate/core/state-machine/src/basic.rs +++ b/substrate/core/state-machine/src/basic.rs @@ -20,6 +20,7 @@ use std::collections::HashMap; use std::iter::FromIterator; use hash_db::Hasher; use trie::trie_root; +use primitives::offchain; use primitives::storage::well_known_keys::{CHANGES_TRIE_CONFIG, CODE, HEAP_PAGES}; use parity_codec::Encode; use super::{ChildStorageKey, Externalities, OverlayedChanges}; @@ -155,9 +156,9 @@ impl Externalities for BasicExternalities where H::Out: Ord { Ok(None) } - fn submit_extrinsic(&mut self, _extrinsic: Vec) -> Result<(), ()> { - warn!("Call to submit_extrinsic without offchain externalities set."); - Err(()) + fn offchain(&mut self) -> Option<&mut offchain::Externalities> { + warn!("Call to non-existent out offchain externalities set."); + None } } diff --git a/substrate/core/state-machine/src/ext.rs b/substrate/core/state-machine/src/ext.rs index f0638edf65..6ec1ce77e6 100644 --- a/substrate/core/state-machine/src/ext.rs +++ b/substrate/core/state-machine/src/ext.rs @@ -20,8 +20,9 @@ use std::{error, fmt, cmp::Ord}; use log::warn; use crate::backend::Backend; use crate::changes_trie::{Storage as ChangesTrieStorage, compute_changes_trie_root}; -use crate::{Externalities, OverlayedChanges, OffchainExt, ChildStorageKey}; +use crate::{Externalities, OverlayedChanges, ChildStorageKey}; use hash_db::Hasher; +use primitives::offchain; use primitives::storage::well_known_keys::is_child_storage_key; use trie::{MemoryDB, TrieDBMut, TrieMut, default_child_trie_root}; @@ -91,7 +92,7 @@ where H: Hasher, B: 'a + Backend, T: 'a + ChangesTrieStorage, - O: 'a + OffchainExt, + O: 'a + offchain::Externalities, H::Out: Ord + 'static, N: crate::changes_trie::BlockNumber, { @@ -145,7 +146,7 @@ where H: Hasher, B: 'a + Backend, T: 'a + ChangesTrieStorage, - O: 'a + OffchainExt, + O: 'a + offchain::Externalities, N: crate::changes_trie::BlockNumber, { pub fn storage_pairs(&self) -> Vec<(Vec, Vec)> { @@ -167,7 +168,7 @@ where H: Hasher, B: 'a + Backend, T: 'a + ChangesTrieStorage, - O: 'a + OffchainExt, + O: 'a + offchain::Externalities, H::Out: Ord + 'static, N: crate::changes_trie::BlockNumber, { @@ -342,15 +343,8 @@ where Ok(root) } - fn submit_extrinsic(&mut self, extrinsic: Vec) -> Result<(), ()> { - let _guard = panic_handler::AbortGuard::new(true); - if let Some(ext) = self.offchain_externalities.as_mut() { - ext.submit_extrinsic(extrinsic); - Ok(()) - } else { - warn!("Call to submit_extrinsic without offchain externalities set."); - Err(()) - } + fn offchain(&mut self) -> Option<&mut offchain::Externalities> { + self.offchain_externalities.as_mut().map(|x| &mut **x as _) } } diff --git a/substrate/core/state-machine/src/lib.rs b/substrate/core/state-machine/src/lib.rs index 4f51677fff..f16c3db14e 100644 --- a/substrate/core/state-machine/src/lib.rs +++ b/substrate/core/state-machine/src/lib.rs @@ -24,7 +24,7 @@ use log::warn; use hash_db::Hasher; use parity_codec::{Decode, Encode}; use primitives::{ - storage::well_known_keys, NativeOrEncoded, NeverNativeValue, OffchainExt + storage::well_known_keys, NativeOrEncoded, NeverNativeValue, offchain }; pub mod backend; @@ -221,10 +221,8 @@ pub trait Externalities { /// Get the change trie root of the current storage overlay at a block with given parent. fn storage_changes_root(&mut self, parent: H::Out) -> Result, ()> where H::Out: Ord; - /// Submit extrinsic. - /// - /// Returns an error in case the API is not available. - fn submit_extrinsic(&mut self, extrinsic: Vec) -> Result<(), ()>; + /// Returns offchain externalities extension if present. + fn offchain(&mut self) -> Option<&mut offchain::Externalities>; } /// An implementation of offchain extensions that should never be triggered. @@ -237,8 +235,121 @@ impl NeverOffchainExt { } } -impl OffchainExt for NeverOffchainExt { - fn submit_extrinsic(&mut self, _extrinsic: Vec) { unreachable!() } +impl offchain::Externalities for NeverOffchainExt { + fn submit_transaction(&mut self, _extrinsic: Vec) -> Result<(), ()> { + unreachable!() + } + + fn new_crypto_key( + &mut self, + _crypto: offchain::CryptoKind, + ) -> Result { + unreachable!() + } + + fn encrypt( + &mut self, + _key: Option, + _data: &[u8], + ) -> Result, ()> { + unreachable!() + } + + fn decrypt( + &mut self, + _key: Option, + _data: &[u8], + ) -> Result, ()> { + unreachable!() + } + + fn sign(&mut self, _key: Option, _data: &[u8]) -> Result, ()> { + unreachable!() + } + + fn verify( + &mut self, + _key: Option, + _msg: &[u8], + _signature: &[u8], + ) -> Result { + unreachable!() + } + + fn timestamp(&mut self) -> offchain::Timestamp { + unreachable!() + } + + fn sleep_until(&mut self, _deadline: offchain::Timestamp) { + unreachable!() + } + + fn random_seed(&mut self) -> [u8; 32] { + unreachable!() + } + + fn local_storage_set(&mut self, _key: &[u8], _value: &[u8]) { + unreachable!() + } + + fn local_storage_compare_and_set(&mut self, _key: &[u8], _old_value: &[u8], _new_value: &[u8]) { + unreachable!() + } + + fn local_storage_get(&mut self, _key: &[u8]) -> Option> { + unreachable!() + } + + fn http_request_start( + &mut self, + _method: &str, + _uri: &str, + _meta: &[u8] + ) -> Result { + unreachable!() + } + + fn http_request_add_header( + &mut self, + _request_id: offchain::HttpRequestId, + _name: &str, + _value: &str + ) -> Result<(), ()> { + unreachable!() + } + + fn http_request_write_body( + &mut self, + _request_id: offchain::HttpRequestId, + _chunk: &[u8], + _deadline: Option + ) -> Result<(), offchain::HttpError> { + unreachable!() + } + + fn http_response_wait( + &mut self, + _ids: &[offchain::HttpRequestId], + _deadline: Option + ) -> Vec { + unreachable!() + } + + fn http_response_headers( + &mut self, + _request_id: offchain::HttpRequestId + ) -> Vec<(Vec, Vec)> { + unreachable!() + } + + fn http_response_read_body( + &mut self, + _request_id: offchain::HttpRequestId, + _buffer: &mut [u8], + _deadline: Option + ) -> Result { + unreachable!() + } } /// Code execution engine. @@ -376,7 +487,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where Exec: CodeExecutor, B: Backend, T: ChangesTrieStorage, - O: OffchainExt, + O: offchain::Externalities, H::Out: Ord + 'static, N: crate::changes_trie::BlockNumber, { @@ -415,12 +526,11 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where R: Decode + Encode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, { - let offchain = self.offchain_ext.as_mut(); let mut externalities = ext::Ext::new( self.overlay, self.backend, self.changes_trie_storage, - offchain.map(|x| &mut **x), + self.offchain_ext.as_mut().map(|x| &mut **x), ); let (result, was_native) = self.exec.call( &mut externalities, diff --git a/substrate/core/state-machine/src/testing.rs b/substrate/core/state-machine/src/testing.rs index 52d208142d..3934e726af 100644 --- a/substrate/core/state-machine/src/testing.rs +++ b/substrate/core/state-machine/src/testing.rs @@ -25,6 +25,7 @@ use crate::changes_trie::{ compute_changes_trie_root, InMemoryStorage as ChangesTrieInMemoryStorage, BlockNumber as ChangesTrieBlockNumber, }; +use primitives::offchain; use primitives::storage::well_known_keys::{CHANGES_TRIE_CONFIG, CODE, HEAP_PAGES}; use parity_codec::Encode; use super::{ChildStorageKey, Externalities, OverlayedChanges}; @@ -36,6 +37,7 @@ pub struct TestExternalities { overlay: OverlayedChanges, backend: InMemory, changes_trie_storage: ChangesTrieInMemoryStorage, + offchain: Option>, } impl TestExternalities { @@ -61,6 +63,7 @@ impl TestExternalities { overlay, changes_trie_storage: ChangesTrieInMemoryStorage::new(), backend: inner.into(), + offchain: None, } } @@ -80,6 +83,11 @@ impl TestExternalities { .filter_map(|(k, maybe_val)| maybe_val.map(|val| (k, val))) } + /// Set offchain externaltiies. + pub fn set_offchain_externalities(&mut self, offchain: impl offchain::Externalities + 'static) { + self.offchain = Some(Box::new(offchain)); + } + /// Get mutable reference to changes trie storage. pub fn changes_trie_storage(&mut self) -> &mut ChangesTrieInMemoryStorage { &mut self.changes_trie_storage @@ -226,8 +234,10 @@ impl Externalities for TestExternalities )?.map(|(root, _)| root.clone())) } - fn submit_extrinsic(&mut self, _extrinsic: Vec) -> Result<(), ()> { - unimplemented!() + fn offchain(&mut self) -> Option<&mut offchain::Externalities> { + self.offchain + .as_mut() + .map(|x| &mut **x as _) } } diff --git a/substrate/core/test-runtime/src/lib.rs b/substrate/core/test-runtime/src/lib.rs index 980ce810e4..e582fd4dc7 100644 --- a/substrate/core/test-runtime/src/lib.rs +++ b/substrate/core/test-runtime/src/lib.rs @@ -476,7 +476,7 @@ cfg_if! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(block: u64) { let ex = Extrinsic::IncludeData(block.encode()); - runtime_io::submit_extrinsic(&ex) + runtime_io::submit_transaction(&ex).unwrap(); } } @@ -628,7 +628,7 @@ cfg_if! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(block: u64) { let ex = Extrinsic::IncludeData(block.encode()); - runtime_io::submit_extrinsic(&ex) + runtime_io::submit_transaction(&ex).unwrap() } } diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 86c73307b8..d243357fc2 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), impl_name: create_runtime_str!("substrate-node"), authoring_version: 10, - spec_version: 88, - impl_version: 90, + spec_version: 89, + impl_version: 89, apis: RUNTIME_API_VERSIONS, };