1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, NetworkArgs, PayloadBuilderArgs,
6 PruningArgs, RocksDbArgs, RpcServerArgs, StaticFilesArgs, 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 rocksdb: RocksDbArgs,
157}
158
159impl NodeConfig<ChainSpec> {
160 pub fn test() -> Self {
162 Self::default()
163 .with_unused_ports()
165 }
166}
167
168impl<ChainSpec> NodeConfig<ChainSpec> {
169 pub fn new(chain: Arc<ChainSpec>) -> Self {
171 Self {
172 config: None,
173 chain,
174 metrics: MetricArgs::default(),
175 instance: None,
176 network: NetworkArgs::default(),
177 rpc: RpcServerArgs::default(),
178 txpool: TxPoolArgs::default(),
179 builder: PayloadBuilderArgs::default(),
180 debug: DebugArgs::default(),
181 db: DatabaseArgs::default(),
182 dev: DevArgs::default(),
183 pruning: PruningArgs::default(),
184 datadir: DatadirArgs::default(),
185 engine: EngineArgs::default(),
186 era: EraArgs::default(),
187 static_files: StaticFilesArgs::default(),
188 rocksdb: RocksDbArgs::default(),
189 }
190 }
191
192 pub const fn dev(mut self) -> Self {
197 self.dev.dev = true;
198 self.network.discovery.disable_discovery = true;
199 self
200 }
201
202 pub fn apply<F>(self, f: F) -> Self
204 where
205 F: FnOnce(Self) -> Self,
206 {
207 f(self)
208 }
209
210 pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
212 where
213 F: FnOnce(Self) -> Result<Self, R>,
214 {
215 f(self)
216 }
217
218 pub const fn set_dev(self, dev: bool) -> Self {
220 if dev {
221 self.dev()
222 } else {
223 self
224 }
225 }
226
227 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
229 self.datadir = datadir_args;
230 self
231 }
232
233 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
235 self.config = Some(config.into());
236 self
237 }
238
239 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
241 self.chain = chain.into();
242 self
243 }
244
245 pub fn map_chain<C>(self, chain: impl Into<Arc<C>>) -> NodeConfig<C> {
247 let Self {
248 datadir,
249 config,
250 metrics,
251 instance,
252 network,
253 rpc,
254 txpool,
255 builder,
256 debug,
257 db,
258 dev,
259 pruning,
260 engine,
261 era,
262 static_files,
263 rocksdb,
264 ..
265 } = self;
266 NodeConfig {
267 datadir,
268 config,
269 chain: chain.into(),
270 metrics,
271 instance,
272 network,
273 rpc,
274 txpool,
275 builder,
276 debug,
277 db,
278 dev,
279 pruning,
280 engine,
281 era,
282 static_files,
283 rocksdb,
284 }
285 }
286
287 pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
289 self.metrics = metrics;
290 self
291 }
292
293 pub const fn with_instance(mut self, instance: u16) -> Self {
295 self.instance = Some(instance);
296 self
297 }
298
299 pub fn get_instance(&self) -> u16 {
301 self.instance.unwrap_or(1)
302 }
303
304 pub fn with_network(mut self, network: NetworkArgs) -> Self {
306 self.network = network;
307 self
308 }
309
310 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
312 self.rpc = rpc;
313 self
314 }
315
316 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
318 self.txpool = txpool;
319 self
320 }
321
322 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
324 self.builder = builder;
325 self
326 }
327
328 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
330 self.debug = debug;
331 self
332 }
333
334 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
336 self.db = db;
337 self
338 }
339
340 pub fn with_dev(mut self, dev: DevArgs) -> Self {
342 self.dev = dev;
343 self
344 }
345
346 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
348 self.pruning = pruning;
349 self
350 }
351
352 pub fn prune_config(&self) -> Option<PruneConfig>
354 where
355 ChainSpec: EthereumHardforks,
356 {
357 self.pruning.prune_config(&self.chain)
358 }
359
360 pub const fn storage_settings(&self) -> StorageSettings {
362 StorageSettings::base()
363 .with_receipts_in_static_files(self.static_files.receipts)
364 .with_transaction_senders_in_static_files(self.static_files.transaction_senders)
365 .with_account_changesets_in_static_files(self.static_files.account_changesets)
366 .with_storage_changesets_in_static_files(self.static_files.storage_changesets)
367 .with_transaction_hash_numbers_in_rocksdb(self.rocksdb.all || self.rocksdb.tx_hash)
368 .with_storages_history_in_rocksdb(self.rocksdb.all || self.rocksdb.storages_history)
369 .with_account_history_in_rocksdb(self.rocksdb.all || self.rocksdb.account_history)
370 }
371
372 pub async fn max_block<Provider, Client>(
375 &self,
376 network_client: Client,
377 provider: Provider,
378 ) -> eyre::Result<Option<BlockNumber>>
379 where
380 Provider: HeaderProvider,
381 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
382 {
383 let max_block = if let Some(block) = self.debug.max_block {
384 Some(block)
385 } else if let Some(tip) = self.debug.tip {
386 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
387 } else {
388 None
389 };
390
391 Ok(max_block)
392 }
393
394 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
398 where
399 Factory: DatabaseProviderFactory<
400 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
401 >,
402 {
403 let provider = factory.database_provider_ro()?;
404
405 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
406
407 let header = provider
408 .header_by_number(head)?
409 .expect("the header for the latest block is missing, database is corrupt");
410
411 let hash = provider
412 .block_hash(head)?
413 .expect("the hash for the latest block is missing, database is corrupt");
414
415 Ok(Head {
416 number: head,
417 hash,
418 difficulty: header.difficulty(),
419 total_difficulty: U256::ZERO,
420 timestamp: header.timestamp(),
421 })
422 }
423
424 pub async fn lookup_or_fetch_tip<Provider, Client>(
429 &self,
430 provider: Provider,
431 client: Client,
432 tip: B256,
433 ) -> ProviderResult<u64>
434 where
435 Provider: HeaderProvider,
436 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
437 {
438 let header = provider.header_by_hash_or_number(tip.into())?;
439
440 if let Some(header) = header {
442 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
443 return Ok(header.number())
444 }
445
446 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
447 }
448
449 pub async fn fetch_tip_from_network<Client>(
453 &self,
454 client: Client,
455 tip: BlockHashOrNumber,
456 ) -> SealedHeader<Client::Header>
457 where
458 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
459 {
460 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
461 let mut fetch_failures = 0;
462 loop {
463 match get_single_header(&client, tip).await {
464 Ok(tip_header) => {
465 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
466 return tip_header
467 }
468 Err(error) => {
469 fetch_failures += 1;
470 if fetch_failures % 20 == 0 {
471 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
472 }
473 }
474 }
475 }
476 }
477
478 pub fn adjust_instance_ports(&mut self) {
481 self.network.adjust_instance_ports(self.instance);
482 self.rpc.adjust_instance_ports(self.instance);
483 }
484
485 pub fn with_unused_ports(mut self) -> Self {
488 self.rpc = self.rpc.with_unused_ports();
489 self.network = self.network.with_unused_ports();
490 self
491 }
492
493 pub const fn with_disabled_discovery(mut self) -> Self {
495 self.network.discovery.disable_discovery = true;
496 self
497 }
498
499 pub const fn with_disabled_rpc_cache(mut self) -> Self {
504 self.rpc.rpc_state_cache.set_zero_lengths();
505 self
506 }
507
508 pub fn datadir(&self) -> ChainPath<DataDirPath>
510 where
511 ChainSpec: EthChainSpec,
512 {
513 self.datadir.clone().resolve_datadir(self.chain.chain())
514 }
515
516 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
521 path: impl AsRef<Path>,
522 ) -> eyre::Result<T> {
523 let path = path.as_ref();
524 match fs::read_to_string(path) {
525 Ok(cfg_string) => {
526 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
527 }
528 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
529 if let Some(parent) = path.parent() {
530 fs::create_dir_all(parent)
531 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
532 }
533 let cfg = T::default();
534 let s = toml::to_string_pretty(&cfg)
535 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
536 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
537 Ok(cfg)
538 }
539 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
540 }
541 }
542
543 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
545 where
546 F: FnOnce(Arc<ChainSpec>) -> C,
547 {
548 let chain = Arc::new(f(self.chain));
549 NodeConfig {
550 chain,
551 datadir: self.datadir,
552 config: self.config,
553 metrics: self.metrics,
554 instance: self.instance,
555 network: self.network,
556 rpc: self.rpc,
557 txpool: self.txpool,
558 builder: self.builder,
559 debug: self.debug,
560 db: self.db,
561 dev: self.dev,
562 pruning: self.pruning,
563 engine: self.engine,
564 era: self.era,
565 static_files: self.static_files,
566 rocksdb: self.rocksdb,
567 }
568 }
569
570 pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
572 where
573 Pool: TransactionPool + Unpin,
574 {
575 if let Some(interval) = self.dev.block_time {
576 MiningMode::interval(interval)
577 } else {
578 MiningMode::instant(pool, self.dev.block_max_transactions)
579 }
580 }
581}
582
583impl Default for NodeConfig<ChainSpec> {
584 fn default() -> Self {
585 Self::new(MAINNET.clone())
586 }
587}
588
589impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
590 fn clone(&self) -> Self {
591 Self {
592 chain: self.chain.clone(),
593 config: self.config.clone(),
594 metrics: self.metrics.clone(),
595 instance: self.instance,
596 network: self.network.clone(),
597 rpc: self.rpc.clone(),
598 txpool: self.txpool.clone(),
599 builder: self.builder.clone(),
600 debug: self.debug.clone(),
601 db: self.db,
602 dev: self.dev.clone(),
603 pruning: self.pruning.clone(),
604 datadir: self.datadir.clone(),
605 engine: self.engine.clone(),
606 era: self.era.clone(),
607 static_files: self.static_files,
608 rocksdb: self.rocksdb,
609 }
610 }
611}