1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, NetworkArgs, PayloadBuilderArgs,
6 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
34use crate::args::EraArgs;
35pub use reth_engine_primitives::{
36 DEFAULT_MAX_PROOF_TASK_CONCURRENCY, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
37 DEFAULT_RESERVED_CPU_CORES,
38};
39
40pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
42
43pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
45
46#[derive(Debug)]
92pub struct NodeConfig<ChainSpec> {
93 pub datadir: DatadirArgs,
95
96 pub config: Option<PathBuf>,
98
99 pub chain: Arc<ChainSpec>,
103
104 pub metrics: Option<SocketAddr>,
108
109 pub instance: Option<u16>,
125
126 pub network: NetworkArgs,
128
129 pub rpc: RpcServerArgs,
131
132 pub txpool: TxPoolArgs,
134
135 pub builder: PayloadBuilderArgs,
137
138 pub debug: DebugArgs,
140
141 pub db: DatabaseArgs,
143
144 pub dev: DevArgs,
146
147 pub pruning: PruningArgs,
149
150 pub engine: EngineArgs,
152
153 pub era: EraArgs,
155}
156
157impl NodeConfig<ChainSpec> {
158 pub fn test() -> Self {
160 Self::default()
161 .with_unused_ports()
163 }
164}
165
166impl<ChainSpec> NodeConfig<ChainSpec> {
167 pub fn new(chain: Arc<ChainSpec>) -> Self {
169 Self {
170 config: None,
171 chain,
172 metrics: None,
173 instance: None,
174 network: NetworkArgs::default(),
175 rpc: RpcServerArgs::default(),
176 txpool: TxPoolArgs::default(),
177 builder: PayloadBuilderArgs::default(),
178 debug: DebugArgs::default(),
179 db: DatabaseArgs::default(),
180 dev: DevArgs::default(),
181 pruning: PruningArgs::default(),
182 datadir: DatadirArgs::default(),
183 engine: EngineArgs::default(),
184 era: EraArgs::default(),
185 }
186 }
187
188 pub const fn dev(mut self) -> Self {
193 self.dev.dev = true;
194 self.network.discovery.disable_discovery = true;
195 self
196 }
197
198 pub const fn set_dev(self, dev: bool) -> Self {
200 if dev {
201 self.dev()
202 } else {
203 self
204 }
205 }
206
207 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
209 self.datadir = datadir_args;
210 self
211 }
212
213 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
215 self.config = Some(config.into());
216 self
217 }
218
219 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
221 self.chain = chain.into();
222 self
223 }
224
225 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
227 self.metrics = Some(metrics);
228 self
229 }
230
231 pub const fn with_instance(mut self, instance: u16) -> Self {
233 self.instance = Some(instance);
234 self
235 }
236
237 pub fn get_instance(&self) -> u16 {
239 self.instance.unwrap_or(1)
240 }
241
242 pub fn with_network(mut self, network: NetworkArgs) -> Self {
244 self.network = network;
245 self
246 }
247
248 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
250 self.rpc = rpc;
251 self
252 }
253
254 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
256 self.txpool = txpool;
257 self
258 }
259
260 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
262 self.builder = builder;
263 self
264 }
265
266 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
268 self.debug = debug;
269 self
270 }
271
272 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
274 self.db = db;
275 self
276 }
277
278 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
280 self.dev = dev;
281 self
282 }
283
284 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
286 self.pruning = pruning;
287 self
288 }
289
290 pub fn prune_config(&self) -> Option<PruneConfig> {
292 self.pruning.prune_config()
293 }
294
295 pub async fn max_block<Provider, Client>(
298 &self,
299 network_client: Client,
300 provider: Provider,
301 ) -> eyre::Result<Option<BlockNumber>>
302 where
303 Provider: HeaderProvider,
304 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
305 {
306 let max_block = if let Some(block) = self.debug.max_block {
307 Some(block)
308 } else if let Some(tip) = self.debug.tip {
309 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
310 } else {
311 None
312 };
313
314 Ok(max_block)
315 }
316
317 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
321 where
322 Factory: DatabaseProviderFactory<
323 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
324 >,
325 {
326 let provider = factory.database_provider_ro()?;
327
328 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
329
330 let header = provider
331 .header_by_number(head)?
332 .expect("the header for the latest block is missing, database is corrupt");
333
334 let total_difficulty = provider
335 .header_td_by_number(head)?
336 .unwrap_or_default();
339
340 let hash = provider
341 .block_hash(head)?
342 .expect("the hash for the latest block is missing, database is corrupt");
343
344 Ok(Head {
345 number: head,
346 hash,
347 difficulty: header.difficulty(),
348 total_difficulty,
349 timestamp: header.timestamp(),
350 })
351 }
352
353 pub async fn lookup_or_fetch_tip<Provider, Client>(
358 &self,
359 provider: Provider,
360 client: Client,
361 tip: B256,
362 ) -> ProviderResult<u64>
363 where
364 Provider: HeaderProvider,
365 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
366 {
367 let header = provider.header_by_hash_or_number(tip.into())?;
368
369 if let Some(header) = header {
371 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
372 return Ok(header.number())
373 }
374
375 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
376 }
377
378 pub async fn fetch_tip_from_network<Client>(
382 &self,
383 client: Client,
384 tip: BlockHashOrNumber,
385 ) -> SealedHeader<Client::Header>
386 where
387 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
388 {
389 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
390 let mut fetch_failures = 0;
391 loop {
392 match get_single_header(&client, tip).await {
393 Ok(tip_header) => {
394 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
395 return tip_header
396 }
397 Err(error) => {
398 fetch_failures += 1;
399 if fetch_failures % 20 == 0 {
400 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
401 }
402 }
403 }
404 }
405 }
406
407 pub fn adjust_instance_ports(&mut self) {
410 self.network.adjust_instance_ports(self.instance);
411 self.rpc.adjust_instance_ports(self.instance);
412 }
413
414 pub fn with_unused_ports(mut self) -> Self {
417 self.rpc = self.rpc.with_unused_ports();
418 self.network = self.network.with_unused_ports();
419 self
420 }
421
422 pub const fn with_disabled_rpc_cache(mut self) -> Self {
427 self.rpc.rpc_state_cache.set_zero_lengths();
428 self
429 }
430
431 pub fn datadir(&self) -> ChainPath<DataDirPath>
433 where
434 ChainSpec: EthChainSpec,
435 {
436 self.datadir.clone().resolve_datadir(self.chain.chain())
437 }
438
439 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
444 path: impl AsRef<Path>,
445 ) -> eyre::Result<T> {
446 let path = path.as_ref();
447 match fs::read_to_string(path) {
448 Ok(cfg_string) => {
449 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
450 }
451 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
452 if let Some(parent) = path.parent() {
453 fs::create_dir_all(parent)
454 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
455 }
456 let cfg = T::default();
457 let s = toml::to_string_pretty(&cfg)
458 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
459 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
460 Ok(cfg)
461 }
462 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
463 }
464 }
465
466 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
468 where
469 F: FnOnce(Arc<ChainSpec>) -> C,
470 {
471 let chain = Arc::new(f(self.chain));
472 NodeConfig {
473 chain,
474 datadir: self.datadir,
475 config: self.config,
476 metrics: self.metrics,
477 instance: self.instance,
478 network: self.network,
479 rpc: self.rpc,
480 txpool: self.txpool,
481 builder: self.builder,
482 debug: self.debug,
483 db: self.db,
484 dev: self.dev,
485 pruning: self.pruning,
486 engine: self.engine,
487 era: self.era,
488 }
489 }
490}
491
492impl Default for NodeConfig<ChainSpec> {
493 fn default() -> Self {
494 Self::new(MAINNET.clone())
495 }
496}
497
498impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
499 fn clone(&self) -> Self {
500 Self {
501 chain: self.chain.clone(),
502 config: self.config.clone(),
503 metrics: self.metrics,
504 instance: self.instance,
505 network: self.network.clone(),
506 rpc: self.rpc.clone(),
507 txpool: self.txpool.clone(),
508 builder: self.builder.clone(),
509 debug: self.debug.clone(),
510 db: self.db,
511 dev: self.dev,
512 pruning: self.pruning.clone(),
513 datadir: self.datadir.clone(),
514 engine: self.engine.clone(),
515 era: self.era.clone(),
516 }
517 }
518}