1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, NetworkArgs, PayloadBuilderArgs,
6 PruningArgs, 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};
25use reth_storage_errors::provider::ProviderResult;
26use reth_transaction_pool::TransactionPool;
27use serde::{de::DeserializeOwned, Serialize};
28use std::{
29 fs,
30 path::{Path, PathBuf},
31 sync::Arc,
32};
33use tracing::*;
34
35use crate::args::{EraArgs, MetricArgs};
36pub use reth_engine_primitives::{
37 DEFAULT_MEMORY_BLOCK_BUFFER_TARGET, DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
38};
39
40pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
42
43#[derive(Debug)]
89pub struct NodeConfig<ChainSpec> {
90 pub datadir: DatadirArgs,
92
93 pub config: Option<PathBuf>,
95
96 pub chain: Arc<ChainSpec>,
100
101 pub metrics: MetricArgs,
103
104 pub instance: Option<u16>,
120
121 pub network: NetworkArgs,
123
124 pub rpc: RpcServerArgs,
126
127 pub txpool: TxPoolArgs,
129
130 pub builder: PayloadBuilderArgs,
132
133 pub debug: DebugArgs,
135
136 pub db: DatabaseArgs,
138
139 pub dev: DevArgs,
141
142 pub pruning: PruningArgs,
144
145 pub engine: EngineArgs,
147
148 pub era: EraArgs,
150
151 pub static_files: StaticFilesArgs,
153}
154
155impl NodeConfig<ChainSpec> {
156 pub fn test() -> Self {
158 Self::default()
159 .with_unused_ports()
161 }
162}
163
164impl<ChainSpec> NodeConfig<ChainSpec> {
165 pub fn new(chain: Arc<ChainSpec>) -> Self {
167 Self {
168 config: None,
169 chain,
170 metrics: MetricArgs::default(),
171 instance: None,
172 network: NetworkArgs::default(),
173 rpc: RpcServerArgs::default(),
174 txpool: TxPoolArgs::default(),
175 builder: PayloadBuilderArgs::default(),
176 debug: DebugArgs::default(),
177 db: DatabaseArgs::default(),
178 dev: DevArgs::default(),
179 pruning: PruningArgs::default(),
180 datadir: DatadirArgs::default(),
181 engine: EngineArgs::default(),
182 era: EraArgs::default(),
183 static_files: StaticFilesArgs::default(),
184 }
185 }
186
187 pub const fn dev(mut self) -> Self {
192 self.dev.dev = true;
193 self.network.discovery.disable_discovery = true;
194 self
195 }
196
197 pub fn apply<F>(self, f: F) -> Self
199 where
200 F: FnOnce(Self) -> Self,
201 {
202 f(self)
203 }
204
205 pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
207 where
208 F: FnOnce(Self) -> Result<Self, R>,
209 {
210 f(self)
211 }
212
213 pub const fn set_dev(self, dev: bool) -> Self {
215 if dev {
216 self.dev()
217 } else {
218 self
219 }
220 }
221
222 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
224 self.datadir = datadir_args;
225 self
226 }
227
228 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
230 self.config = Some(config.into());
231 self
232 }
233
234 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
236 self.chain = chain.into();
237 self
238 }
239
240 pub fn map_chain<C>(self, chain: impl Into<Arc<C>>) -> NodeConfig<C> {
242 let Self {
243 datadir,
244 config,
245 metrics,
246 instance,
247 network,
248 rpc,
249 txpool,
250 builder,
251 debug,
252 db,
253 dev,
254 pruning,
255 engine,
256 era,
257 static_files,
258 ..
259 } = self;
260 NodeConfig {
261 datadir,
262 config,
263 chain: chain.into(),
264 metrics,
265 instance,
266 network,
267 rpc,
268 txpool,
269 builder,
270 debug,
271 db,
272 dev,
273 pruning,
274 engine,
275 era,
276 static_files,
277 }
278 }
279
280 pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
282 self.metrics = metrics;
283 self
284 }
285
286 pub const fn with_instance(mut self, instance: u16) -> Self {
288 self.instance = Some(instance);
289 self
290 }
291
292 pub fn get_instance(&self) -> u16 {
294 self.instance.unwrap_or(1)
295 }
296
297 pub fn with_network(mut self, network: NetworkArgs) -> Self {
299 self.network = network;
300 self
301 }
302
303 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
305 self.rpc = rpc;
306 self
307 }
308
309 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
311 self.txpool = txpool;
312 self
313 }
314
315 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
317 self.builder = builder;
318 self
319 }
320
321 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
323 self.debug = debug;
324 self
325 }
326
327 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
329 self.db = db;
330 self
331 }
332
333 pub fn with_dev(mut self, dev: DevArgs) -> Self {
335 self.dev = dev;
336 self
337 }
338
339 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
341 self.pruning = pruning;
342 self
343 }
344
345 pub fn prune_config(&self) -> Option<PruneConfig>
347 where
348 ChainSpec: EthereumHardforks,
349 {
350 self.pruning.prune_config(&self.chain)
351 }
352
353 pub async fn max_block<Provider, Client>(
356 &self,
357 network_client: Client,
358 provider: Provider,
359 ) -> eyre::Result<Option<BlockNumber>>
360 where
361 Provider: HeaderProvider,
362 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
363 {
364 let max_block = if let Some(block) = self.debug.max_block {
365 Some(block)
366 } else if let Some(tip) = self.debug.tip {
367 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
368 } else {
369 None
370 };
371
372 Ok(max_block)
373 }
374
375 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
379 where
380 Factory: DatabaseProviderFactory<
381 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
382 >,
383 {
384 let provider = factory.database_provider_ro()?;
385
386 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
387
388 let header = provider
389 .header_by_number(head)?
390 .expect("the header for the latest block is missing, database is corrupt");
391
392 let hash = provider
393 .block_hash(head)?
394 .expect("the hash for the latest block is missing, database is corrupt");
395
396 Ok(Head {
397 number: head,
398 hash,
399 difficulty: header.difficulty(),
400 total_difficulty: U256::ZERO,
401 timestamp: header.timestamp(),
402 })
403 }
404
405 pub async fn lookup_or_fetch_tip<Provider, Client>(
410 &self,
411 provider: Provider,
412 client: Client,
413 tip: B256,
414 ) -> ProviderResult<u64>
415 where
416 Provider: HeaderProvider,
417 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
418 {
419 let header = provider.header_by_hash_or_number(tip.into())?;
420
421 if let Some(header) = header {
423 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
424 return Ok(header.number())
425 }
426
427 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
428 }
429
430 pub async fn fetch_tip_from_network<Client>(
434 &self,
435 client: Client,
436 tip: BlockHashOrNumber,
437 ) -> SealedHeader<Client::Header>
438 where
439 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
440 {
441 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
442 let mut fetch_failures = 0;
443 loop {
444 match get_single_header(&client, tip).await {
445 Ok(tip_header) => {
446 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
447 return tip_header
448 }
449 Err(error) => {
450 fetch_failures += 1;
451 if fetch_failures % 20 == 0 {
452 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
453 }
454 }
455 }
456 }
457 }
458
459 pub fn adjust_instance_ports(&mut self) {
462 self.network.adjust_instance_ports(self.instance);
463 self.rpc.adjust_instance_ports(self.instance);
464 }
465
466 pub fn with_unused_ports(mut self) -> Self {
469 self.rpc = self.rpc.with_unused_ports();
470 self.network = self.network.with_unused_ports();
471 self
472 }
473
474 pub const fn with_disabled_discovery(mut self) -> Self {
476 self.network.discovery.disable_discovery = true;
477 self
478 }
479
480 pub const fn with_disabled_rpc_cache(mut self) -> Self {
485 self.rpc.rpc_state_cache.set_zero_lengths();
486 self
487 }
488
489 pub fn datadir(&self) -> ChainPath<DataDirPath>
491 where
492 ChainSpec: EthChainSpec,
493 {
494 self.datadir.clone().resolve_datadir(self.chain.chain())
495 }
496
497 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
502 path: impl AsRef<Path>,
503 ) -> eyre::Result<T> {
504 let path = path.as_ref();
505 match fs::read_to_string(path) {
506 Ok(cfg_string) => {
507 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
508 }
509 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
510 if let Some(parent) = path.parent() {
511 fs::create_dir_all(parent)
512 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
513 }
514 let cfg = T::default();
515 let s = toml::to_string_pretty(&cfg)
516 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
517 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
518 Ok(cfg)
519 }
520 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
521 }
522 }
523
524 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
526 where
527 F: FnOnce(Arc<ChainSpec>) -> C,
528 {
529 let chain = Arc::new(f(self.chain));
530 NodeConfig {
531 chain,
532 datadir: self.datadir,
533 config: self.config,
534 metrics: self.metrics,
535 instance: self.instance,
536 network: self.network,
537 rpc: self.rpc,
538 txpool: self.txpool,
539 builder: self.builder,
540 debug: self.debug,
541 db: self.db,
542 dev: self.dev,
543 pruning: self.pruning,
544 engine: self.engine,
545 era: self.era,
546 static_files: self.static_files,
547 }
548 }
549
550 pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
552 where
553 Pool: TransactionPool + Unpin,
554 {
555 if let Some(interval) = self.dev.block_time {
556 MiningMode::interval(interval)
557 } else {
558 MiningMode::instant(pool, self.dev.block_max_transactions)
559 }
560 }
561}
562
563impl Default for NodeConfig<ChainSpec> {
564 fn default() -> Self {
565 Self::new(MAINNET.clone())
566 }
567}
568
569impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
570 fn clone(&self) -> Self {
571 Self {
572 chain: self.chain.clone(),
573 config: self.config.clone(),
574 metrics: self.metrics.clone(),
575 instance: self.instance,
576 network: self.network.clone(),
577 rpc: self.rpc.clone(),
578 txpool: self.txpool.clone(),
579 builder: self.builder.clone(),
580 debug: self.debug.clone(),
581 db: self.db,
582 dev: self.dev.clone(),
583 pruning: self.pruning.clone(),
584 datadir: self.datadir.clone(),
585 engine: self.engine.clone(),
586 era: self.era.clone(),
587 static_files: self.static_files,
588 }
589 }
590}