1use alloy_consensus::{constants::EIP4844_TX_TYPE_ID, transaction::TxHashRef};
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use smallvec::SmallVec;
6
7pub mod config;
9pub mod constants;
11pub mod fetcher;
13pub mod policy;
15
16pub use self::constants::{
17 tx_fetcher::DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
18 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
19};
20use config::AnnouncementAcceptance;
21pub use config::{
22 AnnouncementFilteringPolicy, TransactionFetcherConfig, TransactionIngressPolicy,
23 TransactionPropagationMode, TransactionPropagationPolicy, TransactionsManagerConfig,
24};
25use policy::NetworkPolicies;
26
27pub(crate) use fetcher::{FetchEvent, TransactionFetcher};
28
29use self::constants::{tx_manager::*, DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE};
30use crate::{
31 budget::{
32 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
33 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS, DEFAULT_BUDGET_TRY_DRAIN_STREAM,
34 },
35 cache::LruCache,
36 duration_metered_exec, metered_poll_nested_stream_with_budget,
37 metrics::{AnnouncedTxTypesMetrics, TransactionsManagerMetrics},
38 transactions::config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
39 NetworkHandle, TxTypesCounter,
40};
41use alloy_eips::eip2718::Typed2718;
42use alloy_primitives::{
43 bytes::BufMut,
44 map::{hash_map::Entry, B256Map, B256Set, FbBuildHasher, HashMap, HashSet},
45 TxHash, B256,
46};
47use alloy_rlp::Encodable;
48use constants::SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE;
49use futures::{stream::FuturesUnordered, Future, StreamExt};
50use reth_eth_wire::{
51 BroadcastPoolTransactions, Cells, DedupPayload, EthNetworkPrimitives, EthVersion, GetCells,
52 GetPooledTransactions, HandleMempoolData, HandleVersionedMempoolData, LazyEncoded,
53 LazyEncodedTransaction, NetworkPrimitives, NewPooledTransactionHashes,
54 NewPooledTransactionHashes66, NewPooledTransactionHashes68, NewPooledTransactionHashes72,
55 PooledTransactions, RequestTxHashes, Transactions, ValidAnnouncementData,
56};
57use reth_ethereum_primitives::TxType;
58use reth_evm::SenderRecoveryCache;
59use reth_metrics::common::mpsc::MemoryBoundedReceiver;
60use reth_network_api::{
61 events::{PeerEvent, SessionInfo},
62 NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
63};
64use reth_network_p2p::{
65 error::{RequestError, RequestResult},
66 sync::SyncStateProvider,
67};
68use reth_network_peers::PeerId;
69use reth_network_types::ReputationChangeKind;
70use reth_primitives_traits::{InMemorySize, SignedTransaction};
71use reth_tokio_util::EventStream;
72use reth_transaction_pool::{
73 error::{PoolError, PoolResult},
74 AddedTransactionOutcome, GetPooledTransactionLimit, PoolTransaction, PropagateKind,
75 PropagatedTransactions, TransactionPool, ValidPoolTransaction,
76};
77use std::{
78 pin::Pin,
79 sync::{
80 atomic::{AtomicUsize, Ordering},
81 Arc,
82 },
83 task::{Context, Poll},
84 time::{Duration, Instant},
85};
86use tokio::sync::{mpsc, oneshot, oneshot::error::RecvError};
87use tokio_stream::wrappers::UnboundedReceiverStream;
88use tracing::{debug, trace};
89
90pub type PoolImportFuture =
94 Pin<Box<dyn Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send + 'static>>;
95
96#[derive(Debug, Clone)]
104pub struct TransactionsHandle<N: NetworkPrimitives = EthNetworkPrimitives> {
105 manager_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
107}
108
109impl<N: NetworkPrimitives> TransactionsHandle<N> {
110 fn send(&self, cmd: TransactionsCommand<N>) {
111 let _ = self.manager_tx.send(cmd);
112 }
113
114 async fn peer_handle(
116 &self,
117 peer_id: PeerId,
118 ) -> Result<Option<PeerRequestSender<PeerRequest<N>>>, RecvError> {
119 let (tx, rx) = oneshot::channel();
120 self.send(TransactionsCommand::GetPeerSender { peer_id, peer_request_sender: tx });
121 rx.await
122 }
123
124 pub fn propagate(&self, hash: TxHash) {
126 self.send(TransactionsCommand::PropagateHash(hash))
127 }
128
129 pub fn propagate_hash_to(&self, hash: TxHash, peer: PeerId) {
133 self.propagate_hashes_to(Some(hash), peer)
134 }
135
136 pub fn propagate_hashes_to(&self, hash: impl IntoIterator<Item = TxHash>, peer: PeerId) {
140 let hashes = hash.into_iter().collect::<Vec<_>>();
141 if hashes.is_empty() {
142 return
143 }
144 self.send(TransactionsCommand::PropagateHashesTo(hashes, peer))
145 }
146
147 pub async fn get_active_peers(&self) -> Result<HashSet<PeerId>, RecvError> {
149 let (tx, rx) = oneshot::channel();
150 self.send(TransactionsCommand::GetActivePeers(tx));
151 rx.await
152 }
153
154 pub fn propagate_transactions_to(&self, transactions: Vec<TxHash>, peer: PeerId) {
158 if transactions.is_empty() {
159 return
160 }
161 self.send(TransactionsCommand::PropagateTransactionsTo(transactions, peer))
162 }
163
164 pub fn propagate_transactions(&self, transactions: Vec<TxHash>) {
169 if transactions.is_empty() {
170 return
171 }
172 self.send(TransactionsCommand::PropagateTransactions(transactions))
173 }
174
175 pub fn broadcast_transactions(
180 &self,
181 transactions: impl IntoIterator<Item = N::BroadcastedTransaction>,
182 ) {
183 let transactions =
184 transactions.into_iter().map(PropagateTransaction::new).collect::<Vec<_>>();
185 if transactions.is_empty() {
186 return
187 }
188 self.send(TransactionsCommand::BroadcastTransactions(transactions))
189 }
190
191 pub async fn get_transaction_hashes(
193 &self,
194 peers: Vec<PeerId>,
195 ) -> Result<HashMap<PeerId, B256Set>, RecvError> {
196 if peers.is_empty() {
197 return Ok(Default::default())
198 }
199 let (tx, rx) = oneshot::channel();
200 self.send(TransactionsCommand::GetTransactionHashes { peers, tx });
201 rx.await
202 }
203
204 pub async fn get_peer_transaction_hashes(&self, peer: PeerId) -> Result<B256Set, RecvError> {
206 let res = self.get_transaction_hashes(vec![peer]).await?;
207 Ok(res.into_values().next().unwrap_or_default())
208 }
209
210 pub async fn get_pooled_transactions_from(
216 &self,
217 peer_id: PeerId,
218 hashes: Vec<B256>,
219 ) -> Result<Option<Vec<N::PooledTransaction>>, RequestError> {
220 let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
221
222 let (tx, rx) = oneshot::channel();
223 let request = PeerRequest::GetPooledTransactions { request: hashes.into(), response: tx };
224 peer.try_send(request).ok();
225
226 rx.await?.map(|res| Some(res.0))
227 }
228
229 pub async fn get_cells_from(
237 &self,
238 peer_id: PeerId,
239 hashes: Vec<B256>,
240 cell_mask: alloy_primitives::B128,
241 ) -> Result<Option<Cells>, RequestError> {
242 if hashes.is_empty() {
243 return Ok(Some(Cells { cell_mask, ..Default::default() }))
244 }
245
246 let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
247
248 let (tx, rx) = oneshot::channel();
249 let request =
250 PeerRequest::GetCells { request: GetCells { hashes, cell_mask }, response: tx };
251 peer.try_send(request).map_err(|_| RequestError::ChannelClosed)?;
252
253 rx.await?.map(Some)
254 }
255}
256
257#[derive(Debug)]
312#[must_use = "Manager does nothing unless polled."]
313pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
314 pool: Pool,
316 sender_recovery_cache: Option<SenderRecoveryCache>,
318 network: NetworkHandle<N>,
320 network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
324 transaction_fetcher: TransactionFetcher<N>,
326 transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
331 pool_imports: FuturesUnordered<PoolImportFuture>,
343 pending_pool_imports_info: PendingPoolImportsInfo,
345 bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
347 peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
349 command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
353 command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
358 pending_transactions: mpsc::Receiver<TxHash>,
367 transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
369 config: TransactionsManagerConfig,
371 policies: NetworkPolicies<N>,
373 metrics: TransactionsManagerMetrics,
375 announced_tx_types_metrics: AnnouncedTxTypesMetrics,
377}
378
379impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
380 pub fn new(
384 network: NetworkHandle<N>,
385 pool: Pool,
386 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
387 transactions_manager_config: TransactionsManagerConfig,
388 ) -> Self {
389 Self::with_policy(
390 network,
391 pool,
392 from_network,
393 transactions_manager_config,
394 NetworkPolicies::new(
395 TransactionPropagationKind::default(),
396 StrictEthAnnouncementFilter::default(),
397 ),
398 )
399 }
400}
401
402impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
403 pub fn with_policy(
407 network: NetworkHandle<N>,
408 pool: Pool,
409 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
410 transactions_manager_config: TransactionsManagerConfig,
411 policies: NetworkPolicies<N>,
412 ) -> Self {
413 let network_events = network.event_listener();
414
415 let (command_tx, command_rx) = mpsc::unbounded_channel();
416
417 let transaction_fetcher = TransactionFetcher::with_transaction_fetcher_config(
418 &transactions_manager_config.transaction_fetcher_config,
419 );
420
421 let pending = pool.pending_transactions_listener();
424 let pending_pool_imports_info =
425 PendingPoolImportsInfo::new(transactions_manager_config.max_pending_pool_imports);
426 let metrics = TransactionsManagerMetrics::default();
427 metrics
428 .capacity_pending_pool_imports
429 .increment(pending_pool_imports_info.max_pending_pool_imports as u64);
430
431 Self {
432 pool,
433 sender_recovery_cache: None,
434 network,
435 network_events,
436 transaction_fetcher,
437 transactions_by_peers: Default::default(),
438 pool_imports: Default::default(),
439 pending_pool_imports_info,
440 bad_imports: LruCache::with_hasher(DEFAULT_MAX_COUNT_BAD_IMPORTS, Default::default()),
441 peers: Default::default(),
442 command_tx,
443 command_rx: UnboundedReceiverStream::new(command_rx),
444 pending_transactions: pending,
445 transaction_events: from_network,
446 config: transactions_manager_config,
447 policies,
448 metrics,
449 announced_tx_types_metrics: AnnouncedTxTypesMetrics::default(),
450 }
451 }
452
453 pub fn handle(&self) -> TransactionsHandle<N> {
455 TransactionsHandle { manager_tx: self.command_tx.clone() }
456 }
457
458 pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
460 self.sender_recovery_cache = Some(cache);
461 self
462 }
463
464 fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
467 self.has_capacity_for_pending_pool_imports() &&
468 self.transaction_fetcher.has_capacity_for_fetching_pending_hashes()
469 }
470
471 fn has_capacity_for_pending_pool_imports(&self) -> bool {
473 self.remaining_pool_import_capacity() > 0
474 }
475
476 fn remaining_pool_import_capacity(&self) -> usize {
478 self.pending_pool_imports_info.max_pending_pool_imports.saturating_sub(
479 self.pending_pool_imports_info.pending_pool_imports.load(Ordering::Relaxed),
480 )
481 }
482
483 fn report_peer_bad_transactions(&self, peer_id: PeerId) {
484 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
485 self.metrics.reported_bad_transactions.increment(1);
486 }
487
488 fn report_peer(&self, peer_id: PeerId, kind: ReputationChangeKind) {
489 trace!(target: "net::tx", ?peer_id, ?kind, "reporting reputation change");
490 self.network.reputation_change(peer_id, kind);
491 }
492
493 fn report_already_seen(&self, peer_id: PeerId) {
494 trace!(target: "net::tx", ?peer_id, "Penalizing peer for already seen transaction");
495 self.network.reputation_change(peer_id, ReputationChangeKind::AlreadySeenTransaction);
496 }
497
498 fn on_peer_session_closed(&mut self, peer_id: &PeerId) {
500 if let Some(mut peer) = self.peers.remove(peer_id) {
501 self.policies.propagation_policy_mut().on_session_closed(&mut peer);
502 }
503 self.transaction_fetcher.remove_peer(peer_id);
504 }
505
506 fn on_good_import(&mut self, hash: TxHash) {
508 self.transactions_by_peers.remove(&hash);
509 }
510
511 fn on_bad_import(&mut self, err: PoolError) {
541 let peers = self.transactions_by_peers.remove(&err.hash);
542
543 if err.is_bad_blob_sidecar() {
544 if let Some(peers) = peers {
552 for peer_id in peers {
553 self.report_peer_bad_transactions(peer_id);
554 }
555 }
556 return
557 }
558
559 if !err.is_bad_transaction() || self.network.is_syncing() {
561 return
562 }
563 if let Some(peers) = peers {
566 for peer_id in peers {
567 self.report_peer_bad_transactions(peer_id);
568 }
569 }
570 self.metrics.bad_imports.increment(1);
571 self.bad_imports.insert(err.hash);
572 }
573
574 fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
578 let info = &self.pending_pool_imports_info;
580 let max_pending_pool_imports = info.max_pending_pool_imports;
581 let has_capacity_wrt_pending_pool_imports =
582 |divisor| info.has_capacity(max_pending_pool_imports / divisor);
583
584 self.transaction_fetcher
585 .on_fetch_pending_hashes(&self.peers, has_capacity_wrt_pending_pool_imports)
586 }
587
588 fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
589 let kind = match req_err {
590 RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
591 RequestError::Timeout => ReputationChangeKind::Timeout,
592 RequestError::ChannelClosed | RequestError::ConnectionDropped => {
593 return
595 }
596 RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
597 };
598 self.report_peer(peer_id, kind);
599 }
600
601 #[inline]
602 fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
603 let metrics = &self.metrics;
604
605 let TxManagerPollDurations {
606 acc_network_events,
607 acc_pending_imports,
608 acc_tx_events,
609 acc_imported_txns,
610 acc_fetch_events,
611 acc_pending_fetch,
612 acc_cmds,
613 } = poll_durations;
614
615 metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
617 metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
619 metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
620 metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
621 metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
622 metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
623 metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
624 metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
625 }
626}
627
628impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
629 fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
631 for res in batch_results {
632 match res {
633 Ok(AddedTransactionOutcome { hash, .. }) => {
634 self.on_good_import(hash);
635 }
636 Err(err) => {
637 self.on_bad_import(err);
638 }
639 }
640 }
641 }
642
643 fn on_new_pooled_transaction_hashes(
645 &mut self,
646 peer_id: PeerId,
647 msg: NewPooledTransactionHashes,
648 ) {
649 if self.network.is_initially_syncing() {
651 return
652 }
653 if self.network.tx_gossip_disabled() {
654 return
655 }
656
657 let Some(peer) = self.peers.get_mut(&peer_id) else {
659 trace!(
660 peer_id = format!("{peer_id:#}"),
661 ?msg,
662 "discarding announcement from inactive peer"
663 );
664
665 return
666 };
667 let client = peer.client_version.clone();
668
669 let mut count_txns_already_seen_by_peer = 0;
671 for tx in msg.iter_hashes().copied() {
672 if !peer.seen_transactions.insert(tx) {
673 count_txns_already_seen_by_peer += 1;
674 }
675 }
676 if count_txns_already_seen_by_peer > 0 {
677 self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
682 self.metrics
683 .occurrences_hash_already_seen_by_peer
684 .increment(count_txns_already_seen_by_peer);
685
686 trace!(target: "net::tx",
687 %count_txns_already_seen_by_peer,
688 peer_id=format!("{peer_id:#}"),
689 ?client,
690 "Peer sent hashes that have already been marked as seen by peer"
691 );
692
693 self.report_already_seen(peer_id);
694 }
695
696 if msg.is_empty() {
698 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
699 return;
700 }
701
702 let original_len = msg.len();
703 let mut partially_valid_msg = msg.dedup();
704
705 if partially_valid_msg.len() != original_len {
706 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
707 }
708
709 partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
711
712 let mut should_report_peer = false;
719 let mut tx_types_counter = TxTypesCounter::default();
720
721 let has_eth68_metadata = partially_valid_msg
722 .msg_version()
723 .expect("partially valid announcement should have a version")
724 .has_eth68_metadata();
725
726 partially_valid_msg.retain(|tx_hash, metadata_ref_mut| {
727 let (ty_byte, size_val) = match *metadata_ref_mut {
728 Some((ty, size)) => {
729 if !has_eth68_metadata {
730 should_report_peer = true;
731 }
732 (ty, size)
733 }
734 None => {
735 if has_eth68_metadata {
736 should_report_peer = true;
737 return false;
738 }
739 (0u8, 0)
740 }
741 };
742
743 if has_eth68_metadata && let Some((actual_ty_byte, _)) = *metadata_ref_mut {
744 match TxType::try_from(actual_ty_byte) {
745 Ok(parsed_tx_type) => tx_types_counter.increase_by_tx_type(parsed_tx_type),
746 Err(_) => tx_types_counter.increase_other(),
747 }
748 }
749
750 let decision = self
751 .policies
752 .announcement_filter()
753 .decide_on_announcement(ty_byte, tx_hash, size_val);
754
755 match decision {
756 AnnouncementAcceptance::Accept => true,
757 AnnouncementAcceptance::Ignore => false,
758 AnnouncementAcceptance::Reject { penalize_peer } => {
759 if penalize_peer {
760 should_report_peer = true;
761 }
762 false
763 }
764 }
765 });
766
767 if has_eth68_metadata {
768 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
769 }
770
771 if should_report_peer {
772 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
773 }
774
775 let hashes_count_pre_pool_filter = partially_valid_msg.len();
783 self.pool.retain_unknown(&mut partially_valid_msg);
784 if hashes_count_pre_pool_filter > partially_valid_msg.len() {
785 let already_known_hashes_count =
786 hashes_count_pre_pool_filter - partially_valid_msg.len();
787 self.metrics
788 .occurrences_hashes_already_in_pool
789 .increment(already_known_hashes_count as u64);
790 }
791
792 if partially_valid_msg.is_empty() {
793 return
795 }
796
797 let mut valid_announcement_data =
798 ValidAnnouncementData::from_partially_valid_data(partially_valid_msg);
799
800 if valid_announcement_data.is_empty() {
801 return
803 }
804
805 let bad_imports = &self.bad_imports;
812 self.transaction_fetcher.filter_unseen_and_pending_hashes(
813 &mut valid_announcement_data,
814 |hash| bad_imports.contains(hash),
815 &peer_id,
816 &client,
817 );
818
819 if valid_announcement_data.is_empty() {
820 return
822 }
823
824 trace!(target: "net::tx::propagation",
825 peer_id=format!("{peer_id:#}"),
826 hashes_len=valid_announcement_data.len(),
827 hashes=?valid_announcement_data.keys(),
828 msg_version=%valid_announcement_data.msg_version(),
829 client_version=%client,
830 "received previously unseen and pending hashes in announcement from peer"
831 );
832
833 if !self.transaction_fetcher.is_idle(&peer_id) {
836 let msg_version = valid_announcement_data.msg_version();
838 let (hashes, _version) = valid_announcement_data.into_request_hashes();
839
840 trace!(target: "net::tx",
841 peer_id=format!("{peer_id:#}"),
842 hashes=?*hashes,
843 %msg_version,
844 %client,
845 "buffering hashes announced by busy peer"
846 );
847
848 self.transaction_fetcher.buffer_hashes(hashes, Some(peer_id));
849
850 return
851 }
852
853 let mut hashes_to_request =
854 RequestTxHashes::with_capacity(valid_announcement_data.len() / 4);
855 let surplus_hashes =
856 self.transaction_fetcher.pack_request(&mut hashes_to_request, valid_announcement_data);
857
858 if !surplus_hashes.is_empty() {
859 trace!(target: "net::tx",
860 peer_id=format!("{peer_id:#}"),
861 surplus_hashes=?*surplus_hashes,
862 %client,
863 "some hashes in announcement from peer didn't fit in `GetPooledTransactions` request, buffering surplus hashes"
864 );
865
866 self.transaction_fetcher.buffer_hashes(surplus_hashes, Some(peer_id));
867 }
868
869 trace!(target: "net::tx",
870 peer_id=format!("{peer_id:#}"),
871 hashes=?*hashes_to_request,
872 %client,
873 "sending hashes in `GetPooledTransactions` request to peer's session"
874 );
875
876 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
880 if let Some(failed_to_request_hashes) =
881 self.transaction_fetcher.request_transactions_from_peer(hashes_to_request, peer)
882 {
883 let conn_eth_version = peer.version;
884
885 trace!(target: "net::tx",
886 peer_id=format!("{peer_id:#}"),
887 failed_to_request_hashes=?*failed_to_request_hashes,
888 %conn_eth_version,
889 %client,
890 "sending `GetPooledTransactions` request to peer's session failed, buffering hashes"
891 );
892 self.transaction_fetcher.buffer_hashes(failed_to_request_hashes, Some(peer_id));
893 }
894 }
895}
896
897impl<Pool, N> TransactionsManager<Pool, N>
898where
899 Pool: TransactionPool + Unpin + 'static,
900 N: NetworkPrimitives<
901 BroadcastedTransaction: SignedTransaction,
902 PooledTransaction: SignedTransaction,
903 > + Unpin,
904 Pool::Transaction:
905 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
906{
907 fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
919 if self.network.tx_gossip_disabled() {
923 return
924 }
925
926 trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
927
928 self.propagate_all(hashes);
929 }
930
931 fn propagate_full_transactions_to_peer(
935 &mut self,
936 txs: Vec<TxHash>,
937 peer_id: PeerId,
938 propagation_mode: PropagationMode,
939 ) -> Option<PropagatedTransactions> {
940 let peer = self.peers.get_mut(&peer_id)?;
941 trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
942 let mut propagated = PropagatedTransactions::default();
943
944 let mut full_transactions = FullTransactionsBuilder::new(peer.version);
946
947 let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
948
949 if propagation_mode.is_forced() {
950 full_transactions.extend(to_propagate);
952 } else {
953 for tx in to_propagate {
956 if !peer.seen_transactions.contains(tx.tx_hash()) {
957 full_transactions.push(&tx);
959 }
960 }
961 }
962
963 if full_transactions.is_empty() {
964 return None
966 }
967
968 let PropagateTransactions { pooled, full } = full_transactions.build();
969
970 if let Some(new_pooled_hashes) = pooled {
972 for hash in new_pooled_hashes.iter_hashes().copied() {
973 propagated.record(hash, PropagateKind::Hash(peer_id));
974 peer.seen_transactions.insert(hash);
976 }
977
978 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
980 }
981
982 if let Some(new_full_transactions) = full {
984 for hash in new_full_transactions.iter_hashes() {
985 propagated.record(*hash, PropagateKind::Full(peer_id));
986 peer.seen_transactions.insert(*hash);
988 }
989
990 self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
992 }
993
994 self.metrics.propagated_transactions.increment(propagated.len() as u64);
996
997 Some(propagated)
998 }
999
1000 fn propagate_hashes_to(
1004 &mut self,
1005 hashes: Vec<TxHash>,
1006 peer_id: PeerId,
1007 propagation_mode: PropagationMode,
1008 ) {
1009 trace!(target: "net::tx", "Start propagating transactions as hashes");
1010
1011 let propagated = {
1014 let Some(peer) = self.peers.get_mut(&peer_id) else {
1015 return
1017 };
1018
1019 let to_propagate =
1020 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
1021
1022 let mut propagated = PropagatedTransactions::default();
1023
1024 let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
1026
1027 if propagation_mode.is_forced() {
1028 hashes.extend(to_propagate)
1029 } else {
1030 for tx in to_propagate {
1031 if !peer.seen_transactions.contains(tx.tx_hash()) {
1032 hashes.push(&tx);
1034 }
1035 }
1036 }
1037
1038 let new_pooled_hashes = hashes.build();
1039
1040 if new_pooled_hashes.is_empty() {
1041 return
1043 }
1044
1045 for hash in new_pooled_hashes.iter_hashes().copied() {
1046 propagated.record(hash, PropagateKind::Hash(peer_id));
1047 peer.seen_transactions.insert(hash);
1048 }
1049
1050 trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
1051
1052 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1054
1055 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1057
1058 propagated
1059 };
1060
1061 self.pool.on_propagated(propagated);
1063 }
1064
1065 fn propagate_transactions(
1072 &mut self,
1073 to_propagate: Vec<PropagateTransaction>,
1074 propagation_mode: PropagationMode,
1075 ) -> PropagatedTransactions {
1076 let mut propagated = PropagatedTransactions::default();
1077 if self.network.tx_gossip_disabled() {
1078 return propagated
1079 }
1080
1081 let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1083
1084 let mut num_full_peers = 0;
1086 for (peer_id, peer) in &mut self.peers {
1087 if !self.policies.propagation_policy().can_propagate(peer) {
1088 continue
1090 }
1091
1092 let mut builder = if num_full_peers < max_num_full {
1094 num_full_peers += 1;
1095 PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1096 } else {
1097 PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1098 };
1099
1100 if propagation_mode.is_forced() {
1103 for tx in &to_propagate {
1104 peer.seen_transactions.insert(*tx.tx_hash());
1105 builder.push(tx);
1106 }
1107 } else {
1108 for tx in &to_propagate {
1112 if peer.seen_transactions.insert(*tx.tx_hash()) {
1114 builder.push(tx);
1115 }
1116 }
1117 }
1118
1119 if builder.is_empty() {
1120 trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1121 continue
1122 }
1123
1124 let PropagateTransactions { pooled, full } = builder.build();
1125
1126 if let Some(mut new_pooled_hashes) = pooled {
1128 if new_pooled_hashes.len() >
1132 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1133 {
1134 for hash in new_pooled_hashes
1137 .iter_hashes()
1138 .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1139 {
1140 peer.seen_transactions.remove(hash);
1141 }
1142 new_pooled_hashes.truncate(
1143 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1144 );
1145 }
1146
1147 for hash in new_pooled_hashes.iter_hashes().copied() {
1148 propagated.record(hash, PropagateKind::Hash(*peer_id));
1149 }
1150
1151 trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1152
1153 self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1155 }
1156
1157 if let Some(new_full_transactions) = full {
1159 for hash in new_full_transactions.iter_hashes() {
1160 propagated.record(*hash, PropagateKind::Full(*peer_id));
1161 }
1162
1163 trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1164
1165 self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1167 }
1168 }
1169
1170 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1172
1173 propagated
1174 }
1175
1176 fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1181 if self.peers.is_empty() {
1182 return
1184 }
1185 let propagated = self.propagate_transactions(
1186 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1187 PropagationMode::Basic,
1188 );
1189
1190 self.pool.on_propagated(propagated);
1192 }
1193
1194 fn on_get_pooled_transactions(
1196 &mut self,
1197 peer_id: PeerId,
1198 request: GetPooledTransactions,
1199 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1200 ) {
1201 if self.network.tx_gossip_disabled() {
1203 let _ = response.send(Ok(PooledTransactions::default()));
1204 return
1205 }
1206 if let Some(peer) = self.peers.get_mut(&peer_id) {
1207 let transactions = self.pool.get_pooled_transaction_elements(
1208 request.0,
1209 GetPooledTransactionLimit::ResponseSizeSoftLimit(
1210 self.transaction_fetcher.info.soft_limit_byte_size_pooled_transactions_response,
1211 ),
1212 );
1213 trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1214
1215 peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1218
1219 let resp = PooledTransactions(transactions);
1220 let _ = response.send(Ok(resp));
1221 }
1222 }
1223
1224 fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1226 match cmd {
1227 TransactionsCommand::PropagateHash(hash) => {
1228 self.on_new_pending_transactions(vec![hash])
1229 }
1230 TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1231 self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1232 }
1233 TransactionsCommand::GetActivePeers(tx) => {
1234 let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1235 tx.send(peers).ok();
1236 }
1237 TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1238 if let Some(propagated) =
1239 self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1240 {
1241 self.pool.on_propagated(propagated);
1242 }
1243 }
1244 TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1245 TransactionsCommand::BroadcastTransactions(txs) => {
1246 let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1247 self.pool.on_propagated(propagated);
1248 }
1249 TransactionsCommand::GetTransactionHashes { peers, tx } => {
1250 let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1251 for peer_id in peers {
1252 let hashes = self
1253 .peers
1254 .get(&peer_id)
1255 .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1256 .unwrap_or_default();
1257 res.insert(peer_id, hashes);
1258 }
1259 tx.send(res).ok();
1260 }
1261 TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1262 let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1263 peer_request_sender.send(sender).ok();
1264 }
1265 }
1266 }
1267
1268 fn handle_peer_session(
1272 &mut self,
1273 info: SessionInfo,
1274 messages: PeerRequestSender<PeerRequest<N>>,
1275 ) {
1276 let SessionInfo { peer_id, client_version, version, .. } = info;
1277
1278 let peer = PeerMetadata::<N>::new(
1280 messages,
1281 version,
1282 client_version,
1283 self.config.max_transactions_seen_by_peer_history,
1284 info.peer_kind,
1285 );
1286 let peer = match self.peers.entry(peer_id) {
1287 Entry::Occupied(mut entry) => {
1288 entry.insert(peer);
1289 entry.into_mut()
1290 }
1291 Entry::Vacant(entry) => entry.insert(peer),
1292 };
1293
1294 self.policies.propagation_policy_mut().on_session_established(peer);
1295
1296 if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1300 trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1301 return
1302 }
1303
1304 let pooled_txs = self.pool.pooled_transactions_max(
1306 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1307 );
1308 if pooled_txs.is_empty() {
1309 trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1310 return;
1311 }
1312
1313 let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1315 for pooled_tx in pooled_txs {
1316 peer.seen_transactions.insert(*pooled_tx.hash());
1317 msg_builder.push_pooled(pooled_tx);
1318 }
1319
1320 debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1321 let msg = msg_builder.build();
1322 self.network.send_transactions_hashes(peer_id, msg);
1323 }
1324
1325 fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1327 match event_result {
1328 NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1329 self.on_peer_session_closed(&peer_id);
1330 }
1331 NetworkEvent::ActivePeerSession { info, messages } => {
1332 self.handle_peer_session(info, messages);
1334 }
1335 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1336 let peer_id = info.peer_id;
1337 let messages = match self.peers.get(&peer_id) {
1339 Some(p) => p.request_tx.clone(),
1340 None => {
1341 debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1342 return;
1343 }
1344 };
1345 self.handle_peer_session(info, messages);
1346 }
1347 _ => {}
1348 }
1349 }
1350
1351 fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1353 if self.config.ingress_policy.allows_all() {
1354 return true;
1355 }
1356 let Some(peer) = self.peers.get(peer_id) else {
1357 return false;
1358 };
1359 self.config.ingress_policy.allows(peer.peer_kind())
1360 }
1361
1362 fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1364 match event {
1365 NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1366 if !self.accepts_incoming_from(&peer_id) {
1367 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1368 return;
1369 }
1370
1371 let has_blob_txs = msg.has_eip4844();
1375
1376 let non_blob_txs = msg
1377 .into_iter()
1378 .map(N::PooledTransaction::try_from)
1379 .filter_map(Result::ok)
1380 .collect();
1381
1382 self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1383
1384 if has_blob_txs {
1385 debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1386 self.report_peer_bad_transactions(peer_id);
1387 }
1388 }
1389 NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1390 if !self.accepts_incoming_from(&peer_id) {
1391 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1392 return;
1393 }
1394 self.on_new_pooled_transaction_hashes(peer_id, msg)
1395 }
1396 NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1397 self.on_get_pooled_transactions(peer_id, request, response)
1398 }
1399 NetworkTransactionEvent::GetTransactionsHandle(response) => {
1400 let _ = response.send(Some(self.handle()));
1401 }
1402 }
1403 }
1404
1405 fn import_transactions(
1407 &mut self,
1408 peer_id: PeerId,
1409 transactions: PooledTransactions<N::PooledTransaction>,
1410 source: TransactionSource,
1411 ) {
1412 if self.network.is_initially_syncing() {
1414 return
1415 }
1416 if self.network.tx_gossip_disabled() {
1417 return
1418 }
1419
1420 if !self.has_capacity_for_pending_pool_imports() {
1422 return
1423 }
1424
1425 let mut transactions = transactions.0;
1426
1427 let capacity = self.remaining_pool_import_capacity();
1431 if transactions.len() > capacity {
1432 let skipped = transactions.len() - capacity;
1433 transactions.truncate(capacity);
1434 self.metrics
1435 .skipped_transactions_pending_pool_imports_at_capacity
1436 .increment(skipped as u64);
1437 trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
1438 }
1439
1440 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
1441 let client_version = peer.client_version.clone();
1442
1443 let start = Instant::now();
1444
1445 self.transaction_fetcher
1447 .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1448
1449 let mut num_already_seen_by_peer = 0;
1454 for tx in &transactions {
1455 if source.is_broadcast() && !peer.seen_transactions.insert(*tx.tx_hash()) {
1456 num_already_seen_by_peer += 1;
1457 }
1458 }
1459
1460 if peer.version() == EthVersion::Eth72 {
1465 let len_before = transactions.len();
1466 transactions.retain(|tx| !tx.is_eip4844());
1467 let dropped = len_before - transactions.len();
1468 if dropped > 0 {
1469 trace!(target: "net::tx",
1470 peer_id=format!("{peer_id:#}"),
1471 dropped,
1472 "dropped blob transactions from eth72 response, cell fetching not implemented"
1473 );
1474 }
1475 }
1476
1477 let mut has_bad_transactions = false;
1479
1480 transactions.retain(|tx| {
1483 if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1484 let peers = entry.get_mut();
1485 if !peers.contains(&peer_id) {
1486 peers.push(peer_id);
1487 }
1488 return false
1489 }
1490 if self.bad_imports.contains(tx.tx_hash()) {
1491 trace!(target: "net::tx",
1492 peer_id=format!("{peer_id:#}"),
1493 hash=%tx.tx_hash(),
1494 %client_version,
1495 "received a known bad transaction from peer"
1496 );
1497 has_bad_transactions = true;
1498 return false;
1499 }
1500 true
1501 });
1502
1503 let txns_count_pre_pool_filter = transactions.len();
1505 self.pool.retain_unknown(&mut transactions);
1506 if txns_count_pre_pool_filter > transactions.len() {
1507 let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1508 self.metrics
1509 .occurrences_transactions_already_in_pool
1510 .increment(already_known_txns_count as u64);
1511 }
1512
1513 let txs_len = transactions.len();
1514
1515 let recover = |tx| {
1516 let recovered = if let Some(cache) = &self.sender_recovery_cache {
1517 Pool::Transaction::try_recover_with_cache(tx, cache)
1518 } else {
1519 Pool::Transaction::try_recover(tx)
1520 };
1521 match recovered {
1522 Ok(tx) => Some(tx),
1523 Err(badtx) => {
1524 trace!(target: "net::tx",
1525 peer_id=format!("{peer_id:#}"),
1526 hash=%badtx.tx_hash(),
1527 client_version=%client_version,
1528 "failed ecrecovery for transaction"
1529 );
1530 None
1531 }
1532 }
1533 };
1534
1535 let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1536
1537 has_bad_transactions |= new_txs.len() != txs_len;
1538
1539 for tx in &new_txs {
1541 self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1542 }
1543
1544 if !new_txs.is_empty() {
1547 let pool = self.pool.clone();
1548 let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1550 metric_pending_pool_imports.increment(new_txs.len() as f64);
1551
1552 self.pending_pool_imports_info
1554 .pending_pool_imports
1555 .fetch_add(new_txs.len(), Ordering::Relaxed);
1556 let tx_manager_info_pending_pool_imports =
1557 self.pending_pool_imports_info.pending_pool_imports.clone();
1558
1559 trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1560 let import = Box::pin(async move {
1561 let added = new_txs.len();
1562 let res = pool.add_external_transactions(new_txs).await;
1563
1564 metric_pending_pool_imports.decrement(added as f64);
1566 tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1568
1569 res
1570 });
1571
1572 self.pool_imports.push(import);
1573 }
1574
1575 if num_already_seen_by_peer > 0 {
1576 self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1577 self.metrics
1578 .occurrences_of_transaction_already_seen_by_peer
1579 .increment(num_already_seen_by_peer);
1580 trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1581 }
1582
1583 if has_bad_transactions {
1584 self.report_peer_bad_transactions(peer_id)
1586 }
1587
1588 if num_already_seen_by_peer > 0 {
1589 self.report_already_seen(peer_id);
1590 }
1591
1592 self.metrics.pool_import_prepare_duration.record(start.elapsed());
1593 }
1594
1595 fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1597 match fetch_event {
1598 FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => {
1599 self.import_transactions(peer_id, transactions, TransactionSource::Response);
1600 if report_peer {
1601 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1602 }
1603 }
1604 FetchEvent::FetchError { peer_id, error } => {
1605 trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1606 self.on_request_error(peer_id, error);
1607 }
1608 FetchEvent::EmptyResponse { peer_id } => {
1609 trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1610 }
1611 }
1612 }
1613}
1614
1615impl<
1623 Pool: TransactionPool + Unpin + 'static,
1624 N: NetworkPrimitives<
1625 BroadcastedTransaction: SignedTransaction,
1626 PooledTransaction: SignedTransaction,
1627 > + Unpin,
1628 > Future for TransactionsManager<Pool, N>
1629where
1630 Pool::Transaction:
1631 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1632{
1633 type Output = ();
1634
1635 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1636 let start = Instant::now();
1637 let mut poll_durations = TxManagerPollDurations::default();
1638
1639 let this = self.get_mut();
1640
1641 let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1647 poll_durations.acc_network_events,
1648 "net::tx",
1649 "Network events stream",
1650 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1651 this.network_events.poll_next_unpin(cx),
1652 |event| this.on_network_event(event)
1653 );
1654
1655 let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1670 poll_durations.acc_tx_events,
1671 "net::tx",
1672 "Network transaction events stream",
1673 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1674 this.transaction_events.poll_next_unpin(cx),
1675 |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1676 );
1677
1678 let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1689 poll_durations.acc_fetch_events,
1690 "net::tx",
1691 "Transaction fetch events stream",
1692 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1693 this.transaction_fetcher.poll_next_unpin(cx),
1694 |event| this.on_fetch_event(event),
1695 );
1696
1697 let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1712 poll_durations.acc_pending_imports,
1713 "net::tx",
1714 "Batched pool imports stream",
1715 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1716 this.pool_imports.poll_next_unpin(cx),
1717 |batch_results| this.on_batch_import_result(batch_results)
1718 );
1719
1720 let mut new_txs = Vec::new();
1733 let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1734 cx,
1735 &mut new_txs,
1736 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1737 ) {
1738 Poll::Ready(count) => {
1739 if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1740 true
1743 } else {
1744 let limit =
1748 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1749 new_txs.len();
1750 this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1751 }
1752 }
1753 Poll::Pending => false,
1754 };
1755 if !new_txs.is_empty() {
1756 this.on_new_pending_transactions(new_txs);
1757 }
1758
1759 duration_metered_exec!(
1764 {
1765 if this.has_capacity_for_fetching_pending_hashes() &&
1766 this.on_fetch_hashes_pending_fetch()
1767 {
1768 maybe_more_tx_fetch_events = true;
1769 }
1770 },
1771 poll_durations.acc_pending_fetch
1772 );
1773
1774 let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1776 poll_durations.acc_cmds,
1777 "net::tx",
1778 "Commands channel",
1779 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1780 this.command_rx.poll_next_unpin(cx),
1781 |cmd| this.on_command(cmd)
1782 );
1783
1784 this.transaction_fetcher.update_metrics();
1785
1786 if maybe_more_network_events ||
1788 maybe_more_commands ||
1789 maybe_more_tx_events ||
1790 maybe_more_tx_fetch_events ||
1791 maybe_more_pool_imports ||
1792 maybe_more_pending_txns
1793 {
1794 cx.waker().wake_by_ref();
1796 return Poll::Pending
1797 }
1798
1799 this.update_poll_metrics(start, poll_durations);
1800
1801 Poll::Pending
1802 }
1803}
1804
1805#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1809enum PropagationMode {
1810 Basic,
1814 Forced,
1819}
1820
1821impl PropagationMode {
1822 const fn is_forced(self) -> bool {
1824 matches!(self, Self::Forced)
1825 }
1826}
1827
1828#[derive(Debug, Clone)]
1830struct PropagateTransaction {
1831 is_broadcastable_in_full: bool,
1832 propagation_size: usize,
1839 transaction: LazyEncodedTransaction,
1840}
1841
1842impl PropagateTransaction {
1843 fn new<T: SignedTransaction>(transaction: T) -> Self {
1849 let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1850 let propagation_size = transaction.encode_2718_len();
1851
1852 Self {
1853 is_broadcastable_in_full,
1854 propagation_size,
1855 transaction: LazyEncoded::new(transaction),
1856 }
1857 }
1858
1859 fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1865 let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1866 let propagation_size = tx.encoded_length();
1867 Self {
1868 is_broadcastable_in_full,
1869 propagation_size,
1870 transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1871 }
1872 }
1873
1874 fn tx_hash(&self) -> &TxHash {
1875 self.transaction.tx_hash()
1876 }
1877
1878 const fn propagation_size(&self) -> usize {
1880 self.propagation_size
1881 }
1882
1883 fn tx_type(&self) -> u8 {
1884 self.transaction.ty()
1885 }
1886
1887 const fn is_broadcastable_in_full(&self) -> bool {
1888 self.is_broadcastable_in_full
1889 }
1890
1891 fn shared(&self) -> LazyEncodedTransaction {
1892 self.transaction.clone()
1893 }
1894}
1895
1896#[derive(Debug)]
1898struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1899 transaction: Arc<ValidPoolTransaction<P>>,
1900}
1901
1902impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1903 const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1904 Self { transaction }
1905 }
1906
1907 fn encode_uncached(&self, out: &mut dyn BufMut) {
1908 (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1909 }
1910}
1911
1912impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1913 fn encode(&self, out: &mut dyn BufMut) {
1914 self.encode_uncached(out);
1915 }
1916
1917 fn length(&self) -> usize {
1918 (*self.transaction.transaction.consensus_ref().inner()).length()
1919 }
1920}
1921
1922impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1923 fn tx_hash(&self) -> &TxHash {
1924 self.transaction.hash()
1925 }
1926}
1927
1928impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1929 fn ty(&self) -> u8 {
1930 self.transaction.transaction.ty()
1931 }
1932}
1933
1934#[derive(Debug, Clone)]
1937enum PropagateTransactionsBuilder {
1938 Pooled(PooledTransactionsHashesBuilder),
1939 Full(FullTransactionsBuilder),
1940}
1941
1942impl PropagateTransactionsBuilder {
1943 fn pooled(version: EthVersion, capacity: usize) -> Self {
1946 Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1947 }
1948
1949 fn full(version: EthVersion, capacity: usize) -> Self {
1952 Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1953 }
1954
1955 fn is_empty(&self) -> bool {
1957 match self {
1958 Self::Pooled(builder) => builder.is_empty(),
1959 Self::Full(builder) => builder.is_empty(),
1960 }
1961 }
1962
1963 fn build(self) -> PropagateTransactions {
1965 match self {
1966 Self::Pooled(pooled) => {
1967 PropagateTransactions { pooled: Some(pooled.build()), full: None }
1968 }
1969 Self::Full(full) => full.build(),
1970 }
1971 }
1972}
1973
1974impl PropagateTransactionsBuilder {
1975 fn push(&mut self, transaction: &PropagateTransaction) {
1977 match self {
1978 Self::Pooled(builder) => builder.push(transaction),
1979 Self::Full(builder) => builder.push(transaction),
1980 }
1981 }
1982}
1983
1984struct PropagateTransactions {
1986 pooled: Option<NewPooledTransactionHashes>,
1988 full: Option<BroadcastPoolTransactions>,
1990}
1991
1992#[derive(Debug, Clone)]
1997struct FullTransactionsBuilder {
1998 total_size: usize,
2000 transactions: Vec<LazyEncodedTransaction>,
2002 pooled: PooledTransactionsHashesBuilder,
2004}
2005
2006impl FullTransactionsBuilder {
2007 fn new(version: EthVersion) -> Self {
2009 Self {
2010 total_size: 0,
2011 pooled: PooledTransactionsHashesBuilder::new(version),
2012 transactions: vec![],
2013 }
2014 }
2015
2016 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2021 Self {
2022 total_size: 0,
2023 pooled: PooledTransactionsHashesBuilder::new(version),
2024 transactions: Vec::with_capacity(capacity),
2025 }
2026 }
2027
2028 fn is_empty(&self) -> bool {
2030 self.transactions.is_empty() && self.pooled.is_empty()
2031 }
2032
2033 fn build(self) -> PropagateTransactions {
2035 let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
2036 let full =
2037 (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
2038 PropagateTransactions { pooled, full }
2039 }
2040
2041 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2043 for tx in txs {
2044 self.push(&tx)
2045 }
2046 }
2047
2048 fn push(&mut self, transaction: &PropagateTransaction) {
2058 if !transaction.is_broadcastable_in_full() {
2067 self.pooled.push(transaction);
2068 return
2069 }
2070
2071 let new_size = self.total_size + transaction.propagation_size();
2072 if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2073 self.total_size > 0
2074 {
2075 self.pooled.push(transaction);
2077 return
2078 }
2079
2080 self.total_size = new_size;
2081 self.transactions.push(transaction.shared());
2082 }
2083}
2084
2085#[derive(Debug, Clone)]
2088enum PooledTransactionsHashesBuilder {
2089 Eth66(NewPooledTransactionHashes66),
2090 Eth68(NewPooledTransactionHashes68),
2091 Eth72(NewPooledTransactionHashes72),
2092}
2093
2094impl PooledTransactionsHashesBuilder {
2097 fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2099 match self {
2100 Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2101 Self::Eth68(msg) => {
2102 msg.hashes.push(*pooled_tx.hash());
2103 msg.sizes.push(pooled_tx.encoded_length());
2104 msg.types.push(pooled_tx.transaction.ty());
2105 }
2106 Self::Eth72(msg) => {
2107 msg.hashes.push(*pooled_tx.hash());
2108 msg.sizes.push(pooled_tx.encoded_length());
2109 let ty = pooled_tx.transaction.ty();
2110 msg.types.push(ty);
2111 if ty == EIP4844_TX_TYPE_ID {
2112 msg.cell_mask = Some(NewPooledTransactionHashes72::ALL_CELLS_MASK);
2114 }
2115 }
2116 }
2117 }
2118
2119 fn is_empty(&self) -> bool {
2121 match self {
2122 Self::Eth66(hashes) => hashes.is_empty(),
2123 Self::Eth68(hashes) => hashes.is_empty(),
2124 Self::Eth72(hashes) => hashes.is_empty(),
2125 }
2126 }
2127
2128 fn len(&self) -> usize {
2130 match self {
2131 Self::Eth66(hashes) => hashes.len(),
2132 Self::Eth68(hashes) => hashes.len(),
2133 Self::Eth72(hashes) => hashes.len(),
2134 }
2135 }
2136
2137 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2139 for tx in txs {
2140 self.push(&tx);
2141 }
2142 }
2143
2144 fn push(&mut self, tx: &PropagateTransaction) {
2145 match self {
2146 Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2147 Self::Eth68(msg) => {
2148 msg.hashes.push(*tx.tx_hash());
2149 msg.sizes.push(tx.propagation_size());
2150 msg.types.push(tx.tx_type());
2151 }
2152 Self::Eth72(msg) => {
2153 msg.hashes.push(*tx.tx_hash());
2154 msg.sizes.push(tx.propagation_size());
2155 let ty = tx.tx_type();
2156 msg.types.push(ty);
2157 if ty == EIP4844_TX_TYPE_ID {
2158 msg.cell_mask = Some(NewPooledTransactionHashes72::ALL_CELLS_MASK);
2160 }
2161 }
2162 }
2163 }
2164
2165 fn new(version: EthVersion) -> Self {
2167 match version {
2168 EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2169 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2170 Self::Eth68(Default::default())
2171 }
2172 EthVersion::Eth72 => Self::Eth72(Default::default()),
2173 }
2174 }
2175
2176 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2179 match version {
2180 EthVersion::Eth66 | EthVersion::Eth67 => {
2181 Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2182 }
2183 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2184 Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2185 }
2186 EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2187 }
2188 }
2189
2190 fn build(self) -> NewPooledTransactionHashes {
2191 match self {
2192 Self::Eth66(mut msg) => {
2193 msg.shrink_to_fit();
2194 msg.into()
2195 }
2196 Self::Eth68(mut msg) => {
2197 msg.shrink_to_fit();
2198 msg.into()
2199 }
2200 Self::Eth72(mut msg) => {
2201 msg.shrink_to_fit();
2202 msg.into()
2203 }
2204 }
2205 }
2206}
2207
2208enum TransactionSource {
2210 Broadcast,
2212 Response,
2214}
2215
2216impl TransactionSource {
2219 const fn is_broadcast(&self) -> bool {
2221 matches!(self, Self::Broadcast)
2222 }
2223}
2224
2225#[derive(Debug)]
2227pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2228 seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2232 request_tx: PeerRequestSender<PeerRequest<N>>,
2234 version: EthVersion,
2236 client_version: Arc<str>,
2238 peer_kind: PeerKind,
2240}
2241
2242impl<N: NetworkPrimitives> PeerMetadata<N> {
2243 pub fn new(
2245 request_tx: PeerRequestSender<PeerRequest<N>>,
2246 version: EthVersion,
2247 client_version: Arc<str>,
2248 max_transactions_seen_by_peer: u32,
2249 peer_kind: PeerKind,
2250 ) -> Self {
2251 Self {
2252 seen_transactions: LruCache::with_hasher(
2253 max_transactions_seen_by_peer,
2254 Default::default(),
2255 ),
2256 request_tx,
2257 version,
2258 client_version,
2259 peer_kind,
2260 }
2261 }
2262
2263 pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2265 &self.request_tx
2266 }
2267
2268 pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2270 &mut self.seen_transactions
2271 }
2272
2273 pub const fn version(&self) -> EthVersion {
2275 self.version
2276 }
2277
2278 pub fn client_version(&self) -> &str {
2280 &self.client_version
2281 }
2282
2283 pub const fn peer_kind(&self) -> PeerKind {
2285 self.peer_kind
2286 }
2287}
2288
2289#[derive(Debug)]
2291enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2292 PropagateHash(B256),
2294 PropagateHashesTo(Vec<B256>, PeerId),
2296 GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2298 PropagateTransactionsTo(Vec<TxHash>, PeerId),
2300 PropagateTransactions(Vec<TxHash>),
2302 BroadcastTransactions(Vec<PropagateTransaction>),
2304 GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2306 GetPeerSender {
2308 peer_id: PeerId,
2309 peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2310 },
2311}
2312
2313#[derive(Debug)]
2315pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2316 IncomingTransactions {
2320 peer_id: PeerId,
2322 msg: Transactions<N::BroadcastedTransaction>,
2324 },
2325 IncomingPooledTransactionHashes {
2327 peer_id: PeerId,
2329 msg: NewPooledTransactionHashes,
2331 },
2332 GetPooledTransactions {
2334 peer_id: PeerId,
2336 request: GetPooledTransactions,
2338 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2340 },
2341 GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2343}
2344
2345#[derive(Debug)]
2347pub struct PendingPoolImportsInfo {
2348 pending_pool_imports: Arc<AtomicUsize>,
2350 max_pending_pool_imports: usize,
2352}
2353
2354impl PendingPoolImportsInfo {
2355 pub fn new(max_pending_pool_imports: usize) -> Self {
2357 Self { pending_pool_imports: Arc::new(AtomicUsize::default()), max_pending_pool_imports }
2358 }
2359
2360 pub fn has_capacity(&self, max_pending_pool_imports: usize) -> bool {
2362 self.pending_pool_imports.load(Ordering::Relaxed) < max_pending_pool_imports
2363 }
2364}
2365
2366impl Default for PendingPoolImportsInfo {
2367 fn default() -> Self {
2368 Self::new(DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS)
2369 }
2370}
2371
2372#[derive(Debug, Default)]
2373struct TxManagerPollDurations {
2374 acc_network_events: Duration,
2375 acc_pending_imports: Duration,
2376 acc_tx_events: Duration,
2377 acc_imported_txns: Duration,
2378 acc_fetch_events: Duration,
2379 acc_pending_fetch: Duration,
2380 acc_cmds: Duration,
2381}
2382
2383impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2384 fn size(&self) -> usize {
2387 match self {
2388 Self::IncomingTransactions { peer_id, msg } => {
2389 core::mem::size_of_val(peer_id) +
2390 msg.0.iter().map(InMemorySize::size).sum::<usize>()
2391 }
2392 Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2393 core::mem::size_of_val(peer_id) + msg.size()
2394 }
2395 Self::GetPooledTransactions { peer_id, request, response } => {
2396 core::mem::size_of_val(peer_id) +
2397 request.0.len() * core::mem::size_of::<TxHash>() +
2398 core::mem::size_of_val(response)
2399 }
2400 Self::GetTransactionsHandle(_) => 0,
2401 }
2402 }
2403}
2404
2405#[cfg(test)]
2406mod tests {
2407 use super::*;
2408 use crate::{
2409 test_utils::{
2410 transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2411 Testnet,
2412 },
2413 transactions::config::RelaxedEthAnnouncementFilter,
2414 NetworkConfigBuilder, NetworkManager,
2415 };
2416 use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2417 use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2418 use alloy_primitives::{hex, Signature, TxKind, B256, U256};
2419 use alloy_rlp::Decodable;
2420 use futures::FutureExt;
2421 use reth_chainspec::MIN_TRANSACTION_GAS;
2422 use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2423 use reth_network_api::{NetworkInfo, PeerKind};
2424 use reth_network_p2p::{
2425 error::{RequestError, RequestResult},
2426 sync::{NetworkSyncUpdater, SyncState},
2427 };
2428 use reth_storage_api::noop::NoopProvider;
2429 use reth_tasks::Runtime;
2430 use reth_transaction_pool::{
2431 blobstore::InMemoryBlobStore,
2432 error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2433 identifier::SenderIdentifiers,
2434 test_utils::{
2435 testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2436 TransactionGenerator,
2437 },
2438 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2439 };
2440 use secp256k1::SecretKey;
2441 use std::{
2442 future::poll_fn,
2443 net::{IpAddr, Ipv4Addr, SocketAddr},
2444 str::FromStr,
2445 time::Instant,
2446 };
2447 use tracing::error;
2448
2449 type EthTestPool = Pool<
2450 OkValidator<EthPooledTransaction>,
2451 CoinbaseTipOrdering<EthPooledTransaction>,
2452 InMemoryBlobStore,
2453 >;
2454
2455 async fn new_eth_tx_manager() -> (
2456 TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2457 NetworkManager<EthNetworkPrimitives>,
2458 ) {
2459 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2460 let client = NoopProvider::default();
2461
2462 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2463 .listener_port(0)
2464 .disable_discovery()
2465 .build(client);
2466
2467 let pool = Pool::new(
2468 OkValidator::default(),
2469 CoinbaseTipOrdering::default(),
2470 InMemoryBlobStore::default(),
2471 Default::default(),
2472 );
2473
2474 let transactions_manager_config = config.transactions_manager_config.clone();
2475 let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2476 .await
2477 .unwrap()
2478 .into_builder()
2479 .transactions(pool.clone(), transactions_manager_config)
2480 .split_with_handle();
2481
2482 (transactions, network)
2483 }
2484
2485 fn valid_eth_pool_transaction(
2486 transaction: EthPooledTransaction,
2487 ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2488 let mut ids = SenderIdentifiers::default();
2489 let transaction_id =
2490 ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2491
2492 Arc::new(ValidPoolTransaction {
2493 propagate: false,
2494 transaction_id,
2495 transaction,
2496 timestamp: Instant::now(),
2497 origin: TransactionOrigin::External,
2498 authority_ids: None,
2499 })
2500 }
2501
2502 fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2503 tx_gen: &mut TransactionGenerator<R>,
2504 nonce: u64,
2505 ) -> EthPooledTransaction {
2506 EthPooledTransaction::try_from_consensus(
2507 tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2508 )
2509 .unwrap()
2510 }
2511
2512 #[tokio::test(flavor = "multi_thread")]
2513 async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2514 reth_tracing::init_test_tracing();
2515 let net = Testnet::create(3).await;
2516
2517 let mut handles = net.handles();
2518 let handle0 = handles.next().unwrap();
2519 let handle1 = handles.next().unwrap();
2520
2521 drop(handles);
2522 let handle = net.spawn();
2523
2524 let listener0 = handle0.event_listener();
2525 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2526 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2527
2528 let client = NoopProvider::default();
2529 let pool = testing_pool();
2530 let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2531 .disable_discovery()
2532 .listener_port(0)
2533 .build(client);
2534 let transactions_manager_config = config.transactions_manager_config.clone();
2535 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2536 .await
2537 .unwrap()
2538 .into_builder()
2539 .transactions(pool.clone(), transactions_manager_config)
2540 .split_with_handle();
2541
2542 tokio::task::spawn(network);
2543
2544 network_handle.update_sync_state(SyncState::Syncing);
2546 assert!(NetworkInfo::is_syncing(&network_handle));
2547 assert!(NetworkInfo::is_initially_syncing(&network_handle));
2548
2549 let mut established = listener0.take(2);
2551 while let Some(ev) = established.next().await {
2552 match ev {
2553 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2554 transactions
2556 .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2557 }
2558 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2559 ev => {
2560 error!("unexpected event {ev:?}")
2561 }
2562 }
2563 }
2564 let input = hex!(
2566 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2567 );
2568 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2569 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2570 peer_id: *handle1.peer_id(),
2571 msg: Transactions(vec![signed_tx.clone()]),
2572 });
2573 poll_fn(|cx| {
2574 let _ = transactions.poll_unpin(cx);
2575 Poll::Ready(())
2576 })
2577 .await;
2578 assert!(pool.is_empty());
2579 handle.terminate().await;
2580 }
2581
2582 #[tokio::test(flavor = "multi_thread")]
2583 async fn test_tx_broadcasts_through_two_syncs() {
2584 reth_tracing::init_test_tracing();
2585 let net = Testnet::create(3).await;
2586
2587 let mut handles = net.handles();
2588 let handle0 = handles.next().unwrap();
2589 let handle1 = handles.next().unwrap();
2590
2591 drop(handles);
2592 let handle = net.spawn();
2593
2594 let listener0 = handle0.event_listener();
2595 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2596 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2597
2598 let client = NoopProvider::default();
2599 let pool = testing_pool();
2600 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2601 .disable_discovery()
2602 .listener_port(0)
2603 .build(client);
2604 let transactions_manager_config = config.transactions_manager_config.clone();
2605 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2606 .await
2607 .unwrap()
2608 .into_builder()
2609 .transactions(pool.clone(), transactions_manager_config)
2610 .split_with_handle();
2611
2612 tokio::task::spawn(network);
2613
2614 network_handle.update_sync_state(SyncState::Syncing);
2616 assert!(NetworkInfo::is_syncing(&network_handle));
2617 network_handle.update_sync_state(SyncState::Idle);
2618 assert!(!NetworkInfo::is_syncing(&network_handle));
2619 network_handle.update_sync_state(SyncState::Syncing);
2620 assert!(NetworkInfo::is_syncing(&network_handle));
2621
2622 let mut established = listener0.take(2);
2624 while let Some(ev) = established.next().await {
2625 match ev {
2626 NetworkEvent::ActivePeerSession { .. } |
2627 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2628 transactions.on_network_event(ev);
2630 }
2631 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2632 _ => {
2633 error!("unexpected event {ev:?}")
2634 }
2635 }
2636 }
2637 let input = hex!(
2639 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2640 );
2641 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2642 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2643 peer_id: *handle1.peer_id(),
2644 msg: Transactions(vec![signed_tx.clone()]),
2645 });
2646 poll_fn(|cx| {
2647 let _ = transactions.poll_unpin(cx);
2648 Poll::Ready(())
2649 })
2650 .await;
2651 assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2652 assert!(NetworkInfo::is_syncing(&network_handle));
2653 assert!(!pool.is_empty());
2654 handle.terminate().await;
2655 }
2656
2657 #[tokio::test(flavor = "multi_thread")]
2660 async fn test_handle_incoming_transactions_hashes() {
2661 reth_tracing::init_test_tracing();
2662
2663 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2664 let client = NoopProvider::default();
2665
2666 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2667 .listener_port(0)
2669 .disable_discovery()
2670 .build(client);
2671
2672 let pool = testing_pool();
2673
2674 let transactions_manager_config = config.transactions_manager_config.clone();
2675 let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2676 .await
2677 .unwrap()
2678 .into_builder()
2679 .transactions(pool.clone(), transactions_manager_config)
2680 .split_with_handle();
2681
2682 let peer_id_1 = PeerId::new([1; 64]);
2683 let eth_version = EthVersion::Eth66;
2684
2685 let txs = vec![TransactionSigned::new_unhashed(
2686 Transaction::Legacy(TxLegacy {
2687 chain_id: Some(4),
2688 nonce: 15u64,
2689 gas_price: 2200000000,
2690 gas_limit: 34811,
2691 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2692 value: U256::from(1234u64),
2693 input: Default::default(),
2694 }),
2695 Signature::new(
2696 U256::from_str(
2697 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2698 )
2699 .unwrap(),
2700 U256::from_str(
2701 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2702 )
2703 .unwrap(),
2704 true,
2705 ),
2706 )];
2707
2708 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2709
2710 let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2711 tx_manager.peers.insert(peer_id_1, peer_1);
2712
2713 assert!(pool.is_empty());
2714
2715 tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2716 peer_id: peer_id_1,
2717 msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2718 txs_hashes.clone(),
2719 )),
2720 });
2721
2722 let req = to_mock_session_rx
2724 .recv()
2725 .await
2726 .expect("peer_1 session should receive request with buffered hashes");
2727 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2728 assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2729
2730 let message: Vec<PooledTransactionVariant> = txs
2731 .into_iter()
2732 .map(|tx| {
2733 PooledTransactionVariant::try_from(tx)
2734 .expect("Failed to convert MockTransaction to PooledTransaction")
2735 })
2736 .collect();
2737
2738 response
2740 .send(Ok(PooledTransactions(message)))
2741 .expect("should send peer_1 response to tx manager");
2742
2743 poll_fn(|cx| {
2745 let _ = tx_manager.poll_unpin(cx);
2746 Poll::Ready(())
2747 })
2748 .await;
2749
2750 assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2753 }
2754
2755 #[tokio::test(flavor = "multi_thread")]
2756 async fn test_handle_incoming_transactions() {
2757 reth_tracing::init_test_tracing();
2758 let net = Testnet::create(3).await;
2759
2760 let mut handles = net.handles();
2761 let handle0 = handles.next().unwrap();
2762 let handle1 = handles.next().unwrap();
2763
2764 drop(handles);
2765 let handle = net.spawn();
2766
2767 let listener0 = handle0.event_listener();
2768
2769 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2770 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2771
2772 let client = NoopProvider::default();
2773 let pool = testing_pool();
2774 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2775 .disable_discovery()
2776 .listener_port(0)
2777 .build(client);
2778 let transactions_manager_config = config.transactions_manager_config.clone();
2779 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2780 .await
2781 .unwrap()
2782 .into_builder()
2783 .transactions(pool.clone(), transactions_manager_config)
2784 .split_with_handle();
2785 tokio::task::spawn(network);
2786
2787 network_handle.update_sync_state(SyncState::Idle);
2788
2789 assert!(!NetworkInfo::is_syncing(&network_handle));
2790
2791 let mut established = listener0.take(2);
2793 while let Some(ev) = established.next().await {
2794 match ev {
2795 NetworkEvent::ActivePeerSession { .. } |
2796 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2797 transactions.on_network_event(ev);
2799 }
2800 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2801 ev => {
2802 error!("unexpected event {ev:?}")
2803 }
2804 }
2805 }
2806 let input = hex!(
2808 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2809 );
2810 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2811 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2812 peer_id: *handle1.peer_id(),
2813 msg: Transactions(vec![signed_tx.clone()]),
2814 });
2815 assert!(transactions
2816 .transactions_by_peers
2817 .get(signed_tx.tx_hash())
2818 .unwrap()
2819 .contains(handle1.peer_id()));
2820
2821 poll_fn(|cx| {
2823 let _ = transactions.poll_unpin(cx);
2824 Poll::Ready(())
2825 })
2826 .await;
2827
2828 assert!(!pool.is_empty());
2829 assert!(pool.get(signed_tx.tx_hash()).is_some());
2830 handle.terminate().await;
2831 }
2832
2833 #[tokio::test(flavor = "multi_thread")]
2834 async fn test_session_closed_cleans_transaction_peer_state() {
2835 let (mut tx_manager, _network) = new_tx_manager().await;
2836 let peer_id = PeerId::new([1; 64]);
2837 let fallback_peer = PeerId::new([2; 64]);
2838 let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2839 let hash_shared = B256::from_slice(&[1; 32]);
2840
2841 tx_manager.peers.insert(peer_id, peer);
2842 buffer_hash_to_tx_fetcher(
2843 &mut tx_manager.transaction_fetcher,
2844 hash_shared,
2845 peer_id,
2846 0,
2847 None,
2848 );
2849 buffer_hash_to_tx_fetcher(
2850 &mut tx_manager.transaction_fetcher,
2851 hash_shared,
2852 fallback_peer,
2853 0,
2854 None,
2855 );
2856 tx_manager.transaction_fetcher.active_peers.insert(peer_id, 1);
2857
2858 tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2859 peer_id,
2860 reason: None,
2861 }));
2862
2863 assert!(!tx_manager.peers.contains_key(&peer_id));
2865 assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2866 assert_eq!(
2868 tx_manager.transaction_fetcher.get_idle_peer_for(hash_shared),
2869 Some(&fallback_peer)
2870 );
2871 }
2872
2873 #[tokio::test(flavor = "multi_thread")]
2874 async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2875 let (mut tx_manager, _network) = new_tx_manager().await;
2876 let peer_id = PeerId::new([1; 64]);
2877 let hash = B256::from_slice(&[1; 32]);
2878
2879 tx_manager.network.update_sync_state(SyncState::Idle);
2880 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2881
2882 let err = PoolError::new(
2883 hash,
2884 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2885 BlobTransactionValidationError::InvalidProof,
2886 )),
2887 );
2888
2889 tx_manager.on_bad_import(err);
2890
2891 assert!(!tx_manager.bad_imports.contains(&hash));
2892 }
2893
2894 #[tokio::test(flavor = "multi_thread")]
2895 async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2896 let (mut tx_manager, _network) = new_tx_manager().await;
2897 let peer_id = PeerId::new([1; 64]);
2898 let hash = B256::from_slice(&[3; 32]);
2899
2900 tx_manager.network.update_sync_state(SyncState::Idle);
2901 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2902
2903 let err = PoolError::new(
2904 hash,
2905 InvalidPoolTransactionError::Eip4844(
2906 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2907 ),
2908 );
2909
2910 tx_manager.on_bad_import(err);
2911
2912 assert!(!tx_manager.bad_imports.contains(&hash));
2913 }
2914
2915 #[tokio::test(flavor = "multi_thread")]
2916 async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2917 let (mut tx_manager, _network) = new_tx_manager().await;
2918 let peer_id = PeerId::new([1; 64]);
2919 let hash = B256::from_slice(&[2; 32]);
2920
2921 tx_manager.network.update_sync_state(SyncState::Idle);
2922 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2923
2924 let err = PoolError::new(
2925 hash,
2926 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2927 );
2928
2929 tx_manager.on_bad_import(err);
2930
2931 assert!(tx_manager.bad_imports.contains(&hash));
2932 }
2933
2934 #[tokio::test(flavor = "multi_thread")]
2935 async fn test_on_get_pooled_transactions_network() {
2936 reth_tracing::init_test_tracing();
2937 let net = Testnet::create(2).await;
2938
2939 let mut handles = net.handles();
2940 let handle0 = handles.next().unwrap();
2941 let handle1 = handles.next().unwrap();
2942
2943 drop(handles);
2944 let handle = net.spawn();
2945
2946 let listener0 = handle0.event_listener();
2947
2948 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2949 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2950
2951 let client = NoopProvider::default();
2952 let pool = testing_pool();
2953 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2954 .disable_discovery()
2955 .listener_port(0)
2956 .build(client);
2957 let transactions_manager_config = config.transactions_manager_config.clone();
2958 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2959 .await
2960 .unwrap()
2961 .into_builder()
2962 .transactions(pool.clone(), transactions_manager_config)
2963 .split_with_handle();
2964 tokio::task::spawn(network);
2965
2966 network_handle.update_sync_state(SyncState::Idle);
2967
2968 assert!(!NetworkInfo::is_syncing(&network_handle));
2969
2970 let mut established = listener0.take(2);
2972 while let Some(ev) = established.next().await {
2973 match ev {
2974 NetworkEvent::ActivePeerSession { .. } |
2975 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2976 transactions.on_network_event(ev);
2977 }
2978 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2979 ev => {
2980 error!("unexpected event {ev:?}")
2981 }
2982 }
2983 }
2984 handle.terminate().await;
2985
2986 let tx = MockTransaction::eip1559();
2987 let _ = transactions
2988 .pool
2989 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
2990 .await;
2991
2992 let request = GetPooledTransactions(vec![*tx.get_hash()]);
2993
2994 let (send, receive) =
2995 oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
2996
2997 transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
2998 peer_id: *handle1.peer_id(),
2999 request,
3000 response: send,
3001 });
3002
3003 match receive.await.unwrap() {
3004 Ok(PooledTransactions(transactions)) => {
3005 assert_eq!(transactions.len(), 1);
3006 }
3007 Err(e) => {
3008 panic!("error: {e:?}");
3009 }
3010 }
3011 }
3012
3013 #[tokio::test]
3017 async fn test_partially_tx_response() {
3018 reth_tracing::init_test_tracing();
3019
3020 let mut tx_manager = new_tx_manager().await.0;
3021 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3022
3023 let peer_id_1 = PeerId::new([1; 64]);
3024 let eth_version = EthVersion::Eth66;
3025
3026 let txs = vec![
3027 TransactionSigned::new_unhashed(
3028 Transaction::Legacy(TxLegacy {
3029 chain_id: Some(4),
3030 nonce: 15u64,
3031 gas_price: 2200000000,
3032 gas_limit: 34811,
3033 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
3034 value: U256::from(1234u64),
3035 input: Default::default(),
3036 }),
3037 Signature::new(
3038 U256::from_str(
3039 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
3040 )
3041 .unwrap(),
3042 U256::from_str(
3043 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
3044 )
3045 .unwrap(),
3046 true,
3047 ),
3048 ),
3049 TransactionSigned::new_unhashed(
3050 Transaction::Eip1559(TxEip1559 {
3051 chain_id: 4,
3052 nonce: 26u64,
3053 max_priority_fee_per_gas: 1500000000,
3054 max_fee_per_gas: 1500000013,
3055 gas_limit: MIN_TRANSACTION_GAS,
3056 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
3057 value: U256::from(3000000000000000000u64),
3058 input: Default::default(),
3059 access_list: Default::default(),
3060 }),
3061 Signature::new(
3062 U256::from_str(
3063 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
3064 )
3065 .unwrap(),
3066 U256::from_str(
3067 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
3068 )
3069 .unwrap(),
3070 true,
3071 ),
3072 ),
3073 ];
3074
3075 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3076
3077 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3078 peer_1.seen_transactions.insert(txs_hashes[0]);
3081 peer_1.seen_transactions.insert(txs_hashes[1]);
3082 tx_manager.peers.insert(peer_id_1, peer_1);
3083
3084 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, 0, None);
3085 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, 0, None);
3086
3087 assert!(tx_fetcher.is_idle(&peer_id_1));
3089 assert_eq!(tx_fetcher.active_peers.len(), 0);
3090
3091 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3093
3094 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3095 assert!(!tx_fetcher.is_idle(&peer_id_1));
3097 assert_eq!(tx_fetcher.active_peers.len(), 1);
3098
3099 let req = to_mock_session_rx
3101 .recv()
3102 .await
3103 .expect("peer_1 session should receive request with buffered hashes");
3104 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3105
3106 let message: Vec<PooledTransactionVariant> = txs
3107 .into_iter()
3108 .take(1)
3109 .map(|tx| {
3110 PooledTransactionVariant::try_from(tx)
3111 .expect("Failed to convert MockTransaction to PooledTransaction")
3112 })
3113 .collect();
3114 response
3116 .send(Ok(PooledTransactions(message)))
3117 .expect("should send peer_1 response to tx manager");
3118 let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3119 unreachable!()
3120 };
3121
3122 assert!(tx_fetcher.is_idle(&peer_id));
3124 assert_eq!(tx_fetcher.active_peers.len(), 0);
3125 assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3127 }
3128
3129 #[tokio::test]
3130 async fn test_max_retries_tx_request() {
3131 reth_tracing::init_test_tracing();
3132
3133 let mut tx_manager = new_tx_manager().await.0;
3134 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3135
3136 let peer_id_1 = PeerId::new([1; 64]);
3137 let peer_id_2 = PeerId::new([2; 64]);
3138 let eth_version = EthVersion::Eth66;
3139 let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3140
3141 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3142 peer_1.seen_transactions.insert(seen_hashes[0]);
3145 peer_1.seen_transactions.insert(seen_hashes[1]);
3146 tx_manager.peers.insert(peer_id_1, peer_1);
3147
3148 let retries = 1;
3151 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[1], peer_id_1, retries, None);
3152 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[0], peer_id_1, retries, None);
3153
3154 assert!(tx_fetcher.is_idle(&peer_id_1));
3156 assert_eq!(tx_fetcher.active_peers.len(), 0);
3157
3158 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3160
3161 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3162
3163 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3164 assert!(!tx_fetcher.is_idle(&peer_id_1));
3166 assert_eq!(tx_fetcher.active_peers.len(), 1);
3167
3168 let req = to_mock_session_rx
3170 .recv()
3171 .await
3172 .expect("peer_1 session should receive request with buffered hashes");
3173 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3174 let GetPooledTransactions(hashes) = request;
3175
3176 let hashes = hashes.into_iter().collect::<B256Set>();
3177
3178 assert_eq!(hashes, seen_hashes.into_iter().collect::<B256Set>());
3179
3180 response
3182 .send(Err(RequestError::BadResponse))
3183 .expect("should send peer_1 response to tx manager");
3184 let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3185 unreachable!()
3186 };
3187
3188 assert!(tx_fetcher.is_idle(&peer_id));
3190 assert_eq!(tx_fetcher.active_peers.len(), 0);
3191 assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3193
3194 let (peer_2, mut to_mock_session_rx) = new_mock_session(peer_id_2, eth_version);
3195 tx_manager.peers.insert(peer_id_2, peer_2);
3196
3197 let msg =
3199 NewPooledTransactionHashes::Eth66(NewPooledTransactionHashes66(seen_hashes.to_vec()));
3200 tx_manager.on_new_pooled_transaction_hashes(peer_id_2, msg);
3201
3202 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3203
3204 assert_eq!(tx_fetcher.active_peers.len(), 1);
3206
3207 assert_eq!(tx_fetcher.num_all_hashes(), 2);
3209 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3211
3212 let req = to_mock_session_rx
3214 .recv()
3215 .await
3216 .expect("peer_2 session should receive request with buffered hashes");
3217 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3218
3219 response
3221 .send(Err(RequestError::BadResponse))
3222 .expect("should send peer_2 response to tx manager");
3223 let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3224
3225 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3228 assert_eq!(tx_fetcher.active_peers.len(), 0);
3229 }
3230
3231 #[test]
3232 fn test_direct_propagation_transaction_uses_2718_size() {
3233 let mut tx_gen = TransactionGenerator::new(rand::rng());
3234 let tx = tx_gen.gen_eip1559();
3235 let expected_size = tx.encode_2718_len();
3236
3237 let tx = PropagateTransaction::new(tx);
3238
3239 assert_eq!(tx.propagation_size(), expected_size);
3240 }
3241
3242 #[test]
3243 fn test_transaction_builder_empty() {
3244 let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3245 assert!(builder.is_empty());
3246
3247 let mut tx_gen = TransactionGenerator::new(rand::rng());
3248 let tx =
3249 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3250 builder.push(&tx);
3251 assert!(!builder.is_empty());
3252
3253 let txs = builder.build();
3254 assert!(txs.full.is_none());
3255 let txs = txs.pooled.unwrap();
3256 assert_eq!(txs.len(), 1);
3257 }
3258
3259 #[test]
3260 fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3261 let mut tx_gen = TransactionGenerator::new(rand::rng());
3262 let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3263 let pooled = PropagatePooledTransactionEncoder::new(tx);
3264
3265 let mut pooled_encoded = Vec::new();
3266 pooled.encode(&mut pooled_encoded);
3267 assert_eq!(pooled.length(), pooled_encoded.len());
3268
3269 let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3270 let mut first_encoded = Vec::new();
3271 broadcast.encode(&mut first_encoded);
3272 let mut second_encoded = Vec::new();
3273 broadcast.encode(&mut second_encoded);
3274 assert_eq!(first_encoded, second_encoded);
3275
3276 let mut encoded = first_encoded.as_slice();
3277 let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3278 assert_eq!(decoded.len(), 1);
3279 assert!(encoded.is_empty());
3280 }
3281
3282 #[test]
3283 fn test_transaction_builder_large() {
3284 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3285 assert!(builder.is_empty());
3286
3287 let mut tx_gen = TransactionGenerator::new(rand::rng());
3288 let mut tx = tx_gen.gen_eip1559_pooled();
3289 tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3291 let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3292 builder.push(&tx);
3293 assert!(!builder.is_empty());
3294
3295 let txs = builder.clone().build();
3296 assert!(txs.pooled.is_none());
3297 let txs = txs.full.unwrap();
3298 assert_eq!(txs.len(), 1);
3299
3300 builder.push(&tx);
3301
3302 let txs = builder.clone().build();
3303 let pooled = txs.pooled.unwrap();
3304 assert_eq!(pooled.len(), 1);
3305 let txs = txs.full.unwrap();
3306 assert_eq!(txs.len(), 1);
3307 }
3308
3309 #[test]
3310 fn test_transaction_builder_eip4844() {
3311 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3312 assert!(builder.is_empty());
3313
3314 let mut tx_gen = TransactionGenerator::new(rand::rng());
3315 let tx =
3316 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3317 builder.push(&tx);
3318 assert!(!builder.is_empty());
3319
3320 let txs = builder.clone().build();
3321 assert!(txs.full.is_none());
3322 let txs = txs.pooled.unwrap();
3323 assert_eq!(txs.len(), 1);
3324
3325 let tx =
3326 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3327 builder.push(&tx);
3328
3329 let txs = builder.clone().build();
3330 let pooled = txs.pooled.unwrap();
3331 assert_eq!(pooled.len(), 1);
3332 let txs = txs.full.unwrap();
3333 assert_eq!(txs.len(), 1);
3334 }
3335
3336 #[tokio::test]
3337 async fn test_propagate_full() {
3338 reth_tracing::init_test_tracing();
3339
3340 let (mut tx_manager, network) = new_eth_tx_manager().await;
3341 let peer_id = PeerId::random();
3342
3343 network.handle().update_sync_state(SyncState::Idle);
3345
3346 let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3348
3349 let session_info = SessionInfo {
3350 peer_id,
3351 remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3352 client_version: Arc::from(""),
3353 capabilities: Arc::new(vec![].into()),
3354 status: Arc::new(Default::default()),
3355 version: EthVersion::Eth68,
3356 peer_kind: PeerKind::Basic,
3357 };
3358 let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3359 tx_manager
3360 .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3361 let mut propagate = vec![];
3362 let mut tx_gen = TransactionGenerator::new(rand::rng());
3363 let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3364 propagate.push(eip1559_tx.clone());
3365 let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3366 propagate.push(eip4844_tx.clone());
3367
3368 let propagated = tx_manager.propagate_transactions(
3369 propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3370 PropagationMode::Basic,
3371 );
3372 assert_eq!(propagated.len(), 2);
3373 let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3374 assert_eq!(prop_txs.len(), 1);
3375 assert!(prop_txs[0].is_full());
3376
3377 let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3378 assert_eq!(prop_txs.len(), 1);
3379 assert!(prop_txs[0].is_hash());
3380
3381 let peer = tx_manager.peers.get(&peer_id).unwrap();
3382 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3383 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3384 peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3385
3386 let propagated = tx_manager.propagate_transactions(
3388 propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3389 PropagationMode::Basic,
3390 );
3391 assert!(propagated.is_empty());
3392 }
3393
3394 #[test]
3395 fn test_eth72_hashes_builder_sets_cell_mask_for_blob_txs() {
3396 let mut tx_gen = TransactionGenerator::new(rand::rng());
3397
3398 let mut builder = PooledTransactionsHashesBuilder::new(EthVersion::Eth72);
3400 builder.push(&PropagateTransaction::pool_tx(valid_eth_pool_transaction(
3401 tx_gen.gen_eip1559_pooled(),
3402 )));
3403 let msg = builder.build();
3404 assert_eq!(msg.as_eth72().unwrap().cell_mask, None);
3405
3406 let mut builder = PooledTransactionsHashesBuilder::new(EthVersion::Eth72);
3408 builder.push(&PropagateTransaction::pool_tx(valid_eth_pool_transaction(
3409 tx_gen.gen_eip1559_pooled(),
3410 )));
3411 builder.push_pooled(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3412 let msg = builder.build();
3413 assert_eq!(
3414 msg.as_eth72().unwrap().cell_mask,
3415 Some(NewPooledTransactionHashes72::ALL_CELLS_MASK)
3416 );
3417 }
3418
3419 #[tokio::test]
3420 async fn test_truncated_hash_announcement_not_marked_seen() {
3421 reth_tracing::init_test_tracing();
3422
3423 let (mut tx_manager, network) = new_eth_tx_manager().await;
3424 tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3426
3427 network.handle().update_sync_state(SyncState::Idle);
3429
3430 let peer_id = PeerId::random();
3431 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3432 tx_manager.peers.insert(peer_id, peer);
3433
3434 let mut tx_gen = TransactionGenerator::new(rand::rng());
3436 let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3437 .map(|nonce| {
3438 valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3439 })
3440 .collect::<Vec<_>>();
3441 let last_sent = *txs[txs.len() - 2].hash();
3442 let truncated = *txs[txs.len() - 1].hash();
3443
3444 let propagated = tx_manager.propagate_transactions(
3445 txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3446 PropagationMode::Basic,
3447 );
3448
3449 assert!(propagated.get(&truncated).is_none());
3451 let peer = tx_manager.peers.get(&peer_id).unwrap();
3452 assert!(!peer.seen_transactions.contains(&truncated));
3453 assert!(peer.seen_transactions.contains(&last_sent));
3454 }
3455
3456 #[tokio::test]
3457 async fn test_propagate_pending_txs_while_initially_syncing() {
3458 reth_tracing::init_test_tracing();
3459
3460 let (mut tx_manager, network) = new_eth_tx_manager().await;
3461 let peer_id = PeerId::random();
3462
3463 network.handle().update_sync_state(SyncState::Syncing);
3465 assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3466
3467 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3469 tx_manager.peers.insert(peer_id, peer);
3470
3471 let mut tx_gen = TransactionGenerator::new(rand::rng());
3472 let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3473 let tx_hash = *tx.hash();
3474 tx_manager
3475 .pool
3476 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3477 .await
3478 .expect("transaction should be accepted into the pool");
3479
3480 tx_manager.on_new_pending_transactions(vec![tx_hash]);
3481
3482 let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3483 assert!(peer.seen_transactions.contains(&tx_hash));
3484 }
3485
3486 #[tokio::test]
3487 async fn test_relaxed_filter_ignores_unknown_tx_types() {
3488 reth_tracing::init_test_tracing();
3489
3490 let transactions_manager_config = TransactionsManagerConfig::default();
3491
3492 let propagation_policy = TransactionPropagationKind::default();
3493 let announcement_policy = RelaxedEthAnnouncementFilter::default();
3494
3495 let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3496
3497 let pool = testing_pool();
3498 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
3499 let client = NoopProvider::default();
3500
3501 let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3502 .listener_port(0)
3503 .disable_discovery()
3504 .build(client.clone());
3505
3506 let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3507 let (to_tx_manager_tx, from_network_rx) =
3508 reth_metrics::common::mpsc::memory_bounded_channel::<
3509 NetworkTransactionEvent<EthNetworkPrimitives>,
3510 >(
3511 crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3512 "test_tx_channel",
3513 );
3514 network_manager.set_transactions(to_tx_manager_tx);
3515 let network_handle = network_manager.handle().clone();
3516 let network_service_handle = tokio::spawn(network_manager);
3517
3518 let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3519 network_handle.clone(),
3520 pool.clone(),
3521 from_network_rx,
3522 transactions_manager_config,
3523 policy_bundle,
3524 );
3525
3526 let peer_id = PeerId::random();
3527 let eth_version = EthVersion::Eth68;
3528 let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3529 tx_manager.peers.insert(peer_id, mock_peer_metadata);
3530
3531 let mut tx_factory = MockTransactionFactory::default();
3532
3533 let valid_known_tx = tx_factory.create_eip1559();
3534 let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3535
3536 let known_tx_hash = *known_tx_signed.hash();
3537 let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3538 let known_tx_size = known_tx_signed.encoded_length();
3539
3540 let unknown_tx_hash = B256::random();
3541 let unknown_tx_type_byte = 0xff_u8;
3542 let unknown_tx_size = 150;
3543
3544 let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3545 types: vec![known_tx_type_byte, unknown_tx_type_byte],
3546 sizes: vec![known_tx_size, unknown_tx_size],
3547 hashes: vec![known_tx_hash, unknown_tx_hash],
3548 });
3549
3550 tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3551
3552 poll_fn(|cx| {
3553 let _ = tx_manager.poll_unpin(cx);
3554 Poll::Ready(())
3555 })
3556 .await;
3557
3558 let mut requested_hashes_in_getpooled = B256Set::default();
3559 let mut unexpected_request_received = false;
3560
3561 match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3562 .await
3563 {
3564 Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3565 let GetPooledTransactions(hashes) = request;
3566 for hash in hashes {
3567 requested_hashes_in_getpooled.insert(hash);
3568 }
3569 let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3570 }
3571 Ok(Some(other_request)) => {
3572 tracing::error!(?other_request, "Received unexpected PeerRequest type");
3573 unexpected_request_received = true;
3574 }
3575 Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3576 Err(_timeout_err) => {
3577 tracing::info!("Timeout: No GetPooledTransactions request received.")
3578 }
3579 }
3580
3581 assert!(
3582 requested_hashes_in_getpooled.contains(&known_tx_hash),
3583 "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3584 );
3585 assert!(
3586 !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3587 "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3588 );
3589 assert!(
3590 !unexpected_request_received,
3591 "An unexpected P2P request was received by the mock peer."
3592 );
3593
3594 network_service_handle.abort();
3595 }
3596}