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