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
11const LOCALNET_SIGNING_KEY_SECRET: &str = "tempo-localnet-signing-key-secret";
12
13#[derive(Debug, clap::Parser)]
17pub(crate) struct GenerateLocalnet {
18 #[arg(long, short, value_name = "DIR")]
23 output: PathBuf,
24
25 #[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 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 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}