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::DEFAULT_MEMORY_BLOCK_BUFFER_TARGET;
35
36pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
38
39pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
41
42#[derive(Debug)]
88pub struct NodeConfig<ChainSpec> {
89 pub datadir: DatadirArgs,
91
92 pub config: Option<PathBuf>,
94
95 pub chain: Arc<ChainSpec>,
99
100 pub metrics: Option<SocketAddr>,
104
105 pub instance: 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
149impl NodeConfig<ChainSpec> {
150 pub fn test() -> Self {
152 Self::default()
153 .with_unused_ports()
155 }
156}
157
158impl<ChainSpec> NodeConfig<ChainSpec> {
159 pub fn new(chain: Arc<ChainSpec>) -> Self {
161 Self {
162 config: None,
163 chain,
164 metrics: None,
165 instance: 1,
166 network: NetworkArgs::default(),
167 rpc: RpcServerArgs::default(),
168 txpool: TxPoolArgs::default(),
169 builder: PayloadBuilderArgs::default(),
170 debug: DebugArgs::default(),
171 db: DatabaseArgs::default(),
172 dev: DevArgs::default(),
173 pruning: PruningArgs::default(),
174 datadir: DatadirArgs::default(),
175 engine: EngineArgs::default(),
176 }
177 }
178
179 pub const fn dev(mut self) -> Self {
184 self.dev.dev = true;
185 self.network.discovery.disable_discovery = true;
186 self
187 }
188
189 pub const fn set_dev(self, dev: bool) -> Self {
191 if dev {
192 self.dev()
193 } else {
194 self
195 }
196 }
197
198 pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
200 self.datadir = datadir_args;
201 self
202 }
203
204 pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
206 self.config = Some(config.into());
207 self
208 }
209
210 pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
212 self.chain = chain.into();
213 self
214 }
215
216 pub const fn with_metrics(mut self, metrics: SocketAddr) -> Self {
218 self.metrics = Some(metrics);
219 self
220 }
221
222 pub const fn with_instance(mut self, instance: u16) -> Self {
224 self.instance = instance;
225 self
226 }
227
228 pub fn with_network(mut self, network: NetworkArgs) -> Self {
230 self.network = network;
231 self
232 }
233
234 pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
236 self.rpc = rpc;
237 self
238 }
239
240 pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
242 self.txpool = txpool;
243 self
244 }
245
246 pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
248 self.builder = builder;
249 self
250 }
251
252 pub fn with_debug(mut self, debug: DebugArgs) -> Self {
254 self.debug = debug;
255 self
256 }
257
258 pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
260 self.db = db;
261 self
262 }
263
264 pub const fn with_dev(mut self, dev: DevArgs) -> Self {
266 self.dev = dev;
267 self
268 }
269
270 pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
272 self.pruning = pruning;
273 self
274 }
275
276 pub fn prune_config(&self) -> Option<PruneConfig>
278 where
279 ChainSpec: EthChainSpec,
280 {
281 self.pruning.prune_config(&self.chain)
282 }
283
284 pub async fn max_block<Provider, Client>(
287 &self,
288 network_client: Client,
289 provider: Provider,
290 ) -> eyre::Result<Option<BlockNumber>>
291 where
292 Provider: HeaderProvider,
293 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
294 {
295 let max_block = if let Some(block) = self.debug.max_block {
296 Some(block)
297 } else if let Some(tip) = self.debug.tip {
298 Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
299 } else {
300 None
301 };
302
303 Ok(max_block)
304 }
305
306 pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
310 where
311 Factory: DatabaseProviderFactory<
312 Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
313 >,
314 {
315 let provider = factory.database_provider_ro()?;
316
317 let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
318
319 let header = provider
320 .header_by_number(head)?
321 .expect("the header for the latest block is missing, database is corrupt");
322
323 let total_difficulty = provider
324 .header_td_by_number(head)?
325 .unwrap_or_default();
328
329 let hash = provider
330 .block_hash(head)?
331 .expect("the hash for the latest block is missing, database is corrupt");
332
333 Ok(Head {
334 number: head,
335 hash,
336 difficulty: header.difficulty(),
337 total_difficulty,
338 timestamp: header.timestamp(),
339 })
340 }
341
342 pub async fn lookup_or_fetch_tip<Provider, Client>(
347 &self,
348 provider: Provider,
349 client: Client,
350 tip: B256,
351 ) -> ProviderResult<u64>
352 where
353 Provider: HeaderProvider,
354 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
355 {
356 let header = provider.header_by_hash_or_number(tip.into())?;
357
358 if let Some(header) = header {
360 info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
361 return Ok(header.number())
362 }
363
364 Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
365 }
366
367 pub async fn fetch_tip_from_network<Client>(
371 &self,
372 client: Client,
373 tip: BlockHashOrNumber,
374 ) -> SealedHeader<Client::Header>
375 where
376 Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
377 {
378 info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
379 let mut fetch_failures = 0;
380 loop {
381 match get_single_header(&client, tip).await {
382 Ok(tip_header) => {
383 info!(target: "reth::cli", ?tip, "Successfully fetched tip");
384 return tip_header
385 }
386 Err(error) => {
387 fetch_failures += 1;
388 if fetch_failures % 20 == 0 {
389 error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
390 }
391 }
392 }
393 }
394 }
395
396 pub fn adjust_instance_ports(&mut self) {
399 self.rpc.adjust_instance_ports(self.instance);
400 self.network.adjust_instance_ports(self.instance);
401 }
402
403 pub fn with_unused_ports(mut self) -> Self {
406 self.rpc = self.rpc.with_unused_ports();
407 self.network = self.network.with_unused_ports();
408 self
409 }
410
411 pub fn datadir(&self) -> ChainPath<DataDirPath>
413 where
414 ChainSpec: EthChainSpec,
415 {
416 self.datadir.clone().resolve_datadir(self.chain.chain())
417 }
418
419 pub fn load_path<T: Serialize + DeserializeOwned + Default>(
424 path: impl AsRef<Path>,
425 ) -> eyre::Result<T> {
426 let path = path.as_ref();
427 match fs::read_to_string(path) {
428 Ok(cfg_string) => {
429 toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
430 }
431 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
432 if let Some(parent) = path.parent() {
433 fs::create_dir_all(parent)
434 .map_err(|e| eyre!("Failed to create directory: {e}"))?;
435 }
436 let cfg = T::default();
437 let s = toml::to_string_pretty(&cfg)
438 .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
439 fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
440 Ok(cfg)
441 }
442 Err(e) => Err(eyre!("Failed to load configuration: {e}")),
443 }
444 }
445
446 pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
448 where
449 F: FnOnce(Arc<ChainSpec>) -> C,
450 {
451 let chain = Arc::new(f(self.chain));
452 NodeConfig {
453 chain,
454 datadir: self.datadir,
455 config: self.config,
456 metrics: self.metrics,
457 instance: self.instance,
458 network: self.network,
459 rpc: self.rpc,
460 txpool: self.txpool,
461 builder: self.builder,
462 debug: self.debug,
463 db: self.db,
464 dev: self.dev,
465 pruning: self.pruning,
466 engine: self.engine,
467 }
468 }
469}
470
471impl Default for NodeConfig<ChainSpec> {
472 fn default() -> Self {
473 Self::new(MAINNET.clone())
474 }
475}
476
477impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
478 fn clone(&self) -> Self {
479 Self {
480 chain: self.chain.clone(),
481 config: self.config.clone(),
482 metrics: self.metrics,
483 instance: self.instance,
484 network: self.network.clone(),
485 rpc: self.rpc.clone(),
486 txpool: self.txpool.clone(),
487 builder: self.builder.clone(),
488 debug: self.debug.clone(),
489 db: self.db,
490 dev: self.dev,
491 pruning: self.pruning.clone(),
492 datadir: self.datadir.clone(),
493 engine: self.engine.clone(),
494 }
495 }
496}