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:
Tomasz Drwięga
2019-05-31 09:33:44 +02:00
committed by Gavin Wood
parent 80b18c8531
commit 308ab4f269
26 changed files with 2648 additions and 120 deletions
+3
View File
@@ -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 = [
+136 -5
View File
@@ -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>;
}
}
+571
View File
@@ -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);
})
}
}
+19
View File
@@ -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;
+136 -6
View File
@@ -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")
}
}
+469 -28
View File
@@ -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 () {}