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