reth_cli_commands/p2p/
mod.rs

1//! P2P Debugging tool
2
3use std::{path::PathBuf, sync::Arc};
4
5use alloy_eips::BlockHashOrNumber;
6use backon::{ConstantBuilder, Retryable};
7use clap::{Parser, Subcommand};
8use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
9use reth_cli::chainspec::ChainSpecParser;
10use reth_cli_util::{get_secret_key, hash_or_num_value_parser};
11use reth_config::Config;
12use reth_network::{BlockDownloaderProvider, NetworkConfigBuilder, NetworkPrimitives};
13use reth_network_p2p::bodies::client::BodiesClient;
14use reth_node_core::{
15    args::{DatabaseArgs, DatadirArgs, NetworkArgs},
16    utils::get_single_header,
17};
18
19pub mod bootnode;
20mod rlpx;
21
22/// `reth p2p` command
23#[derive(Debug, Parser)]
24pub struct Command<C: ChainSpecParser> {
25    /// The path to the configuration file to use.
26    #[arg(long, value_name = "FILE", verbatim_doc_comment)]
27    config: Option<PathBuf>,
28
29    /// The chain this node is running.
30    ///
31    /// Possible values are either a built-in chain or the path to a chain specification file.
32    #[arg(
33        long,
34        value_name = "CHAIN_OR_PATH",
35        long_help = C::help_message(),
36        default_value = C::SUPPORTED_CHAINS[0],
37        value_parser = C::parser()
38    )]
39    chain: Arc<C::ChainSpec>,
40
41    /// The number of retries per request
42    #[arg(long, default_value = "5")]
43    retries: usize,
44
45    #[command(flatten)]
46    network: NetworkArgs,
47
48    #[command(flatten)]
49    datadir: DatadirArgs,
50
51    #[command(flatten)]
52    db: DatabaseArgs,
53
54    #[command(subcommand)]
55    command: Subcommands,
56}
57
58/// `reth p2p` subcommands
59#[derive(Subcommand, Debug)]
60pub enum Subcommands {
61    /// Download block header
62    Header {
63        /// The header number or hash
64        #[arg(value_parser = hash_or_num_value_parser)]
65        id: BlockHashOrNumber,
66    },
67    /// Download block body
68    Body {
69        /// The block number or hash
70        #[arg(value_parser = hash_or_num_value_parser)]
71        id: BlockHashOrNumber,
72    },
73    // RLPx utilities
74    Rlpx(rlpx::Command),
75}
76
77impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>> Command<C> {
78    /// Execute `p2p` command
79    pub async fn execute<N: NetworkPrimitives>(self) -> eyre::Result<()> {
80        let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
81        let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
82
83        // Load configuration
84        let mut config = Config::from_path(&config_path).unwrap_or_default();
85
86        config.peers.trusted_nodes.extend(self.network.trusted_peers.clone());
87
88        if config.peers.trusted_nodes.is_empty() && self.network.trusted_only {
89            eyre::bail!("No trusted nodes. Set trusted peer with `--trusted-peer <enode record>` or set `--trusted-only` to `false`")
90        }
91
92        config.peers.trusted_nodes_only = self.network.trusted_only;
93
94        let default_secret_key_path = data_dir.p2p_secret();
95        let secret_key_path =
96            self.network.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
97        let p2p_secret_key = get_secret_key(&secret_key_path)?;
98        let rlpx_socket = (self.network.addr, self.network.port).into();
99        let boot_nodes = self.chain.bootnodes().unwrap_or_default();
100
101        let net = NetworkConfigBuilder::<N>::new(p2p_secret_key)
102            .peer_config(config.peers_config_with_basic_nodes_from_file(None))
103            .external_ip_resolver(self.network.nat)
104            .disable_discv4_discovery_if(self.chain.chain().is_optimism())
105            .boot_nodes(boot_nodes.clone())
106            .apply(|builder| {
107                self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes)
108            })
109            .build_with_noop_provider(self.chain)
110            .manager()
111            .await?;
112        let network = net.handle().clone();
113        tokio::task::spawn(net);
114
115        let fetch_client = network.fetch_client().await?;
116        let retries = self.retries.max(1);
117        let backoff = ConstantBuilder::default().with_max_times(retries);
118
119        match self.command {
120            Subcommands::Header { id } => {
121                let header = (move || get_single_header(fetch_client.clone(), id))
122                    .retry(backoff)
123                    .notify(|err, _| println!("Error requesting header: {err}. Retrying..."))
124                    .await?;
125                println!("Successfully downloaded header: {header:?}");
126            }
127            Subcommands::Body { id } => {
128                let hash = match id {
129                    BlockHashOrNumber::Hash(hash) => hash,
130                    BlockHashOrNumber::Number(number) => {
131                        println!("Block number provided. Downloading header first...");
132                        let client = fetch_client.clone();
133                        let header = (move || {
134                            get_single_header(client.clone(), BlockHashOrNumber::Number(number))
135                        })
136                        .retry(backoff)
137                        .notify(|err, _| println!("Error requesting header: {err}. Retrying..."))
138                        .await?;
139                        header.hash()
140                    }
141                };
142                let (_, result) = (move || {
143                    let client = fetch_client.clone();
144                    client.get_block_bodies(vec![hash])
145                })
146                .retry(backoff)
147                .notify(|err, _| println!("Error requesting block: {err}. Retrying..."))
148                .await?
149                .split();
150                if result.len() != 1 {
151                    eyre::bail!(
152                        "Invalid number of headers received. Expected: 1. Received: {}",
153                        result.len()
154                    )
155                }
156                let body = result.into_iter().next().unwrap();
157                println!("Successfully downloaded body: {body:?}")
158            }
159            Subcommands::Rlpx(command) => {
160                command.execute().await?;
161            }
162        }
163
164        Ok(())
165    }
166    /// Returns the underlying chain being used to run this command
167    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
168        Some(&self.chain)
169    }
170}