Skip to main content

ethrex_common/
genesis_utils.rs

1use crate::types::Genesis;
2use serde_json::{Map, Value};
3use std::path::Path;
4
5fn sort_config(genesis_map: &Map<String, Value>) -> Result<Map<String, Value>, String> {
6    let config_keys_order = [
7        "chainId",
8        "homesteadBlock",
9        "daoForkBlock",
10        "daoForkSupport",
11        "eip150Block",
12        "eip150Hash",
13        "eip155Block",
14        "eip158Block",
15        "byzantiumBlock",
16        "constantinopleBlock",
17        "petersburgBlock",
18        "istanbulBlock",
19        "muirGlacierBlock",
20        "berlinBlock",
21        "londonBlock",
22        "arrowGlacierBlock",
23        "grayGlacierBlock",
24        "terminalTotalDifficulty",
25        "terminalTotalDifficultyPassed",
26        "shanghaiTime",
27        "cancunTime",
28        "pragueTime",
29        "osakaTime",
30        "bpo1Time",
31        "bpo2Time",
32        "bpo3Time",
33        "bpo4Time",
34        "bpo5Time",
35        "amsterdamTime",
36        "hegotaTime",
37        "lstarTime",
38        "verkleTime",
39        "ethash",
40        "depositContractAddress",
41        "blobSchedule",
42        "mergeNetsplitBlock",
43        "enableVerkleAtGenesis",
44    ];
45    let Value::Object(config) = genesis_map
46        .get("config")
47        .ok_or_else(|| "Genesis file is missing config".to_owned())?
48    else {
49        return Err("Genesis file config is not a json object".to_owned());
50    };
51    let mut ordered_config: Map<String, Value> = Map::new();
52    for key in config_keys_order {
53        // If a key is not present in the config, this means
54        // we're reading a genesis file that simply does not support
55        // a certain configuration from the genesis block,
56        // so we simply ignore it.
57        if let Some(value) = config.get(key)
58            && *value != Value::Null
59        {
60            ordered_config.insert(key.to_owned(), value.clone());
61        };
62    }
63    // Check we're not missing any keys before returning
64    for key in config.keys() {
65        if ordered_config.get(key).is_none() && config.get(key) != Some(&Value::Null) {
66            return Err(format!("Missing key in sorted config: {key}"));
67        }
68    }
69    Ok(ordered_config)
70}
71
72pub fn write_genesis_as_json(genesis: Genesis, path: &Path) -> Result<(), String> {
73    let genesis_json = serde_json::to_string(&genesis)
74        .map_err(|e| format!("Could not convert genesis to string: {e}"))?;
75    let genesis_as_map: Map<String, Value> = serde_json::from_str(&genesis_json)
76        .map_err(|e| format!("Failed to de-serialize genesis file: {e}"))?;
77    // Keys sorting based off this ethpandaops example:
78    // https://github.com/ethpandaops/ethereum-genesis-generator/blob/master/apps/el-gen/mainnet/genesis.json
79    // We actually want 'config' as the first key, but we sort that
80    // separately.
81    let keys = [
82        "nonce",
83        "timestamp",
84        "extraData",
85        "gasLimit",
86        "difficulty",
87        "mixHash",
88        "coinbase",
89        "alloc",
90    ];
91    let ordered_config = sort_config(&genesis_as_map)?;
92    // Some keys that are in our genesis file,
93    // but are not in the example above or
94    // viceversa.
95    let optional_keys = [
96        "number",
97        "gasUsed",
98        "parentHash",
99        "baseFeePerGas",
100        "excessBlobGas",
101        "requestsHash",
102        "blobGasUsed",
103    ];
104    // This map will preserve insertion order because this crate uses the 'preserve_order'
105    // feature from serde_json.
106    let mut ordered_map: Map<String, Value> = serde_json::Map::new();
107    ordered_map.insert(
108        "config".to_owned(),
109        serde_json::Value::Object(ordered_config),
110    );
111    for k in keys {
112        let Some(v) = genesis_as_map.get(k) else {
113            return Err(format!("Missing key in read genesis file: {k}"));
114        };
115        if *v != Value::Null {
116            ordered_map.insert(k.to_owned(), v.clone());
117        }
118    }
119    for k in optional_keys {
120        if let Some(v) = genesis_as_map.get(k)
121            && *v != Value::Null
122        {
123            ordered_map.insert(k.to_owned(), v.clone().take());
124        }
125    }
126    // Check 1: check we're not missing any keys.
127    for k in genesis_as_map.keys() {
128        let expected = genesis_as_map.get(k);
129        if expected != Some(&Value::Null)
130            && ordered_map.contains_key(k) != genesis_as_map.contains_key(k)
131        {
132            return Err(format!("Genesis serialization is missing a key: {k}"));
133        }
134    }
135    // Check 2: the new ordered map can be turned into a genesis struct.
136    let _: Genesis = serde_json::from_value(ordered_map.clone().into())
137        .map_err(|e| format!("Error turning into genesis: {e}"))?;
138    let to_write = serde_json::to_string_pretty(&ordered_map)
139        .map_err(|e| format!("Could not turn map into json: {e}"))?;
140    std::fs::write(path, &to_write).map_err(|e| {
141        format!(
142            "Could not write genesis json to path: {}, error: {e}",
143            path.display()
144        )
145    })
146}