mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Offchain-worker APIs stubs (#2615)
* WiP: HTTP Apis. * Working on the API. * Add docs, clean up the API. * Expose ext_ stuff as well. * Implement HTTP helpers for offchain sr-io. * Remove HTTP stuff. * Revert "Remove HTTP stuff." This reverts commit 7cca029d6ae93c5849b50edfcc6d2c313ba3e5bf. * HTTP apis. * Additional offchain methods. * Make it compile. * Implement wasm-ext boundary of offchain methods. * Add stubs for offchain stuff to prevent panics. * Fix tests. * Addres some more issues. * Introduce typedef, use unsafe from_utf8 * Bump runtime version. * Introduce error to distinguish deadline and io errors. * Add local_storage_cas * Some tests for offchain stuff. * Address more grumbles. * Fix tests compilation. * Fix borked merge. * Improve docs for expected return values from ext functions. * Adding new sign/enrypt/decrypt APIs.
This commit is contained in:
committed by
Gavin Wood
parent
80b18c8531
commit
308ab4f269
Generated
+2
@@ -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",
|
||||
|
||||
@@ -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<B>,
|
||||
@@ -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<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -96,7 +94,7 @@ where
|
||||
///
|
||||
/// No changes are made.
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
S: state_machine::Backend<H>,
|
||||
F: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -181,7 +179,7 @@ where
|
||||
{
|
||||
type Error = E::Error;
|
||||
|
||||
fn call<O: OffchainExt>(
|
||||
fn call<O: offchain::Externalities>(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
@@ -211,7 +209,7 @@ where
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
IB: Fn() -> error::Result<()>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -300,7 +298,7 @@ where
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
S: state_machine::Backend<Blake2Hasher>,
|
||||
F: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
|
||||
@@ -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<Block>,
|
||||
@@ -111,7 +111,7 @@ where
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
IB: Fn() -> ClientResult<()>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -154,7 +154,7 @@ where
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
S: StateBackend<Blake2Hasher>,
|
||||
FF: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -230,7 +230,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
type Error = ClientError;
|
||||
|
||||
fn call<
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
>(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
@@ -247,7 +247,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
|
||||
fn contextual_call<
|
||||
'a,
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
IB: Fn() -> ClientResult<()>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -327,7 +327,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
O: offchain::Externalities,
|
||||
S: StateBackend<Blake2Hasher>,
|
||||
FF: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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<u32> for MemoryInstance {
|
||||
}
|
||||
}
|
||||
|
||||
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
|
||||
if deadline == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(offchain::Timestamp::from_unix_millis(deadline))
|
||||
}
|
||||
}
|
||||
|
||||
fn u32_to_key(key: u32) -> std::result::Result<Option<offchain::CryptoKeyId>, ()> {
|
||||
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<Vec<_>, _>>()?;
|
||||
|
||||
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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<ExtMessage>);
|
||||
|
||||
fn unavailable_yet<R: Default>(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<u8>) {
|
||||
let _ = self.0.unbounded_send(ExtMessage::SubmitExtrinsic(ext));
|
||||
fn submit_transaction(&mut self, ext: Vec<u8>) -> Result<(), ()> {
|
||||
self.0.unbounded_send(ExtMessage::SubmitExtrinsic(ext))
|
||||
.map(|_| ())
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
|
||||
unavailable_yet::<()>("new_crypto_key");
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unavailable_yet::<()>("encrypt");
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn decrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unavailable_yet::<()>("decrypt");
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn sign(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unavailable_yet::<()>("sign");
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn verify(&mut self, _key: Option<CryptoKeyId>, _msg: &[u8], _signature: &[u8]) -> Result<bool, ()> {
|
||||
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<Vec<u8>> {
|
||||
unavailable_yet("local_storage_get")
|
||||
}
|
||||
|
||||
fn http_request_start(
|
||||
&mut self,
|
||||
_method: &str,
|
||||
_uri: &str,
|
||||
_meta: &[u8]
|
||||
) -> Result<HttpRequestId, ()> {
|
||||
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<Timestamp>
|
||||
) -> Result<(), HttpError> {
|
||||
unavailable_yet::<()>("http_request_write_body");
|
||||
Err(HttpError::IoError)
|
||||
}
|
||||
|
||||
fn http_response_wait(
|
||||
&mut self,
|
||||
ids: &[HttpRequestId],
|
||||
_deadline: Option<Timestamp>
|
||||
) -> Vec<HttpRequestStatus> {
|
||||
unavailable_yet::<()>("http_response_wait");
|
||||
ids.iter().map(|_| HttpRequestStatus::Unknown).collect()
|
||||
}
|
||||
|
||||
fn http_response_headers(
|
||||
&mut self,
|
||||
_request_id: HttpRequestId
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
unavailable_yet("http_response_headers")
|
||||
}
|
||||
|
||||
fn http_response_read_body(
|
||||
&mut self,
|
||||
_request_id: HttpRequestId,
|
||||
_buffer: &mut [u8],
|
||||
_deadline: Option<Timestamp>
|
||||
) -> Result<usize, HttpError> {
|
||||
unavailable_yet::<()>("http_response_read_body");
|
||||
Err(HttpError::IoError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ use transaction_pool::txpool::{Pool, ChainApi};
|
||||
|
||||
mod api;
|
||||
|
||||
pub mod testing;
|
||||
|
||||
pub use offchain_primitives::OffchainWorkerApi;
|
||||
|
||||
/// An offchain workers manager.
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<u8>,
|
||||
/// Request headers
|
||||
pub headers: Vec<(String, String)>,
|
||||
/// Request body
|
||||
pub body: Vec<u8>,
|
||||
/// Has the request been sent already.
|
||||
pub sent: bool,
|
||||
/// Response body
|
||||
pub response: Vec<u8>,
|
||||
/// 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<RequestId, PendingRequest>,
|
||||
}
|
||||
|
||||
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<Vec<u8>>,
|
||||
response_headers: impl IntoIterator<Item=(String, String)>,
|
||||
) {
|
||||
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<RwLock<State>>);
|
||||
|
||||
impl offchain::Externalities for TestOffchainExt {
|
||||
fn submit_transaction(&mut self, _ex: Vec<u8>) -> Result<(), ()> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn decrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn sign(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn verify(&mut self, _key: Option<CryptoKeyId>, _msg: &[u8], _signature: &[u8]) -> Result<bool, ()> {
|
||||
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<Vec<u8>> {
|
||||
unimplemented!("not needed in tests so far")
|
||||
}
|
||||
|
||||
fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result<RequestId, ()> {
|
||||
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<Timestamp>
|
||||
) -> 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<Timestamp>,
|
||||
) -> Vec<RequestStatus> {
|
||||
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<u8>, Vec<u8>)> {
|
||||
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<Timestamp>
|
||||
) -> Result<usize, HttpError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OffchainExt>),
|
||||
OffchainWorker(Box<offchain::Externalities>),
|
||||
/// 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<u8>);
|
||||
}
|
||||
impl<T: OffchainExt + ?Sized> OffchainExt for Box<T> {
|
||||
fn submit_extrinsic(&mut self, ex: Vec<u8>) {
|
||||
(&mut **self).submit_extrinsic(ex)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex-serialized shim for `Vec<u8>`.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Hash, PartialOrd, Ord))]
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<u32> for CryptoKind {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(kind: u32) -> Result<Self, Self::Error> {
|
||||
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<u32> for HttpError {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(error: u32) -> Result<Self, Self::Error> {
|
||||
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<HttpRequestStatus> 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<u32> for HttpRequestStatus {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(status: u32) -> Result<Self, Self::Error> {
|
||||
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<u8>) -> 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<CryptoKeyId, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()>;
|
||||
|
||||
/// 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<Vec<u8>>;
|
||||
|
||||
/// 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<HttpRequestId, ()>;
|
||||
|
||||
/// 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<Timestamp>
|
||||
) -> 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<Timestamp>
|
||||
) -> Vec<HttpRequestStatus>;
|
||||
|
||||
/// Read all response headers.
|
||||
///
|
||||
/// Returns a vector of pairs `(HeaderKey, HeaderValue)`.
|
||||
fn http_response_headers(
|
||||
&mut self,
|
||||
request_id: HttpRequestId
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)>;
|
||||
|
||||
/// 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<Timestamp>
|
||||
) -> Result<usize, HttpError>;
|
||||
|
||||
}
|
||||
impl<T: Externalities + ?Sized> Externalities for Box<T> {
|
||||
fn submit_transaction(&mut self, ex: Vec<u8>) -> Result<(), ()> {
|
||||
(&mut **self).submit_transaction(ex)
|
||||
}
|
||||
|
||||
fn new_crypto_key(&mut self, crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
|
||||
(&mut **self).new_crypto_key(crypto)
|
||||
}
|
||||
|
||||
fn encrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
(&mut **self).encrypt(key, data)
|
||||
}
|
||||
|
||||
fn decrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
(&mut **self).decrypt(key, data)
|
||||
}
|
||||
|
||||
fn sign(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
(&mut **self).sign(key, data)
|
||||
}
|
||||
|
||||
fn verify(&mut self, key: Option<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
|
||||
(&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<Vec<u8>> {
|
||||
(&mut **self).local_storage_get(key)
|
||||
}
|
||||
|
||||
fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result<HttpRequestId, ()> {
|
||||
(&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<Timestamp>
|
||||
) -> Result<(), HttpError> {
|
||||
(&mut **self).http_request_write_body(request_id, chunk, deadline)
|
||||
}
|
||||
|
||||
fn http_response_wait(&mut self, ids: &[HttpRequestId], deadline: Option<Timestamp>) -> Vec<HttpRequestStatus> {
|
||||
(&mut **self).http_response_wait(ids, deadline)
|
||||
}
|
||||
|
||||
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
(&mut **self).http_response_headers(request_id)
|
||||
}
|
||||
|
||||
fn http_response_read_body(
|
||||
&mut self,
|
||||
request_id: HttpRequestId,
|
||||
buffer: &mut [u8],
|
||||
deadline: Option<Timestamp>
|
||||
) -> Result<usize, HttpError> {
|
||||
(&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));
|
||||
}
|
||||
}
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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<T: codec::Encode>(data: &T);
|
||||
/// The transaction will end up in the pool.
|
||||
fn submit_transaction<T: codec::Encode>(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<CryptoKeyId, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
|
||||
|
||||
/// 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<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()>;
|
||||
|
||||
/// 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<Vec<u8>>;
|
||||
|
||||
/// 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<HttpRequestId, ()>;
|
||||
|
||||
/// 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<Timestamp>
|
||||
) -> 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<Timestamp>
|
||||
) -> Vec<HttpRequestStatus>;
|
||||
|
||||
/// 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<u8>, Vec<u8>)>;
|
||||
|
||||
/// 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<Timestamp>
|
||||
) -> Result<usize, HttpError>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<str> 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<u8>,
|
||||
value: Vec<u8>,
|
||||
}
|
||||
|
||||
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<Timestamp>,
|
||||
/// Request list of headers.
|
||||
headers: Vec<header::Header>,
|
||||
}
|
||||
|
||||
impl<T: Default> 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<Item=I>> 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<PendingRequest, HttpError> {
|
||||
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<Response, Error>;
|
||||
|
||||
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<Option<Timestamp>>) -> Result<HttpResult, PendingRequest> {
|
||||
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<PendingRequest>) -> Vec<HttpResult> {
|
||||
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<PendingRequest>,
|
||||
deadline: impl Into<Option<Timestamp>>
|
||||
) -> Vec<Result<HttpResult, PendingRequest>> {
|
||||
let ids = requests.iter().map(|r| r.id).collect::<Vec<_>>();
|
||||
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<Headers>,
|
||||
}
|
||||
|
||||
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<HttpError>,
|
||||
buffer: [u8; 4096],
|
||||
filled_up_to: Option<usize>,
|
||||
position: usize,
|
||||
deadline: Option<Timestamp>,
|
||||
}
|
||||
|
||||
#[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<Option<Timestamp>>) {
|
||||
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<HttpError> {
|
||||
&self.error
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ResponseBody {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<u8>, Vec<u8>)>,
|
||||
}
|
||||
|
||||
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<u8>, Vec<u8>)],
|
||||
index: Option<usize>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>(), 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::<Vec<_>>(), b"1234".to_vec());
|
||||
assert_eq!(body.error(), &None);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A collection of higher lever helpers for offchain workers.
|
||||
|
||||
pub mod http;
|
||||
@@ -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<R>(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<T: codec::Encode>(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<T: codec::Encode>(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<offchain::CryptoKeyId, ()> {
|
||||
with_offchain(|ext| {
|
||||
ext.new_crypto_key(crypto)
|
||||
}, "new_crypto_key can be called only in the offchain worker context")
|
||||
}
|
||||
|
||||
fn encrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
with_offchain(|ext| {
|
||||
ext.encrypt(key, data)
|
||||
}, "encrypt can be called only in the offchain worker context")
|
||||
}
|
||||
|
||||
fn decrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
with_offchain(|ext| {
|
||||
ext.decrypt(key, data)
|
||||
}, "decrypt can be called only in the offchain worker context")
|
||||
}
|
||||
|
||||
fn sign(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
with_offchain(|ext| {
|
||||
ext.sign(key, data)
|
||||
}, "sign can be called only in the offchain worker context")
|
||||
}
|
||||
|
||||
fn verify(key: Option<offchain::CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
|
||||
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<Vec<u8>> {
|
||||
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<offchain::HttpRequestId, ()> {
|
||||
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<offchain::Timestamp>
|
||||
) -> 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<offchain::Timestamp>
|
||||
) -> Vec<offchain::HttpRequestStatus> {
|
||||
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<u8>, Vec<u8>)> {
|
||||
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<offchain::Timestamp>
|
||||
) -> Result<usize, offchain::HttpError> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T>, but use a custom allocator instead.
|
||||
// See #300 for more details.
|
||||
Some(<Vec<u8>>::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<T>, but use a custom allocator instead.
|
||||
// See #300 for more details.
|
||||
Some(<Vec<u8>>::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<T>, but use a custom allocator instead.
|
||||
// See #300 for more details.
|
||||
<Vec<u8>>::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<T: codec::Encode>(data: &T) {
|
||||
fn submit_transaction<T: codec::Encode>(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<offchain::CryptoKeyId, ()> {
|
||||
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<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
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<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
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<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
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<offchain::CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
|
||||
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<Vec<u8>> {
|
||||
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<offchain::HttpRequestId, ()> {
|
||||
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<offchain::Timestamp>
|
||||
) -> 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<offchain::Timestamp>
|
||||
) -> Vec<offchain::HttpRequestStatus> {
|
||||
let ids = ids.iter().map(|x| x.0 as u32).collect::<Vec<_>>();
|
||||
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<u8>, Vec<u8>)> {
|
||||
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<offchain::Timestamp>,
|
||||
) -> Result<usize, offchain::HttpError> {
|
||||
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<Vec<u8>> {
|
||||
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<T>, but use a custom allocator instead.
|
||||
// See #300 for more details.
|
||||
Some(<Vec<u8>>::from_raw_parts(ptr, len as usize, len as usize))
|
||||
}
|
||||
}
|
||||
|
||||
impl Api for () {}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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<H: Hasher> Externalities<H> for BasicExternalities where H::Out: Ord {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, _extrinsic: Vec<u8>) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<H>,
|
||||
T: 'a + ChangesTrieStorage<H, N>,
|
||||
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<H>,
|
||||
T: 'a + ChangesTrieStorage<H, N>,
|
||||
O: 'a + OffchainExt,
|
||||
O: 'a + offchain::Externalities,
|
||||
N: crate::changes_trie::BlockNumber,
|
||||
{
|
||||
pub fn storage_pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
@@ -167,7 +168,7 @@ where
|
||||
H: Hasher,
|
||||
B: 'a + Backend<H>,
|
||||
T: 'a + ChangesTrieStorage<H, N>,
|
||||
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<u8>) -> 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 _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<H: Hasher> {
|
||||
/// 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<Option<H::Out>, ()> where H::Out: Ord;
|
||||
|
||||
/// Submit extrinsic.
|
||||
///
|
||||
/// Returns an error in case the API is not available.
|
||||
fn submit_extrinsic(&mut self, extrinsic: Vec<u8>) -> 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<u8>) { unreachable!() }
|
||||
impl offchain::Externalities for NeverOffchainExt {
|
||||
fn submit_transaction(&mut self, _extrinsic: Vec<u8>) -> Result<(), ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn new_crypto_key(
|
||||
&mut self,
|
||||
_crypto: offchain::CryptoKind,
|
||||
) -> Result<offchain::CryptoKeyId, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&mut self,
|
||||
_key: Option<offchain::CryptoKeyId>,
|
||||
_data: &[u8],
|
||||
) -> Result<Vec<u8>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&mut self,
|
||||
_key: Option<offchain::CryptoKeyId>,
|
||||
_data: &[u8],
|
||||
) -> Result<Vec<u8>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn sign(&mut self, _key: Option<offchain::CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&mut self,
|
||||
_key: Option<offchain::CryptoKeyId>,
|
||||
_msg: &[u8],
|
||||
_signature: &[u8],
|
||||
) -> Result<bool, ()> {
|
||||
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<Vec<u8>> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn http_request_start(
|
||||
&mut self,
|
||||
_method: &str,
|
||||
_uri: &str,
|
||||
_meta: &[u8]
|
||||
) -> Result<offchain::HttpRequestId, ()> {
|
||||
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<offchain::Timestamp>
|
||||
) -> Result<(), offchain::HttpError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn http_response_wait(
|
||||
&mut self,
|
||||
_ids: &[offchain::HttpRequestId],
|
||||
_deadline: Option<offchain::Timestamp>
|
||||
) -> Vec<offchain::HttpRequestStatus> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn http_response_headers(
|
||||
&mut self,
|
||||
_request_id: offchain::HttpRequestId
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn http_response_read_body(
|
||||
&mut self,
|
||||
_request_id: offchain::HttpRequestId,
|
||||
_buffer: &mut [u8],
|
||||
_deadline: Option<offchain::Timestamp>
|
||||
) -> Result<usize, offchain::HttpError> {
|
||||
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<H>,
|
||||
B: Backend<H>,
|
||||
T: ChangesTrieStorage<H, N>,
|
||||
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<R, &'static str> + 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,
|
||||
|
||||
@@ -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<H: Hasher, N: ChangesTrieBlockNumber> {
|
||||
overlay: OverlayedChanges,
|
||||
backend: InMemory<H>,
|
||||
changes_trie_storage: ChangesTrieInMemoryStorage<H, N>,
|
||||
offchain: Option<Box<offchain::Externalities>>,
|
||||
}
|
||||
|
||||
impl<H: Hasher, N: ChangesTrieBlockNumber> TestExternalities<H, N> {
|
||||
@@ -61,6 +63,7 @@ impl<H: Hasher, N: ChangesTrieBlockNumber> TestExternalities<H, N> {
|
||||
overlay,
|
||||
changes_trie_storage: ChangesTrieInMemoryStorage::new(),
|
||||
backend: inner.into(),
|
||||
offchain: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,11 @@ impl<H: Hasher, N: ChangesTrieBlockNumber> TestExternalities<H, N> {
|
||||
.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<H, N> {
|
||||
&mut self.changes_trie_storage
|
||||
@@ -226,8 +234,10 @@ impl<H, N> Externalities<H> for TestExternalities<H, N>
|
||||
)?.map(|(root, _)| root.clone()))
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, _extrinsic: Vec<u8>) -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
fn offchain(&mut self) -> Option<&mut offchain::Externalities> {
|
||||
self.offchain
|
||||
.as_mut()
|
||||
.map(|x| &mut **x as _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -476,7 +476,7 @@ cfg_if! {
|
||||
impl offchain_primitives::OffchainWorkerApi<Block> 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<Block> for Runtime {
|
||||
fn offchain_worker(block: u64) {
|
||||
let ex = Extrinsic::IncludeData(block.encode());
|
||||
runtime_io::submit_extrinsic(&ex)
|
||||
runtime_io::submit_transaction(&ex).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user