Persistent Local Storage for offchain workers. (#2894)

* WiP.

* Implement offchain storage APIs.

* Change compare_and_set to return bool.

* Add offchain http test.

* Fix tests.

* Bump spec version.

* Fix warnings and test.

* Fix compilation.

* Remove unused code.

* Introduce Local (fork-aware) and Persistent storage.

* Fix borked merge.

* Prevent warning on depreacated client.backend

* Fix long lines.

* Clean up dependencies.

* Update core/primitives/src/offchain.rs

Co-Authored-By: André Silva <andre.beat@gmail.com>

* Update core/primitives/src/offchain.rs

Co-Authored-By: André Silva <andre.beat@gmail.com>
This commit is contained in:
Tomasz Drwięga
2019-07-02 20:11:06 +02:00
committed by Gavin Wood
parent 24aa882ebc
commit 2217c1e9a1
26 changed files with 726 additions and 118 deletions
+2
View File
@@ -26,6 +26,8 @@ tiny-keccak = "1.4.2"
assert_matches = "1.1"
wabt = "~0.7.4"
hex-literal = "0.2.0"
substrate-client = { path = "../client" }
substrate-offchain = { path = "../offchain/" }
[features]
default = []
+75 -4
View File
@@ -857,24 +857,28 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
.map_err(|_| "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) => {
ext_local_storage_set(kind: u32, key: *const u8, key_len: u32, value: *const u8, value_len: u32) => {
let kind = offchain::StorageKind::try_from(kind)
.map_err(|_| "storage kind OOB while ext_local_storage_set: wasm")?;
let key = this.memory.get(key, key_len as usize)
.map_err(|_| "OOB while ext_local_storage_set: wasm")?;
let value = this.memory.get(value, value_len as usize)
.map_err(|_| "OOB while ext_local_storage_set: wasm")?;
this.ext.offchain()
.map(|api| api.local_storage_set(&key, &value))
.map(|api| api.local_storage_set(kind, &key, &value))
.ok_or_else(|| "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 => {
ext_local_storage_get(kind: u32, key: *const u8, key_len: u32, value_len: *mut u32) -> *mut u8 => {
let kind = offchain::StorageKind::try_from(kind)
.map_err(|_| "storage kind OOB while ext_local_storage_get: wasm")?;
let key = this.memory.get(key, key_len as usize)
.map_err(|_| "OOB while ext_local_storage_get: wasm")?;
let maybe_value = this.ext.offchain()
.map(|api| api.local_storage_get(&key))
.map(|api| api.local_storage_get(kind, &key))
.ok_or_else(|| "Calling unavailable API ext_local_storage_get: wasm")?;
let (offset, len) = if let Some(value) = maybe_value {
@@ -891,6 +895,31 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(offset)
},
ext_local_storage_compare_and_set(
kind: u32,
key: *const u8,
key_len: u32,
old_value: *const u8,
old_value_len: u32,
new_value: *const u8,
new_value_len: u32
) -> u32 => {
let kind = offchain::StorageKind::try_from(kind)
.map_err(|_| "storage kind OOB while ext_local_storage_compare_and_set: wasm")?;
let key = this.memory.get(key, key_len as usize)
.map_err(|_| "OOB while ext_local_storage_compare_and_set: wasm")?;
let old_value = this.memory.get(old_value, old_value_len as usize)
.map_err(|_| "OOB while ext_local_storage_compare_and_set: wasm")?;
let new_value = this.memory.get(new_value, new_value_len as usize)
.map_err(|_| "OOB while ext_local_storage_compare_and_set: wasm")?;
let res = this.ext.offchain()
.map(|api| api.local_storage_compare_and_set(kind, &key, &old_value, &new_value))
.ok_or_else(|| "Calling unavailable API ext_local_storage_compare_andset: wasm")?;
Ok(if res { 0 } else { 1 })
},
ext_http_request_start(
method: *const u8,
method_len: u32,
@@ -1365,6 +1394,7 @@ mod tests {
use state_machine::TestExternalities as CoreTestExternalities;
use hex_literal::hex;
use primitives::map;
use substrate_offchain::testing;
type TestExternalities<H> = CoreTestExternalities<H, u64>;
@@ -1550,4 +1580,45 @@ mod tests {
ordered_trie_root::<Blake2Hasher, _, _>(vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()].iter()).as_fixed_bytes().encode()
);
}
#[test]
fn offchain_local_storage_should_work() {
use substrate_client::backend::OffchainStorage;
let mut ext = TestExternalities::<Blake2Hasher>::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.set_offchain_externalities(offchain);
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!(
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[]).unwrap(),
vec![0]
);
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
}
#[test]
fn offchain_http_should_work() {
let mut ext = TestExternalities::<Blake2Hasher>::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.set_offchain_externalities(offchain);
state.write().expect_request(
0,
testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:12345".into(),
body: vec![1, 2, 3, 4],
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
sent: true,
response: vec![1, 2, 3],
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
..Default::default()
},
);
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!(
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_http", &[]).unwrap(),
vec![0]
);
}
}
+1
View File
@@ -125,6 +125,7 @@ version = "2.0.0"
dependencies = [
"sr-io 2.0.0",
"sr-sandbox 2.0.0",
"sr-std 2.0.0",
"substrate-primitives 2.0.0",
]
+1
View File
@@ -8,6 +8,7 @@ edition = "2018"
crate-type = ["cdylib"]
[dependencies]
rstd = { package = "sr-std", path = "../../sr-std", default-features = false }
runtime_io = { package = "sr-io", path = "../../sr-io", default-features = false }
sandbox = { package = "sr-sandbox", path = "../../sr-sandbox", default-features = false }
substrate-primitives = { path = "../../primitives", default-features = false }
+38 -5
View File
@@ -1,9 +1,7 @@
#![no_std]
#![cfg_attr(feature = "strict", deny(warnings))]
extern crate alloc;
use alloc::vec::Vec;
use alloc::slice;
use rstd::{slice, vec::Vec, vec};
use runtime_io::{
set_storage, storage, clear_prefix, print, blake2_128, blake2_256,
@@ -11,7 +9,7 @@ use runtime_io::{
};
macro_rules! impl_stubs {
( $( $new_name:ident => $invoke:expr ),* ) => {
( $( $new_name:ident => $invoke:expr, )* ) => {
$(
impl_stubs!(@METHOD $new_name => $invoke);
)*
@@ -134,7 +132,42 @@ impl_stubs!(
Err(sandbox::Error::OutOfBounds) => 3,
};
[code].to_vec()
}
},
test_offchain_local_storage => |_| {
let kind = substrate_primitives::offchain::StorageKind::PERSISTENT;
assert_eq!(runtime_io::local_storage_get(kind, b"test"), None);
runtime_io::local_storage_set(kind, b"test", b"asd");
assert_eq!(runtime_io::local_storage_get(kind, b"test"), Some(b"asd".to_vec()));
let res = runtime_io::local_storage_compare_and_set(kind, b"test", b"asd", b"");
assert_eq!(res, true);
assert_eq!(runtime_io::local_storage_get(kind, b"test"), Some(b"".to_vec()));
[0].to_vec()
},
test_offchain_http => |_| {
use substrate_primitives::offchain::HttpRequestStatus;
let run = || -> Option<()> {
let id = runtime_io::http_request_start("POST", "http://localhost:12345", &[]).ok()?;
runtime_io::http_request_add_header(id, "X-Auth", "test").ok()?;
runtime_io::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?;
runtime_io::http_request_write_body(id, &[], None).ok()?;
let status = runtime_io::http_response_wait(&[id], None);
assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status.");
let headers = runtime_io::http_response_headers(id);
assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]);
let mut buffer = vec![0; 64];
let read = runtime_io::http_response_read_body(id, &mut buffer, None).ok()?;
assert_eq!(read, 3);
assert_eq!(&buffer[0..read], &[1, 2, 3]);
let read = runtime_io::http_response_read_body(id, &mut buffer, None).ok()?;
assert_eq!(read, 0);
Some(())
};
[if run().is_some() { 0 } else { 1 }].to_vec()
},
);
fn execute_sandboxed(code: &[u8], args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {