reth_network/
config.rs

1//! Network config support
2
3use crate::{
4    error::NetworkError,
5    import::{BlockImport, ProofOfStakeBlockImport},
6    transactions::TransactionsManagerConfig,
7    NetworkHandle, NetworkManager,
8};
9use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
10use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
11use reth_discv5::NetworkStackId;
12use reth_dns_discovery::DnsDiscoveryConfig;
13use reth_eth_wire::{
14    handshake::{EthHandshake, EthRlpxHandshake},
15    EthNetworkPrimitives, HelloMessage, HelloMessageWithProtocols, NetworkPrimitives,
16    UnifiedStatus,
17};
18use reth_ethereum_forks::{ForkFilter, Head};
19use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
20use reth_network_types::{PeersConfig, SessionsConfig};
21use reth_storage_api::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
22use reth_tasks::{TaskSpawner, TokioTaskExecutor};
23use secp256k1::SECP256K1;
24use std::{collections::HashSet, net::SocketAddr, sync::Arc};
25
26// re-export for convenience
27use crate::protocol::{IntoRlpxSubProtocol, RlpxSubProtocols};
28pub use secp256k1::SecretKey;
29
30/// Convenience function to create a new random [`SecretKey`]
31pub fn rng_secret_key() -> SecretKey {
32    SecretKey::new(&mut rand_08::thread_rng())
33}
34
35/// All network related initialization settings.
36#[derive(Debug)]
37pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
38    /// The client type that can interact with the chain.
39    ///
40    /// This type is used to fetch the block number after we established a session and received the
41    /// [`UnifiedStatus`] block hash.
42    pub client: C,
43    /// The node's secret key, from which the node's identity is derived.
44    pub secret_key: SecretKey,
45    /// All boot nodes to start network discovery with.
46    pub boot_nodes: HashSet<TrustedPeer>,
47    /// How to set up discovery over DNS.
48    pub dns_discovery_config: Option<DnsDiscoveryConfig>,
49    /// Address to use for discovery v4.
50    pub discovery_v4_addr: SocketAddr,
51    /// How to set up discovery.
52    pub discovery_v4_config: Option<Discv4Config>,
53    /// How to set up discovery version 5.
54    pub discovery_v5_config: Option<reth_discv5::Config>,
55    /// Address to listen for incoming connections
56    pub listener_addr: SocketAddr,
57    /// How to instantiate peer manager.
58    pub peers_config: PeersConfig,
59    /// How to configure the [`SessionManager`](crate::session::SessionManager).
60    pub sessions_config: SessionsConfig,
61    /// The chain id
62    pub chain_id: u64,
63    /// The [`ForkFilter`] to use at launch for authenticating sessions.
64    ///
65    /// See also <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md#stale-software-examples>
66    ///
67    /// For sync from block `0`, this should be the default chain [`ForkFilter`] beginning at the
68    /// first hardfork, `Frontier` for mainnet.
69    pub fork_filter: ForkFilter,
70    /// The block importer type.
71    pub block_import: Box<dyn BlockImport<N::Block>>,
72    /// The default mode of the network.
73    pub network_mode: NetworkMode,
74    /// The executor to use for spawning tasks.
75    pub executor: Box<dyn TaskSpawner>,
76    /// The `Status` message to send to peers at the beginning.
77    pub status: UnifiedStatus,
78    /// Sets the hello message for the p2p handshake in `RLPx`
79    pub hello_message: HelloMessageWithProtocols,
80    /// Additional protocols to announce and handle in `RLPx`
81    pub extra_protocols: RlpxSubProtocols,
82    /// Whether to disable transaction gossip
83    pub tx_gossip_disabled: bool,
84    /// How to instantiate transactions manager.
85    pub transactions_manager_config: TransactionsManagerConfig,
86    /// The NAT resolver for external IP
87    pub nat: Option<NatResolver>,
88    /// The Ethereum P2P handshake, see also:
89    /// <https://github.com/ethereum/devp2p/blob/master/rlpx.md#initial-handshake>.
90    /// This can be overridden to support custom handshake logic via the
91    /// [`NetworkConfigBuilder`].
92    pub handshake: Arc<dyn EthRlpxHandshake>,
93}
94
95// === impl NetworkConfig ===
96
97impl<N: NetworkPrimitives> NetworkConfig<(), N> {
98    /// Convenience method for creating the corresponding builder type
99    pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
100        NetworkConfigBuilder::new(secret_key)
101    }
102
103    /// Convenience method for creating the corresponding builder type with a random secret key.
104    pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
105        NetworkConfigBuilder::with_rng_secret_key()
106    }
107}
108
109impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
110    /// Create a new instance with all mandatory fields set, rest is field with defaults.
111    pub fn new(client: C, secret_key: SecretKey) -> Self
112    where
113        C: ChainSpecProvider<ChainSpec: Hardforks>,
114    {
115        NetworkConfig::builder(secret_key).build(client)
116    }
117
118    /// Apply a function to the config.
119    pub fn apply<F>(self, f: F) -> Self
120    where
121        F: FnOnce(Self) -> Self,
122    {
123        f(self)
124    }
125
126    /// Sets the config to use for the discovery v4 protocol.
127    pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
128        self.discovery_v4_config = Some(discovery_config);
129        self
130    }
131
132    /// Sets the address for the incoming `RLPx` connection listener.
133    pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
134        self.listener_addr = listener_addr;
135        self
136    }
137
138    /// Returns the address for the incoming `RLPx` connection listener.
139    pub const fn listener_addr(&self) -> &SocketAddr {
140        &self.listener_addr
141    }
142}
143
144impl<C, N> NetworkConfig<C, N>
145where
146    C: BlockNumReader + 'static,
147    N: NetworkPrimitives,
148{
149    /// Convenience method for calling [`NetworkManager::new`].
150    pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
151        NetworkManager::new(self).await
152    }
153}
154
155impl<C, N> NetworkConfig<C, N>
156where
157    N: NetworkPrimitives,
158    C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
159        + HeaderProvider
160        + Clone
161        + Unpin
162        + 'static,
163{
164    /// Starts the networking stack given a [`NetworkConfig`] and returns a handle to the network.
165    pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
166        let client = self.client.clone();
167        let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
168            .await?
169            .request_handler::<C>(client)
170            .split_with_handle();
171
172        tokio::task::spawn(network);
173        tokio::task::spawn(eth);
174        Ok(handle)
175    }
176}
177
178/// Builder for [`NetworkConfig`](struct.NetworkConfig.html).
179#[derive(Debug)]
180pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
181    /// The node's secret key, from which the node's identity is derived.
182    secret_key: SecretKey,
183    /// How to configure discovery over DNS.
184    dns_discovery_config: Option<DnsDiscoveryConfig>,
185    /// How to set up discovery version 4.
186    discovery_v4_builder: Option<Discv4ConfigBuilder>,
187    /// How to set up discovery version 5.
188    discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
189    /// All boot nodes to start network discovery with.
190    boot_nodes: HashSet<TrustedPeer>,
191    /// Address to use for discovery
192    discovery_addr: Option<SocketAddr>,
193    /// Listener for incoming connections
194    listener_addr: Option<SocketAddr>,
195    /// How to instantiate peer manager.
196    peers_config: Option<PeersConfig>,
197    /// How to configure the sessions manager
198    sessions_config: Option<SessionsConfig>,
199    /// The default mode of the network.
200    network_mode: NetworkMode,
201    /// The executor to use for spawning tasks.
202    executor: Option<Box<dyn TaskSpawner>>,
203    /// Sets the hello message for the p2p handshake in `RLPx`
204    hello_message: Option<HelloMessageWithProtocols>,
205    /// The executor to use for spawning tasks.
206    extra_protocols: RlpxSubProtocols,
207    /// Head used to start set for the fork filter and status.
208    head: Option<Head>,
209    /// Whether tx gossip is disabled
210    tx_gossip_disabled: bool,
211    /// The block importer type
212    block_import: Option<Box<dyn BlockImport<N::Block>>>,
213    /// How to instantiate transactions manager.
214    transactions_manager_config: TransactionsManagerConfig,
215    /// The NAT resolver for external IP
216    nat: Option<NatResolver>,
217    /// The Ethereum P2P handshake, see also:
218    /// <https://github.com/ethereum/devp2p/blob/master/rlpx.md#initial-handshake>.
219    handshake: Arc<dyn EthRlpxHandshake>,
220}
221
222impl NetworkConfigBuilder<EthNetworkPrimitives> {
223    /// Creates the `NetworkConfigBuilder` with [`EthNetworkPrimitives`] types.
224    pub fn eth(secret_key: SecretKey) -> Self {
225        Self::new(secret_key)
226    }
227}
228
229// === impl NetworkConfigBuilder ===
230
231#[expect(missing_docs)]
232impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
233    /// Create a new builder instance with a random secret key.
234    pub fn with_rng_secret_key() -> Self {
235        Self::new(rng_secret_key())
236    }
237
238    /// Create a new builder instance with the given secret key.
239    pub fn new(secret_key: SecretKey) -> Self {
240        Self {
241            secret_key,
242            dns_discovery_config: Some(Default::default()),
243            discovery_v4_builder: Some(Default::default()),
244            discovery_v5_builder: None,
245            boot_nodes: Default::default(),
246            discovery_addr: None,
247            listener_addr: None,
248            peers_config: None,
249            sessions_config: None,
250            network_mode: Default::default(),
251            executor: None,
252            hello_message: None,
253            extra_protocols: Default::default(),
254            head: None,
255            tx_gossip_disabled: false,
256            block_import: None,
257            transactions_manager_config: Default::default(),
258            nat: None,
259            handshake: Arc::new(EthHandshake::default()),
260        }
261    }
262
263    /// Apply a function to the builder.
264    pub fn apply<F>(self, f: F) -> Self
265    where
266        F: FnOnce(Self) -> Self,
267    {
268        f(self)
269    }
270
271    /// Returns the configured [`PeerId`]
272    pub fn get_peer_id(&self) -> PeerId {
273        pk2id(&self.secret_key.public_key(SECP256K1))
274    }
275
276    /// Returns the configured [`SecretKey`], from which the node's identity is derived.
277    pub const fn secret_key(&self) -> &SecretKey {
278        &self.secret_key
279    }
280
281    /// Sets the [`NetworkMode`].
282    pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
283        self.network_mode = network_mode;
284        self
285    }
286
287    /// Configures the network to use proof-of-work.
288    ///
289    /// This effectively allows block propagation in the `eth` sub-protocol, which has been
290    /// soft-deprecated with ethereum `PoS` after the merge. Even if block propagation is
291    /// technically allowed, according to the eth protocol, it is not expected to be used in `PoS`
292    /// networks and peers are supposed to terminate the connection if they receive a `NewBlock`
293    /// message.
294    pub const fn with_pow(self) -> Self {
295        self.network_mode(NetworkMode::Work)
296    }
297
298    /// Sets the highest synced block.
299    ///
300    /// This is used to construct the appropriate [`ForkFilter`] and [`UnifiedStatus`] message.
301    ///
302    /// If not set, this defaults to the genesis specified by the current chain specification.
303    pub const fn set_head(mut self, head: Head) -> Self {
304        self.head = Some(head);
305        self
306    }
307
308    /// Sets the `HelloMessage` to send when connecting to peers.
309    ///
310    /// ```
311    /// # use reth_eth_wire::HelloMessage;
312    /// # use reth_network::NetworkConfigBuilder;
313    /// # fn builder(builder: NetworkConfigBuilder) {
314    /// let peer_id = builder.get_peer_id();
315    /// builder.hello_message(HelloMessage::builder(peer_id).build());
316    /// # }
317    /// ```
318    pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
319        self.hello_message = Some(hello_message);
320        self
321    }
322
323    /// Set a custom peer config for how peers are handled
324    pub fn peer_config(mut self, config: PeersConfig) -> Self {
325        self.peers_config = Some(config);
326        self
327    }
328
329    /// Sets the executor to use for spawning tasks.
330    ///
331    /// If `None`, then [`tokio::spawn`] is used for spawning tasks.
332    pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
333        self.executor = Some(executor);
334        self
335    }
336
337    /// Sets a custom config for how sessions are handled.
338    pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
339        self.sessions_config = Some(config);
340        self
341    }
342
343    /// Configures the transactions manager with the given config.
344    pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
345        self.transactions_manager_config = config;
346        self
347    }
348
349    /// Sets the discovery and listener address
350    ///
351    /// This is a convenience function for both [`NetworkConfigBuilder::listener_addr`] and
352    /// [`NetworkConfigBuilder::discovery_addr`].
353    ///
354    /// By default, both are on the same port:
355    /// [`DEFAULT_DISCOVERY_PORT`](reth_discv4::DEFAULT_DISCOVERY_PORT)
356    pub const fn set_addrs(self, addr: SocketAddr) -> Self {
357        self.listener_addr(addr).discovery_addr(addr)
358    }
359
360    /// Sets the socket address the network will listen on.
361    ///
362    /// By default, this is [`DEFAULT_DISCOVERY_ADDRESS`]
363    pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
364        self.listener_addr = Some(listener_addr);
365        self
366    }
367
368    /// Sets the port of the address the network will listen on.
369    ///
370    /// By default, this is [`DEFAULT_DISCOVERY_PORT`](reth_discv4::DEFAULT_DISCOVERY_PORT)
371    pub fn listener_port(mut self, port: u16) -> Self {
372        self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
373        self
374    }
375
376    /// Sets the socket address the discovery network will listen on
377    pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
378        self.discovery_addr = Some(discovery_addr);
379        self
380    }
381
382    /// Sets the port of the address the discovery network will listen on.
383    ///
384    /// By default, this is [`DEFAULT_DISCOVERY_PORT`](reth_discv4::DEFAULT_DISCOVERY_PORT)
385    pub fn discovery_port(mut self, port: u16) -> Self {
386        self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
387        self
388    }
389
390    /// Launches the network with an unused network and discovery port
391    /// This is useful for testing.
392    pub fn with_unused_ports(self) -> Self {
393        self.with_unused_discovery_port().with_unused_listener_port()
394    }
395
396    /// Sets the discovery port to an unused port.
397    /// This is useful for testing.
398    pub fn with_unused_discovery_port(self) -> Self {
399        self.discovery_port(0)
400    }
401
402    /// Sets the listener port to an unused port.
403    /// This is useful for testing.
404    pub fn with_unused_listener_port(self) -> Self {
405        self.listener_port(0)
406    }
407
408    /// Sets the external ip resolver to use for discovery v4.
409    ///
410    /// If no [`Discv4ConfigBuilder`] is set via [`Self::discovery`], this will create a new one.
411    ///
412    /// This is a convenience function for setting the external ip resolver on the default
413    /// [`Discv4Config`] config.
414    pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
415        self.discovery_v4_builder
416            .get_or_insert_with(Discv4Config::builder)
417            .external_ip_resolver(Some(resolver));
418        self.nat = Some(resolver);
419        self
420    }
421
422    /// Sets the discv4 config to use.
423    pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
424        self.discovery_v4_builder = Some(builder);
425        self
426    }
427
428    /// Sets the discv5 config to use.
429    pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
430        self.discovery_v5_builder = Some(builder);
431        self
432    }
433
434    /// Sets the dns discovery config to use.
435    pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
436        self.dns_discovery_config = Some(config);
437        self
438    }
439
440    /// Convenience function for setting [`Self::boot_nodes`] to the mainnet boot nodes.
441    pub fn mainnet_boot_nodes(self) -> Self {
442        self.boot_nodes(mainnet_nodes())
443    }
444
445    /// Convenience function for setting [`Self::boot_nodes`] to the sepolia boot nodes.
446    pub fn sepolia_boot_nodes(self) -> Self {
447        self.boot_nodes(sepolia_nodes())
448    }
449
450    /// Sets the boot nodes to use to bootstrap the configured discovery services (discv4 + discv5).
451    pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
452        self.boot_nodes = nodes.into_iter().map(Into::into).collect();
453        self
454    }
455
456    /// Returns an iterator over all configured boot nodes.
457    pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
458        self.boot_nodes.iter()
459    }
460
461    /// Disable the DNS discovery.
462    pub fn disable_dns_discovery(mut self) -> Self {
463        self.dns_discovery_config = None;
464        self
465    }
466
467    // Disable nat
468    pub const fn disable_nat(mut self) -> Self {
469        self.nat = None;
470        self
471    }
472
473    /// Disables all discovery.
474    pub fn disable_discovery(self) -> Self {
475        self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
476    }
477
478    /// Disables all discovery if the given condition is true.
479    pub fn disable_discovery_if(self, disable: bool) -> Self {
480        if disable {
481            self.disable_discovery()
482        } else {
483            self
484        }
485    }
486
487    /// Disable the Discv4 discovery.
488    pub fn disable_discv4_discovery(mut self) -> Self {
489        self.discovery_v4_builder = None;
490        self
491    }
492
493    /// Disable the Discv5 discovery.
494    pub fn disable_discv5_discovery(mut self) -> Self {
495        self.discovery_v5_builder = None;
496        self
497    }
498
499    /// Disable the DNS discovery if the given condition is true.
500    pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
501        if disable {
502            self.disable_dns_discovery()
503        } else {
504            self
505        }
506    }
507
508    /// Disable the Discv4 discovery if the given condition is true.
509    pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
510        if disable {
511            self.disable_discv4_discovery()
512        } else {
513            self
514        }
515    }
516
517    /// Disable the Discv5 discovery if the given condition is true.
518    pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
519        if disable {
520            self.disable_discv5_discovery()
521        } else {
522            self
523        }
524    }
525
526    /// Adds a new additional protocol to the `RLPx` sub-protocol list.
527    pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
528        self.extra_protocols.push(protocol);
529        self
530    }
531
532    /// Sets whether tx gossip is disabled.
533    pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
534        self.tx_gossip_disabled = disable_tx_gossip;
535        self
536    }
537
538    /// Sets the block import type.
539    pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::Block>>) -> Self {
540        self.block_import = Some(block_import);
541        self
542    }
543
544    /// Convenience function for creating a [`NetworkConfig`] with a noop provider that does
545    /// nothing.
546    pub fn build_with_noop_provider<ChainSpec>(
547        self,
548        chain_spec: Arc<ChainSpec>,
549    ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
550    where
551        ChainSpec: EthChainSpec + Hardforks + 'static,
552    {
553        self.build(NoopProvider::eth(chain_spec))
554    }
555
556    /// Sets the NAT resolver for external IP.
557    pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
558        self.nat = nat;
559        self
560    }
561
562    /// Overrides the default Eth `RLPx` handshake.
563    pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
564        self.handshake = handshake;
565        self
566    }
567
568    /// Consumes the type and creates the actual [`NetworkConfig`]
569    /// for the given client type that can interact with the chain.
570    ///
571    /// The given client is to be used for interacting with the chain, for example fetching the
572    /// corresponding block for a given block hash we receive from a peer in the status message when
573    /// establishing a connection.
574    pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
575    where
576        C: ChainSpecProvider<ChainSpec: Hardforks>,
577    {
578        let peer_id = self.get_peer_id();
579        let chain_spec = client.chain_spec();
580        let Self {
581            secret_key,
582            mut dns_discovery_config,
583            discovery_v4_builder,
584            mut discovery_v5_builder,
585            boot_nodes,
586            discovery_addr,
587            listener_addr,
588            peers_config,
589            sessions_config,
590            network_mode,
591            executor,
592            hello_message,
593            extra_protocols,
594            head,
595            tx_gossip_disabled,
596            block_import,
597            transactions_manager_config,
598            nat,
599            handshake,
600        } = self;
601
602        let head = head.unwrap_or_else(|| Head {
603            hash: chain_spec.genesis_hash(),
604            number: 0,
605            timestamp: chain_spec.genesis().timestamp,
606            difficulty: chain_spec.genesis().difficulty,
607            total_difficulty: chain_spec.genesis().difficulty,
608        });
609
610        discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
611            if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
612                let fork_id = chain_spec.fork_id(&head);
613                builder = builder.fork(network_stack_id, fork_id)
614            }
615
616            builder
617        });
618
619        let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
620
621        let mut hello_message =
622            hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
623        hello_message.port = listener_addr.port();
624
625        // set the status
626        let status = UnifiedStatus::spec_builder(&chain_spec, &head);
627
628        // set a fork filter based on the chain spec and head
629        let fork_filter = chain_spec.fork_filter(head);
630
631        // get the chain id
632        let chain_id = chain_spec.chain().id();
633
634        // If default DNS config is used then we add the known dns network to bootstrap from
635        if let Some(dns_networks) =
636            dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut())
637        {
638            if dns_networks.is_empty() {
639                if let Some(link) = chain_spec.chain().public_dns_network_protocol() {
640                    dns_networks.insert(link.parse().expect("is valid DNS link entry"));
641                }
642            }
643        }
644
645        NetworkConfig {
646            client,
647            secret_key,
648            boot_nodes,
649            dns_discovery_config,
650            discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
651            discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
652            discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
653            listener_addr,
654            peers_config: peers_config.unwrap_or_default(),
655            sessions_config: sessions_config.unwrap_or_default(),
656            chain_id,
657            block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
658            network_mode,
659            executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
660            status,
661            hello_message,
662            extra_protocols,
663            fork_filter,
664            tx_gossip_disabled,
665            transactions_manager_config,
666            nat,
667            handshake,
668        }
669    }
670}
671
672/// Describes the mode of the network wrt. POS or POW.
673///
674/// This affects block propagation in the `eth` sub-protocol [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#devp2p)
675///
676/// In POS `NewBlockHashes` and `NewBlock` messages become invalid.
677#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
678#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
679pub enum NetworkMode {
680    /// Network is in proof-of-work mode.
681    Work,
682    /// Network is in proof-of-stake mode
683    #[default]
684    Stake,
685}
686
687// === impl NetworkMode ===
688
689impl NetworkMode {
690    /// Returns true if network has entered proof-of-stake
691    pub const fn is_stake(&self) -> bool {
692        matches!(self, Self::Stake)
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699    use alloy_eips::eip2124::ForkHash;
700    use alloy_genesis::Genesis;
701    use alloy_primitives::U256;
702    use reth_chainspec::{
703        Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
704    };
705    use reth_discv5::build_local_enr;
706    use reth_dns_discovery::tree::LinkEntry;
707    use reth_storage_api::noop::NoopProvider;
708    use std::{net::Ipv4Addr, sync::Arc};
709
710    fn builder() -> NetworkConfigBuilder {
711        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
712        NetworkConfigBuilder::new(secret_key)
713    }
714
715    #[test]
716    fn test_network_dns_defaults() {
717        let config = builder().build(NoopProvider::default());
718
719        let dns = config.dns_discovery_config.unwrap();
720        let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
721        let mainnet_dns: LinkEntry =
722            Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
723        assert!(bootstrap_nodes.contains(&mainnet_dns));
724        assert_eq!(bootstrap_nodes.len(), 1);
725    }
726
727    #[test]
728    fn test_network_fork_filter_default() {
729        let mut chain_spec = Arc::clone(&MAINNET);
730
731        // remove any `next` fields we would have by removing all hardforks
732        Arc::make_mut(&mut chain_spec).hardforks = Default::default();
733
734        // check that the forkid is initialized with the genesis and no other forks
735        let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
736
737        // enforce that the fork_id set in the status is consistent with the generated fork filter
738        let config = builder().build_with_noop_provider(chain_spec);
739
740        let status = config.status;
741        let fork_filter = config.fork_filter;
742
743        // assert that there are no other forks
744        assert_eq!(status.forkid.next, 0);
745
746        // assert the same thing for the fork_filter
747        assert_eq!(fork_filter.current().next, 0);
748
749        // check status and fork_filter forkhash
750        assert_eq!(status.forkid.hash, genesis_fork_hash);
751        assert_eq!(fork_filter.current().hash, genesis_fork_hash);
752    }
753
754    #[test]
755    fn test_discv5_fork_id_default() {
756        const GENESIS_TIME: u64 = 151_515;
757
758        let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
759
760        let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
761        let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
762
763        let chain_spec = ChainSpecBuilder::default()
764            .chain(Chain::dev())
765            .genesis(genesis)
766            .with_fork(active_fork.0, active_fork.1)
767            .with_fork(future_fork.0, future_fork.1)
768            .build();
769
770        // get the fork id to advertise on discv5
771        let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
772        let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
773        // check the fork id is set to active fork and _not_ yet future fork
774        assert_eq!(
775            fork_id,
776            chain_spec.fork_id(&Head {
777                hash: chain_spec.genesis_hash(),
778                number: 0,
779                timestamp: GENESIS_TIME,
780                difficulty: U256::ZERO,
781                total_difficulty: U256::ZERO,
782            })
783        );
784        assert_ne!(fork_id, chain_spec.latest_fork_id());
785
786        // enforce that the fork_id set in local enr
787        let fork_key = b"odyssey";
788        let config = builder()
789            .discovery_v5(
790                reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
791                    .fork(fork_key, fork_id),
792            )
793            .build_with_noop_provider(Arc::new(chain_spec));
794
795        let (local_enr, _, _, _) = build_local_enr(
796            &config.secret_key,
797            &config.discovery_v5_config.expect("should build config"),
798        );
799
800        // peers on the odyssey network will check discovered enrs for the 'odyssey' key and
801        // decide based on this if they attempt and rlpx connection to the peer or not
802        let advertised_fork_id = *local_enr
803            .get_decodable::<Vec<ForkId>>(fork_key)
804            .expect("should read 'odyssey'")
805            .expect("should decode fork id list")
806            .first()
807            .expect("should be non-empty");
808
809        assert_eq!(advertised_fork_id, fork_id);
810    }
811}