1use alloy_consensus::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 {
548 for peer_id in peers {
549 self.report_peer_bad_transactions(peer_id);
550 }
551 }
552 return
553 }
554
555 if !err.is_bad_transaction() || self.network.is_syncing() {
557 return
558 }
559 if let Some(peers) = peers {
562 for peer_id in peers {
563 self.report_peer_bad_transactions(peer_id);
564 }
565 }
566 self.metrics.bad_imports.increment(1);
567 self.bad_imports.insert(err.hash);
568 }
569
570 fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
574 let info = &self.pending_pool_imports_info;
576 let max_pending_pool_imports = info.max_pending_pool_imports;
577 let has_capacity_wrt_pending_pool_imports =
578 |divisor| info.has_capacity(max_pending_pool_imports / divisor);
579
580 self.transaction_fetcher
581 .on_fetch_pending_hashes(&self.peers, has_capacity_wrt_pending_pool_imports)
582 }
583
584 fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
585 let kind = match req_err {
586 RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
587 RequestError::Timeout => ReputationChangeKind::Timeout,
588 RequestError::ChannelClosed | RequestError::ConnectionDropped => {
589 return
591 }
592 RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
593 };
594 self.report_peer(peer_id, kind);
595 }
596
597 #[inline]
598 fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
599 let metrics = &self.metrics;
600
601 let TxManagerPollDurations {
602 acc_network_events,
603 acc_pending_imports,
604 acc_tx_events,
605 acc_imported_txns,
606 acc_fetch_events,
607 acc_pending_fetch,
608 acc_cmds,
609 } = poll_durations;
610
611 metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
613 metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
615 metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
616 metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
617 metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
618 metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
619 metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
620 metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
621 }
622}
623
624impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
625 fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
627 for res in batch_results {
628 match res {
629 Ok(AddedTransactionOutcome { hash, .. }) => {
630 self.on_good_import(hash);
631 }
632 Err(err) => {
633 self.on_bad_import(err);
634 }
635 }
636 }
637 }
638
639 fn on_new_pooled_transaction_hashes(
641 &mut self,
642 peer_id: PeerId,
643 msg: NewPooledTransactionHashes,
644 ) {
645 if self.network.is_initially_syncing() {
647 return
648 }
649 if self.network.tx_gossip_disabled() {
650 return
651 }
652
653 let Some(peer) = self.peers.get_mut(&peer_id) else {
655 trace!(
656 peer_id = format!("{peer_id:#}"),
657 ?msg,
658 "discarding announcement from inactive peer"
659 );
660
661 return
662 };
663 let client = peer.client_version.clone();
664
665 let mut count_txns_already_seen_by_peer = 0;
667 for tx in msg.iter_hashes().copied() {
668 if !peer.seen_transactions.insert(tx) {
669 count_txns_already_seen_by_peer += 1;
670 }
671 }
672 if count_txns_already_seen_by_peer > 0 {
673 self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
678 self.metrics
679 .occurrences_hash_already_seen_by_peer
680 .increment(count_txns_already_seen_by_peer);
681
682 trace!(target: "net::tx",
683 %count_txns_already_seen_by_peer,
684 peer_id=format!("{peer_id:#}"),
685 ?client,
686 "Peer sent hashes that have already been marked as seen by peer"
687 );
688
689 self.report_already_seen(peer_id);
690 }
691
692 if msg.is_empty() {
694 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
695 return;
696 }
697
698 let original_len = msg.len();
699 let mut partially_valid_msg = msg.dedup();
700
701 if partially_valid_msg.len() != original_len {
702 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
703 }
704
705 partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
707
708 let mut should_report_peer = false;
715 let mut tx_types_counter = TxTypesCounter::default();
716
717 let has_eth68_metadata = partially_valid_msg
718 .msg_version()
719 .expect("partially valid announcement should have a version")
720 .has_eth68_metadata();
721
722 partially_valid_msg.retain(|tx_hash, metadata_ref_mut| {
723 let (ty_byte, size_val) = match *metadata_ref_mut {
724 Some((ty, size)) => {
725 if !has_eth68_metadata {
726 should_report_peer = true;
727 }
728 (ty, size)
729 }
730 None => {
731 if has_eth68_metadata {
732 should_report_peer = true;
733 return false;
734 }
735 (0u8, 0)
736 }
737 };
738
739 if has_eth68_metadata && let Some((actual_ty_byte, _)) = *metadata_ref_mut {
740 match TxType::try_from(actual_ty_byte) {
741 Ok(parsed_tx_type) => tx_types_counter.increase_by_tx_type(parsed_tx_type),
742 Err(_) => tx_types_counter.increase_other(),
743 }
744 }
745
746 let decision = self
747 .policies
748 .announcement_filter()
749 .decide_on_announcement(ty_byte, tx_hash, size_val);
750
751 match decision {
752 AnnouncementAcceptance::Accept => true,
753 AnnouncementAcceptance::Ignore => false,
754 AnnouncementAcceptance::Reject { penalize_peer } => {
755 if penalize_peer {
756 should_report_peer = true;
757 }
758 false
759 }
760 }
761 });
762
763 if has_eth68_metadata {
764 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
765 }
766
767 if should_report_peer {
768 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
769 }
770
771 let hashes_count_pre_pool_filter = partially_valid_msg.len();
779 self.pool.retain_unknown(&mut partially_valid_msg);
780 if hashes_count_pre_pool_filter > partially_valid_msg.len() {
781 let already_known_hashes_count =
782 hashes_count_pre_pool_filter - partially_valid_msg.len();
783 self.metrics
784 .occurrences_hashes_already_in_pool
785 .increment(already_known_hashes_count as u64);
786 }
787
788 if partially_valid_msg.is_empty() {
789 return
791 }
792
793 let mut valid_announcement_data =
794 ValidAnnouncementData::from_partially_valid_data(partially_valid_msg);
795
796 if valid_announcement_data.is_empty() {
797 return
799 }
800
801 let bad_imports = &self.bad_imports;
808 self.transaction_fetcher.filter_unseen_and_pending_hashes(
809 &mut valid_announcement_data,
810 |hash| bad_imports.contains(hash),
811 &peer_id,
812 &client,
813 );
814
815 if valid_announcement_data.is_empty() {
816 return
818 }
819
820 trace!(target: "net::tx::propagation",
821 peer_id=format!("{peer_id:#}"),
822 hashes_len=valid_announcement_data.len(),
823 hashes=?valid_announcement_data.keys(),
824 msg_version=%valid_announcement_data.msg_version(),
825 client_version=%client,
826 "received previously unseen and pending hashes in announcement from peer"
827 );
828
829 if !self.transaction_fetcher.is_idle(&peer_id) {
832 let msg_version = valid_announcement_data.msg_version();
834 let (hashes, _version) = valid_announcement_data.into_request_hashes();
835
836 trace!(target: "net::tx",
837 peer_id=format!("{peer_id:#}"),
838 hashes=?*hashes,
839 %msg_version,
840 %client,
841 "buffering hashes announced by busy peer"
842 );
843
844 self.transaction_fetcher.buffer_hashes(hashes, Some(peer_id));
845
846 return
847 }
848
849 let mut hashes_to_request =
850 RequestTxHashes::with_capacity(valid_announcement_data.len() / 4);
851 let surplus_hashes =
852 self.transaction_fetcher.pack_request(&mut hashes_to_request, valid_announcement_data);
853
854 if !surplus_hashes.is_empty() {
855 trace!(target: "net::tx",
856 peer_id=format!("{peer_id:#}"),
857 surplus_hashes=?*surplus_hashes,
858 %client,
859 "some hashes in announcement from peer didn't fit in `GetPooledTransactions` request, buffering surplus hashes"
860 );
861
862 self.transaction_fetcher.buffer_hashes(surplus_hashes, Some(peer_id));
863 }
864
865 trace!(target: "net::tx",
866 peer_id=format!("{peer_id:#}"),
867 hashes=?*hashes_to_request,
868 %client,
869 "sending hashes in `GetPooledTransactions` request to peer's session"
870 );
871
872 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
876 if let Some(failed_to_request_hashes) =
877 self.transaction_fetcher.request_transactions_from_peer(hashes_to_request, peer)
878 {
879 let conn_eth_version = peer.version;
880
881 trace!(target: "net::tx",
882 peer_id=format!("{peer_id:#}"),
883 failed_to_request_hashes=?*failed_to_request_hashes,
884 %conn_eth_version,
885 %client,
886 "sending `GetPooledTransactions` request to peer's session failed, buffering hashes"
887 );
888 self.transaction_fetcher.buffer_hashes(failed_to_request_hashes, Some(peer_id));
889 }
890 }
891}
892
893impl<Pool, N> TransactionsManager<Pool, N>
894where
895 Pool: TransactionPool + Unpin + 'static,
896 N: NetworkPrimitives<
897 BroadcastedTransaction: SignedTransaction,
898 PooledTransaction: SignedTransaction,
899 > + Unpin,
900 Pool::Transaction:
901 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
902{
903 fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
915 if self.network.tx_gossip_disabled() {
919 return
920 }
921
922 trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
923
924 self.propagate_all(hashes);
925 }
926
927 fn propagate_full_transactions_to_peer(
931 &mut self,
932 txs: Vec<TxHash>,
933 peer_id: PeerId,
934 propagation_mode: PropagationMode,
935 ) -> Option<PropagatedTransactions> {
936 let peer = self.peers.get_mut(&peer_id)?;
937 trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
938 let mut propagated = PropagatedTransactions::default();
939
940 let mut full_transactions = FullTransactionsBuilder::new(peer.version);
942
943 let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
944
945 if propagation_mode.is_forced() {
946 full_transactions.extend(to_propagate);
948 } else {
949 for tx in to_propagate {
952 if !peer.seen_transactions.contains(tx.tx_hash()) {
953 full_transactions.push(&tx);
955 }
956 }
957 }
958
959 if full_transactions.is_empty() {
960 return None
962 }
963
964 let PropagateTransactions { pooled, full } = full_transactions.build();
965
966 if let Some(new_pooled_hashes) = pooled {
968 for hash in new_pooled_hashes.iter_hashes().copied() {
969 propagated.record(hash, PropagateKind::Hash(peer_id));
970 peer.seen_transactions.insert(hash);
972 }
973
974 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
976 }
977
978 if let Some(new_full_transactions) = full {
980 for hash in new_full_transactions.iter_hashes() {
981 propagated.record(*hash, PropagateKind::Full(peer_id));
982 peer.seen_transactions.insert(*hash);
984 }
985
986 self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
988 }
989
990 self.metrics.propagated_transactions.increment(propagated.len() as u64);
992
993 Some(propagated)
994 }
995
996 fn propagate_hashes_to(
1000 &mut self,
1001 hashes: Vec<TxHash>,
1002 peer_id: PeerId,
1003 propagation_mode: PropagationMode,
1004 ) {
1005 trace!(target: "net::tx", "Start propagating transactions as hashes");
1006
1007 let propagated = {
1010 let Some(peer) = self.peers.get_mut(&peer_id) else {
1011 return
1013 };
1014
1015 let to_propagate =
1016 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
1017
1018 let mut propagated = PropagatedTransactions::default();
1019
1020 let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
1022
1023 if propagation_mode.is_forced() {
1024 hashes.extend(to_propagate)
1025 } else {
1026 for tx in to_propagate {
1027 if !peer.seen_transactions.contains(tx.tx_hash()) {
1028 hashes.push(&tx);
1030 }
1031 }
1032 }
1033
1034 let new_pooled_hashes = hashes.build();
1035
1036 if new_pooled_hashes.is_empty() {
1037 return
1039 }
1040
1041 for hash in new_pooled_hashes.iter_hashes().copied() {
1042 propagated.record(hash, PropagateKind::Hash(peer_id));
1043 peer.seen_transactions.insert(hash);
1044 }
1045
1046 trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
1047
1048 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1050
1051 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1053
1054 propagated
1055 };
1056
1057 self.pool.on_propagated(propagated);
1059 }
1060
1061 fn propagate_transactions(
1068 &mut self,
1069 to_propagate: Vec<PropagateTransaction>,
1070 propagation_mode: PropagationMode,
1071 ) -> PropagatedTransactions {
1072 let mut propagated = PropagatedTransactions::default();
1073 if self.network.tx_gossip_disabled() {
1074 return propagated
1075 }
1076
1077 let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1079
1080 let mut num_full_peers = 0;
1082 for (peer_id, peer) in &mut self.peers {
1083 if !self.policies.propagation_policy().can_propagate(peer) {
1084 continue
1086 }
1087
1088 let mut builder = if num_full_peers < max_num_full {
1090 num_full_peers += 1;
1091 PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1092 } else {
1093 PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1094 };
1095
1096 if propagation_mode.is_forced() {
1099 for tx in &to_propagate {
1100 peer.seen_transactions.insert(*tx.tx_hash());
1101 builder.push(tx);
1102 }
1103 } else {
1104 for tx in &to_propagate {
1108 if peer.seen_transactions.insert(*tx.tx_hash()) {
1110 builder.push(tx);
1111 }
1112 }
1113 }
1114
1115 if builder.is_empty() {
1116 trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1117 continue
1118 }
1119
1120 let PropagateTransactions { pooled, full } = builder.build();
1121
1122 if let Some(mut new_pooled_hashes) = pooled {
1124 if new_pooled_hashes.len() >
1128 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1129 {
1130 for hash in new_pooled_hashes
1133 .iter_hashes()
1134 .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1135 {
1136 peer.seen_transactions.remove(hash);
1137 }
1138 new_pooled_hashes.truncate(
1139 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1140 );
1141 }
1142
1143 for hash in new_pooled_hashes.iter_hashes().copied() {
1144 propagated.record(hash, PropagateKind::Hash(*peer_id));
1145 }
1146
1147 trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1148
1149 self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1151 }
1152
1153 if let Some(new_full_transactions) = full {
1155 for hash in new_full_transactions.iter_hashes() {
1156 propagated.record(*hash, PropagateKind::Full(*peer_id));
1157 }
1158
1159 trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1160
1161 self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1163 }
1164 }
1165
1166 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1168
1169 propagated
1170 }
1171
1172 fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1177 if self.peers.is_empty() {
1178 return
1180 }
1181 let propagated = self.propagate_transactions(
1182 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1183 PropagationMode::Basic,
1184 );
1185
1186 self.pool.on_propagated(propagated);
1188 }
1189
1190 fn on_get_pooled_transactions(
1192 &mut self,
1193 peer_id: PeerId,
1194 request: GetPooledTransactions,
1195 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1196 ) {
1197 if self.network.tx_gossip_disabled() {
1199 let _ = response.send(Ok(PooledTransactions::default()));
1200 return
1201 }
1202 if let Some(peer) = self.peers.get_mut(&peer_id) {
1203 let transactions = self.pool.get_pooled_transaction_elements(
1204 request.0,
1205 GetPooledTransactionLimit::ResponseSizeSoftLimit(
1206 self.transaction_fetcher.info.soft_limit_byte_size_pooled_transactions_response,
1207 ),
1208 );
1209 trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1210
1211 peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1214
1215 let resp = PooledTransactions(transactions);
1216 let _ = response.send(Ok(resp));
1217 }
1218 }
1219
1220 fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1222 match cmd {
1223 TransactionsCommand::PropagateHash(hash) => {
1224 self.on_new_pending_transactions(vec![hash])
1225 }
1226 TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1227 self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1228 }
1229 TransactionsCommand::GetActivePeers(tx) => {
1230 let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1231 tx.send(peers).ok();
1232 }
1233 TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1234 if let Some(propagated) =
1235 self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1236 {
1237 self.pool.on_propagated(propagated);
1238 }
1239 }
1240 TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1241 TransactionsCommand::BroadcastTransactions(txs) => {
1242 let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1243 self.pool.on_propagated(propagated);
1244 }
1245 TransactionsCommand::GetTransactionHashes { peers, tx } => {
1246 let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1247 for peer_id in peers {
1248 let hashes = self
1249 .peers
1250 .get(&peer_id)
1251 .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1252 .unwrap_or_default();
1253 res.insert(peer_id, hashes);
1254 }
1255 tx.send(res).ok();
1256 }
1257 TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1258 let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1259 peer_request_sender.send(sender).ok();
1260 }
1261 }
1262 }
1263
1264 fn handle_peer_session(
1268 &mut self,
1269 info: SessionInfo,
1270 messages: PeerRequestSender<PeerRequest<N>>,
1271 ) {
1272 let SessionInfo { peer_id, client_version, version, .. } = info;
1273
1274 let peer = PeerMetadata::<N>::new(
1276 messages,
1277 version,
1278 client_version,
1279 self.config.max_transactions_seen_by_peer_history,
1280 info.peer_kind,
1281 );
1282 let peer = match self.peers.entry(peer_id) {
1283 Entry::Occupied(mut entry) => {
1284 entry.insert(peer);
1285 entry.into_mut()
1286 }
1287 Entry::Vacant(entry) => entry.insert(peer),
1288 };
1289
1290 self.policies.propagation_policy_mut().on_session_established(peer);
1291
1292 if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1296 trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1297 return
1298 }
1299
1300 let pooled_txs = self.pool.pooled_transactions_max(
1302 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1303 );
1304 if pooled_txs.is_empty() {
1305 trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1306 return;
1307 }
1308
1309 let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1311 for pooled_tx in pooled_txs {
1312 peer.seen_transactions.insert(*pooled_tx.hash());
1313 msg_builder.push_pooled(pooled_tx);
1314 }
1315
1316 debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1317 let msg = msg_builder.build();
1318 self.network.send_transactions_hashes(peer_id, msg);
1319 }
1320
1321 fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1323 match event_result {
1324 NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1325 self.on_peer_session_closed(&peer_id);
1326 }
1327 NetworkEvent::ActivePeerSession { info, messages } => {
1328 self.handle_peer_session(info, messages);
1330 }
1331 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1332 let peer_id = info.peer_id;
1333 let messages = match self.peers.get(&peer_id) {
1335 Some(p) => p.request_tx.clone(),
1336 None => {
1337 debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1338 return;
1339 }
1340 };
1341 self.handle_peer_session(info, messages);
1342 }
1343 _ => {}
1344 }
1345 }
1346
1347 fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1349 if self.config.ingress_policy.allows_all() {
1350 return true;
1351 }
1352 let Some(peer) = self.peers.get(peer_id) else {
1353 return false;
1354 };
1355 self.config.ingress_policy.allows(peer.peer_kind())
1356 }
1357
1358 fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1360 match event {
1361 NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1362 if !self.accepts_incoming_from(&peer_id) {
1363 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1364 return;
1365 }
1366
1367 let has_blob_txs = msg.has_eip4844();
1371
1372 let non_blob_txs = msg
1373 .into_iter()
1374 .map(N::PooledTransaction::try_from)
1375 .filter_map(Result::ok)
1376 .collect();
1377
1378 self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1379
1380 if has_blob_txs {
1381 debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1382 self.report_peer_bad_transactions(peer_id);
1383 }
1384 }
1385 NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1386 if !self.accepts_incoming_from(&peer_id) {
1387 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1388 return;
1389 }
1390 self.on_new_pooled_transaction_hashes(peer_id, msg)
1391 }
1392 NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1393 self.on_get_pooled_transactions(peer_id, request, response)
1394 }
1395 NetworkTransactionEvent::GetTransactionsHandle(response) => {
1396 let _ = response.send(Some(self.handle()));
1397 }
1398 }
1399 }
1400
1401 fn import_transactions(
1403 &mut self,
1404 peer_id: PeerId,
1405 transactions: PooledTransactions<N::PooledTransaction>,
1406 source: TransactionSource,
1407 ) {
1408 if self.network.is_initially_syncing() {
1410 return
1411 }
1412 if self.network.tx_gossip_disabled() {
1413 return
1414 }
1415
1416 if !self.has_capacity_for_pending_pool_imports() {
1418 return
1419 }
1420
1421 let mut transactions = transactions.0;
1422
1423 let capacity = self.remaining_pool_import_capacity();
1427 if transactions.len() > capacity {
1428 let skipped = transactions.len() - capacity;
1429 transactions.truncate(capacity);
1430 self.metrics
1431 .skipped_transactions_pending_pool_imports_at_capacity
1432 .increment(skipped as u64);
1433 trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
1434 }
1435
1436 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
1437 let client_version = peer.client_version.clone();
1438
1439 let start = Instant::now();
1440
1441 self.transaction_fetcher
1443 .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1444
1445 let mut num_already_seen_by_peer = 0;
1450 for tx in &transactions {
1451 if source.is_broadcast() && !peer.seen_transactions.insert(*tx.tx_hash()) {
1452 num_already_seen_by_peer += 1;
1453 }
1454 }
1455
1456 let mut has_bad_transactions = false;
1458
1459 transactions.retain(|tx| {
1462 if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1463 let peers = entry.get_mut();
1464 if !peers.contains(&peer_id) {
1465 peers.push(peer_id);
1466 }
1467 return false
1468 }
1469 if self.bad_imports.contains(tx.tx_hash()) {
1470 trace!(target: "net::tx",
1471 peer_id=format!("{peer_id:#}"),
1472 hash=%tx.tx_hash(),
1473 %client_version,
1474 "received a known bad transaction from peer"
1475 );
1476 has_bad_transactions = true;
1477 return false;
1478 }
1479 true
1480 });
1481
1482 let txns_count_pre_pool_filter = transactions.len();
1484 self.pool.retain_unknown(&mut transactions);
1485 if txns_count_pre_pool_filter > transactions.len() {
1486 let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1487 self.metrics
1488 .occurrences_transactions_already_in_pool
1489 .increment(already_known_txns_count as u64);
1490 }
1491
1492 let txs_len = transactions.len();
1493
1494 let recover = |tx| {
1495 let recovered = if let Some(cache) = &self.sender_recovery_cache {
1496 Pool::Transaction::try_recover_with_cache(tx, cache)
1497 } else {
1498 Pool::Transaction::try_recover(tx)
1499 };
1500 match recovered {
1501 Ok(tx) => Some(tx),
1502 Err(badtx) => {
1503 trace!(target: "net::tx",
1504 peer_id=format!("{peer_id:#}"),
1505 hash=%badtx.tx_hash(),
1506 client_version=%client_version,
1507 "failed ecrecovery for transaction"
1508 );
1509 None
1510 }
1511 }
1512 };
1513
1514 let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1515
1516 has_bad_transactions |= new_txs.len() != txs_len;
1517
1518 for tx in &new_txs {
1520 self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1521 }
1522
1523 if !new_txs.is_empty() {
1526 let pool = self.pool.clone();
1527 let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1529 metric_pending_pool_imports.increment(new_txs.len() as f64);
1530
1531 self.pending_pool_imports_info
1533 .pending_pool_imports
1534 .fetch_add(new_txs.len(), Ordering::Relaxed);
1535 let tx_manager_info_pending_pool_imports =
1536 self.pending_pool_imports_info.pending_pool_imports.clone();
1537
1538 trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1539 let import = Box::pin(async move {
1540 let added = new_txs.len();
1541 let res = pool.add_external_transactions(new_txs).await;
1542
1543 metric_pending_pool_imports.decrement(added as f64);
1545 tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1547
1548 res
1549 });
1550
1551 self.pool_imports.push(import);
1552 }
1553
1554 if num_already_seen_by_peer > 0 {
1555 self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1556 self.metrics
1557 .occurrences_of_transaction_already_seen_by_peer
1558 .increment(num_already_seen_by_peer);
1559 trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1560 }
1561
1562 if has_bad_transactions {
1563 self.report_peer_bad_transactions(peer_id)
1565 }
1566
1567 if num_already_seen_by_peer > 0 {
1568 self.report_already_seen(peer_id);
1569 }
1570
1571 self.metrics.pool_import_prepare_duration.record(start.elapsed());
1572 }
1573
1574 fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1576 match fetch_event {
1577 FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => {
1578 self.import_transactions(peer_id, transactions, TransactionSource::Response);
1579 if report_peer {
1580 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1581 }
1582 }
1583 FetchEvent::FetchError { peer_id, error } => {
1584 trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1585 self.on_request_error(peer_id, error);
1586 }
1587 FetchEvent::EmptyResponse { peer_id } => {
1588 trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1589 }
1590 }
1591 }
1592}
1593
1594impl<
1602 Pool: TransactionPool + Unpin + 'static,
1603 N: NetworkPrimitives<
1604 BroadcastedTransaction: SignedTransaction,
1605 PooledTransaction: SignedTransaction,
1606 > + Unpin,
1607 > Future for TransactionsManager<Pool, N>
1608where
1609 Pool::Transaction:
1610 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1611{
1612 type Output = ();
1613
1614 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1615 let start = Instant::now();
1616 let mut poll_durations = TxManagerPollDurations::default();
1617
1618 let this = self.get_mut();
1619
1620 let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1626 poll_durations.acc_network_events,
1627 "net::tx",
1628 "Network events stream",
1629 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1630 this.network_events.poll_next_unpin(cx),
1631 |event| this.on_network_event(event)
1632 );
1633
1634 let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1649 poll_durations.acc_tx_events,
1650 "net::tx",
1651 "Network transaction events stream",
1652 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1653 this.transaction_events.poll_next_unpin(cx),
1654 |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1655 );
1656
1657 let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1668 poll_durations.acc_fetch_events,
1669 "net::tx",
1670 "Transaction fetch events stream",
1671 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1672 this.transaction_fetcher.poll_next_unpin(cx),
1673 |event| this.on_fetch_event(event),
1674 );
1675
1676 let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1691 poll_durations.acc_pending_imports,
1692 "net::tx",
1693 "Batched pool imports stream",
1694 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1695 this.pool_imports.poll_next_unpin(cx),
1696 |batch_results| this.on_batch_import_result(batch_results)
1697 );
1698
1699 let mut new_txs = Vec::new();
1712 let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1713 cx,
1714 &mut new_txs,
1715 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1716 ) {
1717 Poll::Ready(count) => {
1718 if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1719 true
1722 } else {
1723 let limit =
1727 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1728 new_txs.len();
1729 this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1730 }
1731 }
1732 Poll::Pending => false,
1733 };
1734 if !new_txs.is_empty() {
1735 this.on_new_pending_transactions(new_txs);
1736 }
1737
1738 duration_metered_exec!(
1743 {
1744 if this.has_capacity_for_fetching_pending_hashes() &&
1745 this.on_fetch_hashes_pending_fetch()
1746 {
1747 maybe_more_tx_fetch_events = true;
1748 }
1749 },
1750 poll_durations.acc_pending_fetch
1751 );
1752
1753 let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1755 poll_durations.acc_cmds,
1756 "net::tx",
1757 "Commands channel",
1758 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1759 this.command_rx.poll_next_unpin(cx),
1760 |cmd| this.on_command(cmd)
1761 );
1762
1763 this.transaction_fetcher.update_metrics();
1764
1765 if maybe_more_network_events ||
1767 maybe_more_commands ||
1768 maybe_more_tx_events ||
1769 maybe_more_tx_fetch_events ||
1770 maybe_more_pool_imports ||
1771 maybe_more_pending_txns
1772 {
1773 cx.waker().wake_by_ref();
1775 return Poll::Pending
1776 }
1777
1778 this.update_poll_metrics(start, poll_durations);
1779
1780 Poll::Pending
1781 }
1782}
1783
1784#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1788enum PropagationMode {
1789 Basic,
1793 Forced,
1798}
1799
1800impl PropagationMode {
1801 const fn is_forced(self) -> bool {
1803 matches!(self, Self::Forced)
1804 }
1805}
1806
1807#[derive(Debug, Clone)]
1809struct PropagateTransaction {
1810 is_broadcastable_in_full: bool,
1811 propagation_size: usize,
1818 transaction: LazyEncodedTransaction,
1819}
1820
1821impl PropagateTransaction {
1822 fn new<T: SignedTransaction>(transaction: T) -> Self {
1828 let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1829 let propagation_size = transaction.encode_2718_len();
1830
1831 Self {
1832 is_broadcastable_in_full,
1833 propagation_size,
1834 transaction: LazyEncoded::new(transaction),
1835 }
1836 }
1837
1838 fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1844 let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1845 let propagation_size = tx.encoded_length();
1846 Self {
1847 is_broadcastable_in_full,
1848 propagation_size,
1849 transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1850 }
1851 }
1852
1853 fn tx_hash(&self) -> &TxHash {
1854 self.transaction.tx_hash()
1855 }
1856
1857 const fn propagation_size(&self) -> usize {
1859 self.propagation_size
1860 }
1861
1862 fn tx_type(&self) -> u8 {
1863 self.transaction.ty()
1864 }
1865
1866 const fn is_broadcastable_in_full(&self) -> bool {
1867 self.is_broadcastable_in_full
1868 }
1869
1870 fn shared(&self) -> LazyEncodedTransaction {
1871 self.transaction.clone()
1872 }
1873}
1874
1875#[derive(Debug)]
1877struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1878 transaction: Arc<ValidPoolTransaction<P>>,
1879}
1880
1881impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1882 const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1883 Self { transaction }
1884 }
1885
1886 fn encode_uncached(&self, out: &mut dyn BufMut) {
1887 (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1888 }
1889}
1890
1891impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1892 fn encode(&self, out: &mut dyn BufMut) {
1893 self.encode_uncached(out);
1894 }
1895
1896 fn length(&self) -> usize {
1897 (*self.transaction.transaction.consensus_ref().inner()).length()
1898 }
1899}
1900
1901impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1902 fn tx_hash(&self) -> &TxHash {
1903 self.transaction.hash()
1904 }
1905}
1906
1907impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1908 fn ty(&self) -> u8 {
1909 self.transaction.transaction.ty()
1910 }
1911}
1912
1913#[derive(Debug, Clone)]
1916enum PropagateTransactionsBuilder {
1917 Pooled(PooledTransactionsHashesBuilder),
1918 Full(FullTransactionsBuilder),
1919}
1920
1921impl PropagateTransactionsBuilder {
1922 fn pooled(version: EthVersion, capacity: usize) -> Self {
1925 Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1926 }
1927
1928 fn full(version: EthVersion, capacity: usize) -> Self {
1931 Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1932 }
1933
1934 fn is_empty(&self) -> bool {
1936 match self {
1937 Self::Pooled(builder) => builder.is_empty(),
1938 Self::Full(builder) => builder.is_empty(),
1939 }
1940 }
1941
1942 fn build(self) -> PropagateTransactions {
1944 match self {
1945 Self::Pooled(pooled) => {
1946 PropagateTransactions { pooled: Some(pooled.build()), full: None }
1947 }
1948 Self::Full(full) => full.build(),
1949 }
1950 }
1951}
1952
1953impl PropagateTransactionsBuilder {
1954 fn push(&mut self, transaction: &PropagateTransaction) {
1956 match self {
1957 Self::Pooled(builder) => builder.push(transaction),
1958 Self::Full(builder) => builder.push(transaction),
1959 }
1960 }
1961}
1962
1963struct PropagateTransactions {
1965 pooled: Option<NewPooledTransactionHashes>,
1967 full: Option<BroadcastPoolTransactions>,
1969}
1970
1971#[derive(Debug, Clone)]
1976struct FullTransactionsBuilder {
1977 total_size: usize,
1979 transactions: Vec<LazyEncodedTransaction>,
1981 pooled: PooledTransactionsHashesBuilder,
1983}
1984
1985impl FullTransactionsBuilder {
1986 fn new(version: EthVersion) -> Self {
1988 Self {
1989 total_size: 0,
1990 pooled: PooledTransactionsHashesBuilder::new(version),
1991 transactions: vec![],
1992 }
1993 }
1994
1995 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2000 Self {
2001 total_size: 0,
2002 pooled: PooledTransactionsHashesBuilder::new(version),
2003 transactions: Vec::with_capacity(capacity),
2004 }
2005 }
2006
2007 fn is_empty(&self) -> bool {
2009 self.transactions.is_empty() && self.pooled.is_empty()
2010 }
2011
2012 fn build(self) -> PropagateTransactions {
2014 let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
2015 let full =
2016 (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
2017 PropagateTransactions { pooled, full }
2018 }
2019
2020 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2022 for tx in txs {
2023 self.push(&tx)
2024 }
2025 }
2026
2027 fn push(&mut self, transaction: &PropagateTransaction) {
2037 if !transaction.is_broadcastable_in_full() {
2046 self.pooled.push(transaction);
2047 return
2048 }
2049
2050 let new_size = self.total_size + transaction.propagation_size();
2051 if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2052 self.total_size > 0
2053 {
2054 self.pooled.push(transaction);
2056 return
2057 }
2058
2059 self.total_size = new_size;
2060 self.transactions.push(transaction.shared());
2061 }
2062}
2063
2064#[derive(Debug, Clone)]
2067enum PooledTransactionsHashesBuilder {
2068 Eth66(NewPooledTransactionHashes66),
2069 Eth68(NewPooledTransactionHashes68),
2070 Eth72(NewPooledTransactionHashes72),
2071}
2072
2073impl PooledTransactionsHashesBuilder {
2076 fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2078 match self {
2079 Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2080 Self::Eth68(msg) => {
2081 msg.hashes.push(*pooled_tx.hash());
2082 msg.sizes.push(pooled_tx.encoded_length());
2083 msg.types.push(pooled_tx.transaction.ty());
2084 }
2085 Self::Eth72(msg) => {
2086 msg.hashes.push(*pooled_tx.hash());
2087 msg.sizes.push(pooled_tx.encoded_length());
2088 msg.types.push(pooled_tx.transaction.ty());
2089 }
2090 }
2091 }
2092
2093 fn is_empty(&self) -> bool {
2095 match self {
2096 Self::Eth66(hashes) => hashes.is_empty(),
2097 Self::Eth68(hashes) => hashes.is_empty(),
2098 Self::Eth72(hashes) => hashes.is_empty(),
2099 }
2100 }
2101
2102 fn len(&self) -> usize {
2104 match self {
2105 Self::Eth66(hashes) => hashes.len(),
2106 Self::Eth68(hashes) => hashes.len(),
2107 Self::Eth72(hashes) => hashes.len(),
2108 }
2109 }
2110
2111 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2113 for tx in txs {
2114 self.push(&tx);
2115 }
2116 }
2117
2118 fn push(&mut self, tx: &PropagateTransaction) {
2119 match self {
2120 Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2121 Self::Eth68(msg) => {
2122 msg.hashes.push(*tx.tx_hash());
2123 msg.sizes.push(tx.propagation_size());
2124 msg.types.push(tx.tx_type());
2125 }
2126 Self::Eth72(msg) => {
2127 msg.hashes.push(*tx.tx_hash());
2128 msg.sizes.push(tx.propagation_size());
2129 msg.types.push(tx.tx_type());
2130 }
2131 }
2132 }
2133
2134 fn new(version: EthVersion) -> Self {
2136 match version {
2137 EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2138 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2139 Self::Eth68(Default::default())
2140 }
2141 EthVersion::Eth72 => Self::Eth72(Default::default()),
2142 }
2143 }
2144
2145 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2148 match version {
2149 EthVersion::Eth66 | EthVersion::Eth67 => {
2150 Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2151 }
2152 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2153 Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2154 }
2155 EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2156 }
2157 }
2158
2159 fn build(self) -> NewPooledTransactionHashes {
2160 match self {
2161 Self::Eth66(mut msg) => {
2162 msg.shrink_to_fit();
2163 msg.into()
2164 }
2165 Self::Eth68(mut msg) => {
2166 msg.shrink_to_fit();
2167 msg.into()
2168 }
2169 Self::Eth72(mut msg) => {
2170 msg.shrink_to_fit();
2171 msg.into()
2172 }
2173 }
2174 }
2175}
2176
2177enum TransactionSource {
2179 Broadcast,
2181 Response,
2183}
2184
2185impl TransactionSource {
2188 const fn is_broadcast(&self) -> bool {
2190 matches!(self, Self::Broadcast)
2191 }
2192}
2193
2194#[derive(Debug)]
2196pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2197 seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2201 request_tx: PeerRequestSender<PeerRequest<N>>,
2203 version: EthVersion,
2205 client_version: Arc<str>,
2207 peer_kind: PeerKind,
2209}
2210
2211impl<N: NetworkPrimitives> PeerMetadata<N> {
2212 pub fn new(
2214 request_tx: PeerRequestSender<PeerRequest<N>>,
2215 version: EthVersion,
2216 client_version: Arc<str>,
2217 max_transactions_seen_by_peer: u32,
2218 peer_kind: PeerKind,
2219 ) -> Self {
2220 Self {
2221 seen_transactions: LruCache::with_hasher(
2222 max_transactions_seen_by_peer,
2223 Default::default(),
2224 ),
2225 request_tx,
2226 version,
2227 client_version,
2228 peer_kind,
2229 }
2230 }
2231
2232 pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2234 &self.request_tx
2235 }
2236
2237 pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2239 &mut self.seen_transactions
2240 }
2241
2242 pub const fn version(&self) -> EthVersion {
2244 self.version
2245 }
2246
2247 pub fn client_version(&self) -> &str {
2249 &self.client_version
2250 }
2251
2252 pub const fn peer_kind(&self) -> PeerKind {
2254 self.peer_kind
2255 }
2256}
2257
2258#[derive(Debug)]
2260enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2261 PropagateHash(B256),
2263 PropagateHashesTo(Vec<B256>, PeerId),
2265 GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2267 PropagateTransactionsTo(Vec<TxHash>, PeerId),
2269 PropagateTransactions(Vec<TxHash>),
2271 BroadcastTransactions(Vec<PropagateTransaction>),
2273 GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2275 GetPeerSender {
2277 peer_id: PeerId,
2278 peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2279 },
2280}
2281
2282#[derive(Debug)]
2284pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2285 IncomingTransactions {
2289 peer_id: PeerId,
2291 msg: Transactions<N::BroadcastedTransaction>,
2293 },
2294 IncomingPooledTransactionHashes {
2296 peer_id: PeerId,
2298 msg: NewPooledTransactionHashes,
2300 },
2301 GetPooledTransactions {
2303 peer_id: PeerId,
2305 request: GetPooledTransactions,
2307 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2309 },
2310 GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2312}
2313
2314#[derive(Debug)]
2316pub struct PendingPoolImportsInfo {
2317 pending_pool_imports: Arc<AtomicUsize>,
2319 max_pending_pool_imports: usize,
2321}
2322
2323impl PendingPoolImportsInfo {
2324 pub fn new(max_pending_pool_imports: usize) -> Self {
2326 Self { pending_pool_imports: Arc::new(AtomicUsize::default()), max_pending_pool_imports }
2327 }
2328
2329 pub fn has_capacity(&self, max_pending_pool_imports: usize) -> bool {
2331 self.pending_pool_imports.load(Ordering::Relaxed) < max_pending_pool_imports
2332 }
2333}
2334
2335impl Default for PendingPoolImportsInfo {
2336 fn default() -> Self {
2337 Self::new(DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS)
2338 }
2339}
2340
2341#[derive(Debug, Default)]
2342struct TxManagerPollDurations {
2343 acc_network_events: Duration,
2344 acc_pending_imports: Duration,
2345 acc_tx_events: Duration,
2346 acc_imported_txns: Duration,
2347 acc_fetch_events: Duration,
2348 acc_pending_fetch: Duration,
2349 acc_cmds: Duration,
2350}
2351
2352impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2353 fn size(&self) -> usize {
2356 match self {
2357 Self::IncomingTransactions { peer_id, msg } => {
2358 core::mem::size_of_val(peer_id) +
2359 msg.0.iter().map(InMemorySize::size).sum::<usize>()
2360 }
2361 Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2362 core::mem::size_of_val(peer_id) + msg.size()
2363 }
2364 Self::GetPooledTransactions { peer_id, request, response } => {
2365 core::mem::size_of_val(peer_id) +
2366 request.0.len() * core::mem::size_of::<TxHash>() +
2367 core::mem::size_of_val(response)
2368 }
2369 Self::GetTransactionsHandle(_) => 0,
2370 }
2371 }
2372}
2373
2374#[cfg(test)]
2375mod tests {
2376 use super::*;
2377 use crate::{
2378 test_utils::{
2379 transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2380 Testnet,
2381 },
2382 transactions::config::RelaxedEthAnnouncementFilter,
2383 NetworkConfigBuilder, NetworkManager,
2384 };
2385 use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2386 use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2387 use alloy_primitives::{hex, Signature, TxKind, B256, U256};
2388 use alloy_rlp::Decodable;
2389 use futures::FutureExt;
2390 use reth_chainspec::MIN_TRANSACTION_GAS;
2391 use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2392 use reth_network_api::{NetworkInfo, PeerKind};
2393 use reth_network_p2p::{
2394 error::{RequestError, RequestResult},
2395 sync::{NetworkSyncUpdater, SyncState},
2396 };
2397 use reth_storage_api::noop::NoopProvider;
2398 use reth_tasks::Runtime;
2399 use reth_transaction_pool::{
2400 blobstore::InMemoryBlobStore,
2401 error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2402 identifier::SenderIdentifiers,
2403 test_utils::{
2404 testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2405 TransactionGenerator,
2406 },
2407 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2408 };
2409 use secp256k1::SecretKey;
2410 use std::{
2411 future::poll_fn,
2412 net::{IpAddr, Ipv4Addr, SocketAddr},
2413 str::FromStr,
2414 time::Instant,
2415 };
2416 use tracing::error;
2417
2418 type EthTestPool = Pool<
2419 OkValidator<EthPooledTransaction>,
2420 CoinbaseTipOrdering<EthPooledTransaction>,
2421 InMemoryBlobStore,
2422 >;
2423
2424 async fn new_eth_tx_manager() -> (
2425 TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2426 NetworkManager<EthNetworkPrimitives>,
2427 ) {
2428 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2429 let client = NoopProvider::default();
2430
2431 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2432 .listener_port(0)
2433 .disable_discovery()
2434 .build(client);
2435
2436 let pool = Pool::new(
2437 OkValidator::default(),
2438 CoinbaseTipOrdering::default(),
2439 InMemoryBlobStore::default(),
2440 Default::default(),
2441 );
2442
2443 let transactions_manager_config = config.transactions_manager_config.clone();
2444 let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2445 .await
2446 .unwrap()
2447 .into_builder()
2448 .transactions(pool.clone(), transactions_manager_config)
2449 .split_with_handle();
2450
2451 (transactions, network)
2452 }
2453
2454 fn valid_eth_pool_transaction(
2455 transaction: EthPooledTransaction,
2456 ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2457 let mut ids = SenderIdentifiers::default();
2458 let transaction_id =
2459 ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2460
2461 Arc::new(ValidPoolTransaction {
2462 propagate: false,
2463 transaction_id,
2464 transaction,
2465 timestamp: Instant::now(),
2466 origin: TransactionOrigin::External,
2467 authority_ids: None,
2468 })
2469 }
2470
2471 fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2472 tx_gen: &mut TransactionGenerator<R>,
2473 nonce: u64,
2474 ) -> EthPooledTransaction {
2475 EthPooledTransaction::try_from_consensus(
2476 tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2477 )
2478 .unwrap()
2479 }
2480
2481 #[tokio::test(flavor = "multi_thread")]
2482 async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2483 reth_tracing::init_test_tracing();
2484 let net = Testnet::create(3).await;
2485
2486 let mut handles = net.handles();
2487 let handle0 = handles.next().unwrap();
2488 let handle1 = handles.next().unwrap();
2489
2490 drop(handles);
2491 let handle = net.spawn();
2492
2493 let listener0 = handle0.event_listener();
2494 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2495 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2496
2497 let client = NoopProvider::default();
2498 let pool = testing_pool();
2499 let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2500 .disable_discovery()
2501 .listener_port(0)
2502 .build(client);
2503 let transactions_manager_config = config.transactions_manager_config.clone();
2504 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2505 .await
2506 .unwrap()
2507 .into_builder()
2508 .transactions(pool.clone(), transactions_manager_config)
2509 .split_with_handle();
2510
2511 tokio::task::spawn(network);
2512
2513 network_handle.update_sync_state(SyncState::Syncing);
2515 assert!(NetworkInfo::is_syncing(&network_handle));
2516 assert!(NetworkInfo::is_initially_syncing(&network_handle));
2517
2518 let mut established = listener0.take(2);
2520 while let Some(ev) = established.next().await {
2521 match ev {
2522 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2523 transactions
2525 .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2526 }
2527 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2528 ev => {
2529 error!("unexpected event {ev:?}")
2530 }
2531 }
2532 }
2533 let input = hex!(
2535 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2536 );
2537 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2538 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2539 peer_id: *handle1.peer_id(),
2540 msg: Transactions(vec![signed_tx.clone()]),
2541 });
2542 poll_fn(|cx| {
2543 let _ = transactions.poll_unpin(cx);
2544 Poll::Ready(())
2545 })
2546 .await;
2547 assert!(pool.is_empty());
2548 handle.terminate().await;
2549 }
2550
2551 #[tokio::test(flavor = "multi_thread")]
2552 async fn test_tx_broadcasts_through_two_syncs() {
2553 reth_tracing::init_test_tracing();
2554 let net = Testnet::create(3).await;
2555
2556 let mut handles = net.handles();
2557 let handle0 = handles.next().unwrap();
2558 let handle1 = handles.next().unwrap();
2559
2560 drop(handles);
2561 let handle = net.spawn();
2562
2563 let listener0 = handle0.event_listener();
2564 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2565 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2566
2567 let client = NoopProvider::default();
2568 let pool = testing_pool();
2569 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2570 .disable_discovery()
2571 .listener_port(0)
2572 .build(client);
2573 let transactions_manager_config = config.transactions_manager_config.clone();
2574 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2575 .await
2576 .unwrap()
2577 .into_builder()
2578 .transactions(pool.clone(), transactions_manager_config)
2579 .split_with_handle();
2580
2581 tokio::task::spawn(network);
2582
2583 network_handle.update_sync_state(SyncState::Syncing);
2585 assert!(NetworkInfo::is_syncing(&network_handle));
2586 network_handle.update_sync_state(SyncState::Idle);
2587 assert!(!NetworkInfo::is_syncing(&network_handle));
2588 network_handle.update_sync_state(SyncState::Syncing);
2589 assert!(NetworkInfo::is_syncing(&network_handle));
2590
2591 let mut established = listener0.take(2);
2593 while let Some(ev) = established.next().await {
2594 match ev {
2595 NetworkEvent::ActivePeerSession { .. } |
2596 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2597 transactions.on_network_event(ev);
2599 }
2600 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2601 _ => {
2602 error!("unexpected event {ev:?}")
2603 }
2604 }
2605 }
2606 let input = hex!(
2608 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2609 );
2610 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2611 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2612 peer_id: *handle1.peer_id(),
2613 msg: Transactions(vec![signed_tx.clone()]),
2614 });
2615 poll_fn(|cx| {
2616 let _ = transactions.poll_unpin(cx);
2617 Poll::Ready(())
2618 })
2619 .await;
2620 assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2621 assert!(NetworkInfo::is_syncing(&network_handle));
2622 assert!(!pool.is_empty());
2623 handle.terminate().await;
2624 }
2625
2626 #[tokio::test(flavor = "multi_thread")]
2629 async fn test_handle_incoming_transactions_hashes() {
2630 reth_tracing::init_test_tracing();
2631
2632 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2633 let client = NoopProvider::default();
2634
2635 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2636 .listener_port(0)
2638 .disable_discovery()
2639 .build(client);
2640
2641 let pool = testing_pool();
2642
2643 let transactions_manager_config = config.transactions_manager_config.clone();
2644 let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2645 .await
2646 .unwrap()
2647 .into_builder()
2648 .transactions(pool.clone(), transactions_manager_config)
2649 .split_with_handle();
2650
2651 let peer_id_1 = PeerId::new([1; 64]);
2652 let eth_version = EthVersion::Eth66;
2653
2654 let txs = vec![TransactionSigned::new_unhashed(
2655 Transaction::Legacy(TxLegacy {
2656 chain_id: Some(4),
2657 nonce: 15u64,
2658 gas_price: 2200000000,
2659 gas_limit: 34811,
2660 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2661 value: U256::from(1234u64),
2662 input: Default::default(),
2663 }),
2664 Signature::new(
2665 U256::from_str(
2666 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2667 )
2668 .unwrap(),
2669 U256::from_str(
2670 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2671 )
2672 .unwrap(),
2673 true,
2674 ),
2675 )];
2676
2677 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2678
2679 let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2680 tx_manager.peers.insert(peer_id_1, peer_1);
2681
2682 assert!(pool.is_empty());
2683
2684 tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2685 peer_id: peer_id_1,
2686 msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2687 txs_hashes.clone(),
2688 )),
2689 });
2690
2691 let req = to_mock_session_rx
2693 .recv()
2694 .await
2695 .expect("peer_1 session should receive request with buffered hashes");
2696 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2697 assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2698
2699 let message: Vec<PooledTransactionVariant> = txs
2700 .into_iter()
2701 .map(|tx| {
2702 PooledTransactionVariant::try_from(tx)
2703 .expect("Failed to convert MockTransaction to PooledTransaction")
2704 })
2705 .collect();
2706
2707 response
2709 .send(Ok(PooledTransactions(message)))
2710 .expect("should send peer_1 response to tx manager");
2711
2712 poll_fn(|cx| {
2714 let _ = tx_manager.poll_unpin(cx);
2715 Poll::Ready(())
2716 })
2717 .await;
2718
2719 assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2722 }
2723
2724 #[tokio::test(flavor = "multi_thread")]
2725 async fn test_handle_incoming_transactions() {
2726 reth_tracing::init_test_tracing();
2727 let net = Testnet::create(3).await;
2728
2729 let mut handles = net.handles();
2730 let handle0 = handles.next().unwrap();
2731 let handle1 = handles.next().unwrap();
2732
2733 drop(handles);
2734 let handle = net.spawn();
2735
2736 let listener0 = handle0.event_listener();
2737
2738 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2739 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2740
2741 let client = NoopProvider::default();
2742 let pool = testing_pool();
2743 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2744 .disable_discovery()
2745 .listener_port(0)
2746 .build(client);
2747 let transactions_manager_config = config.transactions_manager_config.clone();
2748 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2749 .await
2750 .unwrap()
2751 .into_builder()
2752 .transactions(pool.clone(), transactions_manager_config)
2753 .split_with_handle();
2754 tokio::task::spawn(network);
2755
2756 network_handle.update_sync_state(SyncState::Idle);
2757
2758 assert!(!NetworkInfo::is_syncing(&network_handle));
2759
2760 let mut established = listener0.take(2);
2762 while let Some(ev) = established.next().await {
2763 match ev {
2764 NetworkEvent::ActivePeerSession { .. } |
2765 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2766 transactions.on_network_event(ev);
2768 }
2769 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2770 ev => {
2771 error!("unexpected event {ev:?}")
2772 }
2773 }
2774 }
2775 let input = hex!(
2777 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2778 );
2779 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2780 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2781 peer_id: *handle1.peer_id(),
2782 msg: Transactions(vec![signed_tx.clone()]),
2783 });
2784 assert!(transactions
2785 .transactions_by_peers
2786 .get(signed_tx.tx_hash())
2787 .unwrap()
2788 .contains(handle1.peer_id()));
2789
2790 poll_fn(|cx| {
2792 let _ = transactions.poll_unpin(cx);
2793 Poll::Ready(())
2794 })
2795 .await;
2796
2797 assert!(!pool.is_empty());
2798 assert!(pool.get(signed_tx.tx_hash()).is_some());
2799 handle.terminate().await;
2800 }
2801
2802 #[tokio::test(flavor = "multi_thread")]
2803 async fn test_session_closed_cleans_transaction_peer_state() {
2804 let (mut tx_manager, _network) = new_tx_manager().await;
2805 let peer_id = PeerId::new([1; 64]);
2806 let fallback_peer = PeerId::new([2; 64]);
2807 let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2808 let hash_shared = B256::from_slice(&[1; 32]);
2809
2810 tx_manager.peers.insert(peer_id, peer);
2811 buffer_hash_to_tx_fetcher(
2812 &mut tx_manager.transaction_fetcher,
2813 hash_shared,
2814 peer_id,
2815 0,
2816 None,
2817 );
2818 buffer_hash_to_tx_fetcher(
2819 &mut tx_manager.transaction_fetcher,
2820 hash_shared,
2821 fallback_peer,
2822 0,
2823 None,
2824 );
2825 tx_manager.transaction_fetcher.active_peers.insert(peer_id, 1);
2826
2827 tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2828 peer_id,
2829 reason: None,
2830 }));
2831
2832 assert!(!tx_manager.peers.contains_key(&peer_id));
2834 assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2835 assert_eq!(
2837 tx_manager.transaction_fetcher.get_idle_peer_for(hash_shared),
2838 Some(&fallback_peer)
2839 );
2840 }
2841
2842 #[tokio::test(flavor = "multi_thread")]
2843 async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2844 let (mut tx_manager, _network) = new_tx_manager().await;
2845 let peer_id = PeerId::new([1; 64]);
2846 let hash = B256::from_slice(&[1; 32]);
2847
2848 tx_manager.network.update_sync_state(SyncState::Idle);
2849 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2850
2851 let err = PoolError::new(
2852 hash,
2853 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2854 BlobTransactionValidationError::InvalidProof,
2855 )),
2856 );
2857
2858 tx_manager.on_bad_import(err);
2859
2860 assert!(!tx_manager.bad_imports.contains(&hash));
2861 }
2862
2863 #[tokio::test(flavor = "multi_thread")]
2864 async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2865 let (mut tx_manager, _network) = new_tx_manager().await;
2866 let peer_id = PeerId::new([1; 64]);
2867 let hash = B256::from_slice(&[3; 32]);
2868
2869 tx_manager.network.update_sync_state(SyncState::Idle);
2870 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2871
2872 let err = PoolError::new(
2873 hash,
2874 InvalidPoolTransactionError::Eip4844(
2875 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2876 ),
2877 );
2878
2879 tx_manager.on_bad_import(err);
2880
2881 assert!(!tx_manager.bad_imports.contains(&hash));
2882 }
2883
2884 #[tokio::test(flavor = "multi_thread")]
2885 async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2886 let (mut tx_manager, _network) = new_tx_manager().await;
2887 let peer_id = PeerId::new([1; 64]);
2888 let hash = B256::from_slice(&[2; 32]);
2889
2890 tx_manager.network.update_sync_state(SyncState::Idle);
2891 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2892
2893 let err = PoolError::new(
2894 hash,
2895 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2896 );
2897
2898 tx_manager.on_bad_import(err);
2899
2900 assert!(tx_manager.bad_imports.contains(&hash));
2901 }
2902
2903 #[tokio::test(flavor = "multi_thread")]
2904 async fn test_on_get_pooled_transactions_network() {
2905 reth_tracing::init_test_tracing();
2906 let net = Testnet::create(2).await;
2907
2908 let mut handles = net.handles();
2909 let handle0 = handles.next().unwrap();
2910 let handle1 = handles.next().unwrap();
2911
2912 drop(handles);
2913 let handle = net.spawn();
2914
2915 let listener0 = handle0.event_listener();
2916
2917 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2918 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2919
2920 let client = NoopProvider::default();
2921 let pool = testing_pool();
2922 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2923 .disable_discovery()
2924 .listener_port(0)
2925 .build(client);
2926 let transactions_manager_config = config.transactions_manager_config.clone();
2927 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2928 .await
2929 .unwrap()
2930 .into_builder()
2931 .transactions(pool.clone(), transactions_manager_config)
2932 .split_with_handle();
2933 tokio::task::spawn(network);
2934
2935 network_handle.update_sync_state(SyncState::Idle);
2936
2937 assert!(!NetworkInfo::is_syncing(&network_handle));
2938
2939 let mut established = listener0.take(2);
2941 while let Some(ev) = established.next().await {
2942 match ev {
2943 NetworkEvent::ActivePeerSession { .. } |
2944 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2945 transactions.on_network_event(ev);
2946 }
2947 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2948 ev => {
2949 error!("unexpected event {ev:?}")
2950 }
2951 }
2952 }
2953 handle.terminate().await;
2954
2955 let tx = MockTransaction::eip1559();
2956 let _ = transactions
2957 .pool
2958 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
2959 .await;
2960
2961 let request = GetPooledTransactions(vec![*tx.get_hash()]);
2962
2963 let (send, receive) =
2964 oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
2965
2966 transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
2967 peer_id: *handle1.peer_id(),
2968 request,
2969 response: send,
2970 });
2971
2972 match receive.await.unwrap() {
2973 Ok(PooledTransactions(transactions)) => {
2974 assert_eq!(transactions.len(), 1);
2975 }
2976 Err(e) => {
2977 panic!("error: {e:?}");
2978 }
2979 }
2980 }
2981
2982 #[tokio::test]
2986 async fn test_partially_tx_response() {
2987 reth_tracing::init_test_tracing();
2988
2989 let mut tx_manager = new_tx_manager().await.0;
2990 let tx_fetcher = &mut tx_manager.transaction_fetcher;
2991
2992 let peer_id_1 = PeerId::new([1; 64]);
2993 let eth_version = EthVersion::Eth66;
2994
2995 let txs = vec![
2996 TransactionSigned::new_unhashed(
2997 Transaction::Legacy(TxLegacy {
2998 chain_id: Some(4),
2999 nonce: 15u64,
3000 gas_price: 2200000000,
3001 gas_limit: 34811,
3002 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
3003 value: U256::from(1234u64),
3004 input: Default::default(),
3005 }),
3006 Signature::new(
3007 U256::from_str(
3008 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
3009 )
3010 .unwrap(),
3011 U256::from_str(
3012 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
3013 )
3014 .unwrap(),
3015 true,
3016 ),
3017 ),
3018 TransactionSigned::new_unhashed(
3019 Transaction::Eip1559(TxEip1559 {
3020 chain_id: 4,
3021 nonce: 26u64,
3022 max_priority_fee_per_gas: 1500000000,
3023 max_fee_per_gas: 1500000013,
3024 gas_limit: MIN_TRANSACTION_GAS,
3025 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
3026 value: U256::from(3000000000000000000u64),
3027 input: Default::default(),
3028 access_list: Default::default(),
3029 }),
3030 Signature::new(
3031 U256::from_str(
3032 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
3033 )
3034 .unwrap(),
3035 U256::from_str(
3036 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
3037 )
3038 .unwrap(),
3039 true,
3040 ),
3041 ),
3042 ];
3043
3044 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3045
3046 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3047 peer_1.seen_transactions.insert(txs_hashes[0]);
3050 peer_1.seen_transactions.insert(txs_hashes[1]);
3051 tx_manager.peers.insert(peer_id_1, peer_1);
3052
3053 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, 0, None);
3054 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, 0, None);
3055
3056 assert!(tx_fetcher.is_idle(&peer_id_1));
3058 assert_eq!(tx_fetcher.active_peers.len(), 0);
3059
3060 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3062
3063 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3064 assert!(!tx_fetcher.is_idle(&peer_id_1));
3066 assert_eq!(tx_fetcher.active_peers.len(), 1);
3067
3068 let req = to_mock_session_rx
3070 .recv()
3071 .await
3072 .expect("peer_1 session should receive request with buffered hashes");
3073 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3074
3075 let message: Vec<PooledTransactionVariant> = txs
3076 .into_iter()
3077 .take(1)
3078 .map(|tx| {
3079 PooledTransactionVariant::try_from(tx)
3080 .expect("Failed to convert MockTransaction to PooledTransaction")
3081 })
3082 .collect();
3083 response
3085 .send(Ok(PooledTransactions(message)))
3086 .expect("should send peer_1 response to tx manager");
3087 let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3088 unreachable!()
3089 };
3090
3091 assert!(tx_fetcher.is_idle(&peer_id));
3093 assert_eq!(tx_fetcher.active_peers.len(), 0);
3094 assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3096 }
3097
3098 #[tokio::test]
3099 async fn test_max_retries_tx_request() {
3100 reth_tracing::init_test_tracing();
3101
3102 let mut tx_manager = new_tx_manager().await.0;
3103 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3104
3105 let peer_id_1 = PeerId::new([1; 64]);
3106 let peer_id_2 = PeerId::new([2; 64]);
3107 let eth_version = EthVersion::Eth66;
3108 let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3109
3110 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3111 peer_1.seen_transactions.insert(seen_hashes[0]);
3114 peer_1.seen_transactions.insert(seen_hashes[1]);
3115 tx_manager.peers.insert(peer_id_1, peer_1);
3116
3117 let retries = 1;
3120 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[1], peer_id_1, retries, None);
3121 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[0], peer_id_1, retries, None);
3122
3123 assert!(tx_fetcher.is_idle(&peer_id_1));
3125 assert_eq!(tx_fetcher.active_peers.len(), 0);
3126
3127 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3129
3130 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3131
3132 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3133 assert!(!tx_fetcher.is_idle(&peer_id_1));
3135 assert_eq!(tx_fetcher.active_peers.len(), 1);
3136
3137 let req = to_mock_session_rx
3139 .recv()
3140 .await
3141 .expect("peer_1 session should receive request with buffered hashes");
3142 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3143 let GetPooledTransactions(hashes) = request;
3144
3145 let hashes = hashes.into_iter().collect::<B256Set>();
3146
3147 assert_eq!(hashes, seen_hashes.into_iter().collect::<B256Set>());
3148
3149 response
3151 .send(Err(RequestError::BadResponse))
3152 .expect("should send peer_1 response to tx manager");
3153 let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3154 unreachable!()
3155 };
3156
3157 assert!(tx_fetcher.is_idle(&peer_id));
3159 assert_eq!(tx_fetcher.active_peers.len(), 0);
3160 assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3162
3163 let (peer_2, mut to_mock_session_rx) = new_mock_session(peer_id_2, eth_version);
3164 tx_manager.peers.insert(peer_id_2, peer_2);
3165
3166 let msg =
3168 NewPooledTransactionHashes::Eth66(NewPooledTransactionHashes66(seen_hashes.to_vec()));
3169 tx_manager.on_new_pooled_transaction_hashes(peer_id_2, msg);
3170
3171 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3172
3173 assert_eq!(tx_fetcher.active_peers.len(), 1);
3175
3176 assert_eq!(tx_fetcher.num_all_hashes(), 2);
3178 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3180
3181 let req = to_mock_session_rx
3183 .recv()
3184 .await
3185 .expect("peer_2 session should receive request with buffered hashes");
3186 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3187
3188 response
3190 .send(Err(RequestError::BadResponse))
3191 .expect("should send peer_2 response to tx manager");
3192 let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3193
3194 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3197 assert_eq!(tx_fetcher.active_peers.len(), 0);
3198 }
3199
3200 #[test]
3201 fn test_direct_propagation_transaction_uses_2718_size() {
3202 let mut tx_gen = TransactionGenerator::new(rand::rng());
3203 let tx = tx_gen.gen_eip1559();
3204 let expected_size = tx.encode_2718_len();
3205
3206 let tx = PropagateTransaction::new(tx);
3207
3208 assert_eq!(tx.propagation_size(), expected_size);
3209 }
3210
3211 #[test]
3212 fn test_transaction_builder_empty() {
3213 let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3214 assert!(builder.is_empty());
3215
3216 let mut tx_gen = TransactionGenerator::new(rand::rng());
3217 let tx =
3218 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3219 builder.push(&tx);
3220 assert!(!builder.is_empty());
3221
3222 let txs = builder.build();
3223 assert!(txs.full.is_none());
3224 let txs = txs.pooled.unwrap();
3225 assert_eq!(txs.len(), 1);
3226 }
3227
3228 #[test]
3229 fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3230 let mut tx_gen = TransactionGenerator::new(rand::rng());
3231 let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3232 let pooled = PropagatePooledTransactionEncoder::new(tx);
3233
3234 let mut pooled_encoded = Vec::new();
3235 pooled.encode(&mut pooled_encoded);
3236 assert_eq!(pooled.length(), pooled_encoded.len());
3237
3238 let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3239 let mut first_encoded = Vec::new();
3240 broadcast.encode(&mut first_encoded);
3241 let mut second_encoded = Vec::new();
3242 broadcast.encode(&mut second_encoded);
3243 assert_eq!(first_encoded, second_encoded);
3244
3245 let mut encoded = first_encoded.as_slice();
3246 let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3247 assert_eq!(decoded.len(), 1);
3248 assert!(encoded.is_empty());
3249 }
3250
3251 #[test]
3252 fn test_transaction_builder_large() {
3253 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3254 assert!(builder.is_empty());
3255
3256 let mut tx_gen = TransactionGenerator::new(rand::rng());
3257 let mut tx = tx_gen.gen_eip1559_pooled();
3258 tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3260 let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3261 builder.push(&tx);
3262 assert!(!builder.is_empty());
3263
3264 let txs = builder.clone().build();
3265 assert!(txs.pooled.is_none());
3266 let txs = txs.full.unwrap();
3267 assert_eq!(txs.len(), 1);
3268
3269 builder.push(&tx);
3270
3271 let txs = builder.clone().build();
3272 let pooled = txs.pooled.unwrap();
3273 assert_eq!(pooled.len(), 1);
3274 let txs = txs.full.unwrap();
3275 assert_eq!(txs.len(), 1);
3276 }
3277
3278 #[test]
3279 fn test_transaction_builder_eip4844() {
3280 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3281 assert!(builder.is_empty());
3282
3283 let mut tx_gen = TransactionGenerator::new(rand::rng());
3284 let tx =
3285 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3286 builder.push(&tx);
3287 assert!(!builder.is_empty());
3288
3289 let txs = builder.clone().build();
3290 assert!(txs.full.is_none());
3291 let txs = txs.pooled.unwrap();
3292 assert_eq!(txs.len(), 1);
3293
3294 let tx =
3295 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3296 builder.push(&tx);
3297
3298 let txs = builder.clone().build();
3299 let pooled = txs.pooled.unwrap();
3300 assert_eq!(pooled.len(), 1);
3301 let txs = txs.full.unwrap();
3302 assert_eq!(txs.len(), 1);
3303 }
3304
3305 #[tokio::test]
3306 async fn test_propagate_full() {
3307 reth_tracing::init_test_tracing();
3308
3309 let (mut tx_manager, network) = new_eth_tx_manager().await;
3310 let peer_id = PeerId::random();
3311
3312 network.handle().update_sync_state(SyncState::Idle);
3314
3315 let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3317
3318 let session_info = SessionInfo {
3319 peer_id,
3320 remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3321 client_version: Arc::from(""),
3322 capabilities: Arc::new(vec![].into()),
3323 status: Arc::new(Default::default()),
3324 version: EthVersion::Eth68,
3325 peer_kind: PeerKind::Basic,
3326 };
3327 let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3328 tx_manager
3329 .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3330 let mut propagate = vec![];
3331 let mut tx_gen = TransactionGenerator::new(rand::rng());
3332 let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3333 propagate.push(eip1559_tx.clone());
3334 let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3335 propagate.push(eip4844_tx.clone());
3336
3337 let propagated = tx_manager.propagate_transactions(
3338 propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3339 PropagationMode::Basic,
3340 );
3341 assert_eq!(propagated.len(), 2);
3342 let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3343 assert_eq!(prop_txs.len(), 1);
3344 assert!(prop_txs[0].is_full());
3345
3346 let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3347 assert_eq!(prop_txs.len(), 1);
3348 assert!(prop_txs[0].is_hash());
3349
3350 let peer = tx_manager.peers.get(&peer_id).unwrap();
3351 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3352 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3353 peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3354
3355 let propagated = tx_manager.propagate_transactions(
3357 propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3358 PropagationMode::Basic,
3359 );
3360 assert!(propagated.is_empty());
3361 }
3362
3363 #[tokio::test]
3364 async fn test_truncated_hash_announcement_not_marked_seen() {
3365 reth_tracing::init_test_tracing();
3366
3367 let (mut tx_manager, network) = new_eth_tx_manager().await;
3368 tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3370
3371 network.handle().update_sync_state(SyncState::Idle);
3373
3374 let peer_id = PeerId::random();
3375 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3376 tx_manager.peers.insert(peer_id, peer);
3377
3378 let mut tx_gen = TransactionGenerator::new(rand::rng());
3380 let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3381 .map(|nonce| {
3382 valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3383 })
3384 .collect::<Vec<_>>();
3385 let last_sent = *txs[txs.len() - 2].hash();
3386 let truncated = *txs[txs.len() - 1].hash();
3387
3388 let propagated = tx_manager.propagate_transactions(
3389 txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3390 PropagationMode::Basic,
3391 );
3392
3393 assert!(propagated.get(&truncated).is_none());
3395 let peer = tx_manager.peers.get(&peer_id).unwrap();
3396 assert!(!peer.seen_transactions.contains(&truncated));
3397 assert!(peer.seen_transactions.contains(&last_sent));
3398 }
3399
3400 #[tokio::test]
3401 async fn test_propagate_pending_txs_while_initially_syncing() {
3402 reth_tracing::init_test_tracing();
3403
3404 let (mut tx_manager, network) = new_eth_tx_manager().await;
3405 let peer_id = PeerId::random();
3406
3407 network.handle().update_sync_state(SyncState::Syncing);
3409 assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3410
3411 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3413 tx_manager.peers.insert(peer_id, peer);
3414
3415 let mut tx_gen = TransactionGenerator::new(rand::rng());
3416 let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3417 let tx_hash = *tx.hash();
3418 tx_manager
3419 .pool
3420 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3421 .await
3422 .expect("transaction should be accepted into the pool");
3423
3424 tx_manager.on_new_pending_transactions(vec![tx_hash]);
3425
3426 let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3427 assert!(peer.seen_transactions.contains(&tx_hash));
3428 }
3429
3430 #[tokio::test]
3431 async fn test_relaxed_filter_ignores_unknown_tx_types() {
3432 reth_tracing::init_test_tracing();
3433
3434 let transactions_manager_config = TransactionsManagerConfig::default();
3435
3436 let propagation_policy = TransactionPropagationKind::default();
3437 let announcement_policy = RelaxedEthAnnouncementFilter::default();
3438
3439 let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3440
3441 let pool = testing_pool();
3442 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
3443 let client = NoopProvider::default();
3444
3445 let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3446 .listener_port(0)
3447 .disable_discovery()
3448 .build(client.clone());
3449
3450 let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3451 let (to_tx_manager_tx, from_network_rx) =
3452 reth_metrics::common::mpsc::memory_bounded_channel::<
3453 NetworkTransactionEvent<EthNetworkPrimitives>,
3454 >(
3455 crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3456 "test_tx_channel",
3457 );
3458 network_manager.set_transactions(to_tx_manager_tx);
3459 let network_handle = network_manager.handle().clone();
3460 let network_service_handle = tokio::spawn(network_manager);
3461
3462 let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3463 network_handle.clone(),
3464 pool.clone(),
3465 from_network_rx,
3466 transactions_manager_config,
3467 policy_bundle,
3468 );
3469
3470 let peer_id = PeerId::random();
3471 let eth_version = EthVersion::Eth68;
3472 let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3473 tx_manager.peers.insert(peer_id, mock_peer_metadata);
3474
3475 let mut tx_factory = MockTransactionFactory::default();
3476
3477 let valid_known_tx = tx_factory.create_eip1559();
3478 let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3479
3480 let known_tx_hash = *known_tx_signed.hash();
3481 let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3482 let known_tx_size = known_tx_signed.encoded_length();
3483
3484 let unknown_tx_hash = B256::random();
3485 let unknown_tx_type_byte = 0xff_u8;
3486 let unknown_tx_size = 150;
3487
3488 let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3489 types: vec![known_tx_type_byte, unknown_tx_type_byte],
3490 sizes: vec![known_tx_size, unknown_tx_size],
3491 hashes: vec![known_tx_hash, unknown_tx_hash],
3492 });
3493
3494 tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3495
3496 poll_fn(|cx| {
3497 let _ = tx_manager.poll_unpin(cx);
3498 Poll::Ready(())
3499 })
3500 .await;
3501
3502 let mut requested_hashes_in_getpooled = B256Set::default();
3503 let mut unexpected_request_received = false;
3504
3505 match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3506 .await
3507 {
3508 Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3509 let GetPooledTransactions(hashes) = request;
3510 for hash in hashes {
3511 requested_hashes_in_getpooled.insert(hash);
3512 }
3513 let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3514 }
3515 Ok(Some(other_request)) => {
3516 tracing::error!(?other_request, "Received unexpected PeerRequest type");
3517 unexpected_request_received = true;
3518 }
3519 Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3520 Err(_timeout_err) => {
3521 tracing::info!("Timeout: No GetPooledTransactions request received.")
3522 }
3523 }
3524
3525 assert!(
3526 requested_hashes_in_getpooled.contains(&known_tx_hash),
3527 "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3528 );
3529 assert!(
3530 !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3531 "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3532 );
3533 assert!(
3534 !unexpected_request_received,
3535 "An unexpected P2P request was received by the mock peer."
3536 );
3537
3538 network_service_handle.abort();
3539 }
3540}