reth_node_core/
node_config.rs

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