tempo_xtask/
generate_localnet.rs1use std::{net::SocketAddr, path::PathBuf};
2
3use alloy_primitives::Address;
4use eyre::{OptionExt as _, WrapErr as _, ensure};
5use rand::SeedableRng as _;
6use reth_network_peers::pk2id;
7use secp256k1::SECP256K1;
8use serde::Serialize;
9
10use crate::genesis_args::GenesisArgs;
11
12#[derive(Debug, clap::Parser)]
16pub(crate) struct GenerateLocalnet {
17 #[arg(long, short, value_name = "DIR")]
22 output: PathBuf,
23
24 #[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 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 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 = rand::rngs::StdRng::seed_from_u64(seed.unwrap_or_else(rand::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 },
113 ));
114 }
115
116 let genesis_ser = serde_json::to_string_pretty(&genesis)
117 .wrap_err("failed serializing genesis as json")?;
118 let genesis_dst = output.join("genesis.json");
119 std::fs::write(&genesis_dst, &genesis_ser)
120 .wrap_err_with(|| format!("failed writing genesis to `{}`", genesis_dst.display()))?;
121
122 for (validator, config) in all_configs.into_iter() {
123 let target_dir = validator.dst_dir(&output);
124 std::fs::create_dir(&target_dir).wrap_err_with(|| {
125 format!(
126 "failed creating target directory to store validator specifici keys at `{}`",
127 &target_dir.display()
128 )
129 })?;
130
131 let signing_key_dst = validator.dst_signing_key(&output);
132 std::fs::write(&signing_key_dst, config.consensus_on_disk_signing_key).wrap_err_with(
133 || {
134 format!(
135 "failed writing signing key to `{}`",
136 signing_key_dst.display()
137 )
138 },
139 )?;
140 let signing_share_dst = validator.dst_signing_share(&output);
141 std::fs::write(&signing_share_dst, config.consensus_on_disk_signing_share)
142 .wrap_err_with(|| {
143 format!(
144 "failed writing signing share to `{}`",
145 signing_share_dst.display()
146 )
147 })?;
148 let enode_key_dst = validator.dst_dir(&output).join("enode.key");
149 std::fs::write(&enode_key_dst, config.execution_p2p_disc_key).wrap_err_with(|| {
150 format!(
151 "failed writing signing share to `{}`",
152 enode_key_dst.display()
153 )
154 })?;
155
156 println!("run the node with the following command:\n");
157 let cmd = format!(
158 "cargo run --bin tempo -- node \
159 \\\n--consensus.signing-key {signing_key} \
160 \\\n--consensus.signing-share {signing_share} \
161 \\\n--consensus.listen-address 127.0.0.1:{listen_port} \
162 \\\n--consensus.metrics-address 127.0.0.1:{metrics_port} \
163 \\\n--chain {genesis} \
164 \\\n--datadir {datadir} \
165 \\\n--trusted-peers {trusted_peers} \
166 \\\n--port {execution_p2p_port} \
167 \\\n--discovery.port {execution_p2p_port} \
168 \\\n--p2p-secret-key {execution_p2p_secret_key} \
169 \\\n--authrpc.port {authrpc_port} \
170 \\\n--consensus.fee-recipient {fee_recipient}",
171 signing_key = signing_key_dst.display(),
172 signing_share = signing_share_dst.display(),
173 listen_port = config.consensus_p2p_port,
174 metrics_port = config.consensus_p2p_port + 2,
175 genesis = genesis_dst.display(),
176 datadir = target_dir.display(),
177 trusted_peers = trusted_peers.join(","),
178 execution_p2p_port = config.execution_p2p_port,
179 execution_p2p_secret_key = enode_key_dst.display(),
180 fee_recipient = Address::ZERO,
181 authrpc_port = config.execution_p2p_port + 2,
182 );
183 println!("{cmd}\n\n");
184 }
185 Ok(())
186 }
187}
188
189#[derive(Debug, Serialize)]
190pub(crate) struct ConfigOutput {
191 consensus_on_disk_signing_key: String,
192 consensus_on_disk_signing_share: String,
193 consensus_p2p_port: u16,
194 execution_p2p_port: u16,
195 execution_p2p_disc_key: String,
196}