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, 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
152impl NodeConfig<ChainSpec> {
153 pub fn test() -> Self {
155 Self::default()
156 .with_unused_ports()
158 }
159}
160
161impl<ChainSpec> NodeConfig<ChainSpec> {
162 pub fn new(chain: Arc<ChainSpec>) -> Self {
164 Self {
165 config: None,
166 chain,
167 metrics: MetricArgs::default(),
168 instance: None,
169 network: NetworkArgs::default(),
170 rpc: RpcServerArgs::default(),
171 txpool: TxPoolArgs::default(),
172 builder: PayloadBuilderArgs::default(),
173 debug: DebugArgs::default(),
174 db: DatabaseArgs::default(),
175 dev: DevArgs::default(),
176 pruning: PruningArgs::default(),
177 datadir: DatadirArgs::default(),
178 engine: EngineArgs::default(),
179 era: EraArgs::default(),
180 }
181 }
182
183 pub const fn dev(mut self) -> Self {
188 self.dev.dev = true;
189 self.network.discovery.disable_discovery = true;
190 self
191 }
192
193 pub fn apply<F>(self, f: F) -> Self
195 where
196 F: FnOnce(Self) -> Self,
197 {
198 f(self)
199 }
200
201 pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
203 where
204 F: FnOnce(Self) -> Result<Self, R>,
205 {
206 f(self)
207 }
208
209 pub const fn set_dev(self, dev: bool) -> Self {
211 if dev {
212 self.dev()
213 } else {
214 self
215 }
216 }
217
218 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
220 self.datadir = datadir_args;
221 self
222 }
223
224 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
226 self.config = Some(config.into());
227 self
228 }
229
230 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
232 self.chain = chain.into();
233 self
234 }
235
236 pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
238 self.metrics = metrics;
239 self
240 }
241
242 pub const fn with_instance(mut self, instance: u16) -> Self {
244 self.instance = Some(instance);
245 self
246 }
247
248 pub fn get_instance(&self) -> u16 {
250 self.instance.unwrap_or(1)
251 }
252
253 pub fn with_network(mut self, network: NetworkArgs) -> Self {
255 self.network = network;
256 self
257 }
258
259 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
261 self.rpc = rpc;
262 self
263 }
264
265 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
267 self.txpool = txpool;
268 self
269 }
270
271 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
273 self.builder = builder;
274 self
275 }
276
277 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
279 self.debug = debug;
280 self
281 }
282
283 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
285 self.db = db;
286 self
287 }
288
289 pub fn with_dev(mut self, dev: DevArgs) -> Self {
291 self.dev = dev;
292 self
293 }
294
295 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
297 self.pruning = pruning;
298 self
299 }
300
301 pub fn prune_config(&self) -> Option<PruneConfig>
303 where
304 ChainSpec: EthereumHardforks,
305 {
306 self.pruning.prune_config(&self.chain)
307 }
308
309 pub async fn max_block<Provider, Client>(
312 &self,
313 network_client: Client,
314 provider: Provider,
315 ) -> eyre::Result<Option<BlockNumber>>
316 where
317 Provider: HeaderProvider,
318 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
319 {
320 let max_block = if let Some(block) = self.debug.max_block {
321 Some(block)
322 } else if let Some(tip) = self.debug.tip {
323 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
324 } else {
325 None
326 };
327
328 Ok(max_block)
329 }
330
331 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
335 where
336 Factory: DatabaseProviderFactory<
337 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
338 >,
339 {
340 let provider = factory.database_provider_ro()?;
341
342 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
343
344 let header = provider
345 .header_by_number(head)?
346 .expect("the header for the latest block is missing, database is corrupt");
347
348 let hash = provider
349 .block_hash(head)?
350 .expect("the hash for the latest block is missing, database is corrupt");
351
352 Ok(Head {
353 number: head,
354 hash,
355 difficulty: header.difficulty(),
356 total_difficulty: U256::ZERO,
357 timestamp: header.timestamp(),
358 })
359 }
360
361 pub async fn lookup_or_fetch_tip<Provider, Client>(
366 &self,
367 provider: Provider,
368 client: Client,
369 tip: B256,
370 ) -> ProviderResult<u64>
371 where
372 Provider: HeaderProvider,
373 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
374 {
375 let header = provider.header_by_hash_or_number(tip.into())?;
376
377 if let Some(header) = header {
379 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
380 return Ok(header.number())
381 }
382
383 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
384 }
385
386 pub async fn fetch_tip_from_network<Client>(
390 &self,
391 client: Client,
392 tip: BlockHashOrNumber,
393 ) -> SealedHeader<Client::Header>
394 where
395 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
396 {
397 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
398 let mut fetch_failures = 0;
399 loop {
400 match get_single_header(&client, tip).await {
401 Ok(tip_header) => {
402 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
403 return tip_header
404 }
405 Err(error) => {
406 fetch_failures += 1;
407 if fetch_failures % 20 == 0 {
408 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
409 }
410 }
411 }
412 }
413 }
414
415 pub fn adjust_instance_ports(&mut self) {
418 self.network.adjust_instance_ports(self.instance);
419 self.rpc.adjust_instance_ports(self.instance);
420 }
421
422 pub fn with_unused_ports(mut self) -> Self {
425 self.rpc = self.rpc.with_unused_ports();
426 self.network = self.network.with_unused_ports();
427 self
428 }
429
430 pub const fn with_disabled_rpc_cache(mut self) -> Self {
435 self.rpc.rpc_state_cache.set_zero_lengths();
436 self
437 }
438
439 pub fn datadir(&self) -> ChainPath<DataDirPath>
441 where
442 ChainSpec: EthChainSpec,
443 {
444 self.datadir.clone().resolve_datadir(self.chain.chain())
445 }
446
447 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
452 path: impl AsRef<Path>,
453 ) -> eyre::Result<T> {
454 let path = path.as_ref();
455 match fs::read_to_string(path) {
456 Ok(cfg_string) => {
457 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
458 }
459 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
460 if let Some(parent) = path.parent() {
461 fs::create_dir_all(parent)
462 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
463 }
464 let cfg = T::default();
465 let s = toml::to_string_pretty(&cfg)
466 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
467 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
468 Ok(cfg)
469 }
470 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
471 }
472 }
473
474 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
476 where
477 F: FnOnce(Arc<ChainSpec>) -> C,
478 {
479 let chain = Arc::new(f(self.chain));
480 NodeConfig {
481 chain,
482 datadir: self.datadir,
483 config: self.config,
484 metrics: self.metrics,
485 instance: self.instance,
486 network: self.network,
487 rpc: self.rpc,
488 txpool: self.txpool,
489 builder: self.builder,
490 debug: self.debug,
491 db: self.db,
492 dev: self.dev,
493 pruning: self.pruning,
494 engine: self.engine,
495 era: self.era,
496 }
497 }
498
499 pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
501 where
502 Pool: TransactionPool + Unpin,
503 {
504 if let Some(interval) = self.dev.block_time {
505 MiningMode::interval(interval)
506 } else {
507 MiningMode::instant(pool, self.dev.block_max_transactions)
508 }
509 }
510}
511
512impl Default for NodeConfig<ChainSpec> {
513 fn default() -> Self {
514 Self::new(MAINNET.clone())
515 }
516}
517
518impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
519 fn clone(&self) -> Self {
520 Self {
521 chain: self.chain.clone(),
522 config: self.config.clone(),
523 metrics: self.metrics.clone(),
524 instance: self.instance,
525 network: self.network.clone(),
526 rpc: self.rpc.clone(),
527 txpool: self.txpool.clone(),
528 builder: self.builder.clone(),
529 debug: self.debug.clone(),
530 db: self.db,
531 dev: self.dev.clone(),
532 pruning: self.pruning.clone(),
533 datadir: self.datadir.clone(),
534 engine: self.engine.clone(),
535 era: self.era.clone(),
536 }
537 }
538}