Skip to main content

tempo_xtask/
generate_localnet.rs

1use std::{net::SocketAddr, path::PathBuf};
2
3use eyre::{OptionExt as _, WrapErr as _, ensure};
4use rand_08::SeedableRng as _;
5use reth_network_peers::pk2id;
6use secp256k1::SECP256K1;
7use serde::Serialize;
8
9use crate::genesis_args::GenesisArgs;
10
11/// Generates a config file to run a bunch of validators locally.
12///
13/// This includes generating a genesis.
14#[derive(Debug, clap::Parser)]
15pub(crate) struct GenerateLocalnet {
16    /// The target directory that will be populated with the
17    ///
18    /// If this directory exists but is not empty the operation will fail unless `--force`
19    /// is specified. In this case the target directory will be first cleaned.
20    #[arg(long, short, value_name = "DIR")]
21    output: PathBuf,
22
23    /// Whether to overwrite `output`.
24    #[arg(long)]
25    force: bool,
26
27    #[clap(flatten)]
28    genesis_args: GenesisArgs,
29}
30
31impl GenerateLocalnet {
32    pub(crate) async fn run(self) -> eyre::Result<()> {
33        let Self {
34            output,
35            force,
36            genesis_args,
37        } = self;
38
39        // Copy the seed here before genesis_args are consumed.
40        let seed = genesis_args.seed;
41
42        let (genesis, consensus_config) = genesis_args
43            .generate_genesis()
44            .await
45            .wrap_err("failed to generate genesis")?;
46
47        let consensus_config = consensus_config
48            .ok_or_eyre("no consensus config generated; did you provide --validators?")?;
49
50        std::fs::create_dir_all(&output).wrap_err_with(|| {
51            format!("failed creating target directory at `{}`", output.display())
52        })?;
53
54        if force {
55            eprintln!(
56                "--force was specified: deleting all files in target directory `{}`",
57                output.display()
58            );
59            // XXX: this first removes the directory and then recreates it. Small workaround
60            // so that one doesn't have to iterate through the entire thing recursively.
61            std::fs::remove_dir_all(&output)
62                .and_then(|_| std::fs::create_dir(&output))
63                .wrap_err_with(|| {
64                    format!("failed clearing target directory at `{}`", output.display())
65                })?;
66        } else {
67            let target_is_empty = std::fs::read_dir(&output)
68                .wrap_err_with(|| {
69                    format!(
70                        "failed reading target directory `{}` to determine if it is empty",
71                        output.display()
72                    )
73                })?
74                .next()
75                .is_none();
76            ensure!(
77                target_is_empty,
78                "target directory `{}` is not empty; delete all its contents or rerun command with --force",
79                output.display(),
80            );
81        }
82
83        let mut rng =
84            rand_08::rngs::StdRng::seed_from_u64(seed.unwrap_or_else(rand_08::random::<u64>));
85        let mut trusted_peers = vec![];
86
87        let mut all_configs = vec![];
88        for validator in &consensus_config.validators {
89            let (execution_p2p_signing_key, execution_p2p_identity) = {
90                let (sk, pk) = SECP256K1.generate_keypair(&mut rng);
91                (sk, pk2id(&pk))
92            };
93
94            let consensus_p2p_port = validator.addr.port();
95            let execution_p2p_port = consensus_p2p_port + 1;
96
97            trusted_peers.push(format!(
98                "enode://{execution_p2p_identity:x}@{}",
99                SocketAddr::new(validator.addr.ip(), execution_p2p_port),
100            ));
101
102            all_configs.push((
103                validator.clone(),
104                ConfigOutput {
105                    consensus_on_disk_signing_key: validator.signing_key.to_string(),
106                    consensus_on_disk_signing_share: validator.signing_share.to_string(),
107
108                    consensus_p2p_port,
109                    execution_p2p_port,
110
111                    execution_p2p_disc_key: execution_p2p_signing_key.display_secret().to_string(),
112                    execution_p2p_identity: format!("{execution_p2p_identity:x}"),
113                },
114            ));
115        }
116
117        let genesis_ser = serde_json::to_string_pretty(&genesis)
118            .wrap_err("failed serializing genesis as json")?;
119        let genesis_dst = output.join("genesis.json");
120        std::fs::write(&genesis_dst, &genesis_ser)
121            .wrap_err_with(|| format!("failed writing genesis to `{}`", genesis_dst.display()))?;
122
123        for (validator, config) in all_configs.into_iter() {
124            let target_dir = validator.dst_dir(&output);
125            std::fs::create_dir(&target_dir).wrap_err_with(|| {
126                format!(
127                    "failed creating target directory to store validator specific keys at `{}`",
128                    target_dir.display()
129                )
130            })?;
131
132            let signing_key_dst = validator.dst_signing_key(&output);
133            std::fs::write(&signing_key_dst, config.consensus_on_disk_signing_key).wrap_err_with(
134                || {
135                    format!(
136                        "failed writing signing key to `{}`",
137                        signing_key_dst.display()
138                    )
139                },
140            )?;
141            let signing_share_dst = validator.dst_signing_share(&output);
142            std::fs::write(&signing_share_dst, config.consensus_on_disk_signing_share)
143                .wrap_err_with(|| {
144                    format!(
145                        "failed writing signing share to `{}`",
146                        signing_share_dst.display()
147                    )
148                })?;
149            let enode_key_dst = validator.dst_dir(&output).join("enode.key");
150            std::fs::write(&enode_key_dst, config.execution_p2p_disc_key).wrap_err_with(|| {
151                format!("failed writing enode key to `{}`", enode_key_dst.display())
152            })?;
153            let enode_identity_dst = validator.dst_dir(&output).join("enode.identity");
154            std::fs::write(&enode_identity_dst, &config.execution_p2p_identity).wrap_err_with(
155                || {
156                    format!(
157                        "failed writing enode identity to `{}`",
158                        enode_identity_dst.display()
159                    )
160                },
161            )?;
162
163            println!("run the node with the following command:\n");
164            let cmd = format!(
165                "cargo run --bin tempo -- node \
166                \\\n--consensus.signing-key {signing_key} \
167                \\\n--consensus.signing-share {signing_share} \
168                \\\n--consensus.listen-address 127.0.0.1:{listen_port} \
169                \\\n--consensus.metrics-address 127.0.0.1:{metrics_port} \
170                \\\n--chain {genesis} \
171                \\\n--datadir {datadir} \
172                \\\n--trusted-peers {trusted_peers} \
173                \\\n--port {execution_p2p_port} \
174                \\\n--discovery.port {execution_p2p_port} \
175                \\\n--p2p-secret-key {execution_p2p_secret_key} \
176                \\\n--authrpc.port {authrpc_port}",
177                signing_key = signing_key_dst.display(),
178                signing_share = signing_share_dst.display(),
179                listen_port = config.consensus_p2p_port,
180                metrics_port = config.consensus_p2p_port + 2,
181                genesis = genesis_dst.display(),
182                datadir = target_dir.display(),
183                trusted_peers = trusted_peers.join(","),
184                execution_p2p_port = config.execution_p2p_port,
185                execution_p2p_secret_key = enode_key_dst.display(),
186                authrpc_port = config.execution_p2p_port + 2,
187            );
188            println!("{cmd}\n\n");
189        }
190        Ok(())
191    }
192}
193
194#[derive(Debug, Serialize)]
195pub(crate) struct ConfigOutput {
196    consensus_on_disk_signing_key: String,
197    consensus_on_disk_signing_share: String,
198    consensus_p2p_port: u16,
199    execution_p2p_port: u16,
200    execution_p2p_disc_key: String,
201    execution_p2p_identity: String,
202}