reth_cli_commands/p2p/
mod.rs1use 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#[derive(Debug, Parser)]
24pub struct Command<C: ChainSpecParser> {
25 #[arg(long, value_name = "FILE", verbatim_doc_comment)]
27 config: Option<PathBuf>,
28
29 #[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 #[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#[derive(Subcommand, Debug)]
60pub enum Subcommands {
61 Header {
63 #[arg(value_parser = hash_or_num_value_parser)]
65 id: BlockHashOrNumber,
66 },
67 Body {
69 #[arg(value_parser = hash_or_num_value_parser)]
71 id: BlockHashOrNumber,
72 },
73 Rlpx(rlpx::Command),
75}
76
77impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>> Command<C> {
78 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 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!(
90 "No trusted nodes. Set trusted peer with `--trusted-peer <enode record>` or set `--trusted-only` to `false`"
91 )
92 }
93
94 config.peers.trusted_nodes_only = self.network.trusted_only;
95
96 let default_secret_key_path = data_dir.p2p_secret();
97 let secret_key_path =
98 self.network.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
99 let p2p_secret_key = get_secret_key(&secret_key_path)?;
100 let rlpx_socket = (self.network.addr, self.network.port).into();
101 let boot_nodes = self.chain.bootnodes().unwrap_or_default();
102
103 let net = NetworkConfigBuilder::<N>::new(p2p_secret_key)
104 .peer_config(config.peers_config_with_basic_nodes_from_file(None))
105 .external_ip_resolver(self.network.nat)
106 .disable_discv4_discovery_if(self.chain.chain().is_optimism())
107 .boot_nodes(boot_nodes.clone())
108 .apply(|builder| {
109 self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes)
110 })
111 .build_with_noop_provider(self.chain)
112 .manager()
113 .await?;
114 let network = net.handle().clone();
115 tokio::task::spawn(net);
116
117 let fetch_client = network.fetch_client().await?;
118 let retries = self.retries.max(1);
119 let backoff = ConstantBuilder::default().with_max_times(retries);
120
121 match self.command {
122 Subcommands::Header { id } => {
123 let header = (move || get_single_header(fetch_client.clone(), id))
124 .retry(backoff)
125 .notify(|err, _| println!("Error requesting header: {err}. Retrying..."))
126 .await?;
127 println!("Successfully downloaded header: {header:?}");
128 }
129 Subcommands::Body { id } => {
130 let hash = match id {
131 BlockHashOrNumber::Hash(hash) => hash,
132 BlockHashOrNumber::Number(number) => {
133 println!("Block number provided. Downloading header first...");
134 let client = fetch_client.clone();
135 let header = (move || {
136 get_single_header(client.clone(), BlockHashOrNumber::Number(number))
137 })
138 .retry(backoff)
139 .notify(|err, _| println!("Error requesting header: {err}. Retrying..."))
140 .await?;
141 header.hash()
142 }
143 };
144 let (_, result) = (move || {
145 let client = fetch_client.clone();
146 client.get_block_bodies(vec![hash])
147 })
148 .retry(backoff)
149 .notify(|err, _| println!("Error requesting block: {err}. Retrying..."))
150 .await?
151 .split();
152 if result.len() != 1 {
153 eyre::bail!(
154 "Invalid number of headers received. Expected: 1. Received: {}",
155 result.len()
156 )
157 }
158 let body = result.into_iter().next().unwrap();
159 println!("Successfully downloaded body: {body:?}")
160 }
161 Subcommands::Rlpx(command) => {
162 command.execute().await?;
163 }
164 }
165
166 Ok(())
167 }
168}
169
170impl<C: ChainSpecParser> Command<C> {
171 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
173 Some(&self.chain)
174 }
175}