sp_runtime/offchain/
storage.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! A set of storage helpers for offchain workers.
19
20use sp_core::offchain::StorageKind;
21
22/// A storage value with a static key.
23pub type StorageValue = StorageValueRef<'static>;
24
25/// An abstraction over local storage value.
26pub struct StorageValueRef<'a> {
27	key: &'a [u8],
28	kind: StorageKind,
29}
30
31/// Reason for not being able to provide the stored value
32#[derive(Debug, PartialEq, Eq)]
33pub enum StorageRetrievalError {
34	/// Value found but undecodable
35	Undecodable,
36}
37
38/// Possible errors when mutating a storage value.
39#[derive(Debug, PartialEq, Eq)]
40pub enum MutateStorageError<T, E> {
41	/// The underlying db failed to update due to a concurrent modification.
42	/// Contains the new value that was not stored.
43	ConcurrentModification(T),
44	/// The function given to us to create the value to be stored failed.
45	/// May be used to signal that having looked at the existing value,
46	/// they don't want to mutate it.
47	ValueFunctionFailed(E),
48}
49
50impl<'a> StorageValueRef<'a> {
51	/// Create a new reference to a value in the persistent local storage.
52	pub fn persistent(key: &'a [u8]) -> Self {
53		Self { key, kind: StorageKind::PERSISTENT }
54	}
55
56	/// Create a new reference to a value in the fork-aware local storage.
57	pub fn local(key: &'a [u8]) -> Self {
58		Self { key, kind: StorageKind::LOCAL }
59	}
60
61	/// Set the value of the storage to encoding of given parameter.
62	///
63	/// Note that the storage may be accessed by workers running concurrently,
64	/// if you happen to write a `get-check-set` pattern you should most likely
65	/// be using `mutate` instead.
66	pub fn set(&self, value: &impl codec::Encode) {
67		value.using_encoded(|val| sp_io::offchain::local_storage_set(self.kind, self.key, val))
68	}
69
70	/// Remove the associated value from the storage.
71	pub fn clear(&mut self) {
72		sp_io::offchain::local_storage_clear(self.kind, self.key)
73	}
74
75	/// Retrieve & decode the value from storage.
76	///
77	/// Note that if you want to do some checks based on the value
78	/// and write changes after that, you should rather be using `mutate`.
79	///
80	/// Returns the value if stored.
81	/// Returns an error if the value could not be decoded.
82	pub fn get<T: codec::Decode>(&self) -> Result<Option<T>, StorageRetrievalError> {
83		sp_io::offchain::local_storage_get(self.kind, self.key)
84			.map(|val| T::decode(&mut &*val).map_err(|_| StorageRetrievalError::Undecodable))
85			.transpose()
86	}
87
88	/// Retrieve & decode the current value and set it to a new value atomically.
89	///
90	/// Function `mutate_val` takes as input the current value and should
91	/// return a new value that is attempted to be written to storage.
92	///
93	/// This function returns:
94	/// 1. `Ok(T)` in case the value has been successfully set.
95	/// 2. `Err(MutateStorageError::ConcurrentModification(T))` in case the value was calculated
96	/// by the passed closure `mutate_val`, but it could not be stored.
97	/// 3. `Err(MutateStorageError::ValueFunctionFailed(_))` in case `mutate_val` returns an error.
98	pub fn mutate<T, E, F>(&self, mutate_val: F) -> Result<T, MutateStorageError<T, E>>
99	where
100		T: codec::Codec,
101		F: FnOnce(Result<Option<T>, StorageRetrievalError>) -> Result<T, E>,
102	{
103		let value = sp_io::offchain::local_storage_get(self.kind, self.key);
104		let decoded = value
105			.as_deref()
106			.map(|mut bytes| T::decode(&mut bytes).map_err(|_| StorageRetrievalError::Undecodable))
107			.transpose();
108
109		let val =
110			mutate_val(decoded).map_err(|err| MutateStorageError::ValueFunctionFailed(err))?;
111
112		let set = val.using_encoded(|new_val| {
113			sp_io::offchain::local_storage_compare_and_set(self.kind, self.key, value, new_val)
114		});
115		if set {
116			Ok(val)
117		} else {
118			Err(MutateStorageError::ConcurrentModification(val))
119		}
120	}
121}
122
123#[cfg(test)]
124mod tests {
125	use super::*;
126	use sp_core::offchain::{testing, OffchainDbExt};
127	use sp_io::TestExternalities;
128
129	#[test]
130	fn should_set_and_get() {
131		let (offchain, state) = testing::TestOffchainExt::new();
132		let mut t = TestExternalities::default();
133		t.register_extension(OffchainDbExt::new(offchain));
134
135		t.execute_with(|| {
136			let val = StorageValue::persistent(b"testval");
137
138			assert_eq!(val.get::<u32>(), Ok(None));
139
140			val.set(&15_u32);
141
142			assert_eq!(val.get::<u32>(), Ok(Some(15_u32)));
143			assert_eq!(val.get::<Vec<u8>>(), Err(StorageRetrievalError::Undecodable));
144			assert_eq!(state.read().persistent_storage.get(b"testval"), Some(vec![15_u8, 0, 0, 0]));
145		})
146	}
147
148	#[test]
149	fn should_mutate() {
150		let (offchain, state) = testing::TestOffchainExt::new();
151		let mut t = TestExternalities::default();
152		t.register_extension(OffchainDbExt::new(offchain));
153
154		t.execute_with(|| {
155			let val = StorageValue::persistent(b"testval");
156
157			let result = val.mutate::<u32, (), _>(|val| {
158				assert_eq!(val, Ok(None));
159
160				Ok(16_u32)
161			});
162			assert_eq!(result, Ok(16_u32));
163			assert_eq!(val.get::<u32>(), Ok(Some(16_u32)));
164			assert_eq!(state.read().persistent_storage.get(b"testval"), Some(vec![16_u8, 0, 0, 0]));
165
166			// mutate again, but this time early-exit.
167			let res = val.mutate::<u32, (), _>(|val| {
168				assert_eq!(val, Ok(Some(16_u32)));
169				Err(())
170			});
171			assert_eq!(res, Err(MutateStorageError::ValueFunctionFailed(())));
172		})
173	}
174}