reth_node_core/
node_config.rs

1//! Support for customizing the node
2
3use crate::{
4    args::{
5        DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, NetworkArgs, PayloadBuilderArgs,
6        PruningArgs, RpcServerArgs, StaticFilesArgs, 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
40/// Default size of cross-block cache in megabytes.
41pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
42
43/// This includes all necessary configuration to launch the node.
44/// The individual configuration options can be overwritten before launching the node.
45///
46/// # Example
47/// ```rust
48/// # use reth_node_core::{
49/// #     node_config::NodeConfig,
50/// #     args::RpcServerArgs,
51/// # };
52/// # use reth_rpc_server_types::RpcModuleSelection;
53/// # use tokio::runtime::Handle;
54///
55/// async fn t() {
56///     // create the builder
57///     let builder = NodeConfig::default();
58///
59///     // configure the rpc apis
60///     let mut rpc = RpcServerArgs::default().with_http().with_ws();
61///     rpc.http_api = Some(RpcModuleSelection::All);
62///     let builder = builder.with_rpc(rpc);
63/// }
64/// ```
65///
66/// This can also be used to launch a node with a temporary test database. This can be done with
67/// the [`NodeConfig::test`] method.
68///
69/// # Example
70/// ```rust
71/// # use reth_node_core::{
72/// #     node_config::NodeConfig,
73/// #     args::RpcServerArgs,
74/// # };
75/// # use reth_rpc_server_types::RpcModuleSelection;
76/// # use tokio::runtime::Handle;
77///
78/// async fn t() {
79///     // create the builder with a test database, using the `test` method
80///     let builder = NodeConfig::test();
81///
82///     // configure the rpc apis
83///     let mut rpc = RpcServerArgs::default().with_http().with_ws();
84///     rpc.http_api = Some(RpcModuleSelection::All);
85///     let builder = builder.with_rpc(rpc);
86/// }
87/// ```
88#[derive(Debug)]
89pub struct NodeConfig<ChainSpec> {
90    /// All data directory related arguments
91    pub datadir: DatadirArgs,
92
93    /// The path to the configuration file to use.
94    pub config: Option<PathBuf>,
95
96    /// The chain this node is running.
97    ///
98    /// Possible values are either a built-in chain or the path to a chain specification file.
99    pub chain: Arc<ChainSpec>,
100
101    /// Enable to configure metrics export to endpoints
102    pub metrics: MetricArgs,
103
104    /// Add a new instance of a node.
105    ///
106    /// Configures the ports of the node to avoid conflicts with the defaults.
107    /// This is useful for running multiple nodes on the same machine.
108    ///
109    /// Max number of instances is 200. It is chosen in a way so that it's not possible to have
110    /// port numbers that conflict with each other.
111    ///
112    /// Changes to the following port numbers:
113    /// - `DISCOVERY_PORT`: default + `instance` - 1
114    /// - `DISCOVERY_V5_PORT`: default + `instance` - 1
115    /// - `AUTH_PORT`: default + `instance` * 100 - 100
116    /// - `HTTP_RPC_PORT`: default - `instance` + 1
117    /// - `WS_RPC_PORT`: default + `instance` * 2 - 2
118    /// - `IPC_PATH`: default + `instance`
119    pub instance: Option<u16>,
120
121    /// All networking related arguments
122    pub network: NetworkArgs,
123
124    /// All rpc related arguments
125    pub rpc: RpcServerArgs,
126
127    /// All txpool related arguments with --txpool prefix
128    pub txpool: TxPoolArgs,
129
130    /// All payload builder related arguments
131    pub builder: PayloadBuilderArgs,
132
133    /// All debug related arguments with --debug prefix
134    pub debug: DebugArgs,
135
136    /// All database related arguments
137    pub db: DatabaseArgs,
138
139    /// All dev related arguments with --dev prefix
140    pub dev: DevArgs,
141
142    /// All pruning related arguments
143    pub pruning: PruningArgs,
144
145    /// All engine related arguments
146    pub engine: EngineArgs,
147
148    /// All ERA import related arguments with --era prefix
149    pub era: EraArgs,
150
151    /// All static files related arguments
152    pub static_files: StaticFilesArgs,
153}
154
155impl NodeConfig<ChainSpec> {
156    /// Creates a testing [`NodeConfig`], causing the database to be launched ephemerally.
157    pub fn test() -> Self {
158        Self::default()
159            // set all ports to zero by default for test instances
160            .with_unused_ports()
161    }
162}
163
164impl<ChainSpec> NodeConfig<ChainSpec> {
165    /// Creates a new config with given chain spec, setting all fields to default values.
166    pub fn new(chain: Arc<ChainSpec>) -> Self {
167        Self {
168            config: None,
169            chain,
170            metrics: MetricArgs::default(),
171            instance: None,
172            network: NetworkArgs::default(),
173            rpc: RpcServerArgs::default(),
174            txpool: TxPoolArgs::default(),
175            builder: PayloadBuilderArgs::default(),
176            debug: DebugArgs::default(),
177            db: DatabaseArgs::default(),
178            dev: DevArgs::default(),
179            pruning: PruningArgs::default(),
180            datadir: DatadirArgs::default(),
181            engine: EngineArgs::default(),
182            era: EraArgs::default(),
183            static_files: StaticFilesArgs::default(),
184        }
185    }
186
187    /// Sets --dev mode for the node.
188    ///
189    /// In addition to setting the `--dev` flag, this also:
190    ///   - disables discovery in [`NetworkArgs`].
191    pub const fn dev(mut self) -> Self {
192        self.dev.dev = true;
193        self.network.discovery.disable_discovery = true;
194        self
195    }
196
197    /// Apply a function to the config.
198    pub fn apply<F>(self, f: F) -> Self
199    where
200        F: FnOnce(Self) -> Self,
201    {
202        f(self)
203    }
204
205    /// Applies a fallible function to the config.
206    pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
207    where
208        F: FnOnce(Self) -> Result<Self, R>,
209    {
210        f(self)
211    }
212
213    /// Sets --dev mode for the node [`NodeConfig::dev`], if `dev` is true.
214    pub const fn set_dev(self, dev: bool) -> Self {
215        if dev {
216            self.dev()
217        } else {
218            self
219        }
220    }
221
222    /// Set the data directory args for the node
223    pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
224        self.datadir = datadir_args;
225        self
226    }
227
228    /// Set the config file for the node
229    pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
230        self.config = Some(config.into());
231        self
232    }
233
234    /// Set the [`ChainSpec`] for the node
235    pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
236        self.chain = chain.into();
237        self
238    }
239
240    /// Set the [`ChainSpec`] for the node and converts the type to that chainid.
241    pub fn map_chain<C>(self, chain: impl Into<Arc<C>>) -> NodeConfig<C> {
242        let Self {
243            datadir,
244            config,
245            metrics,
246            instance,
247            network,
248            rpc,
249            txpool,
250            builder,
251            debug,
252            db,
253            dev,
254            pruning,
255            engine,
256            era,
257            static_files,
258            ..
259        } = self;
260        NodeConfig {
261            datadir,
262            config,
263            chain: chain.into(),
264            metrics,
265            instance,
266            network,
267            rpc,
268            txpool,
269            builder,
270            debug,
271            db,
272            dev,
273            pruning,
274            engine,
275            era,
276            static_files,
277        }
278    }
279
280    /// Set the metrics address for the node
281    pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
282        self.metrics = metrics;
283        self
284    }
285
286    /// Set the instance for the node
287    pub const fn with_instance(mut self, instance: u16) -> Self {
288        self.instance = Some(instance);
289        self
290    }
291
292    /// Returns the instance value, defaulting to 1 if not set.
293    pub fn get_instance(&self) -> u16 {
294        self.instance.unwrap_or(1)
295    }
296
297    /// Set the network args for the node
298    pub fn with_network(mut self, network: NetworkArgs) -> Self {
299        self.network = network;
300        self
301    }
302
303    /// Set the rpc args for the node
304    pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
305        self.rpc = rpc;
306        self
307    }
308
309    /// Set the txpool args for the node
310    pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
311        self.txpool = txpool;
312        self
313    }
314
315    /// Set the builder args for the node
316    pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
317        self.builder = builder;
318        self
319    }
320
321    /// Set the debug args for the node
322    pub fn with_debug(mut self, debug: DebugArgs) -> Self {
323        self.debug = debug;
324        self
325    }
326
327    /// Set the database args for the node
328    pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
329        self.db = db;
330        self
331    }
332
333    /// Set the dev args for the node
334    pub fn with_dev(mut self, dev: DevArgs) -> Self {
335        self.dev = dev;
336        self
337    }
338
339    /// Set the pruning args for the node
340    pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
341        self.pruning = pruning;
342        self
343    }
344
345    /// Returns pruning configuration.
346    pub fn prune_config(&self) -> Option<PruneConfig>
347    where
348        ChainSpec: EthereumHardforks,
349    {
350        self.pruning.prune_config(&self.chain)
351    }
352
353    /// Returns the max block that the node should run to, looking it up from the network if
354    /// necessary
355    pub async fn max_block<Provider, Client>(
356        &self,
357        network_client: Client,
358        provider: Provider,
359    ) -> eyre::Result<Option<BlockNumber>>
360    where
361        Provider: HeaderProvider,
362        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
363    {
364        let max_block = if let Some(block) = self.debug.max_block {
365            Some(block)
366        } else if let Some(tip) = self.debug.tip {
367            Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
368        } else {
369            None
370        };
371
372        Ok(max_block)
373    }
374
375    /// Fetches the head block from the database.
376    ///
377    /// If the database is empty, returns the genesis block.
378    pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
379    where
380        Factory: DatabaseProviderFactory<
381            Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
382        >,
383    {
384        let provider = factory.database_provider_ro()?;
385
386        let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
387
388        let header = provider
389            .header_by_number(head)?
390            .expect("the header for the latest block is missing, database is corrupt");
391
392        let hash = provider
393            .block_hash(head)?
394            .expect("the hash for the latest block is missing, database is corrupt");
395
396        Ok(Head {
397            number: head,
398            hash,
399            difficulty: header.difficulty(),
400            total_difficulty: U256::ZERO,
401            timestamp: header.timestamp(),
402        })
403    }
404
405    /// Attempt to look up the block number for the tip hash in the database.
406    /// If it doesn't exist, download the header and return the block number.
407    ///
408    /// NOTE: The download is attempted with infinite retries.
409    pub async fn lookup_or_fetch_tip<Provider, Client>(
410        &self,
411        provider: Provider,
412        client: Client,
413        tip: B256,
414    ) -> ProviderResult<u64>
415    where
416        Provider: HeaderProvider,
417        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
418    {
419        let header = provider.header_by_hash_or_number(tip.into())?;
420
421        // try to look up the header in the database
422        if let Some(header) = header {
423            info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
424            return Ok(header.number())
425        }
426
427        Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
428    }
429
430    /// Attempt to look up the block with the given number and return the header.
431    ///
432    /// NOTE: The download is attempted with infinite retries.
433    pub async fn fetch_tip_from_network<Client>(
434        &self,
435        client: Client,
436        tip: BlockHashOrNumber,
437    ) -> SealedHeader<Client::Header>
438    where
439        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
440    {
441        info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
442        let mut fetch_failures = 0;
443        loop {
444            match get_single_header(&client, tip).await {
445                Ok(tip_header) => {
446                    info!(target: "reth::cli", ?tip, "Successfully fetched tip");
447                    return tip_header
448                }
449                Err(error) => {
450                    fetch_failures += 1;
451                    if fetch_failures % 20 == 0 {
452                        error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
453                    }
454                }
455            }
456        }
457    }
458
459    /// Change rpc port numbers based on the instance number, using the inner
460    /// [`RpcServerArgs::adjust_instance_ports`] method.
461    pub fn adjust_instance_ports(&mut self) {
462        self.network.adjust_instance_ports(self.instance);
463        self.rpc.adjust_instance_ports(self.instance);
464    }
465
466    /// Sets networking and RPC ports to zero, causing the OS to choose random unused ports when
467    /// sockets are bound.
468    pub fn with_unused_ports(mut self) -> Self {
469        self.rpc = self.rpc.with_unused_ports();
470        self.network = self.network.with_unused_ports();
471        self
472    }
473
474    /// Disables all discovery services for the node.
475    pub const fn with_disabled_discovery(mut self) -> Self {
476        self.network.discovery.disable_discovery = true;
477        self
478    }
479
480    /// Effectively disables the RPC state cache by setting the cache sizes to `0`.
481    ///
482    /// By setting the cache sizes to 0, caching of newly executed or fetched blocks will be
483    /// effectively disabled.
484    pub const fn with_disabled_rpc_cache(mut self) -> Self {
485        self.rpc.rpc_state_cache.set_zero_lengths();
486        self
487    }
488
489    /// Resolve the final datadir path.
490    pub fn datadir(&self) -> ChainPath<DataDirPath>
491    where
492        ChainSpec: EthChainSpec,
493    {
494        self.datadir.clone().resolve_datadir(self.chain.chain())
495    }
496
497    /// Load an application configuration from a specified path.
498    ///
499    /// A new configuration file is created with default values if none
500    /// exists.
501    pub fn load_path<T: Serialize + DeserializeOwned + Default>(
502        path: impl AsRef<Path>,
503    ) -> eyre::Result<T> {
504        let path = path.as_ref();
505        match fs::read_to_string(path) {
506            Ok(cfg_string) => {
507                toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
508            }
509            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
510                if let Some(parent) = path.parent() {
511                    fs::create_dir_all(parent)
512                        .map_err(|e| eyre!("Failed to create directory: {e}"))?;
513                }
514                let cfg = T::default();
515                let s = toml::to_string_pretty(&cfg)
516                    .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
517                fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
518                Ok(cfg)
519            }
520            Err(e) => Err(eyre!("Failed to load configuration: {e}")),
521        }
522    }
523
524    /// Modifies the [`ChainSpec`] generic of the config using the provided closure.
525    pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
526    where
527        F: FnOnce(Arc<ChainSpec>) -> C,
528    {
529        let chain = Arc::new(f(self.chain));
530        NodeConfig {
531            chain,
532            datadir: self.datadir,
533            config: self.config,
534            metrics: self.metrics,
535            instance: self.instance,
536            network: self.network,
537            rpc: self.rpc,
538            txpool: self.txpool,
539            builder: self.builder,
540            debug: self.debug,
541            db: self.db,
542            dev: self.dev,
543            pruning: self.pruning,
544            engine: self.engine,
545            era: self.era,
546            static_files: self.static_files,
547        }
548    }
549
550    /// Returns the [`MiningMode`] intended for --dev mode.
551    pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
552    where
553        Pool: TransactionPool + Unpin,
554    {
555        if let Some(interval) = self.dev.block_time {
556            MiningMode::interval(interval)
557        } else {
558            MiningMode::instant(pool, self.dev.block_max_transactions)
559        }
560    }
561}
562
563impl Default for NodeConfig<ChainSpec> {
564    fn default() -> Self {
565        Self::new(MAINNET.clone())
566    }
567}
568
569impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
570    fn clone(&self) -> Self {
571        Self {
572            chain: self.chain.clone(),
573            config: self.config.clone(),
574            metrics: self.metrics.clone(),
575            instance: self.instance,
576            network: self.network.clone(),
577            rpc: self.rpc.clone(),
578            txpool: self.txpool.clone(),
579            builder: self.builder.clone(),
580            debug: self.debug.clone(),
581            db: self.db,
582            dev: self.dev.clone(),
583            pruning: self.pruning.clone(),
584            datadir: self.datadir.clone(),
585            engine: self.engine.clone(),
586            era: self.era.clone(),
587            static_files: self.static_files,
588        }
589    }
590}