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_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
153impl NodeConfig<ChainSpec> {
154 pub fn test() -> Self {
156 Self::default()
157 .with_unused_ports()
159 }
160}
161
162impl<ChainSpec> NodeConfig<ChainSpec> {
163 pub fn new(chain: Arc<ChainSpec>) -> Self {
165 Self {
166 config: None,
167 chain,
168 metrics: None,
169 instance: None,
170 network: NetworkArgs::default(),
171 rpc: RpcServerArgs::default(),
172 txpool: TxPoolArgs::default(),
173 builder: PayloadBuilderArgs::default(),
174 debug: DebugArgs::default(),
175 db: DatabaseArgs::default(),
176 dev: DevArgs::default(),
177 pruning: PruningArgs::default(),
178 datadir: DatadirArgs::default(),
179 engine: EngineArgs::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 const fn set_dev(self, dev: bool) -> Self {
195 if dev {
196 self.dev()
197 } else {
198 self
199 }
200 }
201
202 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
204 self.datadir = datadir_args;
205 self
206 }
207
208 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
210 self.config = Some(config.into());
211 self
212 }
213
214 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
216 self.chain = chain.into();
217 self
218 }
219
220 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
222 self.metrics = Some(metrics);
223 self
224 }
225
226 pub const fn with_instance(mut self, instance: u16) -> Self {
228 self.instance = Some(instance);
229 self
230 }
231
232 pub fn get_instance(&self) -> u16 {
234 self.instance.unwrap_or(1)
235 }
236
237 pub fn with_network(mut self, network: NetworkArgs) -> Self {
239 self.network = network;
240 self
241 }
242
243 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
245 self.rpc = rpc;
246 self
247 }
248
249 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
251 self.txpool = txpool;
252 self
253 }
254
255 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
257 self.builder = builder;
258 self
259 }
260
261 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
263 self.debug = debug;
264 self
265 }
266
267 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
269 self.db = db;
270 self
271 }
272
273 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
275 self.dev = dev;
276 self
277 }
278
279 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
281 self.pruning = pruning;
282 self
283 }
284
285 pub fn prune_config(&self) -> Option<PruneConfig>
287 where
288 ChainSpec: EthChainSpec,
289 {
290 self.pruning.prune_config(&self.chain)
291 }
292
293 pub async fn max_block<Provider, Client>(
296 &self,
297 network_client: Client,
298 provider: Provider,
299 ) -> eyre::Result<Option<BlockNumber>>
300 where
301 Provider: HeaderProvider,
302 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
303 {
304 let max_block = if let Some(block) = self.debug.max_block {
305 Some(block)
306 } else if let Some(tip) = self.debug.tip {
307 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
308 } else {
309 None
310 };
311
312 Ok(max_block)
313 }
314
315 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
319 where
320 Factory: DatabaseProviderFactory<
321 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
322 >,
323 {
324 let provider = factory.database_provider_ro()?;
325
326 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
327
328 let header = provider
329 .header_by_number(head)?
330 .expect("the header for the latest block is missing, database is corrupt");
331
332 let total_difficulty = provider
333 .header_td_by_number(head)?
334 .unwrap_or_default();
337
338 let hash = provider
339 .block_hash(head)?
340 .expect("the hash for the latest block is missing, database is corrupt");
341
342 Ok(Head {
343 number: head,
344 hash,
345 difficulty: header.difficulty(),
346 total_difficulty,
347 timestamp: header.timestamp(),
348 })
349 }
350
351 pub async fn lookup_or_fetch_tip<Provider, Client>(
356 &self,
357 provider: Provider,
358 client: Client,
359 tip: B256,
360 ) -> ProviderResult<u64>
361 where
362 Provider: HeaderProvider,
363 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
364 {
365 let header = provider.header_by_hash_or_number(tip.into())?;
366
367 if let Some(header) = header {
369 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
370 return Ok(header.number())
371 }
372
373 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
374 }
375
376 pub async fn fetch_tip_from_network<Client>(
380 &self,
381 client: Client,
382 tip: BlockHashOrNumber,
383 ) -> SealedHeader<Client::Header>
384 where
385 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
386 {
387 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
388 let mut fetch_failures = 0;
389 loop {
390 match get_single_header(&client, tip).await {
391 Ok(tip_header) => {
392 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
393 return tip_header
394 }
395 Err(error) => {
396 fetch_failures += 1;
397 if fetch_failures % 20 == 0 {
398 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
399 }
400 }
401 }
402 }
403 }
404
405 pub fn adjust_instance_ports(&mut self) {
408 self.network.adjust_instance_ports(self.instance);
409 self.rpc.adjust_instance_ports(self.instance);
410 }
411
412 pub fn with_unused_ports(mut self) -> Self {
415 self.rpc = self.rpc.with_unused_ports();
416 self.network = self.network.with_unused_ports();
417 self
418 }
419
420 pub fn datadir(&self) -> ChainPath<DataDirPath>
422 where
423 ChainSpec: EthChainSpec,
424 {
425 self.datadir.clone().resolve_datadir(self.chain.chain())
426 }
427
428 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
433 path: impl AsRef<Path>,
434 ) -> eyre::Result<T> {
435 let path = path.as_ref();
436 match fs::read_to_string(path) {
437 Ok(cfg_string) => {
438 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
439 }
440 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
441 if let Some(parent) = path.parent() {
442 fs::create_dir_all(parent)
443 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
444 }
445 let cfg = T::default();
446 let s = toml::to_string_pretty(&cfg)
447 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
448 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
449 Ok(cfg)
450 }
451 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
452 }
453 }
454
455 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
457 where
458 F: FnOnce(Arc<ChainSpec>) -> C,
459 {
460 let chain = Arc::new(f(self.chain));
461 NodeConfig {
462 chain,
463 datadir: self.datadir,
464 config: self.config,
465 metrics: self.metrics,
466 instance: self.instance,
467 network: self.network,
468 rpc: self.rpc,
469 txpool: self.txpool,
470 builder: self.builder,
471 debug: self.debug,
472 db: self.db,
473 dev: self.dev,
474 pruning: self.pruning,
475 engine: self.engine,
476 }
477 }
478}
479
480impl Default for NodeConfig<ChainSpec> {
481 fn default() -> Self {
482 Self::new(MAINNET.clone())
483 }
484}
485
486impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
487 fn clone(&self) -> Self {
488 Self {
489 chain: self.chain.clone(),
490 config: self.config.clone(),
491 metrics: self.metrics,
492 instance: self.instance,
493 network: self.network.clone(),
494 rpc: self.rpc.clone(),
495 txpool: self.txpool.clone(),
496 builder: self.builder.clone(),
497 debug: self.debug.clone(),
498 db: self.db,
499 dev: self.dev,
500 pruning: self.pruning.clone(),
501 datadir: self.datadir.clone(),
502 engine: self.engine.clone(),
503 }
504 }
505}