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