1use crate::{
4 args::{
5 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EnclaveArgs, EngineArgs, NetworkArgs,
6 PayloadBuilderArgs, 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_ethereum_forks::Head;
18use reth_network_p2p::headers::client::HeadersClient;
19use reth_primitives_traits::SealedHeader;
20use reth_stages_types::StageId;
21use reth_storage_api::{
22 BlockHashReader, DatabaseProviderFactory, HeaderProvider, StageCheckpointReader,
23};
24use reth_storage_errors::provider::ProviderResult;
25use serde::{de::DeserializeOwned, Serialize};
26use std::{
27 fs,
28 net::SocketAddr,
29 path::{Path, PathBuf},
30 sync::Arc,
31};
32use tracing::*;
33
34pub use reth_engine_primitives::{
35 DEFAULT_MAX_PROOF_TASK_CONCURRENCY, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
36 DEFAULT_RESERVED_CPU_CORES,
37};
38
39pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
41
42pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 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: Option<SocketAddr>,
107
108 pub instance: Option<u16>,
124
125 pub network: NetworkArgs,
127
128 pub rpc: RpcServerArgs,
130
131 pub txpool: TxPoolArgs,
133
134 pub builder: PayloadBuilderArgs,
136
137 pub debug: DebugArgs,
139
140 pub db: DatabaseArgs,
142
143 pub dev: DevArgs,
145
146 pub pruning: PruningArgs,
148
149 pub engine: EngineArgs,
151
152 pub enclave: EnclaveArgs,
154}
155
156impl NodeConfig<ChainSpec> {
157 pub fn test() -> Self {
159 Self::default()
160 .with_unused_ports()
162 }
163}
164
165impl<ChainSpec> NodeConfig<ChainSpec> {
166 pub fn new(chain: Arc<ChainSpec>) -> Self {
168 Self {
169 config: None,
170 chain,
171 metrics: None,
172 instance: None,
173 network: NetworkArgs::default(),
174 rpc: RpcServerArgs::default(),
175 txpool: TxPoolArgs::default(),
176 builder: PayloadBuilderArgs::default(),
177 debug: DebugArgs::default(),
178 db: DatabaseArgs::default(),
179 dev: DevArgs::default(),
180 pruning: PruningArgs::default(),
181 datadir: DatadirArgs::default(),
182 engine: EngineArgs::default(),
183 enclave: EnclaveArgs::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 const fn set_dev(self, dev: bool) -> Self {
199 if dev {
200 self.dev()
201 } else {
202 self
203 }
204 }
205
206 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
208 self.datadir = datadir_args;
209 self
210 }
211
212 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
214 self.config = Some(config.into());
215 self
216 }
217
218 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
220 self.chain = chain.into();
221 self
222 }
223
224 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
226 self.metrics = Some(metrics);
227 self
228 }
229
230 pub const fn with_instance(mut self, instance: u16) -> Self {
232 self.instance = Some(instance);
233 self
234 }
235
236 pub fn get_instance(&self) -> u16 {
238 self.instance.unwrap_or(1)
239 }
240
241 pub fn with_network(mut self, network: NetworkArgs) -> Self {
243 self.network = network;
244 self
245 }
246
247 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
249 self.rpc = rpc;
250 self
251 }
252
253 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
255 self.txpool = txpool;
256 self
257 }
258
259 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
261 self.builder = builder;
262 self
263 }
264
265 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
267 self.debug = debug;
268 self
269 }
270
271 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
273 self.db = db;
274 self
275 }
276
277 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
279 self.dev = dev;
280 self
281 }
282
283 pub const fn with_enclave(mut self, enclave: EnclaveArgs) -> Self {
285 self.enclave = enclave;
286 self
287 }
288
289 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
291 self.pruning = pruning;
292 self
293 }
294
295 pub fn prune_config(&self) -> Option<PruneConfig> {
297 self.pruning.prune_config()
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 enclave: self.enclave,
493 }
494 }
495}
496
497impl Default for NodeConfig<ChainSpec> {
498 fn default() -> Self {
499 Self::new(MAINNET.clone())
500 }
501}
502
503impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
504 fn clone(&self) -> Self {
505 Self {
506 chain: self.chain.clone(),
507 config: self.config.clone(),
508 metrics: self.metrics,
509 instance: self.instance,
510 network: self.network.clone(),
511 rpc: self.rpc.clone(),
512 txpool: self.txpool.clone(),
513 builder: self.builder.clone(),
514 debug: self.debug.clone(),
515 db: self.db,
516 dev: self.dev,
517 pruning: self.pruning.clone(),
518 datadir: self.datadir.clone(),
519 engine: self.engine.clone(),
520 enclave: self.enclave.clone(),
521 }
522 }
523}