1use alloy_consensus::transaction::TxHashRef;
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use smallvec::SmallVec;
6
7pub mod config;
9pub mod constants;
11pub mod fetcher;
13pub mod policy;
15
16pub use self::constants::{
17 tx_fetcher::DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
18 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
19};
20use config::AnnouncementAcceptance;
21pub use config::{
22 AnnouncementFilteringPolicy, TransactionFetcherConfig, TransactionIngressPolicy,
23 TransactionPropagationMode, TransactionPropagationPolicy, TransactionsManagerConfig,
24};
25use policy::NetworkPolicies;
26
27pub(crate) use fetcher::{FetchEvent, TransactionFetcher};
28
29use self::constants::{tx_manager::*, DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE};
30use crate::{
31 budget::{
32 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
33 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS, DEFAULT_BUDGET_TRY_DRAIN_STREAM,
34 },
35 cache::LruCache,
36 duration_metered_exec, metered_poll_nested_stream_with_budget,
37 metrics::{AnnouncedTxTypesMetrics, TransactionsManagerMetrics},
38 transactions::config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
39 NetworkHandle, TxTypesCounter,
40};
41use alloy_eips::eip2718::Typed2718;
42use alloy_primitives::{
43 bytes::BufMut,
44 map::{hash_map::Entry, B256Map, B256Set, FbBuildHasher, HashMap, HashSet},
45 TxHash, B256,
46};
47use alloy_rlp::Encodable;
48use constants::SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE;
49use futures::{stream::FuturesUnordered, Future, StreamExt};
50use reth_eth_wire::{
51 BroadcastPoolTransactions, DedupPayload, EthNetworkPrimitives, EthVersion,
52 GetPooledTransactions, HandleMempoolData, HandleVersionedMempoolData, LazyEncoded,
53 LazyEncodedTransaction, NetworkPrimitives, NewPooledTransactionHashes,
54 NewPooledTransactionHashes66, NewPooledTransactionHashes68, NewPooledTransactionHashes72,
55 PooledTransactions, RequestTxHashes, Transactions, ValidAnnouncementData,
56};
57use reth_ethereum_primitives::TxType;
58use reth_metrics::common::mpsc::MemoryBoundedReceiver;
59use reth_network_api::{
60 events::{PeerEvent, SessionInfo},
61 NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
62};
63use reth_network_p2p::{
64 error::{RequestError, RequestResult},
65 sync::SyncStateProvider,
66};
67use reth_network_peers::PeerId;
68use reth_network_types::ReputationChangeKind;
69use reth_primitives_traits::{InMemorySize, SignedTransaction};
70use reth_tokio_util::EventStream;
71use reth_transaction_pool::{
72 error::{PoolError, PoolResult},
73 AddedTransactionOutcome, GetPooledTransactionLimit, PoolTransaction, PropagateKind,
74 PropagatedTransactions, TransactionPool, ValidPoolTransaction,
75};
76use std::{
77 pin::Pin,
78 sync::{
79 atomic::{AtomicUsize, Ordering},
80 Arc,
81 },
82 task::{Context, Poll},
83 time::{Duration, Instant},
84};
85use tokio::sync::{mpsc, oneshot, oneshot::error::RecvError};
86use tokio_stream::wrappers::UnboundedReceiverStream;
87use tracing::{debug, trace};
88
89pub type PoolImportFuture =
93 Pin<Box<dyn Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send + 'static>>;
94
95#[derive(Debug, Clone)]
103pub struct TransactionsHandle<N: NetworkPrimitives = EthNetworkPrimitives> {
104 manager_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
106}
107
108impl<N: NetworkPrimitives> TransactionsHandle<N> {
109 fn send(&self, cmd: TransactionsCommand<N>) {
110 let _ = self.manager_tx.send(cmd);
111 }
112
113 async fn peer_handle(
115 &self,
116 peer_id: PeerId,
117 ) -> Result<Option<PeerRequestSender<PeerRequest<N>>>, RecvError> {
118 let (tx, rx) = oneshot::channel();
119 self.send(TransactionsCommand::GetPeerSender { peer_id, peer_request_sender: tx });
120 rx.await
121 }
122
123 pub fn propagate(&self, hash: TxHash) {
125 self.send(TransactionsCommand::PropagateHash(hash))
126 }
127
128 pub fn propagate_hash_to(&self, hash: TxHash, peer: PeerId) {
132 self.propagate_hashes_to(Some(hash), peer)
133 }
134
135 pub fn propagate_hashes_to(&self, hash: impl IntoIterator<Item = TxHash>, peer: PeerId) {
139 let hashes = hash.into_iter().collect::<Vec<_>>();
140 if hashes.is_empty() {
141 return
142 }
143 self.send(TransactionsCommand::PropagateHashesTo(hashes, peer))
144 }
145
146 pub async fn get_active_peers(&self) -> Result<HashSet<PeerId>, RecvError> {
148 let (tx, rx) = oneshot::channel();
149 self.send(TransactionsCommand::GetActivePeers(tx));
150 rx.await
151 }
152
153 pub fn propagate_transactions_to(&self, transactions: Vec<TxHash>, peer: PeerId) {
157 if transactions.is_empty() {
158 return
159 }
160 self.send(TransactionsCommand::PropagateTransactionsTo(transactions, peer))
161 }
162
163 pub fn propagate_transactions(&self, transactions: Vec<TxHash>) {
168 if transactions.is_empty() {
169 return
170 }
171 self.send(TransactionsCommand::PropagateTransactions(transactions))
172 }
173
174 pub fn broadcast_transactions(
179 &self,
180 transactions: impl IntoIterator<Item = N::BroadcastedTransaction>,
181 ) {
182 let transactions =
183 transactions.into_iter().map(PropagateTransaction::new).collect::<Vec<_>>();
184 if transactions.is_empty() {
185 return
186 }
187 self.send(TransactionsCommand::BroadcastTransactions(transactions))
188 }
189
190 pub async fn get_transaction_hashes(
192 &self,
193 peers: Vec<PeerId>,
194 ) -> Result<HashMap<PeerId, B256Set>, RecvError> {
195 if peers.is_empty() {
196 return Ok(Default::default())
197 }
198 let (tx, rx) = oneshot::channel();
199 self.send(TransactionsCommand::GetTransactionHashes { peers, tx });
200 rx.await
201 }
202
203 pub async fn get_peer_transaction_hashes(&self, peer: PeerId) -> Result<B256Set, RecvError> {
205 let res = self.get_transaction_hashes(vec![peer]).await?;
206 Ok(res.into_values().next().unwrap_or_default())
207 }
208
209 pub async fn get_pooled_transactions_from(
215 &self,
216 peer_id: PeerId,
217 hashes: Vec<B256>,
218 ) -> Result<Option<Vec<N::PooledTransaction>>, RequestError> {
219 let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
220
221 let (tx, rx) = oneshot::channel();
222 let request = PeerRequest::GetPooledTransactions { request: hashes.into(), response: tx };
223 peer.try_send(request).ok();
224
225 rx.await?.map(|res| Some(res.0))
226 }
227}
228
229#[derive(Debug)]
284#[must_use = "Manager does nothing unless polled."]
285pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
286 pool: Pool,
288 network: NetworkHandle<N>,
290 network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
294 transaction_fetcher: TransactionFetcher<N>,
296 transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
301 pool_imports: FuturesUnordered<PoolImportFuture>,
313 pending_pool_imports_info: PendingPoolImportsInfo,
315 bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
317 peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
319 command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
323 command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
328 pending_transactions: mpsc::Receiver<TxHash>,
337 transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
339 config: TransactionsManagerConfig,
341 policies: NetworkPolicies<N>,
343 metrics: TransactionsManagerMetrics,
345 announced_tx_types_metrics: AnnouncedTxTypesMetrics,
347}
348
349impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
350 pub fn new(
354 network: NetworkHandle<N>,
355 pool: Pool,
356 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
357 transactions_manager_config: TransactionsManagerConfig,
358 ) -> Self {
359 Self::with_policy(
360 network,
361 pool,
362 from_network,
363 transactions_manager_config,
364 NetworkPolicies::new(
365 TransactionPropagationKind::default(),
366 StrictEthAnnouncementFilter::default(),
367 ),
368 )
369 }
370}
371
372impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
373 pub fn with_policy(
377 network: NetworkHandle<N>,
378 pool: Pool,
379 from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
380 transactions_manager_config: TransactionsManagerConfig,
381 policies: NetworkPolicies<N>,
382 ) -> Self {
383 let network_events = network.event_listener();
384
385 let (command_tx, command_rx) = mpsc::unbounded_channel();
386
387 let transaction_fetcher = TransactionFetcher::with_transaction_fetcher_config(
388 &transactions_manager_config.transaction_fetcher_config,
389 );
390
391 let pending = pool.pending_transactions_listener();
394 let pending_pool_imports_info =
395 PendingPoolImportsInfo::new(transactions_manager_config.max_pending_pool_imports);
396 let metrics = TransactionsManagerMetrics::default();
397 metrics
398 .capacity_pending_pool_imports
399 .increment(pending_pool_imports_info.max_pending_pool_imports as u64);
400
401 Self {
402 pool,
403 network,
404 network_events,
405 transaction_fetcher,
406 transactions_by_peers: Default::default(),
407 pool_imports: Default::default(),
408 pending_pool_imports_info,
409 bad_imports: LruCache::with_hasher(DEFAULT_MAX_COUNT_BAD_IMPORTS, Default::default()),
410 peers: Default::default(),
411 command_tx,
412 command_rx: UnboundedReceiverStream::new(command_rx),
413 pending_transactions: pending,
414 transaction_events: from_network,
415 config: transactions_manager_config,
416 policies,
417 metrics,
418 announced_tx_types_metrics: AnnouncedTxTypesMetrics::default(),
419 }
420 }
421
422 pub fn handle(&self) -> TransactionsHandle<N> {
424 TransactionsHandle { manager_tx: self.command_tx.clone() }
425 }
426
427 fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
430 self.has_capacity_for_pending_pool_imports() &&
431 self.transaction_fetcher.has_capacity_for_fetching_pending_hashes()
432 }
433
434 fn has_capacity_for_pending_pool_imports(&self) -> bool {
436 self.remaining_pool_import_capacity() > 0
437 }
438
439 fn remaining_pool_import_capacity(&self) -> usize {
441 self.pending_pool_imports_info.max_pending_pool_imports.saturating_sub(
442 self.pending_pool_imports_info.pending_pool_imports.load(Ordering::Relaxed),
443 )
444 }
445
446 fn report_peer_bad_transactions(&self, peer_id: PeerId) {
447 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
448 self.metrics.reported_bad_transactions.increment(1);
449 }
450
451 fn report_peer(&self, peer_id: PeerId, kind: ReputationChangeKind) {
452 trace!(target: "net::tx", ?peer_id, ?kind, "reporting reputation change");
453 self.network.reputation_change(peer_id, kind);
454 }
455
456 fn report_already_seen(&self, peer_id: PeerId) {
457 trace!(target: "net::tx", ?peer_id, "Penalizing peer for already seen transaction");
458 self.network.reputation_change(peer_id, ReputationChangeKind::AlreadySeenTransaction);
459 }
460
461 fn on_peer_session_closed(&mut self, peer_id: &PeerId) {
463 if let Some(mut peer) = self.peers.remove(peer_id) {
464 self.policies.propagation_policy_mut().on_session_closed(&mut peer);
465 }
466 self.transaction_fetcher.remove_peer(peer_id);
467 }
468
469 fn on_good_import(&mut self, hash: TxHash) {
471 self.transactions_by_peers.remove(&hash);
472 }
473
474 fn on_bad_import(&mut self, err: PoolError) {
504 let peers = self.transactions_by_peers.remove(&err.hash);
505
506 if err.is_bad_blob_sidecar() {
507 if let Some(peers) = peers {
511 for peer_id in peers {
512 self.report_peer_bad_transactions(peer_id);
513 }
514 }
515 return
516 }
517
518 if !err.is_bad_transaction() || self.network.is_syncing() {
520 return
521 }
522 if let Some(peers) = peers {
525 for peer_id in peers {
526 self.report_peer_bad_transactions(peer_id);
527 }
528 }
529 self.metrics.bad_imports.increment(1);
530 self.bad_imports.insert(err.hash);
531 }
532
533 fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
537 let info = &self.pending_pool_imports_info;
539 let max_pending_pool_imports = info.max_pending_pool_imports;
540 let has_capacity_wrt_pending_pool_imports =
541 |divisor| info.has_capacity(max_pending_pool_imports / divisor);
542
543 self.transaction_fetcher
544 .on_fetch_pending_hashes(&self.peers, has_capacity_wrt_pending_pool_imports)
545 }
546
547 fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
548 let kind = match req_err {
549 RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
550 RequestError::Timeout => ReputationChangeKind::Timeout,
551 RequestError::ChannelClosed | RequestError::ConnectionDropped => {
552 return
554 }
555 RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
556 };
557 self.report_peer(peer_id, kind);
558 }
559
560 #[inline]
561 fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
562 let metrics = &self.metrics;
563
564 let TxManagerPollDurations {
565 acc_network_events,
566 acc_pending_imports,
567 acc_tx_events,
568 acc_imported_txns,
569 acc_fetch_events,
570 acc_pending_fetch,
571 acc_cmds,
572 } = poll_durations;
573
574 metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
576 metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
578 metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
579 metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
580 metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
581 metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
582 metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
583 metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
584 }
585}
586
587impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
588 fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
590 for res in batch_results {
591 match res {
592 Ok(AddedTransactionOutcome { hash, .. }) => {
593 self.on_good_import(hash);
594 }
595 Err(err) => {
596 self.on_bad_import(err);
597 }
598 }
599 }
600 }
601
602 fn on_new_pooled_transaction_hashes(
604 &mut self,
605 peer_id: PeerId,
606 msg: NewPooledTransactionHashes,
607 ) {
608 if self.network.is_initially_syncing() {
610 return
611 }
612 if self.network.tx_gossip_disabled() {
613 return
614 }
615
616 let Some(peer) = self.peers.get_mut(&peer_id) else {
618 trace!(
619 peer_id = format!("{peer_id:#}"),
620 ?msg,
621 "discarding announcement from inactive peer"
622 );
623
624 return
625 };
626 let client = peer.client_version.clone();
627
628 let mut count_txns_already_seen_by_peer = 0;
630 for tx in msg.iter_hashes().copied() {
631 if !peer.seen_transactions.insert(tx) {
632 count_txns_already_seen_by_peer += 1;
633 }
634 }
635 if count_txns_already_seen_by_peer > 0 {
636 self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
641 self.metrics
642 .occurrences_hash_already_seen_by_peer
643 .increment(count_txns_already_seen_by_peer);
644
645 trace!(target: "net::tx",
646 %count_txns_already_seen_by_peer,
647 peer_id=format!("{peer_id:#}"),
648 ?client,
649 "Peer sent hashes that have already been marked as seen by peer"
650 );
651
652 self.report_already_seen(peer_id);
653 }
654
655 if msg.is_empty() {
657 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
658 return;
659 }
660
661 let original_len = msg.len();
662 let mut partially_valid_msg = msg.dedup();
663
664 if partially_valid_msg.len() != original_len {
665 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
666 }
667
668 partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
670
671 let mut should_report_peer = false;
678 let mut tx_types_counter = TxTypesCounter::default();
679
680 let has_eth68_metadata = partially_valid_msg
681 .msg_version()
682 .expect("partially valid announcement should have a version")
683 .has_eth68_metadata();
684
685 partially_valid_msg.retain(|tx_hash, metadata_ref_mut| {
686 let (ty_byte, size_val) = match *metadata_ref_mut {
687 Some((ty, size)) => {
688 if !has_eth68_metadata {
689 should_report_peer = true;
690 }
691 (ty, size)
692 }
693 None => {
694 if has_eth68_metadata {
695 should_report_peer = true;
696 return false;
697 }
698 (0u8, 0)
699 }
700 };
701
702 if has_eth68_metadata && let Some((actual_ty_byte, _)) = *metadata_ref_mut {
703 match TxType::try_from(actual_ty_byte) {
704 Ok(parsed_tx_type) => tx_types_counter.increase_by_tx_type(parsed_tx_type),
705 Err(_) => tx_types_counter.increase_other(),
706 }
707 }
708
709 let decision = self
710 .policies
711 .announcement_filter()
712 .decide_on_announcement(ty_byte, tx_hash, size_val);
713
714 match decision {
715 AnnouncementAcceptance::Accept => true,
716 AnnouncementAcceptance::Ignore => false,
717 AnnouncementAcceptance::Reject { penalize_peer } => {
718 if penalize_peer {
719 should_report_peer = true;
720 }
721 false
722 }
723 }
724 });
725
726 if has_eth68_metadata {
727 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
728 }
729
730 if should_report_peer {
731 self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
732 }
733
734 let hashes_count_pre_pool_filter = partially_valid_msg.len();
742 self.pool.retain_unknown(&mut partially_valid_msg);
743 if hashes_count_pre_pool_filter > partially_valid_msg.len() {
744 let already_known_hashes_count =
745 hashes_count_pre_pool_filter - partially_valid_msg.len();
746 self.metrics
747 .occurrences_hashes_already_in_pool
748 .increment(already_known_hashes_count as u64);
749 }
750
751 if partially_valid_msg.is_empty() {
752 return
754 }
755
756 let mut valid_announcement_data =
757 ValidAnnouncementData::from_partially_valid_data(partially_valid_msg);
758
759 if valid_announcement_data.is_empty() {
760 return
762 }
763
764 let bad_imports = &self.bad_imports;
771 self.transaction_fetcher.filter_unseen_and_pending_hashes(
772 &mut valid_announcement_data,
773 |hash| bad_imports.contains(hash),
774 &peer_id,
775 &client,
776 );
777
778 if valid_announcement_data.is_empty() {
779 return
781 }
782
783 trace!(target: "net::tx::propagation",
784 peer_id=format!("{peer_id:#}"),
785 hashes_len=valid_announcement_data.len(),
786 hashes=?valid_announcement_data.keys(),
787 msg_version=%valid_announcement_data.msg_version(),
788 client_version=%client,
789 "received previously unseen and pending hashes in announcement from peer"
790 );
791
792 if !self.transaction_fetcher.is_idle(&peer_id) {
795 let msg_version = valid_announcement_data.msg_version();
797 let (hashes, _version) = valid_announcement_data.into_request_hashes();
798
799 trace!(target: "net::tx",
800 peer_id=format!("{peer_id:#}"),
801 hashes=?*hashes,
802 %msg_version,
803 %client,
804 "buffering hashes announced by busy peer"
805 );
806
807 self.transaction_fetcher.buffer_hashes(hashes, Some(peer_id));
808
809 return
810 }
811
812 let mut hashes_to_request =
813 RequestTxHashes::with_capacity(valid_announcement_data.len() / 4);
814 let surplus_hashes =
815 self.transaction_fetcher.pack_request(&mut hashes_to_request, valid_announcement_data);
816
817 if !surplus_hashes.is_empty() {
818 trace!(target: "net::tx",
819 peer_id=format!("{peer_id:#}"),
820 surplus_hashes=?*surplus_hashes,
821 %client,
822 "some hashes in announcement from peer didn't fit in `GetPooledTransactions` request, buffering surplus hashes"
823 );
824
825 self.transaction_fetcher.buffer_hashes(surplus_hashes, Some(peer_id));
826 }
827
828 trace!(target: "net::tx",
829 peer_id=format!("{peer_id:#}"),
830 hashes=?*hashes_to_request,
831 %client,
832 "sending hashes in `GetPooledTransactions` request to peer's session"
833 );
834
835 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
839 if let Some(failed_to_request_hashes) =
840 self.transaction_fetcher.request_transactions_from_peer(hashes_to_request, peer)
841 {
842 let conn_eth_version = peer.version;
843
844 trace!(target: "net::tx",
845 peer_id=format!("{peer_id:#}"),
846 failed_to_request_hashes=?*failed_to_request_hashes,
847 %conn_eth_version,
848 %client,
849 "sending `GetPooledTransactions` request to peer's session failed, buffering hashes"
850 );
851 self.transaction_fetcher.buffer_hashes(failed_to_request_hashes, Some(peer_id));
852 }
853 }
854}
855
856impl<Pool, N> TransactionsManager<Pool, N>
857where
858 Pool: TransactionPool + Unpin + 'static,
859 N: NetworkPrimitives<
860 BroadcastedTransaction: SignedTransaction,
861 PooledTransaction: SignedTransaction,
862 > + Unpin,
863 Pool::Transaction:
864 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
865{
866 fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
878 if self.network.tx_gossip_disabled() {
882 return
883 }
884
885 trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
886
887 self.propagate_all(hashes);
888 }
889
890 fn propagate_full_transactions_to_peer(
894 &mut self,
895 txs: Vec<TxHash>,
896 peer_id: PeerId,
897 propagation_mode: PropagationMode,
898 ) -> Option<PropagatedTransactions> {
899 let peer = self.peers.get_mut(&peer_id)?;
900 trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
901 let mut propagated = PropagatedTransactions::default();
902
903 let mut full_transactions = FullTransactionsBuilder::new(peer.version);
905
906 let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
907
908 if propagation_mode.is_forced() {
909 full_transactions.extend(to_propagate);
911 } else {
912 for tx in to_propagate {
915 if !peer.seen_transactions.contains(tx.tx_hash()) {
916 full_transactions.push(&tx);
918 }
919 }
920 }
921
922 if full_transactions.is_empty() {
923 return None
925 }
926
927 let PropagateTransactions { pooled, full } = full_transactions.build();
928
929 if let Some(new_pooled_hashes) = pooled {
931 for hash in new_pooled_hashes.iter_hashes().copied() {
932 propagated.record(hash, PropagateKind::Hash(peer_id));
933 peer.seen_transactions.insert(hash);
935 }
936
937 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
939 }
940
941 if let Some(new_full_transactions) = full {
943 for hash in new_full_transactions.iter_hashes() {
944 propagated.record(*hash, PropagateKind::Full(peer_id));
945 peer.seen_transactions.insert(*hash);
947 }
948
949 self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
951 }
952
953 self.metrics.propagated_transactions.increment(propagated.len() as u64);
955
956 Some(propagated)
957 }
958
959 fn propagate_hashes_to(
963 &mut self,
964 hashes: Vec<TxHash>,
965 peer_id: PeerId,
966 propagation_mode: PropagationMode,
967 ) {
968 trace!(target: "net::tx", "Start propagating transactions as hashes");
969
970 let propagated = {
973 let Some(peer) = self.peers.get_mut(&peer_id) else {
974 return
976 };
977
978 let to_propagate =
979 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
980
981 let mut propagated = PropagatedTransactions::default();
982
983 let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
985
986 if propagation_mode.is_forced() {
987 hashes.extend(to_propagate)
988 } else {
989 for tx in to_propagate {
990 if !peer.seen_transactions.contains(tx.tx_hash()) {
991 hashes.push(&tx);
993 }
994 }
995 }
996
997 let new_pooled_hashes = hashes.build();
998
999 if new_pooled_hashes.is_empty() {
1000 return
1002 }
1003
1004 for hash in new_pooled_hashes.iter_hashes().copied() {
1005 propagated.record(hash, PropagateKind::Hash(peer_id));
1006 peer.seen_transactions.insert(hash);
1007 }
1008
1009 trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
1010
1011 self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1013
1014 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1016
1017 propagated
1018 };
1019
1020 self.pool.on_propagated(propagated);
1022 }
1023
1024 fn propagate_transactions(
1031 &mut self,
1032 to_propagate: Vec<PropagateTransaction>,
1033 propagation_mode: PropagationMode,
1034 ) -> PropagatedTransactions {
1035 let mut propagated = PropagatedTransactions::default();
1036 if self.network.tx_gossip_disabled() {
1037 return propagated
1038 }
1039
1040 let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1042
1043 let mut num_full_peers = 0;
1045 for (peer_id, peer) in &mut self.peers {
1046 if !self.policies.propagation_policy().can_propagate(peer) {
1047 continue
1049 }
1050
1051 let mut builder = if num_full_peers < max_num_full {
1053 num_full_peers += 1;
1054 PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1055 } else {
1056 PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1057 };
1058
1059 if propagation_mode.is_forced() {
1062 for tx in &to_propagate {
1063 peer.seen_transactions.insert(*tx.tx_hash());
1064 builder.push(tx);
1065 }
1066 } else {
1067 for tx in &to_propagate {
1071 if peer.seen_transactions.insert(*tx.tx_hash()) {
1073 builder.push(tx);
1074 }
1075 }
1076 }
1077
1078 if builder.is_empty() {
1079 trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1080 continue
1081 }
1082
1083 let PropagateTransactions { pooled, full } = builder.build();
1084
1085 if let Some(mut new_pooled_hashes) = pooled {
1087 if new_pooled_hashes.len() >
1091 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1092 {
1093 for hash in new_pooled_hashes
1096 .iter_hashes()
1097 .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1098 {
1099 peer.seen_transactions.remove(hash);
1100 }
1101 new_pooled_hashes.truncate(
1102 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1103 );
1104 }
1105
1106 for hash in new_pooled_hashes.iter_hashes().copied() {
1107 propagated.record(hash, PropagateKind::Hash(*peer_id));
1108 }
1109
1110 trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1111
1112 self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1114 }
1115
1116 if let Some(new_full_transactions) = full {
1118 for hash in new_full_transactions.iter_hashes() {
1119 propagated.record(*hash, PropagateKind::Full(*peer_id));
1120 }
1121
1122 trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1123
1124 self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1126 }
1127 }
1128
1129 self.metrics.propagated_transactions.increment(propagated.len() as u64);
1131
1132 propagated
1133 }
1134
1135 fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1140 if self.peers.is_empty() {
1141 return
1143 }
1144 let propagated = self.propagate_transactions(
1145 self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1146 PropagationMode::Basic,
1147 );
1148
1149 self.pool.on_propagated(propagated);
1151 }
1152
1153 fn on_get_pooled_transactions(
1155 &mut self,
1156 peer_id: PeerId,
1157 request: GetPooledTransactions,
1158 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1159 ) {
1160 if self.network.tx_gossip_disabled() {
1162 let _ = response.send(Ok(PooledTransactions::default()));
1163 return
1164 }
1165 if let Some(peer) = self.peers.get_mut(&peer_id) {
1166 let transactions = self.pool.get_pooled_transaction_elements(
1167 request.0,
1168 GetPooledTransactionLimit::ResponseSizeSoftLimit(
1169 self.transaction_fetcher.info.soft_limit_byte_size_pooled_transactions_response,
1170 ),
1171 );
1172 trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1173
1174 peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1177
1178 let resp = PooledTransactions(transactions);
1179 let _ = response.send(Ok(resp));
1180 }
1181 }
1182
1183 fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1185 match cmd {
1186 TransactionsCommand::PropagateHash(hash) => {
1187 self.on_new_pending_transactions(vec![hash])
1188 }
1189 TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1190 self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1191 }
1192 TransactionsCommand::GetActivePeers(tx) => {
1193 let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1194 tx.send(peers).ok();
1195 }
1196 TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1197 if let Some(propagated) =
1198 self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1199 {
1200 self.pool.on_propagated(propagated);
1201 }
1202 }
1203 TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1204 TransactionsCommand::BroadcastTransactions(txs) => {
1205 let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1206 self.pool.on_propagated(propagated);
1207 }
1208 TransactionsCommand::GetTransactionHashes { peers, tx } => {
1209 let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1210 for peer_id in peers {
1211 let hashes = self
1212 .peers
1213 .get(&peer_id)
1214 .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1215 .unwrap_or_default();
1216 res.insert(peer_id, hashes);
1217 }
1218 tx.send(res).ok();
1219 }
1220 TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1221 let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1222 peer_request_sender.send(sender).ok();
1223 }
1224 }
1225 }
1226
1227 fn handle_peer_session(
1231 &mut self,
1232 info: SessionInfo,
1233 messages: PeerRequestSender<PeerRequest<N>>,
1234 ) {
1235 let SessionInfo { peer_id, client_version, version, .. } = info;
1236
1237 let peer = PeerMetadata::<N>::new(
1239 messages,
1240 version,
1241 client_version,
1242 self.config.max_transactions_seen_by_peer_history,
1243 info.peer_kind,
1244 );
1245 let peer = match self.peers.entry(peer_id) {
1246 Entry::Occupied(mut entry) => {
1247 entry.insert(peer);
1248 entry.into_mut()
1249 }
1250 Entry::Vacant(entry) => entry.insert(peer),
1251 };
1252
1253 self.policies.propagation_policy_mut().on_session_established(peer);
1254
1255 if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1259 trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1260 return
1261 }
1262
1263 let pooled_txs = self.pool.pooled_transactions_max(
1265 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1266 );
1267 if pooled_txs.is_empty() {
1268 trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1269 return;
1270 }
1271
1272 let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1274 for pooled_tx in pooled_txs {
1275 peer.seen_transactions.insert(*pooled_tx.hash());
1276 msg_builder.push_pooled(pooled_tx);
1277 }
1278
1279 debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1280 let msg = msg_builder.build();
1281 self.network.send_transactions_hashes(peer_id, msg);
1282 }
1283
1284 fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1286 match event_result {
1287 NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1288 self.on_peer_session_closed(&peer_id);
1289 }
1290 NetworkEvent::ActivePeerSession { info, messages } => {
1291 self.handle_peer_session(info, messages);
1293 }
1294 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1295 let peer_id = info.peer_id;
1296 let messages = match self.peers.get(&peer_id) {
1298 Some(p) => p.request_tx.clone(),
1299 None => {
1300 debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1301 return;
1302 }
1303 };
1304 self.handle_peer_session(info, messages);
1305 }
1306 _ => {}
1307 }
1308 }
1309
1310 fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1312 if self.config.ingress_policy.allows_all() {
1313 return true;
1314 }
1315 let Some(peer) = self.peers.get(peer_id) else {
1316 return false;
1317 };
1318 self.config.ingress_policy.allows(peer.peer_kind())
1319 }
1320
1321 fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1323 match event {
1324 NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1325 if !self.accepts_incoming_from(&peer_id) {
1326 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1327 return;
1328 }
1329
1330 let has_blob_txs = msg.has_eip4844();
1334
1335 let non_blob_txs = msg
1336 .into_iter()
1337 .map(N::PooledTransaction::try_from)
1338 .filter_map(Result::ok)
1339 .collect();
1340
1341 self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1342
1343 if has_blob_txs {
1344 debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1345 self.report_peer_bad_transactions(peer_id);
1346 }
1347 }
1348 NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1349 if !self.accepts_incoming_from(&peer_id) {
1350 trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1351 return;
1352 }
1353 self.on_new_pooled_transaction_hashes(peer_id, msg)
1354 }
1355 NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1356 self.on_get_pooled_transactions(peer_id, request, response)
1357 }
1358 NetworkTransactionEvent::GetTransactionsHandle(response) => {
1359 let _ = response.send(Some(self.handle()));
1360 }
1361 }
1362 }
1363
1364 fn import_transactions(
1366 &mut self,
1367 peer_id: PeerId,
1368 transactions: PooledTransactions<N::PooledTransaction>,
1369 source: TransactionSource,
1370 ) {
1371 if self.network.is_initially_syncing() {
1373 return
1374 }
1375 if self.network.tx_gossip_disabled() {
1376 return
1377 }
1378
1379 if !self.has_capacity_for_pending_pool_imports() {
1381 return
1382 }
1383
1384 let mut transactions = transactions.0;
1385
1386 let capacity = self.remaining_pool_import_capacity();
1390 if transactions.len() > capacity {
1391 let skipped = transactions.len() - capacity;
1392 transactions.truncate(capacity);
1393 self.metrics
1394 .skipped_transactions_pending_pool_imports_at_capacity
1395 .increment(skipped as u64);
1396 trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
1397 }
1398
1399 let Some(peer) = self.peers.get_mut(&peer_id) else { return };
1400 let client_version = peer.client_version.clone();
1401
1402 let start = Instant::now();
1403
1404 self.transaction_fetcher
1406 .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1407
1408 let mut num_already_seen_by_peer = 0;
1413 for tx in &transactions {
1414 if source.is_broadcast() && !peer.seen_transactions.insert(*tx.tx_hash()) {
1415 num_already_seen_by_peer += 1;
1416 }
1417 }
1418
1419 let mut has_bad_transactions = false;
1421
1422 transactions.retain(|tx| {
1425 if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1426 let peers = entry.get_mut();
1427 if !peers.contains(&peer_id) {
1428 peers.push(peer_id);
1429 }
1430 return false
1431 }
1432 if self.bad_imports.contains(tx.tx_hash()) {
1433 trace!(target: "net::tx",
1434 peer_id=format!("{peer_id:#}"),
1435 hash=%tx.tx_hash(),
1436 %client_version,
1437 "received a known bad transaction from peer"
1438 );
1439 has_bad_transactions = true;
1440 return false;
1441 }
1442 true
1443 });
1444
1445 let txns_count_pre_pool_filter = transactions.len();
1447 self.pool.retain_unknown(&mut transactions);
1448 if txns_count_pre_pool_filter > transactions.len() {
1449 let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1450 self.metrics
1451 .occurrences_transactions_already_in_pool
1452 .increment(already_known_txns_count as u64);
1453 }
1454
1455 let txs_len = transactions.len();
1456
1457 let recover = |tx| match Pool::Transaction::try_recover(tx) {
1458 Ok(tx) => Some(tx),
1459 Err(badtx) => {
1460 trace!(target: "net::tx",
1461 peer_id=format!("{peer_id:#}"),
1462 hash=%badtx.tx_hash(),
1463 client_version=%client_version,
1464 "failed ecrecovery for transaction"
1465 );
1466 None
1467 }
1468 };
1469
1470 let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1471
1472 has_bad_transactions |= new_txs.len() != txs_len;
1473
1474 for tx in &new_txs {
1476 self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1477 }
1478
1479 if !new_txs.is_empty() {
1482 let pool = self.pool.clone();
1483 let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1485 metric_pending_pool_imports.increment(new_txs.len() as f64);
1486
1487 self.pending_pool_imports_info
1489 .pending_pool_imports
1490 .fetch_add(new_txs.len(), Ordering::Relaxed);
1491 let tx_manager_info_pending_pool_imports =
1492 self.pending_pool_imports_info.pending_pool_imports.clone();
1493
1494 trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1495 let import = Box::pin(async move {
1496 let added = new_txs.len();
1497 let res = pool.add_external_transactions(new_txs).await;
1498
1499 metric_pending_pool_imports.decrement(added as f64);
1501 tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1503
1504 res
1505 });
1506
1507 self.pool_imports.push(import);
1508 }
1509
1510 if num_already_seen_by_peer > 0 {
1511 self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1512 self.metrics
1513 .occurrences_of_transaction_already_seen_by_peer
1514 .increment(num_already_seen_by_peer);
1515 trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1516 }
1517
1518 if has_bad_transactions {
1519 self.report_peer_bad_transactions(peer_id)
1521 }
1522
1523 if num_already_seen_by_peer > 0 {
1524 self.report_already_seen(peer_id);
1525 }
1526
1527 self.metrics.pool_import_prepare_duration.record(start.elapsed());
1528 }
1529
1530 fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1532 match fetch_event {
1533 FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => {
1534 self.import_transactions(peer_id, transactions, TransactionSource::Response);
1535 if report_peer {
1536 self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1537 }
1538 }
1539 FetchEvent::FetchError { peer_id, error } => {
1540 trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1541 self.on_request_error(peer_id, error);
1542 }
1543 FetchEvent::EmptyResponse { peer_id } => {
1544 trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1545 }
1546 }
1547 }
1548}
1549
1550impl<
1558 Pool: TransactionPool + Unpin + 'static,
1559 N: NetworkPrimitives<
1560 BroadcastedTransaction: SignedTransaction,
1561 PooledTransaction: SignedTransaction,
1562 > + Unpin,
1563 > Future for TransactionsManager<Pool, N>
1564where
1565 Pool::Transaction:
1566 PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1567{
1568 type Output = ();
1569
1570 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1571 let start = Instant::now();
1572 let mut poll_durations = TxManagerPollDurations::default();
1573
1574 let this = self.get_mut();
1575
1576 let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1582 poll_durations.acc_network_events,
1583 "net::tx",
1584 "Network events stream",
1585 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1586 this.network_events.poll_next_unpin(cx),
1587 |event| this.on_network_event(event)
1588 );
1589
1590 let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1605 poll_durations.acc_tx_events,
1606 "net::tx",
1607 "Network transaction events stream",
1608 DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1609 this.transaction_events.poll_next_unpin(cx),
1610 |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1611 );
1612
1613 let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1624 poll_durations.acc_fetch_events,
1625 "net::tx",
1626 "Transaction fetch events stream",
1627 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1628 this.transaction_fetcher.poll_next_unpin(cx),
1629 |event| this.on_fetch_event(event),
1630 );
1631
1632 let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1647 poll_durations.acc_pending_imports,
1648 "net::tx",
1649 "Batched pool imports stream",
1650 DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1651 this.pool_imports.poll_next_unpin(cx),
1652 |batch_results| this.on_batch_import_result(batch_results)
1653 );
1654
1655 let mut new_txs = Vec::new();
1668 let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1669 cx,
1670 &mut new_txs,
1671 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1672 ) {
1673 Poll::Ready(count) => {
1674 if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1675 true
1678 } else {
1679 let limit =
1683 SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1684 new_txs.len();
1685 this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1686 }
1687 }
1688 Poll::Pending => false,
1689 };
1690 if !new_txs.is_empty() {
1691 this.on_new_pending_transactions(new_txs);
1692 }
1693
1694 duration_metered_exec!(
1699 {
1700 if this.has_capacity_for_fetching_pending_hashes() &&
1701 this.on_fetch_hashes_pending_fetch()
1702 {
1703 maybe_more_tx_fetch_events = true;
1704 }
1705 },
1706 poll_durations.acc_pending_fetch
1707 );
1708
1709 let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1711 poll_durations.acc_cmds,
1712 "net::tx",
1713 "Commands channel",
1714 DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1715 this.command_rx.poll_next_unpin(cx),
1716 |cmd| this.on_command(cmd)
1717 );
1718
1719 this.transaction_fetcher.update_metrics();
1720
1721 if maybe_more_network_events ||
1723 maybe_more_commands ||
1724 maybe_more_tx_events ||
1725 maybe_more_tx_fetch_events ||
1726 maybe_more_pool_imports ||
1727 maybe_more_pending_txns
1728 {
1729 cx.waker().wake_by_ref();
1731 return Poll::Pending
1732 }
1733
1734 this.update_poll_metrics(start, poll_durations);
1735
1736 Poll::Pending
1737 }
1738}
1739
1740#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1744enum PropagationMode {
1745 Basic,
1749 Forced,
1754}
1755
1756impl PropagationMode {
1757 const fn is_forced(self) -> bool {
1759 matches!(self, Self::Forced)
1760 }
1761}
1762
1763#[derive(Debug, Clone)]
1765struct PropagateTransaction {
1766 is_broadcastable_in_full: bool,
1767 propagation_size: usize,
1774 transaction: LazyEncodedTransaction,
1775}
1776
1777impl PropagateTransaction {
1778 fn new<T: SignedTransaction>(transaction: T) -> Self {
1784 let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1785 let propagation_size = transaction.encode_2718_len();
1786
1787 Self {
1788 is_broadcastable_in_full,
1789 propagation_size,
1790 transaction: LazyEncoded::new(transaction),
1791 }
1792 }
1793
1794 fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1800 let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1801 let propagation_size = tx.encoded_length();
1802 Self {
1803 is_broadcastable_in_full,
1804 propagation_size,
1805 transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1806 }
1807 }
1808
1809 fn tx_hash(&self) -> &TxHash {
1810 self.transaction.tx_hash()
1811 }
1812
1813 const fn propagation_size(&self) -> usize {
1815 self.propagation_size
1816 }
1817
1818 fn tx_type(&self) -> u8 {
1819 self.transaction.ty()
1820 }
1821
1822 const fn is_broadcastable_in_full(&self) -> bool {
1823 self.is_broadcastable_in_full
1824 }
1825
1826 fn shared(&self) -> LazyEncodedTransaction {
1827 self.transaction.clone()
1828 }
1829}
1830
1831#[derive(Debug)]
1833struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1834 transaction: Arc<ValidPoolTransaction<P>>,
1835}
1836
1837impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1838 const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1839 Self { transaction }
1840 }
1841
1842 fn encode_uncached(&self, out: &mut dyn BufMut) {
1843 (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1844 }
1845}
1846
1847impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1848 fn encode(&self, out: &mut dyn BufMut) {
1849 self.encode_uncached(out);
1850 }
1851
1852 fn length(&self) -> usize {
1853 (*self.transaction.transaction.consensus_ref().inner()).length()
1854 }
1855}
1856
1857impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1858 fn tx_hash(&self) -> &TxHash {
1859 self.transaction.hash()
1860 }
1861}
1862
1863impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1864 fn ty(&self) -> u8 {
1865 self.transaction.transaction.ty()
1866 }
1867}
1868
1869#[derive(Debug, Clone)]
1872enum PropagateTransactionsBuilder {
1873 Pooled(PooledTransactionsHashesBuilder),
1874 Full(FullTransactionsBuilder),
1875}
1876
1877impl PropagateTransactionsBuilder {
1878 fn pooled(version: EthVersion, capacity: usize) -> Self {
1881 Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1882 }
1883
1884 fn full(version: EthVersion, capacity: usize) -> Self {
1887 Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1888 }
1889
1890 fn is_empty(&self) -> bool {
1892 match self {
1893 Self::Pooled(builder) => builder.is_empty(),
1894 Self::Full(builder) => builder.is_empty(),
1895 }
1896 }
1897
1898 fn build(self) -> PropagateTransactions {
1900 match self {
1901 Self::Pooled(pooled) => {
1902 PropagateTransactions { pooled: Some(pooled.build()), full: None }
1903 }
1904 Self::Full(full) => full.build(),
1905 }
1906 }
1907}
1908
1909impl PropagateTransactionsBuilder {
1910 fn push(&mut self, transaction: &PropagateTransaction) {
1912 match self {
1913 Self::Pooled(builder) => builder.push(transaction),
1914 Self::Full(builder) => builder.push(transaction),
1915 }
1916 }
1917}
1918
1919struct PropagateTransactions {
1921 pooled: Option<NewPooledTransactionHashes>,
1923 full: Option<BroadcastPoolTransactions>,
1925}
1926
1927#[derive(Debug, Clone)]
1932struct FullTransactionsBuilder {
1933 total_size: usize,
1935 transactions: Vec<LazyEncodedTransaction>,
1937 pooled: PooledTransactionsHashesBuilder,
1939}
1940
1941impl FullTransactionsBuilder {
1942 fn new(version: EthVersion) -> Self {
1944 Self {
1945 total_size: 0,
1946 pooled: PooledTransactionsHashesBuilder::new(version),
1947 transactions: vec![],
1948 }
1949 }
1950
1951 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
1956 Self {
1957 total_size: 0,
1958 pooled: PooledTransactionsHashesBuilder::new(version),
1959 transactions: Vec::with_capacity(capacity),
1960 }
1961 }
1962
1963 fn is_empty(&self) -> bool {
1965 self.transactions.is_empty() && self.pooled.is_empty()
1966 }
1967
1968 fn build(self) -> PropagateTransactions {
1970 let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
1971 let full =
1972 (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
1973 PropagateTransactions { pooled, full }
1974 }
1975
1976 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
1978 for tx in txs {
1979 self.push(&tx)
1980 }
1981 }
1982
1983 fn push(&mut self, transaction: &PropagateTransaction) {
1993 if !transaction.is_broadcastable_in_full() {
2002 self.pooled.push(transaction);
2003 return
2004 }
2005
2006 let new_size = self.total_size + transaction.propagation_size();
2007 if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2008 self.total_size > 0
2009 {
2010 self.pooled.push(transaction);
2012 return
2013 }
2014
2015 self.total_size = new_size;
2016 self.transactions.push(transaction.shared());
2017 }
2018}
2019
2020#[derive(Debug, Clone)]
2023enum PooledTransactionsHashesBuilder {
2024 Eth66(NewPooledTransactionHashes66),
2025 Eth68(NewPooledTransactionHashes68),
2026 Eth72(NewPooledTransactionHashes72),
2027}
2028
2029impl PooledTransactionsHashesBuilder {
2032 fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2034 match self {
2035 Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2036 Self::Eth68(msg) => {
2037 msg.hashes.push(*pooled_tx.hash());
2038 msg.sizes.push(pooled_tx.encoded_length());
2039 msg.types.push(pooled_tx.transaction.ty());
2040 }
2041 Self::Eth72(msg) => {
2042 msg.hashes.push(*pooled_tx.hash());
2043 msg.sizes.push(pooled_tx.encoded_length());
2044 msg.types.push(pooled_tx.transaction.ty());
2045 }
2046 }
2047 }
2048
2049 fn is_empty(&self) -> bool {
2051 match self {
2052 Self::Eth66(hashes) => hashes.is_empty(),
2053 Self::Eth68(hashes) => hashes.is_empty(),
2054 Self::Eth72(hashes) => hashes.is_empty(),
2055 }
2056 }
2057
2058 fn len(&self) -> usize {
2060 match self {
2061 Self::Eth66(hashes) => hashes.len(),
2062 Self::Eth68(hashes) => hashes.len(),
2063 Self::Eth72(hashes) => hashes.len(),
2064 }
2065 }
2066
2067 fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2069 for tx in txs {
2070 self.push(&tx);
2071 }
2072 }
2073
2074 fn push(&mut self, tx: &PropagateTransaction) {
2075 match self {
2076 Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2077 Self::Eth68(msg) => {
2078 msg.hashes.push(*tx.tx_hash());
2079 msg.sizes.push(tx.propagation_size());
2080 msg.types.push(tx.tx_type());
2081 }
2082 Self::Eth72(msg) => {
2083 msg.hashes.push(*tx.tx_hash());
2084 msg.sizes.push(tx.propagation_size());
2085 msg.types.push(tx.tx_type());
2086 }
2087 }
2088 }
2089
2090 fn new(version: EthVersion) -> Self {
2092 match version {
2093 EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2094 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2095 Self::Eth68(Default::default())
2096 }
2097 EthVersion::Eth72 => Self::Eth72(Default::default()),
2098 }
2099 }
2100
2101 fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2104 match version {
2105 EthVersion::Eth66 | EthVersion::Eth67 => {
2106 Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2107 }
2108 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2109 Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2110 }
2111 EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2112 }
2113 }
2114
2115 fn build(self) -> NewPooledTransactionHashes {
2116 match self {
2117 Self::Eth66(mut msg) => {
2118 msg.shrink_to_fit();
2119 msg.into()
2120 }
2121 Self::Eth68(mut msg) => {
2122 msg.shrink_to_fit();
2123 msg.into()
2124 }
2125 Self::Eth72(mut msg) => {
2126 msg.shrink_to_fit();
2127 msg.into()
2128 }
2129 }
2130 }
2131}
2132
2133enum TransactionSource {
2135 Broadcast,
2137 Response,
2139}
2140
2141impl TransactionSource {
2144 const fn is_broadcast(&self) -> bool {
2146 matches!(self, Self::Broadcast)
2147 }
2148}
2149
2150#[derive(Debug)]
2152pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2153 seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2157 request_tx: PeerRequestSender<PeerRequest<N>>,
2159 version: EthVersion,
2161 client_version: Arc<str>,
2163 peer_kind: PeerKind,
2165}
2166
2167impl<N: NetworkPrimitives> PeerMetadata<N> {
2168 pub fn new(
2170 request_tx: PeerRequestSender<PeerRequest<N>>,
2171 version: EthVersion,
2172 client_version: Arc<str>,
2173 max_transactions_seen_by_peer: u32,
2174 peer_kind: PeerKind,
2175 ) -> Self {
2176 Self {
2177 seen_transactions: LruCache::with_hasher(
2178 max_transactions_seen_by_peer,
2179 Default::default(),
2180 ),
2181 request_tx,
2182 version,
2183 client_version,
2184 peer_kind,
2185 }
2186 }
2187
2188 pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2190 &self.request_tx
2191 }
2192
2193 pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2195 &mut self.seen_transactions
2196 }
2197
2198 pub const fn version(&self) -> EthVersion {
2200 self.version
2201 }
2202
2203 pub fn client_version(&self) -> &str {
2205 &self.client_version
2206 }
2207
2208 pub const fn peer_kind(&self) -> PeerKind {
2210 self.peer_kind
2211 }
2212}
2213
2214#[derive(Debug)]
2216enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2217 PropagateHash(B256),
2219 PropagateHashesTo(Vec<B256>, PeerId),
2221 GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2223 PropagateTransactionsTo(Vec<TxHash>, PeerId),
2225 PropagateTransactions(Vec<TxHash>),
2227 BroadcastTransactions(Vec<PropagateTransaction>),
2229 GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2231 GetPeerSender {
2233 peer_id: PeerId,
2234 peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2235 },
2236}
2237
2238#[derive(Debug)]
2240pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2241 IncomingTransactions {
2245 peer_id: PeerId,
2247 msg: Transactions<N::BroadcastedTransaction>,
2249 },
2250 IncomingPooledTransactionHashes {
2252 peer_id: PeerId,
2254 msg: NewPooledTransactionHashes,
2256 },
2257 GetPooledTransactions {
2259 peer_id: PeerId,
2261 request: GetPooledTransactions,
2263 response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2265 },
2266 GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2268}
2269
2270#[derive(Debug)]
2272pub struct PendingPoolImportsInfo {
2273 pending_pool_imports: Arc<AtomicUsize>,
2275 max_pending_pool_imports: usize,
2277}
2278
2279impl PendingPoolImportsInfo {
2280 pub fn new(max_pending_pool_imports: usize) -> Self {
2282 Self { pending_pool_imports: Arc::new(AtomicUsize::default()), max_pending_pool_imports }
2283 }
2284
2285 pub fn has_capacity(&self, max_pending_pool_imports: usize) -> bool {
2287 self.pending_pool_imports.load(Ordering::Relaxed) < max_pending_pool_imports
2288 }
2289}
2290
2291impl Default for PendingPoolImportsInfo {
2292 fn default() -> Self {
2293 Self::new(DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS)
2294 }
2295}
2296
2297#[derive(Debug, Default)]
2298struct TxManagerPollDurations {
2299 acc_network_events: Duration,
2300 acc_pending_imports: Duration,
2301 acc_tx_events: Duration,
2302 acc_imported_txns: Duration,
2303 acc_fetch_events: Duration,
2304 acc_pending_fetch: Duration,
2305 acc_cmds: Duration,
2306}
2307
2308impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2309 fn size(&self) -> usize {
2312 match self {
2313 Self::IncomingTransactions { peer_id, msg } => {
2314 core::mem::size_of_val(peer_id) +
2315 msg.0.iter().map(InMemorySize::size).sum::<usize>()
2316 }
2317 Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2318 core::mem::size_of_val(peer_id) + msg.size()
2319 }
2320 Self::GetPooledTransactions { peer_id, request, response } => {
2321 core::mem::size_of_val(peer_id) +
2322 request.0.len() * core::mem::size_of::<TxHash>() +
2323 core::mem::size_of_val(response)
2324 }
2325 Self::GetTransactionsHandle(_) => 0,
2326 }
2327 }
2328}
2329
2330#[cfg(test)]
2331mod tests {
2332 use super::*;
2333 use crate::{
2334 test_utils::{
2335 transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2336 Testnet,
2337 },
2338 transactions::config::RelaxedEthAnnouncementFilter,
2339 NetworkConfigBuilder, NetworkManager,
2340 };
2341 use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2342 use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2343 use alloy_primitives::{hex, Signature, TxKind, B256, U256};
2344 use alloy_rlp::Decodable;
2345 use futures::FutureExt;
2346 use reth_chainspec::MIN_TRANSACTION_GAS;
2347 use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2348 use reth_network_api::{NetworkInfo, PeerKind};
2349 use reth_network_p2p::{
2350 error::{RequestError, RequestResult},
2351 sync::{NetworkSyncUpdater, SyncState},
2352 };
2353 use reth_storage_api::noop::NoopProvider;
2354 use reth_tasks::Runtime;
2355 use reth_transaction_pool::{
2356 blobstore::InMemoryBlobStore,
2357 error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2358 identifier::SenderIdentifiers,
2359 test_utils::{
2360 testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2361 TransactionGenerator,
2362 },
2363 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2364 };
2365 use secp256k1::SecretKey;
2366 use std::{
2367 future::poll_fn,
2368 net::{IpAddr, Ipv4Addr, SocketAddr},
2369 str::FromStr,
2370 time::Instant,
2371 };
2372 use tracing::error;
2373
2374 type EthTestPool = Pool<
2375 OkValidator<EthPooledTransaction>,
2376 CoinbaseTipOrdering<EthPooledTransaction>,
2377 InMemoryBlobStore,
2378 >;
2379
2380 async fn new_eth_tx_manager() -> (
2381 TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2382 NetworkManager<EthNetworkPrimitives>,
2383 ) {
2384 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2385 let client = NoopProvider::default();
2386
2387 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2388 .listener_port(0)
2389 .disable_discovery()
2390 .build(client);
2391
2392 let pool = Pool::new(
2393 OkValidator::default(),
2394 CoinbaseTipOrdering::default(),
2395 InMemoryBlobStore::default(),
2396 Default::default(),
2397 );
2398
2399 let transactions_manager_config = config.transactions_manager_config.clone();
2400 let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2401 .await
2402 .unwrap()
2403 .into_builder()
2404 .transactions(pool.clone(), transactions_manager_config)
2405 .split_with_handle();
2406
2407 (transactions, network)
2408 }
2409
2410 fn valid_eth_pool_transaction(
2411 transaction: EthPooledTransaction,
2412 ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2413 let mut ids = SenderIdentifiers::default();
2414 let transaction_id =
2415 ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2416
2417 Arc::new(ValidPoolTransaction {
2418 propagate: false,
2419 transaction_id,
2420 transaction,
2421 timestamp: Instant::now(),
2422 origin: TransactionOrigin::External,
2423 authority_ids: None,
2424 })
2425 }
2426
2427 fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2428 tx_gen: &mut TransactionGenerator<R>,
2429 nonce: u64,
2430 ) -> EthPooledTransaction {
2431 EthPooledTransaction::try_from_consensus(
2432 tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2433 )
2434 .unwrap()
2435 }
2436
2437 #[tokio::test(flavor = "multi_thread")]
2438 async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2439 reth_tracing::init_test_tracing();
2440 let net = Testnet::create(3).await;
2441
2442 let mut handles = net.handles();
2443 let handle0 = handles.next().unwrap();
2444 let handle1 = handles.next().unwrap();
2445
2446 drop(handles);
2447 let handle = net.spawn();
2448
2449 let listener0 = handle0.event_listener();
2450 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2451 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2452
2453 let client = NoopProvider::default();
2454 let pool = testing_pool();
2455 let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2456 .disable_discovery()
2457 .listener_port(0)
2458 .build(client);
2459 let transactions_manager_config = config.transactions_manager_config.clone();
2460 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2461 .await
2462 .unwrap()
2463 .into_builder()
2464 .transactions(pool.clone(), transactions_manager_config)
2465 .split_with_handle();
2466
2467 tokio::task::spawn(network);
2468
2469 network_handle.update_sync_state(SyncState::Syncing);
2471 assert!(NetworkInfo::is_syncing(&network_handle));
2472 assert!(NetworkInfo::is_initially_syncing(&network_handle));
2473
2474 let mut established = listener0.take(2);
2476 while let Some(ev) = established.next().await {
2477 match ev {
2478 NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2479 transactions
2481 .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2482 }
2483 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2484 ev => {
2485 error!("unexpected event {ev:?}")
2486 }
2487 }
2488 }
2489 let input = hex!(
2491 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2492 );
2493 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2494 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2495 peer_id: *handle1.peer_id(),
2496 msg: Transactions(vec![signed_tx.clone()]),
2497 });
2498 poll_fn(|cx| {
2499 let _ = transactions.poll_unpin(cx);
2500 Poll::Ready(())
2501 })
2502 .await;
2503 assert!(pool.is_empty());
2504 handle.terminate().await;
2505 }
2506
2507 #[tokio::test(flavor = "multi_thread")]
2508 async fn test_tx_broadcasts_through_two_syncs() {
2509 reth_tracing::init_test_tracing();
2510 let net = Testnet::create(3).await;
2511
2512 let mut handles = net.handles();
2513 let handle0 = handles.next().unwrap();
2514 let handle1 = handles.next().unwrap();
2515
2516 drop(handles);
2517 let handle = net.spawn();
2518
2519 let listener0 = handle0.event_listener();
2520 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2521 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2522
2523 let client = NoopProvider::default();
2524 let pool = testing_pool();
2525 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2526 .disable_discovery()
2527 .listener_port(0)
2528 .build(client);
2529 let transactions_manager_config = config.transactions_manager_config.clone();
2530 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2531 .await
2532 .unwrap()
2533 .into_builder()
2534 .transactions(pool.clone(), transactions_manager_config)
2535 .split_with_handle();
2536
2537 tokio::task::spawn(network);
2538
2539 network_handle.update_sync_state(SyncState::Syncing);
2541 assert!(NetworkInfo::is_syncing(&network_handle));
2542 network_handle.update_sync_state(SyncState::Idle);
2543 assert!(!NetworkInfo::is_syncing(&network_handle));
2544 network_handle.update_sync_state(SyncState::Syncing);
2545 assert!(NetworkInfo::is_syncing(&network_handle));
2546
2547 let mut established = listener0.take(2);
2549 while let Some(ev) = established.next().await {
2550 match ev {
2551 NetworkEvent::ActivePeerSession { .. } |
2552 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2553 transactions.on_network_event(ev);
2555 }
2556 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2557 _ => {
2558 error!("unexpected event {ev:?}")
2559 }
2560 }
2561 }
2562 let input = hex!(
2564 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2565 );
2566 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2567 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2568 peer_id: *handle1.peer_id(),
2569 msg: Transactions(vec![signed_tx.clone()]),
2570 });
2571 poll_fn(|cx| {
2572 let _ = transactions.poll_unpin(cx);
2573 Poll::Ready(())
2574 })
2575 .await;
2576 assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2577 assert!(NetworkInfo::is_syncing(&network_handle));
2578 assert!(!pool.is_empty());
2579 handle.terminate().await;
2580 }
2581
2582 #[tokio::test(flavor = "multi_thread")]
2585 async fn test_handle_incoming_transactions_hashes() {
2586 reth_tracing::init_test_tracing();
2587
2588 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2589 let client = NoopProvider::default();
2590
2591 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2592 .listener_port(0)
2594 .disable_discovery()
2595 .build(client);
2596
2597 let pool = testing_pool();
2598
2599 let transactions_manager_config = config.transactions_manager_config.clone();
2600 let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2601 .await
2602 .unwrap()
2603 .into_builder()
2604 .transactions(pool.clone(), transactions_manager_config)
2605 .split_with_handle();
2606
2607 let peer_id_1 = PeerId::new([1; 64]);
2608 let eth_version = EthVersion::Eth66;
2609
2610 let txs = vec![TransactionSigned::new_unhashed(
2611 Transaction::Legacy(TxLegacy {
2612 chain_id: Some(4),
2613 nonce: 15u64,
2614 gas_price: 2200000000,
2615 gas_limit: 34811,
2616 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2617 value: U256::from(1234u64),
2618 input: Default::default(),
2619 }),
2620 Signature::new(
2621 U256::from_str(
2622 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2623 )
2624 .unwrap(),
2625 U256::from_str(
2626 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2627 )
2628 .unwrap(),
2629 true,
2630 ),
2631 )];
2632
2633 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2634
2635 let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2636 tx_manager.peers.insert(peer_id_1, peer_1);
2637
2638 assert!(pool.is_empty());
2639
2640 tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2641 peer_id: peer_id_1,
2642 msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2643 txs_hashes.clone(),
2644 )),
2645 });
2646
2647 let req = to_mock_session_rx
2649 .recv()
2650 .await
2651 .expect("peer_1 session should receive request with buffered hashes");
2652 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2653 assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2654
2655 let message: Vec<PooledTransactionVariant> = txs
2656 .into_iter()
2657 .map(|tx| {
2658 PooledTransactionVariant::try_from(tx)
2659 .expect("Failed to convert MockTransaction to PooledTransaction")
2660 })
2661 .collect();
2662
2663 response
2665 .send(Ok(PooledTransactions(message)))
2666 .expect("should send peer_1 response to tx manager");
2667
2668 poll_fn(|cx| {
2670 let _ = tx_manager.poll_unpin(cx);
2671 Poll::Ready(())
2672 })
2673 .await;
2674
2675 assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2678 }
2679
2680 #[tokio::test(flavor = "multi_thread")]
2681 async fn test_handle_incoming_transactions() {
2682 reth_tracing::init_test_tracing();
2683 let net = Testnet::create(3).await;
2684
2685 let mut handles = net.handles();
2686 let handle0 = handles.next().unwrap();
2687 let handle1 = handles.next().unwrap();
2688
2689 drop(handles);
2690 let handle = net.spawn();
2691
2692 let listener0 = handle0.event_listener();
2693
2694 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2695 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2696
2697 let client = NoopProvider::default();
2698 let pool = testing_pool();
2699 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2700 .disable_discovery()
2701 .listener_port(0)
2702 .build(client);
2703 let transactions_manager_config = config.transactions_manager_config.clone();
2704 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2705 .await
2706 .unwrap()
2707 .into_builder()
2708 .transactions(pool.clone(), transactions_manager_config)
2709 .split_with_handle();
2710 tokio::task::spawn(network);
2711
2712 network_handle.update_sync_state(SyncState::Idle);
2713
2714 assert!(!NetworkInfo::is_syncing(&network_handle));
2715
2716 let mut established = listener0.take(2);
2718 while let Some(ev) = established.next().await {
2719 match ev {
2720 NetworkEvent::ActivePeerSession { .. } |
2721 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2722 transactions.on_network_event(ev);
2724 }
2725 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2726 ev => {
2727 error!("unexpected event {ev:?}")
2728 }
2729 }
2730 }
2731 let input = hex!(
2733 "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2734 );
2735 let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2736 transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2737 peer_id: *handle1.peer_id(),
2738 msg: Transactions(vec![signed_tx.clone()]),
2739 });
2740 assert!(transactions
2741 .transactions_by_peers
2742 .get(signed_tx.tx_hash())
2743 .unwrap()
2744 .contains(handle1.peer_id()));
2745
2746 poll_fn(|cx| {
2748 let _ = transactions.poll_unpin(cx);
2749 Poll::Ready(())
2750 })
2751 .await;
2752
2753 assert!(!pool.is_empty());
2754 assert!(pool.get(signed_tx.tx_hash()).is_some());
2755 handle.terminate().await;
2756 }
2757
2758 #[tokio::test(flavor = "multi_thread")]
2759 async fn test_session_closed_cleans_transaction_peer_state() {
2760 let (mut tx_manager, _network) = new_tx_manager().await;
2761 let peer_id = PeerId::new([1; 64]);
2762 let fallback_peer = PeerId::new([2; 64]);
2763 let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2764 let hash_shared = B256::from_slice(&[1; 32]);
2765
2766 tx_manager.peers.insert(peer_id, peer);
2767 buffer_hash_to_tx_fetcher(
2768 &mut tx_manager.transaction_fetcher,
2769 hash_shared,
2770 peer_id,
2771 0,
2772 None,
2773 );
2774 buffer_hash_to_tx_fetcher(
2775 &mut tx_manager.transaction_fetcher,
2776 hash_shared,
2777 fallback_peer,
2778 0,
2779 None,
2780 );
2781 tx_manager.transaction_fetcher.active_peers.insert(peer_id, 1);
2782
2783 tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2784 peer_id,
2785 reason: None,
2786 }));
2787
2788 assert!(!tx_manager.peers.contains_key(&peer_id));
2790 assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2791 assert_eq!(
2793 tx_manager.transaction_fetcher.get_idle_peer_for(hash_shared),
2794 Some(&fallback_peer)
2795 );
2796 }
2797
2798 #[tokio::test(flavor = "multi_thread")]
2799 async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2800 let (mut tx_manager, _network) = new_tx_manager().await;
2801 let peer_id = PeerId::new([1; 64]);
2802 let hash = B256::from_slice(&[1; 32]);
2803
2804 tx_manager.network.update_sync_state(SyncState::Idle);
2805 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2806
2807 let err = PoolError::new(
2808 hash,
2809 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2810 BlobTransactionValidationError::InvalidProof,
2811 )),
2812 );
2813
2814 tx_manager.on_bad_import(err);
2815
2816 assert!(!tx_manager.bad_imports.contains(&hash));
2817 }
2818
2819 #[tokio::test(flavor = "multi_thread")]
2820 async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2821 let (mut tx_manager, _network) = new_tx_manager().await;
2822 let peer_id = PeerId::new([1; 64]);
2823 let hash = B256::from_slice(&[3; 32]);
2824
2825 tx_manager.network.update_sync_state(SyncState::Idle);
2826 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2827
2828 let err = PoolError::new(
2829 hash,
2830 InvalidPoolTransactionError::Eip4844(
2831 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2832 ),
2833 );
2834
2835 tx_manager.on_bad_import(err);
2836
2837 assert!(!tx_manager.bad_imports.contains(&hash));
2838 }
2839
2840 #[tokio::test(flavor = "multi_thread")]
2841 async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2842 let (mut tx_manager, _network) = new_tx_manager().await;
2843 let peer_id = PeerId::new([1; 64]);
2844 let hash = B256::from_slice(&[2; 32]);
2845
2846 tx_manager.network.update_sync_state(SyncState::Idle);
2847 tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2848
2849 let err = PoolError::new(
2850 hash,
2851 InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2852 );
2853
2854 tx_manager.on_bad_import(err);
2855
2856 assert!(tx_manager.bad_imports.contains(&hash));
2857 }
2858
2859 #[tokio::test(flavor = "multi_thread")]
2860 async fn test_on_get_pooled_transactions_network() {
2861 reth_tracing::init_test_tracing();
2862 let net = Testnet::create(2).await;
2863
2864 let mut handles = net.handles();
2865 let handle0 = handles.next().unwrap();
2866 let handle1 = handles.next().unwrap();
2867
2868 drop(handles);
2869 let handle = net.spawn();
2870
2871 let listener0 = handle0.event_listener();
2872
2873 handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2874 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2875
2876 let client = NoopProvider::default();
2877 let pool = testing_pool();
2878 let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2879 .disable_discovery()
2880 .listener_port(0)
2881 .build(client);
2882 let transactions_manager_config = config.transactions_manager_config.clone();
2883 let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2884 .await
2885 .unwrap()
2886 .into_builder()
2887 .transactions(pool.clone(), transactions_manager_config)
2888 .split_with_handle();
2889 tokio::task::spawn(network);
2890
2891 network_handle.update_sync_state(SyncState::Idle);
2892
2893 assert!(!NetworkInfo::is_syncing(&network_handle));
2894
2895 let mut established = listener0.take(2);
2897 while let Some(ev) = established.next().await {
2898 match ev {
2899 NetworkEvent::ActivePeerSession { .. } |
2900 NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2901 transactions.on_network_event(ev);
2902 }
2903 NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2904 ev => {
2905 error!("unexpected event {ev:?}")
2906 }
2907 }
2908 }
2909 handle.terminate().await;
2910
2911 let tx = MockTransaction::eip1559();
2912 let _ = transactions
2913 .pool
2914 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
2915 .await;
2916
2917 let request = GetPooledTransactions(vec![*tx.get_hash()]);
2918
2919 let (send, receive) =
2920 oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
2921
2922 transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
2923 peer_id: *handle1.peer_id(),
2924 request,
2925 response: send,
2926 });
2927
2928 match receive.await.unwrap() {
2929 Ok(PooledTransactions(transactions)) => {
2930 assert_eq!(transactions.len(), 1);
2931 }
2932 Err(e) => {
2933 panic!("error: {e:?}");
2934 }
2935 }
2936 }
2937
2938 #[tokio::test]
2942 async fn test_partially_tx_response() {
2943 reth_tracing::init_test_tracing();
2944
2945 let mut tx_manager = new_tx_manager().await.0;
2946 let tx_fetcher = &mut tx_manager.transaction_fetcher;
2947
2948 let peer_id_1 = PeerId::new([1; 64]);
2949 let eth_version = EthVersion::Eth66;
2950
2951 let txs = vec![
2952 TransactionSigned::new_unhashed(
2953 Transaction::Legacy(TxLegacy {
2954 chain_id: Some(4),
2955 nonce: 15u64,
2956 gas_price: 2200000000,
2957 gas_limit: 34811,
2958 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2959 value: U256::from(1234u64),
2960 input: Default::default(),
2961 }),
2962 Signature::new(
2963 U256::from_str(
2964 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2965 )
2966 .unwrap(),
2967 U256::from_str(
2968 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2969 )
2970 .unwrap(),
2971 true,
2972 ),
2973 ),
2974 TransactionSigned::new_unhashed(
2975 Transaction::Eip1559(TxEip1559 {
2976 chain_id: 4,
2977 nonce: 26u64,
2978 max_priority_fee_per_gas: 1500000000,
2979 max_fee_per_gas: 1500000013,
2980 gas_limit: MIN_TRANSACTION_GAS,
2981 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
2982 value: U256::from(3000000000000000000u64),
2983 input: Default::default(),
2984 access_list: Default::default(),
2985 }),
2986 Signature::new(
2987 U256::from_str(
2988 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
2989 )
2990 .unwrap(),
2991 U256::from_str(
2992 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
2993 )
2994 .unwrap(),
2995 true,
2996 ),
2997 ),
2998 ];
2999
3000 let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3001
3002 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3003 peer_1.seen_transactions.insert(txs_hashes[0]);
3006 peer_1.seen_transactions.insert(txs_hashes[1]);
3007 tx_manager.peers.insert(peer_id_1, peer_1);
3008
3009 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, 0, None);
3010 buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, 0, None);
3011
3012 assert!(tx_fetcher.is_idle(&peer_id_1));
3014 assert_eq!(tx_fetcher.active_peers.len(), 0);
3015
3016 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3018
3019 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3020 assert!(!tx_fetcher.is_idle(&peer_id_1));
3022 assert_eq!(tx_fetcher.active_peers.len(), 1);
3023
3024 let req = to_mock_session_rx
3026 .recv()
3027 .await
3028 .expect("peer_1 session should receive request with buffered hashes");
3029 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3030
3031 let message: Vec<PooledTransactionVariant> = txs
3032 .into_iter()
3033 .take(1)
3034 .map(|tx| {
3035 PooledTransactionVariant::try_from(tx)
3036 .expect("Failed to convert MockTransaction to PooledTransaction")
3037 })
3038 .collect();
3039 response
3041 .send(Ok(PooledTransactions(message)))
3042 .expect("should send peer_1 response to tx manager");
3043 let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3044 unreachable!()
3045 };
3046
3047 assert!(tx_fetcher.is_idle(&peer_id));
3049 assert_eq!(tx_fetcher.active_peers.len(), 0);
3050 assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3052 }
3053
3054 #[tokio::test]
3055 async fn test_max_retries_tx_request() {
3056 reth_tracing::init_test_tracing();
3057
3058 let mut tx_manager = new_tx_manager().await.0;
3059 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3060
3061 let peer_id_1 = PeerId::new([1; 64]);
3062 let peer_id_2 = PeerId::new([2; 64]);
3063 let eth_version = EthVersion::Eth66;
3064 let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3065
3066 let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3067 peer_1.seen_transactions.insert(seen_hashes[0]);
3070 peer_1.seen_transactions.insert(seen_hashes[1]);
3071 tx_manager.peers.insert(peer_id_1, peer_1);
3072
3073 let retries = 1;
3076 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[1], peer_id_1, retries, None);
3077 buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[0], peer_id_1, retries, None);
3078
3079 assert!(tx_fetcher.is_idle(&peer_id_1));
3081 assert_eq!(tx_fetcher.active_peers.len(), 0);
3082
3083 tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3085
3086 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3087
3088 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3089 assert!(!tx_fetcher.is_idle(&peer_id_1));
3091 assert_eq!(tx_fetcher.active_peers.len(), 1);
3092
3093 let req = to_mock_session_rx
3095 .recv()
3096 .await
3097 .expect("peer_1 session should receive request with buffered hashes");
3098 let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3099 let GetPooledTransactions(hashes) = request;
3100
3101 let hashes = hashes.into_iter().collect::<B256Set>();
3102
3103 assert_eq!(hashes, seen_hashes.into_iter().collect::<B256Set>());
3104
3105 response
3107 .send(Err(RequestError::BadResponse))
3108 .expect("should send peer_1 response to tx manager");
3109 let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3110 unreachable!()
3111 };
3112
3113 assert!(tx_fetcher.is_idle(&peer_id));
3115 assert_eq!(tx_fetcher.active_peers.len(), 0);
3116 assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3118
3119 let (peer_2, mut to_mock_session_rx) = new_mock_session(peer_id_2, eth_version);
3120 tx_manager.peers.insert(peer_id_2, peer_2);
3121
3122 let msg =
3124 NewPooledTransactionHashes::Eth66(NewPooledTransactionHashes66(seen_hashes.to_vec()));
3125 tx_manager.on_new_pooled_transaction_hashes(peer_id_2, msg);
3126
3127 let tx_fetcher = &mut tx_manager.transaction_fetcher;
3128
3129 assert_eq!(tx_fetcher.active_peers.len(), 1);
3131
3132 assert_eq!(tx_fetcher.num_all_hashes(), 2);
3134 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3136
3137 let req = to_mock_session_rx
3139 .recv()
3140 .await
3141 .expect("peer_2 session should receive request with buffered hashes");
3142 let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3143
3144 response
3146 .send(Err(RequestError::BadResponse))
3147 .expect("should send peer_2 response to tx manager");
3148 let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3149
3150 assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3153 assert_eq!(tx_fetcher.active_peers.len(), 0);
3154 }
3155
3156 #[test]
3157 fn test_direct_propagation_transaction_uses_2718_size() {
3158 let mut tx_gen = TransactionGenerator::new(rand::rng());
3159 let tx = tx_gen.gen_eip1559();
3160 let expected_size = tx.encode_2718_len();
3161
3162 let tx = PropagateTransaction::new(tx);
3163
3164 assert_eq!(tx.propagation_size(), expected_size);
3165 }
3166
3167 #[test]
3168 fn test_transaction_builder_empty() {
3169 let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3170 assert!(builder.is_empty());
3171
3172 let mut tx_gen = TransactionGenerator::new(rand::rng());
3173 let tx =
3174 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3175 builder.push(&tx);
3176 assert!(!builder.is_empty());
3177
3178 let txs = builder.build();
3179 assert!(txs.full.is_none());
3180 let txs = txs.pooled.unwrap();
3181 assert_eq!(txs.len(), 1);
3182 }
3183
3184 #[test]
3185 fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3186 let mut tx_gen = TransactionGenerator::new(rand::rng());
3187 let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3188 let pooled = PropagatePooledTransactionEncoder::new(tx);
3189
3190 let mut pooled_encoded = Vec::new();
3191 pooled.encode(&mut pooled_encoded);
3192 assert_eq!(pooled.length(), pooled_encoded.len());
3193
3194 let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3195 let mut first_encoded = Vec::new();
3196 broadcast.encode(&mut first_encoded);
3197 let mut second_encoded = Vec::new();
3198 broadcast.encode(&mut second_encoded);
3199 assert_eq!(first_encoded, second_encoded);
3200
3201 let mut encoded = first_encoded.as_slice();
3202 let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3203 assert_eq!(decoded.len(), 1);
3204 assert!(encoded.is_empty());
3205 }
3206
3207 #[test]
3208 fn test_transaction_builder_large() {
3209 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3210 assert!(builder.is_empty());
3211
3212 let mut tx_gen = TransactionGenerator::new(rand::rng());
3213 let mut tx = tx_gen.gen_eip1559_pooled();
3214 tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3216 let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3217 builder.push(&tx);
3218 assert!(!builder.is_empty());
3219
3220 let txs = builder.clone().build();
3221 assert!(txs.pooled.is_none());
3222 let txs = txs.full.unwrap();
3223 assert_eq!(txs.len(), 1);
3224
3225 builder.push(&tx);
3226
3227 let txs = builder.clone().build();
3228 let pooled = txs.pooled.unwrap();
3229 assert_eq!(pooled.len(), 1);
3230 let txs = txs.full.unwrap();
3231 assert_eq!(txs.len(), 1);
3232 }
3233
3234 #[test]
3235 fn test_transaction_builder_eip4844() {
3236 let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3237 assert!(builder.is_empty());
3238
3239 let mut tx_gen = TransactionGenerator::new(rand::rng());
3240 let tx =
3241 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3242 builder.push(&tx);
3243 assert!(!builder.is_empty());
3244
3245 let txs = builder.clone().build();
3246 assert!(txs.full.is_none());
3247 let txs = txs.pooled.unwrap();
3248 assert_eq!(txs.len(), 1);
3249
3250 let tx =
3251 PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3252 builder.push(&tx);
3253
3254 let txs = builder.clone().build();
3255 let pooled = txs.pooled.unwrap();
3256 assert_eq!(pooled.len(), 1);
3257 let txs = txs.full.unwrap();
3258 assert_eq!(txs.len(), 1);
3259 }
3260
3261 #[tokio::test]
3262 async fn test_propagate_full() {
3263 reth_tracing::init_test_tracing();
3264
3265 let (mut tx_manager, network) = new_eth_tx_manager().await;
3266 let peer_id = PeerId::random();
3267
3268 network.handle().update_sync_state(SyncState::Idle);
3270
3271 let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3273
3274 let session_info = SessionInfo {
3275 peer_id,
3276 remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3277 client_version: Arc::from(""),
3278 capabilities: Arc::new(vec![].into()),
3279 status: Arc::new(Default::default()),
3280 version: EthVersion::Eth68,
3281 peer_kind: PeerKind::Basic,
3282 };
3283 let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3284 tx_manager
3285 .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3286 let mut propagate = vec![];
3287 let mut tx_gen = TransactionGenerator::new(rand::rng());
3288 let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3289 propagate.push(eip1559_tx.clone());
3290 let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3291 propagate.push(eip4844_tx.clone());
3292
3293 let propagated = tx_manager.propagate_transactions(
3294 propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3295 PropagationMode::Basic,
3296 );
3297 assert_eq!(propagated.len(), 2);
3298 let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3299 assert_eq!(prop_txs.len(), 1);
3300 assert!(prop_txs[0].is_full());
3301
3302 let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3303 assert_eq!(prop_txs.len(), 1);
3304 assert!(prop_txs[0].is_hash());
3305
3306 let peer = tx_manager.peers.get(&peer_id).unwrap();
3307 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3308 assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3309 peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3310
3311 let propagated = tx_manager.propagate_transactions(
3313 propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3314 PropagationMode::Basic,
3315 );
3316 assert!(propagated.is_empty());
3317 }
3318
3319 #[tokio::test]
3320 async fn test_truncated_hash_announcement_not_marked_seen() {
3321 reth_tracing::init_test_tracing();
3322
3323 let (mut tx_manager, network) = new_eth_tx_manager().await;
3324 tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3326
3327 network.handle().update_sync_state(SyncState::Idle);
3329
3330 let peer_id = PeerId::random();
3331 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3332 tx_manager.peers.insert(peer_id, peer);
3333
3334 let mut tx_gen = TransactionGenerator::new(rand::rng());
3336 let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3337 .map(|nonce| {
3338 valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3339 })
3340 .collect::<Vec<_>>();
3341 let last_sent = *txs[txs.len() - 2].hash();
3342 let truncated = *txs[txs.len() - 1].hash();
3343
3344 let propagated = tx_manager.propagate_transactions(
3345 txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3346 PropagationMode::Basic,
3347 );
3348
3349 assert!(propagated.get(&truncated).is_none());
3351 let peer = tx_manager.peers.get(&peer_id).unwrap();
3352 assert!(!peer.seen_transactions.contains(&truncated));
3353 assert!(peer.seen_transactions.contains(&last_sent));
3354 }
3355
3356 #[tokio::test]
3357 async fn test_propagate_pending_txs_while_initially_syncing() {
3358 reth_tracing::init_test_tracing();
3359
3360 let (mut tx_manager, network) = new_eth_tx_manager().await;
3361 let peer_id = PeerId::random();
3362
3363 network.handle().update_sync_state(SyncState::Syncing);
3365 assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3366
3367 let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3369 tx_manager.peers.insert(peer_id, peer);
3370
3371 let mut tx_gen = TransactionGenerator::new(rand::rng());
3372 let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3373 let tx_hash = *tx.hash();
3374 tx_manager
3375 .pool
3376 .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3377 .await
3378 .expect("transaction should be accepted into the pool");
3379
3380 tx_manager.on_new_pending_transactions(vec![tx_hash]);
3381
3382 let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3383 assert!(peer.seen_transactions.contains(&tx_hash));
3384 }
3385
3386 #[tokio::test]
3387 async fn test_relaxed_filter_ignores_unknown_tx_types() {
3388 reth_tracing::init_test_tracing();
3389
3390 let transactions_manager_config = TransactionsManagerConfig::default();
3391
3392 let propagation_policy = TransactionPropagationKind::default();
3393 let announcement_policy = RelaxedEthAnnouncementFilter::default();
3394
3395 let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3396
3397 let pool = testing_pool();
3398 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
3399 let client = NoopProvider::default();
3400
3401 let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3402 .listener_port(0)
3403 .disable_discovery()
3404 .build(client.clone());
3405
3406 let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3407 let (to_tx_manager_tx, from_network_rx) =
3408 reth_metrics::common::mpsc::memory_bounded_channel::<
3409 NetworkTransactionEvent<EthNetworkPrimitives>,
3410 >(
3411 crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3412 "test_tx_channel",
3413 );
3414 network_manager.set_transactions(to_tx_manager_tx);
3415 let network_handle = network_manager.handle().clone();
3416 let network_service_handle = tokio::spawn(network_manager);
3417
3418 let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3419 network_handle.clone(),
3420 pool.clone(),
3421 from_network_rx,
3422 transactions_manager_config,
3423 policy_bundle,
3424 );
3425
3426 let peer_id = PeerId::random();
3427 let eth_version = EthVersion::Eth68;
3428 let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3429 tx_manager.peers.insert(peer_id, mock_peer_metadata);
3430
3431 let mut tx_factory = MockTransactionFactory::default();
3432
3433 let valid_known_tx = tx_factory.create_eip1559();
3434 let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3435
3436 let known_tx_hash = *known_tx_signed.hash();
3437 let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3438 let known_tx_size = known_tx_signed.encoded_length();
3439
3440 let unknown_tx_hash = B256::random();
3441 let unknown_tx_type_byte = 0xff_u8;
3442 let unknown_tx_size = 150;
3443
3444 let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3445 types: vec![known_tx_type_byte, unknown_tx_type_byte],
3446 sizes: vec![known_tx_size, unknown_tx_size],
3447 hashes: vec![known_tx_hash, unknown_tx_hash],
3448 });
3449
3450 tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3451
3452 poll_fn(|cx| {
3453 let _ = tx_manager.poll_unpin(cx);
3454 Poll::Ready(())
3455 })
3456 .await;
3457
3458 let mut requested_hashes_in_getpooled = B256Set::default();
3459 let mut unexpected_request_received = false;
3460
3461 match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3462 .await
3463 {
3464 Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3465 let GetPooledTransactions(hashes) = request;
3466 for hash in hashes {
3467 requested_hashes_in_getpooled.insert(hash);
3468 }
3469 let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3470 }
3471 Ok(Some(other_request)) => {
3472 tracing::error!(?other_request, "Received unexpected PeerRequest type");
3473 unexpected_request_received = true;
3474 }
3475 Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3476 Err(_timeout_err) => {
3477 tracing::info!("Timeout: No GetPooledTransactions request received.")
3478 }
3479 }
3480
3481 assert!(
3482 requested_hashes_in_getpooled.contains(&known_tx_hash),
3483 "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3484 );
3485 assert!(
3486 !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3487 "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3488 );
3489 assert!(
3490 !unexpected_request_received,
3491 "An unexpected P2P request was received by the mock peer."
3492 );
3493
3494 network_service_handle.abort();
3495 }
3496}