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