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};
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 net::SocketAddr,
31 path::{Path, PathBuf},
32 sync::Arc,
33};
34use tracing::*;
35
36use crate::args::EraArgs;
37pub use reth_engine_primitives::{
38 DEFAULT_MAX_PROOF_TASK_CONCURRENCY, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
39 DEFAULT_RESERVED_CPU_CORES,
40};
41
42pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
44
45pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
47
48#[derive(Debug)]
94pub struct NodeConfig<ChainSpec> {
95 pub datadir: DatadirArgs,
97
98 pub config: Option<PathBuf>,
100
101 pub chain: Arc<ChainSpec>,
105
106 pub metrics: Option<SocketAddr>,
110
111 pub instance: Option<u16>,
127
128 pub network: NetworkArgs,
130
131 pub rpc: RpcServerArgs,
133
134 pub txpool: TxPoolArgs,
136
137 pub builder: PayloadBuilderArgs,
139
140 pub debug: DebugArgs,
142
143 pub db: DatabaseArgs,
145
146 pub dev: DevArgs,
148
149 pub pruning: PruningArgs,
151
152 pub engine: EngineArgs,
154
155 pub era: EraArgs,
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: None,
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 }
188 }
189
190 pub const fn dev(mut self) -> Self {
195 self.dev.dev = true;
196 self.network.discovery.disable_discovery = true;
197 self
198 }
199
200 pub const fn set_dev(self, dev: bool) -> Self {
202 if dev {
203 self.dev()
204 } else {
205 self
206 }
207 }
208
209 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
211 self.datadir = datadir_args;
212 self
213 }
214
215 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
217 self.config = Some(config.into());
218 self
219 }
220
221 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
223 self.chain = chain.into();
224 self
225 }
226
227 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
229 self.metrics = Some(metrics);
230 self
231 }
232
233 pub const fn with_instance(mut self, instance: u16) -> Self {
235 self.instance = Some(instance);
236 self
237 }
238
239 pub fn get_instance(&self) -> u16 {
241 self.instance.unwrap_or(1)
242 }
243
244 pub fn with_network(mut self, network: NetworkArgs) -> Self {
246 self.network = network;
247 self
248 }
249
250 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
252 self.rpc = rpc;
253 self
254 }
255
256 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
258 self.txpool = txpool;
259 self
260 }
261
262 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
264 self.builder = builder;
265 self
266 }
267
268 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
270 self.debug = debug;
271 self
272 }
273
274 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
276 self.db = db;
277 self
278 }
279
280 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
282 self.dev = dev;
283 self
284 }
285
286 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
288 self.pruning = pruning;
289 self
290 }
291
292 pub fn prune_config(&self) -> Option<PruneConfig>
294 where
295 ChainSpec: EthereumHardforks,
296 {
297 self.pruning.prune_config(&self.chain)
298 }
299
300 pub async fn max_block<Provider, Client>(
303 &self,
304 network_client: Client,
305 provider: Provider,
306 ) -> eyre::Result<Option<BlockNumber>>
307 where
308 Provider: HeaderProvider,
309 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
310 {
311 let max_block = if let Some(block) = self.debug.max_block {
312 Some(block)
313 } else if let Some(tip) = self.debug.tip {
314 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
315 } else {
316 None
317 };
318
319 Ok(max_block)
320 }
321
322 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
326 where
327 Factory: DatabaseProviderFactory<
328 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
329 >,
330 {
331 let provider = factory.database_provider_ro()?;
332
333 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
334
335 let header = provider
336 .header_by_number(head)?
337 .expect("the header for the latest block is missing, database is corrupt");
338
339 let total_difficulty = provider
340 .header_td_by_number(head)?
341 .unwrap_or_default();
344
345 let hash = provider
346 .block_hash(head)?
347 .expect("the hash for the latest block is missing, database is corrupt");
348
349 Ok(Head {
350 number: head,
351 hash,
352 difficulty: header.difficulty(),
353 total_difficulty,
354 timestamp: header.timestamp(),
355 })
356 }
357
358 pub async fn lookup_or_fetch_tip<Provider, Client>(
363 &self,
364 provider: Provider,
365 client: Client,
366 tip: B256,
367 ) -> ProviderResult<u64>
368 where
369 Provider: HeaderProvider,
370 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
371 {
372 let header = provider.header_by_hash_or_number(tip.into())?;
373
374 if let Some(header) = header {
376 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
377 return Ok(header.number())
378 }
379
380 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
381 }
382
383 pub async fn fetch_tip_from_network<Client>(
387 &self,
388 client: Client,
389 tip: BlockHashOrNumber,
390 ) -> SealedHeader<Client::Header>
391 where
392 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
393 {
394 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
395 let mut fetch_failures = 0;
396 loop {
397 match get_single_header(&client, tip).await {
398 Ok(tip_header) => {
399 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
400 return tip_header
401 }
402 Err(error) => {
403 fetch_failures += 1;
404 if fetch_failures % 20 == 0 {
405 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
406 }
407 }
408 }
409 }
410 }
411
412 pub fn adjust_instance_ports(&mut self) {
415 self.network.adjust_instance_ports(self.instance);
416 self.rpc.adjust_instance_ports(self.instance);
417 }
418
419 pub fn with_unused_ports(mut self) -> Self {
422 self.rpc = self.rpc.with_unused_ports();
423 self.network = self.network.with_unused_ports();
424 self
425 }
426
427 pub const fn with_disabled_rpc_cache(mut self) -> Self {
432 self.rpc.rpc_state_cache.set_zero_lengths();
433 self
434 }
435
436 pub fn datadir(&self) -> ChainPath<DataDirPath>
438 where
439 ChainSpec: EthChainSpec,
440 {
441 self.datadir.clone().resolve_datadir(self.chain.chain())
442 }
443
444 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
449 path: impl AsRef<Path>,
450 ) -> eyre::Result<T> {
451 let path = path.as_ref();
452 match fs::read_to_string(path) {
453 Ok(cfg_string) => {
454 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
455 }
456 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
457 if let Some(parent) = path.parent() {
458 fs::create_dir_all(parent)
459 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
460 }
461 let cfg = T::default();
462 let s = toml::to_string_pretty(&cfg)
463 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
464 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
465 Ok(cfg)
466 }
467 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
468 }
469 }
470
471 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
473 where
474 F: FnOnce(Arc<ChainSpec>) -> C,
475 {
476 let chain = Arc::new(f(self.chain));
477 NodeConfig {
478 chain,
479 datadir: self.datadir,
480 config: self.config,
481 metrics: self.metrics,
482 instance: self.instance,
483 network: self.network,
484 rpc: self.rpc,
485 txpool: self.txpool,
486 builder: self.builder,
487 debug: self.debug,
488 db: self.db,
489 dev: self.dev,
490 pruning: self.pruning,
491 engine: self.engine,
492 era: self.era,
493 }
494 }
495
496 pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
498 where
499 Pool: TransactionPool + Unpin,
500 {
501 if let Some(interval) = self.dev.block_time {
502 MiningMode::interval(interval)
503 } else {
504 MiningMode::instant(pool, self.dev.block_max_transactions)
505 }
506 }
507}
508
509impl Default for NodeConfig<ChainSpec> {
510 fn default() -> Self {
511 Self::new(MAINNET.clone())
512 }
513}
514
515impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
516 fn clone(&self) -> Self {
517 Self {
518 chain: self.chain.clone(),
519 config: self.config.clone(),
520 metrics: self.metrics,
521 instance: self.instance,
522 network: self.network.clone(),
523 rpc: self.rpc.clone(),
524 txpool: self.txpool.clone(),
525 builder: self.builder.clone(),
526 debug: self.debug.clone(),
527 db: self.db,
528 dev: self.dev,
529 pruning: self.pruning.clone(),
530 datadir: self.datadir.clone(),
531 engine: self.engine.clone(),
532 era: self.era.clone(),
533 }
534 }
535}