sp_runtime/offchain/
storage.rs1use sp_core::offchain::StorageKind;
21
22pub type StorageValue = StorageValueRef<'static>;
24
25pub struct StorageValueRef<'a> {
27 key: &'a [u8],
28 kind: StorageKind,
29}
30
31#[derive(Debug, PartialEq, Eq)]
33pub enum StorageRetrievalError {
34 Undecodable,
36}
37
38#[derive(Debug, PartialEq, Eq)]
40pub enum MutateStorageError<T, E> {
41 ConcurrentModification(T),
44 ValueFunctionFailed(E),
48}
49
50impl<'a> StorageValueRef<'a> {
51 pub fn persistent(key: &'a [u8]) -> Self {
53 Self { key, kind: StorageKind::PERSISTENT }
54 }
55
56 pub fn local(key: &'a [u8]) -> Self {
58 Self { key, kind: StorageKind::LOCAL }
59 }
60
61 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 pub fn clear(&mut self) {
72 sp_io::offchain::local_storage_clear(self.kind, self.key)
73 }
74
75 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 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 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}