Skip to main content

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