reth_cli_commands/p2p/
mod.rs1use std::{path::PathBuf, sync::Arc};
4
5use crate::common::CliNodeTypes;
6use alloy_eips::BlockHashOrNumber;
7use backon::{ConstantBuilder, Retryable};
8use clap::{Parser, Subcommand};
9use eyre::WrapErr;
10use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
11use reth_cli::chainspec::ChainSpecParser;
12use reth_cli_util::hash_or_num_value_parser;
13use reth_config::Config;
14use reth_network::{BlockDownloaderProvider, NetworkConfigBuilder};
15use reth_network_p2p::bodies::client::BodiesClient;
16use reth_node_core::{
17 args::{DatadirArgs, NetworkArgs},
18 utils::get_single_header,
19};
20use reth_primitives_traits::BlockBody;
21use reth_tasks::Runtime;
22
23pub mod bootnode;
24pub mod enode;
25pub mod rlpx;
26
27#[derive(Debug, Parser)]
29pub struct Command<C: ChainSpecParser> {
30 #[command(subcommand)]
31 command: Subcommands<C>,
32}
33
34impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>> Command<C> {
35 pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
37 match self.command {
38 Subcommands::Header { args, id } => {
39 let handle = args.launch_network::<N>().await?;
40 let fetch_client = handle.fetch_client().await?;
41 let backoff = args.backoff();
42
43 let header = (move || get_single_header(fetch_client.clone(), id))
44 .retry(backoff)
45 .notify(|err, _| tracing::warn!(target: "reth::cli", error = %err, "Error requesting header. Retrying..."))
46 .await?;
47 tracing::info!(target: "reth::cli", ?header, "Successfully downloaded header");
48 }
49
50 Subcommands::Body { args, id } => {
51 let handle = args.launch_network::<N>().await?;
52 let fetch_client = handle.fetch_client().await?;
53 let backoff = args.backoff();
54
55 let hash = match id {
56 BlockHashOrNumber::Hash(hash) => hash,
57 BlockHashOrNumber::Number(number) => {
58 tracing::info!(target: "reth::cli", "Block number provided. Downloading header first...");
59 let client = fetch_client.clone();
60 let header = (move || {
61 get_single_header(client.clone(), BlockHashOrNumber::Number(number))
62 })
63 .retry(backoff)
64 .notify(|err, _| tracing::warn!(target: "reth::cli", error = %err, "Error requesting header. Retrying..."))
65 .await?;
66 header.hash()
67 }
68 };
69 let (_, result) = (move || {
70 let client = fetch_client.clone();
71 client.get_block_bodies(vec![hash])
72 })
73 .retry(backoff)
74 .notify(|err, _| tracing::warn!(target: "reth::cli", error = %err, "Error requesting block. Retrying..."))
75 .await?
76 .split();
77 if result.len() != 1 {
78 eyre::bail!(
79 "Invalid number of bodies received. Expected: 1. Received: {}",
80 result.len()
81 );
82 }
83 let body = result.into_iter().next().unwrap();
84 tracing::info!(target: "reth::cli", %hash, txs = body.transaction_count(), "Successfully downloaded body")
85 }
86 Subcommands::Rlpx(command) => {
87 command.execute().await?;
88 }
89 Subcommands::Bootnode(command) => {
90 command.execute().await?;
91 }
92 Subcommands::Enode(command) => {
93 command.execute()?;
94 }
95 }
96
97 Ok(())
98 }
99}
100
101impl<C: ChainSpecParser> Command<C> {
102 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
104 match &self.command {
105 Subcommands::Header { args, .. } => Some(&args.chain),
106 Subcommands::Body { args, .. } => Some(&args.chain),
107 Subcommands::Rlpx(_) => None,
108 Subcommands::Bootnode(_) => None,
109 Subcommands::Enode(_) => None,
110 }
111 }
112}
113
114#[derive(Subcommand, Debug)]
116pub enum Subcommands<C: ChainSpecParser> {
117 Header {
119 #[command(flatten)]
120 args: DownloadArgs<C>,
121 #[arg(value_parser = hash_or_num_value_parser)]
123 id: BlockHashOrNumber,
124 },
125 Body {
127 #[command(flatten)]
128 args: DownloadArgs<C>,
129 #[arg(value_parser = hash_or_num_value_parser)]
131 id: BlockHashOrNumber,
132 },
133 Rlpx(rlpx::Command),
135 Bootnode(bootnode::Command),
137 Enode(enode::Command),
139}
140
141#[derive(Debug, Clone, Parser)]
142pub struct DownloadArgs<C: ChainSpecParser> {
143 #[arg(long, default_value = "5")]
145 retries: usize,
146
147 #[command(flatten)]
148 network: NetworkArgs,
149
150 #[command(flatten)]
151 datadir: DatadirArgs,
152
153 #[arg(long, value_name = "FILE", verbatim_doc_comment)]
155 config: Option<PathBuf>,
156
157 #[arg(
161 long,
162 value_name = "CHAIN_OR_PATH",
163 long_help = C::help_message(),
164 default_value = C::default_value(),
165 value_parser = C::parser()
166 )]
167 chain: Arc<C::ChainSpec>,
168}
169
170impl<C: ChainSpecParser> DownloadArgs<C> {
171 pub async fn launch_network<N>(
173 &self,
174 ) -> eyre::Result<reth_network::NetworkHandle<N::NetworkPrimitives>>
175 where
176 C::ChainSpec: EthChainSpec + Hardforks + EthereumHardforks + 'static,
177 N: CliNodeTypes<ChainSpec = C::ChainSpec>,
178 {
179 let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
180 let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
181
182 let mut config = Config::from_path(&config_path)
184 .wrap_err_with(|| format!("Could not load config file {config_path:?}"))?;
185
186 config.peers.trusted_nodes.extend(self.network.trusted_peers.clone());
187
188 if config.peers.trusted_nodes.is_empty() && self.network.trusted_only {
189 eyre::bail!(
190 "No trusted nodes. Set trusted peer with `--trusted-peer <enode record>` or set `--trusted-only` to `false`"
191 );
192 }
193
194 config.peers.trusted_nodes_only |= self.network.trusted_only;
195
196 let default_secret_key_path = data_dir.p2p_secret();
197 let p2p_secret_key = self.network.secret_key(default_secret_key_path)?;
198 let rlpx_socket = (self.network.addr, self.network.port).into();
199 let boot_nodes = self
200 .network
201 .resolved_bootnodes()
202 .unwrap_or_else(|| self.chain.bootnodes().unwrap_or_default());
203
204 let net =
205 NetworkConfigBuilder::<N::NetworkPrimitives>::new(p2p_secret_key, Runtime::test())
206 .peer_config(config.peers_config_with_basic_nodes_from_file(None))
207 .sessions_config(config.sessions)
208 .external_ip_resolver(self.network.nat.clone())
209 .network_id(self.network.network_id)
210 .boot_nodes(boot_nodes.clone())
211 .apply(|builder| {
212 self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes)
213 })
214 .build_with_noop_provider(self.chain.clone())
215 .manager()
216 .await?;
217 let handle = net.handle().clone();
218 tokio::task::spawn(net);
219
220 Ok(handle)
221 }
222
223 pub fn backoff(&self) -> ConstantBuilder {
224 ConstantBuilder::default().with_max_times(self.retries.max(1))
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
232
233 #[test]
234 fn parse_header_cmd() {
235 let _args: Command<EthereumChainSpecParser> =
236 Command::parse_from(["reth", "header", "--chain", "mainnet", "1000"]);
237 }
238
239 #[test]
240 fn parse_body_cmd() {
241 let _args: Command<EthereumChainSpecParser> =
242 Command::parse_from(["reth", "body", "--chain", "mainnet", "1000"]);
243 }
244
245 #[test]
246 fn parse_enode_cmd() {
247 let _args: Command<EthereumChainSpecParser> =
248 Command::parse_from(["reth", "enode", "/tmp/secret"]);
249 }
250
251 #[test]
252 fn parse_enode_cmd_with_ip() {
253 let _args: Command<EthereumChainSpecParser> =
254 Command::parse_from(["reth", "enode", "/tmp/secret", "--ip", "192.168.1.1"]);
255 }
256}