mithril_cardano_node_internal_database/test/
dummy_cardano_db.rs1use std::fs::File;
2use std::io::prelude::Write;
3use std::path::{Path, PathBuf};
4
5use mithril_common::entities::{ImmutableFileNumber, SlotNumber};
6
7use crate::entities::{ImmutableFile, LedgerStateSnapshot};
8use crate::{IMMUTABLE_DIR, LEDGER_DIR, VOLATILE_DIR};
9
10struct DummyImmutableDb {
12 dir: PathBuf,
14 immutables_files: Vec<ImmutableFile>,
16 non_immutables_files: Vec<PathBuf>,
18}
19
20struct DummyLedgerStateFolder {
22 dir: PathBuf,
24 ledger_state_snapshots: Vec<LedgerStateSnapshot>,
26 non_ledger_state_snapshots: Vec<PathBuf>,
28}
29
30impl DummyImmutableDb {
31 pub fn add_immutable_file(&mut self) -> ImmutableFileNumber {
33 let new_file_number = self.last_immutable_number().unwrap_or(0) + 1;
34 let mut new_files = write_immutable_trio(None, &self.dir, new_file_number);
35
36 self.immutables_files.append(&mut new_files);
37
38 new_file_number
39 }
40
41 pub fn last_immutable_number(&self) -> Option<ImmutableFileNumber> {
43 self.immutables_files.last().map(|f| f.number)
44 }
45}
46
47pub struct DummyCardanoDb {
49 dir: PathBuf,
51
52 immutable_db: DummyImmutableDb,
54
55 ledger_state_folder: DummyLedgerStateFolder,
57}
58
59impl DummyCardanoDb {
60 pub fn get_dir(&self) -> &PathBuf {
62 &self.dir
63 }
64
65 pub fn get_immutable_dir(&self) -> &Path {
67 &self.immutable_db.dir
68 }
69
70 pub fn get_ledger_dir(&self) -> &Path {
72 &self.ledger_state_folder.dir
73 }
74
75 pub fn get_volatile_dir(&self) -> PathBuf {
77 self.dir.join(VOLATILE_DIR)
78 }
79
80 pub fn get_immutable_files(&self) -> &Vec<ImmutableFile> {
82 &self.immutable_db.immutables_files
83 }
84
85 pub fn get_ledger_state_snapshots(&self) -> &Vec<LedgerStateSnapshot> {
87 &self.ledger_state_folder.ledger_state_snapshots
88 }
89
90 pub fn get_non_ledger_state_snapshots(&self) -> &Vec<PathBuf> {
92 &self.ledger_state_folder.non_ledger_state_snapshots
93 }
94
95 pub fn add_immutable_file(&mut self) -> ImmutableFileNumber {
97 self.immutable_db.add_immutable_file()
98 }
99
100 pub fn last_immutable_number(&self) -> Option<ImmutableFileNumber> {
102 self.immutable_db.last_immutable_number()
103 }
104
105 pub fn get_non_immutables_files(&self) -> &Vec<PathBuf> {
107 &self.immutable_db.non_immutables_files
108 }
109}
110
111pub struct DummyCardanoDbBuilder {
113 sub_dir: String,
114 immutables_to_write: Vec<ImmutableFileNumber>,
115 non_immutables_to_write: Vec<String>,
116 append_uncompleted_trio: bool,
117 immutable_file_size: Option<u64>,
118 ledger_snapshot_in_memory_to_write: Vec<SlotNumber>,
119 ledger_snapshot_legacy_to_write: Vec<SlotNumber>,
120 non_ledger_snapshot_to_write: Vec<String>,
121 ledger_file_size: Option<u64>,
122 volatile_files_to_write: Vec<String>,
123 volatile_file_size: Option<u64>,
124}
125
126impl DummyCardanoDbBuilder {
127 pub fn new(dir_name: &str) -> Self {
130 Self {
131 sub_dir: dir_name.to_string(),
132 immutables_to_write: vec![],
133 non_immutables_to_write: vec![],
134 append_uncompleted_trio: false,
135 immutable_file_size: None,
136 non_ledger_snapshot_to_write: vec![],
137 ledger_snapshot_in_memory_to_write: vec![],
138 ledger_snapshot_legacy_to_write: vec![],
139 ledger_file_size: None,
140 volatile_files_to_write: vec![],
141 volatile_file_size: None,
142 }
143 }
144
145 pub fn with_immutables(&mut self, immutables: &[ImmutableFileNumber]) -> &mut Self {
148 self.immutables_to_write = immutables.to_vec();
149 self
150 }
151
152 pub fn with_non_immutables(&mut self, non_immutables: &[&str]) -> &mut Self {
154 self.non_immutables_to_write = non_immutables.iter().map(|f| f.to_string()).collect();
155 self
156 }
157
158 pub fn with_legacy_ledger_snapshots(&mut self, snapshot_slot_numbers: &[u64]) -> &mut Self {
160 self.ledger_snapshot_legacy_to_write =
161 snapshot_slot_numbers.iter().map(|s| SlotNumber(*s)).collect();
162 self
163 }
164
165 pub fn with_in_memory_ledger_snapshots(&mut self, snapshot_slot_numbers: &[u64]) -> &mut Self {
167 self.ledger_snapshot_in_memory_to_write =
168 snapshot_slot_numbers.iter().map(|s| SlotNumber(*s)).collect();
169 self
170 }
171
172 pub fn with_non_ledger_files(&mut self, files: &[&str]) -> &mut Self {
174 self.non_ledger_snapshot_to_write = files.iter().map(|name| name.to_string()).collect();
175 self
176 }
177
178 pub fn set_ledger_file_size(&mut self, file_size: u64) -> &mut Self {
185 self.ledger_file_size = Some(file_size);
186 self
187 }
188
189 pub fn with_volatile_files(&mut self, files: &[&str]) -> &mut Self {
191 self.volatile_files_to_write = files.iter().map(|f| f.to_string()).collect();
192 self
193 }
194
195 pub fn set_volatile_file_size(&mut self, file_size: u64) -> &mut Self {
197 self.volatile_file_size = Some(file_size);
198 self
199 }
200
201 pub fn append_immutable_trio(&mut self) -> &mut Self {
205 self.append_uncompleted_trio = true;
206 self
207 }
208
209 pub fn set_immutable_trio_file_size(&mut self, trio_file_size: u64) -> &mut Self {
213 assert!(
214 trio_file_size.is_multiple_of(3),
215 "'trio_file_size' must be a multiple of 3"
216 );
217
218 self.immutable_file_size = Some(trio_file_size / 3);
219 self
220 }
221
222 pub fn build(&self) -> DummyCardanoDb {
224 let dir = get_test_dir(&self.sub_dir);
225
226 let mut non_immutables_files = vec![];
227 let mut ledger_state_snapshots = vec![];
228 let mut non_ledger_state_snapshots = vec![];
229 let mut immutable_numbers = self.immutables_to_write.clone();
230 immutable_numbers.sort();
231
232 if self.append_uncompleted_trio {
233 write_immutable_trio(
234 self.immutable_file_size,
235 &dir.join(IMMUTABLE_DIR),
236 match immutable_numbers.last() {
237 None => 0,
238 Some(last) => last + 1,
239 },
240 );
241 }
242
243 for non_immutable in &self.non_immutables_to_write {
244 non_immutables_files.push(write_dummy_file(
245 self.immutable_file_size,
246 &dir.join(IMMUTABLE_DIR),
247 non_immutable,
248 ));
249 }
250
251 for filename in &self.non_ledger_snapshot_to_write {
252 let ledger_file_path =
253 write_dummy_file(self.ledger_file_size, &dir.join(LEDGER_DIR), filename);
254 non_ledger_state_snapshots.push(ledger_file_path);
255 }
256
257 for slot_number in &self.ledger_snapshot_legacy_to_write {
258 let ledger_file = write_dummy_file(
259 self.ledger_file_size,
260 &dir.join(LEDGER_DIR),
261 &slot_number.to_string(),
262 );
263
264 ledger_state_snapshots.push(LedgerStateSnapshot::legacy(
265 ledger_file,
266 *slot_number,
267 slot_number.to_string().into(),
268 ));
269 }
270
271 for slot_number in &self.ledger_snapshot_in_memory_to_write {
272 let ledger_state_snapshot =
273 write_in_memory_ledger_snapshot(*slot_number, &dir, self.ledger_file_size);
274 ledger_state_snapshots.push(ledger_state_snapshot);
275 }
276
277 for filename in &self.volatile_files_to_write {
278 write_dummy_file(self.volatile_file_size, &dir.join(VOLATILE_DIR), filename);
279 }
280
281 let immutable_db = DummyImmutableDb {
282 dir: dir.join(IMMUTABLE_DIR),
283 immutables_files: immutable_numbers
284 .into_iter()
285 .flat_map(|ifn| {
286 write_immutable_trio(self.immutable_file_size, &dir.join(IMMUTABLE_DIR), ifn)
287 })
288 .collect::<Vec<_>>(),
289 non_immutables_files,
290 };
291
292 let ledger_state_folder = DummyLedgerStateFolder {
293 dir: dir.join(LEDGER_DIR),
294 ledger_state_snapshots,
295 non_ledger_state_snapshots,
296 };
297
298 DummyCardanoDb {
299 dir,
300 immutable_db,
301 ledger_state_folder,
302 }
303 }
304}
305
306fn write_immutable_trio(
307 optional_size: Option<u64>,
308 dir: &Path,
309 immutable: ImmutableFileNumber,
310) -> Vec<ImmutableFile> {
311 let mut result = vec![];
312 for filename in [
313 format!("{immutable:05}.chunk"),
314 format!("{immutable:05}.primary"),
315 format!("{immutable:05}.secondary"),
316 ] {
317 let file = write_dummy_file(optional_size, dir, &filename);
318 result.push(ImmutableFile {
319 number: immutable.to_owned(),
320 path: file,
321 filename: filename.to_string(),
322 });
323 }
324 result
325}
326
327fn write_in_memory_ledger_snapshot(
328 slot_number: SlotNumber,
329 dir: &Path,
330 optional_size: Option<u64>,
331) -> LedgerStateSnapshot {
332 let ledger_folder_name = slot_number.to_string();
333 let ledger_folder_path = dir.join(LEDGER_DIR).join(&ledger_folder_name);
334 let optional_file_size = optional_size.map(|s| s / 3);
335
336 std::fs::create_dir_all(ledger_folder_path.join(LedgerStateSnapshot::IN_MEMORY_TABLES))
337 .unwrap();
338 write_dummy_file(
339 optional_file_size,
340 &ledger_folder_path,
341 LedgerStateSnapshot::IN_MEMORY_STATE,
342 );
343 write_in_memory_meta_file(optional_file_size, &ledger_folder_path);
344 write_dummy_file(
345 optional_file_size,
346 &ledger_folder_path.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
347 LedgerStateSnapshot::IN_MEMORY_TVAR,
348 );
349
350 LedgerStateSnapshot::InMemoryUpTo10_6 {
351 path: ledger_folder_path,
352 slot_number,
353 folder_name: ledger_folder_name.into(),
354 }
355}
356
357fn write_in_memory_meta_file(optional_size: Option<u64>, dir: &Path) -> PathBuf {
361 const META_JSON: &str = r#"{"backend": "utxohd-mem"}"#;
362
363 let file_path = dir.join(LedgerStateSnapshot::IN_MEMORY_META);
364 let mut file = File::create(&file_path).unwrap();
365 write!(file, "{META_JSON}").unwrap();
366 if let Some(file_size) = optional_size {
367 let json_size = META_JSON.len() as u64;
368 if file_size > json_size {
369 let padding = vec![b' '; (file_size - json_size) as usize];
370 file.write_all(&padding).unwrap();
371 }
372 }
373 file_path
374}
375
376fn write_dummy_file(optional_size: Option<u64>, dir: &Path, filename: &str) -> PathBuf {
379 let file = dir.join(Path::new(filename));
380 let mut source_file = File::create(&file).unwrap();
381
382 write!(source_file, "This is a test file named '{filename}'").unwrap();
383
384 if let Some(file_size) = optional_size {
385 writeln!(source_file).unwrap();
386 source_file.set_len(file_size).unwrap();
387 }
388
389 file
390}
391
392fn get_test_dir(subdir_name: &str) -> PathBuf {
393 let db_dir = std::env::temp_dir()
395 .join("mithril_test")
396 .join("test_cardano_db")
397 .join(subdir_name);
398
399 if db_dir.exists() {
400 std::fs::remove_dir_all(&db_dir)
401 .unwrap_or_else(|e| panic!("Could not remove dir {db_dir:?}: {e}"));
402 }
403 std::fs::create_dir_all(&db_dir)
404 .unwrap_or_else(|e| panic!("Could not create dir {db_dir:?}: {e}"));
405
406 for subdir_name in [LEDGER_DIR, IMMUTABLE_DIR, VOLATILE_DIR] {
407 std::fs::create_dir(db_dir.join(subdir_name)).unwrap();
408 }
409
410 db_dir
411}
412
413#[cfg(test)]
414mod tests {
415 use mithril_common::{assert_dir_eq, current_function};
416
417 use super::*;
418
419 #[test]
420 fn writing_empty_dummy_cardano_db_structure() {
421 let db = DummyCardanoDbBuilder::new(current_function!()).build();
422
423 assert_eq!(db.get_immutable_dir(), db.get_dir().join(IMMUTABLE_DIR));
424 assert_eq!(db.get_ledger_dir(), db.get_dir().join(LEDGER_DIR));
425 assert_eq!(db.get_volatile_dir(), db.get_dir().join(VOLATILE_DIR));
426 assert_eq!(db.last_immutable_number(), None);
427 assert_dir_eq!(
428 &db.dir,
429 format!(
430 "* {IMMUTABLE_DIR}/
431 * {LEDGER_DIR}/
432 * {VOLATILE_DIR}/"
433 )
434 );
435 }
436
437 #[test]
438 fn writing_immutable_files() {
439 let db = DummyCardanoDbBuilder::new(current_function!())
440 .with_immutables(&[1, 2])
441 .build();
442
443 assert_eq!(db.last_immutable_number(), Some(2));
444 assert_eq!(db.get_immutable_files().len(), 6); assert_dir_eq!(
446 &db.dir,
447 format!(
448 "* {IMMUTABLE_DIR}/
449 ** 00001.chunk
450 ** 00001.primary
451 ** 00001.secondary
452 ** 00002.chunk
453 ** 00002.primary
454 ** 00002.secondary
455 * {LEDGER_DIR}/
456 * {VOLATILE_DIR}/"
457 )
458 );
459 }
460
461 #[test]
462 fn adding_non_completed_immutable_files_trio_is_not_take_in_account_in_resulting_db_metadata() {
463 let db = DummyCardanoDbBuilder::new(current_function!())
464 .with_immutables(&[1])
465 .append_immutable_trio()
466 .build();
467
468 assert_eq!(db.last_immutable_number(), Some(1));
469 assert_eq!(db.get_immutable_files().len(), 3); assert_dir_eq!(
471 &db.dir,
472 format!(
473 "* {IMMUTABLE_DIR}/
474 ** 00001.chunk
475 ** 00001.primary
476 ** 00001.secondary
477 ** 00002.chunk
478 ** 00002.primary
479 ** 00002.secondary
480 * {LEDGER_DIR}/
481 * {VOLATILE_DIR}/"
482 )
483 );
484 }
485
486 #[test]
487 fn writing_non_immutable_files() {
488 let db = DummyCardanoDbBuilder::new(current_function!())
489 .with_non_immutables(&["test1.txt", "test2.txt"])
490 .build();
491
492 let non_immutable_files = db.get_non_immutables_files();
493 assert_eq!(
494 non_immutable_files,
495 &vec![
496 db.get_dir().join(IMMUTABLE_DIR).join("test1.txt"),
497 db.get_dir().join(IMMUTABLE_DIR).join("test2.txt"),
498 ]
499 );
500 assert_dir_eq!(
501 &db.dir,
502 format!(
503 "* {IMMUTABLE_DIR}/
504 ** test1.txt
505 ** test2.txt
506 * {LEDGER_DIR}/
507 * {VOLATILE_DIR}/"
508 )
509 );
510 }
511
512 #[test]
513 fn writing_ledger_snapshots() {
514 let db = DummyCardanoDbBuilder::new(current_function!())
515 .with_legacy_ledger_snapshots(&[100])
516 .with_in_memory_ledger_snapshots(&[200])
517 .build();
518
519 let snapshots = db.get_ledger_state_snapshots();
520 assert_eq!(snapshots.len(), 2);
521 assert!(snapshots.iter().any(|s| s.slot_number() == SlotNumber(100)));
522 assert!(snapshots.iter().any(|s| s.slot_number() == SlotNumber(200)));
523 assert_dir_eq!(
524 &db.dir,
525 format!(
526 "* {IMMUTABLE_DIR}/
527 * {LEDGER_DIR}/
528 ** 200/
529 *** {}/
530 **** {}
531 *** {}
532 *** {}
533 ** 100
534 * {VOLATILE_DIR}/",
535 LedgerStateSnapshot::IN_MEMORY_TABLES,
536 LedgerStateSnapshot::IN_MEMORY_TVAR,
537 LedgerStateSnapshot::IN_MEMORY_META,
538 LedgerStateSnapshot::IN_MEMORY_STATE,
539 )
540 );
541 }
542
543 #[test]
544 fn setting_file_sizes() {
545 fn get_file_size(path: &Path) -> u64 {
546 std::fs::metadata(path).unwrap().len()
547 }
548
549 let (immutable_file_size, ledger_file_size, volatile_file_size) = (3000, 6000, 9000);
550 let db = DummyCardanoDbBuilder::new(current_function!())
551 .with_immutables(&[1])
552 .with_legacy_ledger_snapshots(&[100])
553 .with_in_memory_ledger_snapshots(&[200])
554 .with_volatile_files(&["test.txt"])
555 .set_immutable_trio_file_size(immutable_file_size)
556 .set_ledger_file_size(ledger_file_size)
557 .set_volatile_file_size(volatile_file_size)
558 .build();
559
560 for file in db.get_immutable_files() {
561 assert_eq!(get_file_size(&file.path), immutable_file_size / 3);
562 }
563
564 for ledger_state_snapshot in db.get_ledger_state_snapshots() {
565 match ledger_state_snapshot {
566 LedgerStateSnapshot::Legacy { path, .. } => {
567 assert_eq!(get_file_size(path), ledger_file_size);
568 }
569 LedgerStateSnapshot::InMemoryUpTo10_6 { path, .. } => {
570 assert_eq!(
571 get_file_size(&path.join(LedgerStateSnapshot::IN_MEMORY_STATE)),
572 ledger_file_size / 3
573 );
574 assert_eq!(
575 get_file_size(&path.join(LedgerStateSnapshot::IN_MEMORY_META)),
576 ledger_file_size / 3
577 );
578 assert_eq!(
579 get_file_size(
580 &path
581 .join(LedgerStateSnapshot::IN_MEMORY_TABLES)
582 .join(LedgerStateSnapshot::IN_MEMORY_TVAR)
583 ),
584 ledger_file_size / 3
585 );
586 }
587 LedgerStateSnapshot::InMemoryFrom10_7 { .. } => {
588 unreachable!(
589 "DummyCardanoDbBuilder does not create InMemoryFrom10_7 snapshots in this test"
590 )
591 }
592 }
593 }
594
595 assert_eq!(
596 get_file_size(&db.get_volatile_dir().join("test.txt")),
597 volatile_file_size
598 );
599 }
600}