reth_cli_commands/download/
verify.rs1use super::{manifest::OutputFileChecksum, progress::ArchiveVerificationProgress};
2use blake3::Hasher;
3use eyre::Result;
4use reth_fs_util as fs;
5use std::{io::Read, path::Path};
6
7pub(crate) struct OutputVerifier<'a> {
9 target_dir: &'a Path,
11 static_files_dir: Option<&'a Path>,
13}
14
15impl<'a> OutputVerifier<'a> {
16 pub(crate) const fn new(target_dir: &'a Path, static_files_dir: Option<&'a Path>) -> Self {
18 Self { target_dir, static_files_dir }
19 }
20
21 pub(crate) fn verify(&self, output_files: &[OutputFileChecksum]) -> Result<bool> {
24 self.verify_with_progress(output_files, None)
25 }
26
27 pub(crate) fn verify_with_progress(
30 &self,
31 output_files: &[OutputFileChecksum],
32 mut progress: Option<&mut ArchiveVerificationProgress<'_>>,
33 ) -> Result<bool> {
34 if output_files.is_empty() {
35 return Ok(false);
36 }
37
38 for expected in output_files {
39 let output_path = self.output_path(&expected.path);
40 let meta = match fs::metadata(&output_path) {
41 Ok(meta) => meta,
42 Err(_) => return Ok(false),
43 };
44 if meta.len() != expected.size {
45 return Ok(false);
46 }
47
48 let actual = Self::file_blake3_hex(&output_path, progress.as_deref_mut())?;
49 if !actual.eq_ignore_ascii_case(&expected.blake3) {
50 return Ok(false);
51 }
52 }
53
54 Ok(true)
55 }
56
57 pub(crate) fn cleanup(&self, output_files: &[OutputFileChecksum]) {
59 for output in output_files {
60 let _ = fs::remove_file(self.output_path(&output.path));
61 }
62 }
63
64 fn output_path(&self, path: &str) -> std::path::PathBuf {
66 if let Some(static_files_dir) = self.static_files_dir &&
67 let Some(relative_path) = super::extract::static_file_relative_path(Path::new(path))
68 {
69 return static_files_dir.join(relative_path)
70 }
71 self.target_dir.join(path)
72 }
73
74 fn file_blake3_hex(
76 path: &Path,
77 mut progress: Option<&mut ArchiveVerificationProgress<'_>>,
78 ) -> Result<String> {
79 let mut file = fs::open(path)?;
80 let mut hasher = Hasher::new();
81 let mut buf = [0_u8; 64 * 1024];
82
83 loop {
84 let n = file.read(&mut buf)?;
85 if n == 0 {
86 break;
87 }
88 hasher.update(&buf[..n]);
89 if let Some(progress) = progress.as_deref_mut() {
90 progress.record_verified(n as u64);
91 }
92 }
93
94 Ok(hasher.finalize().to_hex().to_string())
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn custom_static_files_verification_and_cleanup() {
104 let datadir = tempfile::tempdir().unwrap();
105 let static_dir = tempfile::tempdir().unwrap();
106 let default_file = datadir.path().join("static_files/headers");
107 fs::create_dir_all(default_file.parent().unwrap()).unwrap();
108 fs::write(&default_file, b"headers").unwrap();
109 let outputs = [OutputFileChecksum {
110 path: "./static_files/headers".into(),
111 size: 7,
112 blake3: blake3::hash(b"headers").to_hex().to_string(),
113 }];
114 let verifier = OutputVerifier::new(datadir.path(), Some(static_dir.path()));
115 assert!(!verifier.verify(&outputs).unwrap());
116 let custom_file = static_dir.path().join("headers");
117 fs::write(&custom_file, b"headers").unwrap();
118 assert!(verifier.verify(&outputs).unwrap());
119 verifier.cleanup(&outputs);
120 assert!(!custom_file.exists());
121 assert_eq!(fs::read(default_file).unwrap(), b"headers");
122 }
123}