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, NetworkArgs, PayloadBuilderArgs,
6        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
159impl NodeConfig<ChainSpec> {
160    /// Creates a testing [`NodeConfig`], causing the database to be launched ephemerally.
161    pub fn test() -> Self {
162        Self::default()
163            // set all ports to zero by default for test instances
164            .with_unused_ports()
165    }
166}
167
168impl<ChainSpec> NodeConfig<ChainSpec> {
169    /// Creates a new config with given chain spec, setting all fields to default values.
170    pub fn new(chain: Arc<ChainSpec>) -> Self {
171        Self {
172            config: None,
173            chain,
174            metrics: MetricArgs::default(),
175            instance: None,
176            network: NetworkArgs::default(),
177            rpc: RpcServerArgs::default(),
178            txpool: TxPoolArgs::default(),
179            builder: PayloadBuilderArgs::default(),
180            debug: DebugArgs::default(),
181            db: DatabaseArgs::default(),
182            dev: DevArgs::default(),
183            pruning: PruningArgs::default(),
184            datadir: DatadirArgs::default(),
185            engine: EngineArgs::default(),
186            era: EraArgs::default(),
187            static_files: StaticFilesArgs::default(),
188            storage: StorageArgs::default(),
189        }
190    }
191
192    /// Sets --dev mode for the node.
193    ///
194    /// In addition to setting the `--dev` flag, this also:
195    ///   - disables discovery in [`NetworkArgs`].
196    pub const fn dev(mut self) -> Self {
197        self.dev.dev = true;
198        self.network.discovery.disable_discovery = true;
199        self
200    }
201
202    /// Apply a function to the config.
203    pub fn apply<F>(self, f: F) -> Self
204    where
205        F: FnOnce(Self) -> Self,
206    {
207        f(self)
208    }
209
210    /// Applies a fallible function to the config.
211    pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
212    where
213        F: FnOnce(Self) -> Result<Self, R>,
214    {
215        f(self)
216    }
217
218    /// Sets --dev mode for the node [`NodeConfig::dev`], if `dev` is true.
219    pub const fn set_dev(self, dev: bool) -> Self {
220        if dev {
221            self.dev()
222        } else {
223            self
224        }
225    }
226
227    /// Set the data directory args for the node
228    pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self {
229        self.datadir = datadir_args;
230        self
231    }
232
233    /// Set the config file for the node
234    pub fn with_config(mut self, config: impl Into<PathBuf>) -> Self {
235        self.config = Some(config.into());
236        self
237    }
238
239    /// Set the [`ChainSpec`] for the node
240    pub fn with_chain(mut self, chain: impl Into<Arc<ChainSpec>>) -> Self {
241        self.chain = chain.into();
242        self
243    }
244
245    /// Set the [`ChainSpec`] for the node and converts the type to that chainid.
246    pub fn map_chain<C>(self, chain: impl Into<Arc<C>>) -> NodeConfig<C> {
247        let Self {
248            datadir,
249            config,
250            metrics,
251            instance,
252            network,
253            rpc,
254            txpool,
255            builder,
256            debug,
257            db,
258            dev,
259            pruning,
260            engine,
261            era,
262            static_files,
263            storage,
264            ..
265        } = self;
266        NodeConfig {
267            datadir,
268            config,
269            chain: chain.into(),
270            metrics,
271            instance,
272            network,
273            rpc,
274            txpool,
275            builder,
276            debug,
277            db,
278            dev,
279            pruning,
280            engine,
281            era,
282            static_files,
283            storage,
284        }
285    }
286
287    /// Set the metrics address for the node
288    pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
289        self.metrics = metrics;
290        self
291    }
292
293    /// Set the instance for the node
294    pub const fn with_instance(mut self, instance: u16) -> Self {
295        self.instance = Some(instance);
296        self
297    }
298
299    /// Returns the instance value, defaulting to 1 if not set.
300    pub fn get_instance(&self) -> u16 {
301        self.instance.unwrap_or(1)
302    }
303
304    /// Set the network args for the node
305    pub fn with_network(mut self, network: NetworkArgs) -> Self {
306        self.network = network;
307        self
308    }
309
310    /// Set the rpc args for the node
311    pub fn with_rpc(mut self, rpc: RpcServerArgs) -> Self {
312        self.rpc = rpc;
313        self
314    }
315
316    /// Set the txpool args for the node
317    pub fn with_txpool(mut self, txpool: TxPoolArgs) -> Self {
318        self.txpool = txpool;
319        self
320    }
321
322    /// Set the builder args for the node
323    pub fn with_payload_builder(mut self, builder: PayloadBuilderArgs) -> Self {
324        self.builder = builder;
325        self
326    }
327
328    /// Set the debug args for the node
329    pub fn with_debug(mut self, debug: DebugArgs) -> Self {
330        self.debug = debug;
331        self
332    }
333
334    /// Set the database args for the node
335    pub const fn with_db(mut self, db: DatabaseArgs) -> Self {
336        self.db = db;
337        self
338    }
339
340    /// Set the dev args for the node
341    pub fn with_dev(mut self, dev: DevArgs) -> Self {
342        self.dev = dev;
343        self
344    }
345
346    /// Set the dev block time for the node.
347    ///
348    /// This sets the interval at which the dev miner produces new blocks.
349    pub const fn with_dev_block_time(mut self, block_time: std::time::Duration) -> Self {
350        self.dev.block_time = Some(block_time);
351        self
352    }
353
354    /// Set the pruning args for the node
355    pub fn with_pruning(mut self, pruning: PruningArgs) -> Self {
356        self.pruning = pruning;
357        self
358    }
359
360    /// Set the storage args for the node
361    pub const fn with_storage(mut self, storage: StorageArgs) -> Self {
362        self.storage = storage;
363        self
364    }
365
366    /// Returns pruning configuration.
367    pub fn prune_config(&self) -> Option<PruneConfig>
368    where
369        ChainSpec: EthereumHardforks,
370    {
371        self.pruning.prune_config(&self.chain)
372    }
373
374    /// Returns the effective storage settings derived from `--storage.v2`.
375    ///
376    /// The base storage mode is determined by `--storage.v2`:
377    /// - When `--storage.v2` is set: uses [`StorageSettings::v2()`] defaults
378    /// - Otherwise: uses [`StorageSettings::base()`] defaults
379    pub const fn storage_settings(&self) -> StorageSettings {
380        if self.storage.v2 {
381            StorageSettings::v2()
382        } else {
383            StorageSettings::base()
384        }
385    }
386
387    /// Returns the max block that the node should run to, looking it up from the network if
388    /// necessary
389    pub async fn max_block<Provider, Client>(
390        &self,
391        network_client: Client,
392        provider: Provider,
393    ) -> eyre::Result<Option<BlockNumber>>
394    where
395        Provider: HeaderProvider,
396        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
397    {
398        let max_block = if let Some(block) = self.debug.max_block {
399            Some(block)
400        } else if let Some(tip) = self.debug.tip {
401            Some(self.lookup_or_fetch_tip(provider, network_client, tip).await?)
402        } else {
403            None
404        };
405
406        Ok(max_block)
407    }
408
409    /// Fetches the head block from the database.
410    ///
411    /// If the database is empty, returns the genesis block.
412    pub fn lookup_head<Factory>(&self, factory: &Factory) -> ProviderResult<Head>
413    where
414        Factory: DatabaseProviderFactory<
415            Provider: HeaderProvider + StageCheckpointReader + BlockHashReader,
416        >,
417    {
418        let provider = factory.database_provider_ro()?;
419
420        let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
421
422        let header = provider
423            .header_by_number(head)?
424            .expect("the header for the latest block is missing, database is corrupt");
425
426        let hash = provider
427            .block_hash(head)?
428            .expect("the hash for the latest block is missing, database is corrupt");
429
430        Ok(Head {
431            number: head,
432            hash,
433            difficulty: header.difficulty(),
434            total_difficulty: U256::ZERO,
435            timestamp: header.timestamp(),
436        })
437    }
438
439    /// Attempt to look up the block number for the tip hash in the database.
440    /// If it doesn't exist, download the header and return the block number.
441    ///
442    /// NOTE: The download is attempted with infinite retries.
443    pub async fn lookup_or_fetch_tip<Provider, Client>(
444        &self,
445        provider: Provider,
446        client: Client,
447        tip: B256,
448    ) -> ProviderResult<u64>
449    where
450        Provider: HeaderProvider,
451        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
452    {
453        let header = provider.header_by_hash_or_number(tip.into())?;
454
455        // try to look up the header in the database
456        if let Some(header) = header {
457            info!(target: "reth::cli", ?tip, "Successfully looked up tip block in the database");
458            return Ok(header.number())
459        }
460
461        Ok(self.fetch_tip_from_network(client, tip.into()).await.number())
462    }
463
464    /// Attempt to look up the block with the given number and return the header.
465    ///
466    /// NOTE: The download is attempted with infinite retries.
467    pub async fn fetch_tip_from_network<Client>(
468        &self,
469        client: Client,
470        tip: BlockHashOrNumber,
471    ) -> SealedHeader<Client::Header>
472    where
473        Client: HeadersClient<Header: reth_primitives_traits::BlockHeader>,
474    {
475        info!(target: "reth::cli", ?tip, "Fetching tip block from the network.");
476        let mut fetch_failures = 0;
477        loop {
478            match get_single_header(&client, tip).await {
479                Ok(tip_header) => {
480                    info!(target: "reth::cli", ?tip, "Successfully fetched tip");
481                    return tip_header
482                }
483                Err(error) => {
484                    fetch_failures += 1;
485                    if fetch_failures % 20 == 0 {
486                        error!(target: "reth::cli", ?fetch_failures, %error, "Failed to fetch the tip. Retrying...");
487                    }
488                }
489            }
490        }
491    }
492
493    /// Change rpc port numbers based on the instance number, using the inner
494    /// [`RpcServerArgs::adjust_instance_ports`] method.
495    pub fn adjust_instance_ports(&mut self) {
496        self.network.adjust_instance_ports(self.instance);
497        self.rpc.adjust_instance_ports(self.instance);
498    }
499
500    /// Sets networking and RPC ports to zero, causing the OS to choose random unused ports when
501    /// sockets are bound.
502    pub fn with_unused_ports(mut self) -> Self {
503        self.rpc = self.rpc.with_unused_ports();
504        self.network = self.network.with_unused_ports();
505        self
506    }
507
508    /// Disables all discovery services for the node.
509    pub const fn with_disabled_discovery(mut self) -> Self {
510        self.network.discovery.disable_discovery = true;
511        self
512    }
513
514    /// Effectively disables the RPC state cache by setting the cache sizes to `0`.
515    ///
516    /// By setting the cache sizes to 0, caching of newly executed or fetched blocks will be
517    /// effectively disabled.
518    pub const fn with_disabled_rpc_cache(mut self) -> Self {
519        self.rpc.rpc_state_cache.set_zero_lengths();
520        self
521    }
522
523    /// Resolve the final datadir path.
524    pub fn datadir(&self) -> ChainPath<DataDirPath>
525    where
526        ChainSpec: EthChainSpec,
527    {
528        self.datadir.clone().resolve_datadir(self.chain.chain())
529    }
530
531    /// Load an application configuration from a specified path.
532    ///
533    /// A new configuration file is created with default values if none
534    /// exists.
535    pub fn load_path<T: Serialize + DeserializeOwned + Default>(
536        path: impl AsRef<Path>,
537    ) -> eyre::Result<T> {
538        let path = path.as_ref();
539        match fs::read_to_string(path) {
540            Ok(cfg_string) => {
541                toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}"))
542            }
543            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
544                if let Some(parent) = path.parent() {
545                    fs::create_dir_all(parent)
546                        .map_err(|e| eyre!("Failed to create directory: {e}"))?;
547                }
548                let cfg = T::default();
549                let s = toml::to_string_pretty(&cfg)
550                    .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?;
551                fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?;
552                Ok(cfg)
553            }
554            Err(e) => Err(eyre!("Failed to load configuration: {e}")),
555        }
556    }
557
558    /// Modifies the [`ChainSpec`] generic of the config using the provided closure.
559    pub fn map_chainspec<F, C>(self, f: F) -> NodeConfig<C>
560    where
561        F: FnOnce(Arc<ChainSpec>) -> C,
562    {
563        let chain = Arc::new(f(self.chain));
564        NodeConfig {
565            chain,
566            datadir: self.datadir,
567            config: self.config,
568            metrics: self.metrics,
569            instance: self.instance,
570            network: self.network,
571            rpc: self.rpc,
572            txpool: self.txpool,
573            builder: self.builder,
574            debug: self.debug,
575            db: self.db,
576            dev: self.dev,
577            pruning: self.pruning,
578            engine: self.engine,
579            era: self.era,
580            static_files: self.static_files,
581            storage: self.storage,
582        }
583    }
584
585    /// Returns the [`MiningMode`] intended for --dev mode.
586    pub fn dev_mining_mode<Pool>(&self, pool: Pool) -> MiningMode<Pool>
587    where
588        Pool: TransactionPool + Unpin,
589    {
590        if let Some(interval) = self.dev.block_time {
591            MiningMode::interval(interval)
592        } else {
593            MiningMode::instant(pool, self.dev.block_max_transactions)
594        }
595    }
596}
597
598impl Default for NodeConfig<ChainSpec> {
599    fn default() -> Self {
600        Self::new(MAINNET.clone())
601    }
602}
603
604impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
605    fn clone(&self) -> Self {
606        Self {
607            chain: self.chain.clone(),
608            config: self.config.clone(),
609            metrics: self.metrics.clone(),
610            instance: self.instance,
611            network: self.network.clone(),
612            rpc: self.rpc.clone(),
613            txpool: self.txpool.clone(),
614            builder: self.builder.clone(),
615            debug: self.debug.clone(),
616            db: self.db,
617            dev: self.dev.clone(),
618            pruning: self.pruning.clone(),
619            datadir: self.datadir.clone(),
620            engine: self.engine.clone(),
621            era: self.era.clone(),
622            static_files: self.static_files,
623            storage: self.storage,
624        }
625    }
626}