reth_optimism_chainspec/superchain/
configs.rs

1use crate::superchain::chain_metadata::{to_genesis_chain_config, ChainMetadata};
2use alloc::{
3    format,
4    string::{String, ToString},
5    vec::Vec,
6};
7use alloy_genesis::Genesis;
8use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit;
9use tar_no_std::{CorruptDataError, TarArchiveRef};
10
11/// A genesis file can be up to 10MiB. This is a reasonable limit for the genesis file size.
12const MAX_GENESIS_SIZE: usize = 16 * 1024 * 1024; // 16MiB
13
14/// The tar file contains the chain configs and genesis files for all chains.
15const SUPER_CHAIN_CONFIGS_TAR_BYTES: &[u8] = include_bytes!("../../res/superchain-configs.tar");
16
17#[derive(Debug, thiserror::Error)]
18pub(crate) enum SuperchainConfigError {
19    #[error("Error reading archive due to corrupt data: {0}")]
20    CorruptDataError(CorruptDataError),
21    #[error("Error converting bytes to UTF-8 String: {0}")]
22    FromUtf8Error(#[from] alloc::string::FromUtf8Error),
23    #[error("Error reading file: {0}")]
24    Utf8Error(#[from] core::str::Utf8Error),
25    #[error("Error deserializing JSON: {0}")]
26    JsonError(#[from] serde_json::Error),
27    #[error("File {0} not found in archive")]
28    FileNotFound(String),
29    #[error("Error decompressing file: {0}")]
30    DecompressError(String),
31}
32
33/// Reads the [`Genesis`] from the superchain config tar file for a superchain.
34/// For example, `read_genesis_from_superchain_config("unichain", "mainnet")`.
35pub(crate) fn read_superchain_genesis(
36    name: &str,
37    environment: &str,
38) -> Result<Genesis, SuperchainConfigError> {
39    // Open the archive.
40    let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES)
41        .map_err(SuperchainConfigError::CorruptDataError)?;
42    // Read and decompress the genesis file.
43    let compressed_genesis_file =
44        read_file(&archive, &format!("genesis/{environment}/{name}.json.zz"))?;
45    let genesis_file =
46        decompress_to_vec_zlib_with_limit(&compressed_genesis_file, MAX_GENESIS_SIZE)
47            .map_err(|e| SuperchainConfigError::DecompressError(format!("{e}")))?;
48
49    // Load the genesis file.
50    let mut genesis: Genesis = serde_json::from_slice(&genesis_file)?;
51
52    // The "config" field is stripped (see fetch_superchain_config.sh) from the genesis file
53    // because it is not always populated. For that reason, we read the config from the chain
54    // metadata file. See: https://github.com/ethereum-optimism/superchain-registry/issues/901
55    genesis.config =
56        to_genesis_chain_config(&read_superchain_metadata(name, environment, &archive)?);
57
58    Ok(genesis)
59}
60
61/// Reads the [`ChainMetadata`] from the superchain config tar file for a superchain.
62/// For example, `read_superchain_config("unichain", "mainnet")`.
63fn read_superchain_metadata(
64    name: &str,
65    environment: &str,
66    archive: &TarArchiveRef<'_>,
67) -> Result<ChainMetadata, SuperchainConfigError> {
68    let config_file = read_file(archive, &format!("configs/{environment}/{name}.json"))?;
69    let config_content = String::from_utf8(config_file)?;
70    let chain_config: ChainMetadata = serde_json::from_str(&config_content)?;
71    Ok(chain_config)
72}
73
74/// Reads a file from the tar archive. The file path is relative to the root of the tar archive.
75fn read_file(
76    archive: &TarArchiveRef<'_>,
77    file_path: &str,
78) -> Result<Vec<u8>, SuperchainConfigError> {
79    for entry in archive.entries() {
80        if entry.filename().as_str()? == file_path {
81            return Ok(entry.data().to_vec())
82        }
83    }
84    Err(SuperchainConfigError::FileNotFound(file_path.to_string()))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::superchain::Superchain;
91    use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
92    use tar_no_std::TarArchiveRef;
93
94    #[test]
95    fn test_read_superchain_genesis() {
96        let genesis = read_superchain_genesis("unichain", "mainnet").unwrap();
97        assert_eq!(genesis.config.chain_id, 130);
98        assert_eq!(genesis.timestamp, 1730748359);
99        assert!(genesis.alloc.contains_key(&ADDRESS_L2_TO_L1_MESSAGE_PASSER));
100    }
101
102    #[test]
103    fn test_read_superchain_genesis_with_workaround() {
104        let genesis = read_superchain_genesis("funki", "mainnet").unwrap();
105        assert_eq!(genesis.config.chain_id, 33979);
106        assert_eq!(genesis.timestamp, 1721211095);
107        assert!(genesis.alloc.contains_key(&ADDRESS_L2_TO_L1_MESSAGE_PASSER));
108    }
109
110    #[test]
111    fn test_read_superchain_metadata() {
112        let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES).unwrap();
113        let chain_config = read_superchain_metadata("funki", "mainnet", &archive).unwrap();
114        assert_eq!(chain_config.chain_id, 33979);
115    }
116
117    #[test]
118    fn test_read_all_genesis_files() {
119        let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES).unwrap();
120        // Check that all genesis files can be read without errors.
121        for entry in archive.entries() {
122            let filename = entry
123                .filename()
124                .as_str()
125                .unwrap()
126                .split('/')
127                .map(|s| s.to_string())
128                .collect::<Vec<String>>();
129            if filename.first().unwrap().ne(&"genesis") {
130                continue
131            }
132            read_superchain_metadata(
133                &filename.get(2).unwrap().replace(".json.zz", ""),
134                filename.get(1).unwrap(),
135                &archive,
136            )
137            .unwrap();
138        }
139    }
140
141    #[test]
142    fn test_genesis_exists_for_all_available_chains() {
143        for &chain in Superchain::ALL {
144            let genesis = read_superchain_genesis(chain.name(), chain.environment());
145            assert!(
146                genesis.is_ok(),
147                "Genesis not found for chain: {}-{}",
148                chain.name(),
149                chain.environment()
150            );
151        }
152    }
153}