Skip to main content

reth_network/test_utils/
testnet.rs

1//! A network implementation for testing purposes.
2
3use crate::{
4    builder::ETH_REQUEST_CHANNEL_CAPACITY,
5    error::NetworkError,
6    eth_requests::EthRequestHandler,
7    protocol::IntoRlpxSubProtocol,
8    transactions::{
9        config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
10        policy::NetworkPolicies,
11        TransactionsHandle, TransactionsManager, TransactionsManagerConfig,
12    },
13    NetworkConfig, NetworkConfigBuilder, NetworkHandle, NetworkManager, PeersConfig,
14};
15use futures::{FutureExt, StreamExt};
16use pin_project::pin_project;
17use reth_chainspec::{ChainSpecProvider, EthereumHardforks, Hardforks};
18use reth_eth_wire::{
19    protocol::Protocol, DisconnectReason, EthNetworkPrimitives, HelloMessageWithProtocols,
20};
21use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
22use reth_evm_ethereum::EthEvmConfig;
23use reth_metrics::common::mpsc::memory_bounded_channel;
24use reth_network_api::{
25    events::{PeerEvent, SessionInfo},
26    test_utils::{PeersHandle, PeersHandleProvider},
27    NetworkEvent, NetworkEventListenerProvider, NetworkInfo, Peers,
28};
29use reth_network_peers::PeerId;
30use reth_storage_api::{
31    noop::NoopProvider, BalProvider, BlockReader, BlockReaderIdExt, HeaderProvider,
32    StateProviderFactory, StateRangeProviderFactory,
33};
34use reth_tasks::Runtime;
35use reth_tokio_util::EventStream;
36use reth_transaction_pool::{
37    blobstore::InMemoryBlobStore,
38    test_utils::{TestPool, TestPoolBuilder},
39    EthTransactionPool, PoolTransaction, TransactionPool, TransactionValidationTaskExecutor,
40};
41use secp256k1::SecretKey;
42use std::{
43    fmt,
44    future::Future,
45    net::{Ipv4Addr, SocketAddr, SocketAddrV4},
46    pin::Pin,
47    task::{Context, Poll},
48};
49use tokio::{
50    sync::{mpsc::channel, oneshot},
51    task::JoinHandle,
52};
53
54use crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES;
55
56/// A test network consisting of multiple peers.
57pub struct Testnet<C, Pool> {
58    /// All running peers in the network.
59    peers: Vec<Peer<C, Pool>>,
60}
61
62// === impl Testnet ===
63
64impl<C> Testnet<C, TestPool>
65where
66    C: BlockReader + HeaderProvider + Clone + 'static + ChainSpecProvider<ChainSpec: Hardforks>,
67{
68    /// Same as [`Self::try_create_with`] but panics on error
69    pub async fn create_with(num_peers: usize, provider: C) -> Self {
70        Self::try_create_with(num_peers, provider).await.unwrap()
71    }
72
73    /// Creates a new [`Testnet`] with the given number of peers and the provider.
74    pub async fn try_create_with(num_peers: usize, provider: C) -> Result<Self, NetworkError> {
75        let mut this = Self { peers: Vec::with_capacity(num_peers) };
76        for _ in 0..num_peers {
77            let config = PeerConfig::new(provider.clone());
78            this.add_peer_with_config(config).await?;
79        }
80        Ok(this)
81    }
82
83    /// Extend the list of peers with new peers that are configured with each of the given
84    /// [`PeerConfig`]s.
85    pub async fn extend_peer_with_config(
86        &mut self,
87        configs: impl IntoIterator<Item = PeerConfig<C>>,
88    ) -> Result<(), NetworkError> {
89        let peers = configs.into_iter().map(|c| c.launch()).collect::<Vec<_>>();
90        let peers = futures::future::join_all(peers).await;
91        for peer in peers {
92            self.peers.push(peer?);
93        }
94        Ok(())
95    }
96}
97
98impl<C, Pool> Testnet<C, Pool>
99where
100    C: BlockReader + HeaderProvider + Clone + 'static,
101    Pool: TransactionPool,
102{
103    /// Return a mutable slice of all peers.
104    pub fn peers_mut(&mut self) -> &mut [Peer<C, Pool>] {
105        &mut self.peers
106    }
107
108    /// Return a slice of all peers.
109    pub fn peers(&self) -> &[Peer<C, Pool>] {
110        &self.peers
111    }
112
113    /// Remove a peer from the [`Testnet`] and return it.
114    ///
115    /// # Panics
116    /// If the index is out of bounds.
117    pub fn remove_peer(&mut self, index: usize) -> Peer<C, Pool> {
118        self.peers.remove(index)
119    }
120
121    /// Return a mutable iterator over all peers.
122    pub fn peers_iter_mut(&mut self) -> impl Iterator<Item = &mut Peer<C, Pool>> + '_ {
123        self.peers.iter_mut()
124    }
125
126    /// Return an iterator over all peers.
127    pub fn peers_iter(&self) -> impl Iterator<Item = &Peer<C, Pool>> + '_ {
128        self.peers.iter()
129    }
130
131    /// Add a peer to the [`Testnet`] with the given [`PeerConfig`].
132    pub async fn add_peer_with_config(
133        &mut self,
134        config: PeerConfig<C>,
135    ) -> Result<(), NetworkError> {
136        let PeerConfig { config, client, secret_key } = config;
137
138        let network = NetworkManager::new(config).await?;
139        let peer = Peer {
140            network,
141            client,
142            secret_key,
143            request_handler: None,
144            transactions_manager: None,
145            pool: None,
146        };
147        self.peers.push(peer);
148        Ok(())
149    }
150
151    /// Returns all handles to the networks
152    pub fn handles(&self) -> impl Iterator<Item = NetworkHandle<EthNetworkPrimitives>> + '_ {
153        self.peers.iter().map(|p| p.handle())
154    }
155
156    /// Maps the pool of each peer with the given closure
157    pub fn map_pool<F, P>(self, f: F) -> Testnet<C, P>
158    where
159        F: Fn(Peer<C, Pool>) -> Peer<C, P>,
160        P: TransactionPool,
161    {
162        Testnet { peers: self.peers.into_iter().map(f).collect() }
163    }
164
165    /// Apply a closure on each peer
166    pub fn for_each<F>(&self, f: F)
167    where
168        F: Fn(&Peer<C, Pool>),
169    {
170        self.peers.iter().for_each(f)
171    }
172
173    /// Apply a closure on each peer
174    pub fn for_each_mut<F>(&mut self, f: F)
175    where
176        F: FnMut(&mut Peer<C, Pool>),
177    {
178        self.peers.iter_mut().for_each(f)
179    }
180}
181
182impl<C, Pool> Testnet<C, Pool>
183where
184    C: ChainSpecProvider<ChainSpec: EthereumHardforks>
185        + StateProviderFactory
186        + BlockReaderIdExt
187        + HeaderProvider<Header = alloy_consensus::Header>
188        + Clone
189        + 'static,
190    Pool: TransactionPool,
191{
192    /// Installs an eth pool on each peer
193    pub fn with_eth_pool(
194        self,
195    ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
196        self.map_pool(|peer| {
197            let blob_store = InMemoryBlobStore::default();
198            let pool = TransactionValidationTaskExecutor::eth(
199                peer.client.clone(),
200                EthEvmConfig::mainnet(),
201                blob_store.clone(),
202                Runtime::test(),
203            );
204            peer.map_transactions_manager(EthTransactionPool::eth_pool(
205                pool,
206                blob_store,
207                Default::default(),
208            ))
209        })
210    }
211
212    /// Installs an eth pool on each peer with custom transaction manager config
213    pub fn with_eth_pool_config(
214        self,
215        tx_manager_config: TransactionsManagerConfig,
216    ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
217        self.with_eth_pool_config_and_policy(tx_manager_config, Default::default())
218    }
219
220    /// Installs an eth pool on each peer with custom transaction manager config and policy.
221    pub fn with_eth_pool_config_and_policy(
222        self,
223        tx_manager_config: TransactionsManagerConfig,
224        policy: TransactionPropagationKind,
225    ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
226        self.map_pool(|peer| {
227            let blob_store = InMemoryBlobStore::default();
228            let pool = TransactionValidationTaskExecutor::eth(
229                peer.client.clone(),
230                EthEvmConfig::mainnet(),
231                blob_store.clone(),
232                Runtime::test(),
233            );
234
235            peer.map_transactions_manager_with(
236                EthTransactionPool::eth_pool(pool, blob_store, Default::default()),
237                tx_manager_config.clone(),
238                policy,
239            )
240        })
241    }
242}
243
244impl<C, Pool> Testnet<C, Pool>
245where
246    C: BlockReader<
247            Block = reth_ethereum_primitives::Block,
248            Receipt = reth_ethereum_primitives::Receipt,
249            Header = alloy_consensus::Header,
250        > + HeaderProvider
251        + BalProvider
252        + StateProviderFactory
253        + StateRangeProviderFactory
254        + Clone
255        + Unpin
256        + 'static,
257    Pool: TransactionPool<
258            Transaction: PoolTransaction<
259                Consensus = TransactionSigned,
260                Pooled = PooledTransactionVariant,
261            >,
262        > + Unpin
263        + 'static,
264{
265    /// Spawns the testnet to a separate task
266    pub fn spawn(self) -> TestnetHandle<C, Pool> {
267        let (tx, rx) = oneshot::channel::<oneshot::Sender<Self>>();
268        let peers = self.peers.iter().map(|peer| peer.peer_handle()).collect::<Vec<_>>();
269        let mut net = self;
270        let handle = tokio::task::spawn(async move {
271            let mut tx = None;
272            tokio::select! {
273                _ = &mut net => {}
274                inc = rx => {
275                    tx = inc.ok();
276                }
277            }
278            if let Some(tx) = tx {
279                let _ = tx.send(net);
280            }
281        });
282
283        TestnetHandle { _handle: handle, peers, terminate: tx }
284    }
285}
286
287impl Testnet<NoopProvider, TestPool> {
288    /// Same as [`Self::try_create`] but panics on error
289    pub async fn create(num_peers: usize) -> Self {
290        Self::try_create(num_peers).await.unwrap()
291    }
292
293    /// Creates a new [`Testnet`] with the given number of peers
294    pub async fn try_create(num_peers: usize) -> Result<Self, NetworkError> {
295        let mut this = Self::default();
296
297        this.extend_peer_with_config((0..num_peers).map(|_| Default::default())).await?;
298        Ok(this)
299    }
300
301    /// Add a peer to the [`Testnet`]
302    pub async fn add_peer(&mut self) -> Result<(), NetworkError> {
303        self.add_peer_with_config(Default::default()).await
304    }
305}
306
307impl<C, Pool> Default for Testnet<C, Pool> {
308    fn default() -> Self {
309        Self { peers: Vec::new() }
310    }
311}
312
313impl<C, Pool> fmt::Debug for Testnet<C, Pool> {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        f.debug_struct("Testnet {{}}").finish_non_exhaustive()
316    }
317}
318
319impl<C, Pool> Future for Testnet<C, Pool>
320where
321    C: BlockReader<
322            Block = reth_ethereum_primitives::Block,
323            Receipt = reth_ethereum_primitives::Receipt,
324            Header = alloy_consensus::Header,
325        > + HeaderProvider
326        + BalProvider
327        + StateProviderFactory
328        + StateRangeProviderFactory
329        + Unpin
330        + 'static,
331    Pool: TransactionPool<
332            Transaction: PoolTransaction<
333                Consensus = TransactionSigned,
334                Pooled = PooledTransactionVariant,
335            >,
336        > + Unpin
337        + 'static,
338{
339    type Output = ();
340
341    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
342        let this = self.get_mut();
343        for peer in &mut this.peers {
344            let _ = peer.poll_unpin(cx);
345        }
346        Poll::Pending
347    }
348}
349
350/// A handle to a [`Testnet`] that can be shared.
351#[derive(Debug)]
352pub struct TestnetHandle<C, Pool> {
353    _handle: JoinHandle<()>,
354    peers: Vec<PeerHandle<Pool>>,
355    terminate: oneshot::Sender<oneshot::Sender<Testnet<C, Pool>>>,
356}
357
358// === impl TestnetHandle ===
359
360impl<C, Pool> TestnetHandle<C, Pool> {
361    /// Terminates the task and returns the [`Testnet`] back.
362    pub async fn terminate(self) -> Testnet<C, Pool> {
363        let (tx, rx) = oneshot::channel();
364        self.terminate.send(tx).unwrap();
365        rx.await.unwrap()
366    }
367
368    /// Returns the [`PeerHandle`]s of this [`Testnet`].
369    pub fn peers(&self) -> &[PeerHandle<Pool>] {
370        &self.peers
371    }
372
373    /// Connects all peers with each other.
374    ///
375    /// This establishes sessions concurrently between all peers.
376    ///
377    /// Returns once all sessions are established.
378    pub async fn connect_peers(&self) {
379        if self.peers.len() < 2 {
380            return
381        }
382
383        // add an event stream for _each_ peer
384        let streams =
385            self.peers.iter().map(|handle| NetworkEventStream::new(handle.event_listener()));
386
387        // add all peers to each other
388        for (idx, handle) in self.peers.iter().enumerate().take(self.peers.len() - 1) {
389            for idx in (idx + 1)..self.peers.len() {
390                let neighbour = &self.peers[idx];
391                handle.network.add_peer(*neighbour.peer_id(), neighbour.local_addr());
392            }
393        }
394
395        // await all sessions to be established
396        let num_sessions_per_peer = self.peers.len() - 1;
397        let fut = streams.into_iter().map(|mut stream| async move {
398            stream.take_session_established(num_sessions_per_peer).await
399        });
400
401        futures::future::join_all(fut).await;
402    }
403}
404
405/// A peer in the [`Testnet`].
406#[pin_project]
407#[derive(Debug)]
408pub struct Peer<C, Pool = TestPool> {
409    #[pin]
410    network: NetworkManager<EthNetworkPrimitives>,
411    #[pin]
412    request_handler: Option<EthRequestHandler<C, EthNetworkPrimitives>>,
413    #[pin]
414    transactions_manager: Option<TransactionsManager<Pool, EthNetworkPrimitives>>,
415    pool: Option<Pool>,
416    client: C,
417    secret_key: SecretKey,
418}
419
420// === impl Peer ===
421
422impl<C, Pool> Peer<C, Pool>
423where
424    C: BlockReader + HeaderProvider + Clone + 'static,
425    Pool: TransactionPool,
426{
427    /// Returns the number of connected peers.
428    pub fn num_peers(&self) -> usize {
429        self.network.num_connected_peers()
430    }
431
432    /// Adds an additional protocol handler to the peer.
433    pub fn add_rlpx_sub_protocol(&mut self, protocol: impl IntoRlpxSubProtocol) {
434        self.network.add_rlpx_sub_protocol(protocol);
435    }
436
437    /// Returns a handle to the peer's network.
438    pub fn peer_handle(&self) -> PeerHandle<Pool> {
439        PeerHandle {
440            network: self.network.handle().clone(),
441            pool: self.pool.clone(),
442            transactions: self.transactions_manager.as_ref().map(|mgr| mgr.handle()),
443        }
444    }
445
446    /// The address that listens for incoming connections.
447    pub const fn local_addr(&self) -> SocketAddr {
448        self.network.local_addr()
449    }
450
451    /// The [`PeerId`] of this peer.
452    pub fn peer_id(&self) -> PeerId {
453        *self.network.peer_id()
454    }
455
456    /// Returns mutable access to the network.
457    pub const fn network_mut(&mut self) -> &mut NetworkManager<EthNetworkPrimitives> {
458        &mut self.network
459    }
460
461    /// Returns the [`NetworkHandle`] of this peer.
462    pub fn handle(&self) -> NetworkHandle<EthNetworkPrimitives> {
463        self.network.handle().clone()
464    }
465
466    /// Returns the [`TestPool`] of this peer.
467    pub const fn pool(&self) -> Option<&Pool> {
468        self.pool.as_ref()
469    }
470
471    /// Set a new request handler that's connected to the peer's network
472    pub fn install_request_handler(&mut self)
473    where
474        C: BalProvider,
475    {
476        let (tx, rx) = channel(ETH_REQUEST_CHANNEL_CAPACITY);
477        self.network.set_eth_request_handler(tx);
478        let peers = self.network.peers_handle();
479        let request_handler = EthRequestHandler::new(self.client.clone(), peers, rx);
480        self.request_handler = Some(request_handler);
481    }
482
483    /// Set a new transactions manager that's connected to the peer's network
484    pub fn install_transactions_manager(&mut self, pool: Pool) {
485        let (tx, rx) = memory_bounded_channel(
486            DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
487            "test_tx_channel",
488        );
489        self.network.set_transactions(tx);
490        let transactions_manager = TransactionsManager::new(
491            self.handle(),
492            pool.clone(),
493            rx,
494            TransactionsManagerConfig::default(),
495        );
496        self.transactions_manager = Some(transactions_manager);
497        self.pool = Some(pool);
498    }
499
500    /// Set a new transactions manager that's connected to the peer's network
501    pub fn map_transactions_manager<P>(self, pool: P) -> Peer<C, P>
502    where
503        P: TransactionPool,
504    {
505        let Self { mut network, request_handler, client, secret_key, .. } = self;
506        let (tx, rx) = memory_bounded_channel(
507            DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
508            "test_tx_channel",
509        );
510        network.set_transactions(tx);
511        let transactions_manager = TransactionsManager::new(
512            network.handle().clone(),
513            pool.clone(),
514            rx,
515            TransactionsManagerConfig::default(),
516        );
517        Peer {
518            network,
519            request_handler,
520            transactions_manager: Some(transactions_manager),
521            pool: Some(pool),
522            client,
523            secret_key,
524        }
525    }
526
527    /// Map transactions manager with custom config
528    pub fn map_transactions_manager_with_config<P>(
529        self,
530        pool: P,
531        config: TransactionsManagerConfig,
532    ) -> Peer<C, P>
533    where
534        P: TransactionPool,
535    {
536        self.map_transactions_manager_with(pool, config, Default::default())
537    }
538
539    /// Map transactions manager with custom config and the given policy.
540    pub fn map_transactions_manager_with<P>(
541        self,
542        pool: P,
543        config: TransactionsManagerConfig,
544        policy: TransactionPropagationKind,
545    ) -> Peer<C, P>
546    where
547        P: TransactionPool,
548    {
549        let Self { mut network, request_handler, client, secret_key, .. } = self;
550        let (tx, rx) = memory_bounded_channel(
551            DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
552            "test_tx_channel",
553        );
554        network.set_transactions(tx);
555
556        let announcement_policy = StrictEthAnnouncementFilter::default();
557        let policies = NetworkPolicies::new(policy, announcement_policy);
558
559        let transactions_manager = TransactionsManager::with_policy(
560            network.handle().clone(),
561            pool.clone(),
562            rx,
563            config,
564            policies,
565        );
566
567        Peer {
568            network,
569            request_handler,
570            transactions_manager: Some(transactions_manager),
571            pool: Some(pool),
572            client,
573            secret_key,
574        }
575    }
576}
577
578impl<C> Peer<C>
579where
580    C: BlockReader + HeaderProvider + Clone + 'static,
581{
582    /// Installs a new [`TestPool`]
583    pub fn install_test_pool(&mut self) {
584        self.install_transactions_manager(TestPoolBuilder::default().into())
585    }
586}
587
588impl<C, Pool> Future for Peer<C, Pool>
589where
590    C: BlockReader<
591            Block = reth_ethereum_primitives::Block,
592            Receipt = reth_ethereum_primitives::Receipt,
593            Header = alloy_consensus::Header,
594        > + HeaderProvider
595        + BalProvider
596        + StateProviderFactory
597        + StateRangeProviderFactory
598        + Unpin
599        + 'static,
600    Pool: TransactionPool<
601            Transaction: PoolTransaction<
602                Consensus = TransactionSigned,
603                Pooled = PooledTransactionVariant,
604            >,
605        > + Unpin
606        + 'static,
607{
608    type Output = ();
609
610    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
611        let this = self.project();
612
613        if let Some(request) = this.request_handler.as_pin_mut() {
614            let _ = request.poll(cx);
615        }
616
617        if let Some(tx_manager) = this.transactions_manager.as_pin_mut() {
618            let _ = tx_manager.poll(cx);
619        }
620
621        this.network.poll(cx)
622    }
623}
624
625/// A helper config for setting up the reth networking stack.
626#[derive(Debug)]
627pub struct PeerConfig<C = NoopProvider> {
628    config: NetworkConfig<C>,
629    client: C,
630    secret_key: SecretKey,
631}
632
633/// A handle to a peer in the [`Testnet`].
634#[derive(Debug)]
635pub struct PeerHandle<Pool> {
636    network: NetworkHandle<EthNetworkPrimitives>,
637    transactions: Option<TransactionsHandle<EthNetworkPrimitives>>,
638    pool: Option<Pool>,
639}
640
641// === impl PeerHandle ===
642
643impl<Pool> PeerHandle<Pool> {
644    /// Returns the [`PeerId`] used in the network.
645    pub fn peer_id(&self) -> &PeerId {
646        self.network.peer_id()
647    }
648
649    /// Returns the [`PeersHandle`] from the network.
650    pub fn peer_handle(&self) -> &PeersHandle {
651        self.network.peers_handle()
652    }
653
654    /// Returns the local socket as configured for the network.
655    pub fn local_addr(&self) -> SocketAddr {
656        self.network.local_addr()
657    }
658
659    /// Creates a new [`NetworkEvent`] listener channel.
660    pub fn event_listener(&self) -> EventStream<NetworkEvent> {
661        self.network.event_listener()
662    }
663
664    /// Returns the [`TransactionsHandle`] of this peer.
665    pub const fn transactions(&self) -> Option<&TransactionsHandle> {
666        self.transactions.as_ref()
667    }
668
669    /// Returns the [`TestPool`] of this peer.
670    pub const fn pool(&self) -> Option<&Pool> {
671        self.pool.as_ref()
672    }
673
674    /// Returns the [`NetworkHandle`] of this peer.
675    pub const fn network(&self) -> &NetworkHandle<EthNetworkPrimitives> {
676        &self.network
677    }
678}
679
680// === impl PeerConfig ===
681
682impl<C> PeerConfig<C>
683where
684    C: BlockReader + HeaderProvider + Clone + 'static,
685{
686    /// Launches the network and returns the [Peer] that manages it
687    pub async fn launch(self) -> Result<Peer<C>, NetworkError> {
688        let Self { config, client, secret_key } = self;
689        let network = NetworkManager::new(config).await?;
690        let peer = Peer {
691            network,
692            client,
693            secret_key,
694            request_handler: None,
695            transactions_manager: None,
696            pool: None,
697        };
698        Ok(peer)
699    }
700
701    /// Initialize the network with a random secret key, allowing the devp2p and discovery to bind
702    /// to any available IP and port.
703    pub fn new(client: C) -> Self
704    where
705        C: ChainSpecProvider<ChainSpec: Hardforks>,
706    {
707        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
708        let config = Self::network_config_builder(secret_key).build(client.clone());
709        Self { config, client, secret_key }
710    }
711
712    /// Initialize the network with a given secret key, allowing devp2p and discovery to bind any
713    /// available IP and port.
714    pub fn with_secret_key(client: C, secret_key: SecretKey) -> Self
715    where
716        C: ChainSpecProvider<ChainSpec: Hardforks>,
717    {
718        let config = Self::network_config_builder(secret_key).build(client.clone());
719        Self { config, client, secret_key }
720    }
721
722    /// Initialize the network with a given capabilities.
723    pub fn with_protocols(client: C, protocols: impl IntoIterator<Item = Protocol>) -> Self
724    where
725        C: ChainSpecProvider<ChainSpec: Hardforks>,
726    {
727        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
728        let protocols: Vec<Protocol> = protocols.into_iter().collect();
729        // `NetworkConfigBuilder::build` re-derives snap advertisement from `snap_enabled`, which
730        // would otherwise silently strip a manually included `snap` capability.
731        let snap_enabled = protocols.iter().any(|p| p.cap.name == Protocol::snap_2().cap.name);
732
733        let builder = Self::network_config_builder(secret_key).with_snap(snap_enabled);
734        let hello_message =
735            HelloMessageWithProtocols::builder(builder.get_peer_id()).protocols(protocols).build();
736        let config = builder.hello_message(hello_message).build(client.clone());
737
738        Self { config, client, secret_key }
739    }
740
741    fn network_config_builder(secret_key: SecretKey) -> NetworkConfigBuilder {
742        NetworkConfigBuilder::new(secret_key, Runtime::test())
743            .listener_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
744            .discovery_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
745            .disable_dns_discovery()
746            .disable_discv4_discovery()
747            .peer_config(PeersConfig::test())
748    }
749}
750
751impl Default for PeerConfig {
752    fn default() -> Self {
753        Self::new(NoopProvider::default())
754    }
755}
756
757/// A helper type to await network events
758///
759/// This makes it easier to await established connections
760#[derive(Debug)]
761pub struct NetworkEventStream {
762    inner: EventStream<NetworkEvent>,
763}
764
765// === impl NetworkEventStream ===
766
767impl NetworkEventStream {
768    /// Create a new [`NetworkEventStream`] from the given network event receiver stream.
769    pub const fn new(inner: EventStream<NetworkEvent>) -> Self {
770        Self { inner }
771    }
772
773    /// Awaits the next event for a session to be closed
774    pub async fn next_session_closed(&mut self) -> Option<(PeerId, Option<DisconnectReason>)> {
775        while let Some(ev) = self.inner.next().await {
776            if let NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, reason }) = ev {
777                return Some((peer_id, reason))
778            }
779        }
780        None
781    }
782
783    /// Awaits the next event for an established session
784    pub async fn next_session_established(&mut self) -> Option<PeerId> {
785        while let Some(ev) = self.inner.next().await {
786            match ev {
787                NetworkEvent::ActivePeerSession { info, .. } |
788                NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
789                    return Some(info.peer_id)
790                }
791                _ => {}
792            }
793        }
794        None
795    }
796
797    /// Awaits the next `num` events for an established session
798    pub async fn take_session_established(&mut self, mut num: usize) -> Vec<PeerId> {
799        if num == 0 {
800            return Vec::new();
801        }
802        let mut peers = Vec::with_capacity(num);
803        while let Some(ev) = self.inner.next().await {
804            if let NetworkEvent::ActivePeerSession { info: SessionInfo { peer_id, .. }, .. } = ev {
805                peers.push(peer_id);
806                num -= 1;
807                if num == 0 {
808                    return peers;
809                }
810            }
811        }
812        peers
813    }
814
815    /// Ensures that the first two events are a [`NetworkEvent::Peer`] and
816    /// [`PeerEvent::PeerAdded`][`NetworkEvent::ActivePeerSession`], returning the [`PeerId`] of the
817    /// established session.
818    pub async fn peer_added_and_established(&mut self) -> Option<PeerId> {
819        let peer_id = match self.inner.next().await {
820            Some(NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id))) => peer_id,
821            _ => return None,
822        };
823
824        match self.inner.next().await {
825            Some(NetworkEvent::ActivePeerSession {
826                info: SessionInfo { peer_id: peer_id2, .. },
827                ..
828            }) => {
829                debug_assert_eq!(
830                    peer_id, peer_id2,
831                    "PeerAdded peer_id {peer_id} does not match SessionEstablished peer_id {peer_id2}"
832                );
833                Some(peer_id)
834            }
835            _ => None,
836        }
837    }
838
839    /// Awaits the next event for a peer added.
840    pub async fn peer_added(&mut self) -> Option<PeerId> {
841        let peer_id = match self.inner.next().await {
842            Some(NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id))) => peer_id,
843            _ => return None,
844        };
845
846        Some(peer_id)
847    }
848
849    /// Awaits the next event for a peer removed.
850    pub async fn peer_removed(&mut self) -> Option<PeerId> {
851        let peer_id = match self.inner.next().await {
852            Some(NetworkEvent::Peer(PeerEvent::PeerRemoved(peer_id))) => peer_id,
853            _ => return None,
854        };
855
856        Some(peer_id)
857    }
858}