1use crate::{
4 builder::ETH_REQUEST_CHANNEL_CAPACITY,
5 config::rng_secret_key,
6 error::NetworkError,
7 eth_requests::EthRequestHandler,
8 protocol::IntoRlpxSubProtocol,
9 transactions::{
10 config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
11 constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
12 policy::NetworkPolicies,
13 TransactionsHandle, TransactionsManager, TransactionsManagerConfig,
14 },
15 NetworkConfigBuilder, NetworkHandle, NetworkManager, PeersConfig,
16};
17use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M;
18use futures::{FutureExt, StreamExt};
19use pin_project::pin_project;
20use reth_chainspec::{ChainSpecProvider, EthereumHardforks, Hardforks};
21use reth_eth_wire::{
22 protocol::Protocol, DisconnectReason, EthNetworkPrimitives, HelloMessageWithProtocols,
23};
24use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
25use reth_evm_ethereum::EthEvmConfig;
26use reth_metrics::common::mpsc::memory_bounded_channel;
27use reth_network_api::{
28 events::{PeerEvent, SessionInfo},
29 test_utils::{PeersHandle, PeersHandleProvider},
30 NetworkEvent, NetworkEventListenerProvider, NetworkInfo, PeerRequest, Peers,
31};
32use reth_network_p2p::error::{RequestError, RequestResult};
33use reth_network_peers::PeerId;
34use reth_storage_api::{
35 noop::NoopProvider, BalProvider, BlockNumReader, BlockReader, BlockReaderIdExt, HeaderProvider,
36 StateProviderFactory, StateRangeProviderFactory,
37};
38use reth_tasks::Runtime;
39use reth_tokio_util::EventStream;
40use reth_transaction_pool::{
41 blobstore::InMemoryBlobStore, test_utils::TestPool, EthTransactionPool, PoolTransaction,
42 TransactionPool, TransactionValidationTaskExecutor,
43};
44use secp256k1::SecretKey;
45use std::{
46 fmt,
47 future::Future,
48 net::{Ipv4Addr, SocketAddr, SocketAddrV4},
49 pin::Pin,
50 task::{Context, Poll},
51};
52use tokio::{
53 sync::{mpsc::channel, oneshot},
54 task::JoinHandle,
55};
56
57pub struct Testnet<C, Pool> {
59 peers: Vec<Peer<C, Pool>>,
61}
62
63impl<C> Testnet<C, TestPool>
66where
67 C: BlockNumReader + ChainSpecProvider<ChainSpec: Hardforks> + Clone + 'static,
68{
69 pub async fn from_configs(configs: impl IntoIterator<Item = PeerConfig<C>>) -> Self {
77 let peers = futures::future::try_join_all(configs.into_iter().map(PeerConfig::launch))
78 .await
79 .expect("failed to launch testnet peers");
80 Self { peers }
81 }
82
83 pub async fn create_with(num_peers: usize, provider: C) -> Self {
89 Self::from_configs((0..num_peers).map(|_| PeerConfig::new(provider.clone()))).await
90 }
91
92 pub async fn add_peer_with_config(
94 &mut self,
95 config: PeerConfig<C>,
96 ) -> Result<(), NetworkError> {
97 self.peers.push(config.launch().await?);
98 Ok(())
99 }
100}
101
102impl Testnet<NoopProvider, TestPool> {
103 pub async fn create(num_peers: usize) -> Self {
109 Self::create_with(num_peers, NoopProvider::default()).await
110 }
111}
112
113impl<C, Pool> Testnet<C, Pool>
114where
115 C: BlockReader + Clone + 'static,
116 Pool: TransactionPool,
117{
118 pub fn peers_mut(&mut self) -> &mut [Peer<C, Pool>] {
120 &mut self.peers
121 }
122
123 pub fn peers(&self) -> &[Peer<C, Pool>] {
125 &self.peers
126 }
127
128 pub fn map_pool<F, P>(self, f: F) -> Testnet<C, P>
130 where
131 F: Fn(Peer<C, Pool>) -> Peer<C, P>,
132 P: TransactionPool,
133 {
134 Testnet { peers: self.peers.into_iter().map(f).collect() }
135 }
136
137 pub fn for_each_mut<F>(&mut self, f: F)
139 where
140 F: FnMut(&mut Peer<C, Pool>),
141 {
142 self.peers.iter_mut().for_each(f)
143 }
144
145 pub fn with_request_handlers(mut self) -> Self
147 where
148 C: BalProvider,
149 {
150 self.for_each_mut(Peer::install_request_handler);
151 self
152 }
153}
154
155impl<C, Pool> Testnet<C, Pool>
156where
157 C: ChainSpecProvider<ChainSpec: EthereumHardforks>
158 + StateProviderFactory
159 + BlockReaderIdExt
160 + HeaderProvider<Header = alloy_consensus::Header>
161 + Clone
162 + 'static,
163 Pool: TransactionPool,
164{
165 pub fn with_eth_pool(
167 self,
168 ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
169 self.with_eth_pool_config(Default::default())
170 }
171
172 pub fn with_eth_pool_config(
174 self,
175 tx_manager_config: TransactionsManagerConfig,
176 ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
177 self.with_eth_pool_config_and_policy(tx_manager_config, Default::default())
178 }
179
180 pub fn with_eth_pool_config_and_policy(
182 self,
183 tx_manager_config: TransactionsManagerConfig,
184 policy: TransactionPropagationKind,
185 ) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
186 self.map_pool(|peer| {
187 let blob_store = InMemoryBlobStore::default();
188 let validator = TransactionValidationTaskExecutor::eth_builder(
191 peer.client.clone(),
192 EthEvmConfig::mainnet(),
193 )
194 .set_block_gas_limit(ETHEREUM_BLOCK_GAS_LIMIT_30M)
195 .build_with_tasks(Runtime::test(), blob_store.clone());
196 let pool = EthTransactionPool::eth_pool(validator, blob_store, Default::default());
197 peer.map_transactions_manager(pool, tx_manager_config.clone(), policy)
198 })
199 }
200}
201
202impl<C, Pool> Testnet<C, Pool>
203where
204 C: TestnetProvider + Clone,
205 Pool: TestnetPool,
206{
207 pub fn spawn(self) -> TestnetHandle<C, Pool> {
209 let (terminate, rx) = oneshot::channel::<oneshot::Sender<Self>>();
210 let peers = self.peers.iter().map(Peer::peer_handle).collect();
211 let mut net = self;
212 let handle = tokio::task::spawn(async move {
213 let tx = tokio::select! {
214 _ = &mut net => None,
215 tx = rx => tx.ok(),
216 };
217 if let Some(tx) = tx {
218 let _ = tx.send(net);
219 }
220 });
221
222 TestnetHandle { _handle: handle, peers, terminate }
223 }
224}
225
226impl<C, Pool> fmt::Debug for Testnet<C, Pool> {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.debug_struct("Testnet").finish_non_exhaustive()
229 }
230}
231
232impl<C, Pool> Future for Testnet<C, Pool>
233where
234 C: TestnetProvider,
235 Pool: TestnetPool,
236{
237 type Output = ();
238
239 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
240 let this = self.get_mut();
241 for peer in &mut this.peers {
242 let _ = peer.poll_unpin(cx);
243 }
244 Poll::Pending
245 }
246}
247
248#[derive(Debug)]
250pub struct TestnetHandle<C, Pool> {
251 _handle: JoinHandle<()>,
252 peers: Vec<PeerHandle<Pool>>,
253 terminate: oneshot::Sender<oneshot::Sender<Testnet<C, Pool>>>,
254}
255
256impl<C, Pool> TestnetHandle<C, Pool> {
259 pub async fn terminate(self) -> Testnet<C, Pool> {
261 let (tx, rx) = oneshot::channel();
262 self.terminate.send(tx).unwrap();
263 rx.await.unwrap()
264 }
265
266 pub fn peers(&self) -> &[PeerHandle<Pool>] {
268 &self.peers
269 }
270
271 pub fn peers_array<const N: usize>(&self) -> &[PeerHandle<Pool>; N] {
278 self.peers.as_slice().try_into().unwrap_or_else(|_| {
279 panic!("expected {N} peers, but the testnet has {}", self.peers.len())
280 })
281 }
282
283 pub async fn connect_peers(&self) {
289 if self.peers.len() < 2 {
290 return
291 }
292
293 let streams = self.peers.iter().map(PeerHandle::event_stream).collect::<Vec<_>>();
295
296 for (idx, handle) in self.peers.iter().enumerate().take(self.peers.len() - 1) {
298 for neighbour in &self.peers[idx + 1..] {
299 handle.add_peer(neighbour);
300 }
301 }
302
303 let num_sessions_per_peer = self.peers.len() - 1;
305 let fut = streams.into_iter().map(|mut stream| async move {
306 stream.take_session_established(num_sessions_per_peer).await
307 });
308
309 futures::future::join_all(fut).await;
310 }
311}
312
313#[pin_project]
315#[derive(Debug)]
316pub struct Peer<C, Pool = TestPool> {
317 #[pin]
318 network: NetworkManager<EthNetworkPrimitives>,
319 #[pin]
320 request_handler: Option<EthRequestHandler<C, EthNetworkPrimitives>>,
321 #[pin]
322 transactions_manager: Option<TransactionsManager<Pool, EthNetworkPrimitives>>,
323 pool: Option<Pool>,
324 client: C,
325}
326
327impl<C, Pool> Peer<C, Pool>
330where
331 C: BlockReader + Clone + 'static,
332 Pool: TransactionPool,
333{
334 pub fn num_peers(&self) -> usize {
336 self.network.num_connected_peers()
337 }
338
339 pub fn add_rlpx_sub_protocol(&mut self, protocol: impl IntoRlpxSubProtocol) {
341 self.network.add_rlpx_sub_protocol(protocol);
342 }
343
344 pub fn peer_handle(&self) -> PeerHandle<Pool> {
346 PeerHandle {
347 network: self.network.handle().clone(),
348 pool: self.pool.clone(),
349 transactions: self.transactions_manager.as_ref().map(|mgr| mgr.handle()),
350 }
351 }
352
353 pub const fn local_addr(&self) -> SocketAddr {
355 self.network.local_addr()
356 }
357
358 pub fn peer_id(&self) -> PeerId {
360 *self.network.peer_id()
361 }
362
363 pub const fn network_mut(&mut self) -> &mut NetworkManager<EthNetworkPrimitives> {
365 &mut self.network
366 }
367
368 pub fn handle(&self) -> NetworkHandle<EthNetworkPrimitives> {
370 self.network.handle().clone()
371 }
372
373 pub const fn pool(&self) -> Option<&Pool> {
375 self.pool.as_ref()
376 }
377
378 pub fn install_request_handler(&mut self)
380 where
381 C: BalProvider,
382 {
383 let (tx, rx) = channel(ETH_REQUEST_CHANNEL_CAPACITY);
384 self.network.set_eth_request_handler(tx);
385 let peers = self.network.peers_handle();
386 let request_handler = EthRequestHandler::new(self.client.clone(), peers, rx);
387 self.request_handler = Some(request_handler);
388 }
389
390 pub fn install_transactions_manager(&mut self, pool: Pool) {
393 self.transactions_manager = Some(new_transactions_manager(
394 &mut self.network,
395 pool.clone(),
396 Default::default(),
397 Default::default(),
398 ));
399 self.pool = Some(pool);
400 }
401
402 pub fn map_transactions_manager<P>(
405 self,
406 pool: P,
407 config: TransactionsManagerConfig,
408 policy: TransactionPropagationKind,
409 ) -> Peer<C, P>
410 where
411 P: TransactionPool,
412 {
413 let Self { mut network, request_handler, client, .. } = self;
414 let transactions_manager =
415 new_transactions_manager(&mut network, pool.clone(), config, policy);
416 Peer {
417 network,
418 request_handler,
419 transactions_manager: Some(transactions_manager),
420 pool: Some(pool),
421 client,
422 }
423 }
424}
425
426impl<C, Pool> Future for Peer<C, Pool>
427where
428 C: TestnetProvider,
429 Pool: TestnetPool,
430{
431 type Output = ();
432
433 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
434 let this = self.project();
435
436 if let Some(request) = this.request_handler.as_pin_mut() {
437 let _ = request.poll(cx);
438 }
439
440 if let Some(tx_manager) = this.transactions_manager.as_pin_mut() {
441 let _ = tx_manager.poll(cx);
442 }
443
444 this.network.poll(cx)
445 }
446}
447
448#[derive(Debug)]
452pub struct PeerConfig<C = NoopProvider> {
453 client: C,
454 secret_key: SecretKey,
455 protocols: Option<Vec<Protocol>>,
456 peers_config: PeersConfig,
457}
458
459#[derive(Debug)]
461pub struct PeerHandle<Pool> {
462 network: NetworkHandle<EthNetworkPrimitives>,
463 transactions: Option<TransactionsHandle<EthNetworkPrimitives>>,
464 pool: Option<Pool>,
465}
466
467impl<Pool> PeerHandle<Pool> {
470 pub fn peer_id(&self) -> &PeerId {
472 self.network.peer_id()
473 }
474
475 pub fn peer_handle(&self) -> &PeersHandle {
477 self.network.peers_handle()
478 }
479
480 pub fn local_addr(&self) -> SocketAddr {
482 self.network.local_addr()
483 }
484
485 pub fn event_listener(&self) -> EventStream<NetworkEvent> {
487 self.network.event_listener()
488 }
489
490 pub const fn transactions(&self) -> Option<&TransactionsHandle> {
492 self.transactions.as_ref()
493 }
494
495 pub const fn pool(&self) -> Option<&Pool> {
497 self.pool.as_ref()
498 }
499
500 pub const fn network(&self) -> &NetworkHandle<EthNetworkPrimitives> {
502 &self.network
503 }
504
505 pub fn add_peer<P>(&self, other: &PeerHandle<P>) {
507 self.network.add_peer(*other.peer_id(), other.local_addr());
508 }
509
510 pub fn add_trusted_peer<P>(&self, other: &PeerHandle<P>) {
512 self.network.add_trusted_peer(*other.peer_id(), other.local_addr());
513 }
514
515 pub fn event_stream(&self) -> NetworkEventStream {
517 NetworkEventStream::new(self.event_listener())
518 }
519
520 pub async fn request<R>(
524 &self,
525 peer_id: PeerId,
526 request: impl FnOnce(oneshot::Sender<RequestResult<R>>) -> PeerRequest,
527 ) -> RequestResult<R> {
528 let (tx, rx) = oneshot::channel();
529 self.network.send_request(peer_id, request(tx));
530 rx.await.unwrap_or(Err(RequestError::ChannelClosed))
531 }
532}
533
534impl<C> PeerConfig<C> {
537 pub fn new(client: C) -> Self {
541 Self {
542 client,
543 secret_key: rng_secret_key(),
544 protocols: None,
545 peers_config: PeersConfig::test(),
546 }
547 }
548
549 pub const fn with_secret_key(mut self, secret_key: SecretKey) -> Self {
551 self.secret_key = secret_key;
552 self
553 }
554
555 pub fn with_protocols(mut self, protocols: impl IntoIterator<Item: Into<Protocol>>) -> Self {
557 self.protocols = Some(protocols.into_iter().map(Into::into).collect());
558 self
559 }
560
561 pub fn with_peers_config(mut self, peers_config: PeersConfig) -> Self {
563 self.peers_config = peers_config;
564 self
565 }
566
567 pub async fn launch(self) -> Result<Peer<C>, NetworkError>
569 where
570 C: BlockNumReader + ChainSpecProvider<ChainSpec: Hardforks> + Clone + 'static,
571 {
572 let Self { client, secret_key, protocols, peers_config } = self;
573 let mut builder = NetworkConfigBuilder::new(secret_key, Runtime::test())
574 .listener_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
575 .discovery_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
576 .disable_dns_discovery()
577 .disable_discv4_discovery()
578 .peer_config(peers_config);
579 if let Some(protocols) = protocols {
580 let snap_enabled = protocols.iter().any(|p| p.cap.name == Protocol::snap_2().cap.name);
583 let hello_message = HelloMessageWithProtocols::builder(builder.get_peer_id())
584 .protocols(protocols)
585 .build();
586 builder = builder.with_snap(snap_enabled).hello_message(hello_message);
587 }
588
589 let network = NetworkManager::new(builder.build(client.clone())).await?;
590 Ok(Peer { network, client, request_handler: None, transactions_manager: None, pool: None })
591 }
592}
593
594impl Default for PeerConfig {
595 fn default() -> Self {
596 Self::new(NoopProvider::default())
597 }
598}
599
600#[derive(Debug)]
604pub struct NetworkEventStream {
605 inner: EventStream<NetworkEvent>,
606}
607
608impl NetworkEventStream {
611 pub const fn new(inner: EventStream<NetworkEvent>) -> Self {
613 Self { inner }
614 }
615
616 pub async fn next_session_closed(&mut self) -> Option<(PeerId, Option<DisconnectReason>)> {
618 while let Some(ev) = self.inner.next().await {
619 if let NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, reason }) = ev {
620 return Some((peer_id, reason))
621 }
622 }
623 None
624 }
625
626 pub async fn next_session_established(&mut self) -> Option<PeerId> {
628 while let Some(ev) = self.inner.next().await {
629 match ev {
630 NetworkEvent::ActivePeerSession { info, .. } |
631 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
632 return Some(info.peer_id)
633 }
634 _ => {}
635 }
636 }
637 None
638 }
639
640 pub async fn take_session_established(&mut self, mut num: usize) -> Vec<PeerId> {
642 if num == 0 {
643 return Vec::new();
644 }
645 let mut peers = Vec::with_capacity(num);
646 while let Some(ev) = self.inner.next().await {
647 if let NetworkEvent::ActivePeerSession { info: SessionInfo { peer_id, .. }, .. } = ev {
648 peers.push(peer_id);
649 num -= 1;
650 if num == 0 {
651 return peers;
652 }
653 }
654 }
655 peers
656 }
657
658 pub async fn peer_added(&mut self) -> Option<PeerId> {
660 match self.inner.next().await {
661 Some(NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id))) => Some(peer_id),
662 _ => None,
663 }
664 }
665
666 pub async fn peer_removed(&mut self) -> Option<PeerId> {
668 match self.inner.next().await {
669 Some(NetworkEvent::Peer(PeerEvent::PeerRemoved(peer_id))) => Some(peer_id),
670 _ => None,
671 }
672 }
673}
674
675pub trait TestnetProvider:
677 BlockReader<
678 Block = reth_ethereum_primitives::Block,
679 Receipt = reth_ethereum_primitives::Receipt,
680 Header = alloy_consensus::Header,
681 > + BalProvider
682 + StateProviderFactory
683 + StateRangeProviderFactory
684 + Unpin
685 + 'static
686{
687}
688
689impl<T> TestnetProvider for T where
690 T: BlockReader<
691 Block = reth_ethereum_primitives::Block,
692 Receipt = reth_ethereum_primitives::Receipt,
693 Header = alloy_consensus::Header,
694 > + BalProvider
695 + StateProviderFactory
696 + StateRangeProviderFactory
697 + Unpin
698 + 'static
699{
700}
701
702pub trait TestnetPool:
705 TransactionPool<
706 Transaction: PoolTransaction<
707 Consensus = TransactionSigned,
708 Pooled = PooledTransactionVariant,
709 >,
710 > + Unpin
711 + 'static
712{
713}
714
715impl<T> TestnetPool for T where
716 T: TransactionPool<
717 Transaction: PoolTransaction<
718 Consensus = TransactionSigned,
719 Pooled = PooledTransactionVariant,
720 >,
721 > + Unpin
722 + 'static
723{
724}
725
726fn new_transactions_manager<Pool: TransactionPool>(
728 network: &mut NetworkManager<EthNetworkPrimitives>,
729 pool: Pool,
730 config: TransactionsManagerConfig,
731 policy: TransactionPropagationKind,
732) -> TransactionsManager<Pool, EthNetworkPrimitives> {
733 let (tx, rx) =
734 memory_bounded_channel(DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES, "test_tx_channel");
735 network.set_transactions(tx);
736 let policies = NetworkPolicies::new(policy, StrictEthAnnouncementFilter::default());
737 TransactionsManager::with_policy(network.handle().clone(), pool, rx, config, policies)
738}