1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EnclaveArgs, NetworkArgs,
6 PayloadBuilderArgs, PruningArgs, RpcServerArgs, TxPoolArgs,
7 },
8 dirs::{ChainPath, DataDirPath},
9 utils::get_single_header,
10};
11use alloy_consensus::BlockHeader;
12use alloy_eips::BlockHashOrNumber;
13use alloy_primitives::{BlockNumber, B256};
14use eyre::eyre;
15use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
16use reth_config::config::PruneConfig;
17use reth_ethereum_forks::Head;
18use reth_network_p2p::headers::client::HeadersClient;
19use reth_primitives_traits::SealedHeader;
20use reth_stages_types::StageId;
21use reth_storage_api::{
22 BlockHashReader, DatabaseProviderFactory, HeaderProvider, StageCheckpointReader,
23};
24use reth_storage_errors::provider::ProviderResult;
25use serde::{de::DeserializeOwned, Serialize};
26use std::{
27 fs,
28 net::SocketAddr,
29 path::{Path, PathBuf},
30 sync::Arc,
31};
32use tracing::*;
33
34#[derive(Debug)]
80pub struct NodeConfig<ChainSpec> {
81 pub datadir: DatadirArgs,
83
84 pub config: Option<PathBuf>,
86
87 pub chain: Arc<ChainSpec>,
91
92 pub metrics: Option<SocketAddr>,
96
97 pub instance: u16,
112
113 pub network: NetworkArgs,
115
116 pub rpc: RpcServerArgs,
118
119 pub txpool: TxPoolArgs,
121
122 pub builder: PayloadBuilderArgs,
124
125 pub debug: DebugArgs,
127
128 pub db: DatabaseArgs,
130
131 pub dev: DevArgs,
133
134 pub pruning: PruningArgs,
136
137 pub enclave: EnclaveArgs,
139}
140
141impl NodeConfig<ChainSpec> {
142 pub fn test() -> Self {
144 Self::default()
145 .with_unused_ports()
147 }
148}
149
150impl<ChainSpec> NodeConfig<ChainSpec> {
151 pub fn new(chain: Arc<ChainSpec>) -> Self {
153 Self {
154 config: None,
155 chain,
156 metrics: None,
157 instance: 1,
158 network: NetworkArgs::default(),
159 rpc: RpcServerArgs::default(),
160 txpool: TxPoolArgs::default(),
161 builder: PayloadBuilderArgs::default(),
162 debug: DebugArgs::default(),
163 db: DatabaseArgs::default(),
164 dev: DevArgs::default(),
165 pruning: PruningArgs::default(),
166 datadir: DatadirArgs::default(),
167 enclave: EnclaveArgs::default(),
168 }
169 }
170
171 pub const fn dev(mut self) -> Self {
176 self.dev.dev = true;
177 self.network.discovery.disable_discovery = true;
178 self
179 }
180
181 pub const fn set_dev(self, dev: bool) -> Self {
183 if dev {
184 self.dev()
185 } else {
186 self
187 }
188 }
189
190 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
192 self.datadir = datadir_args;
193 self
194 }
195
196 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
198 self.config = Some(config.into());
199 self
200 }
201
202 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
204 self.chain = chain.into();
205 self
206 }
207
208 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
210 self.metrics = Some(metrics);
211 self
212 }
213
214 pub const fn with_instance(mut self, instance: u16) -> Self {
216 self.instance = instance;
217 self
218 }
219
220 pub fn with_network(mut self, network: NetworkArgs) -> Self {
222 self.network = network;
223 self
224 }
225
226 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
228 self.rpc = rpc;
229 self
230 }
231
232 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
234 self.txpool = txpool;
235 self
236 }
237
238 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
240 self.builder = builder;
241 self
242 }
243
244 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
246 self.debug = debug;
247 self
248 }
249
250 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
252 self.db = db;
253 self
254 }
255
256 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
258 self.dev = dev;
259 self
260 }
261
262 pub const fn with_enclave(mut self, enclave: EnclaveArgs) -> Self {
264 self.enclave = enclave;
265 self
266 }
267
268 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
270 self.pruning = pruning;
271 self
272 }
273
274 pub fn prune_config(&self) -> Option<PruneConfig>
276 where
277 ChainSpec: EthChainSpec,
278 {
279 self.pruning.prune_config(&self.chain)
280 }
281
282 pub async fn max_block<Provider, Client>(
285 &self,
286 network_client: Client,
287 provider: Provider,
288 ) -> eyre::Result<Option<BlockNumber>>
289 where
290 Provider: HeaderProvider,
291 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
292 {
293 let max_block = if let Some(block) = self.debug.max_block {
294 Some(block)
295 } else if let Some(tip) = self.debug.tip {
296 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
297 } else {
298 None
299 };
300
301 Ok(max_block)
302 }
303
304 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
308 where
309 Factory: DatabaseProviderFactory<
310 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
311 >,
312 {
313 let provider = factory.database_provider_ro()?;
314
315 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
316
317 let header = provider
318 .header_by_number(head)?
319 .expect("the header for the latest block is missing, database is corrupt");
320
321 let total_difficulty = provider
322 .header_td_by_number(head)?
323 .expect("the total difficulty for the latest block is missing, database is corrupt");
324
325 let hash = provider
326 .block_hash(head)?
327 .expect("the hash for the latest block is missing, database is corrupt");
328
329 Ok(Head {
330 number: head,
331 hash,
332 difficulty: header.difficulty(),
333 total_difficulty,
334 timestamp: header.timestamp(),
335 })
336 }
337
338 pub async fn lookup_or_fetch_tip<Provider, Client>(
343 &self,
344 provider: Provider,
345 client: Client,
346 tip: B256,
347 ) -> ProviderResult<u64>
348 where
349 Provider: HeaderProvider,
350 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
351 {
352 let header = provider.header_by_hash_or_number(tip.into())?;
353
354 if let Some(header) = header {
356 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
357 return Ok(header.number())
358 }
359
360 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
361 }
362
363 pub async fn fetch_tip_from_network<Client>(
367 &self,
368 client: Client,
369 tip: BlockHashOrNumber,
370 ) -> SealedHeader<Client::Header>
371 where
372 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
373 {
374 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
375 let mut fetch_failures = 0;
376 loop {
377 match get_single_header(&client, tip).await {
378 Ok(tip_header) => {
379 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
380 return tip_header
381 }
382 Err(error) => {
383 fetch_failures += 1;
384 if fetch_failures % 20 == 0 {
385 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
386 }
387 }
388 }
389 }
390 }
391
392 pub fn adjust_instance_ports(&mut self) {
395 self.rpc.adjust_instance_ports(self.instance);
396 self.network.adjust_instance_ports(self.instance);
397 }
398
399 pub fn with_unused_ports(mut self) -> Self {
402 self.rpc = self.rpc.with_unused_ports();
403 self.network = self.network.with_unused_ports();
404 self
405 }
406
407 pub fn datadir(&self) -> ChainPath<DataDirPath>
409 where
410 ChainSpec: EthChainSpec,
411 {
412 self.datadir.clone().resolve_datadir(self.chain.chain())
413 }
414
415 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
420 path: impl AsRef<Path>,
421 ) -> eyre::Result<T> {
422 let path = path.as_ref();
423 match fs::read_to_string(path) {
424 Ok(cfg_string) => {
425 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
426 }
427 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
428 if let Some(parent) = path.parent() {
429 fs::create_dir_all(parent)
430 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
431 }
432 let cfg = T::default();
433 let s = toml::to_string_pretty(&cfg)
434 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
435 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
436 Ok(cfg)
437 }
438 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
439 }
440 }
441
442 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
444 where
445 F: FnOnce(Arc<ChainSpec>) -> C,
446 {
447 let chain = Arc::new(f(self.chain));
448 NodeConfig {
449 chain,
450 datadir: self.datadir,
451 config: self.config,
452 metrics: self.metrics,
453 instance: self.instance,
454 network: self.network,
455 rpc: self.rpc,
456 txpool: self.txpool,
457 builder: self.builder,
458 debug: self.debug,
459 db: self.db,
460 dev: self.dev,
461 pruning: self.pruning,
462 enclave: self.enclave,
463 }
464 }
465}
466
467impl Default for NodeConfig<ChainSpec> {
468 fn default() -> Self {
469 Self::new(MAINNET.clone())
470 }
471}
472
473impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
474 fn clone(&self) -> Self {
475 Self {
476 chain: self.chain.clone(),
477 config: self.config.clone(),
478 metrics: self.metrics,
479 instance: self.instance,
480 network: self.network.clone(),
481 rpc: self.rpc.clone(),
482 txpool: self.txpool.clone(),
483 builder: self.builder.clone(),
484 debug: self.debug.clone(),
485 db: self.db,
486 dev: self.dev,
487 pruning: self.pruning.clone(),
488 datadir: self.datadir.clone(),
489 enclave: self.enclave.clone(),
490 }
491 }
492}