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