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