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