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