Skip to main content

reth_node_core/
node_config.rs

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