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