Skip to main content

reth_cli_commands/p2p/
mod.rs

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