Files
pezkuwi-subxt/substrate/primitives/runtime/src/offchain/storage.rs
T
Bernhard Schuster 95d73994c1 offchain storage lock (#6010)
* feat/offchain/storage: add remove interface method

* feat/offchain/storeage: add remote to StorageValueRef

* feat/offchain/storage: add storage lock

* fix/review: Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* refactor/offchain/storage/lock: introduce `Lockable` trait part 1 of 2

* chore/offchain/rename: _remove -> clean

* feat/offchain/storage/lock: add TimeAndBlock based part 2 of 2

* fix/offchain/storage/lock: block and time expiry must be && not ||

* chore/offchain/storage: minor fmt doc comments

* doc/comment: prefer markdown emphasis over CAPS

* doc/comment: rewrap multiline module level docs

* doc/comment: rephrase

* impl sleep_until and use the actual time for the test env

* feat/test: add more tests, ignore some sample impl doctests

* fix/review: Apply suggestions from code review

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>

* doc/comment: better description

* fix/review: Apply suggestions from code review

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>

* chore/storage: lifetime cleanup

* fix/cleanup: trait bounds, cargo-spellcheck + extra explanations

* fix/doc: periods +-

* fix/review: Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* cleanup: remove explicit lifetime bound, copy -> clone

* fix/review: make trait Lockable contain only static, try_lock should not return Err(Option<L>),

* chore/lifetimes: remove a couple of lifetime bounds which the compiler can figure out

* refactor: migrate to an instant based

* fix/feedback: fix, reduce, rename, docs update pending

* docs/reword: adjust to changed code

* fix/offchain/testing: timestamp and sleep_until shall not block

* chore/lines: lines must < 100 chars

* fix/docs: add missing pub field doc comments

* refactor/x: try_lock does not need to return an Option<_>

* refactor/simplify: a better way of waiting for a lock to resolve

* docs: consistency

* fix/line: < 100

* fix/doctest/use: avoid crate::

* fix/doctest: *

* fix/review: remove unused trait bound

* fix/review: pretty by const fn

* fix/review: reduce default timeout to 20s

* docs: grammar

* fix/review: add with_block_deadline

* doc: revamp BlockNumberProvider documentation to be less frame centric

* chore: fmt

* docs: add missing doc comment

Co-authored-by: Bernhard Schuster <bernhard@parity.io>
Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
2020-05-28 14:26:11 +02:00

164 lines
4.6 KiB
Rust

// This file is part of Substrate.
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A set of storage helpers for offchain workers.
use sp_core::offchain::StorageKind;
/// A storage value with a static key.
pub type StorageValue = StorageValueRef<'static>;
/// An abstraction over local storage value.
pub struct StorageValueRef<'a> {
key: &'a [u8],
kind: StorageKind,
}
impl<'a> StorageValueRef<'a> {
/// Create a new reference to a value in the persistent local storage.
pub fn persistent(key: &'a [u8]) -> Self {
Self { key, kind: StorageKind::PERSISTENT }
}
/// Create a new reference to a value in the fork-aware local storage.
pub fn local(key: &'a [u8]) -> Self {
Self { key, kind: StorageKind::LOCAL }
}
/// Set the value of the storage to encoding of given parameter.
///
/// Note that the storage may be accessed by workers running concurrently,
/// if you happen to write a `get-check-set` pattern you should most likely
/// be using `mutate` instead.
pub fn set(&self, value: &impl codec::Encode) {
value.using_encoded(|val| {
sp_io::offchain::local_storage_set(self.kind, self.key, val)
})
}
/// Remove the associated value from the storage.
pub fn clear(&mut self) {
sp_io::offchain::local_storage_clear(self.kind, self.key)
}
/// Retrieve & decode the value from storage.
///
/// Note that if you want to do some checks based on the value
/// and write changes after that you should rather be using `mutate`.
///
/// The function returns `None` if the value was not found in storage,
/// otherwise a decoding of the value to requested type.
pub fn get<T: codec::Decode>(&self) -> Option<Option<T>> {
sp_io::offchain::local_storage_get(self.kind, self.key)
.map(|val| T::decode(&mut &*val).ok())
}
/// Retrieve & decode the value and set it to a new one atomically.
///
/// Function `f` should return a new value that we should attempt to write to storage.
/// This function returns:
/// 1. `Ok(Ok(T))` in case the value has been successfully set.
/// 2. `Ok(Err(T))` in case the value was calculated by the passed closure `f`,
/// but it could not be stored.
/// 3. `Err(_)` in case `f` returns an error.
pub fn mutate<T, E, F>(&self, f: F) -> Result<Result<T, T>, E> where
T: codec::Codec,
F: FnOnce(Option<Option<T>>) -> Result<T, E>
{
let value = sp_io::offchain::local_storage_get(self.kind, self.key);
let decoded = value.as_deref().map(|mut v| T::decode(&mut v).ok());
let val = f(decoded)?;
let set = val.using_encoded(|new_val| {
sp_io::offchain::local_storage_compare_and_set(
self.kind,
self.key,
value,
new_val,
)
});
if set {
Ok(Ok(val))
} else {
Ok(Err(val))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_io::TestExternalities;
use sp_core::offchain::{
OffchainExt,
OffchainStorage,
testing,
};
#[test]
fn should_set_and_get() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = TestExternalities::default();
t.register_extension(OffchainExt::new(offchain));
t.execute_with(|| {
let val = StorageValue::persistent(b"testval");
assert_eq!(val.get::<u32>(), None);
val.set(&15_u32);
assert_eq!(val.get::<u32>(), Some(Some(15_u32)));
assert_eq!(val.get::<Vec<u8>>(), Some(None));
assert_eq!(
state.read().persistent_storage.get(b"", b"testval"),
Some(vec![15_u8, 0, 0, 0])
);
})
}
#[test]
fn should_mutate() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = TestExternalities::default();
t.register_extension(OffchainExt::new(offchain));
t.execute_with(|| {
let val = StorageValue::persistent(b"testval");
let result = val.mutate::<u32, (), _>(|val| {
assert_eq!(val, None);
Ok(16_u32)
});
assert_eq!(result, Ok(Ok(16_u32)));
assert_eq!(val.get::<u32>(), Some(Some(16_u32)));
assert_eq!(
state.read().persistent_storage.get(b"", b"testval"),
Some(vec![16_u8, 0, 0, 0])
);
// mutate again, but this time early-exit.
let res = val.mutate::<u32, (), _>(|val| {
assert_eq!(val, Some(Some(16_u32)));
Err(())
});
assert_eq!(res, Err(()));
})
}
}