mithril_common/digesters/
immutable_digester.rs1use async_trait::async_trait;
2use sha2::Sha256;
3use slog::{info, Logger};
4use std::{
5 collections::BTreeMap,
6 io,
7 ops::RangeInclusive,
8 path::{Path, PathBuf},
9};
10use thiserror::Error;
11
12use crate::{
13 crypto_helper::{MKTree, MKTreeStoreInMemory},
14 digesters::ImmutableFileListingError,
15 entities::{CardanoDbBeacon, HexEncodedDigest, ImmutableFileName, ImmutableFileNumber},
16 StdError,
17};
18
19use super::ImmutableFile;
20
21#[async_trait]
77pub trait ImmutableDigester: Sync + Send {
78 async fn compute_digest(
80 &self,
81 dirpath: &Path,
82 beacon: &CardanoDbBeacon,
83 ) -> Result<String, ImmutableDigesterError>;
84
85 async fn compute_digests_for_range(
87 &self,
88 dirpath: &Path,
89 range: &RangeInclusive<ImmutableFileNumber>,
90 ) -> Result<ComputedImmutablesDigests, ImmutableDigesterError>;
91
92 async fn compute_merkle_tree(
94 &self,
95 dirpath: &Path,
96 beacon: &CardanoDbBeacon,
97 ) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError>;
98}
99
100#[derive(Error, Debug)]
102pub enum ImmutableDigesterError {
103 #[error("Immutable files listing failed")]
105 ListImmutablesError(#[from] ImmutableFileListingError),
106
107 #[error("At least two immutable chunks should exist in directory '{db_dir}': expected {expected_number} but found {found_number:?}.")]
110 NotEnoughImmutable {
111 expected_number: ImmutableFileNumber,
113 found_number: Option<ImmutableFileNumber>,
115 db_dir: PathBuf,
117 },
118
119 #[error("Digest computation failed")]
121 DigestComputationError(#[from] io::Error),
122
123 #[error("Merkle tree computation failed")]
125 MerkleTreeComputationError(StdError),
126}
127
128pub struct ComputedImmutablesDigests {
130 pub entries: BTreeMap<ImmutableFile, HexEncodedDigest>,
132 pub(super) new_cached_entries: Vec<ImmutableFileName>,
133}
134
135impl ComputedImmutablesDigests {
136 pub(super) fn compute_immutables_digests(
137 entries: BTreeMap<ImmutableFile, Option<HexEncodedDigest>>,
138 logger: Logger,
139 ) -> Result<ComputedImmutablesDigests, io::Error> {
140 let mut new_cached_entries = Vec::new();
141 let mut progress = Progress {
142 index: 0,
143 total: entries.len(),
144 };
145
146 let mut digests = BTreeMap::new();
147
148 for (ix, (entry, cache)) in entries.into_iter().enumerate() {
149 let hash = match cache {
150 None => {
151 new_cached_entries.push(entry.filename.clone());
152 hex::encode(entry.compute_raw_hash::<Sha256>()?)
153 }
154 Some(digest) => digest,
155 };
156 digests.insert(entry, hash);
157
158 if progress.report(ix) {
159 info!(logger, "Hashing: {progress}");
160 }
161 }
162
163 Ok(ComputedImmutablesDigests {
164 entries: digests,
165 new_cached_entries,
166 })
167 }
168}
169
170pub(super) struct Progress {
171 pub(super) index: usize,
172 pub(super) total: usize,
173}
174
175impl Progress {
176 pub(super) fn report(&mut self, ix: usize) -> bool {
177 self.index = ix;
178 (20 * ix) % self.total == 0
179 }
180
181 pub(super) fn percent(&self) -> f64 {
182 (self.index as f64 * 100.0 / self.total as f64).ceil()
183 }
184}
185
186impl std::fmt::Display for Progress {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
188 write!(f, "{}/{} ({}%)", self.index, self.total, self.percent())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194
195 use super::*;
196
197 #[test]
198 fn reports_progress_every_5_percent() {
199 let mut progress = Progress {
200 index: 0,
201 total: 7000,
202 };
203
204 assert!(!progress.report(1));
205 assert!(!progress.report(4));
206 assert!(progress.report(350));
207 assert!(!progress.report(351));
208 }
209
210 #[test]
211 fn reports_progress_when_total_lower_than_20() {
212 let mut progress = Progress {
213 index: 0,
214 total: 16,
215 };
216
217 assert!(progress.report(4));
218 assert!(progress.report(12));
219 assert!(!progress.report(3));
220 assert!(!progress.report(15));
221 }
222}