mithril_client/cardano_database_client/
api.rs

1#[cfg(feature = "fs")]
2use std::path::Path;
3use std::sync::Arc;
4
5#[cfg(feature = "fs")]
6use slog::Logger;
7
8#[cfg(feature = "fs")]
9use mithril_common::{
10    crypto_helper::MKProof,
11    messages::{CardanoDatabaseSnapshotMessage, CertificateMessage},
12};
13
14#[cfg(feature = "fs")]
15use mithril_cardano_node_internal_database::entities::ImmutableFile;
16
17use crate::aggregator_client::AggregatorClient;
18#[cfg(feature = "fs")]
19use crate::cardano_database_client::{VerifiedDigests, proving::CardanoDatabaseVerificationError};
20use crate::common::Epoch;
21#[cfg(feature = "fs")]
22use crate::feedback::FeedbackSender;
23#[cfg(feature = "fs")]
24use crate::file_downloader::FileDownloader;
25#[cfg(feature = "fs")]
26use crate::utils::{AncillaryVerifier, TempDirectoryProvider};
27use crate::{CardanoDatabaseSnapshot, CardanoDatabaseSnapshotListItem, MithrilResult};
28
29use super::fetch::InternalArtifactRetriever;
30use super::statistics::InternalStatisticsSender;
31#[cfg(feature = "fs")]
32use super::{
33    DownloadUnpackOptions, ImmutableFileRange, download_unpack::InternalArtifactDownloader,
34    proving::InternalArtifactProver,
35};
36
37/// HTTP client for CardanoDatabase API from the Aggregator
38pub struct CardanoDatabaseClient {
39    pub(super) artifact_retriever: InternalArtifactRetriever,
40    #[cfg(feature = "fs")]
41    pub(super) artifact_downloader: InternalArtifactDownloader,
42    #[cfg(feature = "fs")]
43    pub(super) artifact_prover: InternalArtifactProver,
44    pub(super) statistics_sender: InternalStatisticsSender,
45}
46
47impl CardanoDatabaseClient {
48    /// Constructs a new `CardanoDatabase`.
49    pub fn new(
50        aggregator_client: Arc<dyn AggregatorClient>,
51        #[cfg(feature = "fs")] http_file_downloader: Arc<dyn FileDownloader>,
52        #[cfg(feature = "fs")] ancillary_verifier: Option<Arc<AncillaryVerifier>>,
53        #[cfg(feature = "fs")] feedback_sender: FeedbackSender,
54        #[cfg(feature = "fs")] temp_directory_provider: Arc<dyn TempDirectoryProvider>,
55        #[cfg(feature = "fs")] logger: Logger,
56    ) -> Self {
57        #[cfg(feature = "fs")]
58        let logger =
59            mithril_common::logging::LoggerExtensions::new_with_component_name::<Self>(&logger);
60        Self {
61            artifact_retriever: InternalArtifactRetriever::new(aggregator_client.clone()),
62            #[cfg(feature = "fs")]
63            artifact_downloader: InternalArtifactDownloader::new(
64                http_file_downloader.clone(),
65                ancillary_verifier,
66                feedback_sender.clone(),
67                logger.clone(),
68            ),
69            #[cfg(feature = "fs")]
70            artifact_prover: InternalArtifactProver::new(
71                http_file_downloader.clone(),
72                temp_directory_provider.clone(),
73                logger.clone(),
74            ),
75            statistics_sender: InternalStatisticsSender::new(aggregator_client.clone()),
76        }
77    }
78
79    /// Fetch a list of signed CardanoDatabase
80    pub async fn list(&self) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
81        self.artifact_retriever.list().await
82    }
83
84    /// Fetch a list of signed CardanoDatabase for a given epoch
85    pub async fn list_by_epoch(
86        &self,
87        epoch: Epoch,
88    ) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
89        self.artifact_retriever.list_by_epoch(epoch).await
90    }
91
92    /// Fetch a list of signed CardanoDatabase for the latest epoch
93    pub async fn list_for_latest_epoch(
94        &self,
95    ) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
96        self.artifact_retriever.list_for_latest_epoch().await
97    }
98
99    /// Fetch a list of signed CardanoDatabase for the latest epoch minus the given offset
100    pub async fn list_for_latest_epoch_with_offset(
101        &self,
102        offset: u64,
103    ) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
104        self.artifact_retriever
105            .list_for_latest_epoch_with_offset(offset)
106            .await
107    }
108
109    /// Get the given Cardano database data by hash
110    pub async fn get(&self, hash: &str) -> MithrilResult<Option<CardanoDatabaseSnapshot>> {
111        self.artifact_retriever.get(hash).await
112    }
113
114    /// Download and unpack the given Cardano database parts data by hash.
115    #[cfg(feature = "fs")]
116    pub async fn download_unpack(
117        &self,
118        cardano_database_snapshot: &CardanoDatabaseSnapshotMessage,
119        immutable_file_range: &ImmutableFileRange,
120        target_dir: &Path,
121        download_unpack_options: DownloadUnpackOptions,
122    ) -> MithrilResult<()> {
123        self.artifact_downloader
124            .download_unpack(
125                cardano_database_snapshot,
126                immutable_file_range,
127                target_dir,
128                download_unpack_options,
129            )
130            .await
131    }
132
133    /// Download and verify the digests against the certificate.
134    #[cfg(feature = "fs")]
135    pub async fn download_and_verify_digests(
136        &self,
137        certificate: &CertificateMessage,
138        cardano_database_snapshot: &CardanoDatabaseSnapshotMessage,
139    ) -> MithrilResult<VerifiedDigests> {
140        self.artifact_prover
141            .download_and_verify_digests(certificate, cardano_database_snapshot)
142            .await
143    }
144
145    /// Verify a local cardano database
146    #[cfg(feature = "fs")]
147    pub async fn verify_cardano_database(
148        &self,
149        certificate: &CertificateMessage,
150        cardano_database_snapshot: &CardanoDatabaseSnapshotMessage,
151        immutable_file_range: &ImmutableFileRange,
152        allow_missing: bool,
153        database_dir: &Path,
154        verified_digests: &VerifiedDigests,
155    ) -> Result<MKProof, CardanoDatabaseVerificationError> {
156        self.artifact_prover
157            .verify_cardano_database(
158                certificate,
159                cardano_database_snapshot,
160                immutable_file_range,
161                allow_missing,
162                database_dir,
163                verified_digests,
164            )
165            .await
166    }
167
168    /// Checks if immutable directory exists with at least one immutable in it
169    #[cfg(feature = "fs")]
170    pub fn check_has_immutables(&self, database_dir: &Path) -> MithrilResult<()> {
171        ImmutableFile::at_least_one_immutable_files_exist_in_dir(database_dir)?;
172        Ok(())
173    }
174
175    /// Increments the aggregator Cardano database snapshot download statistics
176    pub async fn add_statistics(
177        &self,
178        full_restoration: bool,
179        include_ancillary: bool,
180        number_of_immutable_files_restored: u64,
181    ) -> MithrilResult<()> {
182        self.statistics_sender
183            .add_statistics(
184                full_restoration,
185                include_ancillary,
186                number_of_immutable_files_restored,
187            )
188            .await
189    }
190}
191
192#[cfg(test)]
193pub(crate) mod test_dependency_injector {
194    use super::*;
195
196    #[cfg(feature = "fs")]
197    use mithril_common::crypto_helper::ManifestVerifierVerificationKey;
198
199    use crate::aggregator_client::MockAggregatorClient;
200    #[cfg(feature = "fs")]
201    use crate::file_downloader::{FileDownloader, MockFileDownloaderBuilder};
202    #[cfg(feature = "fs")]
203    use crate::utils::TimestampTempDirectoryProvider;
204    #[cfg(feature = "fs")]
205    use crate::{feedback::FeedbackReceiver, test_utils::TestLogger};
206
207    /// Dependency injector for `CardanoDatabaseClient` for testing purposes.
208    pub(crate) struct CardanoDatabaseClientDependencyInjector {
209        aggregator_client: MockAggregatorClient,
210        #[cfg(feature = "fs")]
211        http_file_downloader: Arc<dyn FileDownloader>,
212        #[cfg(feature = "fs")]
213        ancillary_verifier: Option<Arc<AncillaryVerifier>>,
214        #[cfg(feature = "fs")]
215        feedback_receivers: Vec<Arc<dyn FeedbackReceiver>>,
216        #[cfg(feature = "fs")]
217        temp_directory_provider: Arc<dyn TempDirectoryProvider>,
218        #[cfg(feature = "fs")]
219        logger: Logger,
220    }
221
222    impl CardanoDatabaseClientDependencyInjector {
223        pub(crate) fn new() -> Self {
224            Self {
225                aggregator_client: MockAggregatorClient::new(),
226                #[cfg(feature = "fs")]
227                http_file_downloader: Arc::new(
228                    MockFileDownloaderBuilder::default()
229                        .with_compression(None)
230                        .with_success()
231                        .with_times(0)
232                        .build(),
233                ),
234                #[cfg(feature = "fs")]
235                ancillary_verifier: None,
236                #[cfg(feature = "fs")]
237                feedback_receivers: vec![],
238                #[cfg(feature = "fs")]
239                temp_directory_provider: Arc::new(TimestampTempDirectoryProvider::new(
240                    "cardano_database_client_test",
241                )),
242                #[cfg(feature = "fs")]
243                logger: TestLogger::stdout(),
244            }
245        }
246
247        #[cfg(feature = "fs")]
248        pub(crate) fn with_logger(self, logger: Logger) -> Self {
249            #[cfg(feature = "fs")]
250            Self { logger, ..self }
251        }
252
253        pub(crate) fn with_aggregator_client_mock_config<F>(mut self, config: F) -> Self
254        where
255            F: FnOnce(&mut MockAggregatorClient),
256        {
257            config(&mut self.aggregator_client);
258
259            self
260        }
261
262        #[cfg(feature = "fs")]
263        pub(crate) fn with_http_file_downloader(
264            self,
265            http_file_downloader: Arc<dyn FileDownloader>,
266        ) -> Self {
267            Self {
268                http_file_downloader,
269                ..self
270            }
271        }
272
273        #[cfg(feature = "fs")]
274        pub(crate) fn with_ancillary_verifier<T>(self, ancillary_verification_key: T) -> Self
275        where
276            T: TryInto<ManifestVerifierVerificationKey>,
277            T::Error: std::fmt::Debug,
278        {
279            Self {
280                ancillary_verifier: Some(Arc::new(AncillaryVerifier::new(
281                    ancillary_verification_key.try_into().unwrap(),
282                ))),
283                ..self
284            }
285        }
286
287        #[cfg(feature = "fs")]
288        pub(crate) fn with_feedback_receivers(
289            self,
290            feedback_receivers: &[Arc<dyn FeedbackReceiver>],
291        ) -> Self {
292            Self {
293                feedback_receivers: feedback_receivers.to_vec(),
294                ..self
295            }
296        }
297
298        #[cfg(feature = "fs")]
299        pub(crate) fn with_temp_directory_provider(
300            self,
301            temp_directory_provider: Arc<dyn TempDirectoryProvider>,
302        ) -> Self {
303            Self {
304                temp_directory_provider,
305                ..self
306            }
307        }
308
309        #[cfg(feature = "fs")]
310        pub(crate) fn build_cardano_database_client(self) -> CardanoDatabaseClient {
311            CardanoDatabaseClient::new(
312                Arc::new(self.aggregator_client),
313                self.http_file_downloader,
314                self.ancillary_verifier,
315                FeedbackSender::new(&self.feedback_receivers),
316                self.temp_directory_provider,
317                self.logger,
318            )
319        }
320
321        #[cfg(not(feature = "fs"))]
322        pub(crate) fn build_cardano_database_client(self) -> CardanoDatabaseClient {
323            CardanoDatabaseClient::new(Arc::new(self.aggregator_client))
324        }
325    }
326
327    mod tests {
328        use mithril_common::test::double::Dummy;
329        use mockall::predicate;
330
331        use crate::aggregator_client::AggregatorRequest;
332        #[cfg(feature = "fs")]
333        use crate::feedback::StackFeedbackReceiver;
334
335        use super::*;
336
337        #[cfg(feature = "fs")]
338        #[test]
339        fn test_cardano_database_client_dependency_injector_builds() {
340            let _ = CardanoDatabaseClientDependencyInjector::new()
341                .with_aggregator_client_mock_config(|http_client| {
342                    let message = vec![CardanoDatabaseSnapshotListItem {
343                        hash: "hash-123".to_string(),
344                        ..CardanoDatabaseSnapshotListItem::dummy()
345                    }];
346                    http_client
347                        .expect_get_content()
348                        .with(predicate::eq(
349                            AggregatorRequest::ListCardanoDatabaseSnapshots,
350                        ))
351                        .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
352                })
353                .with_http_file_downloader(Arc::new(
354                    MockFileDownloaderBuilder::default()
355                        .with_success()
356                        .with_times(0)
357                        .build(),
358                ))
359                .with_feedback_receivers(&[Arc::new(StackFeedbackReceiver::new())])
360                .build_cardano_database_client();
361        }
362
363        #[cfg(not(feature = "fs"))]
364        #[test]
365        fn test_cardano_database_client_dependency_injector_builds() {
366            let _ = CardanoDatabaseClientDependencyInjector::new()
367                .with_aggregator_client_mock_config(|http_client| {
368                    let message = vec![CardanoDatabaseSnapshotListItem {
369                        hash: "hash-123".to_string(),
370                        ..CardanoDatabaseSnapshotListItem::dummy()
371                    }];
372                    http_client
373                        .expect_get_content()
374                        .with(predicate::eq(
375                            AggregatorRequest::ListCardanoDatabaseSnapshots,
376                        ))
377                        .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
378                })
379                .build_cardano_database_client();
380        }
381    }
382}