Skip to main content

reth_node_builder/builder/
mod.rs

1//! Customizable node builder.
2
3#![expect(clippy::type_complexity)]
4#![allow(missing_debug_implementations)]
5
6use crate::{
7    common::WithConfigs,
8    components::NodeComponentsBuilder,
9    node::FullNode,
10    rpc::{RethRpcAddOns, RethRpcServerHandles, RpcContext},
11    BlockReaderFor, DebugNode, DebugNodeLauncher, EngineNodeLauncher, LaunchNode, Node,
12};
13use alloy_eips::eip4844::env_settings::EnvKzgSettings;
14use futures::Future;
15use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
16use reth_db_api::{database::Database, database_metrics::DatabaseMetrics};
17use reth_exex::ExExContext;
18use reth_network::{
19    transactions::{
20        config::{AnnouncementFilteringPolicy, StrictEthAnnouncementFilter},
21        TransactionPropagationPolicy, TransactionsManagerConfig,
22    },
23    NetworkBuilder, NetworkConfig, NetworkConfigBuilder, NetworkHandle, NetworkManager,
24    NetworkPrimitives,
25};
26use reth_node_api::{
27    FullNodeTypes, FullNodeTypesAdapter, NodeAddOns, NodeTypes, NodeTypesWithDBAdapter,
28};
29use reth_node_core::{
30    cli::config::{PayloadBuilderConfig, RethTransactionPoolConfig},
31    dirs::{ChainPath, DataDirPath},
32    node_config::NodeConfig,
33    primitives::Head,
34};
35use reth_provider::{
36    providers::{BlockchainProvider, NodeTypesForProvider, RocksDBProvider},
37    ChainSpecProvider, FullProvider,
38};
39use reth_tasks::TaskExecutor;
40use reth_transaction_pool::{PoolConfig, PoolTransaction, TransactionPool};
41use secp256k1::SecretKey;
42use std::sync::Arc;
43use tracing::{info, trace, warn};
44
45pub mod add_ons;
46
47mod states;
48pub use states::*;
49
50/// The adapter type for a reth node with the builtin provider type
51// Note: we need to hardcode this because custom components might depend on it in associated types.
52pub type RethFullAdapter<DB, Types> =
53    FullNodeTypesAdapter<Types, DB, BlockchainProvider<NodeTypesWithDBAdapter<Types, DB>>>;
54
55#[expect(clippy::doc_markdown)]
56#[cfg_attr(doc, aquamarine::aquamarine)]
57/// Declaratively construct a node.
58///
59/// [`NodeBuilder`] provides a [builder-like interface][builder] for composing
60/// components of a node.
61///
62/// ## Order
63///
64/// Configuring a node starts out with a [`NodeConfig`] (this can be obtained from cli arguments for
65/// example) and then proceeds to configure the core static types of the node:
66/// [`NodeTypes`], these include the node's primitive types and the node's engine
67/// types.
68///
69/// Next all stateful components of the node are configured, these include all the
70/// components of the node that are downstream of those types, these include:
71///
72///  - The EVM and Executor configuration: [`ExecutorBuilder`](crate::components::ExecutorBuilder)
73///  - The transaction pool: [`PoolBuilder`](crate::components::PoolBuilder)
74///  - The network: [`NetworkBuilder`](crate::components::NetworkBuilder)
75///  - The payload builder: [`PayloadBuilder`](crate::components::PayloadServiceBuilder)
76///
77/// Once all the components are configured, the node is ready to be launched.
78///
79/// On launch the builder returns a fully type aware [`NodeHandle`] that has access to all the
80/// configured components and can interact with the node.
81///
82/// There are convenience functions for networks that come with a preset of types and components via
83/// the [`Node`] trait, see `reth_node_ethereum::EthereumNode`.
84///
85/// The [`NodeBuilder::node`] function configures the node's types and components in one step.
86///
87/// ## Components
88///
89/// All components are configured with a [`NodeComponentsBuilder`] that is responsible for actually
90/// creating the node components during the launch process. The
91/// [`ComponentsBuilder`](crate::components::ComponentsBuilder) is a general purpose implementation
92/// of the [`NodeComponentsBuilder`] trait that can be used to configure the executor, network,
93/// transaction pool and payload builder of the node. It enforces the correct order of
94/// configuration, for example the network and the payload builder depend on the transaction pool
95/// type that is configured first.
96///
97/// All builder traits are generic over the node types and are invoked with the [`BuilderContext`]
98/// that gives access to internals of the that are needed to configure the components. This include
99/// the original config, chain spec, the database provider and the task executor,
100///
101/// ## Hooks
102///
103/// Once all the components are configured, the builder can be used to set hooks that are run at
104/// specific points in the node's lifecycle. This way custom services can be spawned before the node
105/// is launched [`NodeBuilderWithComponents::on_component_initialized`], or once the rpc server(s)
106/// are launched [`NodeBuilderWithComponents::on_rpc_started`]. The
107/// [`NodeBuilderWithComponents::extend_rpc_modules`] can be used to inject custom rpc modules into
108/// the rpc server before it is launched. See also [`RpcContext`] All hooks accept a closure that is
109/// then invoked at the appropriate time in the node's launch process.
110///
111/// ## Flow
112///
113/// The [`NodeBuilder`] is intended to sit behind a CLI that provides the necessary [`NodeConfig`]
114/// input: [`NodeBuilder::new`]
115///
116/// From there the builder is configured with the node's types, components, and hooks, then launched
117/// with the [`WithLaunchContext::launch`] method. On launch all the builtin internals, such as the
118/// `Database` and its providers [`BlockchainProvider`] are initialized before the configured
119/// [`NodeComponentsBuilder`] is invoked with the [`BuilderContext`] to create the transaction pool,
120/// network, and payload builder components. When the RPC is configured, the corresponding hooks are
121/// invoked to allow for custom rpc modules to be injected into the rpc server:
122/// [`NodeBuilderWithComponents::extend_rpc_modules`]
123///
124/// Finally all components are created and all services are launched and a [`NodeHandle`] is
125/// returned that can be used to interact with the node: [`FullNode`]
126///
127/// The following diagram shows the flow of the node builder from CLI to a launched node.
128///
129/// include_mmd!("docs/mermaid/builder.mmd")
130///
131/// ## Internals
132///
133/// The node builder is fully type safe, it uses the [`NodeTypes`] trait to enforce that
134/// all components are configured with the correct types. However the database types and with that
135/// the provider trait implementations are currently created by the builder itself during the launch
136/// process, hence the database type is not part of the [`NodeTypes`] trait and the node's
137/// components, that depend on the database, are configured separately. In order to have a nice
138/// trait that encapsulates the entire node the
139/// [`FullNodeComponents`](reth_node_api::FullNodeComponents) trait was introduced. This
140/// trait has convenient associated types for all the components of the node. After
141/// [`WithLaunchContext::launch`] the [`NodeHandle`] contains an instance of [`FullNode`] that
142/// implements the [`FullNodeComponents`](reth_node_api::FullNodeComponents) trait and has access to
143/// all the components of the node. Internally the node builder uses several generic adapter types
144/// that are then map to traits with associated types for ease of use.
145///
146/// ### Limitations
147///
148/// Currently the launch process is limited to ethereum nodes and requires all the components
149/// specified above. It also expects beacon consensus with the ethereum engine API that is
150/// configured by the builder itself during launch. This might change in the future.
151///
152/// [builder]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
153pub struct NodeBuilder<DB, ChainSpec> {
154    /// All settings for how the node should be configured.
155    config: NodeConfig<ChainSpec>,
156    /// The configured database for the node.
157    database: DB,
158    /// An optional [`RocksDBProvider`] to use instead of creating one during launch.
159    rocksdb_provider: Option<RocksDBProvider>,
160}
161
162impl<ChainSpec> NodeBuilder<(), ChainSpec> {
163    /// Create a new [`NodeBuilder`].
164    pub const fn new(config: NodeConfig<ChainSpec>) -> Self {
165        Self { config, database: (), rocksdb_provider: None }
166    }
167}
168
169impl<DB, ChainSpec> NodeBuilder<DB, ChainSpec> {
170    /// Returns a reference to the node builder's config.
171    pub const fn config(&self) -> &NodeConfig<ChainSpec> {
172        &self.config
173    }
174
175    /// Returns a mutable reference to the node builder's config.
176    pub const fn config_mut(&mut self) -> &mut NodeConfig<ChainSpec> {
177        &mut self.config
178    }
179
180    /// Returns a reference to the node's database
181    pub const fn db(&self) -> &DB {
182        &self.database
183    }
184
185    /// Returns a mutable reference to the node's database
186    pub const fn db_mut(&mut self) -> &mut DB {
187        &mut self.database
188    }
189
190    /// Applies a fallible function to the builder.
191    pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
192    where
193        F: FnOnce(Self) -> Result<Self, R>,
194    {
195        f(self)
196    }
197
198    /// Applies a fallible function to the builder, if the condition is `true`.
199    pub fn try_apply_if<F, R>(self, cond: bool, f: F) -> Result<Self, R>
200    where
201        F: FnOnce(Self) -> Result<Self, R>,
202    {
203        if cond {
204            f(self)
205        } else {
206            Ok(self)
207        }
208    }
209
210    /// Apply a function to the builder
211    pub fn apply<F>(self, f: F) -> Self
212    where
213        F: FnOnce(Self) -> Self,
214    {
215        f(self)
216    }
217
218    /// Apply a function to the builder, if the condition is `true`.
219    pub fn apply_if<F>(self, cond: bool, f: F) -> Self
220    where
221        F: FnOnce(Self) -> Self,
222    {
223        if cond {
224            f(self)
225        } else {
226            self
227        }
228    }
229}
230
231impl<DB, ChainSpec: EthChainSpec> NodeBuilder<DB, ChainSpec> {
232    /// Configures the underlying database that the node will use.
233    pub fn with_database<D>(self, database: D) -> NodeBuilder<D, ChainSpec> {
234        NodeBuilder { config: self.config, database, rocksdb_provider: self.rocksdb_provider }
235    }
236
237    /// Sets the [`RocksDBProvider`] to use instead of creating one during launch.
238    pub fn with_rocksdb_provider(mut self, rocksdb_provider: RocksDBProvider) -> Self {
239        self.rocksdb_provider = Some(rocksdb_provider);
240        self
241    }
242
243    /// Preconfigure the builder with the context to launch the node.
244    ///
245    /// This provides the task executor and the data directory for the node.
246    pub const fn with_launch_context(self, task_executor: TaskExecutor) -> WithLaunchContext<Self> {
247        WithLaunchContext { builder: self, task_executor }
248    }
249
250    /// Creates an _ephemeral_ preconfigured node for testing purposes.
251    #[cfg(feature = "test-utils")]
252    pub fn testing_node(
253        self,
254        task_executor: TaskExecutor,
255    ) -> WithLaunchContext<
256        NodeBuilder<Arc<reth_db::test_utils::TempDatabase<reth_db::DatabaseEnv>>, ChainSpec>,
257    > {
258        let path = reth_db::test_utils::tempdir_path();
259        self.testing_node_with_datadir(task_executor, path)
260    }
261
262    /// Creates a preconfigured node for testing purposes with a specific datadir.
263    ///
264    /// The entire `datadir` will be cleaned up when the node is dropped.
265    #[cfg(feature = "test-utils")]
266    pub fn testing_node_with_datadir(
267        mut self,
268        task_executor: TaskExecutor,
269        datadir: impl Into<std::path::PathBuf>,
270    ) -> WithLaunchContext<
271        NodeBuilder<Arc<reth_db::test_utils::TempDatabase<reth_db::DatabaseEnv>>, ChainSpec>,
272    > {
273        let path = reth_node_core::dirs::MaybePlatformPath::<DataDirPath>::from(datadir.into());
274        self.config = self.config.with_datadir_args(reth_node_core::args::DatadirArgs {
275            datadir: path.clone(),
276            ..Default::default()
277        });
278
279        let data_dir =
280            path.unwrap_or_chain_default(self.config.chain.chain(), self.config.datadir.clone());
281
282        let db = reth_db::test_utils::create_test_rw_db_with_datadir(data_dir.data_dir());
283
284        WithLaunchContext { builder: self.with_database(db), task_executor }
285    }
286
287    /// Creates a preconfigured test node whose datadir is preserved when the node is dropped.
288    ///
289    /// The caller owns cleanup of `datadir`. This is useful for tests that stop a node and launch
290    /// a new instance against the same database and static files.
291    #[cfg(feature = "test-utils")]
292    pub fn testing_node_with_persistent_datadir(
293        mut self,
294        task_executor: TaskExecutor,
295        datadir: impl Into<std::path::PathBuf>,
296    ) -> WithLaunchContext<NodeBuilder<Arc<reth_db::DatabaseEnv>, ChainSpec>> {
297        let path = reth_node_core::dirs::MaybePlatformPath::<DataDirPath>::from(datadir.into());
298        self.config = self.config.with_datadir_args(reth_node_core::args::DatadirArgs {
299            datadir: path.clone(),
300            ..Default::default()
301        });
302
303        let data_dir =
304            path.unwrap_or_chain_default(self.config.chain.chain(), self.config.datadir.clone());
305        let db_path = data_dir.data_dir().join("db");
306        let db = reth_db::init_db(&db_path, reth_db::mdbx::DatabaseArguments::test())
307            .unwrap_or_else(|error| {
308                panic!("could not create test database at {db_path:?}: {error}")
309            });
310
311        WithLaunchContext { builder: self.with_database(Arc::new(db)), task_executor }
312    }
313}
314
315impl<DB, ChainSpec> NodeBuilder<DB, ChainSpec>
316where
317    DB: Database + DatabaseMetrics + Clone + Unpin + 'static,
318    ChainSpec: EthChainSpec + EthereumHardforks,
319{
320    /// Configures the types of the node.
321    pub fn with_types<T>(self) -> NodeBuilderWithTypes<RethFullAdapter<DB, T>>
322    where
323        T: NodeTypesForProvider<ChainSpec = ChainSpec>,
324    {
325        self.with_types_and_provider()
326    }
327
328    /// Configures the types of the node and the provider type that will be used by the node.
329    pub fn with_types_and_provider<T, P>(
330        self,
331    ) -> NodeBuilderWithTypes<FullNodeTypesAdapter<T, DB, P>>
332    where
333        T: NodeTypesForProvider<ChainSpec = ChainSpec>,
334        P: FullProvider<NodeTypesWithDBAdapter<T, DB>>,
335    {
336        NodeBuilderWithTypes::new(self.config, self.database, self.rocksdb_provider)
337    }
338
339    /// Preconfigures the node with a specific node implementation.
340    ///
341    /// This is a convenience method that sets the node's types and components in one call.
342    pub fn node<N>(
343        self,
344        node: N,
345    ) -> NodeBuilderWithComponents<RethFullAdapter<DB, N>, N::ComponentsBuilder, N::AddOns>
346    where
347        N: Node<RethFullAdapter<DB, N>, ChainSpec = ChainSpec> + NodeTypesForProvider,
348    {
349        self.with_types().with_components(node.components_builder()).with_add_ons(node.add_ons())
350    }
351}
352
353/// A [`NodeBuilder`] with its launch context already configured.
354///
355/// This exposes the same methods as [`NodeBuilder`] but with the launch context already configured,
356/// See [`WithLaunchContext::launch`]
357pub struct WithLaunchContext<Builder> {
358    builder: Builder,
359    task_executor: TaskExecutor,
360}
361
362impl<Builder> WithLaunchContext<Builder> {
363    /// Returns a reference to the task executor.
364    pub const fn task_executor(&self) -> &TaskExecutor {
365        &self.task_executor
366    }
367}
368
369impl<DB, ChainSpec> WithLaunchContext<NodeBuilder<DB, ChainSpec>> {
370    /// Returns a reference to the node builder's config.
371    pub const fn config(&self) -> &NodeConfig<ChainSpec> {
372        self.builder.config()
373    }
374
375    /// Returns a mutable reference to the node builder's config.
376    pub const fn config_mut(&mut self) -> &mut NodeConfig<ChainSpec> {
377        self.builder.config_mut()
378    }
379}
380
381impl<DB, ChainSpec> WithLaunchContext<NodeBuilder<DB, ChainSpec>>
382where
383    DB: Database + DatabaseMetrics + Clone + Unpin + 'static,
384    ChainSpec: EthChainSpec + EthereumHardforks,
385{
386    /// Sets the [`RocksDBProvider`] to use instead of creating one during launch.
387    pub fn with_rocksdb_provider(mut self, rocksdb_provider: RocksDBProvider) -> Self {
388        self.builder.rocksdb_provider = Some(rocksdb_provider);
389        self
390    }
391
392    /// Configures the types of the node.
393    pub fn with_types<T>(self) -> WithLaunchContext<NodeBuilderWithTypes<RethFullAdapter<DB, T>>>
394    where
395        T: NodeTypesForProvider<ChainSpec = ChainSpec>,
396    {
397        WithLaunchContext { builder: self.builder.with_types(), task_executor: self.task_executor }
398    }
399
400    /// Configures the types of the node and the provider type that will be used by the node.
401    pub fn with_types_and_provider<T, P>(
402        self,
403    ) -> WithLaunchContext<NodeBuilderWithTypes<FullNodeTypesAdapter<T, DB, P>>>
404    where
405        T: NodeTypesForProvider<ChainSpec = ChainSpec>,
406        P: FullProvider<NodeTypesWithDBAdapter<T, DB>>,
407    {
408        WithLaunchContext {
409            builder: self.builder.with_types_and_provider(),
410            task_executor: self.task_executor,
411        }
412    }
413
414    /// Preconfigures the node with a specific node implementation.
415    ///
416    /// This is a convenience method that sets the node's types and components in one call.
417    pub fn node<N>(
418        self,
419        node: N,
420    ) -> WithLaunchContext<
421        NodeBuilderWithComponents<RethFullAdapter<DB, N>, N::ComponentsBuilder, N::AddOns>,
422    >
423    where
424        N: Node<RethFullAdapter<DB, N>, ChainSpec = ChainSpec> + NodeTypesForProvider,
425    {
426        self.with_types().with_components(node.components_builder()).with_add_ons(node.add_ons())
427    }
428
429    /// Launches a preconfigured [Node]
430    ///
431    /// This bootstraps the node internals, creates all the components with the given [Node]
432    ///
433    /// Returns a [`NodeHandle`](crate::NodeHandle) that can be used to interact with the node.
434    pub async fn launch_node<N>(
435        self,
436        node: N,
437    ) -> eyre::Result<
438        <EngineNodeLauncher as LaunchNode<
439            NodeBuilderWithComponents<RethFullAdapter<DB, N>, N::ComponentsBuilder, N::AddOns>,
440        >>::Node,
441    >
442    where
443        N: Node<RethFullAdapter<DB, N>, ChainSpec = ChainSpec> + NodeTypesForProvider,
444        N::AddOns: RethRpcAddOns<
445            NodeAdapter<
446                RethFullAdapter<DB, N>,
447                <N::ComponentsBuilder as NodeComponentsBuilder<RethFullAdapter<DB, N>>>::Components,
448            >,
449        >,
450        EngineNodeLauncher: LaunchNode<
451            NodeBuilderWithComponents<RethFullAdapter<DB, N>, N::ComponentsBuilder, N::AddOns>,
452        >,
453    {
454        self.node(node).launch().await
455    }
456}
457
458impl<T: FullNodeTypes> WithLaunchContext<NodeBuilderWithTypes<T>> {
459    /// Advances the state of the node builder to the next state where all components are configured
460    pub fn with_components<CB>(
461        self,
462        components_builder: CB,
463    ) -> WithLaunchContext<NodeBuilderWithComponents<T, CB, ()>>
464    where
465        CB: NodeComponentsBuilder<T>,
466    {
467        WithLaunchContext {
468            builder: self.builder.with_components(components_builder),
469            task_executor: self.task_executor,
470        }
471    }
472}
473
474impl<T, CB> WithLaunchContext<NodeBuilderWithComponents<T, CB, ()>>
475where
476    T: FullNodeTypes,
477    CB: NodeComponentsBuilder<T>,
478{
479    /// Advances the state of the node builder to the next state where all customizable
480    /// [`NodeAddOns`] types are configured.
481    pub fn with_add_ons<AO>(
482        self,
483        add_ons: AO,
484    ) -> WithLaunchContext<NodeBuilderWithComponents<T, CB, AO>>
485    where
486        AO: NodeAddOns<NodeAdapter<T, CB::Components>>,
487    {
488        WithLaunchContext {
489            builder: self.builder.with_add_ons(add_ons),
490            task_executor: self.task_executor,
491        }
492    }
493}
494
495impl<T, CB, AO> WithLaunchContext<NodeBuilderWithComponents<T, CB, AO>>
496where
497    T: FullNodeTypes,
498    CB: NodeComponentsBuilder<T>,
499    AO: RethRpcAddOns<NodeAdapter<T, CB::Components>>,
500{
501    /// Returns a reference to the node builder's config.
502    pub const fn config(&self) -> &NodeConfig<<T::Types as NodeTypes>::ChainSpec> {
503        &self.builder.config
504    }
505
506    /// Returns a mutable reference to the node builder's config.
507    pub const fn config_mut(&mut self) -> &mut NodeConfig<<T::Types as NodeTypes>::ChainSpec> {
508        &mut self.builder.config
509    }
510
511    /// Returns a reference to node's database.
512    pub const fn db(&self) -> &T::DB {
513        &self.builder.adapter.database
514    }
515
516    /// Returns a mutable reference to node's database.
517    pub const fn db_mut(&mut self) -> &mut T::DB {
518        &mut self.builder.adapter.database
519    }
520
521    /// Applies a fallible function to the builder.
522    pub fn try_apply<F, R>(self, f: F) -> Result<Self, R>
523    where
524        F: FnOnce(Self) -> Result<Self, R>,
525    {
526        f(self)
527    }
528
529    /// Applies a fallible function to the builder, if the condition is `true`.
530    pub fn try_apply_if<F, R>(self, cond: bool, f: F) -> Result<Self, R>
531    where
532        F: FnOnce(Self) -> Result<Self, R>,
533    {
534        if cond {
535            f(self)
536        } else {
537            Ok(self)
538        }
539    }
540
541    /// Apply a function to the builder
542    pub fn apply<F>(self, f: F) -> Self
543    where
544        F: FnOnce(Self) -> Self,
545    {
546        f(self)
547    }
548
549    /// Apply a function to the builder, if the condition is `true`.
550    pub fn apply_if<F>(self, cond: bool, f: F) -> Self
551    where
552        F: FnOnce(Self) -> Self,
553    {
554        if cond {
555            f(self)
556        } else {
557            self
558        }
559    }
560
561    /// Sets the hook that is run once the node's components are initialized.
562    pub fn on_component_initialized<F>(self, hook: F) -> Self
563    where
564        F: FnOnce(NodeAdapter<T, CB::Components>) -> eyre::Result<()> + Send + 'static,
565    {
566        Self {
567            builder: self.builder.on_component_initialized(hook),
568            task_executor: self.task_executor,
569        }
570    }
571
572    /// Sets the hook that is run once the node has started.
573    pub fn on_node_started<F>(self, hook: F) -> Self
574    where
575        F: FnOnce(FullNode<NodeAdapter<T, CB::Components>, AO>) -> eyre::Result<()>
576            + Send
577            + 'static,
578    {
579        Self { builder: self.builder.on_node_started(hook), task_executor: self.task_executor }
580    }
581
582    /// Modifies the addons with the given closure.
583    ///
584    /// This method provides access to methods on the addons type that don't have
585    /// direct builder methods. It's useful for advanced configuration scenarios
586    /// where you need to call addon-specific methods.
587    ///
588    /// # Examples
589    ///
590    /// ```rust,ignore
591    /// use tower::layer::util::Identity;
592    ///
593    /// let builder = NodeBuilder::new(config)
594    ///     .with_types::<EthereumNode>()
595    ///     .with_components(EthereumNode::components())
596    ///     .with_add_ons(EthereumAddOns::default())
597    ///     .map_add_ons(|addons| addons.with_rpc_middleware(Identity::default()));
598    /// ```
599    ///
600    /// # See also
601    ///
602    /// - [`NodeAddOns`] trait for available addon types
603    /// - [`crate::NodeBuilderWithComponents::extend_rpc_modules`] for RPC module configuration
604    pub fn map_add_ons<F>(self, f: F) -> Self
605    where
606        F: FnOnce(AO) -> AO,
607    {
608        Self { builder: self.builder.map_add_ons(f), task_executor: self.task_executor }
609    }
610
611    /// Sets the hook that is run once the rpc server is started.
612    pub fn on_rpc_started<F>(self, hook: F) -> Self
613    where
614        F: FnOnce(
615                RpcContext<'_, NodeAdapter<T, CB::Components>, AO::EthApi>,
616                RethRpcServerHandles,
617            ) -> eyre::Result<()>
618            + Send
619            + 'static,
620    {
621        Self { builder: self.builder.on_rpc_started(hook), task_executor: self.task_executor }
622    }
623
624    /// Sets the hook that is run to configure the rpc modules.
625    ///
626    /// This hook can obtain the node's components (txpool, provider, etc.) and can modify the
627    /// modules that the RPC server installs.
628    ///
629    /// # Examples
630    ///
631    /// ```rust,ignore
632    /// use jsonrpsee::{core::RpcResult, proc_macros::rpc};
633    ///
634    /// #[derive(Clone)]
635    /// struct CustomApi<Pool> { pool: Pool }
636    ///
637    /// #[rpc(server, namespace = "custom")]
638    /// impl CustomApi {
639    ///     #[method(name = "hello")]
640    ///     async fn hello(&self) -> RpcResult<String> {
641    ///         Ok("World".to_string())
642    ///     }
643    /// }
644    ///
645    /// let node = NodeBuilder::new(config)
646    ///     .node(EthereumNode::default())
647    ///     .extend_rpc_modules(|ctx| {
648    ///         // Access node components, so they can used by the CustomApi
649    ///         let pool = ctx.pool().clone();
650    ///
651    ///         // Add custom RPC namespace
652    ///         ctx.modules.merge_configured(CustomApi { pool }.into_rpc())?;
653    ///
654    ///         Ok(())
655    ///     })
656    ///     .build()?;
657    /// ```
658    pub fn extend_rpc_modules<F>(self, hook: F) -> Self
659    where
660        F: FnOnce(RpcContext<'_, NodeAdapter<T, CB::Components>, AO::EthApi>) -> eyre::Result<()>
661            + Send
662            + 'static,
663    {
664        Self { builder: self.builder.extend_rpc_modules(hook), task_executor: self.task_executor }
665    }
666
667    /// Installs an `ExEx` (Execution Extension) in the node.
668    ///
669    /// # Note
670    ///
671    /// The `ExEx` ID must be unique.
672    pub fn install_exex<F, R, E>(self, exex_id: impl Into<String>, exex: F) -> Self
673    where
674        F: FnOnce(ExExContext<NodeAdapter<T, CB::Components>>) -> R + Send + 'static,
675        R: Future<Output = eyre::Result<E>> + Send,
676        E: Future<Output = eyre::Result<()>> + Send,
677    {
678        Self {
679            builder: self.builder.install_exex(exex_id, exex),
680            task_executor: self.task_executor,
681        }
682    }
683
684    /// Installs an `ExEx` (Execution Extension) in the node if the condition is true.
685    ///
686    /// # Note
687    ///
688    /// The `ExEx` ID must be unique.
689    pub fn install_exex_if<F, R, E>(self, cond: bool, exex_id: impl Into<String>, exex: F) -> Self
690    where
691        F: FnOnce(ExExContext<NodeAdapter<T, CB::Components>>) -> R + Send + 'static,
692        R: Future<Output = eyre::Result<E>> + Send,
693        E: Future<Output = eyre::Result<()>> + Send,
694    {
695        if cond {
696            self.install_exex(exex_id, exex)
697        } else {
698            self
699        }
700    }
701
702    /// Launches the node with the given launcher.
703    pub async fn launch_with<L>(self, launcher: L) -> eyre::Result<L::Node>
704    where
705        L: LaunchNode<NodeBuilderWithComponents<T, CB, AO>>,
706    {
707        launcher.launch_node(self.builder).await
708    }
709
710    /// Launches the node with the given closure.
711    pub fn launch_with_fn<L, R>(self, launcher: L) -> R
712    where
713        L: FnOnce(Self) -> R,
714    {
715        launcher(self)
716    }
717
718    /// Check that the builder can be launched
719    ///
720    /// This is useful when writing tests to ensure that the builder is configured correctly.
721    pub const fn check_launch(self) -> Self {
722        self
723    }
724
725    /// Launches the node with the [`EngineNodeLauncher`] that sets up engine API consensus and rpc
726    pub async fn launch(
727        self,
728    ) -> eyre::Result<<EngineNodeLauncher as LaunchNode<NodeBuilderWithComponents<T, CB, AO>>>::Node>
729    where
730        EngineNodeLauncher: LaunchNode<NodeBuilderWithComponents<T, CB, AO>>,
731    {
732        let launcher = self.engine_api_launcher();
733        self.builder.launch_with(launcher).await
734    }
735
736    /// Launches the node with the [`DebugNodeLauncher`].
737    ///
738    /// This is equivalent to [`WithLaunchContext::launch`], but will enable the debugging features,
739    /// if they are configured.
740    pub fn launch_with_debug_capabilities(
741        self,
742    ) -> <DebugNodeLauncher as LaunchNode<NodeBuilderWithComponents<T, CB, AO>>>::Future
743    where
744        T::Types: DebugNode<NodeAdapter<T, CB::Components>>,
745        DebugNodeLauncher: LaunchNode<NodeBuilderWithComponents<T, CB, AO>>,
746    {
747        let Self { builder, task_executor } = self;
748
749        let engine_tree_config = builder.config.tree_config();
750
751        let launcher = DebugNodeLauncher::new(EngineNodeLauncher::new(
752            task_executor,
753            builder.config.datadir(),
754            engine_tree_config,
755        ));
756        builder.launch_with(launcher)
757    }
758
759    /// Returns an [`EngineNodeLauncher`] that can be used to launch the node with engine API
760    /// support.
761    pub fn engine_api_launcher(&self) -> EngineNodeLauncher {
762        let engine_tree_config = self.builder.config.tree_config();
763        EngineNodeLauncher::new(
764            self.task_executor.clone(),
765            self.builder.config.datadir(),
766            engine_tree_config,
767        )
768    }
769}
770
771/// Captures the necessary context for building the components of the node.
772pub struct BuilderContext<Node: FullNodeTypes> {
773    /// The current head of the blockchain at launch.
774    pub(crate) head: Head,
775    /// The configured provider to interact with the blockchain.
776    pub(crate) provider: Node::Provider,
777    /// The executor of the node.
778    pub(crate) executor: TaskExecutor,
779    /// Config container
780    pub(crate) config_container: WithConfigs<<Node::Types as NodeTypes>::ChainSpec>,
781    /// Cache of recovered transaction senders shared by node components, if enabled.
782    sender_recovery_cache: Option<reth_evm::SenderRecoveryCache>,
783}
784
785impl<Node: FullNodeTypes> BuilderContext<Node> {
786    /// Create a new instance of [`BuilderContext`]
787    pub fn new(
788        head: Head,
789        provider: Node::Provider,
790        executor: TaskExecutor,
791        config_container: WithConfigs<<Node::Types as NodeTypes>::ChainSpec>,
792    ) -> Self {
793        let sender_recovery_cache = config_container
794            .config
795            .engine
796            .sender_recovery_cache_enabled
797            .then(reth_evm::SenderRecoveryCache::default);
798        Self { head, provider, executor, config_container, sender_recovery_cache }
799    }
800
801    /// Returns the configured provider to interact with the blockchain.
802    pub const fn provider(&self) -> &Node::Provider {
803        &self.provider
804    }
805
806    /// Returns the current head of the blockchain at launch.
807    pub const fn head(&self) -> Head {
808        self.head
809    }
810
811    /// Returns the config of the node.
812    pub const fn config(&self) -> &NodeConfig<<Node::Types as NodeTypes>::ChainSpec> {
813        &self.config_container.config
814    }
815
816    /// Returns a mutable reference to the config of the node.
817    pub const fn config_mut(&mut self) -> &mut NodeConfig<<Node::Types as NodeTypes>::ChainSpec> {
818        &mut self.config_container.config
819    }
820
821    /// Returns the loaded reh.toml config.
822    pub const fn reth_config(&self) -> &reth_config::Config {
823        &self.config_container.toml_config
824    }
825
826    /// Returns the executor of the node.
827    ///
828    /// This can be used to execute async tasks or functions during the setup.
829    pub const fn task_executor(&self) -> &TaskExecutor {
830        &self.executor
831    }
832
833    /// Returns the sender recovery cache shared by node components, if enabled.
834    pub const fn sender_recovery_cache(&self) -> Option<&reth_evm::SenderRecoveryCache> {
835        self.sender_recovery_cache.as_ref()
836    }
837
838    /// Returns the chain spec of the node.
839    pub fn chain_spec(&self) -> Arc<<Node::Types as NodeTypes>::ChainSpec> {
840        self.provider().chain_spec()
841    }
842
843    /// Returns true if the node is configured as --dev
844    pub const fn is_dev(&self) -> bool {
845        self.config().dev.dev
846    }
847
848    /// Returns the transaction pool config of the node.
849    pub fn pool_config(&self) -> PoolConfig {
850        self.config().txpool.pool_config()
851    }
852
853    /// Loads `EnvKzgSettings::Default`.
854    pub const fn kzg_settings(&self) -> eyre::Result<EnvKzgSettings> {
855        Ok(EnvKzgSettings::Default)
856    }
857
858    /// Returns the config for payload building.
859    pub fn payload_builder_config(&self) -> impl PayloadBuilderConfig {
860        self.config().builder.clone()
861    }
862
863    /// Convenience function to start the network tasks.
864    ///
865    /// Spawns the configured network and associated tasks and returns the [`NetworkHandle`]
866    /// connected to that network.
867    pub fn start_network<N, Pool>(
868        &self,
869        builder: NetworkBuilder<(), (), N>,
870        pool: Pool,
871    ) -> NetworkHandle<N>
872    where
873        N: NetworkPrimitives,
874        Pool: TransactionPool<
875                Transaction: PoolTransaction<
876                    Consensus = N::BroadcastedTransaction,
877                    Pooled = N::PooledTransaction,
878                >,
879            > + Unpin
880            + 'static,
881        Node::Provider: BlockReaderFor<N>,
882    {
883        self.start_network_with(
884            builder,
885            pool,
886            self.config().network.transactions_manager_config(),
887            self.config().network.tx_propagation_policy,
888        )
889    }
890
891    /// Convenience function to start the network tasks.
892    ///
893    /// Accepts the config for the transaction task and the policy for propagation.
894    /// Uses the default [`StrictEthAnnouncementFilter`] for announcement filtering.
895    ///
896    /// Spawns the configured network and associated tasks and returns the [`NetworkHandle`]
897    /// connected to that network.
898    pub fn start_network_with<Pool, N, Policy>(
899        &self,
900        builder: NetworkBuilder<(), (), N>,
901        pool: Pool,
902        tx_config: TransactionsManagerConfig,
903        propagation_policy: Policy,
904    ) -> NetworkHandle<N>
905    where
906        N: NetworkPrimitives,
907        Pool: TransactionPool<
908                Transaction: PoolTransaction<
909                    Consensus = N::BroadcastedTransaction,
910                    Pooled = N::PooledTransaction,
911                >,
912            > + Unpin
913            + 'static,
914        Node::Provider: BlockReaderFor<N>,
915        Policy: TransactionPropagationPolicy<N>,
916    {
917        self.start_network_with_policies(
918            builder,
919            pool,
920            tx_config,
921            propagation_policy,
922            StrictEthAnnouncementFilter::default(),
923        )
924    }
925
926    /// Convenience function to start the network tasks with custom policies.
927    ///
928    /// Accepts the config for the transaction task, the policy for propagation,
929    /// and a custom announcement filter. This is useful for configuring which tx types are accepted
930    /// in announcements.
931    ///
932    /// Spawns the configured network and associated tasks and returns the [`NetworkHandle`]
933    /// connected to that network.
934    pub fn start_network_with_policies<Pool, N, PropPolicy, AnnPolicy>(
935        &self,
936        builder: NetworkBuilder<(), (), N>,
937        pool: Pool,
938        tx_config: TransactionsManagerConfig,
939        propagation_policy: PropPolicy,
940        announcement_policy: AnnPolicy,
941    ) -> NetworkHandle<N>
942    where
943        N: NetworkPrimitives,
944        Pool: TransactionPool<
945                Transaction: PoolTransaction<
946                    Consensus = N::BroadcastedTransaction,
947                    Pooled = N::PooledTransaction,
948                >,
949            > + Unpin
950            + 'static,
951        Node::Provider: BlockReaderFor<N>,
952        PropPolicy: TransactionPropagationPolicy<N>,
953        AnnPolicy: AnnouncementFilteringPolicy<N>,
954    {
955        let (handle, network, txpool, eth) = builder
956            .transactions_with_policies(
957                pool.clone(),
958                tx_config,
959                propagation_policy,
960                announcement_policy,
961            )
962            .map_transactions(|transactions| {
963                if let Some(cache) = self.sender_recovery_cache.clone() {
964                    transactions.with_sender_recovery_cache(cache)
965                } else {
966                    transactions
967                }
968            })
969            .request_handler_with_blob_store(self.provider().clone(), pool.blob_store())
970            .split_with_handle();
971
972        self.executor.spawn_critical_blocking_task("p2p txpool", txpool);
973        self.executor.spawn_critical_blocking_task("p2p eth request handler", eth);
974
975        let default_peers_path = self.config().datadir().known_peers();
976        let known_peers_file = self.config().network.persistent_peers_file(default_peers_path);
977        self.executor.spawn_critical_with_graceful_shutdown_signal(
978            "p2p network task",
979            |shutdown| {
980                network.run_until_graceful_shutdown(shutdown, |network| {
981                    if let Some(peers_file) = known_peers_file {
982                        let num_known_peers = network.num_known_peers();
983                        trace!(target: "reth::cli", peers_file=?peers_file, num_peers=%num_known_peers, "Saving current peers");
984                        match network.write_peers_to_file(peers_file.as_path()) {
985                            Ok(_) => {
986                                info!(target: "reth::cli", peers_file=?peers_file, "Wrote network peers to file");
987                            }
988                            Err(err) => {
989                                warn!(target: "reth::cli", %err, "Failed to write network peers to file");
990                            }
991                        }
992                    }
993                })
994            },
995        );
996
997        handle
998    }
999
1000    /// Get the network secret from the given data dir
1001    fn network_secret(&self, data_dir: &ChainPath<DataDirPath>) -> eyre::Result<SecretKey> {
1002        let secret_key = self.config().network.secret_key(data_dir.p2p_secret())?;
1003        Ok(secret_key)
1004    }
1005
1006    /// Builds the [`NetworkConfig`].
1007    pub fn build_network_config<N>(
1008        &self,
1009        network_builder: NetworkConfigBuilder<N>,
1010    ) -> NetworkConfig<Node::Provider, N>
1011    where
1012        N: NetworkPrimitives,
1013        Node::Types: NodeTypes<ChainSpec: Hardforks>,
1014    {
1015        network_builder.build(self.provider.clone())
1016    }
1017}
1018
1019impl<Node: FullNodeTypes<Types: NodeTypes<ChainSpec: Hardforks>>> BuilderContext<Node> {
1020    /// Creates the [`NetworkBuilder`] for the node.
1021    pub async fn network_builder<N>(&self) -> eyre::Result<NetworkBuilder<(), (), N>>
1022    where
1023        N: NetworkPrimitives,
1024    {
1025        let network_config = self.network_config()?;
1026        let builder = NetworkManager::builder(network_config).await?;
1027        Ok(builder)
1028    }
1029
1030    /// Returns the default network config for the node.
1031    pub fn network_config<N>(&self) -> eyre::Result<NetworkConfig<Node::Provider, N>>
1032    where
1033        N: NetworkPrimitives,
1034    {
1035        let network_builder = self.network_config_builder();
1036        Ok(self.build_network_config(network_builder?))
1037    }
1038
1039    /// Get the [`NetworkConfigBuilder`].
1040    pub fn network_config_builder<N>(&self) -> eyre::Result<NetworkConfigBuilder<N>>
1041    where
1042        N: NetworkPrimitives,
1043    {
1044        let secret_key = self.network_secret(&self.config().datadir())?;
1045        let default_peers_path = self.config().datadir().known_peers();
1046        let builder = self
1047            .config()
1048            .network
1049            .network_config(
1050                self.reth_config(),
1051                self.config().chain.clone(),
1052                secret_key,
1053                default_peers_path,
1054                self.executor.clone(),
1055            )
1056            .set_head(self.head);
1057
1058        Ok(builder)
1059    }
1060}
1061
1062impl<Node: FullNodeTypes> std::fmt::Debug for BuilderContext<Node> {
1063    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1064        f.debug_struct("BuilderContext")
1065            .field("head", &self.head)
1066            .field("provider", &std::any::type_name::<Node::Provider>())
1067            .field("executor", &self.executor)
1068            .field("config", &self.config())
1069            .finish()
1070    }
1071}
1072
1073#[cfg(all(test, feature = "test-utils"))]
1074mod tests {
1075    use super::*;
1076    use reth_chainspec::ChainSpec;
1077    use reth_tasks::Runtime;
1078
1079    #[test]
1080    fn persistent_test_datadir_can_be_reopened() {
1081        let root = tempfile::tempdir().unwrap();
1082        let datadir = root.path().join("node");
1083        let runtime = Runtime::test();
1084
1085        let config = || NodeConfig::new(Arc::new(ChainSpec::<alloy_consensus::Header>::default()));
1086        let first = NodeBuilder::new(config())
1087            .testing_node_with_persistent_datadir(runtime.clone(), datadir.clone());
1088        assert!(datadir.join("db").exists());
1089        drop(first);
1090        assert!(datadir.join("db").exists());
1091
1092        let reopened = NodeBuilder::new(config())
1093            .testing_node_with_persistent_datadir(runtime, datadir.clone());
1094        drop(reopened);
1095        assert!(datadir.join("db").exists());
1096    }
1097}