soil_client/db/
offchain.rs1use std::{collections::HashMap, sync::Arc};
10
11use super::{columns, Database, DbHash, Transaction};
12use log::error;
13use parking_lot::Mutex;
14
15#[derive(Clone)]
17pub struct LocalStorage {
18 db: Arc<dyn Database<DbHash>>,
19 locks: Arc<Mutex<HashMap<Vec<u8>, Arc<Mutex<()>>>>>,
20}
21
22impl std::fmt::Debug for LocalStorage {
23 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
24 fmt.debug_struct("LocalStorage").finish()
25 }
26}
27
28impl LocalStorage {
29 #[cfg(any(feature = "test-helpers", test))]
31 pub fn new_test() -> Self {
32 let db = kvdb_memorydb::create(super::utils::NUM_COLUMNS);
33 let db = subsoil::database::as_database(db);
34 Self::new(db as _)
35 }
36
37 pub fn new(db: Arc<dyn Database<DbHash>>) -> Self {
39 Self { db, locks: Default::default() }
40 }
41}
42
43impl subsoil::core::offchain::OffchainStorage for LocalStorage {
44 fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
45 let mut tx = Transaction::new();
46 tx.set(columns::OFFCHAIN, &concatenate_prefix_and_key(prefix, key), value);
47
48 if let Err(err) = self.db.commit(tx) {
49 error!("Error setting on local storage: {}", err)
50 }
51 }
52
53 fn remove(&mut self, prefix: &[u8], key: &[u8]) {
54 let mut tx = Transaction::new();
55 tx.remove(columns::OFFCHAIN, &concatenate_prefix_and_key(prefix, key));
56
57 if let Err(err) = self.db.commit(tx) {
58 error!("Error removing on local storage: {}", err)
59 }
60 }
61
62 fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
63 self.db.get(columns::OFFCHAIN, &concatenate_prefix_and_key(prefix, key))
64 }
65
66 fn compare_and_set(
67 &mut self,
68 prefix: &[u8],
69 item_key: &[u8],
70 old_value: Option<&[u8]>,
71 new_value: &[u8],
72 ) -> bool {
73 let key = concatenate_prefix_and_key(prefix, item_key);
74 let key_lock = {
75 let mut locks = self.locks.lock();
76 locks.entry(key.clone()).or_default().clone()
77 };
78
79 let is_set;
80 {
81 let _key_guard = key_lock.lock();
82 let val = self.db.get(columns::OFFCHAIN, &key);
83 is_set = val.as_deref() == old_value;
84
85 if is_set {
86 self.set(prefix, item_key, new_value)
87 }
88 }
89
90 let mut locks = self.locks.lock();
92 {
93 drop(key_lock);
94 let key_lock = locks.get_mut(&key);
95 if key_lock.and_then(Arc::get_mut).is_some() {
96 locks.remove(&key);
97 }
98 }
99 is_set
100 }
101}
102
103pub(crate) fn concatenate_prefix_and_key(prefix: &[u8], key: &[u8]) -> Vec<u8> {
105 prefix.iter().chain(key.iter()).cloned().collect()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use subsoil::core::offchain::OffchainStorage;
112
113 #[test]
114 fn should_compare_and_set_and_clear_the_locks_map() {
115 let mut storage = LocalStorage::new_test();
116 let prefix = b"prefix";
117 let key = b"key";
118 let value = b"value";
119
120 storage.set(prefix, key, value);
121 assert_eq!(storage.get(prefix, key), Some(value.to_vec()));
122
123 assert_eq!(storage.compare_and_set(prefix, key, Some(value), b"asd"), true);
124 assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
125 assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
126 }
127
128 #[test]
129 fn should_compare_and_set_on_empty_field() {
130 let mut storage = LocalStorage::new_test();
131 let prefix = b"prefix";
132 let key = b"key";
133
134 assert_eq!(storage.compare_and_set(prefix, key, None, b"asd"), true);
135 assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
136 assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
137 }
138}