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