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