Skip to main content

reth_network/transactions/
mod.rs

1//! Transactions management for the p2p network.
2
3use alloy_consensus::transaction::TxHashRef;
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use smallvec::SmallVec;
6
7/// Aggregation on configurable parameters for [`TransactionsManager`].
8pub mod config;
9/// Default and spec'd bounds.
10pub mod constants;
11/// Component responsible for fetching transactions from [`NewPooledTransactionHashes`].
12pub mod fetcher;
13/// Defines the traits for transaction-related policies.
14pub 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
89/// The future for importing transactions into the pool.
90///
91/// Resolves with the result of each transaction import.
92pub type PoolImportFuture =
93    Pin<Box<dyn Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send + 'static>>;
94
95/// Api to interact with [`TransactionsManager`] task.
96///
97/// This can be obtained via [`TransactionsManager::handle`] and can be used to manually interact
98/// with the [`TransactionsManager`] task once it is spawned.
99///
100/// For example [`TransactionsHandle::get_peer_transaction_hashes`] returns the transaction hashes
101/// known by a specific peer.
102#[derive(Debug, Clone)]
103pub struct TransactionsHandle<N: NetworkPrimitives = EthNetworkPrimitives> {
104    /// Command channel to the [`TransactionsManager`]
105    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    /// Fetch the [`PeerRequestSender`] for the given peer.
114    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    /// Manually propagate the transaction that belongs to the hash.
124    pub fn propagate(&self, hash: TxHash) {
125        self.send(TransactionsCommand::PropagateHash(hash))
126    }
127
128    /// Manually propagate the transaction hash to a specific peer.
129    ///
130    /// Note: this only propagates if the pool contains the transaction.
131    pub fn propagate_hash_to(&self, hash: TxHash, peer: PeerId) {
132        self.propagate_hashes_to(Some(hash), peer)
133    }
134
135    /// Manually propagate the transaction hashes to a specific peer.
136    ///
137    /// Note: this only propagates the transactions that are known to the pool.
138    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    /// Request the active peer IDs from the [`TransactionsManager`].
147    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    /// Manually propagate full transaction hashes to a specific peer.
154    ///
155    /// Do nothing if transactions are empty.
156    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    /// Manually propagate the given transaction hashes to all peers.
164    ///
165    /// It's up to the [`TransactionsManager`] whether the transactions are sent as hashes or in
166    /// full.
167    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    /// Manually propagate the given transactions to all peers.
175    ///
176    /// It's up to the [`TransactionsManager`] whether the transactions are sent as hashes or in
177    /// full.
178    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    /// Request the transaction hashes known by specific peers.
191    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    /// Request the transaction hashes known by a specific peer.
204    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    /// Requests the transactions directly from the given peer.
210    ///
211    /// Returns `None` if the peer is not connected.
212    ///
213    /// **Note**: this returns the response from the peer as received.
214    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/// Manages transactions on top of the p2p network.
230///
231/// This can be spawned to another task and is supposed to be run as background service.
232/// [`TransactionsHandle`] can be used as frontend to programmatically send commands to it and
233/// interact with it.
234///
235/// The [`TransactionsManager`] is responsible for:
236///    - handling incoming eth messages for transactions.
237///    - serving transaction requests.
238///    - propagate transactions
239///
240/// This type communicates with the [`NetworkManager`](crate::NetworkManager) in both directions.
241///   - receives incoming network messages.
242///   - sends messages to dispatch (responses, propagate tx)
243///
244/// It is directly connected to the [`TransactionPool`] to retrieve requested transactions and
245/// propagate new transactions over the network.
246///
247/// It can be configured with different policies for transaction propagation and announcement
248/// filtering. See [`NetworkPolicies`] for more details.
249///
250/// ## Network Transaction Processing
251///
252/// ### Message Types
253///
254/// - **`Transactions`**: Full transaction broadcasts (rejects blob transactions)
255/// - **`NewPooledTransactionHashes`**: Hash announcements
256///
257/// ### Peer Tracking
258///
259/// - Maintains per-peer transaction cache (default: 10,240 entries)
260/// - Prevents duplicate imports and enables efficient propagation
261///
262/// ### Bad Transaction Handling
263///
264/// Caches and rejects transactions with consensus violations (gas, signature, chain ID).
265/// Penalizes peers sending invalid transactions.
266///
267/// ### Import Management
268///
269/// Limits concurrent pool imports and backs off when approaching capacity.
270///
271/// ### Transaction Fetching
272///
273/// For announced transactions: filters known → queues unknown → fetches → imports
274///
275/// ### Propagation Rules
276///
277/// Based on: origin (Local/External/Private), peer capabilities, and network state.
278/// Disabled during initial sync.
279///
280/// ### Security
281///
282/// Rate limiting via reputation, bad transaction isolation, peer scoring.
283#[derive(Debug)]
284#[must_use = "Manager does nothing unless polled."]
285pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
286    /// Access to the transaction pool.
287    pool: Pool,
288    /// Network access.
289    network: NetworkHandle<N>,
290    /// Subscriptions to all network related events.
291    ///
292    /// From which we get all new incoming transaction related messages.
293    network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
294    /// Transaction fetcher to handle inflight and missing transaction requests.
295    transaction_fetcher: TransactionFetcher<N>,
296    /// All currently pending transactions grouped by peers.
297    ///
298    /// This way we can track incoming transactions and prevent multiple pool imports for the same
299    /// transaction
300    transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
301    /// Transactions that are currently imported into the `Pool`.
302    ///
303    /// The import process includes:
304    ///  - validation of the transactions, e.g. transaction is well formed: valid tx type, fees are
305    ///    valid, or for 4844 transaction the blobs are valid. See also
306    ///    [`EthTransactionValidator`](reth_transaction_pool::validate::EthTransactionValidator)
307    /// - if the transaction is valid, it is added into the pool.
308    ///
309    /// Once the new transaction reaches the __pending__ state it will be emitted by the pool via
310    /// [`TransactionPool::pending_transactions_listener`] and arrive at the `pending_transactions`
311    /// receiver.
312    pool_imports: FuturesUnordered<PoolImportFuture>,
313    /// Stats on pending pool imports that help the node self-monitor.
314    pending_pool_imports_info: PendingPoolImportsInfo,
315    /// Bad imports.
316    bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
317    /// All the connected peers.
318    peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
319    /// Send half for the command channel.
320    ///
321    /// This is kept so that a new [`TransactionsHandle`] can be created at any time.
322    command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
323    /// Incoming commands from [`TransactionsHandle`].
324    ///
325    /// This will only receive commands if a user manually sends a command to the manager through
326    /// the [`TransactionsHandle`] to interact with this type directly.
327    command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
328    /// A stream that yields new __pending__ transactions.
329    ///
330    /// A transaction is considered __pending__ if it is executable on the current state of the
331    /// chain. In other words, this only yields transactions that satisfy all consensus
332    /// requirements, these include:
333    ///   - no nonce gaps
334    ///   - all dynamic fee requirements are (currently) met
335    ///   - account has enough balance to cover the transaction's gas
336    pending_transactions: mpsc::Receiver<TxHash>,
337    /// Incoming events from the [`NetworkManager`](crate::NetworkManager).
338    transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
339    /// How the `TransactionsManager` is configured.
340    config: TransactionsManagerConfig,
341    /// Network Policies
342    policies: NetworkPolicies<N>,
343    /// `TransactionsManager` metrics
344    metrics: TransactionsManagerMetrics,
345    /// `AnnouncedTxTypes` metrics
346    announced_tx_types_metrics: AnnouncedTxTypesMetrics,
347}
348
349impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
350    /// Sets up a new instance.
351    ///
352    /// Note: This expects an existing [`NetworkManager`](crate::NetworkManager) instance.
353    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    /// Sets up a new instance with given the settings.
374    ///
375    /// Note: This expects an existing [`NetworkManager`](crate::NetworkManager) instance.
376    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        // install a listener for new __pending__ transactions that are allowed to be propagated
392        // over the network
393        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    /// Returns a new handle that can send commands to this type.
423    pub fn handle(&self) -> TransactionsHandle<N> {
424        TransactionsHandle { manager_tx: self.command_tx.clone() }
425    }
426
427    /// Returns `true` if [`TransactionsManager`] has capacity to request pending hashes. Returns
428    /// `false` if [`TransactionsManager`] is operating close to full capacity.
429    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    /// Returns `true` if [`TransactionsManager`] has capacity for more pending pool imports.
435    fn has_capacity_for_pending_pool_imports(&self) -> bool {
436        self.remaining_pool_import_capacity() > 0
437    }
438
439    /// Returns the remaining capacity for pending pool imports.
440    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    /// Handles a closed peer session, removing the peer from transaction-local tracking state.
462    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    /// Clear the transaction
470    fn on_good_import(&mut self, hash: TxHash) {
471        self.transactions_by_peers.remove(&hash);
472    }
473
474    /// Handles a failed transaction import.
475    ///
476    /// Blob sidecar errors (e.g. invalid proof, missing sidecar) are penalized via
477    /// `report_peer_bad_transactions` but NOT cached in `bad_imports` — the transaction itself
478    /// may be valid when fetched from another peer with correct sidecar data.
479    ///
480    /// Other bad transactions are penalized and cached in `bad_imports` to avoid fetching or
481    /// importing them again.
482    ///
483    /// Errors that count as bad transactions are:
484    ///
485    /// - intrinsic gas too low
486    /// - exceeds gas limit
487    /// - gas uint overflow
488    /// - exceeds max init code size
489    /// - oversized data
490    /// - signer account has bytecode
491    /// - chain id mismatch
492    /// - old legacy chain id
493    /// - tx type not supported
494    ///
495    /// (and additionally for blobs txns...)
496    ///
497    /// - no blobs
498    /// - too many blobs
499    /// - invalid kzg proof
500    /// - kzg error
501    /// - not blob transaction (tx type mismatch)
502    /// - wrong versioned kzg commitment hash
503    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            // Blob sidecar errors: penalize but do NOT cache the hash as bad.
508            // The transaction may be valid — only the sidecar from this peer was wrong.
509            // Using regular penalties means repeated offenders still get disconnected.
510            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 we're _currently_ syncing, we ignore a bad transaction
519        if !err.is_bad_transaction() || self.network.is_syncing() {
520            return
521        }
522        // otherwise we penalize the peer that sent the bad transaction, with the assumption that
523        // the peer should have known that this transaction is bad (e.g. violating consensus rules)
524        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    /// Runs an operation to fetch hashes that are cached in [`TransactionFetcher`].
534    ///
535    /// Returns `true` if a request was sent.
536    fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
537        // try drain transaction hashes pending fetch
538        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                // peer is already disconnected
553                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        // update metrics for whole poll function
575        metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
576        // update metrics for nested expressions
577        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    /// Processes a batch import results.
589    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    /// Request handler for an incoming `NewPooledTransactionHashes`
603    fn on_new_pooled_transaction_hashes(
604        &mut self,
605        peer_id: PeerId,
606        msg: NewPooledTransactionHashes,
607    ) {
608        // If the node is initially syncing, ignore transactions
609        if self.network.is_initially_syncing() {
610            return
611        }
612        if self.network.tx_gossip_disabled() {
613            return
614        }
615
616        // get handle to peer's session, if the session is still active
617        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        // keep track of the transactions the peer knows
629        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            // this may occur if transactions are sent or announced to a peer, at the same time as
637            // the peer sends/announces those hashes to us. this is because, marking
638            // txns as seen by a peer is done optimistically upon sending them to the
639            // peer.
640            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        // 1. filter out spam
656        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        // 2. filter out transactions pending import to pool
669        partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
670
671        // 3. filter out invalid entries (spam)
672        //
673        // validates messages with respect to the given network, e.g. allowed tx types.
674        // done before the pool lookup since these are cheap in-memory checks that shrink
675        // the set before acquiring the pool lock.
676        //
677        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        // 4. filter out known hashes
735        //
736        // known txns have already been successfully fetched or received over gossip.
737        //
738        // most hashes will be filtered out here since the mempool protocol is a gossip
739        // protocol, healthy peers will send many of the same hashes.
740        //
741        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            // nothing to request
753            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            // no valid announcement data
761            return
762        }
763
764        // 5. filter out already seen unknown hashes
765        //
766        // seen hashes are already in the tx fetcher, pending fetch.
767        //
768        // for any seen hashes add the peer as fallback. unseen hashes are loaded into the tx
769        // fetcher, hence they should be valid at this point.
770        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            // nothing to request
780            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        // only send request for hashes to idle peer, otherwise buffer hashes storing peer as
793        // fallback
794        if !self.transaction_fetcher.is_idle(&peer_id) {
795            // load message version before announcement data is destructed in packing
796            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        // request the missing transactions
836        //
837        // get handle to peer's session again, at this point we know it exists
838        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    /// Invoked when transactions in the local mempool are considered __pending__.
867    ///
868    /// When a transaction in the local mempool is moved to the pending pool, we propagate them to
869    /// connected peers over network using the `Transactions` and `NewPooledTransactionHashes`
870    /// messages. The Transactions message relays complete transaction objects and is typically
871    /// sent to a small, random fraction of connected peers.
872    ///
873    /// All other peers receive a notification of the transaction hash and can request the
874    /// complete transaction object if it is unknown to them. The dissemination of complete
875    /// transactions to a fraction of peers usually ensures that all nodes receive the transaction
876    /// and won't need to request it.
877    fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
878        // We intentionally do not gate this on initial sync.
879        // During initial sync we skip importing tx announcements from peers in
880        // `on_new_pooled_transaction_hashes`, so transactions reaching this path are local.
881        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    /// Propagate the full transactions to a specific peer.
891    ///
892    /// Returns the propagated transactions.
893    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        // filter all transactions unknown to the peer
904        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            // skip cache check if forced
910            full_transactions.extend(to_propagate);
911        } else {
912            // Iterate through the transactions to propagate and fill the hashes and full
913            // transaction
914            for tx in to_propagate {
915                if !peer.seen_transactions.contains(tx.tx_hash()) {
916                    // Only include if the peer hasn't seen the transaction
917                    full_transactions.push(&tx);
918                }
919            }
920        }
921
922        if full_transactions.is_empty() {
923            // nothing to propagate
924            return None
925        }
926
927        let PropagateTransactions { pooled, full } = full_transactions.build();
928
929        // send hashes if any
930        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                // mark transaction as seen by peer
934                peer.seen_transactions.insert(hash);
935            }
936
937            // send hashes of transactions
938            self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
939        }
940
941        // send full transactions, if any
942        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                // mark transaction as seen by peer
946                peer.seen_transactions.insert(*hash);
947            }
948
949            // send full transactions
950            self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
951        }
952
953        // Update propagated transactions metrics
954        self.metrics.propagated_transactions.increment(propagated.len() as u64);
955
956        Some(propagated)
957    }
958
959    /// Propagate the transaction hashes to the given peer
960    ///
961    /// Note: This will only send the hashes for transactions that exist in the pool.
962    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        // This fetches a transactions from the pool, including the blob transactions, which are
971        // only ever sent as hashes.
972        let propagated = {
973            let Some(peer) = self.peers.get_mut(&peer_id) else {
974                // no such peer
975                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            // check if transaction is known to peer
984            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                        // Include if the peer hasn't seen it
992                        hashes.push(&tx);
993                    }
994                }
995            }
996
997            let new_pooled_hashes = hashes.build();
998
999            if new_pooled_hashes.is_empty() {
1000                // nothing to propagate
1001                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            // send hashes of transactions
1012            self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1013
1014            // Update propagated transactions metrics
1015            self.metrics.propagated_transactions.increment(propagated.len() as u64);
1016
1017            propagated
1018        };
1019
1020        // notify pool so events get fired
1021        self.pool.on_propagated(propagated);
1022    }
1023
1024    /// Propagate the transactions to all connected peers either as full objects or hashes.
1025    ///
1026    /// The message for new pooled hashes depends on the negotiated version of the stream.
1027    /// See [`NewPooledTransactionHashes`]
1028    ///
1029    /// Note: EIP-4844 are disallowed from being broadcast in full and are only ever sent as hashes, see also <https://eips.ethereum.org/EIPS/eip-4844#networking>.
1030    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        // send full transactions to a set of the connected peers based on the configured mode
1041        let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1042
1043        // Note: Assuming ~random~ order due to random state of the peers map hasher
1044        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                // skip peers we should not propagate to
1048                continue
1049            }
1050
1051            // determine whether to send full tx objects or hashes.
1052            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            // Transactions are optimistically marked as seen by the peer when included in the
1060            // message, see `PeerMetadata::seen_transactions`.
1061            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                // Iterate through the transactions to propagate and fill the hashes and full
1068                // transaction lists, before deciding whether or not to send full transactions to
1069                // the peer.
1070                for tx in &to_propagate {
1071                    // Only include the transaction if the peer hasn't seen it yet
1072                    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            // send hashes if any
1086            if let Some(mut new_pooled_hashes) = pooled {
1087                // Unhappy path: too many hashes for a single message. This should not happen
1088                // during regular propagation, which is capped at the soft limit per batch, and
1089                // is only reachable via manual propagation commands with oversized batches.
1090                if new_pooled_hashes.len() >
1091                    SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1092                {
1093                    // hashes that exceed the limit are not sent, so they must not be tracked as
1094                    // seen by the peer
1095                    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                // send hashes of transactions
1113                self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1114            }
1115
1116            // send full transactions, if any
1117            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                // send full transactions
1125                self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1126            }
1127        }
1128
1129        // Update propagated transactions metrics
1130        self.metrics.propagated_transactions.increment(propagated.len() as u64);
1131
1132        propagated
1133    }
1134
1135    /// Propagates the given transactions to the peers
1136    ///
1137    /// This fetches all transaction from the pool, including the 4844 blob transactions but
1138    /// __without__ their sidecar, because 4844 transactions are only ever announced as hashes.
1139    fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1140        if self.peers.is_empty() {
1141            // nothing to propagate
1142            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        // notify pool so events get fired
1150        self.pool.on_propagated(propagated);
1151    }
1152
1153    /// Request handler for an incoming request for transactions
1154    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        // fast exit if gossip is disabled
1161        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            // we sent a response at which point we assume that the peer is aware of the
1175            // transactions
1176            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    /// Handles a command received from a detached [`TransactionsHandle`]
1184    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    /// Handles session establishment and peer transactions initialization.
1228    ///
1229    /// This is invoked when a new session is established.
1230    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        // Insert a new peer into the peerset.
1238        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        // Send a `NewPooledTransactionHashes` to the peer with up to
1256        // `SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE`
1257        // transactions in the pool.
1258        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        // Get transactions to broadcast
1264        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        // Build and send transaction hashes message
1273        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    /// Handles a received event related to common network events.
1285    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                // process active peer session and broadcast available transaction from the pool
1292                self.handle_peer_session(info, messages);
1293            }
1294            NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1295                let peer_id = info.peer_id;
1296                // get messages from existing peer
1297                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    /// Returns true if the ingress policy allows processing messages from the given peer.
1311    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    /// Handles dedicated transaction events related to the `eth` protocol.
1322    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                // ensure we didn't receive any blob transactions as these are disallowed to be
1331                // broadcasted in full
1332
1333                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    /// Starts the import process for the given transactions.
1365    fn import_transactions(
1366        &mut self,
1367        peer_id: PeerId,
1368        transactions: PooledTransactions<N::PooledTransaction>,
1369        source: TransactionSource,
1370    ) {
1371        // If the node is pipeline syncing, ignore transactions
1372        if self.network.is_initially_syncing() {
1373            return
1374        }
1375        if self.network.tx_gossip_disabled() {
1376            return
1377        }
1378
1379        // Early return if we don't have capacity for any imports
1380        if !self.has_capacity_for_pending_pool_imports() {
1381            return
1382        }
1383
1384        let mut transactions = transactions.0;
1385
1386        // Truncate to remaining capacity early to bound work on all subsequent processing.
1387        // Well-behaved peers follow the 4096 soft limit, so oversized payloads are likely
1388        // malicious and we avoid wasting CPU on them.
1389        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        // mark the transactions as received
1405        self.transaction_fetcher
1406            .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1407
1408        // track that the peer knows these transaction, but only if this is a new broadcast.
1409        // If we received the transactions as the response to our `GetPooledTransactions``
1410        // requests (based on received `NewPooledTransactionHashes`) then we already
1411        // recorded the hashes as seen by this peer in `Self::on_new_pooled_transaction_hashes`.
1412        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        // tracks the quality of the given transactions
1420        let mut has_bad_transactions = false;
1421
1422        // 1. Remove known, already-tracked, and invalid transactions first since these are
1423        // cheap in-memory checks against local maps
1424        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        // 2. filter out txns already inserted into pool
1446        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        // Record the transactions as seen by the peer
1475        for tx in &new_txs {
1476            self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1477        }
1478
1479        // 3. import new transactions as a batch to minimize lock contention on the underlying
1480        // pool
1481        if !new_txs.is_empty() {
1482            let pool = self.pool.clone();
1483            // update metrics
1484            let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1485            metric_pending_pool_imports.increment(new_txs.len() as f64);
1486
1487            // update self-monitoring info
1488            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                // update metrics
1500                metric_pending_pool_imports.decrement(added as f64);
1501                // update self-monitoring info
1502                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            // peer sent us invalid transactions
1520            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    /// Processes a [`FetchEvent`].
1531    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
1550/// An endless future. Preemption ensure that future is non-blocking, nonetheless. See
1551/// [`crate::NetworkManager`] for more context on the design pattern.
1552///
1553/// This should be spawned or used as part of `tokio::select!`.
1554//
1555// spawned in `NodeConfig::start_network`(reth_node_core::NodeConfig) and
1556// `NetworkConfig::start_network`(reth_network::NetworkConfig)
1557impl<
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        // All streams are polled until their corresponding budget is exhausted, then we manually
1577        // yield back control to tokio. See `NetworkManager` for more context on the design
1578        // pattern.
1579
1580        // Advance network/peer related events (update peers map).
1581        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        // Advance incoming transaction events (stream new txns/announcements from
1591        // network manager and queue for import to pool/fetch txns).
1592        //
1593        // This will potentially remove hashes from hashes pending fetch, it the event
1594        // is an announcement (if same hashes are announced that didn't fit into a
1595        // previous request).
1596        //
1597        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1598        // (128 KiB / 10 bytes > 13k transactions).
1599        //
1600        // If this is an event with `Transactions` message, since transactions aren't
1601        // validated until they are inserted into the pool, this can potentially queue
1602        // >13k transactions for insertion to pool. More if the message size is bigger
1603        // than the soft limit on a `Transactions` broadcast message, which is 128 KiB.
1604        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        // Advance inflight fetch requests (flush transaction fetcher and queue for
1614        // import to pool).
1615        //
1616        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1617        // (2 MiB / 10 bytes > 200k transactions).
1618        //
1619        // Since transactions aren't validated until they are inserted into the pool,
1620        // this can potentially queue >200k transactions for insertion to pool. More
1621        // if the message size is bigger than the soft limit on a `PooledTransactions`
1622        // response which is 2 MiB.
1623        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        // Advance pool imports (flush txns to pool).
1633        //
1634        // Note, this is done in batches. A batch is filled from one `Transactions`
1635        // broadcast messages or one `PooledTransactions` response at a time. The
1636        // minimum batch size is 1 transaction (and might often be the case with blob
1637        // transactions).
1638        //
1639        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1640        // (2 MiB / 10 bytes > 200k transactions).
1641        //
1642        // Since transactions aren't validated until they are inserted into the pool,
1643        // this can potentially validate >200k transactions. More if the message size
1644        // is bigger than the soft limit on a `PooledTransactions` response which is
1645        // 2 MiB (`Transactions` broadcast messages is smaller, 128 KiB).
1646        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        // Advances new __pending__ transactions, transactions that were successfully inserted into
1656        // pending set in pool (are valid), and propagates them (inform peers which
1657        // transactions we have seen).
1658        //
1659        // This is polled after pool imports so transactions that became pending in this poll
1660        // iteration are propagated immediately, instead of waiting for the task to be woken
1661        // again.
1662        //
1663        // We try to drain this to batch the transactions in a single message.
1664        //
1665        // We don't expect this buffer to be large, since only pending transactions are
1666        // emitted here.
1667        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                    // we filled the entire buffer capacity and need to try again on the next poll
1676                    // immediately
1677                    true
1678                } else {
1679                    // try once more, because mostlikely the channel is now empty and the waker is
1680                    // registered if this is pending, if we filled additional hashes, we poll again
1681                    // on the next iteration
1682                    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        // Tries to drain hashes pending fetch cache if the tx manager currently has
1695        // capacity for this (fetch txns).
1696        //
1697        // Sends at most one request.
1698        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        // Advance commands (propagate/fetch/serve txns).
1710        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        // all channels are fully drained and import futures pending
1722        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            // make sure we're woken up again
1730            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/// Represents the different modes of transaction propagation.
1741///
1742/// This enum is used to determine how transactions are propagated to peers in the network.
1743#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1744enum PropagationMode {
1745    /// Default propagation mode.
1746    ///
1747    /// Transactions are only sent to peers that haven't seen them yet.
1748    Basic,
1749    /// Forced propagation mode.
1750    ///
1751    /// Transactions are sent to all peers regardless of whether they have been sent or received
1752    /// before.
1753    Forced,
1754}
1755
1756impl PropagationMode {
1757    /// Returns `true` if the propagation kind is `Forced`.
1758    const fn is_forced(self) -> bool {
1759        matches!(self, Self::Forced)
1760    }
1761}
1762
1763/// A transaction that's about to be propagated to multiple peers.
1764#[derive(Debug, Clone)]
1765struct PropagateTransaction {
1766    is_broadcastable_in_full: bool,
1767    /// Size advertised in `NewPooledTransactionHashes` metadata and used for full broadcast
1768    /// soft-limit accounting.
1769    ///
1770    /// This is the network encoded transaction size. For pool-backed blob transactions, this is
1771    /// the pool's cached encoded length, which includes the sidecar returned by
1772    /// `PooledTransactions`.
1773    propagation_size: usize,
1774    transaction: LazyEncodedTransaction,
1775}
1776
1777impl PropagateTransaction {
1778    /// Create a new instance from a transaction supplied directly for propagation.
1779    ///
1780    /// Direct transactions use their EIP-2718 encoded length so eth/68+ hash announcements carry
1781    /// the same size metadata as [`NewPooledTransactionHashes68::push`] and
1782    /// [`NewPooledTransactionHashes72::push`].
1783    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    /// Create a new instance from a pooled transaction.
1795    ///
1796    /// Pool transactions already cache the network encoded size used by txpool admission and
1797    /// pooled hash announcements. For blob transactions, this includes the sidecar size expected in
1798    /// a `PooledTransactions` response.
1799    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    /// Returns the network encoded size used for propagation limits and hash metadata.
1814    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/// A pooled transaction encoder that avoids cloning into the consensus transaction for propagation.
1832#[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/// Helper type to construct the appropriate message to send to the peer based on whether the peer
1870/// should receive them in full or as pooled
1871#[derive(Debug, Clone)]
1872enum PropagateTransactionsBuilder {
1873    Pooled(PooledTransactionsHashesBuilder),
1874    Full(FullTransactionsBuilder),
1875}
1876
1877impl PropagateTransactionsBuilder {
1878    /// Create a builder for pooled transactions with capacity for the expected number of
1879    /// transactions.
1880    fn pooled(version: EthVersion, capacity: usize) -> Self {
1881        Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1882    }
1883
1884    /// Create a builder that sends transactions in full and records transactions that don't fit,
1885    /// with capacity for the expected number of transactions.
1886    fn full(version: EthVersion, capacity: usize) -> Self {
1887        Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1888    }
1889
1890    /// Returns true if no transactions are recorded.
1891    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    /// Consumes the type and returns the built messages that should be sent to the peer.
1899    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    /// Appends a transaction to the list.
1911    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
1919/// Represents how the transactions should be sent to a peer if any.
1920struct PropagateTransactions {
1921    /// The pooled transaction hashes to send.
1922    pooled: Option<NewPooledTransactionHashes>,
1923    /// The transactions to send in full.
1924    full: Option<BroadcastPoolTransactions>,
1925}
1926
1927/// Helper type for constructing the full transaction message that enforces the
1928/// [`DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE`] for full transaction broadcast
1929/// and enforces other propagation rules for EIP-4844 and tracks those transactions that can't be
1930/// broadcasted in full.
1931#[derive(Debug, Clone)]
1932struct FullTransactionsBuilder {
1933    /// The soft limit to enforce for a single broadcast message of full transactions.
1934    total_size: usize,
1935    /// All transactions to be broadcasted.
1936    transactions: Vec<LazyEncodedTransaction>,
1937    /// Transactions that didn't fit into the broadcast message
1938    pooled: PooledTransactionsHashesBuilder,
1939}
1940
1941impl FullTransactionsBuilder {
1942    /// Create a builder for the negotiated version of the peer's session
1943    fn new(version: EthVersion) -> Self {
1944        Self {
1945            total_size: 0,
1946            pooled: PooledTransactionsHashesBuilder::new(version),
1947            transactions: vec![],
1948        }
1949    }
1950
1951    /// Create a builder with capacity for the expected number of full transactions.
1952    ///
1953    /// The overflow hashes builder remains lazily allocated since most transactions are expected
1954    /// to be broadcast in full.
1955    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    /// Returns whether or not any transactions are in the [`FullTransactionsBuilder`].
1964    fn is_empty(&self) -> bool {
1965        self.transactions.is_empty() && self.pooled.is_empty()
1966    }
1967
1968    /// Returns the messages that should be propagated to the peer.
1969    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    /// Appends all transactions.
1977    fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
1978        for tx in txs {
1979            self.push(&tx)
1980        }
1981    }
1982
1983    /// Append a transaction to the list of full transaction if the total message bytes size doesn't
1984    /// exceed the soft maximum target byte size. The limit is soft, meaning if one single
1985    /// transaction goes over the limit, it will be broadcasted in its own [`Transactions`]
1986    /// message. The same pattern is followed in filling a [`GetPooledTransactions`] request in
1987    /// [`TransactionFetcher::fill_request_from_hashes_pending_fetch`].
1988    ///
1989    /// If the transaction is unsuitable for broadcast or would exceed the softlimit, it is appended
1990    /// to list of pooled transactions, (e.g. 4844 transactions).
1991    /// See also [`SignedTransaction::is_broadcastable_in_full`].
1992    fn push(&mut self, transaction: &PropagateTransaction) {
1993        // Do not send full 4844 transaction hashes to peers.
1994        //
1995        //  Nodes MUST NOT automatically broadcast blob transactions to their peers.
1996        //  Instead, those transactions are only announced using
1997        //  `NewPooledTransactionHashes` messages, and can then be manually requested
1998        //  via `GetPooledTransactions`.
1999        //
2000        // From: <https://eips.ethereum.org/EIPS/eip-4844#networking>
2001        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            // transaction does not fit into the message
2011            self.pooled.push(transaction);
2012            return
2013        }
2014
2015        self.total_size = new_size;
2016        self.transactions.push(transaction.shared());
2017    }
2018}
2019
2020/// A helper type to create the pooled transactions message based on the negotiated version of the
2021/// session with the peer
2022#[derive(Debug, Clone)]
2023enum PooledTransactionsHashesBuilder {
2024    Eth66(NewPooledTransactionHashes66),
2025    Eth68(NewPooledTransactionHashes68),
2026    Eth72(NewPooledTransactionHashes72),
2027}
2028
2029// === impl PooledTransactionsHashesBuilder ===
2030
2031impl PooledTransactionsHashesBuilder {
2032    /// Push a transaction from the pool to the list.
2033    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    /// Returns whether or not any transactions are in the [`PooledTransactionsHashesBuilder`].
2050    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    /// Returns the number of transactions in the builder.
2059    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    /// Appends all hashes
2068    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    /// Create a builder for the negotiated version of the peer's session
2091    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    /// Create a builder for the negotiated version of the peer's session with capacity for the
2102    /// expected number of hashes.
2103    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
2133/// How we received the transactions.
2134enum TransactionSource {
2135    /// Transactions were broadcast to us via [`Transactions`] message.
2136    Broadcast,
2137    /// Transactions were sent as the response of [`fetcher::GetPooledTxRequest`] issued by us.
2138    Response,
2139}
2140
2141// === impl TransactionSource ===
2142
2143impl TransactionSource {
2144    /// Whether the transaction were sent as broadcast.
2145    const fn is_broadcast(&self) -> bool {
2146        matches!(self, Self::Broadcast)
2147    }
2148}
2149
2150/// Tracks a single peer in the context of [`TransactionsManager`].
2151#[derive(Debug)]
2152pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2153    /// Optimistically keeps track of transactions that we know the peer has seen. Optimistic, in
2154    /// the sense that transactions are preemptively marked as seen by peer when they are sent to
2155    /// the peer.
2156    seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2157    /// A communication channel directly to the peer's session task.
2158    request_tx: PeerRequestSender<PeerRequest<N>>,
2159    /// negotiated version of the session.
2160    version: EthVersion,
2161    /// The peer's client version.
2162    client_version: Arc<str>,
2163    /// The kind of peer.
2164    peer_kind: PeerKind,
2165}
2166
2167impl<N: NetworkPrimitives> PeerMetadata<N> {
2168    /// Returns a new instance of [`PeerMetadata`].
2169    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    /// Returns a reference to the peer's request sender channel.
2189    pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2190        &self.request_tx
2191    }
2192
2193    /// Returns a mutable reference to the seen transactions LRU cache.
2194    pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2195        &mut self.seen_transactions
2196    }
2197
2198    /// Returns the negotiated `EthVersion` of the session.
2199    pub const fn version(&self) -> EthVersion {
2200        self.version
2201    }
2202
2203    /// Returns a reference to the peer's client version string.
2204    pub fn client_version(&self) -> &str {
2205        &self.client_version
2206    }
2207
2208    /// Returns the peer's kind.
2209    pub const fn peer_kind(&self) -> PeerKind {
2210        self.peer_kind
2211    }
2212}
2213
2214/// Commands to send to the [`TransactionsManager`]
2215#[derive(Debug)]
2216enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2217    /// Propagate a transaction hash to the network.
2218    PropagateHash(B256),
2219    /// Propagate transaction hashes to a specific peer.
2220    PropagateHashesTo(Vec<B256>, PeerId),
2221    /// Request the list of active peer IDs from the [`TransactionsManager`].
2222    GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2223    /// Propagate a collection of full transactions to a specific peer.
2224    PropagateTransactionsTo(Vec<TxHash>, PeerId),
2225    /// Propagate a collection of hashes to all peers.
2226    PropagateTransactions(Vec<TxHash>),
2227    /// Propagate a collection of broadcastable transactions in full to all peers.
2228    BroadcastTransactions(Vec<PropagateTransaction>),
2229    /// Request transaction hashes known by specific peers from the [`TransactionsManager`].
2230    GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2231    /// Requests a clone of the sender channel to the peer.
2232    GetPeerSender {
2233        peer_id: PeerId,
2234        peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2235    },
2236}
2237
2238/// All events related to transactions emitted by the network.
2239#[derive(Debug)]
2240pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2241    /// Represents the event of receiving a list of transactions from a peer.
2242    ///
2243    /// This indicates transactions that were broadcasted to us from the peer.
2244    IncomingTransactions {
2245        /// The ID of the peer from which the transactions were received.
2246        peer_id: PeerId,
2247        /// The received transactions.
2248        msg: Transactions<N::BroadcastedTransaction>,
2249    },
2250    /// Represents the event of receiving a list of transaction hashes from a peer.
2251    IncomingPooledTransactionHashes {
2252        /// The ID of the peer from which the transaction hashes were received.
2253        peer_id: PeerId,
2254        /// The received new pooled transaction hashes.
2255        msg: NewPooledTransactionHashes,
2256    },
2257    /// Represents the event of receiving a `GetPooledTransactions` request from a peer.
2258    GetPooledTransactions {
2259        /// The ID of the peer from which the request was received.
2260        peer_id: PeerId,
2261        /// The received `GetPooledTransactions` request.
2262        request: GetPooledTransactions,
2263        /// The sender for responding to the request with a result of `PooledTransactions`.
2264        response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2265    },
2266    /// Represents the event of receiving a `GetTransactionsHandle` request.
2267    GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2268}
2269
2270/// Tracks stats about the [`TransactionsManager`].
2271#[derive(Debug)]
2272pub struct PendingPoolImportsInfo {
2273    /// Number of transactions about to be inserted into the pool.
2274    pending_pool_imports: Arc<AtomicUsize>,
2275    /// Max number of transactions allowed to be imported concurrently.
2276    max_pending_pool_imports: usize,
2277}
2278
2279impl PendingPoolImportsInfo {
2280    /// Returns a new [`PendingPoolImportsInfo`].
2281    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    /// Returns `true` if the number of pool imports is under a given tolerated max.
2286    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    // `N::BroadcastedTransaction` and `N::PooledTransaction` already implement
2310    // `InMemorySize` via `SignedTransaction: InMemorySize`, so no extra bound is needed.
2311    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        // go to syncing (pipeline sync)
2470        network_handle.update_sync_state(SyncState::Syncing);
2471        assert!(NetworkInfo::is_syncing(&network_handle));
2472        assert!(NetworkInfo::is_initially_syncing(&network_handle));
2473
2474        // wait for all initiator connections
2475        let mut established = listener0.take(2);
2476        while let Some(ev) = established.next().await {
2477            match ev {
2478                NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2479                    // to insert a new peer in transactions peerset
2480                    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        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2490        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        // go to syncing (pipeline sync) to idle and then to syncing (live)
2540        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        // wait for all initiator connections
2548        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                    // to insert a new peer in transactions peerset
2554                    transactions.on_network_event(ev);
2555                }
2556                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2557                _ => {
2558                    error!("unexpected event {ev:?}")
2559                }
2560            }
2561        }
2562        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2563        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    // Ensure that the transaction manager correctly handles the `IncomingPooledTransactionHashes`
2583    // event and is able to retrieve the corresponding transactions.
2584    #[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            // let OS choose port
2593            .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        // mock session of peer_1 receives request
2648        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        // return the transactions corresponding to the transaction hashes.
2664        response
2665            .send(Ok(PooledTransactions(message)))
2666            .expect("should send peer_1 response to tx manager");
2667
2668        // adance the transaction manager future
2669        poll_fn(|cx| {
2670            let _ = tx_manager.poll_unpin(cx);
2671            Poll::Ready(())
2672        })
2673        .await;
2674
2675        // ensure that the transactions corresponding to the transaction hashes have been
2676        // successfully retrieved and stored in the Pool.
2677        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        // wait for all initiator connections
2717        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                    // to insert a new peer in transactions peerset
2723                    transactions.on_network_event(ev);
2724                }
2725                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2726                ev => {
2727                    error!("unexpected event {ev:?}")
2728                }
2729            }
2730        }
2731        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2732        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        // advance the transaction manager future
2747        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        // peer removed from peers map and active_peers
2789        assert!(!tx_manager.peers.contains_key(&peer_id));
2790        assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2791        // fallback peer is still available for the hash
2792        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        // wait for all initiator connections
2896        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    // Ensure that when the remote peer only returns part of the requested transactions, the
2939    // replied transactions are removed from the `tx_fetcher`, while the unresponsive ones are
2940    // re-buffered.
2941    #[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        // mark hashes as seen by peer so it can fish them out from the cache for hashes pending
3004        // fetch
3005        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        // peer_1 is idle
3013        assert!(tx_fetcher.is_idle(&peer_id_1));
3014        assert_eq!(tx_fetcher.active_peers.len(), 0);
3015
3016        // sends requests for buffered hashes to peer_1
3017        tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3018
3019        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3020        // as long as request is in flight peer_1 is not idle
3021        assert!(!tx_fetcher.is_idle(&peer_id_1));
3022        assert_eq!(tx_fetcher.active_peers.len(), 1);
3023
3024        // mock session of peer_1 receives request
3025        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 partial request
3040        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        // request has resolved, peer_1 is idle again
3048        assert!(tx_fetcher.is_idle(&peer_id));
3049        assert_eq!(tx_fetcher.active_peers.len(), 0);
3050        // failing peer_1's request buffers requested hashes for retry.
3051        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        // mark hashes as seen by peer so it can fish them out from the cache for hashes pending
3068        // fetch
3069        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        // hashes are seen and currently not inflight, with one fallback peer, and are buffered
3074        // for first retry in reverse order to make index 0 lru
3075        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        // peer_1 is idle
3080        assert!(tx_fetcher.is_idle(&peer_id_1));
3081        assert_eq!(tx_fetcher.active_peers.len(), 0);
3082
3083        // sends request for buffered hashes to peer_1
3084        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        // as long as request is in inflight peer_1 is not idle
3090        assert!(!tx_fetcher.is_idle(&peer_id_1));
3091        assert_eq!(tx_fetcher.active_peers.len(), 1);
3092
3093        // mock session of peer_1 receives request
3094        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        // fail request to peer_1
3106        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        // request has resolved, peer_1 is idle again
3114        assert!(tx_fetcher.is_idle(&peer_id));
3115        assert_eq!(tx_fetcher.active_peers.len(), 0);
3116        // failing peer_1's request buffers requested hashes for retry
3117        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        // peer_2 announces same hashes as peer_1
3123        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        // peer_2 should be in active_peers.
3130        assert_eq!(tx_fetcher.active_peers.len(), 1);
3131
3132        // since hashes are already seen, no changes to length of unknown hashes
3133        assert_eq!(tx_fetcher.num_all_hashes(), 2);
3134        // but hashes are taken out of buffer and packed into request to peer_2
3135        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3136
3137        // mock session of peer_2 receives request
3138        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        // report failed request to tx manager
3145        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        // `MAX_REQUEST_RETRIES_PER_TX_HASH`, 2, for hashes reached so this time won't be buffered
3151        // for retry
3152        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        // create a transaction that still fits
3215        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        // ensure not syncing
3269        network.handle().update_sync_state(SyncState::Idle);
3270
3271        // mock a peer
3272        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        // propagate again
3312        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        // all peers receive hash announcements only
3325        tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3326
3327        // ensure not syncing
3328        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        // one more transaction than fits into a single hashes broadcast message
3335        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        // the truncated hash was not sent, so it must not be tracked as seen by the peer
3350        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        // Keep the node in initial sync mode.
3364        network.handle().update_sync_state(SyncState::Syncing);
3365        assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3366
3367        // Add a peer so propagation has a destination.
3368        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}