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