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]
22pub trait ImmutableDigester: Sync + Send {
23 async fn compute_digest(
25 &self,
26 dirpath: &Path,
27 beacon: &CardanoDbBeacon,
28 ) -> Result<String, ImmutableDigesterError>;
29
30 async fn compute_digests_for_range(
32 &self,
33 dirpath: &Path,
34 range: &RangeInclusive<ImmutableFileNumber>,
35 ) -> Result<ComputedImmutablesDigests, ImmutableDigesterError>;
36
37 async fn compute_merkle_tree(
39 &self,
40 dirpath: &Path,
41 beacon: &CardanoDbBeacon,
42 ) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError>;
43}
44
45#[derive(Error, Debug)]
47pub enum ImmutableDigesterError {
48 #[error("Immutable files listing failed")]
50 ListImmutablesError(#[from] ImmutableFileListingError),
51
52 #[error(
55 "At least two immutable chunks should exist in directory '{db_dir}': expected {expected_number} but found {found_number:?}."
56 )]
57 NotEnoughImmutable {
58 expected_number: ImmutableFileNumber,
60 found_number: Option<ImmutableFileNumber>,
62 db_dir: PathBuf,
64 },
65
66 #[error("Digest computation failed")]
68 DigestComputationError(#[from] io::Error),
69
70 #[error("Merkle tree computation failed")]
72 MerkleTreeComputationError(StdError),
73}
74
75pub struct ComputedImmutablesDigests {
77 pub entries: BTreeMap<ImmutableFile, HexEncodedDigest>,
79 pub(super) new_cached_entries: Vec<ImmutableFileName>,
80}
81
82impl ComputedImmutablesDigests {
83 pub(crate) fn compute_immutables_digests(
84 entries: BTreeMap<ImmutableFile, Option<HexEncodedDigest>>,
85 logger: Logger,
86 ) -> Result<ComputedImmutablesDigests, io::Error> {
87 let mut new_cached_entries = Vec::new();
88 let mut progress = Progress {
89 index: 0,
90 total: entries.len(),
91 };
92
93 let mut digests = BTreeMap::new();
94
95 for (ix, (entry, cache)) in entries.into_iter().enumerate() {
96 let hash = match cache {
97 None => {
98 new_cached_entries.push(entry.filename.clone());
99 hex::encode(entry.compute_raw_hash::<Sha256>()?)
100 }
101 Some(digest) => digest,
102 };
103 digests.insert(entry, hash);
104
105 if progress.report(ix) {
106 info!(logger, "Hashing: {progress}");
107 }
108 }
109
110 Ok(ComputedImmutablesDigests {
111 entries: digests,
112 new_cached_entries,
113 })
114 }
115}
116
117pub(super) struct Progress {
118 pub(super) index: usize,
119 pub(super) total: usize,
120}
121
122impl Progress {
123 pub(super) fn report(&mut self, ix: usize) -> bool {
124 self.index = ix;
125 (20 * ix).is_multiple_of(self.total)
126 }
127
128 pub(super) fn percent(&self) -> f64 {
129 (self.index as f64 * 100.0 / self.total as f64).ceil()
130 }
131}
132
133impl std::fmt::Display for Progress {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
135 write!(f, "{}/{} ({}%)", self.index, self.total, self.percent())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn reports_progress_every_5_percent() {
145 let mut progress = Progress {
146 index: 0,
147 total: 7000,
148 };
149
150 assert!(!progress.report(1));
151 assert!(!progress.report(4));
152 assert!(progress.report(350));
153 assert!(!progress.report(351));
154 }
155
156 #[test]
157 fn reports_progress_when_total_lower_than_20() {
158 let mut progress = Progress {
159 index: 0,
160 total: 16,
161 };
162
163 assert!(progress.report(4));
164 assert!(progress.report(12));
165 assert!(!progress.report(3));
166 assert!(!progress.report(15));
167 }
168}