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
+76 -15
View File
@@ -20,6 +20,7 @@ use std::{
collections::BTreeMap,
sync::Arc,
};
use client::backend::OffchainStorage;
use parking_lot::RwLock;
use primitives::offchain::{
self,
@@ -29,6 +30,7 @@ use primitives::offchain::{
Timestamp,
CryptoKind,
CryptoKeyId,
StorageKind,
};
/// Pending request.
@@ -61,6 +63,11 @@ pub struct PendingRequest {
pub struct State {
/// A list of pending requests.
pub requests: BTreeMap<RequestId, PendingRequest>,
expected_requests: BTreeMap<RequestId, PendingRequest>,
/// Persistent local storage
pub persistent_storage: client::in_mem::OffchainStorage,
/// Local storage
pub local_storage: client::in_mem::OffchainStorage,
}
impl State {
@@ -74,7 +81,7 @@ impl State {
) {
match self.requests.get_mut(&RequestId(id)) {
None => {
panic!("Missing expected request: {:?}.\n\nAll: {:?}", id, self.requests);
panic!("Missing pending request: {:?}.\n\nAll: {:?}", id, self.requests);
}
Some(req) => {
assert_eq!(
@@ -86,12 +93,47 @@ impl State {
}
}
}
fn fulfill_expected(&mut self, id: u16) {
if let Some(mut req) = self.expected_requests.remove(&RequestId(id)) {
let response = std::mem::replace(&mut req.response, vec![]);
let headers = std::mem::replace(&mut req.response_headers, vec![]);
self.fulfill_pending_request(id, req, response, headers);
}
}
/// Add expected HTTP request.
///
/// This method can be used to initialize expected HTTP requests and their responses
/// before running the actual code that utilizes them (for instance before calling into runtime).
/// Expected request has to be fulfilled before this struct is dropped,
/// the `response` and `response_headers` fields will be used to return results to the callers.
pub fn expect_request(&mut self, id: u16, expected: PendingRequest) {
self.expected_requests.insert(RequestId(id), expected);
}
}
impl Drop for State {
fn drop(&mut self) {
if !self.expected_requests.is_empty() {
panic!("Unfulfilled expected requests: {:?}", self.expected_requests);
}
}
}
/// Implementation of offchain externalities used for tests.
#[derive(Clone, Default, Debug)]
pub struct TestOffchainExt(pub Arc<RwLock<State>>);
impl TestOffchainExt {
/// Create new `TestOffchainExt` and a reference to the internal state.
pub fn new() -> (Self, Arc<RwLock<State>>) {
let ext = Self::default();
let state = ext.0.clone();
(ext, state)
}
}
impl offchain::Externalities for TestOffchainExt {
fn submit_transaction(&mut self, _ex: Vec<u8>) -> Result<(), ()> {
unimplemented!("not needed in tests so far")
@@ -129,21 +171,34 @@ impl offchain::Externalities for TestOffchainExt {
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_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
let mut state = self.0.write();
match kind {
StorageKind::LOCAL => &mut state.local_storage,
StorageKind::PERSISTENT => &mut state.persistent_storage,
}.set(b"", key, value);
}
fn local_storage_compare_and_set(
&mut self,
_key: &[u8],
_old_value: &[u8],
_new_value: &[u8]
) {
unimplemented!("not needed in tests so far")
kind: StorageKind,
key: &[u8],
old_value: &[u8],
new_value: &[u8]
) -> bool {
let mut state = self.0.write();
match kind {
StorageKind::LOCAL => &mut state.local_storage,
StorageKind::PERSISTENT => &mut state.persistent_storage,
}.compare_and_set(b"", key, old_value, new_value)
}
fn local_storage_get(&mut self, _key: &[u8]) -> Option<Vec<u8>> {
unimplemented!("not needed in tests so far")
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
let state = self.0.read();
match kind {
StorageKind::LOCAL => &state.local_storage,
StorageKind::PERSISTENT => &state.persistent_storage,
}.get(b"", key)
}
fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result<RequestId, ()> {
@@ -180,15 +235,21 @@ impl offchain::Externalities for TestOffchainExt {
_deadline: Option<Timestamp>
) -> Result<(), HttpError> {
let mut state = self.0.write();
if let Some(req) = state.requests.get_mut(&request_id) {
let sent = {
let req = state.requests.get_mut(&request_id).ok_or(HttpError::IoError)?;
req.body.extend(chunk);
if chunk.is_empty() {
req.sent = true;
}
req.body.extend(chunk);
Ok(())
} else {
Err(HttpError::IoError)
req.sent
};
if sent {
state.fulfill_expected(request_id.0);
}
Ok(())
}
fn http_response_wait(