Skip to main content

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