reth_transaction_pool/
traits.rs

1//! Transaction Pool Traits and Types
2//!
3//! This module defines the core abstractions for transaction pool implementations,
4//! handling the complexity of different transaction representations across the
5//! network, mempool, and the chain itself.
6//!
7//! ## Key Concepts
8//!
9//! ### Transaction Representations
10//!
11//! Transactions exist in different formats throughout their lifecycle:
12//!
13//! 1. **Consensus Format** ([`PoolTransaction::Consensus`])
14//!    - The canonical format stored in blocks
15//!    - Minimal size for efficient storage
16//!    - Example: EIP-4844 transactions store only blob hashes: ([`TransactionSigned::Eip4844`])
17//!
18//! 2. **Pooled Format** ([`PoolTransaction::Pooled`])
19//!    - Extended format for network propagation
20//!    - Includes additional validation data
21//!    - Example: EIP-4844 transactions include full blob sidecars: ([`PooledTransactionVariant`])
22//!
23//! ### Type Relationships
24//!
25//! ```text
26//! NodePrimitives::SignedTx  ←──   NetworkPrimitives::BroadcastedTransaction
27//!        │                              │
28//!        │ (consensus format)           │ (announced to peers)
29//!        │                              │
30//!        └──────────┐  ┌────────────────┘
31//!                   ▼  ▼
32//!            PoolTransaction::Consensus
33//!                   │ ▲
34//!                   │ │ from pooled (always succeeds)
35//!                   │ │
36//!                   ▼ │ try_from consensus (may fail)
37//!            PoolTransaction::Pooled  ←──→  NetworkPrimitives::PooledTransaction
38//!                                             (sent on request)
39//! ```
40//!
41//! ### Special Cases
42//!
43//! #### EIP-4844 Blob Transactions
44//! - Consensus format: Only blob hashes (32 bytes each)
45//! - Pooled format: Full blobs + commitments + proofs (large data per blob)
46//! - Network behavior: Not broadcast automatically, only sent on explicit request
47//!
48//! #### Optimism Deposit Transactions
49//! - Only exist in consensus format
50//! - Never enter the mempool (system transactions)
51//! - Conversion from consensus to pooled always fails
52
53use crate::{
54    blobstore::BlobStoreError,
55    error::{InvalidPoolTransactionError, PoolError, PoolResult},
56    pool::{
57        state::SubPool, BestTransactionFilter, NewTransactionEvent, TransactionEvents,
58        TransactionListenerKind,
59    },
60    validate::ValidPoolTransaction,
61    AddedTransactionOutcome, AllTransactionsEvents,
62};
63use alloy_consensus::{error::ValueError, transaction::TxHashRef, BlockHeader, Signed, Typed2718};
64use alloy_eips::{
65    eip2718::{Encodable2718, WithEncoded},
66    eip2930::AccessList,
67    eip4844::{
68        env_settings::KzgSettings, BlobAndProofV1, BlobAndProofV2, BlobTransactionValidationError,
69    },
70    eip7594::BlobTransactionSidecarVariant,
71    eip7702::SignedAuthorization,
72};
73use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256};
74use futures_util::{ready, Stream};
75use reth_eth_wire_types::HandleMempoolData;
76use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
77use reth_execution_types::ChangedAccount;
78use reth_primitives_traits::{Block, InMemorySize, Recovered, SealedBlock, SignedTransaction};
79use serde::{Deserialize, Serialize};
80use std::{
81    collections::{HashMap, HashSet},
82    fmt,
83    fmt::Debug,
84    future::Future,
85    pin::Pin,
86    sync::Arc,
87    task::{Context, Poll},
88};
89use tokio::sync::mpsc::Receiver;
90
91/// The `PeerId` type.
92pub type PeerId = alloy_primitives::B512;
93
94/// Helper type alias to access [`PoolTransaction`] for a given [`TransactionPool`].
95pub type PoolTx<P> = <P as TransactionPool>::Transaction;
96/// Helper type alias to access [`PoolTransaction::Consensus`] for a given [`TransactionPool`].
97pub type PoolConsensusTx<P> = <<P as TransactionPool>::Transaction as PoolTransaction>::Consensus;
98
99/// Helper type alias to access [`PoolTransaction::Pooled`] for a given [`TransactionPool`].
100pub type PoolPooledTx<P> = <<P as TransactionPool>::Transaction as PoolTransaction>::Pooled;
101
102/// General purpose abstraction of a transaction-pool.
103///
104/// This is intended to be used by API-consumers such as RPC that need inject new incoming,
105/// unverified transactions. And by block production that needs to get transactions to execute in a
106/// new block.
107///
108/// Note: This requires `Clone` for convenience, since it is assumed that this will be implemented
109/// for a wrapped `Arc` type, see also [`Pool`](crate::Pool).
110#[auto_impl::auto_impl(&, Arc)]
111pub trait TransactionPool: Clone + Debug + Send + Sync {
112    /// The transaction type of the pool
113    type Transaction: EthPoolTransaction;
114
115    /// Returns stats about the pool and all sub-pools.
116    fn pool_size(&self) -> PoolSize;
117
118    /// Returns the block the pool is currently tracking.
119    ///
120    /// This tracks the block that the pool has last seen.
121    fn block_info(&self) -> BlockInfo;
122
123    /// Imports an _external_ transaction.
124    ///
125    /// This is intended to be used by the network to insert incoming transactions received over the
126    /// p2p network.
127    ///
128    /// Consumer: P2P
129    fn add_external_transaction(
130        &self,
131        transaction: Self::Transaction,
132    ) -> impl Future<Output = PoolResult<AddedTransactionOutcome>> + Send {
133        self.add_transaction(TransactionOrigin::External, transaction)
134    }
135
136    /// Imports all _external_ transactions
137    ///
138    /// Consumer: Utility
139    fn add_external_transactions(
140        &self,
141        transactions: Vec<Self::Transaction>,
142    ) -> impl Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send {
143        self.add_transactions(TransactionOrigin::External, transactions)
144    }
145
146    /// Adds an _unvalidated_ transaction into the pool and subscribe to state changes.
147    ///
148    /// This is the same as [`TransactionPool::add_transaction`] but returns an event stream for the
149    /// given transaction.
150    ///
151    /// Consumer: Custom
152    fn add_transaction_and_subscribe(
153        &self,
154        origin: TransactionOrigin,
155        transaction: Self::Transaction,
156    ) -> impl Future<Output = PoolResult<TransactionEvents>> + Send;
157
158    /// Adds an _unvalidated_ transaction into the pool.
159    ///
160    /// Consumer: RPC
161    fn add_transaction(
162        &self,
163        origin: TransactionOrigin,
164        transaction: Self::Transaction,
165    ) -> impl Future<Output = PoolResult<AddedTransactionOutcome>> + Send;
166
167    /// Adds the given _unvalidated_ transactions into the pool.
168    ///
169    /// All transactions will use the same `origin`.
170    ///
171    /// Returns a list of results.
172    ///
173    /// Consumer: RPC
174    fn add_transactions(
175        &self,
176        origin: TransactionOrigin,
177        transactions: Vec<Self::Transaction>,
178    ) -> impl Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send;
179
180    /// Adds multiple _unvalidated_ transactions with individual origins.
181    ///
182    /// Each transaction can have its own [`TransactionOrigin`].
183    ///
184    /// Consumer: RPC
185    fn add_transactions_with_origins(
186        &self,
187        transactions: Vec<(TransactionOrigin, Self::Transaction)>,
188    ) -> impl Future<Output = Vec<PoolResult<AddedTransactionOutcome>>> + Send;
189
190    /// Submit a consensus transaction directly to the pool
191    fn add_consensus_transaction(
192        &self,
193        tx: Recovered<<Self::Transaction as PoolTransaction>::Consensus>,
194        origin: TransactionOrigin,
195    ) -> impl Future<Output = PoolResult<AddedTransactionOutcome>> + Send {
196        async move {
197            let tx_hash = *tx.tx_hash();
198
199            let pool_transaction = match Self::Transaction::try_from_consensus(tx) {
200                Ok(tx) => tx,
201                Err(e) => return Err(PoolError::other(tx_hash, e.to_string())),
202            };
203
204            self.add_transaction(origin, pool_transaction).await
205        }
206    }
207
208    /// Submit a consensus transaction and subscribe to event stream
209    fn add_consensus_transaction_and_subscribe(
210        &self,
211        tx: Recovered<<Self::Transaction as PoolTransaction>::Consensus>,
212        origin: TransactionOrigin,
213    ) -> impl Future<Output = PoolResult<TransactionEvents>> + Send {
214        async move {
215            let tx_hash = *tx.tx_hash();
216
217            let pool_transaction = match Self::Transaction::try_from_consensus(tx) {
218                Ok(tx) => tx,
219                Err(e) => return Err(PoolError::other(tx_hash, e.to_string())),
220            };
221
222            self.add_transaction_and_subscribe(origin, pool_transaction).await
223        }
224    }
225
226    /// Returns a new transaction change event stream for the given transaction.
227    ///
228    /// Returns `None` if the transaction is not in the pool.
229    fn transaction_event_listener(&self, tx_hash: TxHash) -> Option<TransactionEvents>;
230
231    /// Returns a new transaction change event stream for _all_ transactions in the pool.
232    fn all_transactions_event_listener(&self) -> AllTransactionsEvents<Self::Transaction>;
233
234    /// Returns a new Stream that yields transactions hashes for new __pending__ transactions
235    /// inserted into the pool that are allowed to be propagated.
236    ///
237    /// Note: This is intended for networking and will __only__ yield transactions that are allowed
238    /// to be propagated over the network, see also [`TransactionListenerKind`].
239    ///
240    /// Consumer: RPC/P2P
241    fn pending_transactions_listener(&self) -> Receiver<TxHash> {
242        self.pending_transactions_listener_for(TransactionListenerKind::PropagateOnly)
243    }
244
245    /// Returns a new [Receiver] that yields transactions hashes for new __pending__ transactions
246    /// inserted into the pending pool depending on the given [`TransactionListenerKind`] argument.
247    fn pending_transactions_listener_for(&self, kind: TransactionListenerKind) -> Receiver<TxHash>;
248
249    /// Returns a new stream that yields new valid transactions added to the pool.
250    fn new_transactions_listener(&self) -> Receiver<NewTransactionEvent<Self::Transaction>> {
251        self.new_transactions_listener_for(TransactionListenerKind::PropagateOnly)
252    }
253
254    /// Returns a new [Receiver] that yields blob "sidecars" (blobs w/ assoc. kzg
255    /// commitments/proofs) for eip-4844 transactions inserted into the pool
256    fn blob_transaction_sidecars_listener(&self) -> Receiver<NewBlobSidecar>;
257
258    /// Returns a new stream that yields new valid transactions added to the pool
259    /// depending on the given [`TransactionListenerKind`] argument.
260    fn new_transactions_listener_for(
261        &self,
262        kind: TransactionListenerKind,
263    ) -> Receiver<NewTransactionEvent<Self::Transaction>>;
264
265    /// Returns a new Stream that yields new transactions added to the pending sub-pool.
266    ///
267    /// This is a convenience wrapper around [`Self::new_transactions_listener`] that filters for
268    /// [`SubPool::Pending`](crate::SubPool).
269    fn new_pending_pool_transactions_listener(
270        &self,
271    ) -> NewSubpoolTransactionStream<Self::Transaction> {
272        NewSubpoolTransactionStream::new(
273            self.new_transactions_listener_for(TransactionListenerKind::PropagateOnly),
274            SubPool::Pending,
275        )
276    }
277
278    /// Returns a new Stream that yields new transactions added to the basefee sub-pool.
279    ///
280    /// This is a convenience wrapper around [`Self::new_transactions_listener`] that filters for
281    /// [`SubPool::BaseFee`](crate::SubPool).
282    fn new_basefee_pool_transactions_listener(
283        &self,
284    ) -> NewSubpoolTransactionStream<Self::Transaction> {
285        NewSubpoolTransactionStream::new(self.new_transactions_listener(), SubPool::BaseFee)
286    }
287
288    /// Returns a new Stream that yields new transactions added to the queued-pool.
289    ///
290    /// This is a convenience wrapper around [`Self::new_transactions_listener`] that filters for
291    /// [`SubPool::Queued`](crate::SubPool).
292    fn new_queued_transactions_listener(&self) -> NewSubpoolTransactionStream<Self::Transaction> {
293        NewSubpoolTransactionStream::new(self.new_transactions_listener(), SubPool::Queued)
294    }
295
296    /// Returns the _hashes_ of all transactions in the pool that are allowed to be propagated.
297    ///
298    /// This excludes hashes that aren't allowed to be propagated.
299    ///
300    /// Note: This returns a `Vec` but should guarantee that all hashes are unique.
301    ///
302    /// Consumer: P2P
303    fn pooled_transaction_hashes(&self) -> Vec<TxHash>;
304
305    /// Returns only the first `max` hashes of transactions in the pool.
306    ///
307    /// Consumer: P2P
308    fn pooled_transaction_hashes_max(&self, max: usize) -> Vec<TxHash>;
309
310    /// Returns the _full_ transaction objects all transactions in the pool that are allowed to be
311    /// propagated.
312    ///
313    /// This is intended to be used by the network for the initial exchange of pooled transaction
314    /// _hashes_
315    ///
316    /// Note: This returns a `Vec` but should guarantee that all transactions are unique.
317    ///
318    /// Caution: In case of blob transactions, this does not include the sidecar.
319    ///
320    /// Consumer: P2P
321    fn pooled_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
322
323    /// Returns only the first `max` transactions in the pool.
324    ///
325    /// Consumer: P2P
326    fn pooled_transactions_max(
327        &self,
328        max: usize,
329    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
330
331    /// Returns converted [`PooledTransactionVariant`] for the given transaction hashes that are
332    /// allowed to be propagated.
333    ///
334    /// This adheres to the expected behavior of
335    /// [`GetPooledTransactions`](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getpooledtransactions-0x09):
336    ///
337    /// The transactions must be in same order as in the request, but it is OK to skip transactions
338    /// which are not available.
339    ///
340    /// If the transaction is a blob transaction, the sidecar will be included.
341    ///
342    /// Consumer: P2P
343    fn get_pooled_transaction_elements(
344        &self,
345        tx_hashes: Vec<TxHash>,
346        limit: GetPooledTransactionLimit,
347    ) -> Vec<<Self::Transaction as PoolTransaction>::Pooled>;
348
349    /// Returns the pooled transaction variant for the given transaction hash.
350    ///
351    /// This adheres to the expected behavior of
352    /// [`GetPooledTransactions`](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getpooledtransactions-0x09):
353    ///
354    /// If the transaction is a blob transaction, the sidecar will be included.
355    ///
356    /// It is expected that this variant represents the valid p2p format for full transactions.
357    /// E.g. for EIP-4844 transactions this is the consensus transaction format with the blob
358    /// sidecar.
359    ///
360    /// Consumer: P2P
361    fn get_pooled_transaction_element(
362        &self,
363        tx_hash: TxHash,
364    ) -> Option<Recovered<<Self::Transaction as PoolTransaction>::Pooled>>;
365
366    /// Returns an iterator that yields transactions that are ready for block production.
367    ///
368    /// Consumer: Block production
369    fn best_transactions(
370        &self,
371    ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>>;
372
373    /// Returns an iterator that yields transactions that are ready for block production with the
374    /// given base fee and optional blob fee attributes.
375    ///
376    /// Consumer: Block production
377    fn best_transactions_with_attributes(
378        &self,
379        best_transactions_attributes: BestTransactionsAttributes,
380    ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>>;
381
382    /// Returns all transactions that can be included in the next block.
383    ///
384    /// This is primarily used for the `txpool_` RPC namespace:
385    /// <https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool> which distinguishes
386    /// between `pending` and `queued` transactions, where `pending` are transactions ready for
387    /// inclusion in the next block and `queued` are transactions that are ready for inclusion in
388    /// future blocks.
389    ///
390    /// Consumer: RPC
391    fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
392
393    /// Returns first `max` transactions that can be included in the next block.
394    /// See <https://github.com/paradigmxyz/reth/issues/12767#issuecomment-2493223579>
395    ///
396    /// Consumer: Block production
397    fn pending_transactions_max(
398        &self,
399        max: usize,
400    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
401
402    /// Returns all transactions that can be included in _future_ blocks.
403    ///
404    /// This and [`Self::pending_transactions`] are mutually exclusive.
405    ///
406    /// Consumer: RPC
407    fn queued_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
408
409    /// Returns the number of transactions that are ready for inclusion in the next block and the
410    /// number of transactions that are ready for inclusion in future blocks: `(pending, queued)`.
411    fn pending_and_queued_txn_count(&self) -> (usize, usize);
412
413    /// Returns all transactions that are currently in the pool grouped by whether they are ready
414    /// for inclusion in the next block or not.
415    ///
416    /// This is primarily used for the `txpool_` namespace: <https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool>
417    ///
418    /// Consumer: RPC
419    fn all_transactions(&self) -> AllPoolTransactions<Self::Transaction>;
420
421    /// Returns the _hashes_ of all transactions regardless of whether they can be propagated or
422    /// not.
423    ///
424    /// Unlike [`Self::pooled_transaction_hashes`] this doesn't consider whether the transaction can
425    /// be propagated or not.
426    ///
427    /// Note: This returns a `Vec` but should guarantee that all hashes are unique.
428    ///
429    /// Consumer: Utility
430    fn all_transaction_hashes(&self) -> Vec<TxHash>;
431
432    /// Removes a single transaction corresponding to the given hash.
433    ///
434    /// Note: This removes the transaction as if it got discarded (_not_ mined).
435    ///
436    /// Returns the removed transaction if it was found in the pool.
437    ///
438    /// Consumer: Utility
439    fn remove_transaction(
440        &self,
441        hash: TxHash,
442    ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
443        self.remove_transactions(vec![hash]).pop()
444    }
445
446    /// Removes all transactions corresponding to the given hashes.
447    ///
448    /// Note: This removes the transactions as if they got discarded (_not_ mined).
449    ///
450    /// Consumer: Utility
451    fn remove_transactions(
452        &self,
453        hashes: Vec<TxHash>,
454    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
455
456    /// Removes all transactions corresponding to the given hashes.
457    ///
458    /// Also removes all _dependent_ transactions.
459    ///
460    /// Consumer: Utility
461    fn remove_transactions_and_descendants(
462        &self,
463        hashes: Vec<TxHash>,
464    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
465
466    /// Removes all transactions from the given sender
467    ///
468    /// Consumer: Utility
469    fn remove_transactions_by_sender(
470        &self,
471        sender: Address,
472    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
473
474    /// Retains only those hashes that are unknown to the pool.
475    /// In other words, removes all transactions from the given set that are currently present in
476    /// the pool. Returns hashes already known to the pool.
477    ///
478    /// Consumer: P2P
479    fn retain_unknown<A>(&self, announcement: &mut A)
480    where
481        A: HandleMempoolData;
482
483    /// Returns if the transaction for the given hash is already included in this pool.
484    fn contains(&self, tx_hash: &TxHash) -> bool {
485        self.get(tx_hash).is_some()
486    }
487
488    /// Returns the transaction for the given hash.
489    fn get(&self, tx_hash: &TxHash) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
490
491    /// Returns all transactions objects for the given hashes.
492    ///
493    /// Caution: This in case of blob transactions, this does not include the sidecar.
494    fn get_all(&self, txs: Vec<TxHash>) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
495
496    /// Notify the pool about transactions that are propagated to peers.
497    ///
498    /// Consumer: P2P
499    fn on_propagated(&self, txs: PropagatedTransactions);
500
501    /// Returns all transactions sent by a given user
502    fn get_transactions_by_sender(
503        &self,
504        sender: Address,
505    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
506
507    /// Returns all pending transactions filtered by predicate
508    fn get_pending_transactions_with_predicate(
509        &self,
510        predicate: impl FnMut(&ValidPoolTransaction<Self::Transaction>) -> bool,
511    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
512
513    /// Returns all pending transactions sent by a given user
514    fn get_pending_transactions_by_sender(
515        &self,
516        sender: Address,
517    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
518
519    /// Returns all queued transactions sent by a given user
520    fn get_queued_transactions_by_sender(
521        &self,
522        sender: Address,
523    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
524
525    /// Returns the highest transaction sent by a given user
526    fn get_highest_transaction_by_sender(
527        &self,
528        sender: Address,
529    ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
530
531    /// Returns the transaction with the highest nonce that is executable given the on chain nonce.
532    /// In other words the highest non nonce gapped transaction.
533    ///
534    /// Note: The next pending pooled transaction must have the on chain nonce.
535    ///
536    /// For example, for a given on chain nonce of `5`, the next transaction must have that nonce.
537    /// If the pool contains txs `[5,6,7]` this returns tx `7`.
538    /// If the pool contains txs `[6,7]` this returns `None` because the next valid nonce (5) is
539    /// missing, which means txs `[6,7]` are nonce gapped.
540    fn get_highest_consecutive_transaction_by_sender(
541        &self,
542        sender: Address,
543        on_chain_nonce: u64,
544    ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
545
546    /// Returns a transaction sent by a given user and a nonce
547    fn get_transaction_by_sender_and_nonce(
548        &self,
549        sender: Address,
550        nonce: u64,
551    ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
552
553    /// Returns all transactions that where submitted with the given [`TransactionOrigin`]
554    fn get_transactions_by_origin(
555        &self,
556        origin: TransactionOrigin,
557    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
558
559    /// Returns all pending transactions filtered by [`TransactionOrigin`]
560    fn get_pending_transactions_by_origin(
561        &self,
562        origin: TransactionOrigin,
563    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
564
565    /// Returns all transactions that where submitted as [`TransactionOrigin::Local`]
566    fn get_local_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
567        self.get_transactions_by_origin(TransactionOrigin::Local)
568    }
569
570    /// Returns all transactions that where submitted as [`TransactionOrigin::Private`]
571    fn get_private_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
572        self.get_transactions_by_origin(TransactionOrigin::Private)
573    }
574
575    /// Returns all transactions that where submitted as [`TransactionOrigin::External`]
576    fn get_external_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
577        self.get_transactions_by_origin(TransactionOrigin::External)
578    }
579
580    /// Returns all pending transactions that where submitted as [`TransactionOrigin::Local`]
581    fn get_local_pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
582        self.get_pending_transactions_by_origin(TransactionOrigin::Local)
583    }
584
585    /// Returns all pending transactions that where submitted as [`TransactionOrigin::Private`]
586    fn get_private_pending_transactions(
587        &self,
588    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
589        self.get_pending_transactions_by_origin(TransactionOrigin::Private)
590    }
591
592    /// Returns all pending transactions that where submitted as [`TransactionOrigin::External`]
593    fn get_external_pending_transactions(
594        &self,
595    ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
596        self.get_pending_transactions_by_origin(TransactionOrigin::External)
597    }
598
599    /// Returns a set of all senders of transactions in the pool
600    fn unique_senders(&self) -> HashSet<Address>;
601
602    /// Returns the [`BlobTransactionSidecarVariant`] for the given transaction hash if it exists in
603    /// the blob store.
604    fn get_blob(
605        &self,
606        tx_hash: TxHash,
607    ) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
608
609    /// Returns all [`BlobTransactionSidecarVariant`] for the given transaction hashes if they
610    /// exists in the blob store.
611    ///
612    /// This only returns the blobs that were found in the store.
613    /// If there's no blob it will not be returned.
614    fn get_all_blobs(
615        &self,
616        tx_hashes: Vec<TxHash>,
617    ) -> Result<Vec<(TxHash, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError>;
618
619    /// Returns the exact [`BlobTransactionSidecarVariant`] for the given transaction hashes in the
620    /// order they were requested.
621    ///
622    /// Returns an error if any of the blobs are not found in the blob store.
623    fn get_all_blobs_exact(
624        &self,
625        tx_hashes: Vec<TxHash>,
626    ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
627
628    /// Return the [`BlobAndProofV1`]s for a list of blob versioned hashes.
629    fn get_blobs_for_versioned_hashes_v1(
630        &self,
631        versioned_hashes: &[B256],
632    ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError>;
633
634    /// Return the [`BlobAndProofV2`]s for a list of blob versioned hashes.
635    /// Blobs and proofs are returned only if they are present for _all_ of the requested versioned
636    /// hashes.
637    fn get_blobs_for_versioned_hashes_v2(
638        &self,
639        versioned_hashes: &[B256],
640    ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError>;
641}
642
643/// Extension for [`TransactionPool`] trait that allows to set the current block info.
644#[auto_impl::auto_impl(&, Arc)]
645pub trait TransactionPoolExt: TransactionPool {
646    /// Sets the current block info for the pool.
647    fn set_block_info(&self, info: BlockInfo);
648
649    /// Event listener for when the pool needs to be updated.
650    ///
651    /// Implementers need to update the pool accordingly:
652    ///
653    /// ## Fee changes
654    ///
655    /// The [`CanonicalStateUpdate`] includes the base and blob fee of the pending block, which
656    /// affects the dynamic fee requirement of pending transactions in the pool.
657    ///
658    /// ## EIP-4844 Blob transactions
659    ///
660    /// Mined blob transactions need to be removed from the pool, but from the pool only. The blob
661    /// sidecar must not be removed from the blob store. Only after a blob transaction is
662    /// finalized, its sidecar is removed from the blob store. This ensures that in case of a reorg,
663    /// the sidecar is still available.
664    fn on_canonical_state_change<B>(&self, update: CanonicalStateUpdate<'_, B>)
665    where
666        B: Block;
667
668    /// Updates the accounts in the pool
669    fn update_accounts(&self, accounts: Vec<ChangedAccount>);
670
671    /// Deletes the blob sidecar for the given transaction from the blob store
672    fn delete_blob(&self, tx: B256);
673
674    /// Deletes multiple blob sidecars from the blob store
675    fn delete_blobs(&self, txs: Vec<B256>);
676
677    /// Maintenance function to cleanup blobs that are no longer needed.
678    fn cleanup_blobs(&self);
679}
680
681/// A Helper type that bundles all transactions in the pool.
682#[derive(Debug, Clone)]
683pub struct AllPoolTransactions<T: PoolTransaction> {
684    /// Transactions that are ready for inclusion in the next block.
685    pub pending: Vec<Arc<ValidPoolTransaction<T>>>,
686    /// Transactions that are ready for inclusion in _future_ blocks, but are currently parked,
687    /// because they depend on other transactions that are not yet included in the pool (nonce gap)
688    /// or otherwise blocked.
689    pub queued: Vec<Arc<ValidPoolTransaction<T>>>,
690}
691
692// === impl AllPoolTransactions ===
693
694impl<T: PoolTransaction> AllPoolTransactions<T> {
695    /// Returns the combined number of all transactions.
696    pub const fn count(&self) -> usize {
697        self.pending.len() + self.queued.len()
698    }
699
700    /// Returns an iterator over all pending [`Recovered`] transactions.
701    pub fn pending_recovered(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
702        self.pending.iter().map(|tx| tx.transaction.clone().into_consensus())
703    }
704
705    /// Returns an iterator over all queued [`Recovered`] transactions.
706    pub fn queued_recovered(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
707        self.queued.iter().map(|tx| tx.transaction.clone().into_consensus())
708    }
709
710    /// Returns an iterator over all transactions, both pending and queued.
711    pub fn all(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
712        self.pending
713            .iter()
714            .chain(self.queued.iter())
715            .map(|tx| tx.transaction.clone().into_consensus())
716    }
717}
718
719impl<T: PoolTransaction> Default for AllPoolTransactions<T> {
720    fn default() -> Self {
721        Self { pending: Default::default(), queued: Default::default() }
722    }
723}
724
725/// Represents transactions that were propagated over the network.
726#[derive(Debug, Clone, Eq, PartialEq, Default)]
727pub struct PropagatedTransactions(pub HashMap<TxHash, Vec<PropagateKind>>);
728
729/// Represents how a transaction was propagated over the network.
730#[derive(Debug, Copy, Clone, Eq, PartialEq)]
731#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
732pub enum PropagateKind {
733    /// The full transaction object was sent to the peer.
734    ///
735    /// This is equivalent to the `Transaction` message
736    Full(PeerId),
737    /// Only the Hash was propagated to the peer.
738    Hash(PeerId),
739}
740
741// === impl PropagateKind ===
742
743impl PropagateKind {
744    /// Returns the peer the transaction was sent to
745    pub const fn peer(&self) -> &PeerId {
746        match self {
747            Self::Full(peer) | Self::Hash(peer) => peer,
748        }
749    }
750
751    /// Returns true if the transaction was sent as a full transaction
752    pub const fn is_full(&self) -> bool {
753        matches!(self, Self::Full(_))
754    }
755
756    /// Returns true if the transaction was sent as a hash
757    pub const fn is_hash(&self) -> bool {
758        matches!(self, Self::Hash(_))
759    }
760}
761
762impl From<PropagateKind> for PeerId {
763    fn from(value: PropagateKind) -> Self {
764        match value {
765            PropagateKind::Full(peer) | PropagateKind::Hash(peer) => peer,
766        }
767    }
768}
769
770/// This type represents a new blob sidecar that has been stored in the transaction pool's
771/// blobstore; it includes the `TransactionHash` of the blob transaction along with the assoc.
772/// sidecar (blobs, commitments, proofs)
773#[derive(Debug, Clone)]
774pub struct NewBlobSidecar {
775    /// hash of the EIP-4844 transaction.
776    pub tx_hash: TxHash,
777    /// the blob transaction sidecar.
778    pub sidecar: Arc<BlobTransactionSidecarVariant>,
779}
780
781/// Where the transaction originates from.
782///
783/// Depending on where the transaction was picked up, it affects how the transaction is handled
784/// internally, e.g. limits for simultaneous transaction of one sender.
785#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
786pub enum TransactionOrigin {
787    /// Transaction is coming from a local source.
788    #[default]
789    Local,
790    /// Transaction has been received externally.
791    ///
792    /// This is usually considered an "untrusted" source, for example received from another in the
793    /// network.
794    External,
795    /// Transaction is originated locally and is intended to remain private.
796    ///
797    /// This type of transaction should not be propagated to the network. It's meant for
798    /// private usage within the local node only.
799    Private,
800}
801
802// === impl TransactionOrigin ===
803
804impl TransactionOrigin {
805    /// Whether the transaction originates from a local source.
806    pub const fn is_local(&self) -> bool {
807        matches!(self, Self::Local)
808    }
809
810    /// Whether the transaction originates from an external source.
811    pub const fn is_external(&self) -> bool {
812        matches!(self, Self::External)
813    }
814    /// Whether the transaction originates from a private source.
815    pub const fn is_private(&self) -> bool {
816        matches!(self, Self::Private)
817    }
818}
819
820/// Represents the kind of update to the canonical state.
821#[derive(Debug, Clone, Copy, PartialEq, Eq)]
822pub enum PoolUpdateKind {
823    /// The update was due to a block commit.
824    Commit,
825    /// The update was due to a reorganization.
826    Reorg,
827}
828
829/// Represents changes after a new canonical block or range of canonical blocks was added to the
830/// chain.
831///
832/// It is expected that this is only used if the added blocks are canonical to the pool's last known
833/// block hash. In other words, the first added block of the range must be the child of the last
834/// known block hash.
835///
836/// This is used to update the pool state accordingly.
837#[derive(Clone, Debug)]
838pub struct CanonicalStateUpdate<'a, B: Block> {
839    /// Hash of the tip block.
840    pub new_tip: &'a SealedBlock<B>,
841    /// EIP-1559 Base fee of the _next_ (pending) block
842    ///
843    /// The base fee of a block depends on the utilization of the last block and its base fee.
844    pub pending_block_base_fee: u64,
845    /// EIP-4844 blob fee of the _next_ (pending) block
846    ///
847    /// Only after Cancun
848    pub pending_block_blob_fee: Option<u128>,
849    /// A set of changed accounts across a range of blocks.
850    pub changed_accounts: Vec<ChangedAccount>,
851    /// All mined transactions in the block range.
852    pub mined_transactions: Vec<B256>,
853    /// The kind of update to the canonical state.
854    pub update_kind: PoolUpdateKind,
855}
856
857impl<B> CanonicalStateUpdate<'_, B>
858where
859    B: Block,
860{
861    /// Returns the number of the tip block.
862    pub fn number(&self) -> u64 {
863        self.new_tip.number()
864    }
865
866    /// Returns the hash of the tip block.
867    pub fn hash(&self) -> B256 {
868        self.new_tip.hash()
869    }
870
871    /// Timestamp of the latest chain update
872    pub fn timestamp(&self) -> u64 {
873        self.new_tip.timestamp()
874    }
875
876    /// Returns the block info for the tip block.
877    pub fn block_info(&self) -> BlockInfo {
878        BlockInfo {
879            block_gas_limit: self.new_tip.gas_limit(),
880            last_seen_block_hash: self.hash(),
881            last_seen_block_number: self.number(),
882            pending_basefee: self.pending_block_base_fee,
883            pending_blob_fee: self.pending_block_blob_fee,
884        }
885    }
886}
887
888impl<B> fmt::Display for CanonicalStateUpdate<'_, B>
889where
890    B: Block,
891{
892    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
893        f.debug_struct("CanonicalStateUpdate")
894            .field("hash", &self.hash())
895            .field("number", &self.number())
896            .field("pending_block_base_fee", &self.pending_block_base_fee)
897            .field("pending_block_blob_fee", &self.pending_block_blob_fee)
898            .field("changed_accounts", &self.changed_accounts.len())
899            .field("mined_transactions", &self.mined_transactions.len())
900            .finish()
901    }
902}
903
904/// Alias to restrict the [`BestTransactions`] items to the pool's transaction type.
905pub type BestTransactionsFor<Pool> = Box<
906    dyn BestTransactions<Item = Arc<ValidPoolTransaction<<Pool as TransactionPool>::Transaction>>>,
907>;
908
909/// An `Iterator` that only returns transactions that are ready to be executed.
910///
911/// This makes no assumptions about the order of the transactions, but expects that _all_
912/// transactions are valid (no nonce gaps.) for the tracked state of the pool.
913///
914/// Note: this iterator will always return the best transaction that it currently knows.
915/// There is no guarantee transactions will be returned sequentially in decreasing
916/// priority order.
917pub trait BestTransactions: Iterator + Send {
918    /// Mark the transaction as invalid.
919    ///
920    /// Implementers must ensure all subsequent transaction _don't_ depend on this transaction.
921    /// In other words, this must remove the given transaction _and_ drain all transaction that
922    /// depend on it.
923    fn mark_invalid(&mut self, transaction: &Self::Item, kind: InvalidPoolTransactionError);
924
925    /// An iterator may be able to receive additional pending transactions that weren't present it
926    /// the pool when it was created.
927    ///
928    /// This ensures that iterator will return the best transaction that it currently knows and not
929    /// listen to pool updates.
930    fn no_updates(&mut self);
931
932    /// Convenience function for [`Self::no_updates`] that returns the iterator again.
933    fn without_updates(mut self) -> Self
934    where
935        Self: Sized,
936    {
937        self.no_updates();
938        self
939    }
940
941    /// Skip all blob transactions.
942    ///
943    /// There's only limited blob space available in a block, once exhausted, EIP-4844 transactions
944    /// can no longer be included.
945    ///
946    /// If called then the iterator will no longer yield blob transactions.
947    ///
948    /// Note: this will also exclude any transactions that depend on blob transactions.
949    fn skip_blobs(&mut self) {
950        self.set_skip_blobs(true);
951    }
952
953    /// Controls whether the iterator skips blob transactions or not.
954    ///
955    /// If set to true, no blob transactions will be returned.
956    fn set_skip_blobs(&mut self, skip_blobs: bool);
957
958    /// Convenience function for [`Self::skip_blobs`] that returns the iterator again.
959    fn without_blobs(mut self) -> Self
960    where
961        Self: Sized,
962    {
963        self.skip_blobs();
964        self
965    }
966
967    /// Creates an iterator which uses a closure to determine whether a transaction should be
968    /// returned by the iterator.
969    ///
970    /// All items the closure returns false for are marked as invalid via [`Self::mark_invalid`] and
971    /// descendant transactions will be skipped.
972    fn filter_transactions<P>(self, predicate: P) -> BestTransactionFilter<Self, P>
973    where
974        P: FnMut(&Self::Item) -> bool,
975        Self: Sized,
976    {
977        BestTransactionFilter::new(self, predicate)
978    }
979}
980
981impl<T> BestTransactions for Box<T>
982where
983    T: BestTransactions + ?Sized,
984{
985    fn mark_invalid(&mut self, transaction: &Self::Item, kind: InvalidPoolTransactionError) {
986        (**self).mark_invalid(transaction, kind)
987    }
988
989    fn no_updates(&mut self) {
990        (**self).no_updates();
991    }
992
993    fn skip_blobs(&mut self) {
994        (**self).skip_blobs();
995    }
996
997    fn set_skip_blobs(&mut self, skip_blobs: bool) {
998        (**self).set_skip_blobs(skip_blobs);
999    }
1000}
1001
1002/// A no-op implementation that yields no transactions.
1003impl<T> BestTransactions for std::iter::Empty<T> {
1004    fn mark_invalid(&mut self, _tx: &T, _kind: InvalidPoolTransactionError) {}
1005
1006    fn no_updates(&mut self) {}
1007
1008    fn skip_blobs(&mut self) {}
1009
1010    fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
1011}
1012
1013/// A filter that allows to check if a transaction satisfies a set of conditions
1014pub trait TransactionFilter {
1015    /// The type of the transaction to check.
1016    type Transaction;
1017
1018    /// Returns true if the transaction satisfies the conditions.
1019    fn is_valid(&self, transaction: &Self::Transaction) -> bool;
1020}
1021
1022/// A no-op implementation of [`TransactionFilter`] which
1023/// marks all transactions as valid.
1024#[derive(Debug, Clone)]
1025pub struct NoopTransactionFilter<T>(std::marker::PhantomData<T>);
1026
1027// We can't derive Default because this forces T to be
1028// Default as well, which isn't necessary.
1029impl<T> Default for NoopTransactionFilter<T> {
1030    fn default() -> Self {
1031        Self(std::marker::PhantomData)
1032    }
1033}
1034
1035impl<T> TransactionFilter for NoopTransactionFilter<T> {
1036    type Transaction = T;
1037
1038    fn is_valid(&self, _transaction: &Self::Transaction) -> bool {
1039        true
1040    }
1041}
1042
1043/// A Helper type that bundles the best transactions attributes together.
1044#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1045pub struct BestTransactionsAttributes {
1046    /// The base fee attribute for best transactions.
1047    pub basefee: u64,
1048    /// The blob fee attribute for best transactions.
1049    pub blob_fee: Option<u64>,
1050}
1051
1052// === impl BestTransactionsAttributes ===
1053
1054impl BestTransactionsAttributes {
1055    /// Creates a new `BestTransactionsAttributes` with the given basefee and blob fee.
1056    pub const fn new(basefee: u64, blob_fee: Option<u64>) -> Self {
1057        Self { basefee, blob_fee }
1058    }
1059
1060    /// Creates a new `BestTransactionsAttributes` with the given basefee.
1061    pub const fn base_fee(basefee: u64) -> Self {
1062        Self::new(basefee, None)
1063    }
1064
1065    /// Sets the given blob fee.
1066    pub const fn with_blob_fee(mut self, blob_fee: u64) -> Self {
1067        self.blob_fee = Some(blob_fee);
1068        self
1069    }
1070}
1071
1072/// Trait for transaction types stored in the transaction pool.
1073///
1074/// This trait represents the actual transaction object stored in the mempool, which includes not
1075/// only the transaction data itself but also additional metadata needed for efficient pool
1076/// operations. Implementations typically cache values that are frequently accessed during
1077/// transaction ordering, validation, and eviction.
1078///
1079/// ## Key Responsibilities
1080///
1081/// 1. **Metadata Caching**: Store computed values like address, cost and encoded size
1082/// 2. **Representation Conversion**: Handle conversions between consensus and pooled
1083///    representations
1084/// 3. **Validation Support**: Provide methods for pool-specific validation rules
1085///
1086/// ## Cached Metadata
1087///
1088/// Implementations should cache frequently accessed values to avoid recomputation:
1089/// - **Address**: Recovered sender address of the transaction
1090/// - **Cost**: Max amount spendable (gas × price + value + blob costs)
1091/// - **Size**: RLP encoded length for mempool size limits
1092///
1093/// See [`EthPooledTransaction`] for a reference implementation.
1094///
1095/// ## Transaction Representations
1096///
1097/// This trait abstracts over the different representations a transaction can have:
1098///
1099/// 1. **Consensus representation** (`Consensus` associated type): The canonical form included in
1100///    blocks
1101///    - Compact representation without networking metadata
1102///    - For EIP-4844: includes only blob hashes, not the actual blobs
1103///    - Used for block execution and state transitions
1104///
1105/// 2. **Pooled representation** (`Pooled` associated type): The form used for network propagation
1106///    - May include additional data for validation
1107///    - For EIP-4844: includes full blob sidecars (blobs, commitments, proofs)
1108///    - Used for mempool validation and p2p gossiping
1109///
1110/// ## Why Two Representations?
1111///
1112/// This distinction is necessary because:
1113///
1114/// - **EIP-4844 blob transactions**: Require large blob sidecars for validation that would bloat
1115///   blocks if included. Only blob hashes are stored on-chain.
1116///
1117/// - **Network efficiency**: Blob transactions are not broadcast to all peers automatically but
1118///   must be explicitly requested to reduce bandwidth usage.
1119///
1120/// - **Special transactions**: Some transactions (like OP deposit transactions) exist only in
1121///   consensus format and are never in the mempool.
1122///
1123/// ## Conversion Rules
1124///
1125/// - `Consensus` → `Pooled`: May fail for transactions that cannot be pooled (e.g., OP deposit
1126///   transactions, blob transactions without sidecars)
1127/// - `Pooled` → `Consensus`: Always succeeds (pooled is a superset)
1128pub trait PoolTransaction:
1129    alloy_consensus::Transaction + InMemorySize + Debug + Send + Sync + Clone
1130{
1131    /// Associated error type for the `try_from_consensus` method.
1132    type TryFromConsensusError: fmt::Display;
1133
1134    /// Associated type representing the raw consensus variant of the transaction.
1135    type Consensus: SignedTransaction + From<Self::Pooled>;
1136
1137    /// Associated type representing the recovered pooled variant of the transaction.
1138    type Pooled: TryFrom<Self::Consensus, Error = Self::TryFromConsensusError> + SignedTransaction;
1139
1140    /// Define a method to convert from the `Consensus` type to `Self`
1141    ///
1142    /// This conversion may fail for transactions that are valid for inclusion in blocks
1143    /// but cannot exist in the transaction pool. Examples include:
1144    ///
1145    /// - **OP Deposit transactions**: These are special system transactions that are directly
1146    ///   included in blocks by the sequencer/validator and never enter the mempool
1147    /// - **Blob transactions without sidecars**: After being included in a block, the sidecar data
1148    ///   is pruned, making the consensus transaction unpoolable
1149    fn try_from_consensus(
1150        tx: Recovered<Self::Consensus>,
1151    ) -> Result<Self, Self::TryFromConsensusError> {
1152        let (tx, signer) = tx.into_parts();
1153        Ok(Self::from_pooled(Recovered::new_unchecked(tx.try_into()?, signer)))
1154    }
1155
1156    /// Clone the transaction into a consensus variant.
1157    ///
1158    /// This method is preferred when the [`PoolTransaction`] already wraps the consensus variant.
1159    fn clone_into_consensus(&self) -> Recovered<Self::Consensus> {
1160        self.clone().into_consensus()
1161    }
1162
1163    /// Define a method to convert from the `Self` type to `Consensus`
1164    fn into_consensus(self) -> Recovered<Self::Consensus>;
1165
1166    /// Converts the transaction into consensus format while preserving the EIP-2718 encoded bytes.
1167    /// This is used to optimize transaction execution by reusing cached encoded bytes instead of
1168    /// re-encoding the transaction. The cached bytes are particularly useful in payload building
1169    /// where the same transaction may be executed multiple times.
1170    fn into_consensus_with2718(self) -> WithEncoded<Recovered<Self::Consensus>> {
1171        self.into_consensus().into_encoded()
1172    }
1173
1174    /// Define a method to convert from the `Pooled` type to `Self`
1175    fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self;
1176
1177    /// Tries to convert the `Consensus` type into the `Pooled` type.
1178    fn try_into_pooled(self) -> Result<Recovered<Self::Pooled>, Self::TryFromConsensusError> {
1179        let consensus = self.into_consensus();
1180        let (tx, signer) = consensus.into_parts();
1181        Ok(Recovered::new_unchecked(tx.try_into()?, signer))
1182    }
1183
1184    /// Converts the `Pooled` type into the `Consensus` type.
1185    fn pooled_into_consensus(tx: Self::Pooled) -> Self::Consensus {
1186        tx.into()
1187    }
1188
1189    /// Hash of the transaction.
1190    fn hash(&self) -> &TxHash;
1191
1192    /// The Sender of the transaction.
1193    fn sender(&self) -> Address;
1194
1195    /// Reference to the Sender of the transaction.
1196    fn sender_ref(&self) -> &Address;
1197
1198    /// Returns the cost that this transaction is allowed to consume:
1199    ///
1200    /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1201    /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1202    /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1203    /// max_blob_fee_per_gas * blob_gas_used`.
1204    fn cost(&self) -> &U256;
1205
1206    /// Returns the length of the rlp encoded transaction object
1207    ///
1208    /// Note: Implementations should cache this value.
1209    fn encoded_length(&self) -> usize;
1210
1211    /// Ensures that the transaction's code size does not exceed the provided `max_init_code_size`.
1212    ///
1213    /// This is specifically relevant for contract creation transactions ([`TxKind::Create`]),
1214    /// where the input data contains the initialization code. If the input code size exceeds
1215    /// the configured limit, an [`InvalidPoolTransactionError::ExceedsMaxInitCodeSize`] error is
1216    /// returned.
1217    fn ensure_max_init_code_size(
1218        &self,
1219        max_init_code_size: usize,
1220    ) -> Result<(), InvalidPoolTransactionError> {
1221        let input_len = self.input().len();
1222        if self.is_create() && input_len > max_init_code_size {
1223            Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize(input_len, max_init_code_size))
1224        } else {
1225            Ok(())
1226        }
1227    }
1228}
1229
1230/// Super trait for transactions that can be converted to and from Eth transactions intended for the
1231/// ethereum style pool.
1232///
1233/// This extends the [`PoolTransaction`] trait with additional methods that are specific to the
1234/// Ethereum pool.
1235pub trait EthPoolTransaction: PoolTransaction {
1236    /// Extracts the blob sidecar from the transaction.
1237    fn take_blob(&mut self) -> EthBlobTransactionSidecar;
1238
1239    /// A specialization for the EIP-4844 transaction type.
1240    /// Tries to reattach the blob sidecar to the transaction.
1241    ///
1242    /// This returns an option, but callers should ensure that the transaction is an EIP-4844
1243    /// transaction: [`Typed2718::is_eip4844`].
1244    fn try_into_pooled_eip4844(
1245        self,
1246        sidecar: Arc<BlobTransactionSidecarVariant>,
1247    ) -> Option<Recovered<Self::Pooled>>;
1248
1249    /// Tries to convert the `Consensus` type with a blob sidecar into the `Pooled` type.
1250    ///
1251    /// Returns `None` if passed transaction is not a blob transaction.
1252    fn try_from_eip4844(
1253        tx: Recovered<Self::Consensus>,
1254        sidecar: BlobTransactionSidecarVariant,
1255    ) -> Option<Self>;
1256
1257    /// Validates the blob sidecar of the transaction with the given settings.
1258    fn validate_blob(
1259        &self,
1260        blob: &BlobTransactionSidecarVariant,
1261        settings: &KzgSettings,
1262    ) -> Result<(), BlobTransactionValidationError>;
1263}
1264
1265/// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum.
1266///
1267/// This type wraps a consensus transaction with additional cached data that's
1268/// frequently accessed by the pool for transaction ordering and validation:
1269///
1270/// - `cost`: Pre-calculated max cost (gas * price + value + blob costs)
1271/// - `encoded_length`: Cached RLP encoding length for size limits
1272/// - `blob_sidecar`: Blob data state (None/Missing/Present)
1273///
1274/// This avoids recalculating these values repeatedly during pool operations.
1275#[derive(Debug, Clone, PartialEq, Eq)]
1276pub struct EthPooledTransaction<T = TransactionSigned> {
1277    /// `EcRecovered` transaction, the consensus format.
1278    pub transaction: Recovered<T>,
1279
1280    /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1281    /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1282    /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1283    /// max_blob_fee_per_gas * blob_gas_used`.
1284    pub cost: U256,
1285
1286    /// This is the RLP length of the transaction, computed when the transaction is added to the
1287    /// pool.
1288    pub encoded_length: usize,
1289
1290    /// The blob side car for this transaction
1291    pub blob_sidecar: EthBlobTransactionSidecar,
1292}
1293
1294impl<T: SignedTransaction> EthPooledTransaction<T> {
1295    /// Create new instance of [Self].
1296    ///
1297    /// Caution: In case of blob transactions, this does marks the blob sidecar as
1298    /// [`EthBlobTransactionSidecar::Missing`]
1299    pub fn new(transaction: Recovered<T>, encoded_length: usize) -> Self {
1300        let mut blob_sidecar = EthBlobTransactionSidecar::None;
1301
1302        let gas_cost = U256::from(transaction.max_fee_per_gas())
1303            .saturating_mul(U256::from(transaction.gas_limit()));
1304
1305        let mut cost = gas_cost.saturating_add(transaction.value());
1306
1307        if let (Some(blob_gas_used), Some(max_fee_per_blob_gas)) =
1308            (transaction.blob_gas_used(), transaction.max_fee_per_blob_gas())
1309        {
1310            // Add max blob cost using saturating math to avoid overflow
1311            cost = cost.saturating_add(U256::from(
1312                max_fee_per_blob_gas.saturating_mul(blob_gas_used as u128),
1313            ));
1314
1315            // because the blob sidecar is not included in this transaction variant, mark it as
1316            // missing
1317            blob_sidecar = EthBlobTransactionSidecar::Missing;
1318        }
1319
1320        Self { transaction, cost, encoded_length, blob_sidecar }
1321    }
1322
1323    /// Return the reference to the underlying transaction.
1324    pub const fn transaction(&self) -> &Recovered<T> {
1325        &self.transaction
1326    }
1327}
1328
1329impl PoolTransaction for EthPooledTransaction {
1330    type TryFromConsensusError = ValueError<TransactionSigned>;
1331
1332    type Consensus = TransactionSigned;
1333
1334    type Pooled = PooledTransactionVariant;
1335
1336    fn clone_into_consensus(&self) -> Recovered<Self::Consensus> {
1337        self.transaction().clone()
1338    }
1339
1340    fn into_consensus(self) -> Recovered<Self::Consensus> {
1341        self.transaction
1342    }
1343
1344    fn from_pooled(tx: Recovered<Self::Pooled>) -> Self {
1345        let encoded_length = tx.encode_2718_len();
1346        let (tx, signer) = tx.into_parts();
1347        match tx {
1348            PooledTransactionVariant::Eip4844(tx) => {
1349                // include the blob sidecar
1350                let (tx, sig, hash) = tx.into_parts();
1351                let (tx, blob) = tx.into_parts();
1352                let tx = Signed::new_unchecked(tx, sig, hash);
1353                let tx = TransactionSigned::from(tx);
1354                let tx = Recovered::new_unchecked(tx, signer);
1355                let mut pooled = Self::new(tx, encoded_length);
1356                pooled.blob_sidecar = EthBlobTransactionSidecar::Present(blob);
1357                pooled
1358            }
1359            tx => {
1360                // no blob sidecar
1361                let tx = Recovered::new_unchecked(tx.into(), signer);
1362                Self::new(tx, encoded_length)
1363            }
1364        }
1365    }
1366
1367    /// Returns hash of the transaction.
1368    fn hash(&self) -> &TxHash {
1369        self.transaction.tx_hash()
1370    }
1371
1372    /// Returns the Sender of the transaction.
1373    fn sender(&self) -> Address {
1374        self.transaction.signer()
1375    }
1376
1377    /// Returns a reference to the Sender of the transaction.
1378    fn sender_ref(&self) -> &Address {
1379        self.transaction.signer_ref()
1380    }
1381
1382    /// Returns the cost that this transaction is allowed to consume:
1383    ///
1384    /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1385    /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1386    /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1387    /// max_blob_fee_per_gas * blob_gas_used`.
1388    fn cost(&self) -> &U256 {
1389        &self.cost
1390    }
1391
1392    /// Returns the length of the rlp encoded object
1393    fn encoded_length(&self) -> usize {
1394        self.encoded_length
1395    }
1396}
1397
1398impl<T: Typed2718> Typed2718 for EthPooledTransaction<T> {
1399    fn ty(&self) -> u8 {
1400        self.transaction.ty()
1401    }
1402}
1403
1404impl<T: InMemorySize> InMemorySize for EthPooledTransaction<T> {
1405    fn size(&self) -> usize {
1406        self.transaction.size()
1407    }
1408}
1409
1410impl<T: alloy_consensus::Transaction> alloy_consensus::Transaction for EthPooledTransaction<T> {
1411    fn chain_id(&self) -> Option<alloy_primitives::ChainId> {
1412        self.transaction.chain_id()
1413    }
1414
1415    fn nonce(&self) -> u64 {
1416        self.transaction.nonce()
1417    }
1418
1419    fn gas_limit(&self) -> u64 {
1420        self.transaction.gas_limit()
1421    }
1422
1423    fn gas_price(&self) -> Option<u128> {
1424        self.transaction.gas_price()
1425    }
1426
1427    fn max_fee_per_gas(&self) -> u128 {
1428        self.transaction.max_fee_per_gas()
1429    }
1430
1431    fn max_priority_fee_per_gas(&self) -> Option<u128> {
1432        self.transaction.max_priority_fee_per_gas()
1433    }
1434
1435    fn max_fee_per_blob_gas(&self) -> Option<u128> {
1436        self.transaction.max_fee_per_blob_gas()
1437    }
1438
1439    fn priority_fee_or_price(&self) -> u128 {
1440        self.transaction.priority_fee_or_price()
1441    }
1442
1443    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1444        self.transaction.effective_gas_price(base_fee)
1445    }
1446
1447    fn is_dynamic_fee(&self) -> bool {
1448        self.transaction.is_dynamic_fee()
1449    }
1450
1451    fn kind(&self) -> TxKind {
1452        self.transaction.kind()
1453    }
1454
1455    fn is_create(&self) -> bool {
1456        self.transaction.is_create()
1457    }
1458
1459    fn value(&self) -> U256 {
1460        self.transaction.value()
1461    }
1462
1463    fn input(&self) -> &Bytes {
1464        self.transaction.input()
1465    }
1466
1467    fn access_list(&self) -> Option<&AccessList> {
1468        self.transaction.access_list()
1469    }
1470
1471    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1472        self.transaction.blob_versioned_hashes()
1473    }
1474
1475    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1476        self.transaction.authorization_list()
1477    }
1478}
1479
1480impl EthPoolTransaction for EthPooledTransaction {
1481    fn take_blob(&mut self) -> EthBlobTransactionSidecar {
1482        if self.is_eip4844() {
1483            std::mem::replace(&mut self.blob_sidecar, EthBlobTransactionSidecar::Missing)
1484        } else {
1485            EthBlobTransactionSidecar::None
1486        }
1487    }
1488
1489    fn try_into_pooled_eip4844(
1490        self,
1491        sidecar: Arc<BlobTransactionSidecarVariant>,
1492    ) -> Option<Recovered<Self::Pooled>> {
1493        let (signed_transaction, signer) = self.into_consensus().into_parts();
1494        let pooled_transaction =
1495            signed_transaction.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar)).ok()?;
1496
1497        Some(Recovered::new_unchecked(pooled_transaction, signer))
1498    }
1499
1500    fn try_from_eip4844(
1501        tx: Recovered<Self::Consensus>,
1502        sidecar: BlobTransactionSidecarVariant,
1503    ) -> Option<Self> {
1504        let (tx, signer) = tx.into_parts();
1505        tx.try_into_pooled_eip4844(sidecar)
1506            .ok()
1507            .map(|tx| tx.with_signer(signer))
1508            .map(Self::from_pooled)
1509    }
1510
1511    fn validate_blob(
1512        &self,
1513        sidecar: &BlobTransactionSidecarVariant,
1514        settings: &KzgSettings,
1515    ) -> Result<(), BlobTransactionValidationError> {
1516        match self.transaction.inner().as_eip4844() {
1517            Some(tx) => tx.tx().validate_blob(sidecar, settings),
1518            _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())),
1519        }
1520    }
1521}
1522
1523/// Represents the blob sidecar of the [`EthPooledTransaction`].
1524///
1525/// EIP-4844 blob transactions require additional data (blobs, commitments, proofs)
1526/// for validation that is not included in the consensus format. This enum tracks
1527/// the sidecar state throughout the transaction's lifecycle in the pool.
1528#[derive(Debug, Clone, PartialEq, Eq)]
1529pub enum EthBlobTransactionSidecar {
1530    /// This transaction does not have a blob sidecar
1531    /// (applies to all non-EIP-4844 transaction types)
1532    None,
1533    /// This transaction has a blob sidecar (EIP-4844) but it is missing.
1534    ///
1535    /// This can happen when:
1536    /// - The sidecar was extracted after the transaction was added to the pool
1537    /// - The transaction was re-injected after a reorg without its sidecar
1538    /// - The transaction was recovered from the consensus format (e.g., from a block)
1539    Missing,
1540    /// The EIP-4844 transaction was received from the network with its complete sidecar.
1541    ///
1542    /// This sidecar contains:
1543    /// - The actual blob data (large data per blob)
1544    /// - KZG commitments for each blob
1545    /// - KZG proofs for validation
1546    ///
1547    /// The sidecar is required for validating the transaction but is not included
1548    /// in blocks (only the blob hashes are included in the consensus format).
1549    Present(BlobTransactionSidecarVariant),
1550}
1551
1552impl EthBlobTransactionSidecar {
1553    /// Returns the blob sidecar if it is present
1554    pub const fn maybe_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
1555        match self {
1556            Self::Present(sidecar) => Some(sidecar),
1557            _ => None,
1558        }
1559    }
1560}
1561
1562/// Represents the current status of the pool.
1563#[derive(Debug, Clone, Copy, Default)]
1564pub struct PoolSize {
1565    /// Number of transactions in the _pending_ sub-pool.
1566    pub pending: usize,
1567    /// Reported size of transactions in the _pending_ sub-pool.
1568    pub pending_size: usize,
1569    /// Number of transactions in the _blob_ pool.
1570    pub blob: usize,
1571    /// Reported size of transactions in the _blob_ pool.
1572    pub blob_size: usize,
1573    /// Number of transactions in the _basefee_ pool.
1574    pub basefee: usize,
1575    /// Reported size of transactions in the _basefee_ sub-pool.
1576    pub basefee_size: usize,
1577    /// Number of transactions in the _queued_ sub-pool.
1578    pub queued: usize,
1579    /// Reported size of transactions in the _queued_ sub-pool.
1580    pub queued_size: usize,
1581    /// Number of all transactions of all sub-pools
1582    ///
1583    /// Note: this is the sum of ```pending + basefee + queued```
1584    pub total: usize,
1585}
1586
1587// === impl PoolSize ===
1588
1589impl PoolSize {
1590    /// Asserts that the invariants of the pool size are met.
1591    #[cfg(test)]
1592    pub(crate) fn assert_invariants(&self) {
1593        assert_eq!(self.total, self.pending + self.basefee + self.queued + self.blob);
1594    }
1595}
1596
1597/// Represents the current status of the pool.
1598#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
1599pub struct BlockInfo {
1600    /// Hash for the currently tracked block.
1601    pub last_seen_block_hash: B256,
1602    /// Currently tracked block.
1603    pub last_seen_block_number: u64,
1604    /// Current block gas limit for the latest block.
1605    pub block_gas_limit: u64,
1606    /// Currently enforced base fee: the threshold for the basefee sub-pool.
1607    ///
1608    /// Note: this is the derived base fee of the _next_ block that builds on the block the pool is
1609    /// currently tracking.
1610    pub pending_basefee: u64,
1611    /// Currently enforced blob fee: the threshold for eip-4844 blob transactions.
1612    ///
1613    /// Note: this is the derived blob fee of the _next_ block that builds on the block the pool is
1614    /// currently tracking
1615    pub pending_blob_fee: Option<u128>,
1616}
1617
1618/// The limit to enforce for [`TransactionPool::get_pooled_transaction_elements`].
1619#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1620pub enum GetPooledTransactionLimit {
1621    /// No limit, return all transactions.
1622    None,
1623    /// Enforce a size limit on the returned transactions, for example 2MB
1624    ResponseSizeSoftLimit(usize),
1625}
1626
1627impl GetPooledTransactionLimit {
1628    /// Returns true if the given size exceeds the limit.
1629    #[inline]
1630    pub const fn exceeds(&self, size: usize) -> bool {
1631        match self {
1632            Self::None => false,
1633            Self::ResponseSizeSoftLimit(limit) => size > *limit,
1634        }
1635    }
1636}
1637
1638/// A Stream that yields full transactions the subpool
1639#[must_use = "streams do nothing unless polled"]
1640#[derive(Debug)]
1641pub struct NewSubpoolTransactionStream<Tx: PoolTransaction> {
1642    st: Receiver<NewTransactionEvent<Tx>>,
1643    subpool: SubPool,
1644}
1645
1646// === impl NewSubpoolTransactionStream ===
1647
1648impl<Tx: PoolTransaction> NewSubpoolTransactionStream<Tx> {
1649    /// Create a new stream that yields full transactions from the subpool
1650    pub const fn new(st: Receiver<NewTransactionEvent<Tx>>, subpool: SubPool) -> Self {
1651        Self { st, subpool }
1652    }
1653
1654    /// Tries to receive the next value for this stream.
1655    pub fn try_recv(
1656        &mut self,
1657    ) -> Result<NewTransactionEvent<Tx>, tokio::sync::mpsc::error::TryRecvError> {
1658        loop {
1659            match self.st.try_recv() {
1660                Ok(event) => {
1661                    if event.subpool == self.subpool {
1662                        return Ok(event)
1663                    }
1664                }
1665                Err(e) => return Err(e),
1666            }
1667        }
1668    }
1669}
1670
1671impl<Tx: PoolTransaction> Stream for NewSubpoolTransactionStream<Tx> {
1672    type Item = NewTransactionEvent<Tx>;
1673
1674    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1675        loop {
1676            match ready!(self.st.poll_recv(cx)) {
1677                Some(event) => {
1678                    if event.subpool == self.subpool {
1679                        return Poll::Ready(Some(event))
1680                    }
1681                }
1682                None => return Poll::Ready(None),
1683            }
1684        }
1685    }
1686}
1687
1688#[cfg(test)]
1689mod tests {
1690    use super::*;
1691    use alloy_consensus::{
1692        EthereumTxEnvelope, SignableTransaction, TxEip1559, TxEip2930, TxEip4844, TxEip7702,
1693        TxEnvelope, TxLegacy,
1694    };
1695    use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
1696    use alloy_primitives::Signature;
1697
1698    #[test]
1699    fn test_pool_size_invariants() {
1700        let pool_size = PoolSize {
1701            pending: 10,
1702            pending_size: 1000,
1703            blob: 5,
1704            blob_size: 500,
1705            basefee: 8,
1706            basefee_size: 800,
1707            queued: 7,
1708            queued_size: 700,
1709            total: 10 + 5 + 8 + 7, // Correct total
1710        };
1711
1712        // Call the assert_invariants method to check if the invariants are correct
1713        pool_size.assert_invariants();
1714    }
1715
1716    #[test]
1717    #[should_panic]
1718    fn test_pool_size_invariants_fail() {
1719        let pool_size = PoolSize {
1720            pending: 10,
1721            pending_size: 1000,
1722            blob: 5,
1723            blob_size: 500,
1724            basefee: 8,
1725            basefee_size: 800,
1726            queued: 7,
1727            queued_size: 700,
1728            total: 10 + 5 + 8, // Incorrect total
1729        };
1730
1731        // Call the assert_invariants method, which should panic
1732        pool_size.assert_invariants();
1733    }
1734
1735    #[test]
1736    fn test_eth_pooled_transaction_new_legacy() {
1737        // Create a legacy transaction with specific parameters
1738        let tx = TxEnvelope::Legacy(
1739            TxLegacy {
1740                gas_price: 10,
1741                gas_limit: 1000,
1742                value: U256::from(100),
1743                ..Default::default()
1744            }
1745            .into_signed(Signature::test_signature()),
1746        );
1747        let transaction = Recovered::new_unchecked(tx, Default::default());
1748        let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1749
1750        // Check that the pooled transaction is created correctly
1751        assert_eq!(pooled_tx.transaction, transaction);
1752        assert_eq!(pooled_tx.encoded_length, 200);
1753        assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1754        assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1755    }
1756
1757    #[test]
1758    fn test_eth_pooled_transaction_new_eip2930() {
1759        // Create an EIP-2930 transaction with specific parameters
1760        let tx = TxEnvelope::Eip2930(
1761            TxEip2930 {
1762                gas_price: 10,
1763                gas_limit: 1000,
1764                value: U256::from(100),
1765                ..Default::default()
1766            }
1767            .into_signed(Signature::test_signature()),
1768        );
1769        let transaction = Recovered::new_unchecked(tx, Default::default());
1770        let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1771        let expected_cost = U256::from(100) + (U256::from(10 * 1000));
1772
1773        assert_eq!(pooled_tx.transaction, transaction);
1774        assert_eq!(pooled_tx.encoded_length, 200);
1775        assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1776        assert_eq!(pooled_tx.cost, expected_cost);
1777    }
1778
1779    #[test]
1780    fn test_eth_pooled_transaction_new_eip1559() {
1781        // Create an EIP-1559 transaction with specific parameters
1782        let tx = TxEnvelope::Eip1559(
1783            TxEip1559 {
1784                max_fee_per_gas: 10,
1785                gas_limit: 1000,
1786                value: U256::from(100),
1787                ..Default::default()
1788            }
1789            .into_signed(Signature::test_signature()),
1790        );
1791        let transaction = Recovered::new_unchecked(tx, Default::default());
1792        let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1793
1794        // Check that the pooled transaction is created correctly
1795        assert_eq!(pooled_tx.transaction, transaction);
1796        assert_eq!(pooled_tx.encoded_length, 200);
1797        assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1798        assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1799    }
1800
1801    #[test]
1802    fn test_eth_pooled_transaction_new_eip4844() {
1803        // Create an EIP-4844 transaction with specific parameters
1804        let tx = EthereumTxEnvelope::Eip4844(
1805            TxEip4844 {
1806                max_fee_per_gas: 10,
1807                gas_limit: 1000,
1808                value: U256::from(100),
1809                max_fee_per_blob_gas: 5,
1810                blob_versioned_hashes: vec![B256::default()],
1811                ..Default::default()
1812            }
1813            .into_signed(Signature::test_signature()),
1814        );
1815        let transaction = Recovered::new_unchecked(tx, Default::default());
1816        let pooled_tx = EthPooledTransaction::new(transaction.clone(), 300);
1817
1818        // Check that the pooled transaction is created correctly
1819        assert_eq!(pooled_tx.transaction, transaction);
1820        assert_eq!(pooled_tx.encoded_length, 300);
1821        assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::Missing);
1822        let expected_cost =
1823            U256::from(100) + U256::from(10 * 1000) + U256::from(5 * DATA_GAS_PER_BLOB);
1824        assert_eq!(pooled_tx.cost, expected_cost);
1825    }
1826
1827    #[test]
1828    fn test_eth_pooled_transaction_new_eip7702() {
1829        // Init an EIP-7702 transaction with specific parameters
1830        let tx = EthereumTxEnvelope::<TxEip4844>::Eip7702(
1831            TxEip7702 {
1832                max_fee_per_gas: 10,
1833                gas_limit: 1000,
1834                value: U256::from(100),
1835                ..Default::default()
1836            }
1837            .into_signed(Signature::test_signature()),
1838        );
1839        let transaction = Recovered::new_unchecked(tx, Default::default());
1840        let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1841
1842        // Check that the pooled transaction is created correctly
1843        assert_eq!(pooled_tx.transaction, transaction);
1844        assert_eq!(pooled_tx.encoded_length, 200);
1845        assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1846        assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1847    }
1848
1849    #[test]
1850    fn test_pooled_transaction_limit() {
1851        // No limit should never exceed
1852        let limit_none = GetPooledTransactionLimit::None;
1853        // Any size should return false
1854        assert!(!limit_none.exceeds(1000));
1855
1856        // Size limit of 2MB (2 * 1024 * 1024 bytes)
1857        let size_limit_2mb = GetPooledTransactionLimit::ResponseSizeSoftLimit(2 * 1024 * 1024);
1858
1859        // Test with size below the limit
1860        // 1MB is below 2MB, should return false
1861        assert!(!size_limit_2mb.exceeds(1024 * 1024));
1862
1863        // Test with size exactly at the limit
1864        // 2MB equals the limit, should return false
1865        assert!(!size_limit_2mb.exceeds(2 * 1024 * 1024));
1866
1867        // Test with size exceeding the limit
1868        // 3MB is above the 2MB limit, should return true
1869        assert!(size_limit_2mb.exceeds(3 * 1024 * 1024));
1870    }
1871}