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 request_handler = EthRequestHandler::new(self.client.clone(), rx);
386 self.request_handler = Some(request_handler);
387 }
388
389 pub fn install_transactions_manager(&mut self, pool: Pool) {
392 self.transactions_manager = Some(new_transactions_manager(
393 &mut self.network,
394 pool.clone(),
395 Default::default(),
396 Default::default(),
397 ));
398 self.pool = Some(pool);
399 }
400
401 pub fn map_transactions_manager<P>(
404 self,
405 pool: P,
406 config: TransactionsManagerConfig,
407 policy: TransactionPropagationKind,
408 ) -> Peer<C, P>
409 where
410 P: TransactionPool,
411 {
412 let Self { mut network, request_handler, client, .. } = self;
413 let transactions_manager =
414 new_transactions_manager(&mut network, pool.clone(), config, policy);
415 Peer {
416 network,
417 request_handler,
418 transactions_manager: Some(transactions_manager),
419 pool: Some(pool),
420 client,
421 }
422 }
423}
424
425impl<C, Pool> Future for Peer<C, Pool>
426where
427 C: TestnetProvider,
428 Pool: TestnetPool,
429{
430 type Output = ();
431
432 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
433 let this = self.project();
434
435 if let Some(request) = this.request_handler.as_pin_mut() {
436 let _ = request.poll(cx);
437 }
438
439 if let Some(tx_manager) = this.transactions_manager.as_pin_mut() {
440 let _ = tx_manager.poll(cx);
441 }
442
443 this.network.poll(cx)
444 }
445}
446
447#[derive(Debug)]
451pub struct PeerConfig<C = NoopProvider> {
452 client: C,
453 secret_key: SecretKey,
454 protocols: Option<Vec<Protocol>>,
455 peers_config: PeersConfig,
456}
457
458#[derive(Debug)]
460pub struct PeerHandle<Pool> {
461 network: NetworkHandle<EthNetworkPrimitives>,
462 transactions: Option<TransactionsHandle<EthNetworkPrimitives>>,
463 pool: Option<Pool>,
464}
465
466impl<Pool> PeerHandle<Pool> {
469 pub fn peer_id(&self) -> &PeerId {
471 self.network.peer_id()
472 }
473
474 pub fn peer_handle(&self) -> &PeersHandle {
476 self.network.peers_handle()
477 }
478
479 pub fn local_addr(&self) -> SocketAddr {
481 self.network.local_addr()
482 }
483
484 pub fn event_listener(&self) -> EventStream<NetworkEvent> {
486 self.network.event_listener()
487 }
488
489 pub const fn transactions(&self) -> Option<&TransactionsHandle> {
491 self.transactions.as_ref()
492 }
493
494 pub const fn pool(&self) -> Option<&Pool> {
496 self.pool.as_ref()
497 }
498
499 pub const fn network(&self) -> &NetworkHandle<EthNetworkPrimitives> {
501 &self.network
502 }
503
504 pub fn add_peer<P>(&self, other: &PeerHandle<P>) {
506 self.network.add_peer(*other.peer_id(), other.local_addr());
507 }
508
509 pub fn add_trusted_peer<P>(&self, other: &PeerHandle<P>) {
511 self.network.add_trusted_peer(*other.peer_id(), other.local_addr());
512 }
513
514 pub fn event_stream(&self) -> NetworkEventStream {
516 NetworkEventStream::new(self.event_listener())
517 }
518
519 pub async fn request<R>(
523 &self,
524 peer_id: PeerId,
525 request: impl FnOnce(oneshot::Sender<RequestResult<R>>) -> PeerRequest,
526 ) -> RequestResult<R> {
527 let (tx, rx) = oneshot::channel();
528 self.network.send_request(peer_id, request(tx));
529 rx.await.unwrap_or(Err(RequestError::ChannelClosed))
530 }
531}
532
533impl<C> PeerConfig<C> {
536 pub fn new(client: C) -> Self {
540 Self {
541 client,
542 secret_key: rng_secret_key(),
543 protocols: None,
544 peers_config: PeersConfig::test(),
545 }
546 }
547
548 pub const fn with_secret_key(mut self, secret_key: SecretKey) -> Self {
550 self.secret_key = secret_key;
551 self
552 }
553
554 pub fn with_protocols(mut self, protocols: impl IntoIterator<Item: Into<Protocol>>) -> Self {
556 self.protocols = Some(protocols.into_iter().map(Into::into).collect());
557 self
558 }
559
560 pub fn with_peers_config(mut self, peers_config: PeersConfig) -> Self {
562 self.peers_config = peers_config;
563 self
564 }
565
566 pub async fn launch(self) -> Result<Peer<C>, NetworkError>
568 where
569 C: BlockNumReader + ChainSpecProvider<ChainSpec: Hardforks> + Clone + 'static,
570 {
571 let Self { client, secret_key, protocols, peers_config } = self;
572 let mut builder = NetworkConfigBuilder::new(secret_key, Runtime::test())
573 .listener_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
574 .discovery_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
575 .disable_dns_discovery()
576 .disable_discv4_discovery()
577 .peer_config(peers_config);
578 if let Some(protocols) = protocols {
579 let snap_enabled = protocols.iter().any(|p| p.cap.name == Protocol::snap_2().cap.name);
582 let hello_message = HelloMessageWithProtocols::builder(builder.get_peer_id())
583 .protocols(protocols)
584 .build();
585 builder = builder.with_snap(snap_enabled).hello_message(hello_message);
586 }
587
588 let network = NetworkManager::new(builder.build(client.clone())).await?;
589 Ok(Peer { network, client, request_handler: None, transactions_manager: None, pool: None })
590 }
591}
592
593impl Default for PeerConfig {
594 fn default() -> Self {
595 Self::new(NoopProvider::default())
596 }
597}
598
599#[derive(Debug)]
603pub struct NetworkEventStream {
604 inner: EventStream<NetworkEvent>,
605}
606
607impl NetworkEventStream {
610 pub const fn new(inner: EventStream<NetworkEvent>) -> Self {
612 Self { inner }
613 }
614
615 pub async fn next_session_closed(&mut self) -> Option<(PeerId, Option<DisconnectReason>)> {
617 while let Some(ev) = self.inner.next().await {
618 if let NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, reason }) = ev {
619 return Some((peer_id, reason))
620 }
621 }
622 None
623 }
624
625 pub async fn next_session_established(&mut self) -> Option<PeerId> {
627 while let Some(ev) = self.inner.next().await {
628 match ev {
629 NetworkEvent::ActivePeerSession { info, .. } |
630 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
631 return Some(info.peer_id)
632 }
633 _ => {}
634 }
635 }
636 None
637 }
638
639 pub async fn take_session_established(&mut self, mut num: usize) -> Vec<PeerId> {
641 if num == 0 {
642 return Vec::new();
643 }
644 let mut peers = Vec::with_capacity(num);
645 while let Some(ev) = self.inner.next().await {
646 if let NetworkEvent::ActivePeerSession { info: SessionInfo { peer_id, .. }, .. } = ev {
647 peers.push(peer_id);
648 num -= 1;
649 if num == 0 {
650 return peers;
651 }
652 }
653 }
654 peers
655 }
656
657 pub async fn peer_added(&mut self) -> Option<PeerId> {
659 match self.inner.next().await {
660 Some(NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id))) => Some(peer_id),
661 _ => None,
662 }
663 }
664
665 pub async fn peer_removed(&mut self) -> Option<PeerId> {
667 match self.inner.next().await {
668 Some(NetworkEvent::Peer(PeerEvent::PeerRemoved(peer_id))) => Some(peer_id),
669 _ => None,
670 }
671 }
672}
673
674pub trait TestnetProvider:
676 BlockReader<
677 Block = reth_ethereum_primitives::Block,
678 Receipt = reth_ethereum_primitives::Receipt,
679 Header = alloy_consensus::Header,
680 > + BalProvider
681 + StateProviderFactory
682 + StateRangeProviderFactory
683 + Clone
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 + Clone
698 + Unpin
699 + 'static
700{
701}
702
703pub trait TestnetPool:
706 TransactionPool<
707 Transaction: PoolTransaction<
708 Consensus = TransactionSigned,
709 Pooled = PooledTransactionVariant,
710 >,
711 > + Unpin
712 + 'static
713{
714}
715
716impl<T> TestnetPool for T where
717 T: TransactionPool<
718 Transaction: PoolTransaction<
719 Consensus = TransactionSigned,
720 Pooled = PooledTransactionVariant,
721 >,
722 > + Unpin
723 + 'static
724{
725}
726
727fn new_transactions_manager<Pool: TransactionPool>(
729 network: &mut NetworkManager<EthNetworkPrimitives>,
730 pool: Pool,
731 config: TransactionsManagerConfig,
732 policy: TransactionPropagationKind,
733) -> TransactionsManager<Pool, EthNetworkPrimitives> {
734 let (tx, rx) =
735 memory_bounded_channel(DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES, "test_tx_channel");
736 network.set_transactions(tx);
737 let policies = NetworkPolicies::new(policy, StrictEthAnnouncementFilter::default());
738 TransactionsManager::with_policy(network.handle().clone(), pool, rx, config, policies)
739}