1use anyhow::{anyhow, Context};
2use async_trait::async_trait;
3use slog::{debug, warn, Logger};
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use mithril_common::digesters::{
9 immutable_trio_names, ImmutableFile, LedgerFile, IMMUTABLE_DIR, LEDGER_DIR,
10};
11use mithril_common::entities::{AncillaryFilesManifest, CompressionAlgorithm, ImmutableFileNumber};
12use mithril_common::logging::LoggerExtensions;
13use mithril_common::StdResult;
14
15use crate::dependency_injection::DependenciesBuilderError;
16use crate::tools::file_archiver::appender::{AppenderData, AppenderEntries, TarAppender};
17use crate::tools::file_archiver::{ArchiveParameters, FileArchive, FileArchiver};
18use crate::tools::file_size;
19
20use super::{ancillary_signer::AncillarySigner, Snapshotter};
21
22pub struct CompressedArchiveSnapshotter {
24 db_directory: PathBuf,
26
27 ongoing_snapshot_directory: PathBuf,
29
30 compression_algorithm: CompressionAlgorithm,
32
33 file_archiver: Arc<FileArchiver>,
34
35 ancillary_signer: Arc<dyn AncillarySigner>,
36
37 logger: Logger,
38}
39
40#[async_trait]
41impl Snapshotter for CompressedArchiveSnapshotter {
42 async fn snapshot_all_completed_immutables(
43 &self,
44 archive_name_without_extension: &str,
45 ) -> StdResult<FileArchive> {
46 debug!(
47 self.logger,
48 "Snapshotting all completed immutables into archive: '{archive_name_without_extension}'"
49 );
50
51 let paths_to_include = ImmutableFile::list_completed_in_dir(&self.db_directory)
52 .with_context(|| {
53 format!(
54 "Can not list completed immutables in database directory: '{}'",
55 self.db_directory.display()
56 )
57 })?
58 .into_iter()
59 .map(|immutable_file: ImmutableFile| {
60 PathBuf::from(IMMUTABLE_DIR).join(immutable_file.filename)
61 })
62 .collect();
63 let appender = AppenderEntries::new(paths_to_include, self.db_directory.clone())?;
64 self.snapshot(archive_name_without_extension, appender)
65 .await
66 }
67
68 async fn snapshot_ancillary(
69 &self,
70 immutable_file_number: ImmutableFileNumber,
71 archive_name_without_extension: &str,
72 ) -> StdResult<FileArchive> {
73 debug!(
74 self.logger,
75 "Snapshotting ancillary archive: '{archive_name_without_extension}'"
76 );
77
78 let temp_snapshot_directory =
79 self.temp_ancillary_snapshot_directory(archive_name_without_extension);
80 fs::create_dir(&temp_snapshot_directory).with_context(|| {
81 format!(
82 "Can not create temporary snapshot ancillary directory: '{}'",
83 temp_snapshot_directory.display()
84 )
85 })?;
86
87 let snapshot_result = self
88 .snapshot_ancillary_from_temp_directory(
89 immutable_file_number,
90 &temp_snapshot_directory,
91 archive_name_without_extension,
92 )
93 .await;
94
95 if let Err(e) = fs::remove_dir_all(&temp_snapshot_directory) {
96 warn!(
97 self.logger, "Failed to remove temporary snapshot ancillary directory '{}'", temp_snapshot_directory.display();
98 "error" => ?e
99 );
100 }
101 snapshot_result
102 }
103
104 async fn snapshot_immutable_trio(
105 &self,
106 immutable_file_number: ImmutableFileNumber,
107 archive_name_without_extension: &str,
108 ) -> StdResult<FileArchive> {
109 debug!(
110 self.logger,
111 "Snapshotting immutable trio {immutable_file_number} into archive '{archive_name_without_extension}'"
112 );
113
114 let files_to_archive = immutable_trio_names(immutable_file_number)
115 .iter()
116 .map(|filename| PathBuf::from(IMMUTABLE_DIR).join(filename))
117 .collect();
118 let appender = AppenderEntries::new(files_to_archive, self.db_directory.clone())?;
119
120 self.snapshot(archive_name_without_extension, appender)
121 .await
122 }
123
124 async fn compute_immutable_files_total_uncompressed_size(
125 &self,
126 up_to_immutable_file_number: ImmutableFileNumber,
127 ) -> StdResult<u64> {
128 if up_to_immutable_file_number == 0 {
129 return Err(anyhow!(
130 "Could not compute the total size without immutable files"
131 ));
132 }
133 let immutable_directory = self.db_directory.join(IMMUTABLE_DIR);
134
135 tokio::task::spawn_blocking(move || -> StdResult<_> {
136 let immutable_paths = (1..=up_to_immutable_file_number)
137 .flat_map(immutable_trio_names)
138 .map(|filename| immutable_directory.join(filename))
139 .collect();
140
141 file_size::compute_size(immutable_paths)
142 })
143 .await?
144 }
145
146 fn compression_algorithm(&self) -> CompressionAlgorithm {
147 self.compression_algorithm
148 }
149}
150
151impl CompressedArchiveSnapshotter {
152 pub fn new(
154 db_directory: PathBuf,
155 ongoing_snapshot_directory: PathBuf,
156 compression_algorithm: CompressionAlgorithm,
157 file_archiver: Arc<FileArchiver>,
158 ancillary_signer: Arc<dyn AncillarySigner>,
159 logger: Logger,
160 ) -> StdResult<CompressedArchiveSnapshotter> {
161 if ongoing_snapshot_directory.exists() {
162 fs::remove_dir_all(&ongoing_snapshot_directory).with_context(|| {
163 format!(
164 "Can not remove snapshotter directory: '{}'.",
165 ongoing_snapshot_directory.display()
166 )
167 })?;
168 }
169
170 fs::create_dir(&ongoing_snapshot_directory).map_err(|e| {
171 DependenciesBuilderError::Initialization {
172 message: format!(
173 "Can not create snapshotter directory: '{}'.",
174 ongoing_snapshot_directory.display()
175 ),
176 error: Some(e.into()),
177 }
178 })?;
179
180 Ok(Self {
181 db_directory,
182 ongoing_snapshot_directory,
183 compression_algorithm,
184 file_archiver,
185 ancillary_signer,
186 logger: logger.new_with_component_name::<Self>(),
187 })
188 }
189
190 async fn snapshot<T: TarAppender + 'static>(
191 &self,
192 name_without_extension: &str,
193 appender: T,
194 ) -> StdResult<FileArchive> {
195 let file_archiver = self.file_archiver.clone();
196 let parameters = ArchiveParameters {
197 archive_name_without_extension: name_without_extension.to_string(),
198 target_directory: self.ongoing_snapshot_directory.clone(),
199 compression_algorithm: self.compression_algorithm,
200 };
201
202 let file_archive = tokio::task::spawn_blocking(move || -> StdResult<FileArchive> {
204 file_archiver.archive(parameters, appender)
205 })
206 .await??;
207
208 Ok(file_archive)
209 }
210
211 fn temp_ancillary_snapshot_directory(&self, discriminator: &str) -> PathBuf {
212 self.ongoing_snapshot_directory
213 .join(format!("temp-ancillary-{discriminator}"))
214 }
215
216 async fn snapshot_ancillary_from_temp_directory(
217 &self,
218 immutable_file_number: ImmutableFileNumber,
219 temp_snapshot_directory: &Path,
220 archive_name_without_extension: &str,
221 ) -> StdResult<FileArchive> {
222 let paths_to_include = self
223 .get_files_and_directories_for_ancillary_snapshot(
224 immutable_file_number,
225 temp_snapshot_directory,
226 )
227 .await?;
228 let signed_manifest = self
229 .build_and_sign_ancillary_manifest(paths_to_include.clone(), temp_snapshot_directory)
230 .await?;
231 let appender =
232 AppenderEntries::new(paths_to_include, temp_snapshot_directory.to_path_buf())?.chain(
233 AppenderData::from_json(
234 PathBuf::from(AncillaryFilesManifest::ANCILLARY_MANIFEST_FILE_NAME),
235 &signed_manifest,
236 )?,
237 );
238 self.snapshot(archive_name_without_extension, appender)
239 .await
240 }
241
242 async fn get_files_and_directories_for_ancillary_snapshot(
250 &self,
251 immutable_file_number: u64,
252 target_folder: &Path,
253 ) -> StdResult<Vec<PathBuf>> {
254 let next_immutable_file_number = immutable_file_number + 1;
255 let mut files_to_snapshot: Vec<PathBuf> = immutable_trio_names(next_immutable_file_number)
256 .into_iter()
257 .map(|filename| PathBuf::from(IMMUTABLE_DIR).join(filename))
258 .collect();
259
260 let db_ledger_dir = self.db_directory.join(LEDGER_DIR);
261 let ledger_files = LedgerFile::list_all_in_dir(&db_ledger_dir)?;
262 let last_ledger = ledger_files.last().ok_or(anyhow!(
263 "No ledger file found in directory: `{}`",
264 db_ledger_dir.display()
265 ))?;
266 files_to_snapshot.push(PathBuf::from(LEDGER_DIR).join(&last_ledger.filename));
267
268 fs::create_dir(target_folder.join(IMMUTABLE_DIR))
269 .with_context(|| format!("Can not create folder: `{}`", target_folder.display()))?;
270 fs::create_dir(target_folder.join(LEDGER_DIR))
271 .with_context(|| format!("Can not create folder: `{}`", target_folder.display()))?;
272
273 for file in &files_to_snapshot {
274 let source = self.db_directory.join(file);
275 let target = target_folder.join(file);
276 tokio::fs::copy(&source, &target).await.with_context(|| {
277 format!(
278 "Failed to copy file `{}` to `{}`",
279 source.display(),
280 target.display()
281 )
282 })?;
283 }
284
285 Ok(files_to_snapshot)
286 }
287
288 async fn build_and_sign_ancillary_manifest(
289 &self,
290 paths_to_include: Vec<PathBuf>,
291 temp_snapshot_directory: &Path,
292 ) -> StdResult<AncillaryFilesManifest> {
293 let manifest =
294 AncillaryFilesManifest::from_paths(temp_snapshot_directory, paths_to_include).await?;
295 let signature = self
296 .ancillary_signer
297 .compute_ancillary_manifest_signature(&manifest)
298 .await?;
299 let signed_manifest = AncillaryFilesManifest {
300 signature: Some(signature),
301 ..manifest
302 };
303
304 Ok(signed_manifest)
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use std::fs::File;
311 use std::path::Path;
312 use std::sync::Arc;
313
314 use mithril_common::digesters::DummyCardanoDbBuilder;
315 use mithril_common::test_utils::assert_equivalent;
316 use mithril_common::{assert_dir_eq, current_function, temp_dir_create};
317
318 use crate::services::ancillary_signer::MockAncillarySigner;
319 use crate::test_tools::TestLogger;
320
321 use super::*;
322
323 fn list_files(test_dir: &Path) -> Vec<String> {
324 fs::read_dir(test_dir)
325 .unwrap()
326 .map(|f| f.unwrap().file_name().to_str().unwrap().to_owned())
327 .collect()
328 }
329
330 fn snapshotter_for_test(
331 test_directory: &Path,
332 db_directory: &Path,
333 compression_algorithm: CompressionAlgorithm,
334 ) -> CompressedArchiveSnapshotter {
335 CompressedArchiveSnapshotter::new(
336 db_directory.to_path_buf(),
337 test_directory.join("ongoing_snapshot"),
338 compression_algorithm,
339 Arc::new(FileArchiver::new_for_test(
340 test_directory.join("verification"),
341 )),
342 Arc::new(MockAncillarySigner::new()),
343 TestLogger::stdout(),
344 )
345 .unwrap()
346 }
347
348 #[test]
349 fn return_parametrized_compression_algorithm() {
350 let test_dir = temp_dir_create!();
351 let snapshotter = snapshotter_for_test(
352 &test_dir,
353 Path::new("whatever"),
354 CompressionAlgorithm::Zstandard,
355 );
356
357 assert_eq!(
358 CompressionAlgorithm::Zstandard,
359 snapshotter.compression_algorithm()
360 );
361 }
362
363 #[test]
364 fn should_create_directory_if_does_not_exist() {
365 let test_dir = temp_dir_create!();
366 let ongoing_snapshot_directory = test_dir.join("ongoing_snapshot");
367 let db_directory = test_dir.join("whatever");
368
369 CompressedArchiveSnapshotter::new(
370 db_directory,
371 ongoing_snapshot_directory.clone(),
372 CompressionAlgorithm::Gzip,
373 Arc::new(FileArchiver::new_for_test(test_dir.join("verification"))),
374 Arc::new(MockAncillarySigner::new()),
375 TestLogger::stdout(),
376 )
377 .unwrap();
378
379 assert!(ongoing_snapshot_directory.is_dir());
380 }
381
382 #[test]
383 fn should_clean_ongoing_snapshot_directory_if_already_exists() {
384 let test_dir = temp_dir_create!();
385 let ongoing_snapshot_directory = test_dir.join("ongoing_snapshot");
386 let db_directory = test_dir.join("whatever");
387
388 fs::create_dir_all(&ongoing_snapshot_directory).unwrap();
389
390 File::create(ongoing_snapshot_directory.join("whatever.txt")).unwrap();
391
392 CompressedArchiveSnapshotter::new(
393 db_directory,
394 ongoing_snapshot_directory.clone(),
395 CompressionAlgorithm::Gzip,
396 Arc::new(FileArchiver::new_for_test(test_dir.join("verification"))),
397 Arc::new(MockAncillarySigner::new()),
398 TestLogger::stdout(),
399 )
400 .unwrap();
401
402 assert_eq!(0, fs::read_dir(ongoing_snapshot_directory).unwrap().count());
403 }
404
405 #[tokio::test]
406 async fn should_create_snapshots_in_its_ongoing_snapshot_directory() {
407 let test_dir = temp_dir_create!();
408 let pending_snapshot_directory = test_dir.join("pending_snapshot");
409 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
410 .with_immutables(&[1])
411 .append_immutable_trio()
412 .build();
413
414 let snapshotter = CompressedArchiveSnapshotter::new(
415 cardano_db.get_dir().clone(),
416 pending_snapshot_directory.clone(),
417 CompressionAlgorithm::Gzip,
418 Arc::new(FileArchiver::new_for_test(test_dir.join("verification"))),
419 Arc::new(MockAncillarySigner::new()),
420 TestLogger::stdout(),
421 )
422 .unwrap();
423 let snapshot = snapshotter
424 .snapshot_all_completed_immutables("whatever")
425 .await
426 .unwrap();
427
428 assert_eq!(
429 pending_snapshot_directory,
430 snapshot.get_file_path().parent().unwrap()
431 );
432 }
433
434 mod snapshot_all_completed_immutables {
435 use super::*;
436
437 #[tokio::test]
438 async fn include_only_completed_immutables() {
439 let test_dir = temp_dir_create!();
440 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
441 .with_immutables(&[1, 2, 3])
442 .append_immutable_trio()
443 .with_ledger_files(&["437"])
444 .with_volatile_files(&["blocks-0.dat"])
445 .with_non_immutables(&["random_file.txt", "00002.trap"])
446 .build();
447
448 let snapshotter =
449 snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip);
450
451 let snapshot = snapshotter
452 .snapshot_all_completed_immutables("completed_immutables")
453 .await
454 .unwrap();
455
456 let unpack_dir = snapshot.unpack_gzip(&test_dir);
457 let unpacked_files = list_files(&unpack_dir);
458 let unpacked_immutable_files = list_files(&unpack_dir.join(IMMUTABLE_DIR));
459
460 assert_equivalent(vec![IMMUTABLE_DIR.to_string()], unpacked_files);
461 assert_equivalent(
462 vec![
463 "00001.chunk".to_string(),
464 "00001.primary".to_string(),
465 "00001.secondary".to_string(),
466 "00002.chunk".to_string(),
467 "00002.primary".to_string(),
468 "00002.secondary".to_string(),
469 "00003.chunk".to_string(),
470 "00003.primary".to_string(),
471 "00003.secondary".to_string(),
472 ],
473 unpacked_immutable_files,
474 );
475 }
476 }
477
478 mod snapshot_immutable_trio {
479 use super::*;
480
481 #[tokio::test]
482 async fn include_only_immutable_trio() {
483 let test_dir = temp_dir_create!();
484 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
485 .with_immutables(&[1, 2, 3])
486 .with_ledger_files(&["437"])
487 .with_volatile_files(&["blocks-0.dat"])
488 .with_non_immutables(&["random_file.txt", "00002.trap"])
489 .build();
490
491 let snapshotter =
492 snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip);
493
494 let snapshot = snapshotter
495 .snapshot_immutable_trio(2, "immutable-2")
496 .await
497 .unwrap();
498
499 let unpack_dir = snapshot.unpack_gzip(&test_dir);
500 let unpacked_files = list_files(&unpack_dir);
501 let unpacked_immutable_files = list_files(&unpack_dir.join(IMMUTABLE_DIR));
502
503 assert_equivalent(vec![IMMUTABLE_DIR.to_string()], unpacked_files);
504 assert_equivalent(
505 vec![
506 "00002.chunk".to_string(),
507 "00002.primary".to_string(),
508 "00002.secondary".to_string(),
509 ],
510 unpacked_immutable_files,
511 );
512 }
513 }
514
515 mod snapshot_ancillary {
516 use mithril_common::test_utils::fake_keys;
517
518 use super::*;
519
520 #[tokio::test]
521 async fn getting_files_to_include_copy_them_to_a_target_directory_while_keeping_source_dir_structure(
522 ) {
523 let test_dir = temp_dir_create!();
524 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
525 .with_immutables(&[1, 2])
526 .with_ledger_files(&["737"])
527 .build();
528 let snapshotter =
529 snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip);
530 let ancillary_snapshot_dir = test_dir.join("ancillary_snapshot");
531 fs::create_dir(&ancillary_snapshot_dir).unwrap();
532
533 snapshotter
534 .get_files_and_directories_for_ancillary_snapshot(1, &ancillary_snapshot_dir)
535 .await
536 .unwrap();
537
538 assert_dir_eq!(
539 &ancillary_snapshot_dir,
540 format!(
541 "* {IMMUTABLE_DIR}/
542 ** 00002.chunk
543 ** 00002.primary
544 ** 00002.secondary
545 * {LEDGER_DIR}/
546 ** 737"
547 )
548 );
549 }
550
551 #[tokio::test]
552 async fn delete_temporary_working_directory_after_snapshot_is_created() {
553 let test_dir = temp_dir_create!();
554 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
555 .with_immutables(&[1, 2])
556 .with_ledger_files(&["637"])
557 .build();
558 let snapshotter = CompressedArchiveSnapshotter {
559 ancillary_signer: Arc::new(MockAncillarySigner::that_succeeds_with_signature(
560 fake_keys::signable_manifest_signature()[0],
561 )),
562 ..snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip)
563 };
564
565 snapshotter
566 .snapshot_ancillary(1, "ancillary")
567 .await
568 .unwrap();
569
570 let temp_ancillary_snapshot_dir =
571 snapshotter.temp_ancillary_snapshot_directory("ancillary");
572 assert!(
573 !temp_ancillary_snapshot_dir.exists(),
574 "Expected temporary ancillary snapshot directory to be deleted, but it still exists: {}",
575 temp_ancillary_snapshot_dir.display()
576 );
577 }
578
579 #[tokio::test]
580 async fn delete_temporary_working_directory_even_if_snapshot_creation_fails() {
581 let test_dir = temp_dir_create!();
582 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
583 .with_immutables(&[1, 2])
584 .with_ledger_files(&["637"])
585 .build();
586 let snapshotter = CompressedArchiveSnapshotter {
587 ancillary_signer: Arc::new(MockAncillarySigner::that_fails_with_message("failure")),
588 ..snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip)
589 };
590
591 snapshotter
592 .snapshot_ancillary(1, "ancillary")
593 .await
594 .unwrap_err();
595
596 let temp_ancillary_snapshot_dir =
597 snapshotter.temp_ancillary_snapshot_directory("ancillary");
598 assert!(
599 !temp_ancillary_snapshot_dir.exists(),
600 "Expected temporary ancillary snapshot directory to be deleted, but it still exists: {}",
601 temp_ancillary_snapshot_dir.display()
602 );
603 }
604
605 #[tokio::test]
606 async fn create_archive_should_embed_only_last_ledger_and_last_immutables() {
607 let test_dir = temp_dir_create!();
608 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
609 .with_immutables(&[1, 2, 3])
610 .with_ledger_files(&["437", "537", "637", "737", "9not_included"])
611 .with_volatile_files(&["blocks-0.dat", "blocks-1.dat", "blocks-2.dat"])
612 .build();
613 fs::create_dir(cardano_db.get_dir().join("whatever")).unwrap();
614
615 let snapshotter = CompressedArchiveSnapshotter {
616 ancillary_signer: Arc::new(MockAncillarySigner::that_succeeds_with_signature(
617 fake_keys::signable_manifest_signature()[0],
618 )),
619 ..snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip)
620 };
621
622 let snapshot = snapshotter
623 .snapshot_ancillary(2, "ancillary")
624 .await
625 .unwrap();
626
627 let unpack_dir = snapshot.unpack_gzip(&test_dir);
628 assert_dir_eq!(
629 &unpack_dir,
630 format!(
632 "* {IMMUTABLE_DIR}/
633 ** 00003.chunk
634 ** 00003.primary
635 ** 00003.secondary
636 * {LEDGER_DIR}/
637 ** 737
638 * {}",
639 AncillaryFilesManifest::ANCILLARY_MANIFEST_FILE_NAME
640 )
641 );
642 }
643
644 #[tokio::test]
645 async fn create_archive_fail_if_manifest_signing_fail() {
646 let test_dir = temp_dir_create!();
647 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
648 .with_immutables(&[1, 2])
649 .with_ledger_files(&["737"])
650 .build();
651
652 let snapshotter = CompressedArchiveSnapshotter {
653 ancillary_signer: Arc::new(MockAncillarySigner::that_fails_with_message(
654 "MockAncillarySigner failed",
655 )),
656 ..snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip)
657 };
658
659 let err = snapshotter
660 .snapshot_ancillary(1, "ancillary")
661 .await
662 .expect_err("Must fail if manifest signing fails");
663 assert!(
664 err.to_string().contains("MockAncillarySigner failed"),
665 "Expected error message to be raised by the mock ancillary signer, but got: '{err:?}'",
666 );
667 }
668
669 #[tokio::test]
670 async fn create_archive_generate_sign_and_include_manifest_file() {
671 let test_dir = temp_dir_create!();
672 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
673 .with_immutables(&[1, 2, 3])
674 .with_ledger_files(&["321", "737"])
675 .with_non_immutables(&["not_to_include.txt"])
676 .build();
677 File::create(cardano_db.get_dir().join("not_to_include_as_well.txt")).unwrap();
678
679 let snapshotter = CompressedArchiveSnapshotter {
680 ancillary_signer: Arc::new(MockAncillarySigner::that_succeeds_with_signature(
681 fake_keys::signable_manifest_signature()[0],
682 )),
683 ..snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip)
684 };
685
686 let archive = snapshotter
687 .snapshot_ancillary(2, "ancillary")
688 .await
689 .unwrap();
690 let unpacked = archive.unpack_gzip(test_dir);
691 let manifest_path = unpacked.join(AncillaryFilesManifest::ANCILLARY_MANIFEST_FILE_NAME);
692
693 assert!(manifest_path.exists());
694
695 let manifest = serde_json::from_reader::<_, AncillaryFilesManifest>(
696 File::open(&manifest_path).unwrap(),
697 )
698 .unwrap();
699
700 assert_eq!(
701 vec![
702 &PathBuf::from(IMMUTABLE_DIR).join("00003.chunk"),
703 &PathBuf::from(IMMUTABLE_DIR).join("00003.primary"),
704 &PathBuf::from(IMMUTABLE_DIR).join("00003.secondary"),
705 &PathBuf::from(LEDGER_DIR).join("737"),
706 ],
707 manifest.data.keys().collect::<Vec<_>>()
708 );
709 assert_eq!(
710 Some(
711 fake_keys::signable_manifest_signature()[0]
712 .try_into()
713 .unwrap()
714 ),
715 manifest.signature
716 )
717 }
718 }
719
720 mod compute_immutable_total_and_average_uncompressed_size {
721 use mithril_common::current_function;
722
723 use super::*;
724
725 #[tokio::test]
726 async fn should_compute_the_total_size_of_the_immutables() {
727 let test_dir = temp_dir_create!();
728 let immutable_trio_file_size = 777;
729
730 let cardano_db = DummyCardanoDbBuilder::new(current_function!())
731 .with_immutables(&[1, 2, 3])
732 .set_immutable_trio_file_size(immutable_trio_file_size)
733 .with_ledger_files(&["737"])
734 .set_ledger_file_size(6666)
735 .with_volatile_files(&["blocks-0.dat"])
736 .set_volatile_file_size(99)
737 .build();
738
739 let snapshotter =
740 snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip);
741
742 let sizes = snapshotter
743 .compute_immutable_files_total_uncompressed_size(2)
744 .await
745 .unwrap();
746
747 assert_eq!(immutable_trio_file_size * 2, sizes)
748 }
749
750 #[tokio::test]
751 async fn should_return_an_error_when_compute_up_to_immutable_0() {
752 let test_dir = temp_dir_create!();
753 let cardano_db = DummyCardanoDbBuilder::new(current_function!()).build();
754
755 let snapshotter =
756 snapshotter_for_test(&test_dir, cardano_db.get_dir(), CompressionAlgorithm::Gzip);
757
758 snapshotter
759 .compute_immutable_files_total_uncompressed_size(0)
760 .await
761 .expect_err("Should return an error when no immutable file number");
762 }
763 }
764}