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