Skip to main content

reth_cli_commands/download/
verify.rs

1use super::{manifest::OutputFileChecksum, progress::ArchiveVerificationProgress};
2use blake3::Hasher;
3use eyre::Result;
4use reth_fs_util as fs;
5use std::{io::Read, path::Path};
6
7/// Verifies and cleans up extracted output files in one target directory.
8pub(crate) struct OutputVerifier<'a> {
9    /// Directory containing the output files declared by the manifest.
10    target_dir: &'a Path,
11}
12
13impl<'a> OutputVerifier<'a> {
14    /// Creates a verifier for one extraction target directory.
15    pub(crate) const fn new(target_dir: &'a Path) -> Self {
16        Self { target_dir }
17    }
18
19    /// Returns `true` only when every declared output file exists and matches size and BLAKE3.
20    /// Returns `false` if any file is missing, mismatched, or no outputs were declared.
21    pub(crate) fn verify(&self, output_files: &[OutputFileChecksum]) -> Result<bool> {
22        self.verify_with_progress(output_files, None)
23    }
24
25    /// Returns `true` only when every declared output file exists and matches size and BLAKE3,
26    /// updating the optional verification progress as file bytes are hashed.
27    pub(crate) fn verify_with_progress(
28        &self,
29        output_files: &[OutputFileChecksum],
30        mut progress: Option<&mut ArchiveVerificationProgress<'_>>,
31    ) -> Result<bool> {
32        if output_files.is_empty() {
33            return Ok(false);
34        }
35
36        for expected in output_files {
37            let output_path = self.target_dir.join(&expected.path);
38            let meta = match fs::metadata(&output_path) {
39                Ok(meta) => meta,
40                Err(_) => return Ok(false),
41            };
42            if meta.len() != expected.size {
43                return Ok(false);
44            }
45
46            let actual = Self::file_blake3_hex(&output_path, progress.as_deref_mut())?;
47            if !actual.eq_ignore_ascii_case(&expected.blake3) {
48                return Ok(false);
49            }
50        }
51
52        Ok(true)
53    }
54
55    /// Removes any declared output files so a fresh archive attempt can restart cleanly.
56    pub(crate) fn cleanup(&self, output_files: &[OutputFileChecksum]) {
57        for output in output_files {
58            let _ = fs::remove_file(self.target_dir.join(&output.path));
59        }
60    }
61
62    /// Computes the hex-encoded BLAKE3 checksum for one plain output file.
63    fn file_blake3_hex(
64        path: &Path,
65        mut progress: Option<&mut ArchiveVerificationProgress<'_>>,
66    ) -> Result<String> {
67        let mut file = fs::open(path)?;
68        let mut hasher = Hasher::new();
69        let mut buf = [0_u8; 64 * 1024];
70
71        loop {
72            let n = file.read(&mut buf)?;
73            if n == 0 {
74                break;
75            }
76            hasher.update(&buf[..n]);
77            if let Some(progress) = progress.as_deref_mut() {
78                progress.record_verified(n as u64);
79            }
80        }
81
82        Ok(hasher.finalize().to_hex().to_string())
83    }
84}