tempo_xtask/
generate_localnet.rs1use 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#[derive(Debug, clap::Parser)]
15pub(crate) struct GenerateLocalnet {
16 #[arg(long, short, value_name = "DIR")]
21 output: PathBuf,
22
23 #[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 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 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}