mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 08:11:04 +00:00
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:
committed by
Gavin Wood
parent
24aa882ebc
commit
2217c1e9a1
@@ -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 = []
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user