mithril_cardano_node_internal_database/digesters/
immutable_digester.rs

1use 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/// A digester that can compute the digest used for mithril signatures
21#[async_trait]
22pub trait ImmutableDigester: Sync + Send {
23    /// Compute the digest
24    async fn compute_digest(
25        &self,
26        dirpath: &Path,
27        beacon: &CardanoDbBeacon,
28    ) -> Result<String, ImmutableDigesterError>;
29
30    /// Compute the digests for a range of immutable files
31    async fn compute_digests_for_range(
32        &self,
33        dirpath: &Path,
34        range: &RangeInclusive<ImmutableFileNumber>,
35    ) -> Result<ComputedImmutablesDigests, ImmutableDigesterError>;
36
37    /// Compute the digests merkle tree
38    async fn compute_merkle_tree(
39        &self,
40        dirpath: &Path,
41        beacon: &CardanoDbBeacon,
42    ) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError>;
43}
44
45/// [ImmutableDigester] related Errors.
46#[derive(Error, Debug)]
47pub enum ImmutableDigesterError {
48    /// Error raised when the files listing failed.
49    #[error("Immutable files listing failed")]
50    ListImmutablesError(#[from] ImmutableFileListingError),
51
52    /// Error raised when there's less than the required number of completed immutables in
53    /// the cardano database or even no immutable at all.
54    #[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 last [ImmutableFileNumber].
59        expected_number: ImmutableFileNumber,
60        /// Last [ImmutableFileNumber] found when listing [ImmutableFiles][crate::entities::ImmutableFile].
61        found_number: Option<ImmutableFileNumber>,
62        /// A cardano node DB directory
63        db_dir: PathBuf,
64    },
65
66    /// Error raised when the digest computation failed.
67    #[error("Digest computation failed")]
68    DigestComputationError(#[from] io::Error),
69
70    /// Error raised when the Merkle tree computation failed.
71    #[error("Merkle tree computation failed")]
72    MerkleTreeComputationError(StdError),
73}
74
75/// Computed immutables digests
76pub struct ComputedImmutablesDigests {
77    /// A map of [ImmutableFile] to their respective digest.
78    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}