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_evm::SenderRecoveryCache;
59use reth_metrics::common::mpsc::MemoryBoundedReceiver;
60use reth_network_api::{
61    events::{PeerEvent, SessionInfo},
62    NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
63};
64use reth_network_p2p::{
65    error::{RequestError, RequestResult},
66    sync::SyncStateProvider,
67};
68use reth_network_peers::PeerId;
69use reth_network_types::ReputationChangeKind;
70use reth_primitives_traits::{InMemorySize, SignedTransaction};
71use reth_tokio_util::EventStream;
72use reth_transaction_pool::{
73    error::{PoolError, PoolResult},
74    AddedTransactionOutcome, GetPooledTransactionLimit, PoolTransaction, PropagateKind,
75    PropagatedTransactions, TransactionPool, ValidPoolTransaction,
76};
77use std::{
78    pin::Pin,
79    sync::{
80        atomic::{AtomicUsize, Ordering},
81        Arc,
82    },
83    task::{Context, Poll},
84    time::{Duration, Instant},
85};
86use tokio::sync::{mpsc, oneshot, oneshot::error::RecvError};
87use tokio_stream::wrappers::UnboundedReceiverStream;
88use tracing::{debug, trace};
89
90/// The future for importing transactions into the pool.
91///
92/// Resolves with the result of each transaction import.
93pub type PoolImportFuture =
94    Pin<Box<dyn Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send + 'static>>;
95
96/// Api to interact with [`TransactionsManager`] task.
97///
98/// This can be obtained via [`TransactionsManager::handle`] and can be used to manually interact
99/// with the [`TransactionsManager`] task once it is spawned.
100///
101/// For example [`TransactionsHandle::get_peer_transaction_hashes`] returns the transaction hashes
102/// known by a specific peer.
103#[derive(Debug, Clone)]
104pub struct TransactionsHandle<N: NetworkPrimitives = EthNetworkPrimitives> {
105    /// Command channel to the [`TransactionsManager`]
106    manager_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
107}
108
109impl<N: NetworkPrimitives> TransactionsHandle<N> {
110    fn send(&self, cmd: TransactionsCommand<N>) {
111        let _ = self.manager_tx.send(cmd);
112    }
113
114    /// Fetch the [`PeerRequestSender`] for the given peer.
115    async fn peer_handle(
116        &self,
117        peer_id: PeerId,
118    ) -> Result<Option<PeerRequestSender<PeerRequest<N>>>, RecvError> {
119        let (tx, rx) = oneshot::channel();
120        self.send(TransactionsCommand::GetPeerSender { peer_id, peer_request_sender: tx });
121        rx.await
122    }
123
124    /// Manually propagate the transaction that belongs to the hash.
125    pub fn propagate(&self, hash: TxHash) {
126        self.send(TransactionsCommand::PropagateHash(hash))
127    }
128
129    /// Manually propagate the transaction hash to a specific peer.
130    ///
131    /// Note: this only propagates if the pool contains the transaction.
132    pub fn propagate_hash_to(&self, hash: TxHash, peer: PeerId) {
133        self.propagate_hashes_to(Some(hash), peer)
134    }
135
136    /// Manually propagate the transaction hashes to a specific peer.
137    ///
138    /// Note: this only propagates the transactions that are known to the pool.
139    pub fn propagate_hashes_to(&self, hash: impl IntoIterator<Item = TxHash>, peer: PeerId) {
140        let hashes = hash.into_iter().collect::<Vec<_>>();
141        if hashes.is_empty() {
142            return
143        }
144        self.send(TransactionsCommand::PropagateHashesTo(hashes, peer))
145    }
146
147    /// Request the active peer IDs from the [`TransactionsManager`].
148    pub async fn get_active_peers(&self) -> Result<HashSet<PeerId>, RecvError> {
149        let (tx, rx) = oneshot::channel();
150        self.send(TransactionsCommand::GetActivePeers(tx));
151        rx.await
152    }
153
154    /// Manually propagate full transaction hashes to a specific peer.
155    ///
156    /// Do nothing if transactions are empty.
157    pub fn propagate_transactions_to(&self, transactions: Vec<TxHash>, peer: PeerId) {
158        if transactions.is_empty() {
159            return
160        }
161        self.send(TransactionsCommand::PropagateTransactionsTo(transactions, peer))
162    }
163
164    /// Manually propagate the given transaction hashes to all peers.
165    ///
166    /// It's up to the [`TransactionsManager`] whether the transactions are sent as hashes or in
167    /// full.
168    pub fn propagate_transactions(&self, transactions: Vec<TxHash>) {
169        if transactions.is_empty() {
170            return
171        }
172        self.send(TransactionsCommand::PropagateTransactions(transactions))
173    }
174
175    /// Manually propagate the given transactions to all peers.
176    ///
177    /// It's up to the [`TransactionsManager`] whether the transactions are sent as hashes or in
178    /// full.
179    pub fn broadcast_transactions(
180        &self,
181        transactions: impl IntoIterator<Item = N::BroadcastedTransaction>,
182    ) {
183        let transactions =
184            transactions.into_iter().map(PropagateTransaction::new).collect::<Vec<_>>();
185        if transactions.is_empty() {
186            return
187        }
188        self.send(TransactionsCommand::BroadcastTransactions(transactions))
189    }
190
191    /// Request the transaction hashes known by specific peers.
192    pub async fn get_transaction_hashes(
193        &self,
194        peers: Vec<PeerId>,
195    ) -> Result<HashMap<PeerId, B256Set>, RecvError> {
196        if peers.is_empty() {
197            return Ok(Default::default())
198        }
199        let (tx, rx) = oneshot::channel();
200        self.send(TransactionsCommand::GetTransactionHashes { peers, tx });
201        rx.await
202    }
203
204    /// Request the transaction hashes known by a specific peer.
205    pub async fn get_peer_transaction_hashes(&self, peer: PeerId) -> Result<B256Set, RecvError> {
206        let res = self.get_transaction_hashes(vec![peer]).await?;
207        Ok(res.into_values().next().unwrap_or_default())
208    }
209
210    /// Requests the transactions directly from the given peer.
211    ///
212    /// Returns `None` if the peer is not connected.
213    ///
214    /// **Note**: this returns the response from the peer as received.
215    pub async fn get_pooled_transactions_from(
216        &self,
217        peer_id: PeerId,
218        hashes: Vec<B256>,
219    ) -> Result<Option<Vec<N::PooledTransaction>>, RequestError> {
220        let Some(peer) = self.peer_handle(peer_id).await? else { return Ok(None) };
221
222        let (tx, rx) = oneshot::channel();
223        let request = PeerRequest::GetPooledTransactions { request: hashes.into(), response: tx };
224        peer.try_send(request).ok();
225
226        rx.await?.map(|res| Some(res.0))
227    }
228}
229
230/// Manages transactions on top of the p2p network.
231///
232/// This can be spawned to another task and is supposed to be run as background service.
233/// [`TransactionsHandle`] can be used as frontend to programmatically send commands to it and
234/// interact with it.
235///
236/// The [`TransactionsManager`] is responsible for:
237///    - handling incoming eth messages for transactions.
238///    - serving transaction requests.
239///    - propagate transactions
240///
241/// This type communicates with the [`NetworkManager`](crate::NetworkManager) in both directions.
242///   - receives incoming network messages.
243///   - sends messages to dispatch (responses, propagate tx)
244///
245/// It is directly connected to the [`TransactionPool`] to retrieve requested transactions and
246/// propagate new transactions over the network.
247///
248/// It can be configured with different policies for transaction propagation and announcement
249/// filtering. See [`NetworkPolicies`] for more details.
250///
251/// ## Network Transaction Processing
252///
253/// ### Message Types
254///
255/// - **`Transactions`**: Full transaction broadcasts (rejects blob transactions)
256/// - **`NewPooledTransactionHashes`**: Hash announcements
257///
258/// ### Peer Tracking
259///
260/// - Maintains per-peer transaction cache (default: 10,240 entries)
261/// - Prevents duplicate imports and enables efficient propagation
262///
263/// ### Bad Transaction Handling
264///
265/// Caches and rejects transactions with consensus violations (gas, signature, chain ID).
266/// Penalizes peers sending invalid transactions.
267///
268/// ### Import Management
269///
270/// Limits concurrent pool imports and backs off when approaching capacity.
271///
272/// ### Transaction Fetching
273///
274/// For announced transactions: filters known → queues unknown → fetches → imports
275///
276/// ### Propagation Rules
277///
278/// Based on: origin (Local/External/Private), peer capabilities, and network state.
279/// Disabled during initial sync.
280///
281/// ### Security
282///
283/// Rate limiting via reputation, bad transaction isolation, peer scoring.
284#[derive(Debug)]
285#[must_use = "Manager does nothing unless polled."]
286pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
287    /// Access to the transaction pool.
288    pool: Pool,
289    /// Cache of recovered transaction senders shared with payload execution, if enabled.
290    sender_recovery_cache: Option<SenderRecoveryCache>,
291    /// Network access.
292    network: NetworkHandle<N>,
293    /// Subscriptions to all network related events.
294    ///
295    /// From which we get all new incoming transaction related messages.
296    network_events: EventStream<NetworkEvent<PeerRequest<N>>>,
297    /// Transaction fetcher to handle inflight and missing transaction requests.
298    transaction_fetcher: TransactionFetcher<N>,
299    /// All currently pending transactions grouped by peers.
300    ///
301    /// This way we can track incoming transactions and prevent multiple pool imports for the same
302    /// transaction
303    transactions_by_peers: B256Map<SmallVec<[PeerId; 1]>>,
304    /// Transactions that are currently imported into the `Pool`.
305    ///
306    /// The import process includes:
307    ///  - validation of the transactions, e.g. transaction is well formed: valid tx type, fees are
308    ///    valid, or for 4844 transaction the blobs are valid. See also
309    ///    [`EthTransactionValidator`](reth_transaction_pool::validate::EthTransactionValidator)
310    /// - if the transaction is valid, it is added into the pool.
311    ///
312    /// Once the new transaction reaches the __pending__ state it will be emitted by the pool via
313    /// [`TransactionPool::pending_transactions_listener`] and arrive at the `pending_transactions`
314    /// receiver.
315    pool_imports: FuturesUnordered<PoolImportFuture>,
316    /// Stats on pending pool imports that help the node self-monitor.
317    pending_pool_imports_info: PendingPoolImportsInfo,
318    /// Bad imports.
319    bad_imports: LruCache<TxHash, FbBuildHasher<32>>,
320    /// All the connected peers.
321    peers: HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
322    /// Send half for the command channel.
323    ///
324    /// This is kept so that a new [`TransactionsHandle`] can be created at any time.
325    command_tx: mpsc::UnboundedSender<TransactionsCommand<N>>,
326    /// Incoming commands from [`TransactionsHandle`].
327    ///
328    /// This will only receive commands if a user manually sends a command to the manager through
329    /// the [`TransactionsHandle`] to interact with this type directly.
330    command_rx: UnboundedReceiverStream<TransactionsCommand<N>>,
331    /// A stream that yields new __pending__ transactions.
332    ///
333    /// A transaction is considered __pending__ if it is executable on the current state of the
334    /// chain. In other words, this only yields transactions that satisfy all consensus
335    /// requirements, these include:
336    ///   - no nonce gaps
337    ///   - all dynamic fee requirements are (currently) met
338    ///   - account has enough balance to cover the transaction's gas
339    pending_transactions: mpsc::Receiver<TxHash>,
340    /// Incoming events from the [`NetworkManager`](crate::NetworkManager).
341    transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
342    /// How the `TransactionsManager` is configured.
343    config: TransactionsManagerConfig,
344    /// Network Policies
345    policies: NetworkPolicies<N>,
346    /// `TransactionsManager` metrics
347    metrics: TransactionsManagerMetrics,
348    /// `AnnouncedTxTypes` metrics
349    announced_tx_types_metrics: AnnouncedTxTypesMetrics,
350}
351
352impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
353    /// Sets up a new instance.
354    ///
355    /// Note: This expects an existing [`NetworkManager`](crate::NetworkManager) instance.
356    pub fn new(
357        network: NetworkHandle<N>,
358        pool: Pool,
359        from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
360        transactions_manager_config: TransactionsManagerConfig,
361    ) -> Self {
362        Self::with_policy(
363            network,
364            pool,
365            from_network,
366            transactions_manager_config,
367            NetworkPolicies::new(
368                TransactionPropagationKind::default(),
369                StrictEthAnnouncementFilter::default(),
370            ),
371        )
372    }
373}
374
375impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
376    /// Sets up a new instance with given the settings.
377    ///
378    /// Note: This expects an existing [`NetworkManager`](crate::NetworkManager) instance.
379    pub fn with_policy(
380        network: NetworkHandle<N>,
381        pool: Pool,
382        from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
383        transactions_manager_config: TransactionsManagerConfig,
384        policies: NetworkPolicies<N>,
385    ) -> Self {
386        let network_events = network.event_listener();
387
388        let (command_tx, command_rx) = mpsc::unbounded_channel();
389
390        let transaction_fetcher = TransactionFetcher::with_transaction_fetcher_config(
391            &transactions_manager_config.transaction_fetcher_config,
392        );
393
394        // install a listener for new __pending__ transactions that are allowed to be propagated
395        // over the network
396        let pending = pool.pending_transactions_listener();
397        let pending_pool_imports_info =
398            PendingPoolImportsInfo::new(transactions_manager_config.max_pending_pool_imports);
399        let metrics = TransactionsManagerMetrics::default();
400        metrics
401            .capacity_pending_pool_imports
402            .increment(pending_pool_imports_info.max_pending_pool_imports as u64);
403
404        Self {
405            pool,
406            sender_recovery_cache: None,
407            network,
408            network_events,
409            transaction_fetcher,
410            transactions_by_peers: Default::default(),
411            pool_imports: Default::default(),
412            pending_pool_imports_info,
413            bad_imports: LruCache::with_hasher(DEFAULT_MAX_COUNT_BAD_IMPORTS, Default::default()),
414            peers: Default::default(),
415            command_tx,
416            command_rx: UnboundedReceiverStream::new(command_rx),
417            pending_transactions: pending,
418            transaction_events: from_network,
419            config: transactions_manager_config,
420            policies,
421            metrics,
422            announced_tx_types_metrics: AnnouncedTxTypesMetrics::default(),
423        }
424    }
425
426    /// Returns a new handle that can send commands to this type.
427    pub fn handle(&self) -> TransactionsHandle<N> {
428        TransactionsHandle { manager_tx: self.command_tx.clone() }
429    }
430
431    /// Uses the provided sender recovery cache.
432    pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
433        self.sender_recovery_cache = Some(cache);
434        self
435    }
436
437    /// Returns `true` if [`TransactionsManager`] has capacity to request pending hashes. Returns
438    /// `false` if [`TransactionsManager`] is operating close to full capacity.
439    fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
440        self.has_capacity_for_pending_pool_imports() &&
441            self.transaction_fetcher.has_capacity_for_fetching_pending_hashes()
442    }
443
444    /// Returns `true` if [`TransactionsManager`] has capacity for more pending pool imports.
445    fn has_capacity_for_pending_pool_imports(&self) -> bool {
446        self.remaining_pool_import_capacity() > 0
447    }
448
449    /// Returns the remaining capacity for pending pool imports.
450    fn remaining_pool_import_capacity(&self) -> usize {
451        self.pending_pool_imports_info.max_pending_pool_imports.saturating_sub(
452            self.pending_pool_imports_info.pending_pool_imports.load(Ordering::Relaxed),
453        )
454    }
455
456    fn report_peer_bad_transactions(&self, peer_id: PeerId) {
457        self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
458        self.metrics.reported_bad_transactions.increment(1);
459    }
460
461    fn report_peer(&self, peer_id: PeerId, kind: ReputationChangeKind) {
462        trace!(target: "net::tx", ?peer_id, ?kind, "reporting reputation change");
463        self.network.reputation_change(peer_id, kind);
464    }
465
466    fn report_already_seen(&self, peer_id: PeerId) {
467        trace!(target: "net::tx", ?peer_id, "Penalizing peer for already seen transaction");
468        self.network.reputation_change(peer_id, ReputationChangeKind::AlreadySeenTransaction);
469    }
470
471    /// Handles a closed peer session, removing the peer from transaction-local tracking state.
472    fn on_peer_session_closed(&mut self, peer_id: &PeerId) {
473        if let Some(mut peer) = self.peers.remove(peer_id) {
474            self.policies.propagation_policy_mut().on_session_closed(&mut peer);
475        }
476        self.transaction_fetcher.remove_peer(peer_id);
477    }
478
479    /// Clear the transaction
480    fn on_good_import(&mut self, hash: TxHash) {
481        self.transactions_by_peers.remove(&hash);
482    }
483
484    /// Handles a failed transaction import.
485    ///
486    /// Blob sidecar errors (e.g. invalid proof, missing sidecar) are penalized via
487    /// `report_peer_bad_transactions` but NOT cached in `bad_imports` — the transaction itself
488    /// may be valid when fetched from another peer with correct sidecar data.
489    ///
490    /// Other bad transactions are penalized and cached in `bad_imports` to avoid fetching or
491    /// importing them again.
492    ///
493    /// Errors that count as bad transactions are:
494    ///
495    /// - intrinsic gas too low
496    /// - exceeds gas limit
497    /// - gas uint overflow
498    /// - exceeds max init code size
499    /// - oversized data
500    /// - signer account has bytecode
501    /// - chain id mismatch
502    /// - old legacy chain id
503    /// - tx type not supported
504    ///
505    /// (and additionally for blobs txns...)
506    ///
507    /// - no blobs
508    /// - too many blobs
509    /// - invalid kzg proof
510    /// - kzg error
511    /// - not blob transaction (tx type mismatch)
512    /// - wrong versioned kzg commitment hash
513    fn on_bad_import(&mut self, err: PoolError) {
514        let peers = self.transactions_by_peers.remove(&err.hash);
515
516        if err.is_bad_blob_sidecar() {
517            // Blob sidecar errors: penalize but do NOT cache the hash as bad.
518            // The transaction may be valid — only the sidecar from this peer was wrong.
519            // Using regular penalties means repeated offenders still get disconnected.
520            if let Some(peers) = peers {
521                for peer_id in peers {
522                    self.report_peer_bad_transactions(peer_id);
523                }
524            }
525            return
526        }
527
528        // if we're _currently_ syncing, we ignore a bad transaction
529        if !err.is_bad_transaction() || self.network.is_syncing() {
530            return
531        }
532        // otherwise we penalize the peer that sent the bad transaction, with the assumption that
533        // the peer should have known that this transaction is bad (e.g. violating consensus rules)
534        if let Some(peers) = peers {
535            for peer_id in peers {
536                self.report_peer_bad_transactions(peer_id);
537            }
538        }
539        self.metrics.bad_imports.increment(1);
540        self.bad_imports.insert(err.hash);
541    }
542
543    /// Runs an operation to fetch hashes that are cached in [`TransactionFetcher`].
544    ///
545    /// Returns `true` if a request was sent.
546    fn on_fetch_hashes_pending_fetch(&mut self) -> bool {
547        // try drain transaction hashes pending fetch
548        let info = &self.pending_pool_imports_info;
549        let max_pending_pool_imports = info.max_pending_pool_imports;
550        let has_capacity_wrt_pending_pool_imports =
551            |divisor| info.has_capacity(max_pending_pool_imports / divisor);
552
553        self.transaction_fetcher
554            .on_fetch_pending_hashes(&self.peers, has_capacity_wrt_pending_pool_imports)
555    }
556
557    fn on_request_error(&self, peer_id: PeerId, req_err: RequestError) {
558        let kind = match req_err {
559            RequestError::UnsupportedCapability => ReputationChangeKind::BadProtocol,
560            RequestError::Timeout => ReputationChangeKind::Timeout,
561            RequestError::ChannelClosed | RequestError::ConnectionDropped => {
562                // peer is already disconnected
563                return
564            }
565            RequestError::BadResponse => return self.report_peer_bad_transactions(peer_id),
566        };
567        self.report_peer(peer_id, kind);
568    }
569
570    #[inline]
571    fn update_poll_metrics(&self, start: Instant, poll_durations: TxManagerPollDurations) {
572        let metrics = &self.metrics;
573
574        let TxManagerPollDurations {
575            acc_network_events,
576            acc_pending_imports,
577            acc_tx_events,
578            acc_imported_txns,
579            acc_fetch_events,
580            acc_pending_fetch,
581            acc_cmds,
582        } = poll_durations;
583
584        // update metrics for whole poll function
585        metrics.duration_poll_tx_manager.set(start.elapsed().as_secs_f64());
586        // update metrics for nested expressions
587        metrics.acc_duration_poll_network_events.set(acc_network_events.as_secs_f64());
588        metrics.acc_duration_poll_pending_pool_imports.set(acc_pending_imports.as_secs_f64());
589        metrics.acc_duration_poll_transaction_events.set(acc_tx_events.as_secs_f64());
590        metrics.acc_duration_poll_imported_transactions.set(acc_imported_txns.as_secs_f64());
591        metrics.acc_duration_poll_fetch_events.set(acc_fetch_events.as_secs_f64());
592        metrics.acc_duration_fetch_pending_hashes.set(acc_pending_fetch.as_secs_f64());
593        metrics.acc_duration_poll_commands.set(acc_cmds.as_secs_f64());
594    }
595}
596
597impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
598    /// Processes a batch import results.
599    fn on_batch_import_result(&mut self, batch_results: Vec<PoolResult<AddedTransactionOutcome>>) {
600        for res in batch_results {
601            match res {
602                Ok(AddedTransactionOutcome { hash, .. }) => {
603                    self.on_good_import(hash);
604                }
605                Err(err) => {
606                    self.on_bad_import(err);
607                }
608            }
609        }
610    }
611
612    /// Request handler for an incoming `NewPooledTransactionHashes`
613    fn on_new_pooled_transaction_hashes(
614        &mut self,
615        peer_id: PeerId,
616        msg: NewPooledTransactionHashes,
617    ) {
618        // If the node is initially syncing, ignore transactions
619        if self.network.is_initially_syncing() {
620            return
621        }
622        if self.network.tx_gossip_disabled() {
623            return
624        }
625
626        // get handle to peer's session, if the session is still active
627        let Some(peer) = self.peers.get_mut(&peer_id) else {
628            trace!(
629                peer_id = format!("{peer_id:#}"),
630                ?msg,
631                "discarding announcement from inactive peer"
632            );
633
634            return
635        };
636        let client = peer.client_version.clone();
637
638        // keep track of the transactions the peer knows
639        let mut count_txns_already_seen_by_peer = 0;
640        for tx in msg.iter_hashes().copied() {
641            if !peer.seen_transactions.insert(tx) {
642                count_txns_already_seen_by_peer += 1;
643            }
644        }
645        if count_txns_already_seen_by_peer > 0 {
646            // this may occur if transactions are sent or announced to a peer, at the same time as
647            // the peer sends/announces those hashes to us. this is because, marking
648            // txns as seen by a peer is done optimistically upon sending them to the
649            // peer.
650            self.metrics.messages_with_hashes_already_seen_by_peer.increment(1);
651            self.metrics
652                .occurrences_hash_already_seen_by_peer
653                .increment(count_txns_already_seen_by_peer);
654
655            trace!(target: "net::tx",
656                %count_txns_already_seen_by_peer,
657                peer_id=format!("{peer_id:#}"),
658                ?client,
659                "Peer sent hashes that have already been marked as seen by peer"
660            );
661
662            self.report_already_seen(peer_id);
663        }
664
665        // 1. filter out spam
666        if msg.is_empty() {
667            self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
668            return;
669        }
670
671        let original_len = msg.len();
672        let mut partially_valid_msg = msg.dedup();
673
674        if partially_valid_msg.len() != original_len {
675            self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
676        }
677
678        // 2. filter out transactions pending import to pool
679        partially_valid_msg.retain_by_hash(|hash| !self.transactions_by_peers.contains_key(hash));
680
681        // 3. filter out invalid entries (spam)
682        //
683        // validates messages with respect to the given network, e.g. allowed tx types.
684        // done before the pool lookup since these are cheap in-memory checks that shrink
685        // the set before acquiring the pool lock.
686        //
687        let mut should_report_peer = false;
688        let mut tx_types_counter = TxTypesCounter::default();
689
690        let has_eth68_metadata = partially_valid_msg
691            .msg_version()
692            .expect("partially valid announcement should have a version")
693            .has_eth68_metadata();
694
695        partially_valid_msg.retain(|tx_hash, metadata_ref_mut| {
696            let (ty_byte, size_val) = match *metadata_ref_mut {
697                Some((ty, size)) => {
698                    if !has_eth68_metadata {
699                        should_report_peer = true;
700                    }
701                    (ty, size)
702                }
703                None => {
704                    if has_eth68_metadata {
705                        should_report_peer = true;
706                        return false;
707                    }
708                    (0u8, 0)
709                }
710            };
711
712            if has_eth68_metadata && let Some((actual_ty_byte, _)) = *metadata_ref_mut {
713                match TxType::try_from(actual_ty_byte) {
714                    Ok(parsed_tx_type) => tx_types_counter.increase_by_tx_type(parsed_tx_type),
715                    Err(_) => tx_types_counter.increase_other(),
716                }
717            }
718
719            let decision = self
720                .policies
721                .announcement_filter()
722                .decide_on_announcement(ty_byte, tx_hash, size_val);
723
724            match decision {
725                AnnouncementAcceptance::Accept => true,
726                AnnouncementAcceptance::Ignore => false,
727                AnnouncementAcceptance::Reject { penalize_peer } => {
728                    if penalize_peer {
729                        should_report_peer = true;
730                    }
731                    false
732                }
733            }
734        });
735
736        if has_eth68_metadata {
737            self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
738        }
739
740        if should_report_peer {
741            self.report_peer(peer_id, ReputationChangeKind::BadAnnouncement);
742        }
743
744        // 4. filter out known hashes
745        //
746        // known txns have already been successfully fetched or received over gossip.
747        //
748        // most hashes will be filtered out here since the mempool protocol is a gossip
749        // protocol, healthy peers will send many of the same hashes.
750        //
751        let hashes_count_pre_pool_filter = partially_valid_msg.len();
752        self.pool.retain_unknown(&mut partially_valid_msg);
753        if hashes_count_pre_pool_filter > partially_valid_msg.len() {
754            let already_known_hashes_count =
755                hashes_count_pre_pool_filter - partially_valid_msg.len();
756            self.metrics
757                .occurrences_hashes_already_in_pool
758                .increment(already_known_hashes_count as u64);
759        }
760
761        if partially_valid_msg.is_empty() {
762            // nothing to request
763            return
764        }
765
766        let mut valid_announcement_data =
767            ValidAnnouncementData::from_partially_valid_data(partially_valid_msg);
768
769        if valid_announcement_data.is_empty() {
770            // no valid announcement data
771            return
772        }
773
774        // 5. filter out already seen unknown hashes
775        //
776        // seen hashes are already in the tx fetcher, pending fetch.
777        //
778        // for any seen hashes add the peer as fallback. unseen hashes are loaded into the tx
779        // fetcher, hence they should be valid at this point.
780        let bad_imports = &self.bad_imports;
781        self.transaction_fetcher.filter_unseen_and_pending_hashes(
782            &mut valid_announcement_data,
783            |hash| bad_imports.contains(hash),
784            &peer_id,
785            &client,
786        );
787
788        if valid_announcement_data.is_empty() {
789            // nothing to request
790            return
791        }
792
793        trace!(target: "net::tx::propagation",
794            peer_id=format!("{peer_id:#}"),
795            hashes_len=valid_announcement_data.len(),
796            hashes=?valid_announcement_data.keys(),
797            msg_version=%valid_announcement_data.msg_version(),
798            client_version=%client,
799            "received previously unseen and pending hashes in announcement from peer"
800        );
801
802        // only send request for hashes to idle peer, otherwise buffer hashes storing peer as
803        // fallback
804        if !self.transaction_fetcher.is_idle(&peer_id) {
805            // load message version before announcement data is destructed in packing
806            let msg_version = valid_announcement_data.msg_version();
807            let (hashes, _version) = valid_announcement_data.into_request_hashes();
808
809            trace!(target: "net::tx",
810                peer_id=format!("{peer_id:#}"),
811                hashes=?*hashes,
812                %msg_version,
813                %client,
814                "buffering hashes announced by busy peer"
815            );
816
817            self.transaction_fetcher.buffer_hashes(hashes, Some(peer_id));
818
819            return
820        }
821
822        let mut hashes_to_request =
823            RequestTxHashes::with_capacity(valid_announcement_data.len() / 4);
824        let surplus_hashes =
825            self.transaction_fetcher.pack_request(&mut hashes_to_request, valid_announcement_data);
826
827        if !surplus_hashes.is_empty() {
828            trace!(target: "net::tx",
829                peer_id=format!("{peer_id:#}"),
830                surplus_hashes=?*surplus_hashes,
831                %client,
832                "some hashes in announcement from peer didn't fit in `GetPooledTransactions` request, buffering surplus hashes"
833            );
834
835            self.transaction_fetcher.buffer_hashes(surplus_hashes, Some(peer_id));
836        }
837
838        trace!(target: "net::tx",
839            peer_id=format!("{peer_id:#}"),
840            hashes=?*hashes_to_request,
841            %client,
842            "sending hashes in `GetPooledTransactions` request to peer's session"
843        );
844
845        // request the missing transactions
846        //
847        // get handle to peer's session again, at this point we know it exists
848        let Some(peer) = self.peers.get_mut(&peer_id) else { return };
849        if let Some(failed_to_request_hashes) =
850            self.transaction_fetcher.request_transactions_from_peer(hashes_to_request, peer)
851        {
852            let conn_eth_version = peer.version;
853
854            trace!(target: "net::tx",
855                peer_id=format!("{peer_id:#}"),
856                failed_to_request_hashes=?*failed_to_request_hashes,
857                %conn_eth_version,
858                %client,
859                "sending `GetPooledTransactions` request to peer's session failed, buffering hashes"
860            );
861            self.transaction_fetcher.buffer_hashes(failed_to_request_hashes, Some(peer_id));
862        }
863    }
864}
865
866impl<Pool, N> TransactionsManager<Pool, N>
867where
868    Pool: TransactionPool + Unpin + 'static,
869    N: NetworkPrimitives<
870            BroadcastedTransaction: SignedTransaction,
871            PooledTransaction: SignedTransaction,
872        > + Unpin,
873    Pool::Transaction:
874        PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
875{
876    /// Invoked when transactions in the local mempool are considered __pending__.
877    ///
878    /// When a transaction in the local mempool is moved to the pending pool, we propagate them to
879    /// connected peers over network using the `Transactions` and `NewPooledTransactionHashes`
880    /// messages. The Transactions message relays complete transaction objects and is typically
881    /// sent to a small, random fraction of connected peers.
882    ///
883    /// All other peers receive a notification of the transaction hash and can request the
884    /// complete transaction object if it is unknown to them. The dissemination of complete
885    /// transactions to a fraction of peers usually ensures that all nodes receive the transaction
886    /// and won't need to request it.
887    fn on_new_pending_transactions(&mut self, hashes: Vec<TxHash>) {
888        // We intentionally do not gate this on initial sync.
889        // During initial sync we skip importing tx announcements from peers in
890        // `on_new_pooled_transaction_hashes`, so transactions reaching this path are local.
891        if self.network.tx_gossip_disabled() {
892            return
893        }
894
895        trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
896
897        self.propagate_all(hashes);
898    }
899
900    /// Propagate the full transactions to a specific peer.
901    ///
902    /// Returns the propagated transactions.
903    fn propagate_full_transactions_to_peer(
904        &mut self,
905        txs: Vec<TxHash>,
906        peer_id: PeerId,
907        propagation_mode: PropagationMode,
908    ) -> Option<PropagatedTransactions> {
909        let peer = self.peers.get_mut(&peer_id)?;
910        trace!(target: "net::tx", ?peer_id, "Propagating transactions to peer");
911        let mut propagated = PropagatedTransactions::default();
912
913        // filter all transactions unknown to the peer
914        let mut full_transactions = FullTransactionsBuilder::new(peer.version);
915
916        let to_propagate = self.pool.get_all(txs).into_iter().map(PropagateTransaction::pool_tx);
917
918        if propagation_mode.is_forced() {
919            // skip cache check if forced
920            full_transactions.extend(to_propagate);
921        } else {
922            // Iterate through the transactions to propagate and fill the hashes and full
923            // transaction
924            for tx in to_propagate {
925                if !peer.seen_transactions.contains(tx.tx_hash()) {
926                    // Only include if the peer hasn't seen the transaction
927                    full_transactions.push(&tx);
928                }
929            }
930        }
931
932        if full_transactions.is_empty() {
933            // nothing to propagate
934            return None
935        }
936
937        let PropagateTransactions { pooled, full } = full_transactions.build();
938
939        // send hashes if any
940        if let Some(new_pooled_hashes) = pooled {
941            for hash in new_pooled_hashes.iter_hashes().copied() {
942                propagated.record(hash, PropagateKind::Hash(peer_id));
943                // mark transaction as seen by peer
944                peer.seen_transactions.insert(hash);
945            }
946
947            // send hashes of transactions
948            self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
949        }
950
951        // send full transactions, if any
952        if let Some(new_full_transactions) = full {
953            for hash in new_full_transactions.iter_hashes() {
954                propagated.record(*hash, PropagateKind::Full(peer_id));
955                // mark transaction as seen by peer
956                peer.seen_transactions.insert(*hash);
957            }
958
959            // send full transactions
960            self.network.send_broadcast_pool_transactions(peer_id, new_full_transactions);
961        }
962
963        // Update propagated transactions metrics
964        self.metrics.propagated_transactions.increment(propagated.len() as u64);
965
966        Some(propagated)
967    }
968
969    /// Propagate the transaction hashes to the given peer
970    ///
971    /// Note: This will only send the hashes for transactions that exist in the pool.
972    fn propagate_hashes_to(
973        &mut self,
974        hashes: Vec<TxHash>,
975        peer_id: PeerId,
976        propagation_mode: PropagationMode,
977    ) {
978        trace!(target: "net::tx", "Start propagating transactions as hashes");
979
980        // This fetches a transactions from the pool, including the blob transactions, which are
981        // only ever sent as hashes.
982        let propagated = {
983            let Some(peer) = self.peers.get_mut(&peer_id) else {
984                // no such peer
985                return
986            };
987
988            let to_propagate =
989                self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx);
990
991            let mut propagated = PropagatedTransactions::default();
992
993            // check if transaction is known to peer
994            let mut hashes = PooledTransactionsHashesBuilder::new(peer.version);
995
996            if propagation_mode.is_forced() {
997                hashes.extend(to_propagate)
998            } else {
999                for tx in to_propagate {
1000                    if !peer.seen_transactions.contains(tx.tx_hash()) {
1001                        // Include if the peer hasn't seen it
1002                        hashes.push(&tx);
1003                    }
1004                }
1005            }
1006
1007            let new_pooled_hashes = hashes.build();
1008
1009            if new_pooled_hashes.is_empty() {
1010                // nothing to propagate
1011                return
1012            }
1013
1014            for hash in new_pooled_hashes.iter_hashes().copied() {
1015                propagated.record(hash, PropagateKind::Hash(peer_id));
1016                peer.seen_transactions.insert(hash);
1017            }
1018
1019            trace!(target: "net::tx::propagation", ?peer_id, ?new_pooled_hashes, "Propagating transactions to peer");
1020
1021            // send hashes of transactions
1022            self.network.send_transactions_hashes(peer_id, new_pooled_hashes);
1023
1024            // Update propagated transactions metrics
1025            self.metrics.propagated_transactions.increment(propagated.len() as u64);
1026
1027            propagated
1028        };
1029
1030        // notify pool so events get fired
1031        self.pool.on_propagated(propagated);
1032    }
1033
1034    /// Propagate the transactions to all connected peers either as full objects or hashes.
1035    ///
1036    /// The message for new pooled hashes depends on the negotiated version of the stream.
1037    /// See [`NewPooledTransactionHashes`]
1038    ///
1039    /// 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>.
1040    fn propagate_transactions(
1041        &mut self,
1042        to_propagate: Vec<PropagateTransaction>,
1043        propagation_mode: PropagationMode,
1044    ) -> PropagatedTransactions {
1045        let mut propagated = PropagatedTransactions::default();
1046        if self.network.tx_gossip_disabled() {
1047            return propagated
1048        }
1049
1050        // send full transactions to a set of the connected peers based on the configured mode
1051        let max_num_full = self.config.propagation_mode.full_peer_count(self.peers.len());
1052
1053        // Note: Assuming ~random~ order due to random state of the peers map hasher
1054        let mut num_full_peers = 0;
1055        for (peer_id, peer) in &mut self.peers {
1056            if !self.policies.propagation_policy().can_propagate(peer) {
1057                // skip peers we should not propagate to
1058                continue
1059            }
1060
1061            // determine whether to send full tx objects or hashes.
1062            let mut builder = if num_full_peers < max_num_full {
1063                num_full_peers += 1;
1064                PropagateTransactionsBuilder::full(peer.version, to_propagate.len())
1065            } else {
1066                PropagateTransactionsBuilder::pooled(peer.version, to_propagate.len())
1067            };
1068
1069            // Transactions are optimistically marked as seen by the peer when included in the
1070            // message, see `PeerMetadata::seen_transactions`.
1071            if propagation_mode.is_forced() {
1072                for tx in &to_propagate {
1073                    peer.seen_transactions.insert(*tx.tx_hash());
1074                    builder.push(tx);
1075                }
1076            } else {
1077                // Iterate through the transactions to propagate and fill the hashes and full
1078                // transaction lists, before deciding whether or not to send full transactions to
1079                // the peer.
1080                for tx in &to_propagate {
1081                    // Only include the transaction if the peer hasn't seen it yet
1082                    if peer.seen_transactions.insert(*tx.tx_hash()) {
1083                        builder.push(tx);
1084                    }
1085                }
1086            }
1087
1088            if builder.is_empty() {
1089                trace!(target: "net::tx", ?peer_id, "Nothing to propagate to peer; has seen all transactions");
1090                continue
1091            }
1092
1093            let PropagateTransactions { pooled, full } = builder.build();
1094
1095            // send hashes if any
1096            if let Some(mut new_pooled_hashes) = pooled {
1097                // Unhappy path: too many hashes for a single message. This should not happen
1098                // during regular propagation, which is capped at the soft limit per batch, and
1099                // is only reachable via manual propagation commands with oversized batches.
1100                if new_pooled_hashes.len() >
1101                    SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE
1102                {
1103                    // hashes that exceed the limit are not sent, so they must not be tracked as
1104                    // seen by the peer
1105                    for hash in new_pooled_hashes
1106                        .iter_hashes()
1107                        .skip(SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
1108                    {
1109                        peer.seen_transactions.remove(hash);
1110                    }
1111                    new_pooled_hashes.truncate(
1112                        SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1113                    );
1114                }
1115
1116                for hash in new_pooled_hashes.iter_hashes().copied() {
1117                    propagated.record(hash, PropagateKind::Hash(*peer_id));
1118                }
1119
1120                trace!(target: "net::tx", ?peer_id, num_txs=?new_pooled_hashes.len(), "Propagating tx hashes to peer");
1121
1122                // send hashes of transactions
1123                self.network.send_transactions_hashes(*peer_id, new_pooled_hashes);
1124            }
1125
1126            // send full transactions, if any
1127            if let Some(new_full_transactions) = full {
1128                for hash in new_full_transactions.iter_hashes() {
1129                    propagated.record(*hash, PropagateKind::Full(*peer_id));
1130                }
1131
1132                trace!(target: "net::tx", ?peer_id, num_txs=?new_full_transactions.len(), "Propagating full transactions to peer");
1133
1134                // send full transactions
1135                self.network.send_broadcast_pool_transactions(*peer_id, new_full_transactions);
1136            }
1137        }
1138
1139        // Update propagated transactions metrics
1140        self.metrics.propagated_transactions.increment(propagated.len() as u64);
1141
1142        propagated
1143    }
1144
1145    /// Propagates the given transactions to the peers
1146    ///
1147    /// This fetches all transaction from the pool, including the 4844 blob transactions but
1148    /// __without__ their sidecar, because 4844 transactions are only ever announced as hashes.
1149    fn propagate_all(&mut self, hashes: Vec<TxHash>) {
1150        if self.peers.is_empty() {
1151            // nothing to propagate
1152            return
1153        }
1154        let propagated = self.propagate_transactions(
1155            self.pool.get_all(hashes).into_iter().map(PropagateTransaction::pool_tx).collect(),
1156            PropagationMode::Basic,
1157        );
1158
1159        // notify pool so events get fired
1160        self.pool.on_propagated(propagated);
1161    }
1162
1163    /// Request handler for an incoming request for transactions
1164    fn on_get_pooled_transactions(
1165        &mut self,
1166        peer_id: PeerId,
1167        request: GetPooledTransactions,
1168        response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
1169    ) {
1170        // fast exit if gossip is disabled
1171        if self.network.tx_gossip_disabled() {
1172            let _ = response.send(Ok(PooledTransactions::default()));
1173            return
1174        }
1175        if let Some(peer) = self.peers.get_mut(&peer_id) {
1176            let transactions = self.pool.get_pooled_transaction_elements(
1177                request.0,
1178                GetPooledTransactionLimit::ResponseSizeSoftLimit(
1179                    self.transaction_fetcher.info.soft_limit_byte_size_pooled_transactions_response,
1180                ),
1181            );
1182            trace!(target: "net::tx::propagation", sent_txs=?transactions.iter().map(|tx| tx.tx_hash()), "Sending requested transactions to peer");
1183
1184            // we sent a response at which point we assume that the peer is aware of the
1185            // transactions
1186            peer.seen_transactions.extend(transactions.iter().map(|tx| *tx.tx_hash()));
1187
1188            let resp = PooledTransactions(transactions);
1189            let _ = response.send(Ok(resp));
1190        }
1191    }
1192
1193    /// Handles a command received from a detached [`TransactionsHandle`]
1194    fn on_command(&mut self, cmd: TransactionsCommand<N>) {
1195        match cmd {
1196            TransactionsCommand::PropagateHash(hash) => {
1197                self.on_new_pending_transactions(vec![hash])
1198            }
1199            TransactionsCommand::PropagateHashesTo(hashes, peer) => {
1200                self.propagate_hashes_to(hashes, peer, PropagationMode::Forced)
1201            }
1202            TransactionsCommand::GetActivePeers(tx) => {
1203                let peers = self.peers.keys().copied().collect::<HashSet<_>>();
1204                tx.send(peers).ok();
1205            }
1206            TransactionsCommand::PropagateTransactionsTo(txs, peer) => {
1207                if let Some(propagated) =
1208                    self.propagate_full_transactions_to_peer(txs, peer, PropagationMode::Forced)
1209                {
1210                    self.pool.on_propagated(propagated);
1211                }
1212            }
1213            TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs),
1214            TransactionsCommand::BroadcastTransactions(txs) => {
1215                let propagated = self.propagate_transactions(txs, PropagationMode::Forced);
1216                self.pool.on_propagated(propagated);
1217            }
1218            TransactionsCommand::GetTransactionHashes { peers, tx } => {
1219                let mut res = HashMap::with_capacity_and_hasher(peers.len(), Default::default());
1220                for peer_id in peers {
1221                    let hashes = self
1222                        .peers
1223                        .get(&peer_id)
1224                        .map(|peer| peer.seen_transactions.iter().copied().collect::<B256Set>())
1225                        .unwrap_or_default();
1226                    res.insert(peer_id, hashes);
1227                }
1228                tx.send(res).ok();
1229            }
1230            TransactionsCommand::GetPeerSender { peer_id, peer_request_sender } => {
1231                let sender = self.peers.get(&peer_id).map(|peer| peer.request_tx.clone());
1232                peer_request_sender.send(sender).ok();
1233            }
1234        }
1235    }
1236
1237    /// Handles session establishment and peer transactions initialization.
1238    ///
1239    /// This is invoked when a new session is established.
1240    fn handle_peer_session(
1241        &mut self,
1242        info: SessionInfo,
1243        messages: PeerRequestSender<PeerRequest<N>>,
1244    ) {
1245        let SessionInfo { peer_id, client_version, version, .. } = info;
1246
1247        // Insert a new peer into the peerset.
1248        let peer = PeerMetadata::<N>::new(
1249            messages,
1250            version,
1251            client_version,
1252            self.config.max_transactions_seen_by_peer_history,
1253            info.peer_kind,
1254        );
1255        let peer = match self.peers.entry(peer_id) {
1256            Entry::Occupied(mut entry) => {
1257                entry.insert(peer);
1258                entry.into_mut()
1259            }
1260            Entry::Vacant(entry) => entry.insert(peer),
1261        };
1262
1263        self.policies.propagation_policy_mut().on_session_established(peer);
1264
1265        // Send a `NewPooledTransactionHashes` to the peer with up to
1266        // `SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE`
1267        // transactions in the pool.
1268        if self.network.is_initially_syncing() || self.network.tx_gossip_disabled() {
1269            trace!(target: "net::tx", ?peer_id, "Skipping transaction broadcast: node syncing or gossip disabled");
1270            return
1271        }
1272
1273        // Get transactions to broadcast
1274        let pooled_txs = self.pool.pooled_transactions_max(
1275            SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1276        );
1277        if pooled_txs.is_empty() {
1278            trace!(target: "net::tx", ?peer_id, "No transactions in the pool to broadcast");
1279            return;
1280        }
1281
1282        // Build and send transaction hashes message
1283        let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
1284        for pooled_tx in pooled_txs {
1285            peer.seen_transactions.insert(*pooled_tx.hash());
1286            msg_builder.push_pooled(pooled_tx);
1287        }
1288
1289        debug!(target: "net::tx", ?peer_id, tx_count = msg_builder.len(), "Broadcasting transaction hashes");
1290        let msg = msg_builder.build();
1291        self.network.send_transactions_hashes(peer_id, msg);
1292    }
1293
1294    /// Handles a received event related to common network events.
1295    fn on_network_event(&mut self, event_result: NetworkEvent<PeerRequest<N>>) {
1296        match event_result {
1297            NetworkEvent::Peer(PeerEvent::SessionClosed { peer_id, .. }) => {
1298                self.on_peer_session_closed(&peer_id);
1299            }
1300            NetworkEvent::ActivePeerSession { info, messages } => {
1301                // process active peer session and broadcast available transaction from the pool
1302                self.handle_peer_session(info, messages);
1303            }
1304            NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
1305                let peer_id = info.peer_id;
1306                // get messages from existing peer
1307                let messages = match self.peers.get(&peer_id) {
1308                    Some(p) => p.request_tx.clone(),
1309                    None => {
1310                        debug!(target: "net::tx", ?peer_id, "No peer request sender found");
1311                        return;
1312                    }
1313                };
1314                self.handle_peer_session(info, messages);
1315            }
1316            _ => {}
1317        }
1318    }
1319
1320    /// Returns true if the ingress policy allows processing messages from the given peer.
1321    fn accepts_incoming_from(&self, peer_id: &PeerId) -> bool {
1322        if self.config.ingress_policy.allows_all() {
1323            return true;
1324        }
1325        let Some(peer) = self.peers.get(peer_id) else {
1326            return false;
1327        };
1328        self.config.ingress_policy.allows(peer.peer_kind())
1329    }
1330
1331    /// Handles dedicated transaction events related to the `eth` protocol.
1332    fn on_network_tx_event(&mut self, event: NetworkTransactionEvent<N>) {
1333        match event {
1334            NetworkTransactionEvent::IncomingTransactions { peer_id, msg } => {
1335                if !self.accepts_incoming_from(&peer_id) {
1336                    trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
1337                    return;
1338                }
1339
1340                // ensure we didn't receive any blob transactions as these are disallowed to be
1341                // broadcasted in full
1342
1343                let has_blob_txs = msg.has_eip4844();
1344
1345                let non_blob_txs = msg
1346                    .into_iter()
1347                    .map(N::PooledTransaction::try_from)
1348                    .filter_map(Result::ok)
1349                    .collect();
1350
1351                self.import_transactions(peer_id, non_blob_txs, TransactionSource::Broadcast);
1352
1353                if has_blob_txs {
1354                    debug!(target: "net::tx", ?peer_id, "received bad full blob transaction broadcast");
1355                    self.report_peer_bad_transactions(peer_id);
1356                }
1357            }
1358            NetworkTransactionEvent::IncomingPooledTransactionHashes { peer_id, msg } => {
1359                if !self.accepts_incoming_from(&peer_id) {
1360                    trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring transaction hashes from peer blocked by ingress policy");
1361                    return;
1362                }
1363                self.on_new_pooled_transaction_hashes(peer_id, msg)
1364            }
1365            NetworkTransactionEvent::GetPooledTransactions { peer_id, request, response } => {
1366                self.on_get_pooled_transactions(peer_id, request, response)
1367            }
1368            NetworkTransactionEvent::GetTransactionsHandle(response) => {
1369                let _ = response.send(Some(self.handle()));
1370            }
1371        }
1372    }
1373
1374    /// Starts the import process for the given transactions.
1375    fn import_transactions(
1376        &mut self,
1377        peer_id: PeerId,
1378        transactions: PooledTransactions<N::PooledTransaction>,
1379        source: TransactionSource,
1380    ) {
1381        // If the node is pipeline syncing, ignore transactions
1382        if self.network.is_initially_syncing() {
1383            return
1384        }
1385        if self.network.tx_gossip_disabled() {
1386            return
1387        }
1388
1389        // Early return if we don't have capacity for any imports
1390        if !self.has_capacity_for_pending_pool_imports() {
1391            return
1392        }
1393
1394        let mut transactions = transactions.0;
1395
1396        // Truncate to remaining capacity early to bound work on all subsequent processing.
1397        // Well-behaved peers follow the 4096 soft limit, so oversized payloads are likely
1398        // malicious and we avoid wasting CPU on them.
1399        let capacity = self.remaining_pool_import_capacity();
1400        if transactions.len() > capacity {
1401            let skipped = transactions.len() - capacity;
1402            transactions.truncate(capacity);
1403            self.metrics
1404                .skipped_transactions_pending_pool_imports_at_capacity
1405                .increment(skipped as u64);
1406            trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
1407        }
1408
1409        let Some(peer) = self.peers.get_mut(&peer_id) else { return };
1410        let client_version = peer.client_version.clone();
1411
1412        let start = Instant::now();
1413
1414        // mark the transactions as received
1415        self.transaction_fetcher
1416            .remove_hashes_from_transaction_fetcher(transactions.iter().map(|tx| tx.tx_hash()));
1417
1418        // track that the peer knows these transaction, but only if this is a new broadcast.
1419        // If we received the transactions as the response to our `GetPooledTransactions``
1420        // requests (based on received `NewPooledTransactionHashes`) then we already
1421        // recorded the hashes as seen by this peer in `Self::on_new_pooled_transaction_hashes`.
1422        let mut num_already_seen_by_peer = 0;
1423        for tx in &transactions {
1424            if source.is_broadcast() && !peer.seen_transactions.insert(*tx.tx_hash()) {
1425                num_already_seen_by_peer += 1;
1426            }
1427        }
1428
1429        // tracks the quality of the given transactions
1430        let mut has_bad_transactions = false;
1431
1432        // 1. Remove known, already-tracked, and invalid transactions first since these are
1433        // cheap in-memory checks against local maps
1434        transactions.retain(|tx| {
1435            if let Entry::Occupied(mut entry) = self.transactions_by_peers.entry(*tx.tx_hash()) {
1436                let peers = entry.get_mut();
1437                if !peers.contains(&peer_id) {
1438                    peers.push(peer_id);
1439                }
1440                return false
1441            }
1442            if self.bad_imports.contains(tx.tx_hash()) {
1443                trace!(target: "net::tx",
1444                    peer_id=format!("{peer_id:#}"),
1445                    hash=%tx.tx_hash(),
1446                    %client_version,
1447                    "received a known bad transaction from peer"
1448                );
1449                has_bad_transactions = true;
1450                return false;
1451            }
1452            true
1453        });
1454
1455        // 2. filter out txns already inserted into pool
1456        let txns_count_pre_pool_filter = transactions.len();
1457        self.pool.retain_unknown(&mut transactions);
1458        if txns_count_pre_pool_filter > transactions.len() {
1459            let already_known_txns_count = txns_count_pre_pool_filter - transactions.len();
1460            self.metrics
1461                .occurrences_transactions_already_in_pool
1462                .increment(already_known_txns_count as u64);
1463        }
1464
1465        let txs_len = transactions.len();
1466
1467        let recover = |tx| {
1468            let recovered = if let Some(cache) = &self.sender_recovery_cache {
1469                Pool::Transaction::try_recover_with_cache(tx, cache)
1470            } else {
1471                Pool::Transaction::try_recover(tx)
1472            };
1473            match recovered {
1474                Ok(tx) => Some(tx),
1475                Err(badtx) => {
1476                    trace!(target: "net::tx",
1477                        peer_id=format!("{peer_id:#}"),
1478                        hash=%badtx.tx_hash(),
1479                        client_version=%client_version,
1480                        "failed ecrecovery for transaction"
1481                    );
1482                    None
1483                }
1484            }
1485        };
1486
1487        let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
1488
1489        has_bad_transactions |= new_txs.len() != txs_len;
1490
1491        // Record the transactions as seen by the peer
1492        for tx in &new_txs {
1493            self.transactions_by_peers.insert(*tx.hash(), smallvec::smallvec![peer_id]);
1494        }
1495
1496        // 3. import new transactions as a batch to minimize lock contention on the underlying
1497        // pool
1498        if !new_txs.is_empty() {
1499            let pool = self.pool.clone();
1500            // update metrics
1501            let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone();
1502            metric_pending_pool_imports.increment(new_txs.len() as f64);
1503
1504            // update self-monitoring info
1505            self.pending_pool_imports_info
1506                .pending_pool_imports
1507                .fetch_add(new_txs.len(), Ordering::Relaxed);
1508            let tx_manager_info_pending_pool_imports =
1509                self.pending_pool_imports_info.pending_pool_imports.clone();
1510
1511            trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions");
1512            let import = Box::pin(async move {
1513                let added = new_txs.len();
1514                let res = pool.add_external_transactions(new_txs).await;
1515
1516                // update metrics
1517                metric_pending_pool_imports.decrement(added as f64);
1518                // update self-monitoring info
1519                tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed);
1520
1521                res
1522            });
1523
1524            self.pool_imports.push(import);
1525        }
1526
1527        if num_already_seen_by_peer > 0 {
1528            self.metrics.messages_with_transactions_already_seen_by_peer.increment(1);
1529            self.metrics
1530                .occurrences_of_transaction_already_seen_by_peer
1531                .increment(num_already_seen_by_peer);
1532            trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
1533        }
1534
1535        if has_bad_transactions {
1536            // peer sent us invalid transactions
1537            self.report_peer_bad_transactions(peer_id)
1538        }
1539
1540        if num_already_seen_by_peer > 0 {
1541            self.report_already_seen(peer_id);
1542        }
1543
1544        self.metrics.pool_import_prepare_duration.record(start.elapsed());
1545    }
1546
1547    /// Processes a [`FetchEvent`].
1548    fn on_fetch_event(&mut self, fetch_event: FetchEvent<N::PooledTransaction>) {
1549        match fetch_event {
1550            FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => {
1551                self.import_transactions(peer_id, transactions, TransactionSource::Response);
1552                if report_peer {
1553                    self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
1554                }
1555            }
1556            FetchEvent::FetchError { peer_id, error } => {
1557                trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed");
1558                self.on_request_error(peer_id, error);
1559            }
1560            FetchEvent::EmptyResponse { peer_id } => {
1561                trace!(target: "net::tx", ?peer_id, "peer returned empty response");
1562            }
1563        }
1564    }
1565}
1566
1567/// An endless future. Preemption ensure that future is non-blocking, nonetheless. See
1568/// [`crate::NetworkManager`] for more context on the design pattern.
1569///
1570/// This should be spawned or used as part of `tokio::select!`.
1571//
1572// spawned in `NodeConfig::start_network`(reth_node_core::NodeConfig) and
1573// `NetworkConfig::start_network`(reth_network::NetworkConfig)
1574impl<
1575        Pool: TransactionPool + Unpin + 'static,
1576        N: NetworkPrimitives<
1577                BroadcastedTransaction: SignedTransaction,
1578                PooledTransaction: SignedTransaction,
1579            > + Unpin,
1580    > Future for TransactionsManager<Pool, N>
1581where
1582    Pool::Transaction:
1583        PoolTransaction<Consensus = N::BroadcastedTransaction, Pooled = N::PooledTransaction>,
1584{
1585    type Output = ();
1586
1587    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1588        let start = Instant::now();
1589        let mut poll_durations = TxManagerPollDurations::default();
1590
1591        let this = self.get_mut();
1592
1593        // All streams are polled until their corresponding budget is exhausted, then we manually
1594        // yield back control to tokio. See `NetworkManager` for more context on the design
1595        // pattern.
1596
1597        // Advance network/peer related events (update peers map).
1598        let maybe_more_network_events = metered_poll_nested_stream_with_budget!(
1599            poll_durations.acc_network_events,
1600            "net::tx",
1601            "Network events stream",
1602            DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1603            this.network_events.poll_next_unpin(cx),
1604            |event| this.on_network_event(event)
1605        );
1606
1607        // Advance incoming transaction events (stream new txns/announcements from
1608        // network manager and queue for import to pool/fetch txns).
1609        //
1610        // This will potentially remove hashes from hashes pending fetch, it the event
1611        // is an announcement (if same hashes are announced that didn't fit into a
1612        // previous request).
1613        //
1614        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1615        // (128 KiB / 10 bytes > 13k transactions).
1616        //
1617        // If this is an event with `Transactions` message, since transactions aren't
1618        // validated until they are inserted into the pool, this can potentially queue
1619        // >13k transactions for insertion to pool. More if the message size is bigger
1620        // than the soft limit on a `Transactions` broadcast message, which is 128 KiB.
1621        let maybe_more_tx_events = metered_poll_nested_stream_with_budget!(
1622            poll_durations.acc_tx_events,
1623            "net::tx",
1624            "Network transaction events stream",
1625            DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
1626            this.transaction_events.poll_next_unpin(cx),
1627            |event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
1628        );
1629
1630        // Advance inflight fetch requests (flush transaction fetcher and queue for
1631        // import to pool).
1632        //
1633        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1634        // (2 MiB / 10 bytes > 200k transactions).
1635        //
1636        // Since transactions aren't validated until they are inserted into the pool,
1637        // this can potentially queue >200k transactions for insertion to pool. More
1638        // if the message size is bigger than the soft limit on a `PooledTransactions`
1639        // response which is 2 MiB.
1640        let mut maybe_more_tx_fetch_events = metered_poll_nested_stream_with_budget!(
1641            poll_durations.acc_fetch_events,
1642            "net::tx",
1643            "Transaction fetch events stream",
1644            DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1645            this.transaction_fetcher.poll_next_unpin(cx),
1646            |event| this.on_fetch_event(event),
1647        );
1648
1649        // Advance pool imports (flush txns to pool).
1650        //
1651        // Note, this is done in batches. A batch is filled from one `Transactions`
1652        // broadcast messages or one `PooledTransactions` response at a time. The
1653        // minimum batch size is 1 transaction (and might often be the case with blob
1654        // transactions).
1655        //
1656        // The smallest decodable transaction is an empty legacy transaction, 10 bytes
1657        // (2 MiB / 10 bytes > 200k transactions).
1658        //
1659        // Since transactions aren't validated until they are inserted into the pool,
1660        // this can potentially validate >200k transactions. More if the message size
1661        // is bigger than the soft limit on a `PooledTransactions` response which is
1662        // 2 MiB (`Transactions` broadcast messages is smaller, 128 KiB).
1663        let maybe_more_pool_imports = metered_poll_nested_stream_with_budget!(
1664            poll_durations.acc_pending_imports,
1665            "net::tx",
1666            "Batched pool imports stream",
1667            DEFAULT_BUDGET_TRY_DRAIN_PENDING_POOL_IMPORTS,
1668            this.pool_imports.poll_next_unpin(cx),
1669            |batch_results| this.on_batch_import_result(batch_results)
1670        );
1671
1672        // Advances new __pending__ transactions, transactions that were successfully inserted into
1673        // pending set in pool (are valid), and propagates them (inform peers which
1674        // transactions we have seen).
1675        //
1676        // This is polled after pool imports so transactions that became pending in this poll
1677        // iteration are propagated immediately, instead of waiting for the task to be woken
1678        // again.
1679        //
1680        // We try to drain this to batch the transactions in a single message.
1681        //
1682        // We don't expect this buffer to be large, since only pending transactions are
1683        // emitted here.
1684        let mut new_txs = Vec::new();
1685        let maybe_more_pending_txns = match this.pending_transactions.poll_recv_many(
1686            cx,
1687            &mut new_txs,
1688            SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE,
1689        ) {
1690            Poll::Ready(count) => {
1691                if count == SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE {
1692                    // we filled the entire buffer capacity and need to try again on the next poll
1693                    // immediately
1694                    true
1695                } else {
1696                    // try once more, because mostlikely the channel is now empty and the waker is
1697                    // registered if this is pending, if we filled additional hashes, we poll again
1698                    // on the next iteration
1699                    let limit =
1700                        SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE -
1701                            new_txs.len();
1702                    this.pending_transactions.poll_recv_many(cx, &mut new_txs, limit).is_ready()
1703                }
1704            }
1705            Poll::Pending => false,
1706        };
1707        if !new_txs.is_empty() {
1708            this.on_new_pending_transactions(new_txs);
1709        }
1710
1711        // Tries to drain hashes pending fetch cache if the tx manager currently has
1712        // capacity for this (fetch txns).
1713        //
1714        // Sends at most one request.
1715        duration_metered_exec!(
1716            {
1717                if this.has_capacity_for_fetching_pending_hashes() &&
1718                    this.on_fetch_hashes_pending_fetch()
1719                {
1720                    maybe_more_tx_fetch_events = true;
1721                }
1722            },
1723            poll_durations.acc_pending_fetch
1724        );
1725
1726        // Advance commands (propagate/fetch/serve txns).
1727        let maybe_more_commands = metered_poll_nested_stream_with_budget!(
1728            poll_durations.acc_cmds,
1729            "net::tx",
1730            "Commands channel",
1731            DEFAULT_BUDGET_TRY_DRAIN_STREAM,
1732            this.command_rx.poll_next_unpin(cx),
1733            |cmd| this.on_command(cmd)
1734        );
1735
1736        this.transaction_fetcher.update_metrics();
1737
1738        // all channels are fully drained and import futures pending
1739        if maybe_more_network_events ||
1740            maybe_more_commands ||
1741            maybe_more_tx_events ||
1742            maybe_more_tx_fetch_events ||
1743            maybe_more_pool_imports ||
1744            maybe_more_pending_txns
1745        {
1746            // make sure we're woken up again
1747            cx.waker().wake_by_ref();
1748            return Poll::Pending
1749        }
1750
1751        this.update_poll_metrics(start, poll_durations);
1752
1753        Poll::Pending
1754    }
1755}
1756
1757/// Represents the different modes of transaction propagation.
1758///
1759/// This enum is used to determine how transactions are propagated to peers in the network.
1760#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1761enum PropagationMode {
1762    /// Default propagation mode.
1763    ///
1764    /// Transactions are only sent to peers that haven't seen them yet.
1765    Basic,
1766    /// Forced propagation mode.
1767    ///
1768    /// Transactions are sent to all peers regardless of whether they have been sent or received
1769    /// before.
1770    Forced,
1771}
1772
1773impl PropagationMode {
1774    /// Returns `true` if the propagation kind is `Forced`.
1775    const fn is_forced(self) -> bool {
1776        matches!(self, Self::Forced)
1777    }
1778}
1779
1780/// A transaction that's about to be propagated to multiple peers.
1781#[derive(Debug, Clone)]
1782struct PropagateTransaction {
1783    is_broadcastable_in_full: bool,
1784    /// Size advertised in `NewPooledTransactionHashes` metadata and used for full broadcast
1785    /// soft-limit accounting.
1786    ///
1787    /// This is the network encoded transaction size. For pool-backed blob transactions, this is
1788    /// the pool's cached encoded length, which includes the sidecar returned by
1789    /// `PooledTransactions`.
1790    propagation_size: usize,
1791    transaction: LazyEncodedTransaction,
1792}
1793
1794impl PropagateTransaction {
1795    /// Create a new instance from a transaction supplied directly for propagation.
1796    ///
1797    /// Direct transactions use their EIP-2718 encoded length so eth/68+ hash announcements carry
1798    /// the same size metadata as [`NewPooledTransactionHashes68::push`] and
1799    /// [`NewPooledTransactionHashes72::push`].
1800    fn new<T: SignedTransaction>(transaction: T) -> Self {
1801        let is_broadcastable_in_full = transaction.is_broadcastable_in_full();
1802        let propagation_size = transaction.encode_2718_len();
1803
1804        Self {
1805            is_broadcastable_in_full,
1806            propagation_size,
1807            transaction: LazyEncoded::new(transaction),
1808        }
1809    }
1810
1811    /// Create a new instance from a pooled transaction.
1812    ///
1813    /// Pool transactions already cache the network encoded size used by txpool admission and
1814    /// pooled hash announcements. For blob transactions, this includes the sidecar size expected in
1815    /// a `PooledTransactions` response.
1816    fn pool_tx<P: PoolTransaction>(tx: Arc<ValidPoolTransaction<P>>) -> Self {
1817        let is_broadcastable_in_full = tx.transaction.consensus_ref().is_broadcastable_in_full();
1818        let propagation_size = tx.encoded_length();
1819        Self {
1820            is_broadcastable_in_full,
1821            propagation_size,
1822            transaction: LazyEncoded::new(PropagatePooledTransactionEncoder::new(tx)),
1823        }
1824    }
1825
1826    fn tx_hash(&self) -> &TxHash {
1827        self.transaction.tx_hash()
1828    }
1829
1830    /// Returns the network encoded size used for propagation limits and hash metadata.
1831    const fn propagation_size(&self) -> usize {
1832        self.propagation_size
1833    }
1834
1835    fn tx_type(&self) -> u8 {
1836        self.transaction.ty()
1837    }
1838
1839    const fn is_broadcastable_in_full(&self) -> bool {
1840        self.is_broadcastable_in_full
1841    }
1842
1843    fn shared(&self) -> LazyEncodedTransaction {
1844        self.transaction.clone()
1845    }
1846}
1847
1848/// A pooled transaction encoder that avoids cloning into the consensus transaction for propagation.
1849#[derive(Debug)]
1850struct PropagatePooledTransactionEncoder<P: PoolTransaction> {
1851    transaction: Arc<ValidPoolTransaction<P>>,
1852}
1853
1854impl<P: PoolTransaction> PropagatePooledTransactionEncoder<P> {
1855    const fn new(transaction: Arc<ValidPoolTransaction<P>>) -> Self {
1856        Self { transaction }
1857    }
1858
1859    fn encode_uncached(&self, out: &mut dyn BufMut) {
1860        (*self.transaction.transaction.consensus_ref().inner()).encode(out);
1861    }
1862}
1863
1864impl<P: PoolTransaction> Encodable for PropagatePooledTransactionEncoder<P> {
1865    fn encode(&self, out: &mut dyn BufMut) {
1866        self.encode_uncached(out);
1867    }
1868
1869    fn length(&self) -> usize {
1870        (*self.transaction.transaction.consensus_ref().inner()).length()
1871    }
1872}
1873
1874impl<P: PoolTransaction> TxHashRef for PropagatePooledTransactionEncoder<P> {
1875    fn tx_hash(&self) -> &TxHash {
1876        self.transaction.hash()
1877    }
1878}
1879
1880impl<P: PoolTransaction> Typed2718 for PropagatePooledTransactionEncoder<P> {
1881    fn ty(&self) -> u8 {
1882        self.transaction.transaction.ty()
1883    }
1884}
1885
1886/// Helper type to construct the appropriate message to send to the peer based on whether the peer
1887/// should receive them in full or as pooled
1888#[derive(Debug, Clone)]
1889enum PropagateTransactionsBuilder {
1890    Pooled(PooledTransactionsHashesBuilder),
1891    Full(FullTransactionsBuilder),
1892}
1893
1894impl PropagateTransactionsBuilder {
1895    /// Create a builder for pooled transactions with capacity for the expected number of
1896    /// transactions.
1897    fn pooled(version: EthVersion, capacity: usize) -> Self {
1898        Self::Pooled(PooledTransactionsHashesBuilder::with_capacity(version, capacity))
1899    }
1900
1901    /// Create a builder that sends transactions in full and records transactions that don't fit,
1902    /// with capacity for the expected number of transactions.
1903    fn full(version: EthVersion, capacity: usize) -> Self {
1904        Self::Full(FullTransactionsBuilder::with_capacity(version, capacity))
1905    }
1906
1907    /// Returns true if no transactions are recorded.
1908    fn is_empty(&self) -> bool {
1909        match self {
1910            Self::Pooled(builder) => builder.is_empty(),
1911            Self::Full(builder) => builder.is_empty(),
1912        }
1913    }
1914
1915    /// Consumes the type and returns the built messages that should be sent to the peer.
1916    fn build(self) -> PropagateTransactions {
1917        match self {
1918            Self::Pooled(pooled) => {
1919                PropagateTransactions { pooled: Some(pooled.build()), full: None }
1920            }
1921            Self::Full(full) => full.build(),
1922        }
1923    }
1924}
1925
1926impl PropagateTransactionsBuilder {
1927    /// Appends a transaction to the list.
1928    fn push(&mut self, transaction: &PropagateTransaction) {
1929        match self {
1930            Self::Pooled(builder) => builder.push(transaction),
1931            Self::Full(builder) => builder.push(transaction),
1932        }
1933    }
1934}
1935
1936/// Represents how the transactions should be sent to a peer if any.
1937struct PropagateTransactions {
1938    /// The pooled transaction hashes to send.
1939    pooled: Option<NewPooledTransactionHashes>,
1940    /// The transactions to send in full.
1941    full: Option<BroadcastPoolTransactions>,
1942}
1943
1944/// Helper type for constructing the full transaction message that enforces the
1945/// [`DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE`] for full transaction broadcast
1946/// and enforces other propagation rules for EIP-4844 and tracks those transactions that can't be
1947/// broadcasted in full.
1948#[derive(Debug, Clone)]
1949struct FullTransactionsBuilder {
1950    /// The soft limit to enforce for a single broadcast message of full transactions.
1951    total_size: usize,
1952    /// All transactions to be broadcasted.
1953    transactions: Vec<LazyEncodedTransaction>,
1954    /// Transactions that didn't fit into the broadcast message
1955    pooled: PooledTransactionsHashesBuilder,
1956}
1957
1958impl FullTransactionsBuilder {
1959    /// Create a builder for the negotiated version of the peer's session
1960    fn new(version: EthVersion) -> Self {
1961        Self {
1962            total_size: 0,
1963            pooled: PooledTransactionsHashesBuilder::new(version),
1964            transactions: vec![],
1965        }
1966    }
1967
1968    /// Create a builder with capacity for the expected number of full transactions.
1969    ///
1970    /// The overflow hashes builder remains lazily allocated since most transactions are expected
1971    /// to be broadcast in full.
1972    fn with_capacity(version: EthVersion, capacity: usize) -> Self {
1973        Self {
1974            total_size: 0,
1975            pooled: PooledTransactionsHashesBuilder::new(version),
1976            transactions: Vec::with_capacity(capacity),
1977        }
1978    }
1979
1980    /// Returns whether or not any transactions are in the [`FullTransactionsBuilder`].
1981    fn is_empty(&self) -> bool {
1982        self.transactions.is_empty() && self.pooled.is_empty()
1983    }
1984
1985    /// Returns the messages that should be propagated to the peer.
1986    fn build(self) -> PropagateTransactions {
1987        let pooled = Some(self.pooled.build()).filter(|pooled| !pooled.is_empty());
1988        let full =
1989            (!self.transactions.is_empty()).then_some(BroadcastPoolTransactions(self.transactions));
1990        PropagateTransactions { pooled, full }
1991    }
1992
1993    /// Appends all transactions.
1994    fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
1995        for tx in txs {
1996            self.push(&tx)
1997        }
1998    }
1999
2000    /// Append a transaction to the list of full transaction if the total message bytes size doesn't
2001    /// exceed the soft maximum target byte size. The limit is soft, meaning if one single
2002    /// transaction goes over the limit, it will be broadcasted in its own [`Transactions`]
2003    /// message. The same pattern is followed in filling a [`GetPooledTransactions`] request in
2004    /// [`TransactionFetcher::fill_request_from_hashes_pending_fetch`].
2005    ///
2006    /// If the transaction is unsuitable for broadcast or would exceed the softlimit, it is appended
2007    /// to list of pooled transactions, (e.g. 4844 transactions).
2008    /// See also [`SignedTransaction::is_broadcastable_in_full`].
2009    fn push(&mut self, transaction: &PropagateTransaction) {
2010        // Do not send full 4844 transaction hashes to peers.
2011        //
2012        //  Nodes MUST NOT automatically broadcast blob transactions to their peers.
2013        //  Instead, those transactions are only announced using
2014        //  `NewPooledTransactionHashes` messages, and can then be manually requested
2015        //  via `GetPooledTransactions`.
2016        //
2017        // From: <https://eips.ethereum.org/EIPS/eip-4844#networking>
2018        if !transaction.is_broadcastable_in_full() {
2019            self.pooled.push(transaction);
2020            return
2021        }
2022
2023        let new_size = self.total_size + transaction.propagation_size();
2024        if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE &&
2025            self.total_size > 0
2026        {
2027            // transaction does not fit into the message
2028            self.pooled.push(transaction);
2029            return
2030        }
2031
2032        self.total_size = new_size;
2033        self.transactions.push(transaction.shared());
2034    }
2035}
2036
2037/// A helper type to create the pooled transactions message based on the negotiated version of the
2038/// session with the peer
2039#[derive(Debug, Clone)]
2040enum PooledTransactionsHashesBuilder {
2041    Eth66(NewPooledTransactionHashes66),
2042    Eth68(NewPooledTransactionHashes68),
2043    Eth72(NewPooledTransactionHashes72),
2044}
2045
2046// === impl PooledTransactionsHashesBuilder ===
2047
2048impl PooledTransactionsHashesBuilder {
2049    /// Push a transaction from the pool to the list.
2050    fn push_pooled<T: PoolTransaction>(&mut self, pooled_tx: Arc<ValidPoolTransaction<T>>) {
2051        match self {
2052            Self::Eth66(msg) => msg.push(*pooled_tx.hash()),
2053            Self::Eth68(msg) => {
2054                msg.hashes.push(*pooled_tx.hash());
2055                msg.sizes.push(pooled_tx.encoded_length());
2056                msg.types.push(pooled_tx.transaction.ty());
2057            }
2058            Self::Eth72(msg) => {
2059                msg.hashes.push(*pooled_tx.hash());
2060                msg.sizes.push(pooled_tx.encoded_length());
2061                msg.types.push(pooled_tx.transaction.ty());
2062            }
2063        }
2064    }
2065
2066    /// Returns whether or not any transactions are in the [`PooledTransactionsHashesBuilder`].
2067    fn is_empty(&self) -> bool {
2068        match self {
2069            Self::Eth66(hashes) => hashes.is_empty(),
2070            Self::Eth68(hashes) => hashes.is_empty(),
2071            Self::Eth72(hashes) => hashes.is_empty(),
2072        }
2073    }
2074
2075    /// Returns the number of transactions in the builder.
2076    fn len(&self) -> usize {
2077        match self {
2078            Self::Eth66(hashes) => hashes.len(),
2079            Self::Eth68(hashes) => hashes.len(),
2080            Self::Eth72(hashes) => hashes.len(),
2081        }
2082    }
2083
2084    /// Appends all hashes
2085    fn extend(&mut self, txs: impl IntoIterator<Item = PropagateTransaction>) {
2086        for tx in txs {
2087            self.push(&tx);
2088        }
2089    }
2090
2091    fn push(&mut self, tx: &PropagateTransaction) {
2092        match self {
2093            Self::Eth66(msg) => msg.push(*tx.tx_hash()),
2094            Self::Eth68(msg) => {
2095                msg.hashes.push(*tx.tx_hash());
2096                msg.sizes.push(tx.propagation_size());
2097                msg.types.push(tx.tx_type());
2098            }
2099            Self::Eth72(msg) => {
2100                msg.hashes.push(*tx.tx_hash());
2101                msg.sizes.push(tx.propagation_size());
2102                msg.types.push(tx.tx_type());
2103            }
2104        }
2105    }
2106
2107    /// Create a builder for the negotiated version of the peer's session
2108    fn new(version: EthVersion) -> Self {
2109        match version {
2110            EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
2111            EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2112                Self::Eth68(Default::default())
2113            }
2114            EthVersion::Eth72 => Self::Eth72(Default::default()),
2115        }
2116    }
2117
2118    /// Create a builder for the negotiated version of the peer's session with capacity for the
2119    /// expected number of hashes.
2120    fn with_capacity(version: EthVersion, capacity: usize) -> Self {
2121        match version {
2122            EthVersion::Eth66 | EthVersion::Eth67 => {
2123                Self::Eth66(NewPooledTransactionHashes66::with_capacity(capacity))
2124            }
2125            EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => {
2126                Self::Eth68(NewPooledTransactionHashes68::with_capacity(capacity))
2127            }
2128            EthVersion::Eth72 => Self::Eth72(NewPooledTransactionHashes72::with_capacity(capacity)),
2129        }
2130    }
2131
2132    fn build(self) -> NewPooledTransactionHashes {
2133        match self {
2134            Self::Eth66(mut msg) => {
2135                msg.shrink_to_fit();
2136                msg.into()
2137            }
2138            Self::Eth68(mut msg) => {
2139                msg.shrink_to_fit();
2140                msg.into()
2141            }
2142            Self::Eth72(mut msg) => {
2143                msg.shrink_to_fit();
2144                msg.into()
2145            }
2146        }
2147    }
2148}
2149
2150/// How we received the transactions.
2151enum TransactionSource {
2152    /// Transactions were broadcast to us via [`Transactions`] message.
2153    Broadcast,
2154    /// Transactions were sent as the response of [`fetcher::GetPooledTxRequest`] issued by us.
2155    Response,
2156}
2157
2158// === impl TransactionSource ===
2159
2160impl TransactionSource {
2161    /// Whether the transaction were sent as broadcast.
2162    const fn is_broadcast(&self) -> bool {
2163        matches!(self, Self::Broadcast)
2164    }
2165}
2166
2167/// Tracks a single peer in the context of [`TransactionsManager`].
2168#[derive(Debug)]
2169pub struct PeerMetadata<N: NetworkPrimitives = EthNetworkPrimitives> {
2170    /// Optimistically keeps track of transactions that we know the peer has seen. Optimistic, in
2171    /// the sense that transactions are preemptively marked as seen by peer when they are sent to
2172    /// the peer.
2173    seen_transactions: LruCache<TxHash, FbBuildHasher<32>>,
2174    /// A communication channel directly to the peer's session task.
2175    request_tx: PeerRequestSender<PeerRequest<N>>,
2176    /// negotiated version of the session.
2177    version: EthVersion,
2178    /// The peer's client version.
2179    client_version: Arc<str>,
2180    /// The kind of peer.
2181    peer_kind: PeerKind,
2182}
2183
2184impl<N: NetworkPrimitives> PeerMetadata<N> {
2185    /// Returns a new instance of [`PeerMetadata`].
2186    pub fn new(
2187        request_tx: PeerRequestSender<PeerRequest<N>>,
2188        version: EthVersion,
2189        client_version: Arc<str>,
2190        max_transactions_seen_by_peer: u32,
2191        peer_kind: PeerKind,
2192    ) -> Self {
2193        Self {
2194            seen_transactions: LruCache::with_hasher(
2195                max_transactions_seen_by_peer,
2196                Default::default(),
2197            ),
2198            request_tx,
2199            version,
2200            client_version,
2201            peer_kind,
2202        }
2203    }
2204
2205    /// Returns a reference to the peer's request sender channel.
2206    pub const fn request_tx(&self) -> &PeerRequestSender<PeerRequest<N>> {
2207        &self.request_tx
2208    }
2209
2210    /// Returns a mutable reference to the seen transactions LRU cache.
2211    pub const fn seen_transactions_mut(&mut self) -> &mut LruCache<TxHash, FbBuildHasher<32>> {
2212        &mut self.seen_transactions
2213    }
2214
2215    /// Returns the negotiated `EthVersion` of the session.
2216    pub const fn version(&self) -> EthVersion {
2217        self.version
2218    }
2219
2220    /// Returns a reference to the peer's client version string.
2221    pub fn client_version(&self) -> &str {
2222        &self.client_version
2223    }
2224
2225    /// Returns the peer's kind.
2226    pub const fn peer_kind(&self) -> PeerKind {
2227        self.peer_kind
2228    }
2229}
2230
2231/// Commands to send to the [`TransactionsManager`]
2232#[derive(Debug)]
2233enum TransactionsCommand<N: NetworkPrimitives = EthNetworkPrimitives> {
2234    /// Propagate a transaction hash to the network.
2235    PropagateHash(B256),
2236    /// Propagate transaction hashes to a specific peer.
2237    PropagateHashesTo(Vec<B256>, PeerId),
2238    /// Request the list of active peer IDs from the [`TransactionsManager`].
2239    GetActivePeers(oneshot::Sender<HashSet<PeerId>>),
2240    /// Propagate a collection of full transactions to a specific peer.
2241    PropagateTransactionsTo(Vec<TxHash>, PeerId),
2242    /// Propagate a collection of hashes to all peers.
2243    PropagateTransactions(Vec<TxHash>),
2244    /// Propagate a collection of broadcastable transactions in full to all peers.
2245    BroadcastTransactions(Vec<PropagateTransaction>),
2246    /// Request transaction hashes known by specific peers from the [`TransactionsManager`].
2247    GetTransactionHashes { peers: Vec<PeerId>, tx: oneshot::Sender<HashMap<PeerId, B256Set>> },
2248    /// Requests a clone of the sender channel to the peer.
2249    GetPeerSender {
2250        peer_id: PeerId,
2251        peer_request_sender: oneshot::Sender<Option<PeerRequestSender<PeerRequest<N>>>>,
2252    },
2253}
2254
2255/// All events related to transactions emitted by the network.
2256#[derive(Debug)]
2257pub enum NetworkTransactionEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
2258    /// Represents the event of receiving a list of transactions from a peer.
2259    ///
2260    /// This indicates transactions that were broadcasted to us from the peer.
2261    IncomingTransactions {
2262        /// The ID of the peer from which the transactions were received.
2263        peer_id: PeerId,
2264        /// The received transactions.
2265        msg: Transactions<N::BroadcastedTransaction>,
2266    },
2267    /// Represents the event of receiving a list of transaction hashes from a peer.
2268    IncomingPooledTransactionHashes {
2269        /// The ID of the peer from which the transaction hashes were received.
2270        peer_id: PeerId,
2271        /// The received new pooled transaction hashes.
2272        msg: NewPooledTransactionHashes,
2273    },
2274    /// Represents the event of receiving a `GetPooledTransactions` request from a peer.
2275    GetPooledTransactions {
2276        /// The ID of the peer from which the request was received.
2277        peer_id: PeerId,
2278        /// The received `GetPooledTransactions` request.
2279        request: GetPooledTransactions,
2280        /// The sender for responding to the request with a result of `PooledTransactions`.
2281        response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
2282    },
2283    /// Represents the event of receiving a `GetTransactionsHandle` request.
2284    GetTransactionsHandle(oneshot::Sender<Option<TransactionsHandle<N>>>),
2285}
2286
2287/// Tracks stats about the [`TransactionsManager`].
2288#[derive(Debug)]
2289pub struct PendingPoolImportsInfo {
2290    /// Number of transactions about to be inserted into the pool.
2291    pending_pool_imports: Arc<AtomicUsize>,
2292    /// Max number of transactions allowed to be imported concurrently.
2293    max_pending_pool_imports: usize,
2294}
2295
2296impl PendingPoolImportsInfo {
2297    /// Returns a new [`PendingPoolImportsInfo`].
2298    pub fn new(max_pending_pool_imports: usize) -> Self {
2299        Self { pending_pool_imports: Arc::new(AtomicUsize::default()), max_pending_pool_imports }
2300    }
2301
2302    /// Returns `true` if the number of pool imports is under a given tolerated max.
2303    pub fn has_capacity(&self, max_pending_pool_imports: usize) -> bool {
2304        self.pending_pool_imports.load(Ordering::Relaxed) < max_pending_pool_imports
2305    }
2306}
2307
2308impl Default for PendingPoolImportsInfo {
2309    fn default() -> Self {
2310        Self::new(DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS)
2311    }
2312}
2313
2314#[derive(Debug, Default)]
2315struct TxManagerPollDurations {
2316    acc_network_events: Duration,
2317    acc_pending_imports: Duration,
2318    acc_tx_events: Duration,
2319    acc_imported_txns: Duration,
2320    acc_fetch_events: Duration,
2321    acc_pending_fetch: Duration,
2322    acc_cmds: Duration,
2323}
2324
2325impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
2326    // `N::BroadcastedTransaction` and `N::PooledTransaction` already implement
2327    // `InMemorySize` via `SignedTransaction: InMemorySize`, so no extra bound is needed.
2328    fn size(&self) -> usize {
2329        match self {
2330            Self::IncomingTransactions { peer_id, msg } => {
2331                core::mem::size_of_val(peer_id) +
2332                    msg.0.iter().map(InMemorySize::size).sum::<usize>()
2333            }
2334            Self::IncomingPooledTransactionHashes { peer_id, msg } => {
2335                core::mem::size_of_val(peer_id) + msg.size()
2336            }
2337            Self::GetPooledTransactions { peer_id, request, response } => {
2338                core::mem::size_of_val(peer_id) +
2339                    request.0.len() * core::mem::size_of::<TxHash>() +
2340                    core::mem::size_of_val(response)
2341            }
2342            Self::GetTransactionsHandle(_) => 0,
2343        }
2344    }
2345}
2346
2347#[cfg(test)]
2348mod tests {
2349    use super::*;
2350    use crate::{
2351        test_utils::{
2352            transactions::{buffer_hash_to_tx_fetcher, new_mock_session, new_tx_manager},
2353            Testnet,
2354        },
2355        transactions::config::RelaxedEthAnnouncementFilter,
2356        NetworkConfigBuilder, NetworkManager,
2357    };
2358    use alloy_consensus::{Transaction as _, TxEip1559, TxLegacy};
2359    use alloy_eips::{eip2718::Encodable2718, eip4844::BlobTransactionValidationError};
2360    use alloy_primitives::{hex, Signature, TxKind, B256, U256};
2361    use alloy_rlp::Decodable;
2362    use futures::FutureExt;
2363    use reth_chainspec::MIN_TRANSACTION_GAS;
2364    use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
2365    use reth_network_api::{NetworkInfo, PeerKind};
2366    use reth_network_p2p::{
2367        error::{RequestError, RequestResult},
2368        sync::{NetworkSyncUpdater, SyncState},
2369    };
2370    use reth_storage_api::noop::NoopProvider;
2371    use reth_tasks::Runtime;
2372    use reth_transaction_pool::{
2373        blobstore::InMemoryBlobStore,
2374        error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
2375        identifier::SenderIdentifiers,
2376        test_utils::{
2377            testing_pool, MockTransaction, MockTransactionFactory, OkValidator, TestPool,
2378            TransactionGenerator,
2379        },
2380        CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionOrigin, ValidPoolTransaction,
2381    };
2382    use secp256k1::SecretKey;
2383    use std::{
2384        future::poll_fn,
2385        net::{IpAddr, Ipv4Addr, SocketAddr},
2386        str::FromStr,
2387        time::Instant,
2388    };
2389    use tracing::error;
2390
2391    type EthTestPool = Pool<
2392        OkValidator<EthPooledTransaction>,
2393        CoinbaseTipOrdering<EthPooledTransaction>,
2394        InMemoryBlobStore,
2395    >;
2396
2397    async fn new_eth_tx_manager() -> (
2398        TransactionsManager<EthTestPool, EthNetworkPrimitives>,
2399        NetworkManager<EthNetworkPrimitives>,
2400    ) {
2401        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2402        let client = NoopProvider::default();
2403
2404        let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2405            .listener_port(0)
2406            .disable_discovery()
2407            .build(client);
2408
2409        let pool = Pool::new(
2410            OkValidator::default(),
2411            CoinbaseTipOrdering::default(),
2412            InMemoryBlobStore::default(),
2413            Default::default(),
2414        );
2415
2416        let transactions_manager_config = config.transactions_manager_config.clone();
2417        let (_network_handle, network, transactions, _) = NetworkManager::new(config)
2418            .await
2419            .unwrap()
2420            .into_builder()
2421            .transactions(pool.clone(), transactions_manager_config)
2422            .split_with_handle();
2423
2424        (transactions, network)
2425    }
2426
2427    fn valid_eth_pool_transaction(
2428        transaction: EthPooledTransaction,
2429    ) -> Arc<ValidPoolTransaction<EthPooledTransaction>> {
2430        let mut ids = SenderIdentifiers::default();
2431        let transaction_id =
2432            ids.sender_id_or_create(transaction.sender()).into_transaction_id(transaction.nonce());
2433
2434        Arc::new(ValidPoolTransaction {
2435            propagate: false,
2436            transaction_id,
2437            transaction,
2438            timestamp: Instant::now(),
2439            origin: TransactionOrigin::External,
2440            authority_ids: None,
2441        })
2442    }
2443
2444    fn gen_eip1559_pooled_with_nonce<R: rand::RngCore>(
2445        tx_gen: &mut TransactionGenerator<R>,
2446        nonce: u64,
2447    ) -> EthPooledTransaction {
2448        EthPooledTransaction::try_from_consensus(
2449            tx_gen.transaction().nonce(nonce).into_eip1559().try_into_recovered().unwrap(),
2450        )
2451        .unwrap()
2452    }
2453
2454    #[tokio::test(flavor = "multi_thread")]
2455    async fn test_ignored_tx_broadcasts_while_initially_syncing() {
2456        reth_tracing::init_test_tracing();
2457        let net = Testnet::create(3).await;
2458
2459        let mut handles = net.handles();
2460        let handle0 = handles.next().unwrap();
2461        let handle1 = handles.next().unwrap();
2462
2463        drop(handles);
2464        let handle = net.spawn();
2465
2466        let listener0 = handle0.event_listener();
2467        handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2468        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2469
2470        let client = NoopProvider::default();
2471        let pool = testing_pool();
2472        let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
2473            .disable_discovery()
2474            .listener_port(0)
2475            .build(client);
2476        let transactions_manager_config = config.transactions_manager_config.clone();
2477        let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2478            .await
2479            .unwrap()
2480            .into_builder()
2481            .transactions(pool.clone(), transactions_manager_config)
2482            .split_with_handle();
2483
2484        tokio::task::spawn(network);
2485
2486        // go to syncing (pipeline sync)
2487        network_handle.update_sync_state(SyncState::Syncing);
2488        assert!(NetworkInfo::is_syncing(&network_handle));
2489        assert!(NetworkInfo::is_initially_syncing(&network_handle));
2490
2491        // wait for all initiator connections
2492        let mut established = listener0.take(2);
2493        while let Some(ev) = established.next().await {
2494            match ev {
2495                NetworkEvent::Peer(PeerEvent::SessionEstablished(info)) => {
2496                    // to insert a new peer in transactions peerset
2497                    transactions
2498                        .on_network_event(NetworkEvent::Peer(PeerEvent::SessionEstablished(info)))
2499                }
2500                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2501                ev => {
2502                    error!("unexpected event {ev:?}")
2503                }
2504            }
2505        }
2506        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2507        let input = hex!(
2508            "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2509        );
2510        let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2511        transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2512            peer_id: *handle1.peer_id(),
2513            msg: Transactions(vec![signed_tx.clone()]),
2514        });
2515        poll_fn(|cx| {
2516            let _ = transactions.poll_unpin(cx);
2517            Poll::Ready(())
2518        })
2519        .await;
2520        assert!(pool.is_empty());
2521        handle.terminate().await;
2522    }
2523
2524    #[tokio::test(flavor = "multi_thread")]
2525    async fn test_tx_broadcasts_through_two_syncs() {
2526        reth_tracing::init_test_tracing();
2527        let net = Testnet::create(3).await;
2528
2529        let mut handles = net.handles();
2530        let handle0 = handles.next().unwrap();
2531        let handle1 = handles.next().unwrap();
2532
2533        drop(handles);
2534        let handle = net.spawn();
2535
2536        let listener0 = handle0.event_listener();
2537        handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2538        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2539
2540        let client = NoopProvider::default();
2541        let pool = testing_pool();
2542        let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2543            .disable_discovery()
2544            .listener_port(0)
2545            .build(client);
2546        let transactions_manager_config = config.transactions_manager_config.clone();
2547        let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2548            .await
2549            .unwrap()
2550            .into_builder()
2551            .transactions(pool.clone(), transactions_manager_config)
2552            .split_with_handle();
2553
2554        tokio::task::spawn(network);
2555
2556        // go to syncing (pipeline sync) to idle and then to syncing (live)
2557        network_handle.update_sync_state(SyncState::Syncing);
2558        assert!(NetworkInfo::is_syncing(&network_handle));
2559        network_handle.update_sync_state(SyncState::Idle);
2560        assert!(!NetworkInfo::is_syncing(&network_handle));
2561        network_handle.update_sync_state(SyncState::Syncing);
2562        assert!(NetworkInfo::is_syncing(&network_handle));
2563
2564        // wait for all initiator connections
2565        let mut established = listener0.take(2);
2566        while let Some(ev) = established.next().await {
2567            match ev {
2568                NetworkEvent::ActivePeerSession { .. } |
2569                NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2570                    // to insert a new peer in transactions peerset
2571                    transactions.on_network_event(ev);
2572                }
2573                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2574                _ => {
2575                    error!("unexpected event {ev:?}")
2576                }
2577            }
2578        }
2579        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2580        let input = hex!(
2581            "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2582        );
2583        let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2584        transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2585            peer_id: *handle1.peer_id(),
2586            msg: Transactions(vec![signed_tx.clone()]),
2587        });
2588        poll_fn(|cx| {
2589            let _ = transactions.poll_unpin(cx);
2590            Poll::Ready(())
2591        })
2592        .await;
2593        assert!(!NetworkInfo::is_initially_syncing(&network_handle));
2594        assert!(NetworkInfo::is_syncing(&network_handle));
2595        assert!(!pool.is_empty());
2596        handle.terminate().await;
2597    }
2598
2599    // Ensure that the transaction manager correctly handles the `IncomingPooledTransactionHashes`
2600    // event and is able to retrieve the corresponding transactions.
2601    #[tokio::test(flavor = "multi_thread")]
2602    async fn test_handle_incoming_transactions_hashes() {
2603        reth_tracing::init_test_tracing();
2604
2605        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2606        let client = NoopProvider::default();
2607
2608        let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2609            // let OS choose port
2610            .listener_port(0)
2611            .disable_discovery()
2612            .build(client);
2613
2614        let pool = testing_pool();
2615
2616        let transactions_manager_config = config.transactions_manager_config.clone();
2617        let (_network_handle, _network, mut tx_manager, _) = NetworkManager::new(config)
2618            .await
2619            .unwrap()
2620            .into_builder()
2621            .transactions(pool.clone(), transactions_manager_config)
2622            .split_with_handle();
2623
2624        let peer_id_1 = PeerId::new([1; 64]);
2625        let eth_version = EthVersion::Eth66;
2626
2627        let txs = vec![TransactionSigned::new_unhashed(
2628            Transaction::Legacy(TxLegacy {
2629                chain_id: Some(4),
2630                nonce: 15u64,
2631                gas_price: 2200000000,
2632                gas_limit: 34811,
2633                to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2634                value: U256::from(1234u64),
2635                input: Default::default(),
2636            }),
2637            Signature::new(
2638                U256::from_str(
2639                    "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2640                )
2641                .unwrap(),
2642                U256::from_str(
2643                    "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2644                )
2645                .unwrap(),
2646                true,
2647            ),
2648        )];
2649
2650        let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
2651
2652        let (peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
2653        tx_manager.peers.insert(peer_id_1, peer_1);
2654
2655        assert!(pool.is_empty());
2656
2657        tx_manager.on_network_tx_event(NetworkTransactionEvent::IncomingPooledTransactionHashes {
2658            peer_id: peer_id_1,
2659            msg: NewPooledTransactionHashes::from(NewPooledTransactionHashes66::from(
2660                txs_hashes.clone(),
2661            )),
2662        });
2663
2664        // mock session of peer_1 receives request
2665        let req = to_mock_session_rx
2666            .recv()
2667            .await
2668            .expect("peer_1 session should receive request with buffered hashes");
2669        let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
2670        assert_eq!(request, GetPooledTransactions::from(txs_hashes.clone()));
2671
2672        let message: Vec<PooledTransactionVariant> = txs
2673            .into_iter()
2674            .map(|tx| {
2675                PooledTransactionVariant::try_from(tx)
2676                    .expect("Failed to convert MockTransaction to PooledTransaction")
2677            })
2678            .collect();
2679
2680        // return the transactions corresponding to the transaction hashes.
2681        response
2682            .send(Ok(PooledTransactions(message)))
2683            .expect("should send peer_1 response to tx manager");
2684
2685        // adance the transaction manager future
2686        poll_fn(|cx| {
2687            let _ = tx_manager.poll_unpin(cx);
2688            Poll::Ready(())
2689        })
2690        .await;
2691
2692        // ensure that the transactions corresponding to the transaction hashes have been
2693        // successfully retrieved and stored in the Pool.
2694        assert_eq!(pool.get_all(txs_hashes.clone()).len(), txs_hashes.len());
2695    }
2696
2697    #[tokio::test(flavor = "multi_thread")]
2698    async fn test_handle_incoming_transactions() {
2699        reth_tracing::init_test_tracing();
2700        let net = Testnet::create(3).await;
2701
2702        let mut handles = net.handles();
2703        let handle0 = handles.next().unwrap();
2704        let handle1 = handles.next().unwrap();
2705
2706        drop(handles);
2707        let handle = net.spawn();
2708
2709        let listener0 = handle0.event_listener();
2710
2711        handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2712        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2713
2714        let client = NoopProvider::default();
2715        let pool = testing_pool();
2716        let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2717            .disable_discovery()
2718            .listener_port(0)
2719            .build(client);
2720        let transactions_manager_config = config.transactions_manager_config.clone();
2721        let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2722            .await
2723            .unwrap()
2724            .into_builder()
2725            .transactions(pool.clone(), transactions_manager_config)
2726            .split_with_handle();
2727        tokio::task::spawn(network);
2728
2729        network_handle.update_sync_state(SyncState::Idle);
2730
2731        assert!(!NetworkInfo::is_syncing(&network_handle));
2732
2733        // wait for all initiator connections
2734        let mut established = listener0.take(2);
2735        while let Some(ev) = established.next().await {
2736            match ev {
2737                NetworkEvent::ActivePeerSession { .. } |
2738                NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2739                    // to insert a new peer in transactions peerset
2740                    transactions.on_network_event(ev);
2741                }
2742                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2743                ev => {
2744                    error!("unexpected event {ev:?}")
2745                }
2746            }
2747        }
2748        // random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
2749        let input = hex!(
2750            "02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76"
2751        );
2752        let signed_tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2753        transactions.on_network_tx_event(NetworkTransactionEvent::IncomingTransactions {
2754            peer_id: *handle1.peer_id(),
2755            msg: Transactions(vec![signed_tx.clone()]),
2756        });
2757        assert!(transactions
2758            .transactions_by_peers
2759            .get(signed_tx.tx_hash())
2760            .unwrap()
2761            .contains(handle1.peer_id()));
2762
2763        // advance the transaction manager future
2764        poll_fn(|cx| {
2765            let _ = transactions.poll_unpin(cx);
2766            Poll::Ready(())
2767        })
2768        .await;
2769
2770        assert!(!pool.is_empty());
2771        assert!(pool.get(signed_tx.tx_hash()).is_some());
2772        handle.terminate().await;
2773    }
2774
2775    #[tokio::test(flavor = "multi_thread")]
2776    async fn test_session_closed_cleans_transaction_peer_state() {
2777        let (mut tx_manager, _network) = new_tx_manager().await;
2778        let peer_id = PeerId::new([1; 64]);
2779        let fallback_peer = PeerId::new([2; 64]);
2780        let (peer, _) = new_mock_session(peer_id, EthVersion::Eth66);
2781        let hash_shared = B256::from_slice(&[1; 32]);
2782
2783        tx_manager.peers.insert(peer_id, peer);
2784        buffer_hash_to_tx_fetcher(
2785            &mut tx_manager.transaction_fetcher,
2786            hash_shared,
2787            peer_id,
2788            0,
2789            None,
2790        );
2791        buffer_hash_to_tx_fetcher(
2792            &mut tx_manager.transaction_fetcher,
2793            hash_shared,
2794            fallback_peer,
2795            0,
2796            None,
2797        );
2798        tx_manager.transaction_fetcher.active_peers.insert(peer_id, 1);
2799
2800        tx_manager.on_network_event(NetworkEvent::Peer(PeerEvent::SessionClosed {
2801            peer_id,
2802            reason: None,
2803        }));
2804
2805        // peer removed from peers map and active_peers
2806        assert!(!tx_manager.peers.contains_key(&peer_id));
2807        assert!(tx_manager.transaction_fetcher.active_peers.peek(&peer_id).is_none());
2808        // fallback peer is still available for the hash
2809        assert_eq!(
2810            tx_manager.transaction_fetcher.get_idle_peer_for(hash_shared),
2811            Some(&fallback_peer)
2812        );
2813    }
2814
2815    #[tokio::test(flavor = "multi_thread")]
2816    async fn test_bad_blob_sidecar_not_cached_as_bad_import() {
2817        let (mut tx_manager, _network) = new_tx_manager().await;
2818        let peer_id = PeerId::new([1; 64]);
2819        let hash = B256::from_slice(&[1; 32]);
2820
2821        tx_manager.network.update_sync_state(SyncState::Idle);
2822        tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2823
2824        let err = PoolError::new(
2825            hash,
2826            InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::InvalidEip4844Blob(
2827                BlobTransactionValidationError::InvalidProof,
2828            )),
2829        );
2830
2831        tx_manager.on_bad_import(err);
2832
2833        assert!(!tx_manager.bad_imports.contains(&hash));
2834    }
2835
2836    #[tokio::test(flavor = "multi_thread")]
2837    async fn test_missing_blob_sidecar_not_cached_as_bad_import() {
2838        let (mut tx_manager, _network) = new_tx_manager().await;
2839        let peer_id = PeerId::new([1; 64]);
2840        let hash = B256::from_slice(&[3; 32]);
2841
2842        tx_manager.network.update_sync_state(SyncState::Idle);
2843        tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2844
2845        let err = PoolError::new(
2846            hash,
2847            InvalidPoolTransactionError::Eip4844(
2848                Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
2849            ),
2850        );
2851
2852        tx_manager.on_bad_import(err);
2853
2854        assert!(!tx_manager.bad_imports.contains(&hash));
2855    }
2856
2857    #[tokio::test(flavor = "multi_thread")]
2858    async fn test_non_blob_sidecar_error_still_cached_as_bad_import() {
2859        let (mut tx_manager, _network) = new_tx_manager().await;
2860        let peer_id = PeerId::new([1; 64]);
2861        let hash = B256::from_slice(&[2; 32]);
2862
2863        tx_manager.network.update_sync_state(SyncState::Idle);
2864        tx_manager.transactions_by_peers.insert(hash, smallvec::smallvec![peer_id]);
2865
2866        let err = PoolError::new(
2867            hash,
2868            InvalidPoolTransactionError::Eip4844(Eip4844PoolTransactionError::NoEip4844Blobs),
2869        );
2870
2871        tx_manager.on_bad_import(err);
2872
2873        assert!(tx_manager.bad_imports.contains(&hash));
2874    }
2875
2876    #[tokio::test(flavor = "multi_thread")]
2877    async fn test_on_get_pooled_transactions_network() {
2878        reth_tracing::init_test_tracing();
2879        let net = Testnet::create(2).await;
2880
2881        let mut handles = net.handles();
2882        let handle0 = handles.next().unwrap();
2883        let handle1 = handles.next().unwrap();
2884
2885        drop(handles);
2886        let handle = net.spawn();
2887
2888        let listener0 = handle0.event_listener();
2889
2890        handle0.add_peer(*handle1.peer_id(), handle1.local_addr());
2891        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
2892
2893        let client = NoopProvider::default();
2894        let pool = testing_pool();
2895        let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
2896            .disable_discovery()
2897            .listener_port(0)
2898            .build(client);
2899        let transactions_manager_config = config.transactions_manager_config.clone();
2900        let (network_handle, network, mut transactions, _) = NetworkManager::new(config)
2901            .await
2902            .unwrap()
2903            .into_builder()
2904            .transactions(pool.clone(), transactions_manager_config)
2905            .split_with_handle();
2906        tokio::task::spawn(network);
2907
2908        network_handle.update_sync_state(SyncState::Idle);
2909
2910        assert!(!NetworkInfo::is_syncing(&network_handle));
2911
2912        // wait for all initiator connections
2913        let mut established = listener0.take(2);
2914        while let Some(ev) = established.next().await {
2915            match ev {
2916                NetworkEvent::ActivePeerSession { .. } |
2917                NetworkEvent::Peer(PeerEvent::SessionEstablished(_)) => {
2918                    transactions.on_network_event(ev);
2919                }
2920                NetworkEvent::Peer(PeerEvent::PeerAdded(_peer_id)) => {}
2921                ev => {
2922                    error!("unexpected event {ev:?}")
2923                }
2924            }
2925        }
2926        handle.terminate().await;
2927
2928        let tx = MockTransaction::eip1559();
2929        let _ = transactions
2930            .pool
2931            .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
2932            .await;
2933
2934        let request = GetPooledTransactions(vec![*tx.get_hash()]);
2935
2936        let (send, receive) =
2937            oneshot::channel::<RequestResult<PooledTransactions<PooledTransactionVariant>>>();
2938
2939        transactions.on_network_tx_event(NetworkTransactionEvent::GetPooledTransactions {
2940            peer_id: *handle1.peer_id(),
2941            request,
2942            response: send,
2943        });
2944
2945        match receive.await.unwrap() {
2946            Ok(PooledTransactions(transactions)) => {
2947                assert_eq!(transactions.len(), 1);
2948            }
2949            Err(e) => {
2950                panic!("error: {e:?}");
2951            }
2952        }
2953    }
2954
2955    // Ensure that when the remote peer only returns part of the requested transactions, the
2956    // replied transactions are removed from the `tx_fetcher`, while the unresponsive ones are
2957    // re-buffered.
2958    #[tokio::test]
2959    async fn test_partially_tx_response() {
2960        reth_tracing::init_test_tracing();
2961
2962        let mut tx_manager = new_tx_manager().await.0;
2963        let tx_fetcher = &mut tx_manager.transaction_fetcher;
2964
2965        let peer_id_1 = PeerId::new([1; 64]);
2966        let eth_version = EthVersion::Eth66;
2967
2968        let txs = vec![
2969            TransactionSigned::new_unhashed(
2970                Transaction::Legacy(TxLegacy {
2971                    chain_id: Some(4),
2972                    nonce: 15u64,
2973                    gas_price: 2200000000,
2974                    gas_limit: 34811,
2975                    to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
2976                    value: U256::from(1234u64),
2977                    input: Default::default(),
2978                }),
2979                Signature::new(
2980                    U256::from_str(
2981                        "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
2982                    )
2983                    .unwrap(),
2984                    U256::from_str(
2985                        "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
2986                    )
2987                    .unwrap(),
2988                    true,
2989                ),
2990            ),
2991            TransactionSigned::new_unhashed(
2992                Transaction::Eip1559(TxEip1559 {
2993                    chain_id: 4,
2994                    nonce: 26u64,
2995                    max_priority_fee_per_gas: 1500000000,
2996                    max_fee_per_gas: 1500000013,
2997                    gas_limit: MIN_TRANSACTION_GAS,
2998                    to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
2999                    value: U256::from(3000000000000000000u64),
3000                    input: Default::default(),
3001                    access_list: Default::default(),
3002                }),
3003                Signature::new(
3004                    U256::from_str(
3005                        "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
3006                    )
3007                    .unwrap(),
3008                    U256::from_str(
3009                        "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
3010                    )
3011                    .unwrap(),
3012                    true,
3013                ),
3014            ),
3015        ];
3016
3017        let txs_hashes: Vec<B256> = txs.iter().map(|tx| *tx.hash()).collect();
3018
3019        let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3020        // mark hashes as seen by peer so it can fish them out from the cache for hashes pending
3021        // fetch
3022        peer_1.seen_transactions.insert(txs_hashes[0]);
3023        peer_1.seen_transactions.insert(txs_hashes[1]);
3024        tx_manager.peers.insert(peer_id_1, peer_1);
3025
3026        buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[0], peer_id_1, 0, None);
3027        buffer_hash_to_tx_fetcher(tx_fetcher, txs_hashes[1], peer_id_1, 0, None);
3028
3029        // peer_1 is idle
3030        assert!(tx_fetcher.is_idle(&peer_id_1));
3031        assert_eq!(tx_fetcher.active_peers.len(), 0);
3032
3033        // sends requests for buffered hashes to peer_1
3034        tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3035
3036        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3037        // as long as request is in flight peer_1 is not idle
3038        assert!(!tx_fetcher.is_idle(&peer_id_1));
3039        assert_eq!(tx_fetcher.active_peers.len(), 1);
3040
3041        // mock session of peer_1 receives request
3042        let req = to_mock_session_rx
3043            .recv()
3044            .await
3045            .expect("peer_1 session should receive request with buffered hashes");
3046        let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3047
3048        let message: Vec<PooledTransactionVariant> = txs
3049            .into_iter()
3050            .take(1)
3051            .map(|tx| {
3052                PooledTransactionVariant::try_from(tx)
3053                    .expect("Failed to convert MockTransaction to PooledTransaction")
3054            })
3055            .collect();
3056        // response partial request
3057        response
3058            .send(Ok(PooledTransactions(message)))
3059            .expect("should send peer_1 response to tx manager");
3060        let Some(FetchEvent::TransactionsFetched { peer_id, .. }) = tx_fetcher.next().await else {
3061            unreachable!()
3062        };
3063
3064        // request has resolved, peer_1 is idle again
3065        assert!(tx_fetcher.is_idle(&peer_id));
3066        assert_eq!(tx_fetcher.active_peers.len(), 0);
3067        // failing peer_1's request buffers requested hashes for retry.
3068        assert_eq!(tx_fetcher.num_pending_hashes(), 1);
3069    }
3070
3071    #[tokio::test]
3072    async fn test_max_retries_tx_request() {
3073        reth_tracing::init_test_tracing();
3074
3075        let mut tx_manager = new_tx_manager().await.0;
3076        let tx_fetcher = &mut tx_manager.transaction_fetcher;
3077
3078        let peer_id_1 = PeerId::new([1; 64]);
3079        let peer_id_2 = PeerId::new([2; 64]);
3080        let eth_version = EthVersion::Eth66;
3081        let seen_hashes = [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32])];
3082
3083        let (mut peer_1, mut to_mock_session_rx) = new_mock_session(peer_id_1, eth_version);
3084        // mark hashes as seen by peer so it can fish them out from the cache for hashes pending
3085        // fetch
3086        peer_1.seen_transactions.insert(seen_hashes[0]);
3087        peer_1.seen_transactions.insert(seen_hashes[1]);
3088        tx_manager.peers.insert(peer_id_1, peer_1);
3089
3090        // hashes are seen and currently not inflight, with one fallback peer, and are buffered
3091        // for first retry in reverse order to make index 0 lru
3092        let retries = 1;
3093        buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[1], peer_id_1, retries, None);
3094        buffer_hash_to_tx_fetcher(tx_fetcher, seen_hashes[0], peer_id_1, retries, None);
3095
3096        // peer_1 is idle
3097        assert!(tx_fetcher.is_idle(&peer_id_1));
3098        assert_eq!(tx_fetcher.active_peers.len(), 0);
3099
3100        // sends request for buffered hashes to peer_1
3101        tx_fetcher.on_fetch_pending_hashes(&tx_manager.peers, |_| true);
3102
3103        let tx_fetcher = &mut tx_manager.transaction_fetcher;
3104
3105        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3106        // as long as request is in inflight peer_1 is not idle
3107        assert!(!tx_fetcher.is_idle(&peer_id_1));
3108        assert_eq!(tx_fetcher.active_peers.len(), 1);
3109
3110        // mock session of peer_1 receives request
3111        let req = to_mock_session_rx
3112            .recv()
3113            .await
3114            .expect("peer_1 session should receive request with buffered hashes");
3115        let PeerRequest::GetPooledTransactions { request, response } = req else { unreachable!() };
3116        let GetPooledTransactions(hashes) = request;
3117
3118        let hashes = hashes.into_iter().collect::<B256Set>();
3119
3120        assert_eq!(hashes, seen_hashes.into_iter().collect::<B256Set>());
3121
3122        // fail request to peer_1
3123        response
3124            .send(Err(RequestError::BadResponse))
3125            .expect("should send peer_1 response to tx manager");
3126        let Some(FetchEvent::FetchError { peer_id, .. }) = tx_fetcher.next().await else {
3127            unreachable!()
3128        };
3129
3130        // request has resolved, peer_1 is idle again
3131        assert!(tx_fetcher.is_idle(&peer_id));
3132        assert_eq!(tx_fetcher.active_peers.len(), 0);
3133        // failing peer_1's request buffers requested hashes for retry
3134        assert_eq!(tx_fetcher.num_pending_hashes(), 2);
3135
3136        let (peer_2, mut to_mock_session_rx) = new_mock_session(peer_id_2, eth_version);
3137        tx_manager.peers.insert(peer_id_2, peer_2);
3138
3139        // peer_2 announces same hashes as peer_1
3140        let msg =
3141            NewPooledTransactionHashes::Eth66(NewPooledTransactionHashes66(seen_hashes.to_vec()));
3142        tx_manager.on_new_pooled_transaction_hashes(peer_id_2, msg);
3143
3144        let tx_fetcher = &mut tx_manager.transaction_fetcher;
3145
3146        // peer_2 should be in active_peers.
3147        assert_eq!(tx_fetcher.active_peers.len(), 1);
3148
3149        // since hashes are already seen, no changes to length of unknown hashes
3150        assert_eq!(tx_fetcher.num_all_hashes(), 2);
3151        // but hashes are taken out of buffer and packed into request to peer_2
3152        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3153
3154        // mock session of peer_2 receives request
3155        let req = to_mock_session_rx
3156            .recv()
3157            .await
3158            .expect("peer_2 session should receive request with buffered hashes");
3159        let PeerRequest::GetPooledTransactions { response, .. } = req else { unreachable!() };
3160
3161        // report failed request to tx manager
3162        response
3163            .send(Err(RequestError::BadResponse))
3164            .expect("should send peer_2 response to tx manager");
3165        let Some(FetchEvent::FetchError { .. }) = tx_fetcher.next().await else { unreachable!() };
3166
3167        // `MAX_REQUEST_RETRIES_PER_TX_HASH`, 2, for hashes reached so this time won't be buffered
3168        // for retry
3169        assert_eq!(tx_fetcher.num_pending_hashes(), 0);
3170        assert_eq!(tx_fetcher.active_peers.len(), 0);
3171    }
3172
3173    #[test]
3174    fn test_direct_propagation_transaction_uses_2718_size() {
3175        let mut tx_gen = TransactionGenerator::new(rand::rng());
3176        let tx = tx_gen.gen_eip1559();
3177        let expected_size = tx.encode_2718_len();
3178
3179        let tx = PropagateTransaction::new(tx);
3180
3181        assert_eq!(tx.propagation_size(), expected_size);
3182    }
3183
3184    #[test]
3185    fn test_transaction_builder_empty() {
3186        let mut builder = PropagateTransactionsBuilder::pooled(EthVersion::Eth68, 0);
3187        assert!(builder.is_empty());
3188
3189        let mut tx_gen = TransactionGenerator::new(rand::rng());
3190        let tx =
3191            PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3192        builder.push(&tx);
3193        assert!(!builder.is_empty());
3194
3195        let txs = builder.build();
3196        assert!(txs.full.is_none());
3197        let txs = txs.pooled.unwrap();
3198        assert_eq!(txs.len(), 1);
3199    }
3200
3201    #[test]
3202    fn test_pooled_propagation_transaction_encoder_length_matches_network_encoding() {
3203        let mut tx_gen = TransactionGenerator::new(rand::rng());
3204        let tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3205        let pooled = PropagatePooledTransactionEncoder::new(tx);
3206
3207        let mut pooled_encoded = Vec::new();
3208        pooled.encode(&mut pooled_encoded);
3209        assert_eq!(pooled.length(), pooled_encoded.len());
3210
3211        let broadcast = BroadcastPoolTransactions(vec![LazyEncoded::new(pooled)]);
3212        let mut first_encoded = Vec::new();
3213        broadcast.encode(&mut first_encoded);
3214        let mut second_encoded = Vec::new();
3215        broadcast.encode(&mut second_encoded);
3216        assert_eq!(first_encoded, second_encoded);
3217
3218        let mut encoded = first_encoded.as_slice();
3219        let decoded = Transactions::<TransactionSigned>::decode(&mut encoded).unwrap();
3220        assert_eq!(decoded.len(), 1);
3221        assert!(encoded.is_empty());
3222    }
3223
3224    #[test]
3225    fn test_transaction_builder_large() {
3226        let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3227        assert!(builder.is_empty());
3228
3229        let mut tx_gen = TransactionGenerator::new(rand::rng());
3230        let mut tx = tx_gen.gen_eip1559_pooled();
3231        // create a transaction that still fits
3232        tx.encoded_length = DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE + 1;
3233        let tx = PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx));
3234        builder.push(&tx);
3235        assert!(!builder.is_empty());
3236
3237        let txs = builder.clone().build();
3238        assert!(txs.pooled.is_none());
3239        let txs = txs.full.unwrap();
3240        assert_eq!(txs.len(), 1);
3241
3242        builder.push(&tx);
3243
3244        let txs = builder.clone().build();
3245        let pooled = txs.pooled.unwrap();
3246        assert_eq!(pooled.len(), 1);
3247        let txs = txs.full.unwrap();
3248        assert_eq!(txs.len(), 1);
3249    }
3250
3251    #[test]
3252    fn test_transaction_builder_eip4844() {
3253        let mut builder = PropagateTransactionsBuilder::full(EthVersion::Eth68, 0);
3254        assert!(builder.is_empty());
3255
3256        let mut tx_gen = TransactionGenerator::new(rand::rng());
3257        let tx =
3258            PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled()));
3259        builder.push(&tx);
3260        assert!(!builder.is_empty());
3261
3262        let txs = builder.clone().build();
3263        assert!(txs.full.is_none());
3264        let txs = txs.pooled.unwrap();
3265        assert_eq!(txs.len(), 1);
3266
3267        let tx =
3268            PropagateTransaction::pool_tx(valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled()));
3269        builder.push(&tx);
3270
3271        let txs = builder.clone().build();
3272        let pooled = txs.pooled.unwrap();
3273        assert_eq!(pooled.len(), 1);
3274        let txs = txs.full.unwrap();
3275        assert_eq!(txs.len(), 1);
3276    }
3277
3278    #[tokio::test]
3279    async fn test_propagate_full() {
3280        reth_tracing::init_test_tracing();
3281
3282        let (mut tx_manager, network) = new_eth_tx_manager().await;
3283        let peer_id = PeerId::random();
3284
3285        // ensure not syncing
3286        network.handle().update_sync_state(SyncState::Idle);
3287
3288        // mock a peer
3289        let (tx, _rx) = mpsc::channel::<PeerRequest>(1);
3290
3291        let session_info = SessionInfo {
3292            peer_id,
3293            remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
3294            client_version: Arc::from(""),
3295            capabilities: Arc::new(vec![].into()),
3296            status: Arc::new(Default::default()),
3297            version: EthVersion::Eth68,
3298            peer_kind: PeerKind::Basic,
3299        };
3300        let messages: PeerRequestSender<PeerRequest> = PeerRequestSender::new(peer_id, tx);
3301        tx_manager
3302            .on_network_event(NetworkEvent::ActivePeerSession { info: session_info, messages });
3303        let mut propagate = vec![];
3304        let mut tx_gen = TransactionGenerator::new(rand::rng());
3305        let eip1559_tx = valid_eth_pool_transaction(tx_gen.gen_eip1559_pooled());
3306        propagate.push(eip1559_tx.clone());
3307        let eip4844_tx = valid_eth_pool_transaction(tx_gen.gen_eip4844_pooled());
3308        propagate.push(eip4844_tx.clone());
3309
3310        let propagated = tx_manager.propagate_transactions(
3311            propagate.clone().into_iter().map(PropagateTransaction::pool_tx).collect(),
3312            PropagationMode::Basic,
3313        );
3314        assert_eq!(propagated.len(), 2);
3315        let prop_txs = propagated.get(eip1559_tx.transaction.hash()).unwrap();
3316        assert_eq!(prop_txs.len(), 1);
3317        assert!(prop_txs[0].is_full());
3318
3319        let prop_txs = propagated.get(eip4844_tx.transaction.hash()).unwrap();
3320        assert_eq!(prop_txs.len(), 1);
3321        assert!(prop_txs[0].is_hash());
3322
3323        let peer = tx_manager.peers.get(&peer_id).unwrap();
3324        assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3325        assert!(peer.seen_transactions.contains(eip1559_tx.transaction.hash()));
3326        peer.seen_transactions.contains(eip4844_tx.transaction.hash());
3327
3328        // propagate again
3329        let propagated = tx_manager.propagate_transactions(
3330            propagate.into_iter().map(PropagateTransaction::pool_tx).collect(),
3331            PropagationMode::Basic,
3332        );
3333        assert!(propagated.is_empty());
3334    }
3335
3336    #[tokio::test]
3337    async fn test_truncated_hash_announcement_not_marked_seen() {
3338        reth_tracing::init_test_tracing();
3339
3340        let (mut tx_manager, network) = new_eth_tx_manager().await;
3341        // all peers receive hash announcements only
3342        tx_manager.config.propagation_mode = TransactionPropagationMode::Max(0);
3343
3344        // ensure not syncing
3345        network.handle().update_sync_state(SyncState::Idle);
3346
3347        let peer_id = PeerId::random();
3348        let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3349        tx_manager.peers.insert(peer_id, peer);
3350
3351        // one more transaction than fits into a single hashes broadcast message
3352        let mut tx_gen = TransactionGenerator::new(rand::rng());
3353        let txs = (0..=SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE)
3354            .map(|nonce| {
3355                valid_eth_pool_transaction(gen_eip1559_pooled_with_nonce(&mut tx_gen, nonce as u64))
3356            })
3357            .collect::<Vec<_>>();
3358        let last_sent = *txs[txs.len() - 2].hash();
3359        let truncated = *txs[txs.len() - 1].hash();
3360
3361        let propagated = tx_manager.propagate_transactions(
3362            txs.into_iter().map(PropagateTransaction::pool_tx).collect(),
3363            PropagationMode::Basic,
3364        );
3365
3366        // the truncated hash was not sent, so it must not be tracked as seen by the peer
3367        assert!(propagated.get(&truncated).is_none());
3368        let peer = tx_manager.peers.get(&peer_id).unwrap();
3369        assert!(!peer.seen_transactions.contains(&truncated));
3370        assert!(peer.seen_transactions.contains(&last_sent));
3371    }
3372
3373    #[tokio::test]
3374    async fn test_propagate_pending_txs_while_initially_syncing() {
3375        reth_tracing::init_test_tracing();
3376
3377        let (mut tx_manager, network) = new_eth_tx_manager().await;
3378        let peer_id = PeerId::random();
3379
3380        // Keep the node in initial sync mode.
3381        network.handle().update_sync_state(SyncState::Syncing);
3382        assert!(NetworkInfo::is_initially_syncing(&network.handle()));
3383
3384        // Add a peer so propagation has a destination.
3385        let (peer, _rx) = new_mock_session(peer_id, EthVersion::Eth68);
3386        tx_manager.peers.insert(peer_id, peer);
3387
3388        let mut tx_gen = TransactionGenerator::new(rand::rng());
3389        let tx = gen_eip1559_pooled_with_nonce(&mut tx_gen, 0);
3390        let tx_hash = *tx.hash();
3391        tx_manager
3392            .pool
3393            .add_transaction(reth_transaction_pool::TransactionOrigin::External, tx.clone())
3394            .await
3395            .expect("transaction should be accepted into the pool");
3396
3397        tx_manager.on_new_pending_transactions(vec![tx_hash]);
3398
3399        let peer = tx_manager.peers.get(&peer_id).expect("peer should exist");
3400        assert!(peer.seen_transactions.contains(&tx_hash));
3401    }
3402
3403    #[tokio::test]
3404    async fn test_relaxed_filter_ignores_unknown_tx_types() {
3405        reth_tracing::init_test_tracing();
3406
3407        let transactions_manager_config = TransactionsManagerConfig::default();
3408
3409        let propagation_policy = TransactionPropagationKind::default();
3410        let announcement_policy = RelaxedEthAnnouncementFilter::default();
3411
3412        let policy_bundle = NetworkPolicies::new(propagation_policy, announcement_policy);
3413
3414        let pool = testing_pool();
3415        let secret_key = SecretKey::new(&mut rand_08::thread_rng());
3416        let client = NoopProvider::default();
3417
3418        let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
3419            .listener_port(0)
3420            .disable_discovery()
3421            .build(client.clone());
3422
3423        let mut network_manager = NetworkManager::new(network_config).await.unwrap();
3424        let (to_tx_manager_tx, from_network_rx) =
3425            reth_metrics::common::mpsc::memory_bounded_channel::<
3426                NetworkTransactionEvent<EthNetworkPrimitives>,
3427            >(
3428                crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
3429                "test_tx_channel",
3430            );
3431        network_manager.set_transactions(to_tx_manager_tx);
3432        let network_handle = network_manager.handle().clone();
3433        let network_service_handle = tokio::spawn(network_manager);
3434
3435        let mut tx_manager = TransactionsManager::<TestPool, EthNetworkPrimitives>::with_policy(
3436            network_handle.clone(),
3437            pool.clone(),
3438            from_network_rx,
3439            transactions_manager_config,
3440            policy_bundle,
3441        );
3442
3443        let peer_id = PeerId::random();
3444        let eth_version = EthVersion::Eth68;
3445        let (mock_peer_metadata, mut mock_session_rx) = new_mock_session(peer_id, eth_version);
3446        tx_manager.peers.insert(peer_id, mock_peer_metadata);
3447
3448        let mut tx_factory = MockTransactionFactory::default();
3449
3450        let valid_known_tx = tx_factory.create_eip1559();
3451        let known_tx_signed: Arc<ValidPoolTransaction<MockTransaction>> = Arc::new(valid_known_tx);
3452
3453        let known_tx_hash = *known_tx_signed.hash();
3454        let known_tx_type_byte = known_tx_signed.transaction.tx_type();
3455        let known_tx_size = known_tx_signed.encoded_length();
3456
3457        let unknown_tx_hash = B256::random();
3458        let unknown_tx_type_byte = 0xff_u8;
3459        let unknown_tx_size = 150;
3460
3461        let announcement_msg = NewPooledTransactionHashes::Eth68(NewPooledTransactionHashes68 {
3462            types: vec![known_tx_type_byte, unknown_tx_type_byte],
3463            sizes: vec![known_tx_size, unknown_tx_size],
3464            hashes: vec![known_tx_hash, unknown_tx_hash],
3465        });
3466
3467        tx_manager.on_new_pooled_transaction_hashes(peer_id, announcement_msg);
3468
3469        poll_fn(|cx| {
3470            let _ = tx_manager.poll_unpin(cx);
3471            Poll::Ready(())
3472        })
3473        .await;
3474
3475        let mut requested_hashes_in_getpooled = B256Set::default();
3476        let mut unexpected_request_received = false;
3477
3478        match tokio::time::timeout(std::time::Duration::from_millis(200), mock_session_rx.recv())
3479            .await
3480        {
3481            Ok(Some(PeerRequest::GetPooledTransactions { request, response: tx_response_ch })) => {
3482                let GetPooledTransactions(hashes) = request;
3483                for hash in hashes {
3484                    requested_hashes_in_getpooled.insert(hash);
3485                }
3486                let _ = tx_response_ch.send(Ok(PooledTransactions(vec![])));
3487            }
3488            Ok(Some(other_request)) => {
3489                tracing::error!(?other_request, "Received unexpected PeerRequest type");
3490                unexpected_request_received = true;
3491            }
3492            Ok(None) => tracing::info!("Mock session channel closed or no request received."),
3493            Err(_timeout_err) => {
3494                tracing::info!("Timeout: No GetPooledTransactions request received.")
3495            }
3496        }
3497
3498        assert!(
3499            requested_hashes_in_getpooled.contains(&known_tx_hash),
3500            "Should have requested the known EIP-1559 transaction. Requested: {requested_hashes_in_getpooled:?}"
3501        );
3502        assert!(
3503            !requested_hashes_in_getpooled.contains(&unknown_tx_hash),
3504            "Should NOT have requested the unknown transaction type. Requested: {requested_hashes_in_getpooled:?}"
3505        );
3506        assert!(
3507            !unexpected_request_received,
3508            "An unexpected P2P request was received by the mock peer."
3509        );
3510
3511        network_service_handle.abort();
3512    }
3513}