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