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