mithril_cardano_node_internal_database/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 mithril_common::{
13 crypto_helper::{MKTree, MKTreeStoreInMemory},
14 entities::{CardanoDbBeacon, HexEncodedDigest, ImmutableFileName, ImmutableFileNumber},
15 StdError,
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("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(crate) 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 use super::*;
195
196 #[test]
197 fn reports_progress_every_5_percent() {
198 let mut progress = Progress {
199 index: 0,
200 total: 7000,
201 };
202
203 assert!(!progress.report(1));
204 assert!(!progress.report(4));
205 assert!(progress.report(350));
206 assert!(!progress.report(351));
207 }
208
209 #[test]
210 fn reports_progress_when_total_lower_than_20() {
211 let mut progress = Progress {
212 index: 0,
213 total: 16,
214 };
215
216 assert!(progress.report(4));
217 assert!(progress.report(12));
218 assert!(!progress.report(3));
219 assert!(!progress.report(15));
220 }
221}