reth_network/
config.rs

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