1use crate::types::{
2 ModuleInfo, ModuleMetadata, ModuleMetadataDeserializer, ModuleMetadataSerializer,
3};
4use massa_hash::Hash;
5use massa_sc_runtime::{CondomLimits, GasCosts, RuntimeModule};
6use massa_serialization::{DeserializeError, Deserializer, Serializer};
7use rand::RngCore;
8use rocksdb::{Direction, IteratorMode, Options, WriteBatch, DB};
9use std::path::PathBuf;
10use tracing::{debug, warn};
11
12const OPEN_ERROR: &str = "critical: rocksdb open operation failed";
13const CRUD_ERROR: &str = "critical: rocksdb crud operation failed";
14const DATA_SER_ERROR: &str = "critical: metadata serialization failed";
15const DATA_DESER_ERROR: &str = "critical: metadata deserialization failed";
16const MOD_SER_ERROR: &str = "critical: module serialization failed";
17const MOD_DESER_ERROR: &str = "critical: module deserialization failed";
18const MODULE_IDENT: u8 = 0u8;
19const DATA_IDENT: u8 = 1u8;
20
21#[macro_export]
23macro_rules! module_key {
24 ($bc_hash:expr) => {
25 [&$bc_hash.to_bytes()[..], &[MODULE_IDENT]].concat()
26 };
27}
28
29#[macro_export]
31macro_rules! metadata_key {
32 ($bc_hash:expr) => {
33 [&$bc_hash.to_bytes()[..], &[DATA_IDENT]].concat()
34 };
35}
36
37pub(crate) struct HDCache {
38 db: Option<DB>,
40 entry_count: usize,
43 max_entry_count: usize,
46 snip_amount: usize,
48 meta_ser: ModuleMetadataSerializer,
50 meta_deser: ModuleMetadataDeserializer,
52 #[cfg(test)]
55 pub(crate) read_count: std::sync::atomic::AtomicUsize,
56}
57
58impl HDCache {
59 pub fn new(path: PathBuf, max_entry_count: usize, snip_amount: usize) -> Self {
66 if path.exists() {
68 if let Err(e) = DB::destroy(&Options::default(), path.clone()) {
69 warn!("Failed to destroy the db: {:?}", e);
70 }
71 }
72 let db = DB::open_default(path).expect(OPEN_ERROR);
73 let entry_count = 0;
74
75 Self {
76 db: Some(db),
77 entry_count,
78 max_entry_count,
79 snip_amount,
80 meta_ser: ModuleMetadataSerializer::new(),
81 meta_deser: ModuleMetadataDeserializer::new(),
82 #[cfg(test)]
83 read_count: std::sync::atomic::AtomicUsize::new(0),
84 }
85 }
86
87 pub fn reset(&mut self) {
88 let path = self.db.as_ref().unwrap().path().to_path_buf();
89
90 let _ = self.db.take();
92
93 if path.exists() {
95 if let Err(e) = DB::destroy(&Options::default(), path.clone()) {
96 warn!("Failed to destroy the db: {:?}", e);
97 }
98 }
99 let db = DB::open_default(&path).expect(OPEN_ERROR);
101 self.db = Some(db);
102 self.entry_count = 0;
103 }
104
105 pub fn insert(&mut self, hash: Hash, module_info: ModuleInfo) {
107 if self.entry_count >= self.max_entry_count {
108 self.snip();
109 }
110
111 let mut ser_metadata = Vec::new();
112 let ser_module = match module_info {
113 ModuleInfo::Invalid(err_msg) => {
114 self.meta_ser
115 .serialize(&ModuleMetadata::Invalid(err_msg), &mut ser_metadata)
116 .expect(DATA_SER_ERROR);
117 Vec::new()
118 }
119 ModuleInfo::Module(module) => {
120 self.meta_ser
121 .serialize(&ModuleMetadata::NotExecuted, &mut ser_metadata)
122 .expect(DATA_SER_ERROR);
123 module.serialize().expect(MOD_SER_ERROR)
124 }
125 ModuleInfo::ModuleAndDelta((module, delta)) => {
126 self.meta_ser
127 .serialize(&ModuleMetadata::Delta(delta), &mut ser_metadata)
128 .expect(DATA_SER_ERROR);
129 module.serialize().expect(MOD_SER_ERROR)
130 }
131 };
132
133 let mut batch = WriteBatch::default();
134 batch.put(module_key!(hash), ser_module);
135 batch.put(metadata_key!(hash), ser_metadata);
136 self.db
137 .as_ref()
138 .expect(CRUD_ERROR)
139 .write(&batch)
140 .expect(CRUD_ERROR);
141
142 self.entry_count = self.entry_count.saturating_add(1);
143
144 debug!("(HD insert) entry_count is: {}", self.entry_count);
145 }
146
147 pub fn set_init_cost(&self, hash: Hash, init_cost: u64) {
153 let mut ser_metadata = Vec::new();
154 self.meta_ser
155 .serialize(&ModuleMetadata::Delta(init_cost), &mut ser_metadata)
156 .expect(DATA_SER_ERROR);
157 self.db
158 .as_ref()
159 .expect(CRUD_ERROR)
160 .put(metadata_key!(hash), ser_metadata)
161 .expect(CRUD_ERROR);
162 }
163
164 pub fn set_invalid(&self, hash: Hash, err_msg: String) {
166 let mut ser_metadata = Vec::new();
167 self.meta_ser
168 .serialize(&ModuleMetadata::Invalid(err_msg), &mut ser_metadata)
169 .expect(DATA_SER_ERROR);
170 self.db
171 .as_ref()
172 .expect(CRUD_ERROR)
173 .put(metadata_key!(hash), ser_metadata)
174 .expect(CRUD_ERROR);
175 }
176
177 pub fn get(
179 &self,
180 hash: Hash,
181 gas_costs: GasCosts,
182 condom_limits: CondomLimits,
183 ) -> Option<ModuleInfo> {
184 #[cfg(test)]
185 self.read_count
186 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
187
188 let mut iterator = self
189 .db
190 .as_ref()
191 .expect(CRUD_ERROR)
192 .iterator(IteratorMode::From(&module_key!(hash), Direction::Forward));
193
194 if let (Some(Ok((key_1, ser_module))), Some(Ok((key_2, ser_metadata)))) =
195 (iterator.next(), iterator.next())
196 {
197 if *key_1 == module_key!(hash) && *key_2 == metadata_key!(hash) {
198 let (_, metadata) = self
199 .meta_deser
200 .deserialize::<DeserializeError>(&ser_metadata)
201 .expect(DATA_DESER_ERROR);
202 if let ModuleMetadata::Invalid(err_msg) = metadata {
203 return Some(ModuleInfo::Invalid(err_msg));
204 }
205 let module = RuntimeModule::deserialize(
206 &ser_module,
207 gas_costs.max_instance_cost,
208 gas_costs,
209 condom_limits,
210 )
211 .expect(MOD_DESER_ERROR);
212 let result = match metadata {
213 ModuleMetadata::Invalid(err_msg) => ModuleInfo::Invalid(err_msg),
214 ModuleMetadata::NotExecuted => ModuleInfo::Module(module),
215 ModuleMetadata::Delta(delta) => ModuleInfo::ModuleAndDelta((module, delta)),
216 };
217 Some(result)
218 } else {
219 None
220 }
221 } else {
222 None
223 }
224 }
225
226 fn snip(&mut self) {
228 let mut iter = self.db.as_ref().expect(CRUD_ERROR).raw_iterator();
229 let mut batch = WriteBatch::default();
230 let mut snipped_count: usize = 0;
231
232 while snipped_count < self.snip_amount {
233 let mut rbytes = [0u8; 16];
235 rand::thread_rng().fill_bytes(&mut rbytes);
236 let key = *Hash::compute_from(&rbytes).to_bytes();
237
238 iter.seek_for_prev(key);
240
241 if !iter.valid() {
243 continue;
244 }
245
246 let metadata_key = iter.key().unwrap();
250 batch.delete(metadata_key);
251 iter.prev();
252 let module_key = iter.key().unwrap();
253 batch.delete(module_key);
254
255 snipped_count += 1;
257 }
258
259 if batch.len() / 2 != snipped_count {
261 panic!("snipped_count incoherence");
262 }
263
264 self.db
266 .as_ref()
267 .expect(CRUD_ERROR)
268 .write(&batch)
269 .expect(CRUD_ERROR);
270 self.entry_count -= snipped_count;
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use massa_hash::Hash;
278 use massa_sc_runtime::{Compiler, GasCosts, RuntimeModule};
279 use rand::thread_rng;
280 use serial_test::serial;
281 use tempfile::TempDir;
282
283 fn make_default_module_info() -> ModuleInfo {
284 let bytecode: Vec<u8> = vec![
285 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f,
286 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0b, 0x01, 0x07, 0x61, 0x64, 0x64, 0x5f,
287 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x41, 0x01,
288 0x6a, 0x0b, 0x00, 0x1a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x0a, 0x01, 0x00, 0x07,
289 0x61, 0x64, 0x64, 0x5f, 0x6f, 0x6e, 0x65, 0x02, 0x07, 0x01, 0x00, 0x01, 0x00, 0x02,
290 0x70, 0x30,
291 ];
292 ModuleInfo::Module(
293 RuntimeModule::new(
294 &bytecode,
295 GasCosts::default(),
296 Compiler::CL,
297 CondomLimits::default(),
298 )
299 .unwrap(),
300 )
301 }
302
303 fn setup() -> HDCache {
304 let tmp_path = TempDir::new().unwrap().path().to_path_buf();
305 HDCache::new(tmp_path, 1000, 10)
306 }
307
308 #[test]
309 #[serial]
310 fn test_basic_crud() {
311 let mut cache = setup();
312 let hash = Hash::compute_from(b"test_hash");
313 let module = make_default_module_info();
314
315 let init_cost = 100;
316 let gas_costs = GasCosts::default();
317 let condom_limits = CondomLimits::default();
318
319 cache.insert(hash, module);
320 let cached_module_v1 = cache
321 .get(hash, gas_costs.clone(), condom_limits.clone())
322 .unwrap();
323 assert!(matches!(cached_module_v1, ModuleInfo::Module(_)));
324
325 cache.set_init_cost(hash, init_cost);
326 let cached_module_v2 = cache
327 .get(hash, gas_costs.clone(), condom_limits.clone())
328 .unwrap();
329 assert!(matches!(cached_module_v2, ModuleInfo::ModuleAndDelta(_)));
330
331 let err_msg = "test_error".to_string();
332 cache.set_invalid(hash, err_msg.clone());
333 let cached_module_v3 = cache.get(hash, gas_costs, condom_limits.clone()).unwrap();
334 let ModuleInfo::Invalid(res_err) = cached_module_v3 else {
335 panic!("expected ModuleInfo::Invalid");
336 };
337 assert_eq!(res_err, err_msg);
338 }
339
340 #[test]
341 #[serial]
342 fn test_insert_more_than_max_entry() {
343 let mut cache = setup();
344 let module = make_default_module_info();
345
346 for count in 0..cache.max_entry_count {
348 let key = Hash::compute_from(count.to_string().as_bytes());
349 cache.insert(key, module.clone());
350 }
351 assert_eq!(cache.entry_count, cache.max_entry_count);
352
353 let key = Hash::compute_from(cache.max_entry_count.to_string().as_bytes());
355 cache.insert(key, module);
356 assert_eq!(
357 cache.entry_count,
358 cache.max_entry_count - cache.snip_amount + 1
359 );
360 dbg!(cache.entry_count);
361 }
362
363 #[test]
364 #[serial]
365 fn test_missing_module() {
366 let mut cache = setup();
367 let module = make_default_module_info();
368
369 let gas_costs = GasCosts::default();
370 let condom_limits = CondomLimits::default();
371
372 for count in 0..cache.max_entry_count {
373 let key = Hash::compute_from(count.to_string().as_bytes());
374 cache.insert(key, module.clone());
375 }
376
377 for _ in 0..cache.max_entry_count {
378 let mut rbytes = [0u8; 16];
379 thread_rng().fill_bytes(&mut rbytes);
380 let get_key = Hash::compute_from(&rbytes);
381 let cached_module = cache.get(get_key, gas_costs.clone(), condom_limits.clone());
382 assert!(cached_module.is_none());
383 }
384 }
385}