mithril_build_script/
fake_aggregator.rs

1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::fmt::Write as _;
4use std::fs;
5use std::fs::File;
6use std::path::Path;
7
8use serde_json;
9
10pub type ArtifactId = String;
11pub type FileContent = String;
12
13/// In memory representation of a folder containing data imported using the `scripts/import.sh` script
14/// of the fake aggregator.
15#[derive(Debug, Default)]
16pub struct FakeAggregatorData {
17    epoch_settings: FileContent,
18
19    certificates_list: FileContent,
20    individual_certificates: BTreeMap<ArtifactId, FileContent>,
21
22    snapshots_list: FileContent,
23    individual_snapshots: BTreeMap<ArtifactId, FileContent>,
24
25    mithril_stake_distributions_list: FileContent,
26    individual_mithril_stake_distributions: BTreeMap<ArtifactId, FileContent>,
27
28    cardano_transaction_snapshots_list: FileContent,
29    individual_cardano_transaction_snapshots: BTreeMap<ArtifactId, FileContent>,
30    cardano_transaction_proofs: BTreeMap<ArtifactId, FileContent>,
31
32    cardano_stake_distributions_list: FileContent,
33    individual_cardano_stake_distributions: BTreeMap<ArtifactId, FileContent>,
34
35    cardano_database_snapshots_list: FileContent,
36    individual_cardano_database_snapshots: BTreeMap<ArtifactId, FileContent>,
37}
38
39impl FakeAggregatorData {
40    pub fn load_from_folder(folder: &Path) -> Self {
41        let mut data = FakeAggregatorData::default();
42
43        for entry in list_json_files_in_folder(folder) {
44            let filename = entry.file_name().to_string_lossy().to_string();
45            let file_content = fs::read_to_string(entry.path()).unwrap_or_else(|_| {
46                panic!(
47                    "Could not read file content, file_path: {}",
48                    entry.path().display()
49                )
50            });
51
52            match filename.as_str() {
53                "epoch-settings.json" => {
54                    data.epoch_settings = file_content;
55                }
56                "mithril-stake-distributions-list.json" => {
57                    data.mithril_stake_distributions_list = file_content;
58                }
59                "snapshots-list.json" => {
60                    data.snapshots_list = file_content;
61                }
62                "cardano-stake-distributions-list.json" => {
63                    data.cardano_stake_distributions_list = file_content;
64                }
65                "cardano-databases-list.json" => {
66                    data.cardano_database_snapshots_list = file_content;
67                }
68                "certificates-list.json" => {
69                    data.certificates_list = file_content;
70                }
71                "ctx-snapshots-list.json" => {
72                    data.cardano_transaction_snapshots_list = file_content;
73                }
74                "mithril-stake-distributions.json" => {
75                    data.individual_mithril_stake_distributions =
76                        Self::read_artifacts_json_file(&entry.path());
77                }
78                "snapshots.json" => {
79                    data.individual_snapshots = Self::read_artifacts_json_file(&entry.path());
80                }
81                "cardano-stake-distributions.json" => {
82                    data.individual_cardano_stake_distributions =
83                        Self::read_artifacts_json_file(&entry.path());
84                }
85                "cardano-databases.json" => {
86                    data.individual_cardano_database_snapshots =
87                        Self::read_artifacts_json_file(&entry.path());
88                }
89                "certificates.json" => {
90                    data.individual_certificates = Self::read_artifacts_json_file(&entry.path());
91                }
92                "ctx-snapshots.json" => {
93                    data.individual_cardano_transaction_snapshots =
94                        Self::read_artifacts_json_file(&entry.path());
95                }
96                "ctx-proofs.json" => {
97                    data.cardano_transaction_proofs = Self::read_artifacts_json_file(&entry.path());
98                }
99                // unknown file
100                _ => {}
101            }
102        }
103
104        data
105    }
106
107    pub fn generate_code_for_ids(self) -> String {
108        Self::assemble_code(
109            &[
110                generate_ids_array(
111                    "snapshot_digests",
112                    BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
113                ),
114                generate_ids_array(
115                    "mithril_stake_distribution_hashes",
116                    BTreeSet::from_iter(
117                        self.individual_mithril_stake_distributions.keys().cloned(),
118                    ),
119                ),
120                generate_ids_array(
121                    "cardano_stake_distribution_hashes",
122                    BTreeSet::from_iter(
123                        self.individual_cardano_stake_distributions.keys().cloned(),
124                    ),
125                ),
126                generate_ids_array(
127                    "cardano_stake_distribution_epochs",
128                    BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
129                        &self.individual_cardano_stake_distributions,
130                    )),
131                ),
132                generate_ids_array(
133                    "cardano_database_snapshot_hashes",
134                    BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
135                ),
136                generate_ids_array(
137                    "certificate_hashes",
138                    BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
139                ),
140                generate_ids_array(
141                    "cardano_transaction_snapshot_hashes",
142                    BTreeSet::from_iter(
143                        self.individual_cardano_transaction_snapshots
144                            .keys()
145                            .cloned(),
146                    ),
147                ),
148                generate_ids_array(
149                    "proof_transaction_hashes",
150                    BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
151                ),
152            ],
153            false,
154        )
155    }
156
157    pub fn generate_code_for_all_data(self) -> String {
158        Self::assemble_code(
159            &[
160                generate_list_getter("epoch_settings", self.epoch_settings),
161                generate_ids_array(
162                    "snapshot_digests",
163                    BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
164                ),
165                generate_artifact_getter("snapshots", self.individual_snapshots),
166                generate_list_getter("snapshot_list", self.snapshots_list),
167                generate_ids_array(
168                    "mithril_stake_distribution_hashes",
169                    BTreeSet::from_iter(
170                        self.individual_mithril_stake_distributions.keys().cloned(),
171                    ),
172                ),
173                generate_artifact_getter(
174                    "mithril_stake_distributions",
175                    self.individual_mithril_stake_distributions,
176                ),
177                generate_list_getter(
178                    "mithril_stake_distribution_list",
179                    self.mithril_stake_distributions_list,
180                ),
181                generate_ids_array(
182                    "cardano_stake_distribution_hashes",
183                    BTreeSet::from_iter(
184                        self.individual_cardano_stake_distributions.keys().cloned(),
185                    ),
186                ),
187                generate_ids_array(
188                    "cardano_stake_distribution_epochs",
189                    BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
190                        &self.individual_cardano_stake_distributions,
191                    )),
192                ),
193                generate_artifact_getter(
194                    "cardano_stake_distributions",
195                    self.individual_cardano_stake_distributions,
196                ),
197                generate_list_getter(
198                    "cardano_stake_distribution_list",
199                    self.cardano_stake_distributions_list,
200                ),
201                generate_ids_array(
202                    "certificate_hashes",
203                    BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
204                ),
205                generate_ids_array(
206                    "cardano_database_snapshot_hashes",
207                    BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
208                ),
209                generate_artifact_getter(
210                    "cardano_database_snapshots",
211                    self.individual_cardano_database_snapshots,
212                ),
213                generate_list_getter(
214                    "cardano_database_snapshot_list",
215                    self.cardano_database_snapshots_list,
216                ),
217                generate_artifact_getter("certificates", self.individual_certificates),
218                generate_list_getter("certificate_list", self.certificates_list),
219                generate_ids_array(
220                    "cardano_transaction_snapshot_hashes",
221                    BTreeSet::from_iter(
222                        self.individual_cardano_transaction_snapshots
223                            .keys()
224                            .cloned(),
225                    ),
226                ),
227                generate_artifact_getter(
228                    "cardano_transaction_snapshots",
229                    self.individual_cardano_transaction_snapshots,
230                ),
231                generate_list_getter(
232                    "cardano_transaction_snapshots_list",
233                    self.cardano_transaction_snapshots_list,
234                ),
235                generate_ids_array(
236                    "proof_transaction_hashes",
237                    BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
238                ),
239                generate_artifact_getter(
240                    "cardano_transaction_proofs",
241                    self.cardano_transaction_proofs,
242                ),
243            ],
244            true,
245        )
246    }
247
248    fn assemble_code(functions_code: &[String], include_use_btree_map: bool) -> String {
249        format!(
250            "{}{}
251",
252            if include_use_btree_map {
253                "use std::collections::BTreeMap;
254
255"
256            } else {
257                ""
258            },
259            functions_code.join(
260                "
261
262"
263            )
264        )
265    }
266
267    fn read_artifacts_json_file(json_file: &Path) -> BTreeMap<ArtifactId, FileContent> {
268        let file = File::open(json_file).unwrap();
269        let parsed_json: serde_json::Value = serde_json::from_reader(&file).unwrap();
270
271        let json_object = parsed_json.as_object().unwrap();
272        let res: Result<Vec<_>, _> = json_object
273            .iter()
274            .map(|(key, value)| extract_artifact_id_and_content(key, value))
275            .collect();
276
277        BTreeMap::from_iter(res.unwrap())
278    }
279}
280
281pub fn extract_cardano_stake_distribution_epochs(
282    individual_csds: &BTreeMap<ArtifactId, FileContent>,
283) -> Vec<String> {
284    individual_csds
285        .values()
286        .map(|content| {
287            let json_value: serde_json::Value =
288                serde_json::from_str(content).unwrap_or_else(|err| {
289                    panic!(
290                        "Failed to parse JSON in csd content: {}\nError: {}",
291                        content, err
292                    );
293                });
294
295            json_value
296                .get("epoch")
297                .and_then(|epoch| epoch.as_u64().map(|s| s.to_string()))
298                .unwrap_or_else(|| {
299                    panic!("Epoch not found or invalid in csd content: {}", content);
300                })
301        })
302        .collect()
303}
304
305fn extract_artifact_id_and_content(
306    key: &String,
307    value: &serde_json::Value,
308) -> Result<(ArtifactId, FileContent), String> {
309    let json_content = serde_json::to_string_pretty(value).map_err(|e| e.to_string())?;
310    Ok((key.to_owned(), json_content))
311}
312
313pub fn list_json_files_in_folder(folder: &Path) -> impl Iterator<Item = fs::DirEntry> + '_ {
314    crate::list_files_in_folder(folder)
315        .filter(|e| e.file_name().to_string_lossy().ends_with(".json"))
316}
317
318// pub(crate) fn $fun_name()() -> BTreeMap<String, String>
319pub fn generate_artifact_getter(
320    fun_name: &str,
321    source_jsons: BTreeMap<ArtifactId, FileContent>,
322) -> String {
323    let mut artifacts_list = String::new();
324
325    for (artifact_id, file_content) in source_jsons {
326        write!(
327            artifacts_list,
328            r###"
329        (
330            "{}",
331            r#"{}"#
332        ),"###,
333            artifact_id, file_content
334        )
335        .unwrap();
336    }
337
338    format!(
339        r###"pub(crate) fn {}() -> BTreeMap<String, String> {{
340    [{}
341    ]
342    .into_iter()
343    .map(|(k, v)| (k.to_owned(), v.to_owned()))
344    .collect()
345}}"###,
346        fun_name, artifacts_list
347    )
348}
349
350/// pub(crate) fn $fun_name() -> &'static str
351pub fn generate_list_getter(fun_name: &str, source_json: FileContent) -> String {
352    format!(
353        r###"pub(crate) fn {}() -> &'static str {{
354    r#"{}"#
355}}"###,
356        fun_name, source_json
357    )
358}
359
360/// pub(crate) fn $array_name() -> [&'a str; $ids.len]
361pub fn generate_ids_array(array_name: &str, ids: BTreeSet<ArtifactId>) -> String {
362    let mut ids_list = String::new();
363
364    for id in &ids {
365        write!(
366            ids_list,
367            r#"
368        "{}","#,
369            id
370        )
371        .unwrap();
372    }
373
374    format!(
375        r###"pub(crate) const fn {}<'a>() -> [&'a str; {}] {{
376    [{}
377    ]
378}}"###,
379        array_name,
380        ids.len(),
381        ids_list,
382    )
383}
384
385#[cfg(test)]
386mod tests {
387    use crate::get_temp_dir;
388
389    use super::*;
390
391    #[test]
392    fn generate_ids_array_with_empty_data() {
393        assert_eq!(
394            generate_ids_array("snapshots_digests", BTreeSet::new()),
395            "pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 0] {
396    [
397    ]
398}"
399        );
400    }
401
402    #[test]
403    fn generate_ids_array_with_non_empty_data() {
404        assert_eq!(
405            generate_ids_array(
406                "snapshots_digests",
407                BTreeSet::from_iter(["abc".to_string(), "def".to_string(), "hij".to_string()])
408            ),
409            r#"pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 3] {
410    [
411        "abc",
412        "def",
413        "hij",
414    ]
415}"#
416        );
417    }
418
419    #[test]
420    fn assemble_code_with_btree_use() {
421        assert_eq!(
422            "use std::collections::BTreeMap;
423
424fn a() {}
425
426fn b() {}
427",
428            FakeAggregatorData::assemble_code(
429                &["fn a() {}".to_string(), "fn b() {}".to_string()],
430                true
431            )
432        )
433    }
434
435    #[test]
436    fn assemble_code_without_btree_use() {
437        assert_eq!(
438            "fn a() {}
439
440fn b() {}
441",
442            FakeAggregatorData::assemble_code(
443                &["fn a() {}".to_string(), "fn b() {}".to_string()],
444                false
445            )
446        )
447    }
448
449    #[test]
450    fn parse_artifacts_json_into_btree_of_key_and_pretty_sub_json() {
451        let dir = get_temp_dir("read_artifacts_json_file");
452        let file = dir.join("test.json");
453        fs::write(
454            &file,
455            r#"{
456    "hash1": { "name": "artifact1" },
457    "hash2": { "name": "artifact2" }
458}"#,
459        )
460        .unwrap();
461
462        let id_per_json = FakeAggregatorData::read_artifacts_json_file(&file);
463
464        let expected = BTreeMap::from([
465            (
466                "hash1".to_string(),
467                r#"{
468  "name": "artifact1"
469}"#
470                .to_string(),
471            ),
472            (
473                "hash2".to_string(),
474                r#"{
475  "name": "artifact2"
476}"#
477                .to_string(),
478            ),
479        ]);
480        assert_eq!(expected, id_per_json);
481    }
482
483    #[test]
484    fn extract_csd_epochs_with_valid_data() {
485        let mut csds = BTreeMap::new();
486        csds.insert(
487            "csd-123".to_string(),
488            r#"{"hash": "csd-123", "epoch": 123}"#.to_string(),
489        );
490        csds.insert(
491            "csd-456".to_string(),
492            r#"{"hash": "csd-456", "epoch": 456}"#.to_string(),
493        );
494
495        let epochs = extract_cardano_stake_distribution_epochs(&csds);
496
497        assert_eq!(epochs, vec![123.to_string(), 456.to_string()]);
498    }
499
500    #[test]
501    #[should_panic(expected = "Failed to parse JSON in csd content")]
502    fn extract_csd_epochs_with_invalid_json() {
503        let mut csds = BTreeMap::new();
504        csds.insert(
505            "csd-123".to_string(),
506            r#""hash": "csd-123", "epoch": "123"#.to_string(),
507        );
508
509        extract_cardano_stake_distribution_epochs(&csds);
510    }
511
512    #[test]
513    #[should_panic(expected = "Epoch not found or invalid in csd content")]
514    fn test_extract_csd_epochs_with_missing_epoch() {
515        let mut csds = BTreeMap::new();
516        csds.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());
517
518        extract_cardano_stake_distribution_epochs(&csds);
519    }
520
521    #[test]
522    fn test_extract_csd_epochs_with_empty_map() {
523        let csds = BTreeMap::new();
524
525        let epochs = extract_cardano_stake_distribution_epochs(&csds);
526
527        assert!(epochs.is_empty());
528    }
529}