Skip to content

Commit 24af55f

Browse files
committed
test(aggregator-fake): add route cardano-database/epoch/{epoch}
With `{epoch}` either a number, `latest`, or `latest-x` where X is a number + Add support for `latest` / `latest-x` on the route `cardano-stake-distribution/epoch/{epoch}`
1 parent b1329ac commit 24af55f

File tree

2 files changed

+203
-25
lines changed

2 files changed

+203
-25
lines changed

mithril-test-lab/mithril-aggregator-fake/src/handlers.rs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ pub async fn aggregator_router() -> Router<SharedState> {
5959
"/artifact/cardano-database",
6060
get(cardano_database_snapshots),
6161
)
62+
.route(
63+
"/artifact/cardano-database/epoch/{epoch}",
64+
get(cardano_database_snapshots_by_epoch),
65+
)
6266
.route(
6367
"/artifact/cardano-database/{hash}",
6468
get(cardano_database_snapshot),
@@ -234,34 +238,14 @@ pub async fn cardano_stake_distribution_by_epoch(
234238
Path(epoch): Path<String>,
235239
State(state): State<SharedState>,
236240
) -> Result<Response<Body>, AppError> {
237-
#[derive(Debug, serde::Deserialize)]
238-
struct TmpCardanoStakeDistributionData {
239-
hash: String,
240-
epoch: u64,
241-
}
242-
243241
let app_state = state.read().await;
244242

245-
let cardano_stake_distributions = app_state.get_cardano_stake_distributions().await?;
246-
let cardano_stake_distributions: Vec<TmpCardanoStakeDistributionData> =
247-
serde_json::from_str(&cardano_stake_distributions)?;
248-
249-
// Find the cardano stake distribution hash corresponding to the epoch
250-
let hash = cardano_stake_distributions
251-
.into_iter()
252-
.find(|csd| csd.epoch.to_string() == epoch)
253-
.map(|csd| csd.hash)
254-
.ok_or_else(|| {
255-
debug!("No cardano stake distribution found for epoch={epoch}.");
256-
AppError::NotFound
257-
})?;
258-
259243
app_state
260-
.get_cardano_stake_distribution(&hash)
244+
.get_cardano_stake_distribution_by_epoch(&epoch)
261245
.await?
262246
.map(|s| s.into_response())
263247
.ok_or_else(|| {
264-
debug!("cardano stake distribution hash={hash} NOT FOUND.");
248+
debug!("cardano stake distribution epoch={epoch} NOT FOUND.");
265249
AppError::NotFound
266250
})
267251
}
@@ -276,6 +260,23 @@ pub async fn cardano_database_snapshots(
276260
Ok(cardano_database_snapshots)
277261
}
278262

263+
/// HTTP: return the list of cardano database snapshots.
264+
pub async fn cardano_database_snapshots_by_epoch(
265+
Path(epoch): Path<String>,
266+
State(state): State<SharedState>,
267+
) -> Result<Response<Body>, AppError> {
268+
let app_state = state.read().await;
269+
270+
app_state
271+
.get_cardano_database_snapshots_for_epoch(&epoch)
272+
.await?
273+
.map(|s| s.into_response())
274+
.ok_or_else(|| {
275+
debug!("cardano database epoch={epoch} NOT FOUND.");
276+
AppError::NotFound
277+
})
278+
}
279+
279280
/// HTTP: return a cardano database snapshot identified by its hash.
280281
pub async fn cardano_database_snapshot(
281282
Path(key): Path<String>,

mithril-test-lab/mithril-aggregator-fake/src/shared_state.rs

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ pub struct AppState {
2424
cardano_transaction_proofs: BTreeMap<String, String>,
2525
cardano_stake_distribution_list: String,
2626
cardano_stake_distributions: BTreeMap<String, String>,
27+
cardano_stake_distributions_per_epoch: BTreeMap<u64, String>,
2728
cardano_database_snapshot_list: String,
29+
cardano_database_snapshot_list_per_epoch: BTreeMap<u64, String>,
2830
cardano_database_snapshots: BTreeMap<String, String>,
2931
}
3032

@@ -40,6 +42,17 @@ impl From<AppState> for SharedState {
4042

4143
impl Default for AppState {
4244
fn default() -> Self {
45+
let cardano_stake_distributions = default_values::cardano_stake_distributions();
46+
let cardano_stake_distributions_per_epoch =
47+
extract_cardano_stake_distribution_by_epoch(&cardano_stake_distributions)
48+
.expect("Embedded default values are not valid JSON");
49+
50+
let cardano_database_snapshot_list =
51+
default_values::cardano_database_snapshot_list().to_owned();
52+
let cardano_database_snapshot_list_per_epoch =
53+
extract_cardano_database_snapshots_for_epoch(&cardano_database_snapshot_list)
54+
.expect("Embedded default values are not valid JSON");
55+
4356
Self {
4457
status: default_values::status().to_owned(),
4558
epoch_settings: default_values::epoch_settings().to_owned(),
@@ -56,9 +69,10 @@ impl Default for AppState {
5669
cardano_transaction_proofs: default_values::cardano_transaction_proofs(),
5770
cardano_stake_distribution_list: default_values::cardano_stake_distribution_list()
5871
.to_owned(),
59-
cardano_stake_distributions: default_values::cardano_stake_distributions(),
60-
cardano_database_snapshot_list: default_values::cardano_database_snapshot_list()
61-
.to_owned(),
72+
cardano_stake_distributions,
73+
cardano_stake_distributions_per_epoch,
74+
cardano_database_snapshot_list,
75+
cardano_database_snapshot_list_per_epoch,
6276
cardano_database_snapshots: default_values::cardano_database_snapshots(),
6377
}
6478
}
@@ -83,6 +97,12 @@ impl AppState {
8397
let (cardano_database_snapshot_list, cardano_database_snapshots) =
8498
reader.read_files("cardano-database")?;
8599

100+
// derived values
101+
let cardano_stake_distributions_per_epoch =
102+
extract_cardano_stake_distribution_by_epoch(&cardano_stake_distributions)?;
103+
let cardano_database_snapshot_list_per_epoch =
104+
extract_cardano_database_snapshots_for_epoch(&cardano_database_snapshot_list)?;
105+
86106
let instance = Self {
87107
status,
88108
epoch_settings,
@@ -96,8 +116,10 @@ impl AppState {
96116
cardano_transaction_snapshots,
97117
cardano_transaction_proofs,
98118
cardano_stake_distribution_list,
119+
cardano_stake_distributions_per_epoch,
99120
cardano_stake_distributions,
100121
cardano_database_snapshot_list,
122+
cardano_database_snapshot_list_per_epoch,
101123
cardano_database_snapshots,
102124
};
103125

@@ -169,11 +191,57 @@ impl AppState {
169191
Ok(self.cardano_stake_distributions.get(key).cloned())
170192
}
171193

194+
/// return the Cardano stake distribution identified by the given epoch if any.
195+
///
196+
/// `epoch` can be either a number, `latest`, or `latest-X` where x is a number
197+
pub async fn get_cardano_stake_distribution_by_epoch(
198+
&self,
199+
epoch: &str,
200+
) -> StdResult<Option<String>> {
201+
let stake_distribution = if epoch.starts_with("latest") {
202+
let offset = parse_epoch_offset(epoch)?;
203+
self.cardano_stake_distributions_per_epoch
204+
.values()
205+
.rev()
206+
.nth(offset.unwrap_or_default())
207+
.cloned()
208+
} else {
209+
self.cardano_stake_distributions_per_epoch
210+
.get(&epoch.parse().with_context(|| "invalid epoch")?)
211+
.cloned()
212+
};
213+
214+
Ok(stake_distribution)
215+
}
216+
172217
/// return the list of Cardano database snapshots in the same order as they were read
173218
pub async fn get_cardano_database_snapshots(&self) -> StdResult<String> {
174219
Ok(self.cardano_database_snapshot_list.clone())
175220
}
176221

222+
/// return the list of Cardano database snapshots for a given epoch in the same order as they were read
223+
///
224+
/// `epoch` can be either a number, `latest`, or `latest-X` where x is a number
225+
pub async fn get_cardano_database_snapshots_for_epoch(
226+
&self,
227+
epoch: &str,
228+
) -> StdResult<Option<String>> {
229+
let snapshots = if epoch.starts_with("latest") {
230+
let offset = parse_epoch_offset(epoch)?;
231+
self.cardano_database_snapshot_list_per_epoch
232+
.values()
233+
.rev()
234+
.nth(offset.unwrap_or_default())
235+
.cloned()
236+
} else {
237+
self.cardano_database_snapshot_list_per_epoch
238+
.get(&epoch.parse().with_context(|| "invalid epoch")?)
239+
.cloned()
240+
};
241+
242+
Ok(snapshots)
243+
}
244+
177245
/// return the Cardano database snapshot identified by the given key if any.
178246
pub async fn get_cardano_database_snapshot(&self, key: &str) -> StdResult<Option<String>> {
179247
Ok(self.cardano_database_snapshots.get(key).cloned())
@@ -262,6 +330,52 @@ impl DataDir {
262330
}
263331
}
264332

333+
fn parse_epoch_offset(epoch: &str) -> StdResult<Option<usize>> {
334+
epoch
335+
.strip_prefix("latest-")
336+
.map(|s| s.parse::<usize>())
337+
.transpose()
338+
.with_context(|| "invalid epoch offset")
339+
}
340+
341+
fn extract_cardano_stake_distribution_by_epoch(
342+
source: &BTreeMap<String, String>,
343+
) -> StdResult<BTreeMap<u64, String>> {
344+
let mut res = BTreeMap::new();
345+
346+
for (key, value) in source {
347+
let parsed_json: serde_json::Value = serde_json::from_str(value)
348+
.with_context(|| format!("Could not parse JSON entity '{key}'"))?;
349+
let epoch = parsed_json
350+
.pointer("/epoch")
351+
.with_context(|| "missing epoch for a json value")?
352+
.as_u64()
353+
.with_context(|| "epoch is not a number")?;
354+
res.insert(epoch, value.clone());
355+
}
356+
357+
Ok(res)
358+
}
359+
360+
fn extract_cardano_database_snapshots_for_epoch(source: &str) -> StdResult<BTreeMap<u64, String>> {
361+
let parsed_json: Vec<serde_json::Value> = serde_json::from_str(source)?;
362+
let mut cardano_db_snapshots_per_epoch = BTreeMap::<u64, Vec<serde_json::Value>>::new();
363+
364+
for item in parsed_json {
365+
let epoch = item
366+
.pointer("/beacon/epoch")
367+
.with_context(|| "missing beacon.epoch for a json value")?
368+
.as_u64()
369+
.with_context(|| "beacon.epoch is not a number")?;
370+
cardano_db_snapshots_per_epoch.entry(epoch).or_default().push(item);
371+
}
372+
373+
Ok(cardano_db_snapshots_per_epoch
374+
.into_iter()
375+
.map(|(k, v)| (k, serde_json::to_string(&v).unwrap()))
376+
.collect())
377+
}
378+
265379
#[cfg(test)]
266380
mod tests {
267381
use super::*;
@@ -271,4 +385,67 @@ mod tests {
271385
AppState::from_directory(Path::new("./default_data"))
272386
.expect("Should be able to construct an AppState from the default_data");
273387
}
388+
389+
#[test]
390+
fn test_extract_cardano_stake_distribution_by_epoch() {
391+
let source = BTreeMap::from([
392+
(
393+
"hash1".to_string(),
394+
r#"{"bar":4,"epoch":3,"foo":"...","hash":"2"}"#.to_string(),
395+
),
396+
(
397+
"hash2".to_string(),
398+
r#"{"bar":7,"epoch":2,"foo":"...","hash":"1"}"#.to_string(),
399+
),
400+
]);
401+
let extracted = extract_cardano_stake_distribution_by_epoch(&source).unwrap();
402+
403+
// note: values are not re-serialized, so they are kept as is
404+
assert_eq!(
405+
BTreeMap::from([
406+
(3, source.get("hash1").unwrap().to_string()),
407+
(2, source.get("hash2").unwrap().to_string())
408+
]),
409+
extracted,
410+
)
411+
}
412+
413+
#[test]
414+
fn test_extract_cardano_database_snapshots_for_epoch() {
415+
let extracted = extract_cardano_database_snapshots_for_epoch(
416+
r#"[
417+
{ "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." },
418+
{ "beacon": { "epoch": 2}, "hash":"2","foo":"..." },
419+
{ "beacon": { "epoch": 1}, "hash":"1","foo":"..." }
420+
]"#,
421+
)
422+
.unwrap();
423+
424+
// note: values are re-serialized, so serde_json reorders the keys
425+
assert_eq!(
426+
BTreeMap::from([
427+
(
428+
1,
429+
r#"[{"beacon":{"bar":4,"epoch":1},"foo":"...","hash":"3"},{"beacon":{"epoch":1},"foo":"...","hash":"1"}]"#
430+
.to_string()
431+
),
432+
(2, r#"[{"beacon":{"epoch":2},"foo":"...","hash":"2"}]"#.to_string()),
433+
]),
434+
extracted,
435+
)
436+
}
437+
438+
#[test]
439+
fn extract_cardano_stake_distribution_by_epoch_from_default_data_dont_panic() {
440+
extract_cardano_stake_distribution_by_epoch(&default_values::cardano_stake_distributions())
441+
.unwrap();
442+
}
443+
444+
#[test]
445+
fn extract_cardano_database_snapshots_for_epoch_from_default_data_dont_panic() {
446+
extract_cardano_database_snapshots_for_epoch(
447+
default_values::cardano_database_snapshot_list(),
448+
)
449+
.unwrap();
450+
}
274451
}

0 commit comments

Comments
 (0)