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, DedupPayload, EthNetworkPrimitives, EthVersion,
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
230#[derive(Debug)]
285#[must_use = "Manager does nothing unless polled."]
286pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
287 pool: Pool,
289 sender_recovery_cache: Option<SenderRecoveryCache>,
291 network: NetworkHandle<N>,
293 network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
297 transaction_fetcher: TransactionFetcher<N>,
299 transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
304 pool_imports: FuturesUnordered<PoolImportFuture>,
316 pending_pool_imports_info: PendingPoolImportsInfo,
318 bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
320 peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
322 command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
326 command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
331 pending_transactions: mpsc::Receiver<TxHash>,
340 transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
342 config: TransactionsManagerConfig,
344 policies: NetworkPolicies<N>,
346 metrics: TransactionsManagerMetrics,
348 announced_tx_types_metrics: AnnouncedTxTypesMetrics,
350}
351
352impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
353 pub fn new(
357 network: NetworkHandle<N>,
358 pool: Pool,
359 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
360 transactions_manager_config: TransactionsManagerConfig,
361 ) -> Self {
362 Self::with_policy(
363 network,
364 pool,
365 from_network,
366 transactions_manager_config,
367 NetworkPolicies::new(
368 TransactionPropagationKind::default(),
369 StrictEthAnnouncementFilter::default(),
370 ),
371 )
372 }
373}
374
375impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
376 pub fn with_policy(
380 network: NetworkHandle<N>,
381 pool: Pool,
382 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
383 transactions_manager_config: TransactionsManagerConfig,
384 policies: NetworkPolicies<N>,
385 ) -> Self {
386 let network_events = network.event_listener();
387
388 let (command_tx, command_rx) = mpsc::unbounded_channel();
389
390 let transaction_fetcher = TransactionFetcher::with_transaction_fetcher_config(
391 &transactions_manager_config.transaction_fetcher_config,
392 );
393
394 let pending = pool.pending_transactions_listener();
397 let pending_pool_imports_info =
398 PendingPoolImportsInfo::new(transactions_manager_config.max_pending_pool_imports);
399 let metrics = TransactionsManagerMetrics::default();
400 metrics
401 .capacity_pending_pool_imports
402 .increment(pending_pool_imports_info.max_pending_pool_imports as u64);
403
404 Self {
405 pool,
406 sender_recovery_cache: None,
407 network,
408 network_events,
409 transaction_fetcher,
410 transactions_by_peers: Default::default(),
411 pool_imports: Default::default(),
412 pending_pool_imports_info,
413 bad_imports: LruCache::with_hasher(DEFAULT_MAX_COUNT_BAD_IMPORTS, Default::default()),
414 peers: Default::default(),
415 command_tx,
416 command_rx: UnboundedReceiverStream::new(command_rx),
417 pending_transactions: pending,
418 transaction_events: from_network,
419 config: transactions_manager_config,
420 policies,
421 metrics,
422 announced_tx_types_metrics: AnnouncedTxTypesMetrics::default(),
423 }
424 }
425
426 pub fn handle(&self) -> TransactionsHandle<N> {
428 TransactionsHandle { manager_tx: self.command_tx.clone() }
429 }
430
431 pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
433 self.sender_recovery_cache = Some(cache);
434 self
435 }
436
437 fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
440 self.has_capacity_for_pending_pool_imports() &&
441 self.transaction_fetcher.has_capacity_for_fetching_pending_hashes()
442 }
443
444 fn has_capacity_for_pending_pool_imports(&self) -> bool {
446 self.remaining_pool_import_capacity() > 0
447 }
448
449 fn remaining_pool_import_capacity(&self) -> usize {
451 self.pending_pool_imports_info.max_pending_pool_imports.saturating_sub(
452 self.pending_pool_imports_info.pending_pool_imports.load(Ordering::Relaxed),
453 )
454 }
455
456 fn report_peer_bad_transactions(&self, peer_id: PeerId) {
457 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
458 self.metrics.reported_bad_transactions.increment(1);
459 }
460
461 fn report_peer(&self, peer_id: PeerId, kind: ReputationChangeKind) {
462 trace!(target: "net::tx", ?peer_id, ?kind, "reporting reputation change");
463 self.network.reputation_change(peer_id, kind);
464 }
465
466 fn report_already_seen(&self, peer_id: PeerId) {
467 trace!(target: "net::tx", ?peer_id, "Penalizing peer for already seen transaction");
468 self.network.reputation_change(peer_id, ReputationChangeKind::AlreadySeenTransaction);
469 }
470
471 fn on_peer_session_closed(&mut self, peer_id: &PeerId) {
473 if let Some(mut peer) = self.peers.remove(peer_id) {
474 self.policies.propagation_policy_mut().on_session_closed(&mut peer);
475 }
476 self.transaction_fetcher.remove_peer(peer_id);
477 }
478
479 fn on_good_import(&mut self, hash: TxHash) {
481 self.transactions_by_peers.remove(&hash);
482 }
483
484 fn on_bad_import(&mut self, err: PoolError) {
514 let peers = self.transactions_by_peers.remove(&err.hash);
515
516 if err.is_bad_blob_sidecar() {
517 if let Some(peers) = peers {
521 for peer_id in peers {
522 self.report_peer_bad_transactions(peer_id);
523 }
524 }
525 return
526 }
527
528 if !err.is_bad_transaction() || self.network.is_syncing() {
530 return
531 }
532 if let Some(peers) = peers {
535 for peer_id in peers {
536 self.report_peer_bad_transactions(peer_id);
537 }
538 }
539 self.metrics.bad_imports.increment(1);
540 self.bad_imports.insert(err.hash);
541 }
542
543 fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
547 let info = &self.pending_pool_imports_info;
549 let max_pending_pool_imports = info.max_pending_pool_imports;
550 let has_capacity_wrt_pending_pool_imports =
551 |divisor| info.has_capacity(max_pending_pool_imports / divisor);
552
553 self.transaction_fetcher
554 .on_fetch_pending_hashes(&self.peers, has_capacity_wrt_pending_pool_imports)
555 }
556
557 fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
558 let kind = match req_err {
559 RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
560 RequestError::Timeout => ReputationChangeKind::Timeout,
561 RequestError::ChannelClosed | RequestError::ConnectionDropped => {
562 return
564 }
565 RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
566 };
567 self.report_peer(peer_id, kind);
568 }
569
570 #[inline]
571 fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
572 let metrics = &self.metrics;
573
574 let TxManagerPollDurations {
575 acc_network_events,
576 acc_pending_imports,
577 acc_tx_events,
578 acc_imported_txns,
579 acc_fetch_events,
580 acc_pending_fetch,
581 acc_cmds,
582 } = poll_durations;
583
584 metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
586 metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
588 metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
589 metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
590 metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
591 metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
592 metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
593 metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
594 }
595}
596
597impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
598 fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
600 for res in batch_results {
601 match res {
602 Ok(AddedTransactionOutcome { hash, .. }) => {
603 self.on_good_import(hash);
604 }
605 Err(err) => {
606 self.on_bad_import(err);
607 }
608 }
609 }
610 }
611
612 fn on_new_pooled_transaction_hashes(
614 &mut self,
615 peer_id: PeerId,
616 msg: NewPooledTransactionHashes,
617 ) {
618 if self.network.is_initially_syncing() {
620 return
621 }
622 if self.network.tx_gossip_disabled() {
623 return
624 }
625
626 let Some(peer) = self.peers.get_mut(&peer_id) else {
628 trace!(
629 peer_id = format!("{peer_id:#}"),
630 ?msg,
631 "discarding announcement from inactive peer"
632 );
633
634 return
635 };
636 let client = peer.client_version.clone();
637
638 let mut count_txns_already_seen_by_peer = 0;
640 for tx in msg.iter_hashes().copied() {
641 if !peer.seen_transactions.insert(tx) {
642 count_txns_already_seen_by_peer += 1;
643 }
644 }
645 if count_txns_already_seen_by_peer > 0 {
646 self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
651 self.metrics
652 .occurrences_hash_already_seen_by_peer
653 .increment(count_txns_already_seen_by_peer);
654
655 trace!(target: "net::tx",
656 %count_txns_already_seen_by_peer,
657 peer_id=format!("{peer_id:#}"),
658 ?client,
659 "Peer sent hashes that have already been marked as seen by peer"
660 );
661
662 self.report_already_seen(peer_id);
663 }
664
665 if msg.is_empty() {
667 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
668 return;
669 }
670
671 let original_len = msg.len();
672 let mut partially_valid_msg = msg.dedup();
673
674 if partially_valid_msg.len() != original_len {
675 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
676 }
677
678 partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
680
681 let mut should_report_peer = false;
688 let mut tx_types_counter = TxTypesCounter::default();
689
690 let has_eth68_metadata = partially_valid_msg
691 .msg_version()
692 .expect("partially valid announcement should have a version")
693 .has_eth68_metadata();
694
695 partially_valid_msg.retain(|tx_hash, metadata_ref_mut| {
696 let (ty_byte, size_val) = match *metadata_ref_mut {
697 Some((ty, size)) => {
698 if !has_eth68_metadata {
699 should_report_peer = true;
700 }
701 (ty, size)
702 }
703 None => {
704 if has_eth68_metadata {
705 should_report_peer = true;
706 return false;
707 }
708 (0u8, 0)
709 }
710 };
711
712 if has_eth68_metadata && let Some((actual_ty_byte, _)) = *metadata_ref_mut {
713 match TxType::try_from(actual_ty_byte) {
714 Ok(parsed_tx_type) => tx_types_counter.increase_by_tx_type(parsed_tx_type),
715 Err(_) => tx_types_counter.increase_other(),
716 }
717 }
718
719 let decision = self
720 .policies
721 .announcement_filter()
722 .decide_on_announcement(ty_byte, tx_hash, size_val);
723
724 match decision {
725 AnnouncementAcceptance::Accept => true,
726 AnnouncementAcceptance::Ignore => false,
727 AnnouncementAcceptance::Reject { penalize_peer } => {
728 if penalize_peer {
729 should_report_peer = true;
730 }
731 false
732 }
733 }
734 });
735
736 if has_eth68_metadata {
737 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
738 }
739
740 if should_report_peer {
741 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
742 }
743
744 let hashes_count_pre_pool_filter = partially_valid_msg.len();
752 self.pool.retain_unknown(&mut partially_valid_msg);
753 if hashes_count_pre_pool_filter > partially_valid_msg.len() {
754 let already_known_hashes_count =
755 hashes_count_pre_pool_filter - partially_valid_msg.len();
756 self.metrics
757 .occurrences_hashes_already_in_pool
758 .increment(already_known_hashes_count as u64);
759 }
760
761 if partially_valid_msg.is_empty() {
762 return
764 }
765
766 let mut valid_announcement_data =
767 ValidAnnouncementData::from_partially_valid_data(partially_valid_msg);
768
769 if valid_announcement_data.is_empty() {
770 return
772 }
773
774 let bad_imports = &self.bad_imports;
781 self.transaction_fetcher.filter_unseen_and_pending_hashes(
782 &mut valid_announcement_data,
783 |hash| bad_imports.contains(hash),
784 &peer_id,
785 &client,
786 );
787
788 if valid_announcement_data.is_empty() {
789 return
791 }
792
793 trace!(target: "net::tx::propagation",
794 peer_id=format!("{peer_id:#}"),
795 hashes_len=valid_announcement_data.len(),
796 hashes=?valid_announcement_data.keys(),
797 msg_version=%valid_announcement_data.msg_version(),
798 client_version=%client,
799 "received previously unseen and pending hashes in announcement from peer"
800 );
801
802 if !self.transaction_fetcher.is_idle(&peer_id) {
805 let msg_version = valid_announcement_data.msg_version();
807 let (hashes, _version) = valid_announcement_data.into_request_hashes();
808
809 trace!(target: "net::tx",
810 peer_id=format!("{peer_id:#}"),
811 hashes=?*hashes,
812 %msg_version,
813 %client,
814 "buffering hashes announced by busy peer"
815 );
816
817 self.transaction_fetcher.buffer_hashes(hashes, Some(peer_id));
818
819 return
820 }
821
822 let mut hashes_to_request =
823 RequestTxHashes::with_capacity(valid_announcement_data.len() / 4);
824 let surplus_hashes =
825 self.transaction_fetcher.pack_request(&mut hashes_to_request, valid_announcement_data);
826
827 if !surplus_hashes.is_empty() {
828 trace!(target: "net::tx",
829 peer_id=format!("{peer_id:#}"),
830 surplus_hashes=?*surplus_hashes,
831 %client,
832 "some hashes in announcement from peer didn't fit in `GetPooledTransactions` request, buffering surplus hashes"
833 );
834
835 self.transaction_fetcher.buffer_hashes(surplus_hashes, Some(peer_id));
836 }
837
838 trace!(target: "net::tx",
839 peer_id=format!("{peer_id:#}"),
840 hashes=?*hashes_to_request,
841 %client,
842 "sending hashes in `GetPooledTransactions` request to peer's session"
843 );
844
845 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
849 if let Some(failed_to_request_hashes) =
850 self.transaction_fetcher.request_transactions_from_peer(hashes_to_request, peer)
851 {
852 let conn_eth_version = peer.version;
853
854 trace!(target: "net::tx",
855 peer_id=format!("{peer_id:#}"),
856 failed_to_request_hashes=?*failed_to_request_hashes,
857 %conn_eth_version,
858 %client,
859 "sending `GetPooledTransactions` request to peer's session failed, buffering hashes"
860 );
861 self.transaction_fetcher.buffer_hashes(failed_to_request_hashes, Some(peer_id));
862 }
863 }
864}
865
866impl<Pool, N> TransactionsManager<Pool, N>
867where
868 Pool: TransactionPool + Unpin + 'static,
869 N: NetworkPrimitives<
870 BroadcastedTransaction: SignedTransaction,
871 PooledTransaction: SignedTransaction,
872 > + Unpin,
873 Pool::Transaction:
874 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
875{
876 fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
888 if self.network.tx_gossip_disabled() {
892 return
893 }
894
895 trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
896
897 self.propagate_all(hashes);
898 }
899
900 fn propagate_full_transactions_to_peer(
904 &mut self,
905 txs: Vec<TxHash>,
906 peer_id: PeerId,
907 propagation_mode: PropagationMode,
908 ) -> Option<PropagatedTransactions> {
909 let peer = self.peers.get_mut(&peer_id)?;
910 trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
911 let mut propagated = PropagatedTransactions::default();
912
913 let mut full_transactions = FullTransactionsBuilder::new(peer.version);
915
916 let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
917
918 if propagation_mode.is_forced() {
919 full_transactions.extend(to_propagate);
921 } else {
922 for tx in to_propagate {
925 if !peer.seen_transactions.contains(tx.tx_hash()) {
926 full_transactions.push(&tx);
928 }
929 }
930 }
931
932 if full_transactions.is_empty() {
933 return None
935 }
936
937 let PropagateTransactions { pooled, full } = full_transactions.build();
938
939 if let Some(new_pooled_hashes) = pooled {
941 for hash in new_pooled_hashes.iter_hashes().copied() {
942 propagated.record(hash, PropagateKind::Hash(peer_id));
943 peer.seen_transactions.insert(hash);
945 }
946
947 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
949 }
950
951 if let Some(new_full_transactions) = full {
953 for hash in new_full_transactions.iter_hashes() {
954 propagated.record(*hash, PropagateKind::Full(peer_id));
955 peer.seen_transactions.insert(*hash);
957 }
958
959 self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
961 }
962
963 self.metrics.propagated_transactions.increment(propagated.len() as u64);
965
966 Some(propagated)
967 }
968
969 fn propagate_hashes_to(
973 &mut self,
974 hashes: Vec<TxHash>,
975 peer_id: PeerId,
976 propagation_mode: PropagationMode,
977 ) {
978 trace!(target: "net::tx", "Start propagating transactions as hashes");
979
980 let propagated = {
983 let Some(peer) = self.peers.get_mut(&peer_id) else {
984 return
986 };
987
988 let to_propagate =
989 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
990
991 let mut propagated = PropagatedTransactions::default();
992
993 let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
995
996 if propagation_mode.is_forced() {
997 hashes.extend(to_propagate)
998 } else {
999 for tx in to_propagate {
1000 if !peer.seen_transactions.contains(tx.tx_hash()) {
1001 hashes.push(&tx);
1003 }
1004 }
1005 }
1006
1007 let new_pooled_hashes = hashes.build();
1008
1009 if new_pooled_hashes.is_empty() {
1010 return
1012 }
1013
1014 for hash in new_pooled_hashes.iter_hashes().copied() {
1015 propagated.record(hash, PropagateKind::Hash(peer_id));
1016 peer.seen_transactions.insert(hash);
1017 }
1018
1019 trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
1020
1021 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1023
1024 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1026
1027 propagated
1028 };
1029
1030 self.pool.on_propagated(propagated);
1032 }
1033
1034 fn propagate_transactions(
1041 &mut self,
1042 to_propagate: Vec<PropagateTransaction>,
1043 propagation_mode: PropagationMode,
1044 ) -> PropagatedTransactions {
1045 let mut propagated = PropagatedTransactions::default();
1046 if self.network.tx_gossip_disabled() {
1047 return propagated
1048 }
1049
1050 let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1052
1053 let mut num_full_peers = 0;
1055 for (peer_id, peer) in &mut self.peers {
1056 if !self.policies.propagation_policy().can_propagate(peer) {
1057 continue
1059 }
1060
1061 let mut builder = if num_full_peers < max_num_full {
1063 num_full_peers += 1;
1064 PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1065 } else {
1066 PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1067 };
1068
1069 if propagation_mode.is_forced() {
1072 for tx in &to_propagate {
1073 peer.seen_transactions.insert(*tx.tx_hash());
1074 builder.push(tx);
1075 }
1076 } else {
1077 for tx in &to_propagate {
1081 if peer.seen_transactions.insert(*tx.tx_hash()) {
1083 builder.push(tx);
1084 }
1085 }
1086 }
1087
1088 if builder.is_empty() {
1089 trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1090 continue
1091 }
1092
1093 let PropagateTransactions { pooled, full } = builder.build();
1094
1095 if let Some(mut new_pooled_hashes) = pooled {
1097 if new_pooled_hashes.len() >
1101 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1102 {
1103 for hash in new_pooled_hashes
1106 .iter_hashes()
1107 .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1108 {
1109 peer.seen_transactions.remove(hash);
1110 }
1111 new_pooled_hashes.truncate(
1112 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1113 );
1114 }
1115
1116 for hash in new_pooled_hashes.iter_hashes().copied() {
1117 propagated.record(hash, PropagateKind::Hash(*peer_id));
1118 }
1119
1120 trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1121
1122 self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1124 }
1125
1126 if let Some(new_full_transactions) = full {
1128 for hash in new_full_transactions.iter_hashes() {
1129 propagated.record(*hash, PropagateKind::Full(*peer_id));
1130 }
1131
1132 trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1133
1134 self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1136 }
1137 }
1138
1139 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1141
1142 propagated
1143 }
1144
1145 fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1150 if self.peers.is_empty() {
1151 return
1153 }
1154 let propagated = self.propagate_transactions(
1155 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1156 PropagationMode::Basic,
1157 );
1158
1159 self.pool.on_propagated(propagated);
1161 }
1162
1163 fn on_get_pooled_transactions(
1165 &mut self,
1166 peer_id: PeerId,
1167 request: GetPooledTransactions,
1168 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1169 ) {
1170 if self.network.tx_gossip_disabled() {
1172 let _ = response.send(Ok(PooledTransactions::default()));
1173 return
1174 }
1175 if let Some(peer) = self.peers.get_mut(&peer_id) {
1176 let transactions = self.pool.get_pooled_transaction_elements(
1177 request.0,
1178 GetPooledTransactionLimit::ResponseSizeSoftLimit(
1179 self.transaction_fetcher.info.soft_limit_byte_size_pooled_transactions_response,
1180 ),
1181 );
1182 trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1183
1184 peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1187
1188 let resp = PooledTransactions(transactions);
1189 let _ = response.send(Ok(resp));
1190 }
1191 }
1192
1193 fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1195 match cmd {
1196 TransactionsCommand::PropagateHash(hash) => {
1197 self.on_new_pending_transactions(vec![hash])
1198 }
1199 TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1200 self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1201 }
1202 TransactionsCommand::GetActivePeers(tx) => {
1203 let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1204 tx.send(peers).ok();
1205 }
1206 TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1207 if let Some(propagated) =
1208 self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1209 {
1210 self.pool.on_propagated(propagated);
1211 }
1212 }
1213 TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1214 TransactionsCommand::BroadcastTransactions(txs) => {
1215 let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1216 self.pool.on_propagated(propagated);
1217 }
1218 TransactionsCommand::GetTransactionHashes { peers, tx } => {
1219 let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1220 for peer_id in peers {
1221 let hashes = self
1222 .peers
1223 .get(&peer_id)
1224 .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1225 .unwrap_or_default();
1226 res.insert(peer_id, hashes);
1227 }
1228 tx.send(res).ok();
1229 }
1230 TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1231 let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1232 peer_request_sender.send(sender).ok();
1233 }
1234 }
1235 }
1236
1237 fn handle_peer_session(
1241 &mut self,
1242 info: SessionInfo,
1243 messages: PeerRequestSender<PeerRequest<N>>,
1244 ) {
1245 let SessionInfo { peer_id, client_version, version, .. } = info;
1246
1247 let peer = PeerMetadata::<N>::new(
1249 messages,
1250 version,
1251 client_version,
1252 self.config.max_transactions_seen_by_peer_history,
1253 info.peer_kind,
1254 );
1255 let peer = match self.peers.entry(peer_id) {
1256 Entry::Occupied(mut entry) => {
1257 entry.insert(peer);
1258 entry.into_mut()
1259 }
1260 Entry::Vacant(entry) => entry.insert(peer),
1261 };
1262
1263 self.policies.propagation_policy_mut().on_session_established(peer);
1264
1265 if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1269 trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1270 return
1271 }
1272
1273 let pooled_txs = self.pool.pooled_transactions_max(
1275 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1276 );
1277 if pooled_txs.is_empty() {
1278 trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1279 return;
1280 }
1281
1282 let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1284 for pooled_tx in pooled_txs {
1285 peer.seen_transactions.insert(*pooled_tx.hash());
1286 msg_builder.push_pooled(pooled_tx);
1287 }
1288
1289 debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1290 let msg = msg_builder.build();
1291 self.network.send_transactions_hashes(peer_id, msg);
1292 }
1293
1294 fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1296 match event_result {
1297 NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1298 self.on_peer_session_closed(&peer_id);
1299 }
1300 NetworkEvent::ActivePeerSession { info, messages } => {
1301 self.handle_peer_session(info, messages);
1303 }
1304 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1305 let peer_id = info.peer_id;
1306 let messages = match self.peers.get(&peer_id) {
1308 Some(p) => p.request_tx.clone(),
1309 None => {
1310 debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1311 return;
1312 }
1313 };
1314 self.handle_peer_session(info, messages);
1315 }
1316 _ => {}
1317 }
1318 }
1319
1320 fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1322 if self.config.ingress_policy.allows_all() {
1323 return true;
1324 }
1325 let Some(peer) = self.peers.get(peer_id) else {
1326 return false;
1327 };
1328 self.config.ingress_policy.allows(peer.peer_kind())
1329 }
1330
1331 fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1333 match event {
1334 NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1335 if !self.accepts_incoming_from(&peer_id) {
1336 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1337 return;
1338 }
1339
1340 let has_blob_txs = msg.has_eip4844();
1344
1345 let non_blob_txs = msg
1346 .into_iter()
1347 .map(N::PooledTransaction::try_from)
1348 .filter_map(Result::ok)
1349 .collect();
1350
1351 self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1352
1353 if has_blob_txs {
1354 debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1355 self.report_peer_bad_transactions(peer_id);
1356 }
1357 }
1358 NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1359 if !self.accepts_incoming_from(&peer_id) {
1360 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1361 return;
1362 }
1363 self.on_new_pooled_transaction_hashes(peer_id, msg)
1364 }
1365 NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1366 self.on_get_pooled_transactions(peer_id, request, response)
1367 }
1368 NetworkTransactionEvent::GetTransactionsHandle(response) => {
1369 let _ = response.send(Some(self.handle()));
1370 }
1371 }
1372 }
1373
1374 fn import_transactions(
1376 &mut self,
1377 peer_id: PeerId,
1378 transactions: PooledTransactions<N::PooledTransaction>,
1379 source: TransactionSource,
1380 ) {
1381 if self.network.is_initially_syncing() {
1383 return
1384 }
1385 if self.network.tx_gossip_disabled() {
1386 return
1387 }
1388
1389 if !self.has_capacity_for_pending_pool_imports() {
1391 return
1392 }
1393
1394 let mut transactions = transactions.0;
1395
1396 let capacity = self.remaining_pool_import_capacity();
1400 if transactions.len() > capacity {
1401 let skipped = transactions.len() - capacity;
1402 transactions.truncate(capacity);
1403 self.metrics
1404 .skipped_transactions_pending_pool_imports_at_capacity
1405 .increment(skipped as u64);
1406 trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
1407 }
1408
1409 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
1410 let client_version = peer.client_version.clone();
1411
1412 let start = Instant::now();
1413
1414 self.transaction_fetcher
1416 .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1417
1418 let mut num_already_seen_by_peer = 0;
1423 for tx in &transactions {
1424 if source.is_broadcast() && !peer.seen_transactions.insert(*tx.tx_hash()) {
1425 num_already_seen_by_peer += 1;
1426 }
1427 }
1428
1429 let mut has_bad_transactions = false;
1431
1432 transactions.retain(|tx| {
1435 if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1436 let peers = entry.get_mut();
1437 if !peers.contains(&peer_id) {
1438 peers.push(peer_id);
1439 }
1440 return false
1441 }
1442 if self.bad_imports.contains(tx.tx_hash()) {
1443 trace!(target: "net::tx",
1444 peer_id=format!("{peer_id:#}"),
1445 hash=%tx.tx_hash(),
1446 %client_version,
1447 "received a known bad transaction from peer"
1448 );
1449 has_bad_transactions = true;
1450 return false;
1451 }
1452 true
1453 });
1454
1455 let txns_count_pre_pool_filter = transactions.len();
1457 self.pool.retain_unknown(&mut transactions);
1458 if txns_count_pre_pool_filter > transactions.len() {
1459 let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1460 self.metrics
1461 .occurrences_transactions_already_in_pool
1462 .increment(already_known_txns_count as u64);
1463 }
1464
1465 let txs_len = transactions.len();
1466
1467 let recover = |tx| {
1468 let recovered = if let Some(cache) = &self.sender_recovery_cache {
1469 Pool::Transaction::try_recover_with_cache(tx, cache)
1470 } else {
1471 Pool::Transaction::try_recover(tx)
1472 };
1473 match recovered {
1474 Ok(tx) => Some(tx),
1475 Err(badtx) => {
1476 trace!(target: "net::tx",
1477 peer_id=format!("{peer_id:#}"),
1478 hash=%badtx.tx_hash(),
1479 client_version=%client_version,
1480 "failed ecrecovery for transaction"
1481 );
1482 None
1483 }
1484 }
1485 };
1486
1487 let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1488
1489 has_bad_transactions |= new_txs.len() != txs_len;
1490
1491 for tx in &new_txs {
1493 self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1494 }
1495
1496 if !new_txs.is_empty() {
1499 let pool = self.pool.clone();
1500 let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1502 metric_pending_pool_imports.increment(new_txs.len() as f64);
1503
1504 self.pending_pool_imports_info
1506 .pending_pool_imports
1507 .fetch_add(new_txs.len(), Ordering::Relaxed);
1508 let tx_manager_info_pending_pool_imports =
1509 self.pending_pool_imports_info.pending_pool_imports.clone();
1510
1511 trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1512 let import = Box::pin(async move {
1513 let added = new_txs.len();
1514 let res = pool.add_external_transactions(new_txs).await;
1515
1516 metric_pending_pool_imports.decrement(added as f64);
1518 tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1520
1521 res
1522 });
1523
1524 self.pool_imports.push(import);
1525 }
1526
1527 if num_already_seen_by_peer > 0 {
1528 self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1529 self.metrics
1530 .occurrences_of_transaction_already_seen_by_peer
1531 .increment(num_already_seen_by_peer);
1532 trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1533 }
1534
1535 if has_bad_transactions {
1536 self.report_peer_bad_transactions(peer_id)
1538 }
1539
1540 if num_already_seen_by_peer > 0 {
1541 self.report_already_seen(peer_id);
1542 }
1543
1544 self.metrics.pool_import_prepare_duration.record(start.elapsed());
1545 }
1546
1547 fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1549 match fetch_event {
1550 FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => {
1551 self.import_transactions(peer_id, transactions, TransactionSource::Response);
1552 if report_peer {
1553 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1554 }
1555 }
1556 FetchEvent::FetchError { peer_id, error } => {
1557 trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1558 self.on_request_error(peer_id, error);
1559 }
1560 FetchEvent::EmptyResponse { peer_id } => {
1561 trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1562 }
1563 }
1564 }
1565}
1566
1567impl<
1575 Pool: TransactionPool + Unpin + 'static,
1576 N: NetworkPrimitives<
1577 BroadcastedTransaction: SignedTransaction,
1578 PooledTransaction: SignedTransaction,
1579 > + Unpin,
1580 > Future for TransactionsManager<Pool, N>
1581where
1582 Pool::Transaction:
1583 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1584{
1585 type Output = ();
1586
1587 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1588 let start = Instant::now();
1589 let mut poll_durations = TxManagerPollDurations::default();
1590
1591 let this = self.get_mut();
1592
1593 let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1599 poll_durations.acc_network_events,
1600 "net::tx",
1601 "Network events stream",
1602 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1603 this.network_events.poll_next_unpin(cx),
1604 |event| this.on_network_event(event)
1605 );
1606
1607 let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1622 poll_durations.acc_tx_events,
1623 "net::tx",
1624 "Network transaction events stream",
1625 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1626 this.transaction_events.poll_next_unpin(cx),
1627 |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1628 );
1629
1630 let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1641 poll_durations.acc_fetch_events,
1642 "net::tx",
1643 "Transaction fetch events stream",
1644 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1645 this.transaction_fetcher.poll_next_unpin(cx),
1646 |event| this.on_fetch_event(event),
1647 );
1648
1649 let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1664 poll_durations.acc_pending_imports,
1665 "net::tx",
1666 "Batched pool imports stream",
1667 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1668 this.pool_imports.poll_next_unpin(cx),
1669 |batch_results| this.on_batch_import_result(batch_results)
1670 );
1671
1672 let mut new_txs = Vec::new();
1685 let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1686 cx,
1687 &mut new_txs,
1688 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1689 ) {
1690 Poll::Ready(count) => {
1691 if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1692 true
1695 } else {
1696 let limit =
1700 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1701 new_txs.len();
1702 this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1703 }
1704 }
1705 Poll::Pending => false,
1706 };
1707 if !new_txs.is_empty() {
1708 this.on_new_pending_transactions(new_txs);
1709 }
1710
1711 duration_metered_exec!(
1716 {
1717 if this.has_capacity_for_fetching_pending_hashes() &&
1718 this.on_fetch_hashes_pending_fetch()
1719 {
1720 maybe_more_tx_fetch_events = true;
1721 }
1722 },
1723 poll_durations.acc_pending_fetch
1724 );
1725
1726 let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1728 poll_durations.acc_cmds,
1729 "net::tx",
1730 "Commands channel",
1731 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1732 this.command_rx.poll_next_unpin(cx),
1733 |cmd| this.on_command(cmd)
1734 );
1735
1736 this.transaction_fetcher.update_metrics();
1737
1738 if maybe_more_network_events ||
1740 maybe_more_commands ||
1741 maybe_more_tx_events ||
1742 maybe_more_tx_fetch_events ||
1743 maybe_more_pool_imports ||
1744 maybe_more_pending_txns
1745 {
1746 cx.waker().wake_by_ref();
1748 return Poll::Pending
1749 }
1750
1751 this.update_poll_metrics(start, poll_durations);
1752
1753 Poll::Pending
1754 }
1755}
1756
1757#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1761enum PropagationMode {
1762 Basic,
1766 Forced,
1771}
1772
1773impl PropagationMode {
1774 const fn is_forced(self) -> bool {
1776 matches!(self, Self::Forced)
1777 }
1778}
1779
1780#[derive(Debug, Clone)]
1782struct PropagateTransaction {
1783 is_broadcastable_in_full: bool,
1784 propagation_size: usize,
1791 transaction: LazyEncodedTransaction,
1792}
1793
1794impl PropagateTransaction {
1795 fn new<T: SignedTransaction>(transaction: T) -> Self {
1801 let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1802 let propagation_size = transaction.encode_2718_len();
1803
1804 Self {
1805 is_broadcastable_in_full,
1806 propagation_size,
1807 transaction: LazyEncoded::new(transaction),
1808 }
1809 }
1810
1811 fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1817 let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1818 let propagation_size = tx.encoded_length();
1819 Self {
1820 is_broadcastable_in_full,
1821 propagation_size,
1822 transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1823 }
1824 }
1825
1826 fn tx_hash(&self) -> &TxHash {
1827 self.transaction.tx_hash()
1828 }
1829
1830 const fn propagation_size(&self) -> usize {
1832 self.propagation_size
1833 }
1834
1835 fn tx_type(&self) -> u8 {
1836 self.transaction.ty()
1837 }
1838
1839 const fn is_broadcastable_in_full(&self) -> bool {
1840 self.is_broadcastable_in_full
1841 }
1842
1843 fn shared(&self) -> LazyEncodedTransaction {
1844 self.transaction.clone()
1845 }
1846}
1847
1848#[derive(Debug)]
1850struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1851 transaction: Arc<ValidPoolTransaction<P>>,
1852}
1853
1854impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1855 const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1856 Self { transaction }
1857 }
1858
1859 fn encode_uncached(&self, out: &mut dyn BufMut) {
1860 (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1861 }
1862}
1863
1864impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1865 fn encode(&self, out: &mut dyn BufMut) {
1866 self.encode_uncached(out);
1867 }
1868
1869 fn length(&self) -> usize {
1870 (*self.transaction.transaction.consensus_ref().inner()).length()
1871 }
1872}
1873
1874impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1875 fn tx_hash(&self) -> &TxHash {
1876 self.transaction.hash()
1877 }
1878}
1879
1880impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1881 fn ty(&self) -> u8 {
1882 self.transaction.transaction.ty()
1883 }
1884}
1885
1886#[derive(Debug, Clone)]
1889enum PropagateTransactionsBuilder {
1890 Pooled(PooledTransactionsHashesBuilder),
1891 Full(FullTransactionsBuilder),
1892}
1893
1894impl PropagateTransactionsBuilder {
1895 fn pooled(version: EthVersion, capacity: usize) -> Self {
1898 Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1899 }
1900
1901 fn full(version: EthVersion, capacity: usize) -> Self {
1904 Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1905 }
1906
1907 fn is_empty(&self) -> bool {
1909 match self {
1910 Self::Pooled(builder) => builder.is_empty(),
1911 Self::Full(builder) => builder.is_empty(),
1912 }
1913 }
1914
1915 fn build(self) -> PropagateTransactions {
1917 match self {
1918 Self::Pooled(pooled) => {
1919 PropagateTransactions { pooled: Some(pooled.build()), full: None }
1920 }
1921 Self::Full(full) => full.build(),
1922 }
1923 }
1924}
1925
1926impl PropagateTransactionsBuilder {
1927 fn push(&mut self, transaction: &PropagateTransaction) {
1929 match self {
1930 Self::Pooled(builder) => builder.push(transaction),
1931 Self::Full(builder) => builder.push(transaction),
1932 }
1933 }
1934}
1935
1936struct PropagateTransactions {
1938 pooled: Option<NewPooledTransactionHashes>,
1940 full: Option<BroadcastPoolTransactions>,
1942}
1943
1944#[derive(Debug, Clone)]
1949struct FullTransactionsBuilder {
1950 total_size: usize,
1952 transactions: Vec<LazyEncodedTransaction>,
1954 pooled: PooledTransactionsHashesBuilder,
1956}
1957
1958impl FullTransactionsBuilder {
1959 fn new(version: EthVersion) -> Self {
1961 Self {
1962 total_size: 0,
1963 pooled: PooledTransactionsHashesBuilder::new(version),
1964 transactions: vec![],
1965 }
1966 }
1967
1968 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
1973 Self {
1974 total_size: 0,
1975 pooled: PooledTransactionsHashesBuilder::new(version),
1976 transactions: Vec::with_capacity(capacity),
1977 }
1978 }
1979
1980 fn is_empty(&self) -> bool {
1982 self.transactions.is_empty() && self.pooled.is_empty()
1983 }
1984
1985 fn build(self) -> PropagateTransactions {
1987 let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
1988 let full =
1989 (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
1990 PropagateTransactions { pooled, full }
1991 }
1992
1993 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
1995 for tx in txs {
1996 self.push(&tx)
1997 }
1998 }
1999
2000 fn push(&mut self, transaction: &PropagateTransaction) {
2010 if !transaction.is_broadcastable_in_full() {
2019 self.pooled.push(transaction);
2020 return
2021 }
2022
2023 let new_size = self.total_size + transaction.propagation_size();
2024 if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2025 self.total_size > 0
2026 {
2027 self.pooled.push(transaction);
2029 return
2030 }
2031
2032 self.total_size = new_size;
2033 self.transactions.push(transaction.shared());
2034 }
2035}
2036
2037#[derive(Debug, Clone)]
2040enum PooledTransactionsHashesBuilder {
2041 Eth66(NewPooledTransactionHashes66),
2042 Eth68(NewPooledTransactionHashes68),
2043 Eth72(NewPooledTransactionHashes72),
2044}
2045
2046impl PooledTransactionsHashesBuilder {
2049 fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2051 match self {
2052 Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2053 Self::Eth68(msg) => {
2054 msg.hashes.push(*pooled_tx.hash());
2055 msg.sizes.push(pooled_tx.encoded_length());
2056 msg.types.push(pooled_tx.transaction.ty());
2057 }
2058 Self::Eth72(msg) => {
2059 msg.hashes.push(*pooled_tx.hash());
2060 msg.sizes.push(pooled_tx.encoded_length());
2061 msg.types.push(pooled_tx.transaction.ty());
2062 }
2063 }
2064 }
2065
2066 fn is_empty(&self) -> bool {
2068 match self {
2069 Self::Eth66(hashes) => hashes.is_empty(),
2070 Self::Eth68(hashes) => hashes.is_empty(),
2071 Self::Eth72(hashes) => hashes.is_empty(),
2072 }
2073 }
2074
2075 fn len(&self) -> usize {
2077 match self {
2078 Self::Eth66(hashes) => hashes.len(),
2079 Self::Eth68(hashes) => hashes.len(),
2080 Self::Eth72(hashes) => hashes.len(),
2081 }
2082 }
2083
2084 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2086 for tx in txs {
2087 self.push(&tx);
2088 }
2089 }
2090
2091 fn push(&mut self, tx: &PropagateTransaction) {
2092 match self {
2093 Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2094 Self::Eth68(msg) => {
2095 msg.hashes.push(*tx.tx_hash());
2096 msg.sizes.push(tx.propagation_size());
2097 msg.types.push(tx.tx_type());
2098 }
2099 Self::Eth72(msg) => {
2100 msg.hashes.push(*tx.tx_hash());
2101 msg.sizes.push(tx.propagation_size());
2102 msg.types.push(tx.tx_type());
2103 }
2104 }
2105 }
2106
2107 fn new(version: EthVersion) -> Self {
2109 match version {
2110 EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2111 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2112 Self::Eth68(Default::default())
2113 }
2114 EthVersion::Eth72 => Self::Eth72(Default::default()),
2115 }
2116 }
2117
2118 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2121 match version {
2122 EthVersion::Eth66 | EthVersion::Eth67 => {
2123 Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2124 }
2125 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2126 Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2127 }
2128 EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2129 }
2130 }
2131
2132 fn build(self) -> NewPooledTransactionHashes {
2133 match self {
2134 Self::Eth66(mut msg) => {
2135 msg.shrink_to_fit();
2136 msg.into()
2137 }
2138 Self::Eth68(mut msg) => {
2139 msg.shrink_to_fit();
2140 msg.into()
2141 }
2142 Self::Eth72(mut msg) => {
2143 msg.shrink_to_fit();
2144 msg.into()
2145 }
2146 }
2147 }
2148}
2149
2150enum TransactionSource {
2152 Broadcast,
2154 Response,
2156}
2157
2158impl TransactionSource {
2161 const fn is_broadcast(&self) -> bool {
2163 matches!(self, Self::Broadcast)
2164 }
2165}
2166
2167#[derive(Debug)]
2169pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2170 seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2174 request_tx: PeerRequestSender<PeerRequest<N>>,
2176 version: EthVersion,
2178 client_version: Arc<str>,
2180 peer_kind: PeerKind,
2182}
2183
2184impl<N: NetworkPrimitives> PeerMetadata<N> {
2185 pub fn new(
2187 request_tx: PeerRequestSender<PeerRequest<N>>,
2188 version: EthVersion,
2189 client_version: Arc<str>,
2190 max_transactions_seen_by_peer: u32,
2191 peer_kind: PeerKind,
2192 ) -> Self {
2193 Self {
2194 seen_transactions: LruCache::with_hasher(
2195 max_transactions_seen_by_peer,
2196 Default::default(),
2197 ),
2198 request_tx,
2199 version,
2200 client_version,
2201 peer_kind,
2202 }
2203 }
2204
2205 pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2207 &self.request_tx
2208 }
2209
2210 pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2212 &mut self.seen_transactions
2213 }
2214
2215 pub const fn version(&self) -> EthVersion {
2217 self.version
2218 }
2219
2220 pub fn client_version(&self) -> &str {
2222 &self.client_version
2223 }
2224
2225 pub const fn peer_kind(&self) -> PeerKind {
2227 self.peer_kind
2228 }
2229}
2230
2231#[derive(Debug)]
2233enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2234 PropagateHash(B256),
2236 PropagateHashesTo(Vec<B256>, PeerId),
2238 GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2240 PropagateTransactionsTo(Vec<TxHash>, PeerId),
2242 PropagateTransactions(Vec<TxHash>),
2244 BroadcastTransactions(Vec<PropagateTransaction>),
2246 GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2248 GetPeerSender {
2250 peer_id: PeerId,
2251 peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2252 },
2253}
2254
2255#[derive(Debug)]
2257pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2258 IncomingTransactions {
2262 peer_id: PeerId,
2264 msg: Transactions<N::BroadcastedTransaction>,
2266 },
2267 IncomingPooledTransactionHashes {
2269 peer_id: PeerId,
2271 msg: NewPooledTransactionHashes,
2273 },
2274 GetPooledTransactions {
2276 peer_id: PeerId,
2278 request: GetPooledTransactions,
2280 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2282 },
2283 GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2285}
2286
2287#[derive(Debug)]
2289pub struct PendingPoolImportsInfo {
2290 pending_pool_imports: Arc<AtomicUsize>,
2292 max_pending_pool_imports: usize,
2294}
2295
2296impl PendingPoolImportsInfo {
2297 pub fn new(max_pending_pool_imports: usize) -> Self {
2299 Self { pending_pool_imports: Arc::new(AtomicUsize::default()), max_pending_pool_imports }
2300 }
2301
2302 pub fn has_capacity(&self, max_pending_pool_imports: usize) -> bool {
2304 self.pending_pool_imports.load(Ordering::Relaxed) < max_pending_pool_imports
2305 }
2306}
2307
2308impl Default for PendingPoolImportsInfo {
2309 fn default() -> Self {
2310 Self::new(DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS)
2311 }
2312}
2313
2314#[derive(Debug, Default)]
2315struct TxManagerPollDurations {
2316 acc_network_events: Duration,
2317 acc_pending_imports: Duration,
2318 acc_tx_events: Duration,
2319 acc_imported_txns: Duration,
2320 acc_fetch_events: Duration,
2321 acc_pending_fetch: Duration,
2322 acc_cmds: Duration,
2323}
2324
2325impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2326 fn size(&self) -> usize {
2329 match self {
2330 Self::IncomingTransactions { peer_id, msg } => {
2331 core::mem::size_of_val(peer_id) +
2332 msg.0.iter().map(InMemorySize::size).sum::<usize>()
2333 }
2334 Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2335 core::mem::size_of_val(peer_id) + msg.size()
2336 }
2337 Self::GetPooledTransactions { peer_id, request, response } => {
2338 core::mem::size_of_val(peer_id) +
2339 request.0.len() * core::mem::size_of::<TxHash>() +
2340 core::mem::size_of_val(response)
2341 }
2342 Self::GetTransactionsHandle(_) => 0,
2343 }
2344 }
2345}
2346
2347#[cfg(test)]
2348mod tests {
2349 use super::*;
2350 use crate::{
2351 test_utils::{
2352 transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2353 Testnet,
2354 },
2355 transactions::config::RelaxedEthAnnouncementFilter,
2356 NetworkConfigBuilder, NetworkManager,
2357 };
2358 use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2359 use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2360 use alloy_primitives::{hex, Signature, TxKind, B256, U256};
2361 use alloy_rlp::Decodable;
2362 use futures::FutureExt;
2363 use reth_chainspec::MIN_TRANSACTION_GAS;
2364 use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2365 use reth_network_api::{NetworkInfo, PeerKind};
2366 use reth_network_p2p::{
2367 error::{RequestError, RequestResult},
2368 sync::{NetworkSyncUpdater, SyncState},
2369 };
2370 use reth_storage_api::noop::NoopProvider;
2371 use reth_tasks::Runtime;
2372 use reth_transaction_pool::{
2373 blobstore::InMemoryBlobStore,
2374 error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2375 identifier::SenderIdentifiers,
2376 test_utils::{
2377 testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2378 TransactionGenerator,
2379 },
2380 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2381 };
2382 use secp256k1::SecretKey;
2383 use std::{
2384 future::poll_fn,
2385 net::{IpAddr, Ipv4Addr, SocketAddr},
2386 str::FromStr,
2387 time::Instant,
2388 };
2389 use tracing::error;
2390
2391 type EthTestPool = Pool<
2392 OkValidator<EthPooledTransaction>,
2393 CoinbaseTipOrdering<EthPooledTransaction>,
2394 InMemoryBlobStore,
2395 >;
2396
2397 async fn new_eth_tx_manager() -> (
2398 TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2399 NetworkManager<EthNetworkPrimitives>,
2400 ) {
2401 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2402 let client = NoopProvider::default();
2403
2404 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2405 .listener_port(0)
2406 .disable_discovery()
2407 .build(client);
2408
2409 let pool = Pool::new(
2410 OkValidator::default(),
2411 CoinbaseTipOrdering::default(),
2412 InMemoryBlobStore::default(),
2413 Default::default(),
2414 );
2415
2416 let transactions_manager_config = config.transactions_manager_config.clone();
2417 let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2418 .await
2419 .unwrap()
2420 .into_builder()
2421 .transactions(pool.clone(), transactions_manager_config)
2422 .split_with_handle();
2423
2424 (transactions, network)
2425 }
2426
2427 fn valid_eth_pool_transaction(
2428 transaction: EthPooledTransaction,
2429 ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2430 let mut ids = SenderIdentifiers::default();
2431 let transaction_id =
2432 ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2433
2434 Arc::new(ValidPoolTransaction {
2435 propagate: false,
2436 transaction_id,
2437 transaction,
2438 timestamp: Instant::now(),
2439 origin: TransactionOrigin::External,
2440 authority_ids: None,
2441 })
2442 }
2443
2444 fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2445 tx_gen: &mut TransactionGenerator<R>,
2446 nonce: u64,
2447 ) -> EthPooledTransaction {
2448 EthPooledTransaction::try_from_consensus(
2449 tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2450 )
2451 .unwrap()
2452 }
2453
2454 #[tokio::test(flavor = "multi_thread")]
2455 async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2456 reth_tracing::init_test_tracing();
2457 let net = Testnet::create(3).await;
2458
2459 let mut handles = net.handles();
2460 let handle0 = handles.next().unwrap();
2461 let handle1 = handles.next().unwrap();
2462
2463 drop(handles);
2464 let handle = net.spawn();
2465
2466 let listener0 = handle0.event_listener();
2467 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2468 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2469
2470 let client = NoopProvider::default();
2471 let pool = testing_pool();
2472 let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2473 .disable_discovery()
2474 .listener_port(0)
2475 .build(client);
2476 let transactions_manager_config = config.transactions_manager_config.clone();
2477 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2478 .await
2479 .unwrap()
2480 .into_builder()
2481 .transactions(pool.clone(), transactions_manager_config)
2482 .split_with_handle();
2483
2484 tokio::task::spawn(network);
2485
2486 network_handle.update_sync_state(SyncState::Syncing);
2488 assert!(NetworkInfo::is_syncing(&network_handle));
2489 assert!(NetworkInfo::is_initially_syncing(&network_handle));
2490
2491 let mut established = listener0.take(2);
2493 while let Some(ev) = established.next().await {
2494 match ev {
2495 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2496 transactions
2498 .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2499 }
2500 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2501 ev => {
2502 error!("unexpected event {ev:?}")
2503 }
2504 }
2505 }
2506 let input = hex!(
2508 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2509 );
2510 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2511 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2512 peer_id: *handle1.peer_id(),
2513 msg: Transactions(vec![signed_tx.clone()]),
2514 });
2515 poll_fn(|cx| {
2516 let _ = transactions.poll_unpin(cx);
2517 Poll::Ready(())
2518 })
2519 .await;
2520 assert!(pool.is_empty());
2521 handle.terminate().await;
2522 }
2523
2524 #[tokio::test(flavor = "multi_thread")]
2525 async fn test_tx_broadcasts_through_two_syncs() {
2526 reth_tracing::init_test_tracing();
2527 let net = Testnet::create(3).await;
2528
2529 let mut handles = net.handles();
2530 let handle0 = handles.next().unwrap();
2531 let handle1 = handles.next().unwrap();
2532
2533 drop(handles);
2534 let handle = net.spawn();
2535
2536 let listener0 = handle0.event_listener();
2537 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2538 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2539
2540 let client = NoopProvider::default();
2541 let pool = testing_pool();
2542 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2543 .disable_discovery()
2544 .listener_port(0)
2545 .build(client);
2546 let transactions_manager_config = config.transactions_manager_config.clone();
2547 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2548 .await
2549 .unwrap()
2550 .into_builder()
2551 .transactions(pool.clone(), transactions_manager_config)
2552 .split_with_handle();
2553
2554 tokio::task::spawn(network);
2555
2556 network_handle.update_sync_state(SyncState::Syncing);
2558 assert!(NetworkInfo::is_syncing(&network_handle));
2559 network_handle.update_sync_state(SyncState::Idle);
2560 assert!(!NetworkInfo::is_syncing(&network_handle));
2561 network_handle.update_sync_state(SyncState::Syncing);
2562 assert!(NetworkInfo::is_syncing(&network_handle));
2563
2564 let mut established = listener0.take(2);
2566 while let Some(ev) = established.next().await {
2567 match ev {
2568 NetworkEvent::ActivePeerSession { .. } |
2569 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2570 transactions.on_network_event(ev);
2572 }
2573 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2574 _ => {
2575 error!("unexpected event {ev:?}")
2576 }
2577 }
2578 }
2579 let input = hex!(
2581 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2582 );
2583 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2584 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2585 peer_id: *handle1.peer_id(),
2586 msg: Transactions(vec![signed_tx.clone()]),
2587 });
2588 poll_fn(|cx| {
2589 let _ = transactions.poll_unpin(cx);
2590 Poll::Ready(())
2591 })
2592 .await;
2593 assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2594 assert!(NetworkInfo::is_syncing(&network_handle));
2595 assert!(!pool.is_empty());
2596 handle.terminate().await;
2597 }
2598
2599 #[tokio::test(flavor = "multi_thread")]
2602 async fn test_handle_incoming_transactions_hashes() {
2603 reth_tracing::init_test_tracing();
2604
2605 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2606 let client = NoopProvider::default();
2607
2608 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2609 .listener_port(0)
2611 .disable_discovery()
2612 .build(client);
2613
2614 let pool = testing_pool();
2615
2616 let transactions_manager_config = config.transactions_manager_config.clone();
2617 let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2618 .await
2619 .unwrap()
2620 .into_builder()
2621 .transactions(pool.clone(), transactions_manager_config)
2622 .split_with_handle();
2623
2624 let peer_id_1 = PeerId::new([1; 64]);
2625 let eth_version = EthVersion::Eth66;
2626
2627 let txs = vec![TransactionSigned::new_unhashed(
2628 Transaction::Legacy(TxLegacy {
2629 chain_id: Some(4),
2630 nonce: 15u64,
2631 gas_price: 2200000000,
2632 gas_limit: 34811,
2633 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2634 value: U256::from(1234u64),
2635 input: Default::default(),
2636 }),
2637 Signature::new(
2638 U256::from_str(
2639 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2640 )
2641 .unwrap(),
2642 U256::from_str(
2643 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2644 )
2645 .unwrap(),
2646 true,
2647 ),
2648 )];
2649
2650 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2651
2652 let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2653 tx_manager.peers.insert(peer_id_1, peer_1);
2654
2655 assert!(pool.is_empty());
2656
2657 tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2658 peer_id: peer_id_1,
2659 msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2660 txs_hashes.clone(),
2661 )),
2662 });
2663
2664 let req = to_mock_session_rx
2666 .recv()
2667 .await
2668 .expect("peer_1 session should receive request with buffered hashes");
2669 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2670 assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2671
2672 let message: Vec<PooledTransactionVariant> = txs
2673 .into_iter()
2674 .map(|tx| {
2675 PooledTransactionVariant::try_from(tx)
2676 .expect("Failed to convert MockTransaction to PooledTransaction")
2677 })
2678 .collect();
2679
2680 response
2682 .send(Ok(PooledTransactions(message)))
2683 .expect("should send peer_1 response to tx manager");
2684
2685 poll_fn(|cx| {
2687 let _ = tx_manager.poll_unpin(cx);
2688 Poll::Ready(())
2689 })
2690 .await;
2691
2692 assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2695 }
2696
2697 #[tokio::test(flavor = "multi_thread")]
2698 async fn test_handle_incoming_transactions() {
2699 reth_tracing::init_test_tracing();
2700 let net = Testnet::create(3).await;
2701
2702 let mut handles = net.handles();
2703 let handle0 = handles.next().unwrap();
2704 let handle1 = handles.next().unwrap();
2705
2706 drop(handles);
2707 let handle = net.spawn();
2708
2709 let listener0 = handle0.event_listener();
2710
2711 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2712 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2713
2714 let client = NoopProvider::default();
2715 let pool = testing_pool();
2716 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2717 .disable_discovery()
2718 .listener_port(0)
2719 .build(client);
2720 let transactions_manager_config = config.transactions_manager_config.clone();
2721 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2722 .await
2723 .unwrap()
2724 .into_builder()
2725 .transactions(pool.clone(), transactions_manager_config)
2726 .split_with_handle();
2727 tokio::task::spawn(network);
2728
2729 network_handle.update_sync_state(SyncState::Idle);
2730
2731 assert!(!NetworkInfo::is_syncing(&network_handle));
2732
2733 let mut established = listener0.take(2);
2735 while let Some(ev) = established.next().await {
2736 match ev {
2737 NetworkEvent::ActivePeerSession { .. } |
2738 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2739 transactions.on_network_event(ev);
2741 }
2742 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2743 ev => {
2744 error!("unexpected event {ev:?}")
2745 }
2746 }
2747 }
2748 let input = hex!(
2750 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2751 );
2752 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2753 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2754 peer_id: *handle1.peer_id(),
2755 msg: Transactions(vec![signed_tx.clone()]),
2756 });
2757 assert!(transactions
2758 .transactions_by_peers
2759 .get(signed_tx.tx_hash())
2760 .unwrap()
2761 .contains(handle1.peer_id()));
2762
2763 poll_fn(|cx| {
2765 let _ = transactions.poll_unpin(cx);
2766 Poll::Ready(())
2767 })
2768 .await;
2769
2770 assert!(!pool.is_empty());
2771 assert!(pool.get(signed_tx.tx_hash()).is_some());
2772 handle.terminate().await;
2773 }
2774
2775 #[tokio::test(flavor = "multi_thread")]
2776 async fn test_session_closed_cleans_transaction_peer_state() {
2777 let (mut tx_manager, _network) = new_tx_manager().await;
2778 let peer_id = PeerId::new([1; 64]);
2779 let fallback_peer = PeerId::new([2; 64]);
2780 let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2781 let hash_shared = B256::from_slice(&[1; 32]);
2782
2783 tx_manager.peers.insert(peer_id, peer);
2784 buffer_hash_to_tx_fetcher(
2785 &mut tx_manager.transaction_fetcher,
2786 hash_shared,
2787 peer_id,
2788 0,
2789 None,
2790 );
2791 buffer_hash_to_tx_fetcher(
2792 &mut tx_manager.transaction_fetcher,
2793 hash_shared,
2794 fallback_peer,
2795 0,
2796 None,
2797 );
2798 tx_manager.transaction_fetcher.active_peers.insert(peer_id, 1);
2799
2800 tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2801 peer_id,
2802 reason: None,
2803 }));
2804
2805 assert!(!tx_manager.peers.contains_key(&peer_id));
2807 assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2808 assert_eq!(
2810 tx_manager.transaction_fetcher.get_idle_peer_for(hash_shared),
2811 Some(&fallback_peer)
2812 );
2813 }
2814
2815 #[tokio::test(flavor = "multi_thread")]
2816 async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2817 let (mut tx_manager, _network) = new_tx_manager().await;
2818 let peer_id = PeerId::new([1; 64]);
2819 let hash = B256::from_slice(&[1; 32]);
2820
2821 tx_manager.network.update_sync_state(SyncState::Idle);
2822 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2823
2824 let err = PoolError::new(
2825 hash,
2826 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2827 BlobTransactionValidationError::InvalidProof,
2828 )),
2829 );
2830
2831 tx_manager.on_bad_import(err);
2832
2833 assert!(!tx_manager.bad_imports.contains(&hash));
2834 }
2835
2836 #[tokio::test(flavor = "multi_thread")]
2837 async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2838 let (mut tx_manager, _network) = new_tx_manager().await;
2839 let peer_id = PeerId::new([1; 64]);
2840 let hash = B256::from_slice(&[3; 32]);
2841
2842 tx_manager.network.update_sync_state(SyncState::Idle);
2843 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2844
2845 let err = PoolError::new(
2846 hash,
2847 InvalidPoolTransactionError::Eip4844(
2848 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2849 ),
2850 );
2851
2852 tx_manager.on_bad_import(err);
2853
2854 assert!(!tx_manager.bad_imports.contains(&hash));
2855 }
2856
2857 #[tokio::test(flavor = "multi_thread")]
2858 async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2859 let (mut tx_manager, _network) = new_tx_manager().await;
2860 let peer_id = PeerId::new([1; 64]);
2861 let hash = B256::from_slice(&[2; 32]);
2862
2863 tx_manager.network.update_sync_state(SyncState::Idle);
2864 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2865
2866 let err = PoolError::new(
2867 hash,
2868 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2869 );
2870
2871 tx_manager.on_bad_import(err);
2872
2873 assert!(tx_manager.bad_imports.contains(&hash));
2874 }
2875
2876 #[tokio::test(flavor = "multi_thread")]
2877 async fn test_on_get_pooled_transactions_network() {
2878 reth_tracing::init_test_tracing();
2879 let net = Testnet::create(2).await;
2880
2881 let mut handles = net.handles();
2882 let handle0 = handles.next().unwrap();
2883 let handle1 = handles.next().unwrap();
2884
2885 drop(handles);
2886 let handle = net.spawn();
2887
2888 let listener0 = handle0.event_listener();
2889
2890 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2891 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2892
2893 let client = NoopProvider::default();
2894 let pool = testing_pool();
2895 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2896 .disable_discovery()
2897 .listener_port(0)
2898 .build(client);
2899 let transactions_manager_config = config.transactions_manager_config.clone();
2900 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2901 .await
2902 .unwrap()
2903 .into_builder()
2904 .transactions(pool.clone(), transactions_manager_config)
2905 .split_with_handle();
2906 tokio::task::spawn(network);
2907
2908 network_handle.update_sync_state(SyncState::Idle);
2909
2910 assert!(!NetworkInfo::is_syncing(&network_handle));
2911
2912 let mut established = listener0.take(2);
2914 while let Some(ev) = established.next().await {
2915 match ev {
2916 NetworkEvent::ActivePeerSession { .. } |
2917 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2918 transactions.on_network_event(ev);
2919 }
2920 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2921 ev => {
2922 error!("unexpected event {ev:?}")
2923 }
2924 }
2925 }
2926 handle.terminate().await;
2927
2928 let tx = MockTransaction::eip1559();
2929 let _ = transactions
2930 .pool
2931 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
2932 .await;
2933
2934 let request = GetPooledTransactions(vec![*tx.get_hash()]);
2935
2936 let (send, receive) =
2937 oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
2938
2939 transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
2940 peer_id: *handle1.peer_id(),
2941 request,
2942 response: send,
2943 });
2944
2945 match receive.await.unwrap() {
2946 Ok(PooledTransactions(transactions)) => {
2947 assert_eq!(transactions.len(), 1);
2948 }
2949 Err(e) => {
2950 panic!("error: {e:?}");
2951 }
2952 }
2953 }
2954
2955 #[tokio::test]
2959 async fn test_partially_tx_response() {
2960 reth_tracing::init_test_tracing();
2961
2962 let mut tx_manager = new_tx_manager().await.0;
2963 let tx_fetcher = &mut tx_manager.transaction_fetcher;
2964
2965 let peer_id_1 = PeerId::new([1; 64]);
2966 let eth_version = EthVersion::Eth66;
2967
2968 let txs = vec![
2969 TransactionSigned::new_unhashed(
2970 Transaction::Legacy(TxLegacy {
2971 chain_id: Some(4),
2972 nonce: 15u64,
2973 gas_price: 2200000000,
2974 gas_limit: 34811,
2975 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2976 value: U256::from(1234u64),
2977 input: Default::default(),
2978 }),
2979 Signature::new(
2980 U256::from_str(
2981 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2982 )
2983 .unwrap(),
2984 U256::from_str(
2985 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2986 )
2987 .unwrap(),
2988 true,
2989 ),
2990 ),
2991 TransactionSigned::new_unhashed(
2992 Transaction::Eip1559(TxEip1559 {
2993 chain_id: 4,
2994 nonce: 26u64,
2995 max_priority_fee_per_gas: 1500000000,
2996 max_fee_per_gas: 1500000013,
2997 gas_limit: MIN_TRANSACTION_GAS,
2998 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
2999 value: U256::from(3000000000000000000u64),
3000 input: Default::default(),
3001 access_list: Default::default(),
3002 }),
3003 Signature::new(
3004 U256::from_str(
3005 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
3006 )
3007 .unwrap(),
3008 U256::from_str(
3009 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
3010 )
3011 .unwrap(),
3012 true,
3013 ),
3014 ),
3015 ];
3016
3017 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3018
3019 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3020 peer_1.seen_transactions.insert(txs_hashes[0]);
3023 peer_1.seen_transactions.insert(txs_hashes[1]);
3024 tx_manager.peers.insert(peer_id_1, peer_1);
3025
3026 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, 0, None);
3027 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, 0, None);
3028
3029 assert!(tx_fetcher.is_idle(&peer_id_1));
3031 assert_eq!(tx_fetcher.active_peers.len(), 0);
3032
3033 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3035
3036 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3037 assert!(!tx_fetcher.is_idle(&peer_id_1));
3039 assert_eq!(tx_fetcher.active_peers.len(), 1);
3040
3041 let req = to_mock_session_rx
3043 .recv()
3044 .await
3045 .expect("peer_1 session should receive request with buffered hashes");
3046 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3047
3048 let message: Vec<PooledTransactionVariant> = txs
3049 .into_iter()
3050 .take(1)
3051 .map(|tx| {
3052 PooledTransactionVariant::try_from(tx)
3053 .expect("Failed to convert MockTransaction to PooledTransaction")
3054 })
3055 .collect();
3056 response
3058 .send(Ok(PooledTransactions(message)))
3059 .expect("should send peer_1 response to tx manager");
3060 let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3061 unreachable!()
3062 };
3063
3064 assert!(tx_fetcher.is_idle(&peer_id));
3066 assert_eq!(tx_fetcher.active_peers.len(), 0);
3067 assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3069 }
3070
3071 #[tokio::test]
3072 async fn test_max_retries_tx_request() {
3073 reth_tracing::init_test_tracing();
3074
3075 let mut tx_manager = new_tx_manager().await.0;
3076 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3077
3078 let peer_id_1 = PeerId::new([1; 64]);
3079 let peer_id_2 = PeerId::new([2; 64]);
3080 let eth_version = EthVersion::Eth66;
3081 let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3082
3083 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3084 peer_1.seen_transactions.insert(seen_hashes[0]);
3087 peer_1.seen_transactions.insert(seen_hashes[1]);
3088 tx_manager.peers.insert(peer_id_1, peer_1);
3089
3090 let retries = 1;
3093 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[1], peer_id_1, retries, None);
3094 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[0], peer_id_1, retries, None);
3095
3096 assert!(tx_fetcher.is_idle(&peer_id_1));
3098 assert_eq!(tx_fetcher.active_peers.len(), 0);
3099
3100 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3102
3103 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3104
3105 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3106 assert!(!tx_fetcher.is_idle(&peer_id_1));
3108 assert_eq!(tx_fetcher.active_peers.len(), 1);
3109
3110 let req = to_mock_session_rx
3112 .recv()
3113 .await
3114 .expect("peer_1 session should receive request with buffered hashes");
3115 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3116 let GetPooledTransactions(hashes) = request;
3117
3118 let hashes = hashes.into_iter().collect::<B256Set>();
3119
3120 assert_eq!(hashes, seen_hashes.into_iter().collect::<B256Set>());
3121
3122 response
3124 .send(Err(RequestError::BadResponse))
3125 .expect("should send peer_1 response to tx manager");
3126 let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3127 unreachable!()
3128 };
3129
3130 assert!(tx_fetcher.is_idle(&peer_id));
3132 assert_eq!(tx_fetcher.active_peers.len(), 0);
3133 assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3135
3136 let (peer_2, mut to_mock_session_rx) = new_mock_session(peer_id_2, eth_version);
3137 tx_manager.peers.insert(peer_id_2, peer_2);
3138
3139 let msg =
3141 NewPooledTransactionHashes::Eth66(NewPooledTransactionHashes66(seen_hashes.to_vec()));
3142 tx_manager.on_new_pooled_transaction_hashes(peer_id_2, msg);
3143
3144 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3145
3146 assert_eq!(tx_fetcher.active_peers.len(), 1);
3148
3149 assert_eq!(tx_fetcher.num_all_hashes(), 2);
3151 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3153
3154 let req = to_mock_session_rx
3156 .recv()
3157 .await
3158 .expect("peer_2 session should receive request with buffered hashes");
3159 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3160
3161 response
3163 .send(Err(RequestError::BadResponse))
3164 .expect("should send peer_2 response to tx manager");
3165 let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3166
3167 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3170 assert_eq!(tx_fetcher.active_peers.len(), 0);
3171 }
3172
3173 #[test]
3174 fn test_direct_propagation_transaction_uses_2718_size() {
3175 let mut tx_gen = TransactionGenerator::new(rand::rng());
3176 let tx = tx_gen.gen_eip1559();
3177 let expected_size = tx.encode_2718_len();
3178
3179 let tx = PropagateTransaction::new(tx);
3180
3181 assert_eq!(tx.propagation_size(), expected_size);
3182 }
3183
3184 #[test]
3185 fn test_transaction_builder_empty() {
3186 let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3187 assert!(builder.is_empty());
3188
3189 let mut tx_gen = TransactionGenerator::new(rand::rng());
3190 let tx =
3191 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3192 builder.push(&tx);
3193 assert!(!builder.is_empty());
3194
3195 let txs = builder.build();
3196 assert!(txs.full.is_none());
3197 let txs = txs.pooled.unwrap();
3198 assert_eq!(txs.len(), 1);
3199 }
3200
3201 #[test]
3202 fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3203 let mut tx_gen = TransactionGenerator::new(rand::rng());
3204 let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3205 let pooled = PropagatePooledTransactionEncoder::new(tx);
3206
3207 let mut pooled_encoded = Vec::new();
3208 pooled.encode(&mut pooled_encoded);
3209 assert_eq!(pooled.length(), pooled_encoded.len());
3210
3211 let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3212 let mut first_encoded = Vec::new();
3213 broadcast.encode(&mut first_encoded);
3214 let mut second_encoded = Vec::new();
3215 broadcast.encode(&mut second_encoded);
3216 assert_eq!(first_encoded, second_encoded);
3217
3218 let mut encoded = first_encoded.as_slice();
3219 let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3220 assert_eq!(decoded.len(), 1);
3221 assert!(encoded.is_empty());
3222 }
3223
3224 #[test]
3225 fn test_transaction_builder_large() {
3226 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3227 assert!(builder.is_empty());
3228
3229 let mut tx_gen = TransactionGenerator::new(rand::rng());
3230 let mut tx = tx_gen.gen_eip1559_pooled();
3231 tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3233 let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3234 builder.push(&tx);
3235 assert!(!builder.is_empty());
3236
3237 let txs = builder.clone().build();
3238 assert!(txs.pooled.is_none());
3239 let txs = txs.full.unwrap();
3240 assert_eq!(txs.len(), 1);
3241
3242 builder.push(&tx);
3243
3244 let txs = builder.clone().build();
3245 let pooled = txs.pooled.unwrap();
3246 assert_eq!(pooled.len(), 1);
3247 let txs = txs.full.unwrap();
3248 assert_eq!(txs.len(), 1);
3249 }
3250
3251 #[test]
3252 fn test_transaction_builder_eip4844() {
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 tx =
3258 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3259 builder.push(&tx);
3260 assert!(!builder.is_empty());
3261
3262 let txs = builder.clone().build();
3263 assert!(txs.full.is_none());
3264 let txs = txs.pooled.unwrap();
3265 assert_eq!(txs.len(), 1);
3266
3267 let tx =
3268 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
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 #[tokio::test]
3279 async fn test_propagate_full() {
3280 reth_tracing::init_test_tracing();
3281
3282 let (mut tx_manager, network) = new_eth_tx_manager().await;
3283 let peer_id = PeerId::random();
3284
3285 network.handle().update_sync_state(SyncState::Idle);
3287
3288 let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3290
3291 let session_info = SessionInfo {
3292 peer_id,
3293 remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3294 client_version: Arc::from(""),
3295 capabilities: Arc::new(vec![].into()),
3296 status: Arc::new(Default::default()),
3297 version: EthVersion::Eth68,
3298 peer_kind: PeerKind::Basic,
3299 };
3300 let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3301 tx_manager
3302 .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3303 let mut propagate = vec![];
3304 let mut tx_gen = TransactionGenerator::new(rand::rng());
3305 let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3306 propagate.push(eip1559_tx.clone());
3307 let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3308 propagate.push(eip4844_tx.clone());
3309
3310 let propagated = tx_manager.propagate_transactions(
3311 propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3312 PropagationMode::Basic,
3313 );
3314 assert_eq!(propagated.len(), 2);
3315 let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3316 assert_eq!(prop_txs.len(), 1);
3317 assert!(prop_txs[0].is_full());
3318
3319 let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3320 assert_eq!(prop_txs.len(), 1);
3321 assert!(prop_txs[0].is_hash());
3322
3323 let peer = tx_manager.peers.get(&peer_id).unwrap();
3324 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3325 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3326 peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3327
3328 let propagated = tx_manager.propagate_transactions(
3330 propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3331 PropagationMode::Basic,
3332 );
3333 assert!(propagated.is_empty());
3334 }
3335
3336 #[tokio::test]
3337 async fn test_truncated_hash_announcement_not_marked_seen() {
3338 reth_tracing::init_test_tracing();
3339
3340 let (mut tx_manager, network) = new_eth_tx_manager().await;
3341 tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3343
3344 network.handle().update_sync_state(SyncState::Idle);
3346
3347 let peer_id = PeerId::random();
3348 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3349 tx_manager.peers.insert(peer_id, peer);
3350
3351 let mut tx_gen = TransactionGenerator::new(rand::rng());
3353 let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3354 .map(|nonce| {
3355 valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3356 })
3357 .collect::<Vec<_>>();
3358 let last_sent = *txs[txs.len() - 2].hash();
3359 let truncated = *txs[txs.len() - 1].hash();
3360
3361 let propagated = tx_manager.propagate_transactions(
3362 txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3363 PropagationMode::Basic,
3364 );
3365
3366 assert!(propagated.get(&truncated).is_none());
3368 let peer = tx_manager.peers.get(&peer_id).unwrap();
3369 assert!(!peer.seen_transactions.contains(&truncated));
3370 assert!(peer.seen_transactions.contains(&last_sent));
3371 }
3372
3373 #[tokio::test]
3374 async fn test_propagate_pending_txs_while_initially_syncing() {
3375 reth_tracing::init_test_tracing();
3376
3377 let (mut tx_manager, network) = new_eth_tx_manager().await;
3378 let peer_id = PeerId::random();
3379
3380 network.handle().update_sync_state(SyncState::Syncing);
3382 assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3383
3384 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3386 tx_manager.peers.insert(peer_id, peer);
3387
3388 let mut tx_gen = TransactionGenerator::new(rand::rng());
3389 let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3390 let tx_hash = *tx.hash();
3391 tx_manager
3392 .pool
3393 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3394 .await
3395 .expect("transaction should be accepted into the pool");
3396
3397 tx_manager.on_new_pending_transactions(vec![tx_hash]);
3398
3399 let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3400 assert!(peer.seen_transactions.contains(&tx_hash));
3401 }
3402
3403 #[tokio::test]
3404 async fn test_relaxed_filter_ignores_unknown_tx_types() {
3405 reth_tracing::init_test_tracing();
3406
3407 let transactions_manager_config = TransactionsManagerConfig::default();
3408
3409 let propagation_policy = TransactionPropagationKind::default();
3410 let announcement_policy = RelaxedEthAnnouncementFilter::default();
3411
3412 let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3413
3414 let pool = testing_pool();
3415 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
3416 let client = NoopProvider::default();
3417
3418 let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3419 .listener_port(0)
3420 .disable_discovery()
3421 .build(client.clone());
3422
3423 let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3424 let (to_tx_manager_tx, from_network_rx) =
3425 reth_metrics::common::mpsc::memory_bounded_channel::<
3426 NetworkTransactionEvent<EthNetworkPrimitives>,
3427 >(
3428 crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3429 "test_tx_channel",
3430 );
3431 network_manager.set_transactions(to_tx_manager_tx);
3432 let network_handle = network_manager.handle().clone();
3433 let network_service_handle = tokio::spawn(network_manager);
3434
3435 let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3436 network_handle.clone(),
3437 pool.clone(),
3438 from_network_rx,
3439 transactions_manager_config,
3440 policy_bundle,
3441 );
3442
3443 let peer_id = PeerId::random();
3444 let eth_version = EthVersion::Eth68;
3445 let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3446 tx_manager.peers.insert(peer_id, mock_peer_metadata);
3447
3448 let mut tx_factory = MockTransactionFactory::default();
3449
3450 let valid_known_tx = tx_factory.create_eip1559();
3451 let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3452
3453 let known_tx_hash = *known_tx_signed.hash();
3454 let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3455 let known_tx_size = known_tx_signed.encoded_length();
3456
3457 let unknown_tx_hash = B256::random();
3458 let unknown_tx_type_byte = 0xff_u8;
3459 let unknown_tx_size = 150;
3460
3461 let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3462 types: vec![known_tx_type_byte, unknown_tx_type_byte],
3463 sizes: vec![known_tx_size, unknown_tx_size],
3464 hashes: vec![known_tx_hash, unknown_tx_hash],
3465 });
3466
3467 tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3468
3469 poll_fn(|cx| {
3470 let _ = tx_manager.poll_unpin(cx);
3471 Poll::Ready(())
3472 })
3473 .await;
3474
3475 let mut requested_hashes_in_getpooled = B256Set::default();
3476 let mut unexpected_request_received = false;
3477
3478 match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3479 .await
3480 {
3481 Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3482 let GetPooledTransactions(hashes) = request;
3483 for hash in hashes {
3484 requested_hashes_in_getpooled.insert(hash);
3485 }
3486 let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3487 }
3488 Ok(Some(other_request)) => {
3489 tracing::error!(?other_request, "Received unexpected PeerRequest type");
3490 unexpected_request_received = true;
3491 }
3492 Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3493 Err(_timeout_err) => {
3494 tracing::info!("Timeout: No GetPooledTransactions request received.")
3495 }
3496 }
3497
3498 assert!(
3499 requested_hashes_in_getpooled.contains(&known_tx_hash),
3500 "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3501 );
3502 assert!(
3503 !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3504 "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3505 );
3506 assert!(
3507 !unexpected_request_received,
3508 "An unexpected P2P request was received by the mock peer."
3509 );
3510
3511 network_service_handle.abort();
3512 }
3513}