1use alloy_consensus::{constants::EIP4844_TX_TYPE_ID, transaction::TxHashRef};
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use smallvec::SmallVec;
6
7pub mod announcement;
9
10pub mod config;
12pub mod constants;
14pub mod fetcher;
16pub mod policy;
18#[cfg(test)]
19mod test_harness;
20
21pub use self::constants::{
22 tx_fetcher::DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
23 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
24};
25use announcement::TransactionAnnouncement;
26use config::AnnouncementAcceptance;
27pub use config::{
28 AnnouncementFilteringPolicy, TransactionFetcherConfig, TransactionIngressPolicy,
29 TransactionPropagationMode, TransactionPropagationPolicy, TransactionsManagerConfig,
30};
31use policy::NetworkPolicies;
32
33pub(crate) use fetcher::{FetchEvent, TransactionFetcher};
34
35use self::constants::{
36 tx_manager::*, DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE,
37 SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST,
38};
39use crate::{
40 budget::{
41 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
42 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS, DEFAULT_BUDGET_TRY_DRAIN_STREAM,
43 },
44 cache::LruCache,
45 duration_metered_exec, metered_poll_nested_stream_with_budget,
46 metrics::{AnnouncedTxTypesMetrics, TransactionsManagerMetrics},
47 transactions::config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
48 NetworkHandle, TxTypesCounter,
49};
50use alloy_eips::eip2718::{Encodable2718, Typed2718};
51use alloy_primitives::{
52 bytes::BufMut,
53 map::{hash_map::Entry, B256Map, B256Set, FbBuildHasher, HashMap, HashSet},
54 TxHash, B256,
55};
56use alloy_rlp::Encodable;
57use constants::SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE;
58use futures::{stream::FuturesUnordered, Future, StreamExt};
59use reth_eth_wire::{
60 BroadcastPoolTransactions, Cells, EthNetworkPrimitives, EthVersion, GetCells,
61 GetPooledTransactions, LazyEncoded, LazyEncodedTransaction, NetworkPrimitives,
62 NewPooledTransactionHashes, NewPooledTransactionHashes66, NewPooledTransactionHashes68,
63 NewPooledTransactionHashes72, PooledTransactions, Transactions,
64};
65use reth_ethereum_primitives::TxType;
66use reth_evm::SenderRecoveryCache;
67use reth_metrics::common::mpsc::MemoryBoundedReceiver;
68use reth_network_api::{
69 events::{PeerEvent, SessionInfo},
70 NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
71};
72use reth_network_p2p::{
73 error::{RequestError, RequestResult},
74 sync::SyncStateProvider,
75};
76use reth_network_peers::PeerId;
77use reth_network_types::ReputationChangeKind;
78use reth_primitives_traits::{InMemorySize, SignedTransaction};
79use reth_tokio_util::EventStream;
80use reth_transaction_pool::{
81 error::{PoolError, PoolResult},
82 AddedTransactionOutcome, GetPooledTransactionLimit, PoolTransaction, PropagateKind,
83 PropagatedTransactions, TransactionPool, ValidPoolTransaction,
84};
85use std::{
86 pin::Pin,
87 sync::{
88 atomic::{AtomicUsize, Ordering},
89 Arc,
90 },
91 task::{Context, Poll},
92 time::{Duration, Instant},
93};
94use tokio::sync::{mpsc, oneshot, oneshot::error::RecvError};
95use tokio_stream::wrappers::UnboundedReceiverStream;
96use tracing::{debug, trace};
97
98pub type PoolImportFuture =
102 Pin<Box<dyn Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send + 'static>>;
103
104#[derive(Debug, Clone)]
112pub struct TransactionsHandle<N: NetworkPrimitives = EthNetworkPrimitives> {
113 manager_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
115}
116
117impl<N: NetworkPrimitives> TransactionsHandle<N> {
118 fn send(&self, cmd: TransactionsCommand<N>) {
119 let _ = self.manager_tx.send(cmd);
120 }
121
122 async fn peer_handle(
124 &self,
125 peer_id: PeerId,
126 ) -> Result<Option<PeerRequestSender<PeerRequest<N>>>, RecvError> {
127 let (tx, rx) = oneshot::channel();
128 self.send(TransactionsCommand::GetPeerSender { peer_id, peer_request_sender: tx });
129 rx.await
130 }
131
132 pub fn propagate(&self, hash: TxHash) {
134 self.send(TransactionsCommand::PropagateHash(hash))
135 }
136
137 pub fn propagate_hash_to(&self, hash: TxHash, peer: PeerId) {
141 self.propagate_hashes_to(Some(hash), peer)
142 }
143
144 pub fn propagate_hashes_to(&self, hash: impl IntoIterator<Item = TxHash>, peer: PeerId) {
148 let hashes = hash.into_iter().collect::<Vec<_>>();
149 if hashes.is_empty() {
150 return
151 }
152 self.send(TransactionsCommand::PropagateHashesTo(hashes, peer))
153 }
154
155 pub async fn get_active_peers(&self) -> Result<HashSet<PeerId>, RecvError> {
157 let (tx, rx) = oneshot::channel();
158 self.send(TransactionsCommand::GetActivePeers(tx));
159 rx.await
160 }
161
162 pub fn propagate_transactions_to(&self, transactions: Vec<TxHash>, peer: PeerId) {
166 if transactions.is_empty() {
167 return
168 }
169 self.send(TransactionsCommand::PropagateTransactionsTo(transactions, peer))
170 }
171
172 pub fn propagate_transactions(&self, transactions: Vec<TxHash>) {
177 if transactions.is_empty() {
178 return
179 }
180 self.send(TransactionsCommand::PropagateTransactions(transactions))
181 }
182
183 pub fn broadcast_transactions(
188 &self,
189 transactions: impl IntoIterator<Item = N::BroadcastedTransaction>,
190 ) {
191 let transactions =
192 transactions.into_iter().map(PropagateTransaction::new).collect::<Vec<_>>();
193 if transactions.is_empty() {
194 return
195 }
196 self.send(TransactionsCommand::BroadcastTransactions(transactions))
197 }
198
199 pub async fn get_transaction_hashes(
201 &self,
202 peers: Vec<PeerId>,
203 ) -> Result<HashMap<PeerId, B256Set>, RecvError> {
204 if peers.is_empty() {
205 return Ok(Default::default())
206 }
207 let (tx, rx) = oneshot::channel();
208 self.send(TransactionsCommand::GetTransactionHashes { peers, tx });
209 rx.await
210 }
211
212 pub async fn get_peer_transaction_hashes(&self, peer: PeerId) -> Result<B256Set, RecvError> {
214 let res = self.get_transaction_hashes(vec![peer]).await?;
215 Ok(res.into_values().next().unwrap_or_default())
216 }
217
218 pub async fn get_pooled_transactions_from(
224 &self,
225 peer_id: PeerId,
226 hashes: Vec<B256>,
227 ) -> Result<Option<Vec<N::PooledTransaction>>, RequestError> {
228 let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
229
230 let (tx, rx) = oneshot::channel();
231 let request = PeerRequest::GetPooledTransactions { request: hashes.into(), response: tx };
232 peer.try_send(request).ok();
233
234 rx.await?.map(|res| Some(res.0))
235 }
236
237 pub async fn get_cells_from(
245 &self,
246 peer_id: PeerId,
247 hashes: Vec<B256>,
248 cell_mask: alloy_primitives::B128,
249 ) -> Result<Option<Cells>, RequestError> {
250 if hashes.is_empty() {
251 return Ok(Some(Cells { cell_mask, ..Default::default() }))
252 }
253
254 let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
255
256 let (tx, rx) = oneshot::channel();
257 let request =
258 PeerRequest::GetCells { request: GetCells { hashes, cell_mask }, response: tx };
259 peer.try_send(request).map_err(|_| RequestError::ChannelClosed)?;
260
261 rx.await?.map(Some)
262 }
263}
264
265#[derive(Debug)]
320#[must_use = "Manager does nothing unless polled."]
321pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
322 pool: Pool,
324 sender_recovery_cache: Option<SenderRecoveryCache>,
326 network: NetworkHandle<N>,
328 network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
332 transaction_fetcher: TransactionFetcher<N>,
334 transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
339 announcement_hashes: B256Set,
341 pool_imports: FuturesUnordered<PoolImportFuture>,
353 pending_pool_imports_info: PendingPoolImportsInfo,
355 bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
357 peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
359 command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
363 command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
368 pending_transactions: mpsc::Receiver<TxHash>,
377 transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
379 config: TransactionsManagerConfig,
381 policies: NetworkPolicies<N>,
383 metrics: TransactionsManagerMetrics,
385 announced_tx_types_metrics: AnnouncedTxTypesMetrics,
387}
388
389impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
390 pub fn new(
394 network: NetworkHandle<N>,
395 pool: Pool,
396 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
397 transactions_manager_config: TransactionsManagerConfig,
398 ) -> Self {
399 Self::with_policy(
400 network,
401 pool,
402 from_network,
403 transactions_manager_config,
404 NetworkPolicies::new(
405 TransactionPropagationKind::default(),
406 StrictEthAnnouncementFilter::default(),
407 ),
408 )
409 }
410}
411
412impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
413 pub fn with_policy(
417 network: NetworkHandle<N>,
418 pool: Pool,
419 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
420 transactions_manager_config: TransactionsManagerConfig,
421 policies: NetworkPolicies<N>,
422 ) -> Self {
423 let network_events = network.event_listener();
424
425 let (command_tx, command_rx) = mpsc::unbounded_channel();
426
427 let transaction_fetcher =
428 TransactionFetcher::new(transactions_manager_config.transaction_fetcher_config.clone());
429
430 let pending = pool.pending_transactions_listener();
433 let pending_pool_imports_info = PendingPoolImportsInfo::new(
434 transactions_manager_config.max_pending_pool_imports,
435 transactions_manager_config.max_pending_pool_imports_bytes,
436 );
437 let metrics = TransactionsManagerMetrics::default();
438 metrics
439 .capacity_pending_pool_imports
440 .increment(pending_pool_imports_info.max_pending_pool_imports as u64);
441 metrics
442 .capacity_pending_pool_imports_bytes
443 .increment(pending_pool_imports_info.max_pending_pool_imports_bytes as u64);
444
445 Self {
446 pool,
447 sender_recovery_cache: None,
448 network,
449 network_events,
450 transaction_fetcher,
451 transactions_by_peers: Default::default(),
452 announcement_hashes: B256Set::default(),
453 pool_imports: Default::default(),
454 pending_pool_imports_info,
455 bad_imports: LruCache::with_hasher(DEFAULT_MAX_COUNT_BAD_IMPORTS, Default::default()),
456 peers: Default::default(),
457 command_tx,
458 command_rx: UnboundedReceiverStream::new(command_rx),
459 pending_transactions: pending,
460 transaction_events: from_network,
461 config: transactions_manager_config,
462 policies,
463 metrics,
464 announced_tx_types_metrics: AnnouncedTxTypesMetrics::default(),
465 }
466 }
467
468 pub fn handle(&self) -> TransactionsHandle<N> {
470 TransactionsHandle { manager_tx: self.command_tx.clone() }
471 }
472
473 pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
475 self.sender_recovery_cache = Some(cache);
476 self
477 }
478
479 fn has_capacity_for_pending_pool_imports(&self) -> bool {
486 self.remaining_pool_import_capacity() > 0
487 }
488
489 fn remaining_pool_import_capacity(&self) -> usize {
494 let info = &self.pending_pool_imports_info;
495 if info.pending_pool_imports_bytes.load(Ordering::Relaxed) >=
496 info.max_pending_pool_imports_bytes
497 {
498 return 0
499 }
500 info.max_pending_pool_imports
501 .saturating_sub(info.pending_pool_imports.load(Ordering::Relaxed))
502 }
503
504 fn remaining_broadcast_import_capacity(&self) -> (usize, usize) {
507 let capacity = self.remaining_pool_import_capacity();
508 let info = &self.pending_pool_imports_info;
509 let bytes = info
510 .max_pending_pool_imports_bytes
511 .saturating_sub(info.pending_pool_imports_bytes.load(Ordering::Relaxed));
512 if self.transaction_fetcher.num_hashes() == 0 {
513 return (capacity, bytes)
514 }
515 let reserved = info
518 .max_pending_pool_imports
519 .div_ceil(2)
520 .min(SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST);
521 let reserved_bytes = info
524 .max_pending_pool_imports_bytes
525 .div_ceil(2)
526 .min(SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE);
527 (capacity.saturating_sub(reserved), bytes.saturating_sub(reserved_bytes))
528 }
529
530 fn report_peer_bad_transactions(&self, peer_id: PeerId) {
531 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
532 self.metrics.reported_bad_transactions.increment(1);
533 }
534
535 fn report_peer(&self, peer_id: PeerId, kind: ReputationChangeKind) {
536 trace!(target: "net::tx", ?peer_id, ?kind, "reporting reputation change");
537 self.network.reputation_change(peer_id, kind);
538 }
539
540 fn report_already_seen(&self, peer_id: PeerId) {
541 trace!(target: "net::tx", ?peer_id, "Penalizing peer for already seen transaction");
542 self.network.reputation_change(peer_id, ReputationChangeKind::AlreadySeenTransaction);
543 }
544
545 fn on_peer_session_closed(&mut self, peer_id: &PeerId) {
547 if let Some(mut peer) = self.peers.remove(peer_id) {
548 self.policies.propagation_policy_mut().on_session_closed(&mut peer);
549 }
550 self.transaction_fetcher.on_peer_disconnected(peer_id);
551 }
552
553 fn on_good_import(&mut self, hash: TxHash) {
555 self.transactions_by_peers.remove(&hash);
556 }
557
558 fn on_bad_import(&mut self, err: PoolError) {
587 let peers = self.transactions_by_peers.remove(&err.hash);
588
589 if err.is_bad_blob_sidecar() {
590 if let Some(peers) = peers {
598 for peer_id in peers {
599 self.report_peer_bad_transactions(peer_id);
600 }
601 }
602 return
603 }
604
605 if !err.is_bad_transaction() || self.network.is_syncing() {
607 return
608 }
609 if let Some(peers) = peers {
612 for peer_id in peers {
613 self.report_peer_bad_transactions(peer_id);
614 }
615 }
616 self.metrics.bad_imports.increment(1);
617 self.bad_imports.insert(err.hash);
618 }
619
620 fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
621 let kind = match req_err {
622 RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
623 RequestError::Timeout => ReputationChangeKind::Timeout,
624 RequestError::ChannelClosed | RequestError::ConnectionDropped => {
625 return
627 }
628 RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
629 };
630 self.report_peer(peer_id, kind);
631 }
632
633 #[inline]
634 fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
635 let metrics = &self.metrics;
636
637 let TxManagerPollDurations {
638 acc_network_events,
639 acc_pending_imports,
640 acc_tx_events,
641 acc_imported_txns,
642 acc_fetch_events,
643 acc_pending_fetch,
644 acc_cmds,
645 } = poll_durations;
646
647 metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
649 metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
651 metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
652 metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
653 metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
654 metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
655 metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
656 metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
657 }
658}
659
660impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
661 fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
663 for res in batch_results {
664 match res {
665 Ok(AddedTransactionOutcome { hash, .. }) => {
666 self.on_good_import(hash);
667 }
668 Err(err) => {
669 self.on_bad_import(err);
670 }
671 }
672 }
673 }
674
675 fn on_new_pooled_transaction_hashes(
677 &mut self,
678 peer_id: PeerId,
679 msg: NewPooledTransactionHashes,
680 ) {
681 if self.network.is_initially_syncing() {
683 return
684 }
685 if self.network.tx_gossip_disabled() {
686 return
687 }
688
689 let Some(peer) = self.peers.get_mut(&peer_id) else {
691 trace!(
692 peer_id = format!("{peer_id:#}"),
693 ?msg,
694 "discarding announcement from inactive peer"
695 );
696
697 return
698 };
699 let client = peer.client_version.clone();
700
701 if msg.is_empty() {
702 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
703 return
704 }
705
706 let Ok(mut announcement) =
707 TransactionAnnouncement::from_message(&msg, &mut self.announcement_hashes)
708 else {
709 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
710 return
711 };
712 let has_duplicates = announcement.len() != msg.len();
713
714 let count_txns_already_seen_by_peer =
716 msg.iter_hashes().filter(|hash| !peer.seen_transactions.insert(**hash)).count();
717 drop(msg);
718 if count_txns_already_seen_by_peer > 0 {
719 self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
724 self.metrics
725 .occurrences_hash_already_seen_by_peer
726 .increment(count_txns_already_seen_by_peer as u64);
727
728 trace!(target: "net::tx",
729 %count_txns_already_seen_by_peer,
730 peer_id=format!("{peer_id:#}"),
731 ?client,
732 "Peer sent hashes that have already been marked as seen by peer"
733 );
734
735 self.report_already_seen(peer_id);
736 }
737
738 if has_duplicates {
739 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
740 }
741
742 let mut should_report_peer = false;
745 let mut tx_types_counter = TxTypesCounter::default();
746 let has_eth68_metadata = announcement.version().has_eth68_metadata();
747
748 announcement.retain(|tx| {
749 if self.transactions_by_peers.contains_key(&tx.hash) ||
750 self.bad_imports.contains(&tx.hash)
751 {
752 return false
753 }
754 let (ty_byte, size_val) = tx.metadata.map_or((0, 0), |metadata| {
755 match TxType::try_from(metadata.tx_type) {
756 Ok(tx_type) => tx_types_counter.increase_by_tx_type(tx_type),
757 Err(_) => tx_types_counter.increase_other(),
758 }
759 (metadata.tx_type, metadata.size)
760 });
761
762 let decision = self
763 .policies
764 .announcement_filter()
765 .decide_on_announcement(ty_byte, &tx.hash, size_val);
766
767 match decision {
768 AnnouncementAcceptance::Accept => true,
769 AnnouncementAcceptance::Ignore => false,
770 AnnouncementAcceptance::Reject { penalize_peer } => {
771 if penalize_peer {
772 should_report_peer = true;
773 }
774 false
775 }
776 }
777 });
778
779 if has_eth68_metadata {
780 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
781 }
782
783 if should_report_peer {
784 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
785 }
786
787 let hashes_count_pre_pool_filter = announcement.len();
795 self.pool.retain_unknown(&mut announcement);
796 if hashes_count_pre_pool_filter > announcement.len() {
797 let already_known_hashes_count = hashes_count_pre_pool_filter - announcement.len();
798 self.metrics
799 .occurrences_hashes_already_in_pool
800 .increment(already_known_hashes_count as u64);
801 }
802
803 if announcement.is_empty() {
804 return
806 }
807
808 trace!(target: "net::tx::propagation",
809 peer_id=format!("{peer_id:#}"),
810 hashes_len=announcement.len(),
811 msg_version=%announcement.version(),
812 client_version=%client,
813 "received unknown hashes in announcement from peer"
814 );
815
816 self.transaction_fetcher.on_announcement(peer_id, announcement);
818 }
819}
820
821impl<Pool, N> TransactionsManager<Pool, N>
822where
823 Pool: TransactionPool + Unpin + 'static,
824 N: NetworkPrimitives<
825 BroadcastedTransaction: SignedTransaction,
826 PooledTransaction: SignedTransaction,
827 >,
828 Pool::Transaction:
829 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
830{
831 fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
843 if self.network.tx_gossip_disabled() {
847 return
848 }
849
850 trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
851
852 self.propagate_all(hashes);
853 }
854
855 fn propagate_full_transactions_to_peer(
859 &mut self,
860 txs: Vec<TxHash>,
861 peer_id: PeerId,
862 propagation_mode: PropagationMode,
863 ) -> Option<PropagatedTransactions> {
864 let peer = self.peers.get_mut(&peer_id)?;
865 trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
866 let mut propagated = PropagatedTransactions::default();
867
868 let mut full_transactions = FullTransactionsBuilder::new(peer.version);
870
871 let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
872
873 if propagation_mode.is_forced() {
874 full_transactions.extend(to_propagate);
876 } else {
877 for tx in to_propagate {
880 if !peer.seen_transactions.contains(tx.tx_hash()) {
881 full_transactions.push(&tx);
883 }
884 }
885 }
886
887 if full_transactions.is_empty() {
888 return None
890 }
891
892 let PropagateTransactions { pooled, full } = full_transactions.build();
893
894 if let Some(new_pooled_hashes) = pooled {
896 for hash in new_pooled_hashes.iter_hashes().copied() {
897 propagated.record(hash, PropagateKind::Hash(peer_id));
898 peer.seen_transactions.insert(hash);
900 }
901
902 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
904 }
905
906 if let Some(new_full_transactions) = full {
908 for hash in new_full_transactions.iter_hashes() {
909 propagated.record(*hash, PropagateKind::Full(peer_id));
910 peer.seen_transactions.insert(*hash);
912 }
913
914 self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
916 }
917
918 self.metrics.propagated_transactions.increment(propagated.len() as u64);
920
921 Some(propagated)
922 }
923
924 fn propagate_hashes_to(
928 &mut self,
929 hashes: Vec<TxHash>,
930 peer_id: PeerId,
931 propagation_mode: PropagationMode,
932 ) {
933 trace!(target: "net::tx", "Start propagating transactions as hashes");
934
935 let propagated = {
938 let Some(peer) = self.peers.get_mut(&peer_id) else {
939 return
941 };
942
943 let to_propagate =
944 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
945
946 let mut propagated = PropagatedTransactions::default();
947
948 let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
950
951 if propagation_mode.is_forced() {
952 hashes.extend(to_propagate)
953 } else {
954 for tx in to_propagate {
955 if !peer.seen_transactions.contains(tx.tx_hash()) {
956 hashes.push(&tx);
958 }
959 }
960 }
961
962 let new_pooled_hashes = hashes.build();
963
964 if new_pooled_hashes.is_empty() {
965 return
967 }
968
969 for hash in new_pooled_hashes.iter_hashes().copied() {
970 propagated.record(hash, PropagateKind::Hash(peer_id));
971 peer.seen_transactions.insert(hash);
972 }
973
974 trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
975
976 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
978
979 self.metrics.propagated_transactions.increment(propagated.len() as u64);
981
982 propagated
983 };
984
985 self.pool.on_propagated(propagated);
987 }
988
989 fn propagate_transactions(
996 &mut self,
997 to_propagate: Vec<PropagateTransaction>,
998 propagation_mode: PropagationMode,
999 ) -> PropagatedTransactions {
1000 let mut propagated = PropagatedTransactions::default();
1001 if self.network.tx_gossip_disabled() {
1002 return propagated
1003 }
1004
1005 let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1007
1008 let mut num_full_peers = 0;
1010 for (peer_id, peer) in &mut self.peers {
1011 if !self.policies.propagation_policy().can_propagate(peer) {
1012 continue
1014 }
1015
1016 let mut builder = if num_full_peers < max_num_full {
1018 num_full_peers += 1;
1019 PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1020 } else {
1021 PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1022 };
1023
1024 if propagation_mode.is_forced() {
1027 for tx in &to_propagate {
1028 peer.seen_transactions.insert(*tx.tx_hash());
1029 builder.push(tx);
1030 }
1031 } else {
1032 for tx in &to_propagate {
1036 if peer.seen_transactions.insert(*tx.tx_hash()) {
1038 builder.push(tx);
1039 }
1040 }
1041 }
1042
1043 if builder.is_empty() {
1044 trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1045 continue
1046 }
1047
1048 let PropagateTransactions { pooled, full } = builder.build();
1049
1050 if let Some(mut new_pooled_hashes) = pooled {
1052 if new_pooled_hashes.len() >
1056 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1057 {
1058 for hash in new_pooled_hashes
1061 .iter_hashes()
1062 .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1063 {
1064 peer.seen_transactions.remove(hash);
1065 }
1066 new_pooled_hashes.truncate(
1067 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1068 );
1069 }
1070
1071 for hash in new_pooled_hashes.iter_hashes().copied() {
1072 propagated.record(hash, PropagateKind::Hash(*peer_id));
1073 }
1074
1075 trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1076
1077 self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1079 }
1080
1081 if let Some(new_full_transactions) = full {
1083 for hash in new_full_transactions.iter_hashes() {
1084 propagated.record(*hash, PropagateKind::Full(*peer_id));
1085 }
1086
1087 trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1088
1089 self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1091 }
1092 }
1093
1094 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1096
1097 propagated
1098 }
1099
1100 fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1105 if self.peers.is_empty() {
1106 return
1108 }
1109 let propagated = self.propagate_transactions(
1110 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1111 PropagationMode::Basic,
1112 );
1113
1114 self.pool.on_propagated(propagated);
1116 }
1117
1118 fn on_get_pooled_transactions(
1120 &mut self,
1121 peer_id: PeerId,
1122 request: GetPooledTransactions,
1123 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1124 ) {
1125 if self.network.tx_gossip_disabled() {
1127 let _ = response.send(Ok(PooledTransactions::default()));
1128 return
1129 }
1130 if let Some(peer) = self.peers.get_mut(&peer_id) {
1131 if !self.policies.propagation_policy().can_propagate(peer) {
1133 let _ = response.send(Ok(PooledTransactions::default()));
1134 return
1135 }
1136
1137 let transactions = self.pool.get_pooled_transaction_elements(
1138 request.0,
1139 GetPooledTransactionLimit::ResponseSizeSoftLimit(
1140 self.config
1141 .transaction_fetcher_config
1142 .soft_limit_byte_size_pooled_transactions_response,
1143 ),
1144 );
1145 trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1146
1147 peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1150
1151 let resp = PooledTransactions(transactions);
1152 let _ = response.send(Ok(resp));
1153 }
1154 }
1155
1156 fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1158 match cmd {
1159 TransactionsCommand::PropagateHash(hash) => {
1160 self.on_new_pending_transactions(vec![hash])
1161 }
1162 TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1163 self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1164 }
1165 TransactionsCommand::GetActivePeers(tx) => {
1166 let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1167 tx.send(peers).ok();
1168 }
1169 TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1170 if let Some(propagated) =
1171 self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1172 {
1173 self.pool.on_propagated(propagated);
1174 }
1175 }
1176 TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1177 TransactionsCommand::BroadcastTransactions(txs) => {
1178 let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1179 self.pool.on_propagated(propagated);
1180 }
1181 TransactionsCommand::GetTransactionHashes { peers, tx } => {
1182 let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1183 for peer_id in peers {
1184 let hashes = self
1185 .peers
1186 .get(&peer_id)
1187 .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1188 .unwrap_or_default();
1189 res.insert(peer_id, hashes);
1190 }
1191 tx.send(res).ok();
1192 }
1193 TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1194 let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1195 peer_request_sender.send(sender).ok();
1196 }
1197 }
1198 }
1199
1200 fn handle_peer_session(
1204 &mut self,
1205 info: SessionInfo,
1206 messages: PeerRequestSender<PeerRequest<N>>,
1207 ) {
1208 let SessionInfo { peer_id, client_version, version, .. } = info;
1209
1210 let peer = PeerMetadata::<N>::new(
1212 messages,
1213 version,
1214 client_version,
1215 self.config.max_transactions_seen_by_peer_history,
1216 info.peer_kind,
1217 );
1218 let peer = match self.peers.entry(peer_id) {
1219 Entry::Occupied(mut entry) => {
1220 entry.insert(peer);
1221 entry.into_mut()
1222 }
1223 Entry::Vacant(entry) => entry.insert(peer),
1224 };
1225
1226 self.policies.propagation_policy_mut().on_session_established(peer);
1227
1228 if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1232 trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1233 return
1234 }
1235
1236 if !self.policies.propagation_policy().can_propagate(peer) {
1238 return
1239 }
1240
1241 let pooled_txs = self.pool.pooled_transactions_max(
1243 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1244 );
1245 if pooled_txs.is_empty() {
1246 trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1247 return;
1248 }
1249
1250 let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1252 for pooled_tx in pooled_txs {
1253 peer.seen_transactions.insert(*pooled_tx.hash());
1254 msg_builder.push_pooled(pooled_tx);
1255 }
1256
1257 debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1258 let msg = msg_builder.build();
1259 self.network.send_transactions_hashes(peer_id, msg);
1260 }
1261
1262 fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1264 match event_result {
1265 NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1266 self.on_peer_session_closed(&peer_id);
1267 }
1268 NetworkEvent::ActivePeerSession { info, messages } => {
1269 self.handle_peer_session(info, messages);
1271 }
1272 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1273 let peer_id = info.peer_id;
1274 let messages = match self.peers.get(&peer_id) {
1276 Some(p) => p.request_tx.clone(),
1277 None => {
1278 debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1279 return;
1280 }
1281 };
1282 self.handle_peer_session(info, messages);
1283 }
1284 _ => {}
1285 }
1286 }
1287
1288 fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1290 if self.config.ingress_policy.allows_all() {
1291 return true;
1292 }
1293 let Some(peer) = self.peers.get(peer_id) else {
1294 return false;
1295 };
1296 self.config.ingress_policy.allows(peer.peer_kind())
1297 }
1298
1299 fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1301 match event {
1302 NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1303 if !self.accepts_incoming_from(&peer_id) {
1304 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1305 return;
1306 }
1307
1308 let has_blob_txs = msg.has_eip4844();
1312
1313 let non_blob_txs = msg
1314 .into_iter()
1315 .map(N::PooledTransaction::try_from)
1316 .filter_map(Result::ok)
1317 .collect();
1318
1319 self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1320
1321 if has_blob_txs {
1322 debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1323 self.report_peer_bad_transactions(peer_id);
1324 }
1325 }
1326 NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1327 if !self.accepts_incoming_from(&peer_id) {
1328 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1329 return;
1330 }
1331 self.on_new_pooled_transaction_hashes(peer_id, msg)
1332 }
1333 NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1334 self.on_get_pooled_transactions(peer_id, request, response)
1335 }
1336 NetworkTransactionEvent::GetTransactionsHandle(response) => {
1337 let _ = response.send(Some(self.handle()));
1338 }
1339 }
1340 }
1341
1342 fn import_transactions(
1348 &mut self,
1349 peer_id: PeerId,
1350 transactions: PooledTransactions<N::PooledTransaction>,
1351 source: TransactionSource,
1352 ) {
1353 if self.network.is_initially_syncing() {
1355 return
1356 }
1357 if self.network.tx_gossip_disabled() {
1358 return
1359 }
1360
1361 let (version, client_version) = match &source {
1362 TransactionSource::Broadcast => {
1363 let Some(peer) = self.peers.get(&peer_id) else { return };
1364 (peer.version(), peer.client_version.clone())
1365 }
1366 TransactionSource::Response { version, client_version } => {
1367 (*version, client_version.clone())
1368 }
1369 };
1370 let is_broadcast = source.is_broadcast();
1371 let mut transactions = transactions.0;
1372
1373 if is_broadcast {
1376 let (capacity, byte_capacity) = self.remaining_broadcast_import_capacity();
1377 let mut bytes = 0usize;
1378 let admitted = transactions
1379 .iter()
1380 .take(capacity)
1381 .take_while(|tx| {
1382 bytes = bytes.saturating_add(tx.encode_2718_len());
1383 bytes <= byte_capacity
1384 })
1385 .count();
1386 if transactions.len() > admitted {
1387 self.metrics
1388 .skipped_transactions_pending_pool_imports_at_capacity
1389 .increment((transactions.len() - admitted) as u64);
1390 transactions.truncate(admitted);
1391 }
1392 }
1393
1394 if transactions.is_empty() {
1395 return
1396 }
1397
1398 let start = Instant::now();
1399
1400 self.transaction_fetcher
1402 .on_transactions_received(transactions.iter().map(|tx| tx.tx_hash()));
1403
1404 let mut num_already_seen_by_peer = 0;
1409 if is_broadcast && let Some(peer) = self.peers.get_mut(&peer_id) {
1410 for tx in &transactions {
1411 if !peer.seen_transactions.insert(*tx.tx_hash()) {
1412 num_already_seen_by_peer += 1;
1413 }
1414 }
1415 }
1416
1417 if version.is_eth72() {
1422 let len_before = transactions.len();
1423 transactions.retain(|tx| !tx.is_eip4844());
1424 let dropped = len_before - transactions.len();
1425 if dropped > 0 {
1426 trace!(target: "net::tx",
1427 peer_id=format!("{peer_id:#}"),
1428 dropped,
1429 "dropped blob transactions from eth72 response, cell fetching not implemented"
1430 );
1431 }
1432 }
1433
1434 let mut has_bad_transactions = false;
1436
1437 transactions.retain(|tx| {
1440 if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1441 let peers = entry.get_mut();
1442 if !peers.contains(&peer_id) {
1443 peers.push(peer_id);
1444 }
1445 return false
1446 }
1447 if self.bad_imports.contains(tx.tx_hash()) {
1448 trace!(target: "net::tx",
1449 peer_id=format!("{peer_id:#}"),
1450 hash=%tx.tx_hash(),
1451 %client_version,
1452 "received a known bad transaction from peer"
1453 );
1454 has_bad_transactions = true;
1455 return false;
1456 }
1457 true
1458 });
1459
1460 let txns_count_pre_pool_filter = transactions.len();
1462 self.pool.retain_unknown(&mut transactions);
1463 if txns_count_pre_pool_filter > transactions.len() {
1464 let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1465 self.metrics
1466 .occurrences_transactions_already_in_pool
1467 .increment(already_known_txns_count as u64);
1468 }
1469
1470 let txs_len = transactions.len();
1471
1472 let recover = |tx| {
1473 let recovered = Pool::Transaction::try_recover_with_cache_opt(
1474 tx,
1475 self.sender_recovery_cache.as_ref(),
1476 );
1477 match recovered {
1478 Ok(tx) => Some(tx),
1479 Err(badtx) => {
1480 trace!(target: "net::tx",
1481 peer_id=format!("{peer_id:#}"),
1482 hash=%badtx.tx_hash(),
1483 client_version=%client_version,
1484 "failed ecrecovery for transaction"
1485 );
1486 None
1487 }
1488 }
1489 };
1490
1491 let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1492
1493 has_bad_transactions |= new_txs.len() != txs_len;
1494
1495 for tx in &new_txs {
1497 self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1498 }
1499
1500 if !new_txs.is_empty() {
1503 let pool = self.pool.clone();
1504 let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1506 metric_pending_pool_imports.increment(new_txs.len() as f64);
1507 let bytes = new_txs.iter().map(PoolTransaction::encoded_length).sum::<usize>();
1509 let metric_pending_pool_imports_bytes = self.metrics.pending_pool_imports_bytes.clone();
1510 metric_pending_pool_imports_bytes.increment(bytes as f64);
1511
1512 self.pending_pool_imports_info
1514 .pending_pool_imports
1515 .fetch_add(new_txs.len(), Ordering::Relaxed);
1516 let tx_manager_info_pending_pool_imports =
1517 self.pending_pool_imports_info.pending_pool_imports.clone();
1518 self.pending_pool_imports_info
1519 .pending_pool_imports_bytes
1520 .fetch_add(bytes, Ordering::Relaxed);
1521 let tx_manager_info_pending_pool_imports_bytes =
1522 self.pending_pool_imports_info.pending_pool_imports_bytes.clone();
1523
1524 trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1525 let import = Box::pin(async move {
1526 let added = new_txs.len();
1527 let res = pool.add_external_transactions(new_txs).await;
1528
1529 metric_pending_pool_imports.decrement(added as f64);
1531 metric_pending_pool_imports_bytes.decrement(bytes as f64);
1532 tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1534 tx_manager_info_pending_pool_imports_bytes.fetch_sub(bytes, Ordering::Relaxed);
1535
1536 res
1537 });
1538
1539 self.pool_imports.push(import);
1540 }
1541
1542 if num_already_seen_by_peer > 0 {
1543 self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1544 self.metrics
1545 .occurrences_of_transaction_already_seen_by_peer
1546 .increment(num_already_seen_by_peer);
1547 trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1548 }
1549
1550 if has_bad_transactions {
1551 self.report_peer_bad_transactions(peer_id)
1553 }
1554
1555 if num_already_seen_by_peer > 0 {
1556 self.report_already_seen(peer_id);
1557 }
1558
1559 self.metrics.pool_import_prepare_duration.record(start.elapsed());
1560 }
1561
1562 fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1564 match fetch_event {
1565 FetchEvent::TransactionsFetched {
1566 peer_id,
1567 transactions,
1568 report_peer,
1569 version,
1570 client_version,
1571 } => {
1572 self.import_transactions(
1573 peer_id,
1574 transactions,
1575 TransactionSource::Response { version, client_version },
1576 );
1577 if report_peer {
1578 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1579 }
1580 }
1581 FetchEvent::FetchError { peer_id, error } => {
1582 trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1583 self.on_request_error(peer_id, error);
1584 }
1585 FetchEvent::EmptyResponse { peer_id } => {
1586 trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1587 }
1588 }
1589 }
1590}
1591
1592impl<
1600 Pool: TransactionPool + Unpin + 'static,
1601 N: NetworkPrimitives<
1602 BroadcastedTransaction: SignedTransaction,
1603 PooledTransaction: SignedTransaction,
1604 >,
1605 > Future for TransactionsManager<Pool, N>
1606where
1607 Pool::Transaction:
1608 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1609{
1610 type Output = ();
1611
1612 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1613 let start = Instant::now();
1614 let mut poll_durations = TxManagerPollDurations::default();
1615
1616 let this = self.get_mut();
1617
1618 let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1624 poll_durations.acc_network_events,
1625 "net::tx",
1626 "Network events stream",
1627 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1628 this.network_events.poll_next_unpin(cx),
1629 |event| this.on_network_event(event)
1630 );
1631
1632 let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1641 poll_durations.acc_tx_events,
1642 "net::tx",
1643 "Network transaction events stream",
1644 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1645 this.transaction_events.poll_next_unpin(cx),
1646 |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1647 );
1648
1649 let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1658 poll_durations.acc_fetch_events,
1659 "net::tx",
1660 "Transaction fetch events stream",
1661 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1662 if this.has_capacity_for_pending_pool_imports() {
1663 this.transaction_fetcher.poll_next_unpin(cx)
1664 } else {
1665 Poll::Pending
1666 },
1667 |event| this.on_fetch_event(event),
1668 );
1669
1670 let fetcher_paused_at_capacity = !this.has_capacity_for_pending_pool_imports();
1673
1674 let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1676 poll_durations.acc_pending_imports,
1677 "net::tx",
1678 "Batched pool imports stream",
1679 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1680 this.pool_imports.poll_next_unpin(cx),
1681 |batch_results| this.on_batch_import_result(batch_results)
1682 );
1683
1684 let mut new_txs = Vec::new();
1697 let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1698 cx,
1699 &mut new_txs,
1700 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1701 ) {
1702 Poll::Ready(count) => {
1703 if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1704 true
1707 } else {
1708 let limit =
1712 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1713 new_txs.len();
1714 this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1715 }
1716 }
1717 Poll::Pending => false,
1718 };
1719 if !new_txs.is_empty() {
1720 this.on_new_pending_transactions(new_txs);
1721 }
1722
1723 duration_metered_exec!(
1726 {
1727 let budget = this.remaining_pool_import_capacity();
1730 if budget > 0 && this.transaction_fetcher.dispatch(&this.peers, budget) > 0 {
1731 maybe_more_tx_fetch_events = true;
1733 }
1734 },
1735 poll_durations.acc_pending_fetch
1736 );
1737
1738 let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1740 poll_durations.acc_cmds,
1741 "net::tx",
1742 "Commands channel",
1743 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1744 this.command_rx.poll_next_unpin(cx),
1745 |cmd| this.on_command(cmd)
1746 );
1747
1748 this.transaction_fetcher.update_metrics();
1749
1750 let resume_fetcher =
1752 fetcher_paused_at_capacity && this.has_capacity_for_pending_pool_imports();
1753
1754 if maybe_more_network_events ||
1756 maybe_more_commands ||
1757 maybe_more_tx_events ||
1758 maybe_more_tx_fetch_events ||
1759 maybe_more_pool_imports ||
1760 maybe_more_pending_txns ||
1761 resume_fetcher
1762 {
1763 cx.waker().wake_by_ref();
1765 return Poll::Pending
1766 }
1767
1768 this.update_poll_metrics(start, poll_durations);
1769
1770 Poll::Pending
1771 }
1772}
1773
1774#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1778enum PropagationMode {
1779 Basic,
1783 Forced,
1788}
1789
1790impl PropagationMode {
1791 const fn is_forced(self) -> bool {
1793 matches!(self, Self::Forced)
1794 }
1795}
1796
1797#[derive(Debug, Clone)]
1799struct PropagateTransaction {
1800 is_broadcastable_in_full: bool,
1801 propagation_size: usize,
1808 transaction: LazyEncodedTransaction,
1809}
1810
1811impl PropagateTransaction {
1812 fn new<T: SignedTransaction>(transaction: T) -> Self {
1818 let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1819 let propagation_size = transaction.encode_2718_len();
1820
1821 Self {
1822 is_broadcastable_in_full,
1823 propagation_size,
1824 transaction: LazyEncoded::new(transaction),
1825 }
1826 }
1827
1828 fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1834 let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1835 let propagation_size = tx.encoded_length();
1836 Self {
1837 is_broadcastable_in_full,
1838 propagation_size,
1839 transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1840 }
1841 }
1842
1843 fn tx_hash(&self) -> &TxHash {
1844 self.transaction.tx_hash()
1845 }
1846
1847 const fn propagation_size(&self) -> usize {
1849 self.propagation_size
1850 }
1851
1852 fn tx_type(&self) -> u8 {
1853 self.transaction.ty()
1854 }
1855
1856 const fn is_broadcastable_in_full(&self) -> bool {
1857 self.is_broadcastable_in_full
1858 }
1859
1860 fn shared(&self) -> LazyEncodedTransaction {
1861 self.transaction.clone()
1862 }
1863}
1864
1865#[derive(Debug)]
1867struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1868 transaction: Arc<ValidPoolTransaction<P>>,
1869}
1870
1871impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1872 const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1873 Self { transaction }
1874 }
1875
1876 fn encode_uncached(&self, out: &mut dyn BufMut) {
1877 (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1878 }
1879}
1880
1881impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1882 fn encode(&self, out: &mut dyn BufMut) {
1883 self.encode_uncached(out);
1884 }
1885
1886 fn length(&self) -> usize {
1887 (*self.transaction.transaction.consensus_ref().inner()).length()
1888 }
1889}
1890
1891impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1892 fn tx_hash(&self) -> &TxHash {
1893 self.transaction.hash()
1894 }
1895}
1896
1897impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1898 fn ty(&self) -> u8 {
1899 self.transaction.transaction.ty()
1900 }
1901}
1902
1903#[derive(Debug, Clone)]
1906enum PropagateTransactionsBuilder {
1907 Pooled(PooledTransactionsHashesBuilder),
1908 Full(FullTransactionsBuilder),
1909}
1910
1911impl PropagateTransactionsBuilder {
1912 fn pooled(version: EthVersion, capacity: usize) -> Self {
1915 Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1916 }
1917
1918 fn full(version: EthVersion, capacity: usize) -> Self {
1921 Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1922 }
1923
1924 fn is_empty(&self) -> bool {
1926 match self {
1927 Self::Pooled(builder) => builder.is_empty(),
1928 Self::Full(builder) => builder.is_empty(),
1929 }
1930 }
1931
1932 fn build(self) -> PropagateTransactions {
1934 match self {
1935 Self::Pooled(pooled) => {
1936 PropagateTransactions { pooled: Some(pooled.build()), full: None }
1937 }
1938 Self::Full(full) => full.build(),
1939 }
1940 }
1941}
1942
1943impl PropagateTransactionsBuilder {
1944 fn push(&mut self, transaction: &PropagateTransaction) {
1946 match self {
1947 Self::Pooled(builder) => builder.push(transaction),
1948 Self::Full(builder) => builder.push(transaction),
1949 }
1950 }
1951}
1952
1953struct PropagateTransactions {
1955 pooled: Option<NewPooledTransactionHashes>,
1957 full: Option<BroadcastPoolTransactions>,
1959}
1960
1961#[derive(Debug, Clone)]
1966struct FullTransactionsBuilder {
1967 total_size: usize,
1969 transactions: Vec<LazyEncodedTransaction>,
1971 pooled: PooledTransactionsHashesBuilder,
1973}
1974
1975impl FullTransactionsBuilder {
1976 fn new(version: EthVersion) -> Self {
1978 Self {
1979 total_size: 0,
1980 pooled: PooledTransactionsHashesBuilder::new(version),
1981 transactions: vec![],
1982 }
1983 }
1984
1985 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
1990 Self {
1991 total_size: 0,
1992 pooled: PooledTransactionsHashesBuilder::new(version),
1993 transactions: Vec::with_capacity(capacity),
1994 }
1995 }
1996
1997 fn is_empty(&self) -> bool {
1999 self.transactions.is_empty() && self.pooled.is_empty()
2000 }
2001
2002 fn build(self) -> PropagateTransactions {
2004 let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
2005 let full =
2006 (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
2007 PropagateTransactions { pooled, full }
2008 }
2009
2010 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2012 for tx in txs {
2013 self.push(&tx)
2014 }
2015 }
2016
2017 fn push(&mut self, transaction: &PropagateTransaction) {
2027 if !transaction.is_broadcastable_in_full() {
2036 self.pooled.push(transaction);
2037 return
2038 }
2039
2040 let new_size = self.total_size + transaction.propagation_size();
2041 if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2042 self.total_size > 0
2043 {
2044 self.pooled.push(transaction);
2046 return
2047 }
2048
2049 self.total_size = new_size;
2050 self.transactions.push(transaction.shared());
2051 }
2052}
2053
2054#[derive(Debug, Clone)]
2057enum PooledTransactionsHashesBuilder {
2058 Eth66(NewPooledTransactionHashes66),
2059 Eth68(NewPooledTransactionHashes68),
2060 Eth72(NewPooledTransactionHashes72),
2061}
2062
2063impl PooledTransactionsHashesBuilder {
2066 fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2068 match self {
2069 Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2070 Self::Eth68(msg) => {
2071 msg.hashes.push(*pooled_tx.hash());
2072 msg.sizes.push(pooled_tx.encoded_length());
2073 msg.types.push(pooled_tx.transaction.ty());
2074 }
2075 Self::Eth72(msg) => {
2076 msg.hashes.push(*pooled_tx.hash());
2077 msg.sizes.push(pooled_tx.encoded_length());
2078 let ty = pooled_tx.transaction.ty();
2079 msg.types.push(ty);
2080 if ty == EIP4844_TX_TYPE_ID {
2081 msg.cell_mask = Some(NewPooledTransactionHashes72::ALL_CELLS_MASK);
2083 }
2084 }
2085 }
2086 }
2087
2088 fn is_empty(&self) -> bool {
2090 match self {
2091 Self::Eth66(hashes) => hashes.is_empty(),
2092 Self::Eth68(hashes) => hashes.is_empty(),
2093 Self::Eth72(hashes) => hashes.is_empty(),
2094 }
2095 }
2096
2097 fn len(&self) -> usize {
2099 match self {
2100 Self::Eth66(hashes) => hashes.len(),
2101 Self::Eth68(hashes) => hashes.len(),
2102 Self::Eth72(hashes) => hashes.len(),
2103 }
2104 }
2105
2106 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2108 for tx in txs {
2109 self.push(&tx);
2110 }
2111 }
2112
2113 fn push(&mut self, tx: &PropagateTransaction) {
2114 match self {
2115 Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2116 Self::Eth68(msg) => {
2117 msg.hashes.push(*tx.tx_hash());
2118 msg.sizes.push(tx.propagation_size());
2119 msg.types.push(tx.tx_type());
2120 }
2121 Self::Eth72(msg) => {
2122 msg.hashes.push(*tx.tx_hash());
2123 msg.sizes.push(tx.propagation_size());
2124 let ty = tx.tx_type();
2125 msg.types.push(ty);
2126 if ty == EIP4844_TX_TYPE_ID {
2127 msg.cell_mask = Some(NewPooledTransactionHashes72::ALL_CELLS_MASK);
2129 }
2130 }
2131 }
2132 }
2133
2134 fn new(version: EthVersion) -> Self {
2136 match version {
2137 EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2138 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2139 Self::Eth68(Default::default())
2140 }
2141 EthVersion::Eth72 => Self::Eth72(Default::default()),
2142 }
2143 }
2144
2145 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2148 match version {
2149 EthVersion::Eth66 | EthVersion::Eth67 => {
2150 Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2151 }
2152 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2153 Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2154 }
2155 EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2156 }
2157 }
2158
2159 fn build(self) -> NewPooledTransactionHashes {
2160 match self {
2161 Self::Eth66(mut msg) => {
2162 msg.shrink_to_fit();
2163 msg.into()
2164 }
2165 Self::Eth68(mut msg) => {
2166 msg.shrink_to_fit();
2167 msg.into()
2168 }
2169 Self::Eth72(mut msg) => {
2170 msg.shrink_to_fit();
2171 msg.into()
2172 }
2173 }
2174 }
2175}
2176
2177#[derive(Debug)]
2179enum TransactionSource {
2180 Broadcast,
2182 Response {
2184 version: EthVersion,
2186 client_version: Arc<str>,
2187 },
2188}
2189
2190impl TransactionSource {
2193 const fn is_broadcast(&self) -> bool {
2195 matches!(self, Self::Broadcast)
2196 }
2197}
2198
2199#[derive(Debug)]
2201pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2202 seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2206 request_tx: PeerRequestSender<PeerRequest<N>>,
2208 version: EthVersion,
2210 client_version: Arc<str>,
2212 peer_kind: PeerKind,
2214}
2215
2216impl<N: NetworkPrimitives> PeerMetadata<N> {
2217 pub fn new(
2219 request_tx: PeerRequestSender<PeerRequest<N>>,
2220 version: EthVersion,
2221 client_version: Arc<str>,
2222 max_transactions_seen_by_peer: u32,
2223 peer_kind: PeerKind,
2224 ) -> Self {
2225 Self {
2226 seen_transactions: LruCache::with_hasher(
2227 max_transactions_seen_by_peer,
2228 Default::default(),
2229 ),
2230 request_tx,
2231 version,
2232 client_version,
2233 peer_kind,
2234 }
2235 }
2236
2237 pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2239 &self.request_tx
2240 }
2241
2242 pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2244 &mut self.seen_transactions
2245 }
2246
2247 pub const fn version(&self) -> EthVersion {
2249 self.version
2250 }
2251
2252 pub fn client_version(&self) -> &str {
2254 &self.client_version
2255 }
2256
2257 pub const fn peer_kind(&self) -> PeerKind {
2259 self.peer_kind
2260 }
2261}
2262
2263#[derive(Debug)]
2265enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2266 PropagateHash(B256),
2268 PropagateHashesTo(Vec<B256>, PeerId),
2270 GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2272 PropagateTransactionsTo(Vec<TxHash>, PeerId),
2274 PropagateTransactions(Vec<TxHash>),
2276 BroadcastTransactions(Vec<PropagateTransaction>),
2278 GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2280 GetPeerSender {
2282 peer_id: PeerId,
2283 peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2284 },
2285}
2286
2287#[derive(Debug)]
2289pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2290 IncomingTransactions {
2294 peer_id: PeerId,
2296 msg: Transactions<N::BroadcastedTransaction>,
2298 },
2299 IncomingPooledTransactionHashes {
2301 peer_id: PeerId,
2303 msg: NewPooledTransactionHashes,
2305 },
2306 GetPooledTransactions {
2308 peer_id: PeerId,
2310 request: GetPooledTransactions,
2312 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2314 },
2315 GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2317}
2318
2319#[derive(Debug)]
2321pub struct PendingPoolImportsInfo {
2322 pending_pool_imports: Arc<AtomicUsize>,
2324 max_pending_pool_imports: usize,
2326 pending_pool_imports_bytes: Arc<AtomicUsize>,
2328 max_pending_pool_imports_bytes: usize,
2330}
2331
2332impl PendingPoolImportsInfo {
2333 pub fn new(max_pending_pool_imports: usize, max_pending_pool_imports_bytes: usize) -> Self {
2335 Self {
2336 pending_pool_imports: Arc::new(AtomicUsize::default()),
2337 max_pending_pool_imports,
2338 pending_pool_imports_bytes: Arc::new(AtomicUsize::default()),
2339 max_pending_pool_imports_bytes,
2340 }
2341 }
2342
2343 pub fn has_capacity(&self) -> bool {
2346 self.pending_pool_imports.load(Ordering::Relaxed) < self.max_pending_pool_imports &&
2347 self.pending_pool_imports_bytes.load(Ordering::Relaxed) <
2348 self.max_pending_pool_imports_bytes
2349 }
2350}
2351
2352impl Default for PendingPoolImportsInfo {
2353 fn default() -> Self {
2354 Self::new(
2355 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
2356 DEFAULT_MAX_BYTE_SIZE_PENDING_POOL_IMPORTS,
2357 )
2358 }
2359}
2360
2361#[derive(Debug, Default)]
2362struct TxManagerPollDurations {
2363 acc_network_events: Duration,
2364 acc_pending_imports: Duration,
2365 acc_tx_events: Duration,
2366 acc_imported_txns: Duration,
2367 acc_fetch_events: Duration,
2368 acc_pending_fetch: Duration,
2369 acc_cmds: Duration,
2370}
2371
2372impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2373 fn size(&self) -> usize {
2376 match self {
2377 Self::IncomingTransactions { peer_id, msg } => {
2378 core::mem::size_of_val(peer_id) +
2379 msg.0.iter().map(InMemorySize::size).sum::<usize>()
2380 }
2381 Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2382 core::mem::size_of_val(peer_id) + msg.size()
2383 }
2384 Self::GetPooledTransactions { peer_id, request, response } => {
2385 core::mem::size_of_val(peer_id) +
2386 request.0.len() * core::mem::size_of::<TxHash>() +
2387 core::mem::size_of_val(response)
2388 }
2389 Self::GetTransactionsHandle(_) => 0,
2390 }
2391 }
2392}
2393
2394#[cfg(test)]
2395mod tests {
2396 use super::*;
2397 use crate::{
2398 test_utils::{
2399 transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2400 Testnet,
2401 },
2402 transactions::config::RelaxedEthAnnouncementFilter,
2403 NetworkConfigBuilder, NetworkManager,
2404 };
2405 use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2406 use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2407 use alloy_primitives::{address, hex, Signature, TxKind, B256, U256};
2408 use alloy_rlp::Decodable;
2409 use futures::FutureExt;
2410 use reth_chainspec::MIN_TRANSACTION_GAS;
2411 use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2412 use reth_network_api::{NetworkInfo, PeerKind};
2413 use reth_network_p2p::{
2414 error::{RequestError, RequestResult},
2415 sync::{NetworkSyncUpdater, SyncState},
2416 };
2417 use reth_storage_api::noop::NoopProvider;
2418 use reth_tasks::Runtime;
2419 use reth_transaction_pool::{
2420 blobstore::InMemoryBlobStore,
2421 error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2422 identifier::SenderIdentifiers,
2423 test_utils::{
2424 testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2425 TransactionGenerator,
2426 },
2427 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2428 };
2429 use secp256k1::SecretKey;
2430 use std::{
2431 future::poll_fn,
2432 net::{IpAddr, Ipv4Addr, SocketAddr},
2433 str::FromStr,
2434 time::Instant,
2435 };
2436 use tracing::error;
2437
2438 type EthTestPool = Pool<
2439 OkValidator<EthPooledTransaction>,
2440 CoinbaseTipOrdering<EthPooledTransaction>,
2441 InMemoryBlobStore,
2442 >;
2443
2444 #[tokio::test]
2445 async fn announcement_policy_preserves_order_and_skips_pending_and_bad_imports() {
2446 #[derive(Debug, Clone, Default)]
2447 struct RecordingPolicy(Arc<parking_lot::Mutex<Vec<(u8, TxHash, usize)>>>);
2448
2449 impl AnnouncementFilteringPolicy<EthNetworkPrimitives> for RecordingPolicy {
2450 fn decide_on_announcement(
2451 &self,
2452 tx_type: u8,
2453 hash: &TxHash,
2454 size: usize,
2455 ) -> AnnouncementAcceptance {
2456 self.0.lock().push((tx_type, *hash, size));
2457 match tx_type {
2458 0xff => AnnouncementAcceptance::Reject { penalize_peer: true },
2459 1 => AnnouncementAcceptance::Ignore,
2460 _ => AnnouncementAcceptance::Accept,
2461 }
2462 }
2463 }
2464
2465 let (mut manager, _network) = new_tx_manager().await;
2466 let policy = RecordingPolicy::default();
2467 manager.policies =
2468 NetworkPolicies::new(TransactionPropagationKind::default(), policy.clone());
2469 let peer_id = PeerId::repeat_byte(1);
2470 let (peer, mut rx) = new_mock_session(peer_id, EthVersion::Eth68);
2471 manager.peers.insert(peer_id, peer);
2472 let [pending, accepted, ignored, rejected, later, bad] =
2473 [1, 2, 3, 4, 5, 6].map(B256::repeat_byte);
2474 manager.transactions_by_peers.insert(pending, Default::default());
2475 manager.bad_imports.insert(bad);
2476 manager.on_new_pooled_transaction_hashes(
2477 peer_id,
2478 NewPooledTransactionHashes68 {
2479 hashes: vec![pending, accepted, accepted, ignored, rejected, later, bad],
2480 types: vec![0xff, 2, 0xff, 1, 0xff, 2, 0xff],
2481 sizes: vec![100, 200, 300, 400, 500, 600, 700],
2482 }
2483 .into(),
2484 );
2485
2486 assert_eq!(
2487 *policy.0.lock(),
2488 [(2, accepted, 200), (1, ignored, 400), (0xff, rejected, 500), (2, later, 600)]
2489 );
2490 poll_fn(|cx| {
2491 let _ = manager.poll_unpin(cx);
2492 Poll::Ready(())
2493 })
2494 .await;
2495 let PeerRequest::GetPooledTransactions { request, .. } = rx.try_recv().unwrap() else {
2496 panic!("expected transaction request")
2497 };
2498 assert_eq!(request.0, [accepted, later]);
2499 }
2500
2501 async fn new_eth_tx_manager() -> (
2502 TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2503 NetworkManager<EthNetworkPrimitives>,
2504 ) {
2505 let secret_key = SecretKey::new(&mut rand::rng());
2506 let client = NoopProvider::default();
2507
2508 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2509 .listener_port(0)
2510 .disable_discovery()
2511 .build(client);
2512
2513 let pool = Pool::new(
2514 OkValidator::default(),
2515 CoinbaseTipOrdering::default(),
2516 InMemoryBlobStore::default(),
2517 Default::default(),
2518 );
2519
2520 let transactions_manager_config = config.transactions_manager_config.clone();
2521 let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2522 .await
2523 .unwrap()
2524 .into_builder()
2525 .transactions(pool.clone(), transactions_manager_config)
2526 .split_with_handle();
2527
2528 (transactions, network)
2529 }
2530
2531 fn valid_eth_pool_transaction(
2532 transaction: EthPooledTransaction,
2533 ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2534 let mut ids = SenderIdentifiers::default();
2535 let transaction_id =
2536 ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2537
2538 Arc::new(ValidPoolTransaction {
2539 propagate: false,
2540 transaction_id,
2541 transaction,
2542 timestamp: Instant::now(),
2543 origin: TransactionOrigin::External,
2544 authority_ids: None,
2545 })
2546 }
2547
2548 fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2549 tx_gen: &mut TransactionGenerator<R>,
2550 nonce: u64,
2551 ) -> EthPooledTransaction {
2552 EthPooledTransaction::try_from_consensus(
2553 tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2554 )
2555 .unwrap()
2556 }
2557
2558 #[tokio::test(flavor = "multi_thread")]
2559 async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2560 reth_tracing::init_test_tracing();
2561 let net = Testnet::create(3).await.spawn();
2562 let [peer0, peer1, _] = net.peers_array();
2563
2564 let listener0 = peer0.event_listener();
2565 peer0.add_peer(peer1);
2566 let secret_key = SecretKey::new(&mut rand::rng());
2567
2568 let client = NoopProvider::default();
2569 let pool = testing_pool();
2570 let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2571 .disable_discovery()
2572 .listener_port(0)
2573 .build(client);
2574 let transactions_manager_config = config.transactions_manager_config.clone();
2575 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2576 .await
2577 .unwrap()
2578 .into_builder()
2579 .transactions(pool.clone(), transactions_manager_config)
2580 .split_with_handle();
2581
2582 tokio::task::spawn(network);
2583
2584 network_handle.update_sync_state(SyncState::Syncing);
2586 assert!(NetworkInfo::is_syncing(&network_handle));
2587 assert!(NetworkInfo::is_initially_syncing(&network_handle));
2588
2589 let mut established = listener0.take(2);
2591 while let Some(ev) = established.next().await {
2592 match ev {
2593 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2594 transactions
2596 .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2597 }
2598 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2599 ev => {
2600 error!("unexpected event {ev:?}")
2601 }
2602 }
2603 }
2604 let input = hex!(
2606 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2607 );
2608 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2609 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2610 peer_id: *peer1.peer_id(),
2611 msg: Transactions(vec![signed_tx.clone()]),
2612 });
2613 poll_fn(|cx| {
2614 let _ = transactions.poll_unpin(cx);
2615 Poll::Ready(())
2616 })
2617 .await;
2618 assert!(pool.is_empty());
2619 }
2620
2621 #[tokio::test(flavor = "multi_thread")]
2622 async fn test_tx_broadcasts_through_two_syncs() {
2623 reth_tracing::init_test_tracing();
2624 let net = Testnet::create(3).await.spawn();
2625 let [peer0, peer1, _] = net.peers_array();
2626
2627 let listener0 = peer0.event_listener();
2628 peer0.add_peer(peer1);
2629 let secret_key = SecretKey::new(&mut rand::rng());
2630
2631 let client = NoopProvider::default();
2632 let pool = testing_pool();
2633 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2634 .disable_discovery()
2635 .listener_port(0)
2636 .build(client);
2637 let transactions_manager_config = config.transactions_manager_config.clone();
2638 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2639 .await
2640 .unwrap()
2641 .into_builder()
2642 .transactions(pool.clone(), transactions_manager_config)
2643 .split_with_handle();
2644
2645 tokio::task::spawn(network);
2646
2647 network_handle.update_sync_state(SyncState::Syncing);
2649 assert!(NetworkInfo::is_syncing(&network_handle));
2650 network_handle.update_sync_state(SyncState::Idle);
2651 assert!(!NetworkInfo::is_syncing(&network_handle));
2652 network_handle.update_sync_state(SyncState::Syncing);
2653 assert!(NetworkInfo::is_syncing(&network_handle));
2654
2655 let mut established = listener0.take(2);
2657 while let Some(ev) = established.next().await {
2658 match ev {
2659 NetworkEvent::ActivePeerSession { .. } |
2660 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2661 transactions.on_network_event(ev);
2663 }
2664 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2665 _ => {
2666 error!("unexpected event {ev:?}")
2667 }
2668 }
2669 }
2670 let input = hex!(
2672 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2673 );
2674 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2675 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2676 peer_id: *peer1.peer_id(),
2677 msg: Transactions(vec![signed_tx.clone()]),
2678 });
2679 poll_fn(|cx| {
2680 let _ = transactions.poll_unpin(cx);
2681 Poll::Ready(())
2682 })
2683 .await;
2684 assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2685 assert!(NetworkInfo::is_syncing(&network_handle));
2686 assert!(!pool.is_empty());
2687 }
2688
2689 #[tokio::test(flavor = "multi_thread")]
2692 async fn test_handle_incoming_transactions_hashes() {
2693 reth_tracing::init_test_tracing();
2694
2695 let secret_key = SecretKey::new(&mut rand::rng());
2696 let client = NoopProvider::default();
2697
2698 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2699 .listener_port(0)
2701 .disable_discovery()
2702 .build(client);
2703
2704 let pool = testing_pool();
2705
2706 let transactions_manager_config = config.transactions_manager_config.clone();
2707 let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2708 .await
2709 .unwrap()
2710 .into_builder()
2711 .transactions(pool.clone(), transactions_manager_config)
2712 .split_with_handle();
2713
2714 let peer_id_1 = PeerId::repeat_byte(1);
2715 let eth_version = EthVersion::Eth66;
2716
2717 let txs = vec![TransactionSigned::new_unhashed(
2718 Transaction::Legacy(TxLegacy {
2719 chain_id: Some(4),
2720 nonce: 15u64,
2721 gas_price: 2200000000,
2722 gas_limit: 34811,
2723 to: TxKind::Call(address!("cf7f9e66af820a19257a2108375b180b0ec49167")),
2724 value: U256::from(1234u64),
2725 input: Default::default(),
2726 }),
2727 Signature::new(
2728 U256::from_str(
2729 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2730 )
2731 .unwrap(),
2732 U256::from_str(
2733 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2734 )
2735 .unwrap(),
2736 true,
2737 ),
2738 )];
2739
2740 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2741
2742 let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2743 tx_manager.peers.insert(peer_id_1, peer_1);
2744
2745 assert!(pool.is_empty());
2746
2747 tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2748 peer_id: peer_id_1,
2749 msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2750 txs_hashes.clone(),
2751 )),
2752 });
2753
2754 poll_fn(|cx| {
2756 let _ = tx_manager.poll_unpin(cx);
2757 Poll::Ready(())
2758 })
2759 .await;
2760
2761 let req = to_mock_session_rx
2763 .recv()
2764 .await
2765 .expect("peer_1 session should receive request with buffered hashes");
2766 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2767 assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2768
2769 let message: Vec<PooledTransactionVariant> = txs
2770 .into_iter()
2771 .map(|tx| {
2772 PooledTransactionVariant::try_from(tx)
2773 .expect("Failed to convert MockTransaction to PooledTransaction")
2774 })
2775 .collect();
2776
2777 response
2779 .send(Ok(PooledTransactions(message)))
2780 .expect("should send peer_1 response to tx manager");
2781
2782 poll_fn(|cx| {
2784 let _ = tx_manager.poll_unpin(cx);
2785 Poll::Ready(())
2786 })
2787 .await;
2788
2789 assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2792 }
2793
2794 #[tokio::test(flavor = "multi_thread")]
2795 async fn test_handle_incoming_transactions() {
2796 reth_tracing::init_test_tracing();
2797 let net = Testnet::create(3).await.spawn();
2798 let [peer0, peer1, _] = net.peers_array();
2799
2800 let listener0 = peer0.event_listener();
2801
2802 peer0.add_peer(peer1);
2803 let secret_key = SecretKey::new(&mut rand::rng());
2804
2805 let client = NoopProvider::default();
2806 let pool = testing_pool();
2807 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2808 .disable_discovery()
2809 .listener_port(0)
2810 .build(client);
2811 let transactions_manager_config = config.transactions_manager_config.clone();
2812 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2813 .await
2814 .unwrap()
2815 .into_builder()
2816 .transactions(pool.clone(), transactions_manager_config)
2817 .split_with_handle();
2818 tokio::task::spawn(network);
2819
2820 network_handle.update_sync_state(SyncState::Idle);
2821
2822 assert!(!NetworkInfo::is_syncing(&network_handle));
2823
2824 let mut established = listener0.take(2);
2826 while let Some(ev) = established.next().await {
2827 match ev {
2828 NetworkEvent::ActivePeerSession { .. } |
2829 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2830 transactions.on_network_event(ev);
2832 }
2833 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2834 ev => {
2835 error!("unexpected event {ev:?}")
2836 }
2837 }
2838 }
2839 let input = hex!(
2841 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2842 );
2843 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2844 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2845 peer_id: *peer1.peer_id(),
2846 msg: Transactions(vec![signed_tx.clone()]),
2847 });
2848 assert!(transactions
2849 .transactions_by_peers
2850 .get(signed_tx.tx_hash())
2851 .unwrap()
2852 .contains(peer1.peer_id()));
2853
2854 poll_fn(|cx| {
2856 let _ = transactions.poll_unpin(cx);
2857 Poll::Ready(())
2858 })
2859 .await;
2860
2861 assert!(!pool.is_empty());
2862 assert!(pool.get(signed_tx.tx_hash()).is_some());
2863 }
2864
2865 #[tokio::test(flavor = "multi_thread")]
2866 async fn test_session_closed_cleans_transaction_peer_state() {
2867 let (mut tx_manager, _network) = new_tx_manager().await;
2868 let peer_id = PeerId::repeat_byte(1);
2869 let fallback_peer = PeerId::repeat_byte(2);
2870 let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2871 let (fallback, _) = new_mock_session(fallback_peer, EthVersion::Eth66);
2872 let hash_shared = B256::from_slice(&[1; 32]);
2873
2874 tx_manager.peers.insert(peer_id, peer);
2875 tx_manager.peers.insert(fallback_peer, fallback);
2876 buffer_hash_to_tx_fetcher(&mut tx_manager.transaction_fetcher, hash_shared, peer_id, None);
2877 buffer_hash_to_tx_fetcher(
2878 &mut tx_manager.transaction_fetcher,
2879 hash_shared,
2880 fallback_peer,
2881 None,
2882 );
2883 assert_eq!(
2884 tx_manager.transaction_fetcher.candidate_peers(&hash_shared),
2885 vec![peer_id, fallback_peer]
2886 );
2887
2888 tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2889 peer_id,
2890 reason: None,
2891 }));
2892
2893 assert!(!tx_manager.peers.contains_key(&peer_id));
2895 assert!(tx_manager.transaction_fetcher.queued_hashes(&peer_id).is_empty());
2896 assert_eq!(
2898 tx_manager.transaction_fetcher.candidate_peers(&hash_shared),
2899 vec![fallback_peer]
2900 );
2901 }
2902
2903 #[tokio::test(flavor = "multi_thread")]
2904 async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2905 let (mut tx_manager, _network) = new_tx_manager().await;
2906 let peer_id = PeerId::repeat_byte(1);
2907 let hash = B256::from_slice(&[1; 32]);
2908
2909 tx_manager.network.update_sync_state(SyncState::Idle);
2910 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2911
2912 let err = PoolError::new(
2913 hash,
2914 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2915 BlobTransactionValidationError::InvalidProof,
2916 )),
2917 );
2918
2919 tx_manager.on_bad_import(err);
2920
2921 assert!(!tx_manager.bad_imports.contains(&hash));
2922 }
2923
2924 #[tokio::test(flavor = "multi_thread")]
2925 async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2926 let (mut tx_manager, _network) = new_tx_manager().await;
2927 let peer_id = PeerId::repeat_byte(1);
2928 let hash = B256::from_slice(&[3; 32]);
2929
2930 tx_manager.network.update_sync_state(SyncState::Idle);
2931 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2932
2933 let err = PoolError::new(
2934 hash,
2935 InvalidPoolTransactionError::Eip4844(
2936 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2937 ),
2938 );
2939
2940 tx_manager.on_bad_import(err);
2941
2942 assert!(!tx_manager.bad_imports.contains(&hash));
2943 }
2944
2945 #[tokio::test(flavor = "multi_thread")]
2946 async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2947 let (mut tx_manager, _network) = new_tx_manager().await;
2948 let peer_id = PeerId::repeat_byte(1);
2949 let hash = B256::from_slice(&[2; 32]);
2950
2951 tx_manager.network.update_sync_state(SyncState::Idle);
2952 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2953
2954 let err = PoolError::new(
2955 hash,
2956 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2957 );
2958
2959 tx_manager.on_bad_import(err);
2960
2961 assert!(tx_manager.bad_imports.contains(&hash));
2962 }
2963
2964 #[tokio::test(flavor = "multi_thread")]
2965 async fn test_on_get_pooled_transactions_network() {
2966 reth_tracing::init_test_tracing();
2967 let net = Testnet::create(2).await.spawn();
2968 let [peer0, peer1] = net.peers_array();
2969
2970 let listener0 = peer0.event_listener();
2971
2972 peer0.add_peer(peer1);
2973 let secret_key = SecretKey::new(&mut rand::rng());
2974
2975 let client = NoopProvider::default();
2976 let pool = testing_pool();
2977 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2978 .disable_discovery()
2979 .listener_port(0)
2980 .build(client);
2981 let transactions_manager_config = config.transactions_manager_config.clone();
2982 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2983 .await
2984 .unwrap()
2985 .into_builder()
2986 .transactions(pool.clone(), transactions_manager_config)
2987 .split_with_handle();
2988 tokio::task::spawn(network);
2989
2990 network_handle.update_sync_state(SyncState::Idle);
2991
2992 assert!(!NetworkInfo::is_syncing(&network_handle));
2993
2994 let mut established = listener0.take(2);
2996 while let Some(ev) = established.next().await {
2997 match ev {
2998 NetworkEvent::ActivePeerSession { .. } |
2999 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
3000 transactions.on_network_event(ev);
3001 }
3002 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
3003 ev => {
3004 error!("unexpected event {ev:?}")
3005 }
3006 }
3007 }
3008
3009 let tx = MockTransaction::eip1559();
3010 let _ = transactions
3011 .pool
3012 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3013 .await;
3014
3015 let request = GetPooledTransactions(vec![*tx.get_hash()]);
3016
3017 let (send, receive) =
3018 oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
3019
3020 transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
3021 peer_id: *peer1.peer_id(),
3022 request,
3023 response: send,
3024 });
3025
3026 match receive.await.unwrap() {
3027 Ok(PooledTransactions(transactions)) => {
3028 assert_eq!(transactions.len(), 1);
3029 }
3030 Err(e) => {
3031 panic!("error: {e:?}");
3032 }
3033 }
3034 }
3035
3036 #[tokio::test]
3040 async fn test_partially_tx_response() {
3041 reth_tracing::init_test_tracing();
3042
3043 let mut tx_manager = new_tx_manager().await.0;
3044 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3045
3046 let peer_id_1 = PeerId::repeat_byte(1);
3047 let eth_version = EthVersion::Eth66;
3048
3049 let txs = vec![
3050 TransactionSigned::new_unhashed(
3051 Transaction::Legacy(TxLegacy {
3052 chain_id: Some(4),
3053 nonce: 15u64,
3054 gas_price: 2200000000,
3055 gas_limit: 34811,
3056 to: TxKind::Call(address!("cf7f9e66af820a19257a2108375b180b0ec49167")),
3057 value: U256::from(1234u64),
3058 input: Default::default(),
3059 }),
3060 Signature::new(
3061 U256::from_str(
3062 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
3063 )
3064 .unwrap(),
3065 U256::from_str(
3066 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
3067 )
3068 .unwrap(),
3069 true,
3070 ),
3071 ),
3072 TransactionSigned::new_unhashed(
3073 Transaction::Eip1559(TxEip1559 {
3074 chain_id: 4,
3075 nonce: 26u64,
3076 max_priority_fee_per_gas: 1500000000,
3077 max_fee_per_gas: 1500000013,
3078 gas_limit: MIN_TRANSACTION_GAS,
3079 to: TxKind::Call(address!("61815774383099e24810ab832a5b2a5425c154d5")),
3080 value: U256::from(3000000000000000000u64),
3081 input: Default::default(),
3082 access_list: Default::default(),
3083 }),
3084 Signature::new(
3085 U256::from_str(
3086 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
3087 )
3088 .unwrap(),
3089 U256::from_str(
3090 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
3091 )
3092 .unwrap(),
3093 true,
3094 ),
3095 ),
3096 ];
3097
3098 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3099
3100 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3101 peer_1.seen_transactions.insert(txs_hashes[0]);
3104 peer_1.seen_transactions.insert(txs_hashes[1]);
3105 tx_manager.peers.insert(peer_id_1, peer_1);
3106
3107 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, None);
3108 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, None);
3109
3110 assert!(tx_fetcher.is_idle(&peer_id_1));
3112 assert_eq!(tx_fetcher.num_inflight_requests(), 0);
3113
3114 assert_eq!(tx_fetcher.dispatch(&tx_manager.peers, usize::MAX), 1);
3116
3117 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3118 assert!(!tx_fetcher.is_idle(&peer_id_1));
3120 assert_eq!(tx_fetcher.num_inflight_requests(), 1);
3121
3122 let req = to_mock_session_rx
3124 .recv()
3125 .await
3126 .expect("peer_1 session should receive request with buffered hashes");
3127 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3128
3129 let message: Vec<PooledTransactionVariant> = txs
3130 .into_iter()
3131 .take(1)
3132 .map(|tx| {
3133 PooledTransactionVariant::try_from(tx)
3134 .expect("Failed to convert MockTransaction to PooledTransaction")
3135 })
3136 .collect();
3137 response
3139 .send(Ok(PooledTransactions(message)))
3140 .expect("should send peer_1 response to tx manager");
3141 let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3142 unreachable!()
3143 };
3144
3145 assert!(tx_fetcher.is_idle(&peer_id));
3147 assert_eq!(tx_fetcher.num_inflight_requests(), 0);
3148 assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3150 assert_eq!(tx_fetcher.candidate_peers(&txs_hashes[1]), vec![peer_id_1]);
3151 }
3152
3153 #[tokio::test]
3156 async fn test_failed_request_retries_on_alternate_peer() {
3157 reth_tracing::init_test_tracing();
3158
3159 let mut tx_manager = new_tx_manager().await.0;
3160 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3161
3162 let peer_id_1 = PeerId::repeat_byte(1);
3163 let peer_id_2 = PeerId::repeat_byte(2);
3164 let eth_version = EthVersion::Eth66;
3165 let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3166
3167 let (peer_1, mut to_mock_session_rx_1) = new_mock_session(peer_id_1, eth_version);
3168 let (peer_2, mut to_mock_session_rx_2) = new_mock_session(peer_id_2, eth_version);
3169 tx_manager.peers.insert(peer_id_1, peer_1);
3170 tx_manager.peers.insert(peer_id_2, peer_2);
3171
3172 for hash in seen_hashes {
3174 buffer_hash_to_tx_fetcher(tx_fetcher, hash, peer_id_1, None);
3175 buffer_hash_to_tx_fetcher(tx_fetcher, hash, peer_id_2, None);
3176 }
3177 assert!(tx_fetcher.is_idle(&peer_id_1));
3178 assert!(tx_fetcher.is_idle(&peer_id_2));
3179
3180 assert_eq!(tx_fetcher.dispatch(&tx_manager.peers, usize::MAX), 1);
3182 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3183 assert!(!tx_fetcher.is_idle(&peer_id_1));
3184 assert!(tx_fetcher.is_idle(&peer_id_2));
3185
3186 let req = to_mock_session_rx_1
3187 .recv()
3188 .await
3189 .expect("peer_1 session should receive request with buffered hashes");
3190 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3191 let GetPooledTransactions(hashes) = request;
3192 assert_eq!(
3193 hashes.into_iter().collect::<B256Set>(),
3194 seen_hashes.into_iter().collect::<B256Set>()
3195 );
3196
3197 response
3199 .send(Err(RequestError::BadResponse))
3200 .expect("should send peer_1 response to tx manager");
3201 let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3202 unreachable!()
3203 };
3204 assert_eq!(peer_id, peer_id_1);
3205
3206 assert!(tx_fetcher.is_idle(&peer_id_1));
3208 assert_eq!(tx_fetcher.num_inflight_requests(), 0);
3209 assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3210 assert_eq!(tx_fetcher.candidate_peers(&seen_hashes[0]), vec![peer_id_2]);
3211
3212 assert_eq!(tx_fetcher.dispatch(&tx_manager.peers, usize::MAX), 1);
3214 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3215 let req = to_mock_session_rx_2
3216 .recv()
3217 .await
3218 .expect("peer_2 session should receive request with buffered hashes");
3219 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3220
3221 response
3223 .send(Err(RequestError::BadResponse))
3224 .expect("should send peer_2 response to tx manager");
3225 let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3226
3227 assert_eq!(tx_fetcher.num_hashes(), 0);
3229 assert_eq!(tx_fetcher.num_inflight_requests(), 0);
3230 }
3231
3232 #[test]
3233 fn test_direct_propagation_transaction_uses_2718_size() {
3234 let mut tx_gen = TransactionGenerator::new(rand::rng());
3235 let tx = tx_gen.gen_eip1559();
3236 let expected_size = tx.encode_2718_len();
3237
3238 let tx = PropagateTransaction::new(tx);
3239
3240 assert_eq!(tx.propagation_size(), expected_size);
3241 }
3242
3243 #[test]
3244 fn test_transaction_builder_empty() {
3245 let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3246 assert!(builder.is_empty());
3247
3248 let mut tx_gen = TransactionGenerator::new(rand::rng());
3249 let tx =
3250 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3251 builder.push(&tx);
3252 assert!(!builder.is_empty());
3253
3254 let txs = builder.build();
3255 assert!(txs.full.is_none());
3256 let txs = txs.pooled.unwrap();
3257 assert_eq!(txs.len(), 1);
3258 }
3259
3260 #[test]
3261 fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3262 let mut tx_gen = TransactionGenerator::new(rand::rng());
3263 let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3264 let pooled = PropagatePooledTransactionEncoder::new(tx);
3265
3266 let mut pooled_encoded = Vec::new();
3267 pooled.encode(&mut pooled_encoded);
3268 assert_eq!(pooled.length(), pooled_encoded.len());
3269
3270 let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3271 let mut first_encoded = Vec::new();
3272 broadcast.encode(&mut first_encoded);
3273 let mut second_encoded = Vec::new();
3274 broadcast.encode(&mut second_encoded);
3275 assert_eq!(first_encoded, second_encoded);
3276
3277 let mut encoded = first_encoded.as_slice();
3278 let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3279 assert_eq!(decoded.len(), 1);
3280 assert!(encoded.is_empty());
3281 }
3282
3283 #[test]
3284 fn test_transaction_builder_large() {
3285 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3286 assert!(builder.is_empty());
3287
3288 let mut tx_gen = TransactionGenerator::new(rand::rng());
3289 let mut tx = tx_gen.gen_eip1559_pooled();
3290 tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3292 let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3293 builder.push(&tx);
3294 assert!(!builder.is_empty());
3295
3296 let txs = builder.clone().build();
3297 assert!(txs.pooled.is_none());
3298 let txs = txs.full.unwrap();
3299 assert_eq!(txs.len(), 1);
3300
3301 builder.push(&tx);
3302
3303 let txs = builder.clone().build();
3304 let pooled = txs.pooled.unwrap();
3305 assert_eq!(pooled.len(), 1);
3306 let txs = txs.full.unwrap();
3307 assert_eq!(txs.len(), 1);
3308 }
3309
3310 #[test]
3311 fn test_transaction_builder_eip4844() {
3312 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3313 assert!(builder.is_empty());
3314
3315 let mut tx_gen = TransactionGenerator::new(rand::rng());
3316 let tx =
3317 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3318 builder.push(&tx);
3319 assert!(!builder.is_empty());
3320
3321 let txs = builder.clone().build();
3322 assert!(txs.full.is_none());
3323 let txs = txs.pooled.unwrap();
3324 assert_eq!(txs.len(), 1);
3325
3326 let tx =
3327 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3328 builder.push(&tx);
3329
3330 let txs = builder.clone().build();
3331 let pooled = txs.pooled.unwrap();
3332 assert_eq!(pooled.len(), 1);
3333 let txs = txs.full.unwrap();
3334 assert_eq!(txs.len(), 1);
3335 }
3336
3337 #[tokio::test]
3338 async fn test_propagate_full() {
3339 reth_tracing::init_test_tracing();
3340
3341 let (mut tx_manager, network) = new_eth_tx_manager().await;
3342 let peer_id = PeerId::random();
3343
3344 network.handle().update_sync_state(SyncState::Idle);
3346
3347 let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3349
3350 let session_info = SessionInfo {
3351 peer_id,
3352 remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3353 client_version: Arc::from(""),
3354 capabilities: Arc::new(vec![].into()),
3355 status: Arc::new(Default::default()),
3356 version: EthVersion::Eth68,
3357 peer_kind: PeerKind::Basic,
3358 };
3359 let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3360 tx_manager
3361 .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3362 let mut propagate = vec![];
3363 let mut tx_gen = TransactionGenerator::new(rand::rng());
3364 let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3365 propagate.push(eip1559_tx.clone());
3366 let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3367 propagate.push(eip4844_tx.clone());
3368
3369 let propagated = tx_manager.propagate_transactions(
3370 propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3371 PropagationMode::Basic,
3372 );
3373 assert_eq!(propagated.len(), 2);
3374 let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3375 assert_eq!(prop_txs.len(), 1);
3376 assert!(prop_txs[0].is_full());
3377
3378 let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3379 assert_eq!(prop_txs.len(), 1);
3380 assert!(prop_txs[0].is_hash());
3381
3382 let peer = tx_manager.peers.get(&peer_id).unwrap();
3383 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3384 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3385 peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3386
3387 let propagated = tx_manager.propagate_transactions(
3389 propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3390 PropagationMode::Basic,
3391 );
3392 assert!(propagated.is_empty());
3393 }
3394
3395 #[test]
3396 fn test_eth72_hashes_builder_sets_cell_mask_for_blob_txs() {
3397 let mut tx_gen = TransactionGenerator::new(rand::rng());
3398
3399 let mut builder = PooledTransactionsHashesBuilder::new(EthVersion::Eth72);
3401 builder.push(&PropagateTransaction::pool_tx(valid_eth_pool_transaction(
3402 tx_gen.gen_eip1559_pooled(),
3403 )));
3404 let msg = builder.build();
3405 assert_eq!(msg.as_eth72().unwrap().cell_mask, None);
3406
3407 let mut builder = PooledTransactionsHashesBuilder::new(EthVersion::Eth72);
3409 builder.push(&PropagateTransaction::pool_tx(valid_eth_pool_transaction(
3410 tx_gen.gen_eip1559_pooled(),
3411 )));
3412 builder.push_pooled(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3413 let msg = builder.build();
3414 assert_eq!(
3415 msg.as_eth72().unwrap().cell_mask,
3416 Some(NewPooledTransactionHashes72::ALL_CELLS_MASK)
3417 );
3418 }
3419
3420 #[tokio::test]
3421 async fn test_truncated_hash_announcement_not_marked_seen() {
3422 reth_tracing::init_test_tracing();
3423
3424 let (mut tx_manager, network) = new_eth_tx_manager().await;
3425 tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3427
3428 network.handle().update_sync_state(SyncState::Idle);
3430
3431 let peer_id = PeerId::random();
3432 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3433 tx_manager.peers.insert(peer_id, peer);
3434
3435 let mut tx_gen = TransactionGenerator::new(rand::rng());
3437 let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3438 .map(|nonce| {
3439 valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3440 })
3441 .collect::<Vec<_>>();
3442 let last_sent = *txs[txs.len() - 2].hash();
3443 let truncated = *txs[txs.len() - 1].hash();
3444
3445 let propagated = tx_manager.propagate_transactions(
3446 txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3447 PropagationMode::Basic,
3448 );
3449
3450 assert!(propagated.get(&truncated).is_none());
3452 let peer = tx_manager.peers.get(&peer_id).unwrap();
3453 assert!(!peer.seen_transactions.contains(&truncated));
3454 assert!(peer.seen_transactions.contains(&last_sent));
3455 }
3456
3457 #[tokio::test]
3458 async fn test_propagate_pending_txs_while_initially_syncing() {
3459 reth_tracing::init_test_tracing();
3460
3461 let (mut tx_manager, network) = new_eth_tx_manager().await;
3462 let peer_id = PeerId::random();
3463
3464 network.handle().update_sync_state(SyncState::Syncing);
3466 assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3467
3468 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3470 tx_manager.peers.insert(peer_id, peer);
3471
3472 let mut tx_gen = TransactionGenerator::new(rand::rng());
3473 let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3474 let tx_hash = *tx.hash();
3475 tx_manager
3476 .pool
3477 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3478 .await
3479 .expect("transaction should be accepted into the pool");
3480
3481 tx_manager.on_new_pending_transactions(vec![tx_hash]);
3482
3483 let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3484 assert!(peer.seen_transactions.contains(&tx_hash));
3485 }
3486
3487 #[tokio::test]
3488 async fn test_relaxed_filter_ignores_unknown_tx_types() {
3489 reth_tracing::init_test_tracing();
3490
3491 let transactions_manager_config = TransactionsManagerConfig::default();
3492
3493 let propagation_policy = TransactionPropagationKind::default();
3494 let announcement_policy = RelaxedEthAnnouncementFilter::default();
3495
3496 let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3497
3498 let pool = testing_pool();
3499 let secret_key = SecretKey::new(&mut rand::rng());
3500 let client = NoopProvider::default();
3501
3502 let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3503 .listener_port(0)
3504 .disable_discovery()
3505 .build(client.clone());
3506
3507 let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3508 let (to_tx_manager_tx, from_network_rx) =
3509 reth_metrics::common::mpsc::memory_bounded_channel::<
3510 NetworkTransactionEvent<EthNetworkPrimitives>,
3511 >(
3512 crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3513 "test_tx_channel",
3514 );
3515 network_manager.set_transactions(to_tx_manager_tx);
3516 let network_handle = network_manager.handle().clone();
3517 let network_service_handle = tokio::spawn(network_manager);
3518
3519 let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3520 network_handle.clone(),
3521 pool.clone(),
3522 from_network_rx,
3523 transactions_manager_config,
3524 policy_bundle,
3525 );
3526
3527 let peer_id = PeerId::random();
3528 let eth_version = EthVersion::Eth68;
3529 let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3530 tx_manager.peers.insert(peer_id, mock_peer_metadata);
3531
3532 let mut tx_factory = MockTransactionFactory::default();
3533
3534 let valid_known_tx = tx_factory.create_eip1559();
3535 let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3536
3537 let known_tx_hash = *known_tx_signed.hash();
3538 let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3539 let known_tx_size = known_tx_signed.encoded_length();
3540
3541 let unknown_tx_hash = B256::random();
3542 let unknown_tx_type_byte = 0xff_u8;
3543 let unknown_tx_size = 150;
3544
3545 let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3546 types: vec![known_tx_type_byte, unknown_tx_type_byte],
3547 sizes: vec![known_tx_size, unknown_tx_size],
3548 hashes: vec![known_tx_hash, unknown_tx_hash],
3549 });
3550
3551 tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3552
3553 poll_fn(|cx| {
3554 let _ = tx_manager.poll_unpin(cx);
3555 Poll::Ready(())
3556 })
3557 .await;
3558
3559 let mut requested_hashes_in_getpooled = B256Set::default();
3560 let mut unexpected_request_received = false;
3561
3562 match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3563 .await
3564 {
3565 Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3566 let GetPooledTransactions(hashes) = request;
3567 for hash in hashes {
3568 requested_hashes_in_getpooled.insert(hash);
3569 }
3570 let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3571 }
3572 Ok(Some(other_request)) => {
3573 tracing::error!(?other_request, "Received unexpected PeerRequest type");
3574 unexpected_request_received = true;
3575 }
3576 Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3577 Err(_timeout_err) => {
3578 tracing::info!("Timeout: No GetPooledTransactions request received.")
3579 }
3580 }
3581
3582 assert!(
3583 requested_hashes_in_getpooled.contains(&known_tx_hash),
3584 "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3585 );
3586 assert!(
3587 !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3588 "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3589 );
3590 assert!(
3591 !unexpected_request_received,
3592 "An unexpected P2P request was received by the mock peer."
3593 );
3594
3595 network_service_handle.abort();
3596 }
3597}