Skip to main content

tempo_xtask/
generate_localnet.rs

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