1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, JitArgs, NetworkArgs,
6 PayloadBuilderArgs, PruningArgs, RpcServerArgs, StaticFilesArgs, StorageArgs, 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, U256};
14use eyre::eyre;
15use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
16use reth_config::config::PruneConfig;
17use reth_engine_local::MiningMode;
18use reth_ethereum_forks::{EthereumHardforks, Head};
19use reth_network_p2p::headers::client::HeadersClient;
20use reth_primitives_traits::SealedHeader;
21use reth_stages_types::StageId;
22use reth_storage_api::{
23 BlockHashReader, DatabaseProviderFactory, HeaderProvider, StageCheckpointReader,
24 StorageSettings,
25};
26use reth_storage_errors::provider::ProviderResult;
27use reth_transaction_pool::TransactionPool;
28use serde::{de::DeserializeOwned, Serialize};
29use std::{
30 fs,
31 path::{Path, PathBuf},
32 sync::Arc,
33};
34use tracing::*;
35
36use crate::args::{EraArgs, MetricArgs};
37pub use reth_engine_primitives::{
38 DEFAULT_MEMORY_BLOCK_BUFFER_TARGET, DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
39};
40
41pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: usize = 4 * 1024;
43
44#[derive(Debug)]
90pub struct NodeConfig<ChainSpec> {
91 pub datadir: DatadirArgs,
93
94 pub config: Option<PathBuf>,
96
97 pub chain: Arc<ChainSpec>,
101
102 pub metrics: MetricArgs,
104
105 pub instance: Option<u16>,
121
122 pub network: NetworkArgs,
124
125 pub rpc: RpcServerArgs,
127
128 pub txpool: TxPoolArgs,
130
131 pub builder: PayloadBuilderArgs,
133
134 pub debug: DebugArgs,
136
137 pub db: DatabaseArgs,
139
140 pub dev: DevArgs,
142
143 pub pruning: PruningArgs,
145
146 pub engine: EngineArgs,
148
149 pub era: EraArgs,
151
152 pub static_files: StaticFilesArgs,
154
155 pub storage: StorageArgs,
157
158 pub jit: JitArgs,
160}
161
162impl NodeConfig<ChainSpec> {
163 pub fn test() -> Self {
165 Self::default()
166 .with_unused_ports()
168 }
169}
170
171impl<ChainSpec> NodeConfig<ChainSpec> {
172 pub fn new(chain: Arc<ChainSpec>) -> Self {
174 Self {
175 config: None,
176 chain,
177 metrics: MetricArgs::default(),
178 instance: None,
179 network: NetworkArgs::default(),
180 rpc: RpcServerArgs::default(),
181 txpool: TxPoolArgs::default(),
182 builder: PayloadBuilderArgs::default(),
183 debug: DebugArgs::default(),
184 db: DatabaseArgs::default(),
185 dev: DevArgs::default(),
186 pruning: PruningArgs::default(),
187 datadir: DatadirArgs::default(),
188 engine: EngineArgs::default(),
189 era: EraArgs::default(),
190 static_files: StaticFilesArgs::default(),
191 storage: StorageArgs::default(),
192 jit: JitArgs::default(),
193 }
194 }
195
196 pub const fn dev(mut self) -> Self {
201 self.dev.dev = true;
202 self.network.discovery.disable_discovery = true;
203 self
204 }
205
206 pub fn apply<F>(self, f: F) -> Self
208 where
209 F: FnOnce(Self) -> Self,
210 {
211 f(self)
212 }
213
214 pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
216 where
217 F: FnOnce(Self) -> Result<Self, R>,
218 {
219 f(self)
220 }
221
222 pub const fn set_dev(self, dev: bool) -> Self {
224 if dev {
225 self.dev()
226 } else {
227 self
228 }
229 }
230
231 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
233 self.datadir = datadir_args;
234 self
235 }
236
237 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
239 self.config = Some(config.into());
240 self
241 }
242
243 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
245 self.chain = chain.into();
246 self
247 }
248
249 pub fn map_chain<C>(self, chain: impl Into<Arc<C>>) -> NodeConfig<C> {
251 let Self {
252 datadir,
253 config,
254 metrics,
255 instance,
256 network,
257 rpc,
258 txpool,
259 builder,
260 debug,
261 db,
262 dev,
263 pruning,
264 engine,
265 era,
266 static_files,
267 storage,
268 jit,
269 ..
270 } = self;
271 NodeConfig {
272 datadir,
273 config,
274 chain: chain.into(),
275 metrics,
276 instance,
277 network,
278 rpc,
279 txpool,
280 builder,
281 debug,
282 db,
283 dev,
284 pruning,
285 engine,
286 era,
287 static_files,
288 storage,
289 jit,
290 }
291 }
292
293 pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
295 self.metrics = metrics;
296 self
297 }
298
299 pub const fn with_instance(mut self, instance: u16) -> Self {
301 self.instance = Some(instance);
302 self
303 }
304
305 pub fn get_instance(&self) -> u16 {
307 self.instance.unwrap_or(1)
308 }
309
310 pub fn with_network(mut self, network: NetworkArgs) -> Self {
312 self.network = network;
313 self
314 }
315
316 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
318 self.rpc = rpc;
319 self
320 }
321
322 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
324 self.txpool = txpool;
325 self
326 }
327
328 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
330 self.builder = builder;
331 self
332 }
333
334 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
336 self.debug = debug;
337 self
338 }
339
340 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
342 self.db = db;
343 self
344 }
345
346 pub fn with_dev(mut self, dev: DevArgs) -> Self {
348 self.dev = dev;
349 self
350 }
351
352 pub const fn with_dev_block_time(mut self, block_time: std::time::Duration) -> Self {
356 self.dev.block_time = Some(block_time);
357 self
358 }
359
360 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
362 self.pruning = pruning;
363 self
364 }
365
366 pub const fn with_storage(mut self, storage: StorageArgs) -> Self {
368 self.storage = storage;
369 self
370 }
371
372 pub fn prune_config(&self) -> Option<PruneConfig>
374 where
375 ChainSpec: EthereumHardforks,
376 {
377 self.pruning.prune_config(&self.chain)
378 }
379
380 pub const fn storage_settings(&self) -> StorageSettings {
386 if self.storage.v2 {
387 StorageSettings::v2()
388 } else {
389 StorageSettings::v1()
390 }
391 }
392
393 pub async fn max_block<Provider, Client>(
396 &self,
397 network_client: Client,
398 provider: Provider,
399 ) -> eyre::Result<Option<BlockNumber>>
400 where
401 Provider: HeaderProvider,
402 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
403 {
404 let max_block = if let Some(block) = self.debug.max_block {
405 Some(block)
406 } else if let Some(tip) = self.debug.tip {
407 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
408 } else {
409 None
410 };
411
412 Ok(max_block)
413 }
414
415 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
419 where
420 Factory: DatabaseProviderFactory<
421 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
422 >,
423 {
424 let provider = factory.database_provider_ro()?;
425
426 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
427
428 let header = provider
429 .header_by_number(head)?
430 .expect("the header for the latest block is missing, database is corrupt");
431
432 let hash = provider
433 .block_hash(head)?
434 .expect("the hash for the latest block is missing, database is corrupt");
435
436 Ok(Head {
437 number: head,
438 hash,
439 difficulty: header.difficulty(),
440 total_difficulty: U256::ZERO,
441 timestamp: header.timestamp(),
442 })
443 }
444
445 pub async fn lookup_or_fetch_tip<Provider, Client>(
450 &self,
451 provider: Provider,
452 client: Client,
453 tip: B256,
454 ) -> ProviderResult<u64>
455 where
456 Provider: HeaderProvider,
457 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
458 {
459 let header = provider.header_by_hash_or_number(tip.into())?;
460
461 if let Some(header) = header {
463 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
464 return Ok(header.number())
465 }
466
467 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
468 }
469
470 pub async fn fetch_tip_from_network<Client>(
474 &self,
475 client: Client,
476 tip: BlockHashOrNumber,
477 ) -> SealedHeader<Client::Header>
478 where
479 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
480 {
481 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
482 let mut fetch_failures = 0;
483 loop {
484 match get_single_header(&client, tip).await {
485 Ok(tip_header) => {
486 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
487 return tip_header
488 }
489 Err(error) => {
490 fetch_failures += 1;
491 if fetch_failures % 20 == 0 {
492 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
493 }
494 }
495 }
496 }
497 }
498
499 pub fn adjust_instance_ports(&mut self) {
502 self.network.adjust_instance_ports(self.instance);
503 self.rpc.adjust_instance_ports(self.instance);
504 }
505
506 pub fn with_unused_ports(mut self) -> Self {
509 self.rpc = self.rpc.with_unused_ports();
510 self.network = self.network.with_unused_ports();
511 self
512 }
513
514 pub const fn with_disabled_discovery(mut self) -> Self {
516 self.network.discovery.disable_discovery = true;
517 self
518 }
519
520 pub const fn with_disabled_rpc_cache(mut self) -> Self {
525 self.rpc.rpc_state_cache.set_zero_lengths();
526 self
527 }
528
529 pub fn datadir(&self) -> ChainPath<DataDirPath>
531 where
532 ChainSpec: EthChainSpec,
533 {
534 self.datadir.clone().resolve_datadir(self.chain.chain())
535 }
536
537 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
542 path: impl AsRef<Path>,
543 ) -> eyre::Result<T> {
544 let path = path.as_ref();
545 match fs::read_to_string(path) {
546 Ok(cfg_string) => {
547 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
548 }
549 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
550 if let Some(parent) = path.parent() {
551 fs::create_dir_all(parent)
552 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
553 }
554 let cfg = T::default();
555 let s = toml::to_string_pretty(&cfg)
556 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
557 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
558 Ok(cfg)
559 }
560 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
561 }
562 }
563
564 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
566 where
567 F: FnOnce(Arc<ChainSpec>) -> C,
568 {
569 let chain = Arc::new(f(self.chain));
570 NodeConfig {
571 chain,
572 datadir: self.datadir,
573 config: self.config,
574 metrics: self.metrics,
575 instance: self.instance,
576 network: self.network,
577 rpc: self.rpc,
578 txpool: self.txpool,
579 builder: self.builder,
580 debug: self.debug,
581 db: self.db,
582 dev: self.dev,
583 pruning: self.pruning,
584 engine: self.engine,
585 era: self.era,
586 static_files: self.static_files,
587 storage: self.storage,
588 jit: self.jit,
589 }
590 }
591
592 pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
594 where
595 Pool: TransactionPool + Unpin,
596 {
597 if let Some(interval) = self.dev.block_time {
598 MiningMode::interval(interval)
599 } else {
600 MiningMode::instant(pool, self.dev.block_max_transactions)
601 }
602 }
603}
604
605impl Default for NodeConfig<ChainSpec> {
606 fn default() -> Self {
607 Self::new(MAINNET.clone())
608 }
609}
610
611impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
612 fn clone(&self) -> Self {
613 Self {
614 chain: self.chain.clone(),
615 config: self.config.clone(),
616 metrics: self.metrics.clone(),
617 instance: self.instance,
618 network: self.network.clone(),
619 rpc: self.rpc.clone(),
620 txpool: self.txpool.clone(),
621 builder: self.builder.clone(),
622 debug: self.debug.clone(),
623 db: self.db,
624 dev: self.dev.clone(),
625 pruning: self.pruning.clone(),
626 datadir: self.datadir.clone(),
627 engine: self.engine.clone(),
628 era: self.era.clone(),
629 static_files: self.static_files,
630 storage: self.storage,
631 jit: self.jit.clone(),
632 }
633 }
634}