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