Skip to main content

tempo_xtask/
shadowfork.rs

1use std::{
2    ffi::OsStr,
3    path::{Path, PathBuf},
4};
5
6use eyre::{OptionExt as _, WrapErr as _, eyre};
7
8pub(crate) const SHADOWFORK_SIGNING_KEY_SECRET: &str = "tempo-shadowfork-signing-key-secret";
9pub(crate) const SHADOW_CHAINSPEC_FILE: &str = "shadowfork-chain.json";
10pub(crate) const SHADOW_EPOCH: u64 = 1;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub(crate) struct SourceExecutionDataDir {
14    pub(crate) db_path: PathBuf,
15    pub(crate) static_files_path: PathBuf,
16}
17
18pub(crate) fn source_chain_cli_arg(
19    source_chain: &str,
20    source_chain_id: u64,
21) -> Option<&'static str> {
22    match source_chain.to_ascii_lowercase().as_str() {
23        "mainnet" | "presto" => Some("mainnet"),
24        "moderato" | "testnet" => Some("moderato"),
25        "dev" => Some("dev"),
26        _ if source_chain_id == 4217 => Some("mainnet"),
27        _ if source_chain_id == 42431 => Some("moderato"),
28        _ => None,
29    }
30}
31
32pub(crate) fn source_chain_id(source_chain: &str) -> Option<u64> {
33    match source_chain.to_ascii_lowercase().as_str() {
34        "mainnet" | "presto" => Some(4217),
35        "moderato" | "testnet" => Some(42431),
36        "dev" => Some(1337),
37        _ => None,
38    }
39}
40
41pub(crate) fn resolve_source_execution_data_dir(
42    path: &Path,
43    source_chain: &str,
44    source_chain_id: u64,
45) -> eyre::Result<SourceExecutionDataDir> {
46    let mut candidates = Vec::new();
47    if path.file_name() == Some(OsStr::new("db")) {
48        candidates.push(path.to_path_buf());
49    }
50    candidates.push(path.join("db"));
51    if let Some(chain_dir) = source_chain_cli_arg(source_chain, source_chain_id) {
52        candidates.push(path.join(chain_dir).join("db"));
53    }
54    candidates.push(path.join(source_chain_id.to_string()).join("db"));
55
56    for db_path in &candidates {
57        if db_path.exists() {
58            let static_files_path = db_path
59                .parent()
60                .ok_or_else(|| {
61                    eyre!(
62                        "execution database path `{}` has no parent directory",
63                        db_path.display(),
64                    )
65                })?
66                .join("static_files");
67            return Ok(SourceExecutionDataDir {
68                db_path: db_path.clone(),
69                static_files_path,
70            });
71        }
72    }
73
74    let candidates = candidates
75        .iter()
76        .map(|path| format!("`{}`", path.display()))
77        .collect::<Vec<_>>()
78        .join(", ");
79    Err(eyre!(
80        "could not find execution database under `{}`; looked for {candidates}",
81        path.display(),
82    ))
83}
84
85pub(crate) fn write_shadow_chainspec(
86    path: &Path,
87    source_chain: &str,
88    source_chain_id: u64,
89    shadow_epoch_length: u64,
90) -> eyre::Result<()> {
91    let mut genesis = source_genesis_json(source_chain, source_chain_id)?;
92    let config = genesis
93        .get_mut("config")
94        .and_then(serde_json::Value::as_object_mut)
95        .ok_or_eyre("source genesis JSON does not contain an object at `config`")?;
96    config.insert(
97        "epochLength".to_string(),
98        serde_json::Value::from(shadow_epoch_length),
99    );
100
101    let json = serde_json::to_string_pretty(&genesis)
102        .wrap_err("failed serializing shadow chainspec JSON")?;
103    std::fs::write(path, json)
104        .wrap_err_with(|| format!("failed writing shadow chainspec to `{}`", path.display()))
105}
106
107fn source_genesis_json(
108    source_chain: &str,
109    source_chain_id: u64,
110) -> eyre::Result<serde_json::Value> {
111    let genesis = match source_chain.to_ascii_lowercase().as_str() {
112        "mainnet" | "presto" => include_str!("../../crates/chainspec/src/genesis/presto.json"),
113        "moderato" | "testnet" => include_str!("../../crates/chainspec/src/genesis/moderato.json"),
114        "dev" => include_str!("../../crates/chainspec/src/genesis/dev.json"),
115        _ if source_chain_id == 4217 => {
116            include_str!("../../crates/chainspec/src/genesis/presto.json")
117        }
118        _ if source_chain_id == 42431 => {
119            include_str!("../../crates/chainspec/src/genesis/moderato.json")
120        }
121        _ => {
122            return Err(eyre!(
123                "cannot infer source chainspec for source_chain `{source_chain}` and chain id `{source_chain_id}`"
124            ));
125        }
126    };
127
128    serde_json::from_str(genesis).wrap_err("failed parsing bundled source genesis JSON")
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn maps_source_chain_to_execution_chain() {
137        assert_eq!(source_chain_cli_arg("mainnet", 0), Some("mainnet"));
138        assert_eq!(source_chain_cli_arg("presto", 0), Some("mainnet"));
139        assert_eq!(source_chain_cli_arg("moderato", 0), Some("moderato"));
140        assert_eq!(source_chain_cli_arg("testnet", 0), Some("moderato"));
141        assert_eq!(source_chain_cli_arg("dev", 0), Some("dev"));
142        assert_eq!(source_chain_cli_arg("custom", 4217), Some("mainnet"));
143        assert_eq!(source_chain_cli_arg("custom", 42431), Some("moderato"));
144        assert_eq!(source_chain_cli_arg("custom", 1), None);
145    }
146
147    #[test]
148    fn maps_source_chain_to_chain_id() {
149        assert_eq!(source_chain_id("mainnet"), Some(4217));
150        assert_eq!(source_chain_id("presto"), Some(4217));
151        assert_eq!(source_chain_id("moderato"), Some(42431));
152        assert_eq!(source_chain_id("testnet"), Some(42431));
153        assert_eq!(source_chain_id("dev"), Some(1337));
154        assert_eq!(source_chain_id("custom"), None);
155    }
156}