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