Skip to content

Commit dee0d33

Browse files
committed
feat(client-lib): list cardano db snapshot by epoch/latest epoch (with/without offset)
1 parent 29375b2 commit dee0d33

File tree

4 files changed

+261
-15
lines changed

4 files changed

+261
-15
lines changed

mithril-client/src/aggregator_client.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ pub enum AggregatorRequest {
9191
/// Lists the aggregator [Cardano database snapshots][crate::CardanoDatabaseSnapshot]
9292
ListCardanoDatabaseSnapshots,
9393

94+
/// Lists the aggregator [Cardano database snapshots][crate::CardanoDatabaseSnapshot] for an epoch
95+
ListCardanoDatabaseSnapshotByEpoch {
96+
/// Epoch of the Cardano Database snapshots
97+
epoch: Epoch,
98+
},
99+
100+
/// Lists the aggregator [Cardano database snapshots][crate::CardanoDatabaseSnapshot] for the latest epoch
101+
ListCardanoDatabaseSnapshotForLatestEpoch {
102+
/// Optional offset to subtract to the latest epoch
103+
offset: Option<u64>,
104+
},
105+
94106
/// Increments the aggregator Cardano database snapshot immutable files restored statistics
95107
IncrementCardanoDatabaseImmutablesRestoredStatistic {
96108
/// Number of immutable files restored
@@ -167,6 +179,15 @@ impl AggregatorRequest {
167179
AggregatorRequest::ListCardanoDatabaseSnapshots => {
168180
"artifact/cardano-database".to_string()
169181
}
182+
AggregatorRequest::ListCardanoDatabaseSnapshotByEpoch { epoch } => {
183+
format!("artifact/cardano-database/epoch/{epoch}")
184+
}
185+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch { offset } => {
186+
format!(
187+
"artifact/cardano-database/epoch/latest{}",
188+
offset.map(|o| format!("-{o}")).unwrap_or_default()
189+
)
190+
}
170191
AggregatorRequest::IncrementCardanoDatabaseImmutablesRestoredStatistic {
171192
number_of_immutables: _,
172193
} => "statistics/cardano-database/immutable-files-restored".to_string(),
@@ -606,6 +627,22 @@ mod tests {
606627
AggregatorRequest::ListCardanoDatabaseSnapshots.route()
607628
);
608629

630+
assert_eq!(
631+
"artifact/cardano-database/epoch/5".to_string(),
632+
AggregatorRequest::ListCardanoDatabaseSnapshotByEpoch { epoch: Epoch(5) }.route()
633+
);
634+
635+
assert_eq!(
636+
"artifact/cardano-database/epoch/latest".to_string(),
637+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch { offset: None }.route()
638+
);
639+
640+
assert_eq!(
641+
"artifact/cardano-database/epoch/latest-6".to_string(),
642+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch { offset: Some(6) }
643+
.route()
644+
);
645+
609646
assert_eq!(
610647
"statistics/cardano-database/immutable-files-restored".to_string(),
611648
AggregatorRequest::IncrementCardanoDatabaseImmutablesRestoredStatistic {

mithril-client/src/cardano_database_client/api.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use mithril_cardano_node_internal_database::entities::ImmutableFile;
1717
use crate::aggregator_client::AggregatorClient;
1818
#[cfg(feature = "fs")]
1919
use crate::cardano_database_client::{VerifiedDigests, proving::CardanoDatabaseVerificationError};
20+
use crate::common::Epoch;
2021
#[cfg(feature = "fs")]
2122
use crate::feedback::FeedbackSender;
2223
#[cfg(feature = "fs")]
@@ -80,6 +81,31 @@ impl CardanoDatabaseClient {
8081
self.artifact_retriever.list().await
8182
}
8283

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+
83109
/// Get the given Cardano database data by hash
84110
pub async fn get(&self, hash: &str) -> MithrilResult<Option<CardanoDatabaseSnapshot>> {
85111
self.artifact_retriever.get(hash).await

mithril-client/src/cardano_database_client/fetch.rs

Lines changed: 174 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::de::DeserializeOwned;
66
use crate::{
77
CardanoDatabaseSnapshot, CardanoDatabaseSnapshotListItem, MithrilResult,
88
aggregator_client::{AggregatorClient, AggregatorClientError, AggregatorRequest},
9+
common::Epoch,
910
};
1011

1112
pub struct InternalArtifactRetriever {
@@ -20,15 +21,42 @@ impl InternalArtifactRetriever {
2021

2122
/// Fetch a list of signed CardanoDatabase
2223
pub async fn list(&self) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
23-
let response = self
24-
.aggregator_client
25-
.get_content(AggregatorRequest::ListCardanoDatabaseSnapshots)
24+
self.fetch_list_with_aggregator_request(AggregatorRequest::ListCardanoDatabaseSnapshots)
2625
.await
27-
.with_context(|| "CardanoDatabase client can not get the artifact list")?;
28-
let items = serde_json::from_str::<Vec<CardanoDatabaseSnapshotListItem>>(&response)
29-
.with_context(|| "CardanoDatabase client can not deserialize artifact list")?;
26+
}
27+
28+
/// Fetch a list of signed CardanoDatabase for a given epoch
29+
pub async fn list_by_epoch(
30+
&self,
31+
epoch: Epoch,
32+
) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
33+
self.fetch_list_with_aggregator_request(
34+
AggregatorRequest::ListCardanoDatabaseSnapshotByEpoch { epoch },
35+
)
36+
.await
37+
}
3038

31-
Ok(items)
39+
/// Fetch a list of signed CardanoDatabase for the latest epoch
40+
pub async fn list_for_latest_epoch(
41+
&self,
42+
) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
43+
self.fetch_list_with_aggregator_request(
44+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch { offset: None },
45+
)
46+
.await
47+
}
48+
49+
/// Fetch a list of signed CardanoDatabase for the latest epoch minus the given offset
50+
pub async fn list_for_latest_epoch_with_offset(
51+
&self,
52+
offset: u64,
53+
) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
54+
self.fetch_list_with_aggregator_request(
55+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch {
56+
offset: Some(offset),
57+
},
58+
)
59+
.await
3260
}
3361

3462
/// Get the given Cardano database data by hash.
@@ -40,7 +68,7 @@ impl InternalArtifactRetriever {
4068
}
4169

4270
/// Fetch the given Cardano database data with an aggregator request.
43-
/// If it cannot be found, a None is returned.
71+
/// If it cannot be found, None is returned.
4472
async fn fetch_with_aggregator_request<T: DeserializeOwned>(
4573
&self,
4674
request: AggregatorRequest,
@@ -56,6 +84,20 @@ impl InternalArtifactRetriever {
5684
Err(e) => Err(e.into()),
5785
}
5886
}
87+
88+
async fn fetch_list_with_aggregator_request(
89+
&self,
90+
request: AggregatorRequest,
91+
) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
92+
let response = self
93+
.aggregator_client
94+
.get_content(request)
95+
.await
96+
.with_context(|| "CardanoDatabase client can not get the artifact list")?;
97+
98+
serde_json::from_str(&response)
99+
.with_context(|| "CardanoDatabase client can not deserialize artifact list")
100+
}
59101
}
60102

61103
#[cfg(test)]
@@ -68,10 +110,19 @@ mod tests {
68110
use mithril_common::entities::{CardanoDbBeacon, Epoch};
69111
use mithril_common::test::double::Dummy;
70112

113+
use crate::aggregator_client;
71114
use crate::cardano_database_client::CardanoDatabaseClientDependencyInjector;
72115

73116
use super::*;
74117

118+
fn config_aggregator_client_to_always_returns_invalid_json(
119+
http_client: &mut aggregator_client::MockAggregatorClient,
120+
) {
121+
http_client
122+
.expect_get_content()
123+
.returning(move |_| Ok("invalid json structure".to_string()));
124+
}
125+
75126
fn fake_messages() -> Vec<CardanoDatabaseSnapshotListItem> {
76127
vec![
77128
CardanoDatabaseSnapshotListItem {
@@ -131,16 +182,126 @@ mod tests {
131182
#[tokio::test]
132183
async fn list_cardano_database_snapshots_returns_error_when_invalid_json_structure_in_response()
133184
{
185+
let client = CardanoDatabaseClientDependencyInjector::new()
186+
.with_aggregator_client_mock_config(
187+
config_aggregator_client_to_always_returns_invalid_json,
188+
)
189+
.build_cardano_database_client();
190+
191+
client
192+
.list()
193+
.await
194+
.expect_err("List Cardano databases should return an error");
195+
}
196+
197+
#[tokio::test]
198+
async fn list_cardano_database_snapshots_by_epoch_returns_messages() {
199+
let message = fake_messages();
134200
let client = CardanoDatabaseClientDependencyInjector::new()
135201
.with_aggregator_client_mock_config(|http_client| {
136202
http_client
137203
.expect_get_content()
138-
.return_once(move |_| Ok("invalid json structure".to_string()));
204+
.with(eq(AggregatorRequest::ListCardanoDatabaseSnapshotByEpoch {
205+
epoch: Epoch(4),
206+
}))
207+
.return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
139208
})
140209
.build_cardano_database_client();
141210

211+
let messages = client.list_by_epoch(Epoch(4)).await.unwrap();
212+
213+
assert_eq!(2, messages.len());
214+
assert_eq!("hash-123".to_string(), messages[0].hash);
215+
assert_eq!("hash-456".to_string(), messages[1].hash);
216+
}
217+
218+
#[tokio::test]
219+
async fn list_cardano_database_snapshots_returns_by_epoch_error_when_invalid_json_structure_in_response()
220+
{
221+
let client = CardanoDatabaseClientDependencyInjector::new()
222+
.with_aggregator_client_mock_config(
223+
config_aggregator_client_to_always_returns_invalid_json,
224+
)
225+
.build_cardano_database_client();
226+
142227
client
143-
.list()
228+
.list_by_epoch(Epoch(4))
229+
.await
230+
.expect_err("List Cardano databases should return an error");
231+
}
232+
233+
#[tokio::test]
234+
async fn list_cardano_database_snapshots_for_latest_epoch_returns_messages() {
235+
let message = fake_messages();
236+
let client = CardanoDatabaseClientDependencyInjector::new()
237+
.with_aggregator_client_mock_config(|http_client| {
238+
http_client
239+
.expect_get_content()
240+
.with(eq(
241+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch {
242+
offset: None,
243+
},
244+
))
245+
.return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
246+
})
247+
.build_cardano_database_client();
248+
249+
let messages = client.list_for_latest_epoch().await.unwrap();
250+
251+
assert_eq!(2, messages.len());
252+
assert_eq!("hash-123".to_string(), messages[0].hash);
253+
assert_eq!("hash-456".to_string(), messages[1].hash);
254+
}
255+
256+
#[tokio::test]
257+
async fn list_cardano_database_snapshots_returns_for_latest_epoch_error_when_invalid_json_structure_in_response()
258+
{
259+
let client = CardanoDatabaseClientDependencyInjector::new()
260+
.with_aggregator_client_mock_config(
261+
config_aggregator_client_to_always_returns_invalid_json,
262+
)
263+
.build_cardano_database_client();
264+
265+
client
266+
.list_for_latest_epoch()
267+
.await
268+
.expect_err("List Cardano databases should return an error");
269+
}
270+
271+
#[tokio::test]
272+
async fn list_cardano_database_snapshots_for_latest_epoch_with_offset_returns_messages() {
273+
let message = fake_messages();
274+
let client = CardanoDatabaseClientDependencyInjector::new()
275+
.with_aggregator_client_mock_config(|http_client| {
276+
http_client
277+
.expect_get_content()
278+
.with(eq(
279+
AggregatorRequest::ListCardanoDatabaseSnapshotForLatestEpoch {
280+
offset: Some(42),
281+
},
282+
))
283+
.return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
284+
})
285+
.build_cardano_database_client();
286+
287+
let messages = client.list_for_latest_epoch_with_offset(42).await.unwrap();
288+
289+
assert_eq!(2, messages.len());
290+
assert_eq!("hash-123".to_string(), messages[0].hash);
291+
assert_eq!("hash-456".to_string(), messages[1].hash);
292+
}
293+
294+
#[tokio::test]
295+
async fn list_cardano_database_snapshots_returns_for_latest_epoch_with_offset_error_when_invalid_json_structure_in_response()
296+
{
297+
let client = CardanoDatabaseClientDependencyInjector::new()
298+
.with_aggregator_client_mock_config(
299+
config_aggregator_client_to_always_returns_invalid_json,
300+
)
301+
.build_cardano_database_client();
302+
303+
client
304+
.list_for_latest_epoch_with_offset(42)
144305
.await
145306
.expect_err("List Cardano databases should return an error");
146307
}
@@ -180,11 +341,9 @@ mod tests {
180341
async fn get_cardano_database_snapshot_returns_error_when_invalid_json_structure_in_response()
181342
{
182343
let client = CardanoDatabaseClientDependencyInjector::new()
183-
.with_aggregator_client_mock_config(|http_client| {
184-
http_client
185-
.expect_get_content()
186-
.return_once(move |_| Ok("invalid json structure".to_string()));
187-
})
344+
.with_aggregator_client_mock_config(
345+
config_aggregator_client_to_always_returns_invalid_json,
346+
)
188347
.build_cardano_database_client();
189348

190349
client

mithril-client/src/cardano_database_client/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@
4444
//! # }
4545
//! ```
4646
//!
47+
//! # List available Cardano databases filtered by an epoch
48+
//!
49+
//! To list available Cardano databases using the [ClientBuilder][crate::client::ClientBuilder].
50+
//!
51+
//! ```no_run
52+
//! # async fn run() -> mithril_client::MithrilResult<()> {
53+
//! use mithril_client::{ClientBuilder, common::Epoch};
54+
//!
55+
//! let client = ClientBuilder::aggregator("YOUR_AGGREGATOR_ENDPOINT", "YOUR_GENESIS_VERIFICATION_KEY").build()?;
56+
//!
57+
//! // For a specific epoch
58+
//! let cardano_databases = client.cardano_database_v2().list_by_epoch(Epoch(8)).await?;
59+
//! // For the latest epoch known by the Mithril aggregator
60+
//! let cardano_databases = client.cardano_database_v2().list_for_latest_epoch().await?;
61+
//! // For the latest epoch known by the Mithril aggregator with an offset
62+
//! let cardano_databases = client.cardano_database_v2().list_for_latest_epoch_with_offset(4).await?;
63+
//!
64+
//! for cardano_database in cardano_databases {
65+
//! println!("Cardano database hash={}, immutable_file_number={}", cardano_database.hash, cardano_database.beacon.immutable_file_number);
66+
//! }
67+
//! # Ok(())
68+
//! # }
69+
//! ```
70+
//!
4771
//! # Download a Cardano database snapshot
4872
//! **Note:** _Available on crate feature_ **fs** _only._
4973
//!

0 commit comments

Comments
 (0)