Skip to main content

tempo_xtask/
generate_devnet.rs

1use std::{net::SocketAddr, path::PathBuf};
2
3use alloy_primitives::Address;
4use eyre::{Context, OptionExt as _, ensure};
5use rand_08::SeedableRng as _;
6use reth_network_peers::pk2id;
7use secp256k1::SECP256K1;
8use serde::Serialize;
9
10use crate::genesis_args::GenesisArgs;
11
12/// Generates a config file to run a bunch of validators locally.
13///
14/// This includes generating a genesis.
15#[derive(Debug, clap::Parser)]
16pub(crate) struct GenerateDevnet {
17    /// The target directory that will be populated with the
18    ///
19    /// If this directory exists but is not empty the operation will fail unless `--force`
20    /// is specified. In this case the target directory will be first cleaned.
21    #[arg(long, short, value_name = "DIR")]
22    output: PathBuf,
23
24    /// Whether to overwrite `output`.
25    #[arg(long)]
26    force: bool,
27
28    #[arg(long)]
29    image_tag: String,
30
31    /// The URL at which genesis will be found.
32    #[arg(long)]
33    genesis_url: String,
34
35    #[clap(flatten)]
36    genesis_args: GenesisArgs,
37}
38
39impl GenerateDevnet {
40    pub(crate) async fn run(self) -> eyre::Result<()> {
41        let Self {
42            output,
43            force,
44            image_tag,
45            genesis_url,
46            genesis_args,
47        } = self;
48
49        let seed = genesis_args.seed;
50        let (genesis, consensus_config) = genesis_args
51            .generate_genesis()
52            .await
53            .wrap_err("failed to generate genesis")?;
54
55        let consensus_config = consensus_config
56            .ok_or_eyre("no consensus config generated; did you provide --validators?")?;
57
58        std::fs::create_dir_all(&output).wrap_err_with(|| {
59            format!("failed creating target directory at `{}`", output.display())
60        })?;
61
62        if force {
63            eprintln!(
64                "--force was specified: deleting all files in target directory `{}`",
65                output.display()
66            );
67            // XXX: this first removes the directory and then recreates it. Small workaround
68            // so that one doesn't have to iterate through the entire thing recursively.
69            std::fs::remove_dir_all(&output)
70                .and_then(|_| std::fs::create_dir(&output))
71                .wrap_err_with(|| {
72                    format!("failed clearing target directory at `{}`", output.display())
73                })?;
74        } else {
75            let target_is_empty = std::fs::read_dir(&output)
76                .wrap_err_with(|| {
77                    format!(
78                        "failed reading target directory `{}` to determine if it is empty",
79                        output.display()
80                    )
81                })?
82                .next()
83                .is_none();
84            ensure!(
85                target_is_empty,
86                "target directory `{}` is not empty; delete all its contents or rerun command with --force",
87                output.display(),
88            );
89        }
90
91        let mut rng =
92            rand_08::rngs::StdRng::seed_from_u64(seed.unwrap_or_else(rand_08::random::<u64>));
93        let mut execution_peers = vec![];
94
95        let devmode = consensus_config.validators.len() == 1;
96
97        let mut all_configs = vec![];
98        for validator in consensus_config.validators {
99            let (execution_p2p_signing_key, execution_p2p_identity) = {
100                let (sk, pk) = SECP256K1.generate_keypair(&mut rng);
101                (sk, pk2id(&pk))
102            };
103
104            let consensus_p2p_port = validator.addr.port();
105            let execution_p2p_port = consensus_p2p_port + 1;
106            let consensus_metrics_port = consensus_p2p_port + 2;
107
108            execution_peers.push(format!(
109                "enode://{execution_p2p_identity:x}@{}",
110                SocketAddr::new(validator.addr.ip(), execution_p2p_port),
111            ));
112
113            all_configs.push((
114                validator.clone(),
115                ConfigOutput {
116                    execution_genesis_url: genesis_url.clone(),
117
118                    devmode,
119                    node_image_tag: image_tag.clone(),
120
121                    consensus_on_disk_signing_key: validator.signing_key.to_string(),
122                    consensus_on_disk_signing_share: validator.signing_share.to_string(),
123
124                    // FIXME(janis): this should not be zero
125                    consensus_fee_recipient: Address::ZERO,
126
127                    consensus_p2p_port,
128                    consensus_metrics_port,
129                    execution_p2p_port,
130
131                    execution_p2p_disc_key: execution_p2p_signing_key.display_secret().to_string(),
132
133                    // set in next loop, before writing.
134                    execution_peers: vec![],
135                },
136            ));
137
138            println!("created a config for validator `{}`", validator.addr);
139        }
140
141        for (validator, mut config) in all_configs {
142            config.execution_peers = execution_peers.clone();
143            let config_json = serde_json::to_string_pretty(&config)
144                .wrap_err("failed to convert config to json")?;
145            // TODO: use Path::with_added_extension once we are on 1.91
146            let dst = output.join(format!("{}.json", validator.addr));
147            std::fs::write(&dst, config_json).wrap_err_with(|| {
148                format!("failed to write deployment config to `{}`", dst.display())
149            })?;
150            println!("wrote config to `{}`", dst.display());
151        }
152        eprintln!("config files written");
153
154        let genesis_ser = serde_json::to_string_pretty(&genesis)
155            .wrap_err("failed serializing genesis as json")?;
156        let dst = output.join("genesis.json");
157        std::fs::write(&dst, &genesis_ser)
158            .wrap_err_with(|| format!("failed writing genesis to `{}`", dst.display()))?;
159        println!("wrote genesis to `{}`", dst.display());
160        Ok(())
161    }
162}
163
164#[derive(Debug, Serialize)]
165pub(crate) struct ConfigOutput {
166    devmode: bool,
167    consensus_on_disk_signing_key: String,
168    consensus_on_disk_signing_share: String,
169    consensus_p2p_port: u16,
170    consensus_fee_recipient: Address,
171    consensus_metrics_port: u16,
172    node_image_tag: String,
173    execution_genesis_url: String,
174    execution_p2p_port: u16,
175    execution_peers: Vec<String>,
176    execution_p2p_disc_key: String,
177}