Skip to content

Commit a2bd270

Browse files
committed
feat(build-script): add generation of cdb list per epoch & csd per epoch
+ add epoch lists for cardano database (cdb), indexed by epoch number + refactor cardano stake distribution (csd) epoch list gen: - use a common mechanism with cdb - change index from str to epoch number
1 parent 6ede592 commit a2bd270

File tree

3 files changed

+221
-58
lines changed

3 files changed

+221
-58
lines changed

internal/mithril-build-script/src/fake_aggregator.rs

Lines changed: 218 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ impl FakeAggregatorData {
110110
}
111111

112112
pub fn generate_code_for_ids(self) -> String {
113+
let cardano_stake_distributions_per_epoch =
114+
extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch");
115+
let cardano_database_snapshots_per_epoch =
116+
extract_item_list_per_epoch(&self.cardano_database_snapshots_list, "/beacon/epoch");
117+
113118
Self::assemble_code(
114119
&[
115120
generate_ids_array(
@@ -128,16 +133,18 @@ impl FakeAggregatorData {
128133
self.individual_cardano_stake_distributions.keys().cloned(),
129134
),
130135
),
131-
generate_ids_array(
136+
generate_epoch_array(
132137
"cardano_stake_distribution_epochs",
133-
BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
134-
&self.individual_cardano_stake_distributions,
135-
)),
138+
BTreeSet::from_iter(cardano_stake_distributions_per_epoch.keys().cloned()),
136139
),
137140
generate_ids_array(
138141
"cardano_database_snapshot_hashes",
139142
BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
140143
),
144+
generate_epoch_array(
145+
"cardano_database_snapshot_epochs",
146+
BTreeSet::from_iter(cardano_database_snapshots_per_epoch.keys().cloned()),
147+
),
141148
generate_ids_array(
142149
"certificate_hashes",
143150
BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
@@ -158,6 +165,11 @@ impl FakeAggregatorData {
158165
}
159166

160167
pub fn generate_code_for_all_data(self) -> String {
168+
let cardano_stake_distributions_per_epoch =
169+
extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch");
170+
let cardano_database_snapshots_per_epoch =
171+
extract_item_list_per_epoch(&self.cardano_database_snapshots_list, "/beacon/epoch");
172+
161173
Self::assemble_code(
162174
&[
163175
generate_list_getter("status", self.status),
@@ -188,11 +200,13 @@ impl FakeAggregatorData {
188200
self.individual_cardano_stake_distributions.keys().cloned(),
189201
),
190202
),
191-
generate_ids_array(
203+
generate_epoch_array(
192204
"cardano_stake_distribution_epochs",
193-
BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
194-
&self.individual_cardano_stake_distributions,
195-
)),
205+
BTreeSet::from_iter(cardano_stake_distributions_per_epoch.keys().cloned()),
206+
),
207+
generate_artifact_per_epoch_getter(
208+
"cardano_stake_distributions_per_epoch",
209+
extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch"),
196210
),
197211
generate_artifact_getter(
198212
"cardano_stake_distributions",
@@ -210,6 +224,10 @@ impl FakeAggregatorData {
210224
"cardano_database_snapshot_hashes",
211225
BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
212226
),
227+
generate_epoch_array(
228+
"cardano_database_snapshot_epochs",
229+
BTreeSet::from_iter(cardano_database_snapshots_per_epoch.keys().cloned()),
230+
),
213231
generate_artifact_getter(
214232
"cardano_database_snapshots",
215233
self.individual_cardano_database_snapshots,
@@ -218,6 +236,10 @@ impl FakeAggregatorData {
218236
"cardano_database_snapshot_list",
219237
self.cardano_database_snapshots_list,
220238
),
239+
generate_artifact_per_epoch_getter(
240+
"cardano_database_snapshot_list_per_epoch",
241+
cardano_database_snapshots_per_epoch,
242+
),
221243
generate_artifact_getter("certificates", self.individual_certificates),
222244
generate_list_getter("certificate_list", self.certificates_list),
223245
generate_ids_array(
@@ -280,27 +302,6 @@ impl FakeAggregatorData {
280302
}
281303
}
282304

283-
pub fn extract_cardano_stake_distribution_epochs(
284-
individual_csds: &BTreeMap<ArtifactId, FileContent>,
285-
) -> Vec<String> {
286-
individual_csds
287-
.values()
288-
.map(|content| {
289-
let json_value: serde_json::Value =
290-
serde_json::from_str(content).unwrap_or_else(|err| {
291-
panic!("Failed to parse JSON in csd content: {content}\nError: {err}");
292-
});
293-
294-
json_value
295-
.get("epoch")
296-
.and_then(|epoch| epoch.as_u64().map(|s| s.to_string()))
297-
.unwrap_or_else(|| {
298-
panic!("Epoch not found or invalid in csd content: {content}");
299-
})
300-
})
301-
.collect()
302-
}
303-
304305
fn extract_artifact_id_and_content(
305306
key: &String,
306307
value: &serde_json::Value,
@@ -309,6 +310,57 @@ fn extract_artifact_id_and_content(
309310
Ok((key.to_owned(), json_content))
310311
}
311312

313+
/// Takes a map of json string indexed by hashes and re-indexes them using their epoch
314+
///
315+
/// Each item in the map must contain an epoch value at the specified JSON pointer location.
316+
pub fn extract_item_by_epoch(
317+
items_per_hash: &BTreeMap<String, String>,
318+
json_pointer_for_epoch: &str,
319+
) -> BTreeMap<u64, String> {
320+
let mut res = BTreeMap::new();
321+
322+
for (key, value) in items_per_hash {
323+
let parsed_json: serde_json::Value = serde_json::from_str(value)
324+
.unwrap_or_else(|_| panic!("Could not parse JSON entity '{key}'"));
325+
let epoch = parsed_json
326+
.pointer(json_pointer_for_epoch)
327+
.unwrap_or_else(|| panic!("missing `{json_pointer_for_epoch}` for JSON entity '{key}'"))
328+
.as_u64()
329+
.unwrap_or_else(|| {
330+
panic!("`{json_pointer_for_epoch}` is not a number for JSON entity '{key}'")
331+
});
332+
res.insert(epoch, value.clone());
333+
}
334+
335+
res
336+
}
337+
338+
/// Takes a JSON string containing a list of items and extracts them into a map keyed by epoch.
339+
///
340+
/// Each item in the list must contain an epoch value at the specified JSON pointer location.
341+
pub fn extract_item_list_per_epoch(
342+
source: &str,
343+
json_pointer_for_epoch: &str,
344+
) -> BTreeMap<u64, String> {
345+
let parsed_json: Vec<serde_json::Value> =
346+
serde_json::from_str(source).expect("Failed to parse JSON list");
347+
let mut list_per_epoch = BTreeMap::<u64, Vec<serde_json::Value>>::new();
348+
349+
for item in parsed_json {
350+
let epoch = item
351+
.pointer(json_pointer_for_epoch)
352+
.unwrap_or_else(|| panic!("missing `{json_pointer_for_epoch}` for a json value"))
353+
.as_u64()
354+
.unwrap_or_else(|| panic!("`{json_pointer_for_epoch}` is not a number"));
355+
list_per_epoch.entry(epoch).or_default().push(item);
356+
}
357+
358+
list_per_epoch
359+
.into_iter()
360+
.map(|(k, v)| (k, serde_json::to_string(&v).unwrap()))
361+
.collect()
362+
}
363+
312364
pub fn list_json_files_in_folder(folder: &Path) -> impl Iterator<Item = fs::DirEntry> + '_ {
313365
crate::list_files_in_folder(folder)
314366
.filter(|e| e.file_name().to_string_lossy().ends_with(".json"))
@@ -344,6 +396,36 @@ pub fn generate_artifact_getter(
344396
)
345397
}
346398

399+
// pub(crate) fn $fun_name()() -> BTreeMap<u64, String>
400+
pub fn generate_artifact_per_epoch_getter(
401+
fun_name: &str,
402+
source_jsons: BTreeMap<u64, FileContent>,
403+
) -> String {
404+
let mut artifacts_list = String::new();
405+
406+
for (artifact_id, file_content) in source_jsons {
407+
write!(
408+
artifacts_list,
409+
r###"
410+
(
411+
{artifact_id},
412+
r#"{file_content}"#
413+
),"###
414+
)
415+
.unwrap();
416+
}
417+
418+
format!(
419+
r###"pub(crate) fn {fun_name}() -> BTreeMap<u64, String> {{
420+
[{artifacts_list}
421+
]
422+
.into_iter()
423+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
424+
.collect()
425+
}}"###
426+
)
427+
}
428+
347429
/// pub(crate) fn $fun_name() -> &'static str
348430
pub fn generate_list_getter(fun_name: &str, source_json: FileContent) -> String {
349431
format!(
@@ -377,6 +459,30 @@ pub fn generate_ids_array(array_name: &str, ids: BTreeSet<ArtifactId>) -> String
377459
)
378460
}
379461

462+
/// pub(crate) fn $array_name() -> [u64; $epoch.len]
463+
pub fn generate_epoch_array(array_name: &str, epoch: BTreeSet<u64>) -> String {
464+
let mut ids_list = String::new();
465+
466+
for id in &epoch {
467+
write!(
468+
ids_list,
469+
r#"
470+
{id},"#
471+
)
472+
.unwrap();
473+
}
474+
475+
format!(
476+
r###"pub(crate) const fn {}() -> [u64; {}] {{
477+
[{}
478+
]
479+
}}"###,
480+
array_name,
481+
epoch.len(),
482+
ids_list,
483+
)
484+
}
485+
380486
#[cfg(test)]
381487
mod tests {
382488
use crate::get_temp_dir;
@@ -476,48 +582,105 @@ fn b() {}
476582
}
477583

478584
#[test]
479-
fn extract_csd_epochs_with_valid_data() {
480-
let mut csds = BTreeMap::new();
481-
csds.insert(
482-
"csd-123".to_string(),
483-
r#"{"hash": "csd-123", "epoch": 123}"#.to_string(),
484-
);
485-
csds.insert(
486-
"csd-456".to_string(),
487-
r#"{"hash": "csd-456", "epoch": 456}"#.to_string(),
488-
);
489-
490-
let epochs = extract_cardano_stake_distribution_epochs(&csds);
585+
fn test_extract_item_by_epoch_by_epoch_with_valid_data() {
586+
let items_per_hash = BTreeMap::from([
587+
(
588+
"hash1".to_string(),
589+
r#"{"bar":4,"epoch":3,"foo":"...","hash":"2"}"#.to_string(),
590+
),
591+
(
592+
"hash2".to_string(),
593+
r#"{"bar":7,"epoch":2,"foo":"...","hash":"1"}"#.to_string(),
594+
),
595+
]);
491596

492-
assert_eq!(epochs, vec![123.to_string(), 456.to_string()]);
597+
// note: values are not re-serialized, so they are kept as is
598+
let item_per_epoch = extract_item_by_epoch(&items_per_hash, "/epoch");
599+
assert_eq!(
600+
BTreeMap::from([
601+
(3, items_per_hash.get("hash1").unwrap().to_string()),
602+
(2, items_per_hash.get("hash2").unwrap().to_string())
603+
]),
604+
item_per_epoch
605+
)
493606
}
494607

495608
#[test]
496-
#[should_panic(expected = "Failed to parse JSON in csd content")]
497-
fn extract_csd_epochs_with_invalid_json() {
498-
let mut csds = BTreeMap::new();
499-
csds.insert(
609+
#[should_panic(expected = "Could not parse JSON entity 'csd-123'")]
610+
fn test_extract_item_by_epoch_by_epoch_with_invalid_json() {
611+
let mut items_per_hash = BTreeMap::new();
612+
items_per_hash.insert(
500613
"csd-123".to_string(),
501614
r#""hash": "csd-123", "epoch": "123"#.to_string(),
502615
);
503616

504-
extract_cardano_stake_distribution_epochs(&csds);
617+
extract_item_by_epoch(&items_per_hash, "/epoch");
618+
}
619+
620+
#[test]
621+
#[should_panic(expected = "missing `/epoch` for JSON entity 'csd-123'")]
622+
fn test_extract_item_by_epoch_with_missing_epoch() {
623+
let mut items_per_hash = BTreeMap::new();
624+
items_per_hash.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());
625+
626+
extract_item_by_epoch(&items_per_hash, "/epoch");
627+
}
628+
629+
#[test]
630+
fn test_extract_item_by_epoch_with_empty_map() {
631+
let items_per_hash = BTreeMap::new();
632+
633+
let epochs = extract_item_by_epoch(&items_per_hash, "/epoch");
634+
635+
assert!(epochs.is_empty());
636+
}
637+
638+
#[test]
639+
fn test_extract_item_list_per_epoch_for_epoch() {
640+
let list_per_epoch_json = r#"[
641+
{ "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." },
642+
{ "beacon": { "epoch": 2}, "hash":"2","foo":"..." },
643+
{ "beacon": { "epoch": 1}, "hash":"1","foo":"..." }
644+
]"#;
645+
646+
// note: values are re-serialized, so serde_json reorders the keys
647+
let map_per_epoch = extract_item_list_per_epoch(list_per_epoch_json, "/beacon/epoch");
648+
assert_eq!(
649+
BTreeMap::from([
650+
(
651+
1,
652+
r#"[{"beacon":{"bar":4,"epoch":1},"foo":"...","hash":"3"},{"beacon":{"epoch":1},"foo":"...","hash":"1"}]"#
653+
.to_string()
654+
),
655+
(2, r#"[{"beacon":{"epoch":2},"foo":"...","hash":"2"}]"#.to_string()),
656+
]),
657+
map_per_epoch
658+
)
659+
}
660+
661+
#[test]
662+
#[should_panic(expected = "Failed to parse JSON list")]
663+
fn test_extract_item_list_per_epoch_with_invalid_json() {
664+
// invalid because of the trailing comma
665+
let list_per_epoch_json =
666+
r#"[ { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." }, ]"#;
667+
668+
extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
505669
}
506670

507671
#[test]
508-
#[should_panic(expected = "Epoch not found or invalid in csd content")]
509-
fn test_extract_csd_epochs_with_missing_epoch() {
510-
let mut csds = BTreeMap::new();
511-
csds.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());
672+
#[should_panic(expected = "missing `/epoch` for a json value")]
673+
fn test_extract_item_list_per_epoch_with_missing_epoch() {
674+
let list_per_epoch_json = r#"[ { "beacon": { "bar": 4 }, "hash":"3","foo":"..." } ]"#;
512675

513-
extract_cardano_stake_distribution_epochs(&csds);
676+
extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
514677
}
515678

516679
#[test]
517-
fn test_extract_csd_epochs_with_empty_map() {
518-
let csds = BTreeMap::new();
680+
fn test_extract_item_list_per_epoch_with_list() {
681+
let list_per_epoch_json = "[]";
519682

520-
let epochs = extract_cardano_stake_distribution_epochs(&csds);
683+
let epochs = extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
521684

522685
assert!(epochs.is_empty());
523686
}

mithril-client-wasm/src/client_wasm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ mod tests {
895895
#[wasm_bindgen_test]
896896
async fn get_cardano_stake_distribution_by_epoch_should_return_value_convertible_in_rust_type()
897897
{
898-
let epoch: u64 = test_data::cardano_stake_distribution_epochs()[0].parse().unwrap();
898+
let epoch = test_data::cardano_stake_distribution_epochs()[0];
899899
let csd_js_value = get_mithril_client_stable()
900900
.get_cardano_stake_distribution_by_epoch(epoch)
901901
.await

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,8 @@ mod tests {
585585
yield_now().await;
586586

587587
let path = "/artifact/cardano-stake-distribution/epoch/{epoch}";
588-
let epoch = default_values::cardano_stake_distribution_epochs()[0];
589-
let response = http_request(PORT, &path.replace("{epoch}", epoch)).await;
588+
let epoch = default_values::cardano_stake_distribution_epochs()[0].to_string();
589+
let response = http_request(PORT, &path.replace("{epoch}", &epoch)).await;
590590

591591
APISpec::verify_conformity(
592592
get_spec_file(),

0 commit comments

Comments
 (0)