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