mithril_aggregator/services/snapshotter/
compressed_archive_snapshotter.rs

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
22/// Compressed Archive Snapshotter create a compressed file.
23pub struct CompressedArchiveSnapshotter {
24    /// DB directory to snapshot
25    db_directory: PathBuf,
26
27    /// Directory to store ongoing snapshot
28    ongoing_snapshot_directory: PathBuf,
29
30    /// Compression algorithm to use
31    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    /// Snapshotter factory
153    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        // spawn a separate thread to prevent blocking
203        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    /// Returns the list of files and directories to include in ancillary snapshot.
243    ///
244    /// Those files will be copied to the target folder in order to have fixed copies that do not
245    /// change during the snapshot process.
246    ///
247    /// The immutable file included in the ancillary archive corresponds to the last one (and not finalized yet)
248    /// when the immutable file number given to the function corresponds to the penultimate.
249    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                // Only the last ledger file should be included
631                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}