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