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, 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 all transactions corresponding to the given hashes.
433 ///
434 /// Note: This removes the transactions as if they got discarded (_not_ mined).
435 ///
436 /// Consumer: Utility
437 fn remove_transactions(
438 &self,
439 hashes: Vec<TxHash>,
440 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
441
442 /// Removes all transactions corresponding to the given hashes.
443 ///
444 /// Also removes all _dependent_ transactions.
445 ///
446 /// Consumer: Utility
447 fn remove_transactions_and_descendants(
448 &self,
449 hashes: Vec<TxHash>,
450 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
451
452 /// Removes all transactions from the given sender
453 ///
454 /// Consumer: Utility
455 fn remove_transactions_by_sender(
456 &self,
457 sender: Address,
458 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
459
460 /// Retains only those hashes that are unknown to the pool.
461 /// In other words, removes all transactions from the given set that are currently present in
462 /// the pool. Returns hashes already known to the pool.
463 ///
464 /// Consumer: P2P
465 fn retain_unknown<A>(&self, announcement: &mut A)
466 where
467 A: HandleMempoolData;
468
469 /// Returns if the transaction for the given hash is already included in this pool.
470 fn contains(&self, tx_hash: &TxHash) -> bool {
471 self.get(tx_hash).is_some()
472 }
473
474 /// Returns the transaction for the given hash.
475 fn get(&self, tx_hash: &TxHash) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
476
477 /// Returns all transactions objects for the given hashes.
478 ///
479 /// Caution: This in case of blob transactions, this does not include the sidecar.
480 fn get_all(&self, txs: Vec<TxHash>) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
481
482 /// Notify the pool about transactions that are propagated to peers.
483 ///
484 /// Consumer: P2P
485 fn on_propagated(&self, txs: PropagatedTransactions);
486
487 /// Returns all transactions sent by a given user
488 fn get_transactions_by_sender(
489 &self,
490 sender: Address,
491 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
492
493 /// Returns all pending transactions filtered by predicate
494 fn get_pending_transactions_with_predicate(
495 &self,
496 predicate: impl FnMut(&ValidPoolTransaction<Self::Transaction>) -> bool,
497 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
498
499 /// Returns all pending transactions sent by a given user
500 fn get_pending_transactions_by_sender(
501 &self,
502 sender: Address,
503 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
504
505 /// Returns all queued transactions sent by a given user
506 fn get_queued_transactions_by_sender(
507 &self,
508 sender: Address,
509 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
510
511 /// Returns the highest transaction sent by a given user
512 fn get_highest_transaction_by_sender(
513 &self,
514 sender: Address,
515 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
516
517 /// Returns the transaction with the highest nonce that is executable given the on chain nonce.
518 /// In other words the highest non nonce gapped transaction.
519 ///
520 /// Note: The next pending pooled transaction must have the on chain nonce.
521 ///
522 /// For example, for a given on chain nonce of `5`, the next transaction must have that nonce.
523 /// If the pool contains txs `[5,6,7]` this returns tx `7`.
524 /// If the pool contains txs `[6,7]` this returns `None` because the next valid nonce (5) is
525 /// missing, which means txs `[6,7]` are nonce gapped.
526 fn get_highest_consecutive_transaction_by_sender(
527 &self,
528 sender: Address,
529 on_chain_nonce: u64,
530 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
531
532 /// Returns a transaction sent by a given user and a nonce
533 fn get_transaction_by_sender_and_nonce(
534 &self,
535 sender: Address,
536 nonce: u64,
537 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>>;
538
539 /// Returns all transactions that where submitted with the given [`TransactionOrigin`]
540 fn get_transactions_by_origin(
541 &self,
542 origin: TransactionOrigin,
543 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
544
545 /// Returns all pending transactions filtered by [`TransactionOrigin`]
546 fn get_pending_transactions_by_origin(
547 &self,
548 origin: TransactionOrigin,
549 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
550
551 /// Returns all transactions that where submitted as [`TransactionOrigin::Local`]
552 fn get_local_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
553 self.get_transactions_by_origin(TransactionOrigin::Local)
554 }
555
556 /// Returns all transactions that where submitted as [`TransactionOrigin::Private`]
557 fn get_private_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
558 self.get_transactions_by_origin(TransactionOrigin::Private)
559 }
560
561 /// Returns all transactions that where submitted as [`TransactionOrigin::External`]
562 fn get_external_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
563 self.get_transactions_by_origin(TransactionOrigin::External)
564 }
565
566 /// Returns all pending transactions that where submitted as [`TransactionOrigin::Local`]
567 fn get_local_pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
568 self.get_pending_transactions_by_origin(TransactionOrigin::Local)
569 }
570
571 /// Returns all pending transactions that where submitted as [`TransactionOrigin::Private`]
572 fn get_private_pending_transactions(
573 &self,
574 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
575 self.get_pending_transactions_by_origin(TransactionOrigin::Private)
576 }
577
578 /// Returns all pending transactions that where submitted as [`TransactionOrigin::External`]
579 fn get_external_pending_transactions(
580 &self,
581 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
582 self.get_pending_transactions_by_origin(TransactionOrigin::External)
583 }
584
585 /// Returns a set of all senders of transactions in the pool
586 fn unique_senders(&self) -> HashSet<Address>;
587
588 /// Returns the [`BlobTransactionSidecarVariant`] for the given transaction hash if it exists in
589 /// the blob store.
590 fn get_blob(
591 &self,
592 tx_hash: TxHash,
593 ) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
594
595 /// Returns all [`BlobTransactionSidecarVariant`] for the given transaction hashes if they
596 /// exists in the blob store.
597 ///
598 /// This only returns the blobs that were found in the store.
599 /// If there's no blob it will not be returned.
600 fn get_all_blobs(
601 &self,
602 tx_hashes: Vec<TxHash>,
603 ) -> Result<Vec<(TxHash, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError>;
604
605 /// Returns the exact [`BlobTransactionSidecarVariant`] for the given transaction hashes in the
606 /// order they were requested.
607 ///
608 /// Returns an error if any of the blobs are not found in the blob store.
609 fn get_all_blobs_exact(
610 &self,
611 tx_hashes: Vec<TxHash>,
612 ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
613
614 /// Return the [`BlobAndProofV1`]s for a list of blob versioned hashes.
615 fn get_blobs_for_versioned_hashes_v1(
616 &self,
617 versioned_hashes: &[B256],
618 ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError>;
619
620 /// Return the [`BlobAndProofV2`]s for a list of blob versioned hashes.
621 /// Blobs and proofs are returned only if they are present for _all_ of the requested versioned
622 /// hashes.
623 fn get_blobs_for_versioned_hashes_v2(
624 &self,
625 versioned_hashes: &[B256],
626 ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError>;
627}
628
629/// Extension for [`TransactionPool`] trait that allows to set the current block info.
630#[auto_impl::auto_impl(&, Arc)]
631pub trait TransactionPoolExt: TransactionPool {
632 /// Sets the current block info for the pool.
633 fn set_block_info(&self, info: BlockInfo);
634
635 /// Event listener for when the pool needs to be updated.
636 ///
637 /// Implementers need to update the pool accordingly:
638 ///
639 /// ## Fee changes
640 ///
641 /// The [`CanonicalStateUpdate`] includes the base and blob fee of the pending block, which
642 /// affects the dynamic fee requirement of pending transactions in the pool.
643 ///
644 /// ## EIP-4844 Blob transactions
645 ///
646 /// Mined blob transactions need to be removed from the pool, but from the pool only. The blob
647 /// sidecar must not be removed from the blob store. Only after a blob transaction is
648 /// finalized, its sidecar is removed from the blob store. This ensures that in case of a reorg,
649 /// the sidecar is still available.
650 fn on_canonical_state_change<B>(&self, update: CanonicalStateUpdate<'_, B>)
651 where
652 B: Block;
653
654 /// Updates the accounts in the pool
655 fn update_accounts(&self, accounts: Vec<ChangedAccount>);
656
657 /// Deletes the blob sidecar for the given transaction from the blob store
658 fn delete_blob(&self, tx: B256);
659
660 /// Deletes multiple blob sidecars from the blob store
661 fn delete_blobs(&self, txs: Vec<B256>);
662
663 /// Maintenance function to cleanup blobs that are no longer needed.
664 fn cleanup_blobs(&self);
665}
666
667/// A Helper type that bundles all transactions in the pool.
668#[derive(Debug, Clone)]
669pub struct AllPoolTransactions<T: PoolTransaction> {
670 /// Transactions that are ready for inclusion in the next block.
671 pub pending: Vec<Arc<ValidPoolTransaction<T>>>,
672 /// Transactions that are ready for inclusion in _future_ blocks, but are currently parked,
673 /// because they depend on other transactions that are not yet included in the pool (nonce gap)
674 /// or otherwise blocked.
675 pub queued: Vec<Arc<ValidPoolTransaction<T>>>,
676}
677
678// === impl AllPoolTransactions ===
679
680impl<T: PoolTransaction> AllPoolTransactions<T> {
681 /// Returns the combined number of all transactions.
682 pub const fn count(&self) -> usize {
683 self.pending.len() + self.queued.len()
684 }
685
686 /// Returns an iterator over all pending [`Recovered`] transactions.
687 pub fn pending_recovered(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
688 self.pending.iter().map(|tx| tx.transaction.clone().into_consensus())
689 }
690
691 /// Returns an iterator over all queued [`Recovered`] transactions.
692 pub fn queued_recovered(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
693 self.queued.iter().map(|tx| tx.transaction.clone().into_consensus())
694 }
695
696 /// Returns an iterator over all transactions, both pending and queued.
697 pub fn all(&self) -> impl Iterator<Item = Recovered<T::Consensus>> + '_ {
698 self.pending
699 .iter()
700 .chain(self.queued.iter())
701 .map(|tx| tx.transaction.clone().into_consensus())
702 }
703}
704
705impl<T: PoolTransaction> Default for AllPoolTransactions<T> {
706 fn default() -> Self {
707 Self { pending: Default::default(), queued: Default::default() }
708 }
709}
710
711/// Represents transactions that were propagated over the network.
712#[derive(Debug, Clone, Eq, PartialEq, Default)]
713pub struct PropagatedTransactions(pub HashMap<TxHash, Vec<PropagateKind>>);
714
715/// Represents how a transaction was propagated over the network.
716#[derive(Debug, Copy, Clone, Eq, PartialEq)]
717#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
718pub enum PropagateKind {
719 /// The full transaction object was sent to the peer.
720 ///
721 /// This is equivalent to the `Transaction` message
722 Full(PeerId),
723 /// Only the Hash was propagated to the peer.
724 Hash(PeerId),
725}
726
727// === impl PropagateKind ===
728
729impl PropagateKind {
730 /// Returns the peer the transaction was sent to
731 pub const fn peer(&self) -> &PeerId {
732 match self {
733 Self::Full(peer) | Self::Hash(peer) => peer,
734 }
735 }
736
737 /// Returns true if the transaction was sent as a full transaction
738 pub const fn is_full(&self) -> bool {
739 matches!(self, Self::Full(_))
740 }
741
742 /// Returns true if the transaction was sent as a hash
743 pub const fn is_hash(&self) -> bool {
744 matches!(self, Self::Hash(_))
745 }
746}
747
748impl From<PropagateKind> for PeerId {
749 fn from(value: PropagateKind) -> Self {
750 match value {
751 PropagateKind::Full(peer) | PropagateKind::Hash(peer) => peer,
752 }
753 }
754}
755
756/// This type represents a new blob sidecar that has been stored in the transaction pool's
757/// blobstore; it includes the `TransactionHash` of the blob transaction along with the assoc.
758/// sidecar (blobs, commitments, proofs)
759#[derive(Debug, Clone)]
760pub struct NewBlobSidecar {
761 /// hash of the EIP-4844 transaction.
762 pub tx_hash: TxHash,
763 /// the blob transaction sidecar.
764 pub sidecar: Arc<BlobTransactionSidecarVariant>,
765}
766
767/// Where the transaction originates from.
768///
769/// Depending on where the transaction was picked up, it affects how the transaction is handled
770/// internally, e.g. limits for simultaneous transaction of one sender.
771#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
772pub enum TransactionOrigin {
773 /// Transaction is coming from a local source.
774 #[default]
775 Local,
776 /// Transaction has been received externally.
777 ///
778 /// This is usually considered an "untrusted" source, for example received from another in the
779 /// network.
780 External,
781 /// Transaction is originated locally and is intended to remain private.
782 ///
783 /// This type of transaction should not be propagated to the network. It's meant for
784 /// private usage within the local node only.
785 Private,
786}
787
788// === impl TransactionOrigin ===
789
790impl TransactionOrigin {
791 /// Whether the transaction originates from a local source.
792 pub const fn is_local(&self) -> bool {
793 matches!(self, Self::Local)
794 }
795
796 /// Whether the transaction originates from an external source.
797 pub const fn is_external(&self) -> bool {
798 matches!(self, Self::External)
799 }
800 /// Whether the transaction originates from a private source.
801 pub const fn is_private(&self) -> bool {
802 matches!(self, Self::Private)
803 }
804}
805
806/// Represents the kind of update to the canonical state.
807#[derive(Debug, Clone, Copy, PartialEq, Eq)]
808pub enum PoolUpdateKind {
809 /// The update was due to a block commit.
810 Commit,
811 /// The update was due to a reorganization.
812 Reorg,
813}
814
815/// Represents changes after a new canonical block or range of canonical blocks was added to the
816/// chain.
817///
818/// It is expected that this is only used if the added blocks are canonical to the pool's last known
819/// block hash. In other words, the first added block of the range must be the child of the last
820/// known block hash.
821///
822/// This is used to update the pool state accordingly.
823#[derive(Clone, Debug)]
824pub struct CanonicalStateUpdate<'a, B: Block> {
825 /// Hash of the tip block.
826 pub new_tip: &'a SealedBlock<B>,
827 /// EIP-1559 Base fee of the _next_ (pending) block
828 ///
829 /// The base fee of a block depends on the utilization of the last block and its base fee.
830 pub pending_block_base_fee: u64,
831 /// EIP-4844 blob fee of the _next_ (pending) block
832 ///
833 /// Only after Cancun
834 pub pending_block_blob_fee: Option<u128>,
835 /// A set of changed accounts across a range of blocks.
836 pub changed_accounts: Vec<ChangedAccount>,
837 /// All mined transactions in the block range.
838 pub mined_transactions: Vec<B256>,
839 /// The kind of update to the canonical state.
840 pub update_kind: PoolUpdateKind,
841}
842
843impl<B> CanonicalStateUpdate<'_, B>
844where
845 B: Block,
846{
847 /// Returns the number of the tip block.
848 pub fn number(&self) -> u64 {
849 self.new_tip.number()
850 }
851
852 /// Returns the hash of the tip block.
853 pub fn hash(&self) -> B256 {
854 self.new_tip.hash()
855 }
856
857 /// Timestamp of the latest chain update
858 pub fn timestamp(&self) -> u64 {
859 self.new_tip.timestamp()
860 }
861
862 /// Returns the block info for the tip block.
863 pub fn block_info(&self) -> BlockInfo {
864 BlockInfo {
865 block_gas_limit: self.new_tip.gas_limit(),
866 last_seen_block_hash: self.hash(),
867 last_seen_block_number: self.number(),
868 pending_basefee: self.pending_block_base_fee,
869 pending_blob_fee: self.pending_block_blob_fee,
870 }
871 }
872}
873
874impl<B> fmt::Display for CanonicalStateUpdate<'_, B>
875where
876 B: Block,
877{
878 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
879 f.debug_struct("CanonicalStateUpdate")
880 .field("hash", &self.hash())
881 .field("number", &self.number())
882 .field("pending_block_base_fee", &self.pending_block_base_fee)
883 .field("pending_block_blob_fee", &self.pending_block_blob_fee)
884 .field("changed_accounts", &self.changed_accounts.len())
885 .field("mined_transactions", &self.mined_transactions.len())
886 .finish()
887 }
888}
889
890/// Alias to restrict the [`BestTransactions`] items to the pool's transaction type.
891pub type BestTransactionsFor<Pool> = Box<
892 dyn BestTransactions<Item = Arc<ValidPoolTransaction<<Pool as TransactionPool>::Transaction>>>,
893>;
894
895/// An `Iterator` that only returns transactions that are ready to be executed.
896///
897/// This makes no assumptions about the order of the transactions, but expects that _all_
898/// transactions are valid (no nonce gaps.) for the tracked state of the pool.
899///
900/// Note: this iterator will always return the best transaction that it currently knows.
901/// There is no guarantee transactions will be returned sequentially in decreasing
902/// priority order.
903pub trait BestTransactions: Iterator + Send {
904 /// Mark the transaction as invalid.
905 ///
906 /// Implementers must ensure all subsequent transaction _don't_ depend on this transaction.
907 /// In other words, this must remove the given transaction _and_ drain all transaction that
908 /// depend on it.
909 fn mark_invalid(&mut self, transaction: &Self::Item, kind: InvalidPoolTransactionError);
910
911 /// An iterator may be able to receive additional pending transactions that weren't present it
912 /// the pool when it was created.
913 ///
914 /// This ensures that iterator will return the best transaction that it currently knows and not
915 /// listen to pool updates.
916 fn no_updates(&mut self);
917
918 /// Convenience function for [`Self::no_updates`] that returns the iterator again.
919 fn without_updates(mut self) -> Self
920 where
921 Self: Sized,
922 {
923 self.no_updates();
924 self
925 }
926
927 /// Skip all blob transactions.
928 ///
929 /// There's only limited blob space available in a block, once exhausted, EIP-4844 transactions
930 /// can no longer be included.
931 ///
932 /// If called then the iterator will no longer yield blob transactions.
933 ///
934 /// Note: this will also exclude any transactions that depend on blob transactions.
935 fn skip_blobs(&mut self) {
936 self.set_skip_blobs(true);
937 }
938
939 /// Controls whether the iterator skips blob transactions or not.
940 ///
941 /// If set to true, no blob transactions will be returned.
942 fn set_skip_blobs(&mut self, skip_blobs: bool);
943
944 /// Convenience function for [`Self::skip_blobs`] that returns the iterator again.
945 fn without_blobs(mut self) -> Self
946 where
947 Self: Sized,
948 {
949 self.skip_blobs();
950 self
951 }
952
953 /// Creates an iterator which uses a closure to determine whether a transaction should be
954 /// returned by the iterator.
955 ///
956 /// All items the closure returns false for are marked as invalid via [`Self::mark_invalid`] and
957 /// descendant transactions will be skipped.
958 fn filter_transactions<P>(self, predicate: P) -> BestTransactionFilter<Self, P>
959 where
960 P: FnMut(&Self::Item) -> bool,
961 Self: Sized,
962 {
963 BestTransactionFilter::new(self, predicate)
964 }
965}
966
967impl<T> BestTransactions for Box<T>
968where
969 T: BestTransactions + ?Sized,
970{
971 fn mark_invalid(&mut self, transaction: &Self::Item, kind: InvalidPoolTransactionError) {
972 (**self).mark_invalid(transaction, kind)
973 }
974
975 fn no_updates(&mut self) {
976 (**self).no_updates();
977 }
978
979 fn skip_blobs(&mut self) {
980 (**self).skip_blobs();
981 }
982
983 fn set_skip_blobs(&mut self, skip_blobs: bool) {
984 (**self).set_skip_blobs(skip_blobs);
985 }
986}
987
988/// A no-op implementation that yields no transactions.
989impl<T> BestTransactions for std::iter::Empty<T> {
990 fn mark_invalid(&mut self, _tx: &T, _kind: InvalidPoolTransactionError) {}
991
992 fn no_updates(&mut self) {}
993
994 fn skip_blobs(&mut self) {}
995
996 fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
997}
998
999/// A filter that allows to check if a transaction satisfies a set of conditions
1000pub trait TransactionFilter {
1001 /// The type of the transaction to check.
1002 type Transaction;
1003
1004 /// Returns true if the transaction satisfies the conditions.
1005 fn is_valid(&self, transaction: &Self::Transaction) -> bool;
1006}
1007
1008/// A no-op implementation of [`TransactionFilter`] which
1009/// marks all transactions as valid.
1010#[derive(Debug, Clone)]
1011pub struct NoopTransactionFilter<T>(std::marker::PhantomData<T>);
1012
1013// We can't derive Default because this forces T to be
1014// Default as well, which isn't necessary.
1015impl<T> Default for NoopTransactionFilter<T> {
1016 fn default() -> Self {
1017 Self(std::marker::PhantomData)
1018 }
1019}
1020
1021impl<T> TransactionFilter for NoopTransactionFilter<T> {
1022 type Transaction = T;
1023
1024 fn is_valid(&self, _transaction: &Self::Transaction) -> bool {
1025 true
1026 }
1027}
1028
1029/// A Helper type that bundles the best transactions attributes together.
1030#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1031pub struct BestTransactionsAttributes {
1032 /// The base fee attribute for best transactions.
1033 pub basefee: u64,
1034 /// The blob fee attribute for best transactions.
1035 pub blob_fee: Option<u64>,
1036}
1037
1038// === impl BestTransactionsAttributes ===
1039
1040impl BestTransactionsAttributes {
1041 /// Creates a new `BestTransactionsAttributes` with the given basefee and blob fee.
1042 pub const fn new(basefee: u64, blob_fee: Option<u64>) -> Self {
1043 Self { basefee, blob_fee }
1044 }
1045
1046 /// Creates a new `BestTransactionsAttributes` with the given basefee.
1047 pub const fn base_fee(basefee: u64) -> Self {
1048 Self::new(basefee, None)
1049 }
1050
1051 /// Sets the given blob fee.
1052 pub const fn with_blob_fee(mut self, blob_fee: u64) -> Self {
1053 self.blob_fee = Some(blob_fee);
1054 self
1055 }
1056}
1057
1058/// Trait for transaction types stored in the transaction pool.
1059///
1060/// This trait represents the actual transaction object stored in the mempool, which includes not
1061/// only the transaction data itself but also additional metadata needed for efficient pool
1062/// operations. Implementations typically cache values that are frequently accessed during
1063/// transaction ordering, validation, and eviction.
1064///
1065/// ## Key Responsibilities
1066///
1067/// 1. **Metadata Caching**: Store computed values like address, cost and encoded size
1068/// 2. **Representation Conversion**: Handle conversions between consensus and pooled
1069/// representations
1070/// 3. **Validation Support**: Provide methods for pool-specific validation rules
1071///
1072/// ## Cached Metadata
1073///
1074/// Implementations should cache frequently accessed values to avoid recomputation:
1075/// - **Address**: Recovered sender address of the transaction
1076/// - **Cost**: Max amount spendable (gas × price + value + blob costs)
1077/// - **Size**: RLP encoded length for mempool size limits
1078///
1079/// See [`EthPooledTransaction`] for a reference implementation.
1080///
1081/// ## Transaction Representations
1082///
1083/// This trait abstracts over the different representations a transaction can have:
1084///
1085/// 1. **Consensus representation** (`Consensus` associated type): The canonical form included in
1086/// blocks
1087/// - Compact representation without networking metadata
1088/// - For EIP-4844: includes only blob hashes, not the actual blobs
1089/// - Used for block execution and state transitions
1090///
1091/// 2. **Pooled representation** (`Pooled` associated type): The form used for network propagation
1092/// - May include additional data for validation
1093/// - For EIP-4844: includes full blob sidecars (blobs, commitments, proofs)
1094/// - Used for mempool validation and p2p gossiping
1095///
1096/// ## Why Two Representations?
1097///
1098/// This distinction is necessary because:
1099///
1100/// - **EIP-4844 blob transactions**: Require large blob sidecars for validation that would bloat
1101/// blocks if included. Only blob hashes are stored on-chain.
1102///
1103/// - **Network efficiency**: Blob transactions are not broadcast to all peers automatically but
1104/// must be explicitly requested to reduce bandwidth usage.
1105///
1106/// - **Special transactions**: Some transactions (like OP deposit transactions) exist only in
1107/// consensus format and are never in the mempool.
1108///
1109/// ## Conversion Rules
1110///
1111/// - `Consensus` → `Pooled`: May fail for transactions that cannot be pooled (e.g., OP deposit
1112/// transactions, blob transactions without sidecars)
1113/// - `Pooled` → `Consensus`: Always succeeds (pooled is a superset)
1114pub trait PoolTransaction:
1115 alloy_consensus::Transaction + InMemorySize + Debug + Send + Sync + Clone
1116{
1117 /// Associated error type for the `try_from_consensus` method.
1118 type TryFromConsensusError: fmt::Display;
1119
1120 /// Associated type representing the raw consensus variant of the transaction.
1121 type Consensus: SignedTransaction + From<Self::Pooled>;
1122
1123 /// Associated type representing the recovered pooled variant of the transaction.
1124 type Pooled: TryFrom<Self::Consensus, Error = Self::TryFromConsensusError> + SignedTransaction;
1125
1126 /// Define a method to convert from the `Consensus` type to `Self`
1127 ///
1128 /// This conversion may fail for transactions that are valid for inclusion in blocks
1129 /// but cannot exist in the transaction pool. Examples include:
1130 ///
1131 /// - **OP Deposit transactions**: These are special system transactions that are directly
1132 /// included in blocks by the sequencer/validator and never enter the mempool
1133 /// - **Blob transactions without sidecars**: After being included in a block, the sidecar data
1134 /// is pruned, making the consensus transaction unpoolable
1135 fn try_from_consensus(
1136 tx: Recovered<Self::Consensus>,
1137 ) -> Result<Self, Self::TryFromConsensusError> {
1138 let (tx, signer) = tx.into_parts();
1139 Ok(Self::from_pooled(Recovered::new_unchecked(tx.try_into()?, signer)))
1140 }
1141
1142 /// Clone the transaction into a consensus variant.
1143 ///
1144 /// This method is preferred when the [`PoolTransaction`] already wraps the consensus variant.
1145 fn clone_into_consensus(&self) -> Recovered<Self::Consensus> {
1146 self.clone().into_consensus()
1147 }
1148
1149 /// Define a method to convert from the `Self` type to `Consensus`
1150 fn into_consensus(self) -> Recovered<Self::Consensus>;
1151
1152 /// Converts the transaction into consensus format while preserving the EIP-2718 encoded bytes.
1153 /// This is used to optimize transaction execution by reusing cached encoded bytes instead of
1154 /// re-encoding the transaction. The cached bytes are particularly useful in payload building
1155 /// where the same transaction may be executed multiple times.
1156 fn into_consensus_with2718(self) -> WithEncoded<Recovered<Self::Consensus>> {
1157 self.into_consensus().into_encoded()
1158 }
1159
1160 /// Define a method to convert from the `Pooled` type to `Self`
1161 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self;
1162
1163 /// Tries to convert the `Consensus` type into the `Pooled` type.
1164 fn try_into_pooled(self) -> Result<Recovered<Self::Pooled>, Self::TryFromConsensusError> {
1165 let consensus = self.into_consensus();
1166 let (tx, signer) = consensus.into_parts();
1167 Ok(Recovered::new_unchecked(tx.try_into()?, signer))
1168 }
1169
1170 /// Converts the `Pooled` type into the `Consensus` type.
1171 fn pooled_into_consensus(tx: Self::Pooled) -> Self::Consensus {
1172 tx.into()
1173 }
1174
1175 /// Hash of the transaction.
1176 fn hash(&self) -> &TxHash;
1177
1178 /// The Sender of the transaction.
1179 fn sender(&self) -> Address;
1180
1181 /// Reference to the Sender of the transaction.
1182 fn sender_ref(&self) -> &Address;
1183
1184 /// Returns the cost that this transaction is allowed to consume:
1185 ///
1186 /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1187 /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1188 /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1189 /// max_blob_fee_per_gas * blob_gas_used`.
1190 fn cost(&self) -> &U256;
1191
1192 /// Returns the length of the rlp encoded transaction object
1193 ///
1194 /// Note: Implementations should cache this value.
1195 fn encoded_length(&self) -> usize;
1196
1197 /// Ensures that the transaction's code size does not exceed the provided `max_init_code_size`.
1198 ///
1199 /// This is specifically relevant for contract creation transactions ([`TxKind::Create`]),
1200 /// where the input data contains the initialization code. If the input code size exceeds
1201 /// the configured limit, an [`InvalidPoolTransactionError::ExceedsMaxInitCodeSize`] error is
1202 /// returned.
1203 fn ensure_max_init_code_size(
1204 &self,
1205 max_init_code_size: usize,
1206 ) -> Result<(), InvalidPoolTransactionError> {
1207 let input_len = self.input().len();
1208 if self.is_create() && input_len > max_init_code_size {
1209 Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize(input_len, max_init_code_size))
1210 } else {
1211 Ok(())
1212 }
1213 }
1214}
1215
1216/// Super trait for transactions that can be converted to and from Eth transactions intended for the
1217/// ethereum style pool.
1218///
1219/// This extends the [`PoolTransaction`] trait with additional methods that are specific to the
1220/// Ethereum pool.
1221pub trait EthPoolTransaction: PoolTransaction {
1222 /// Extracts the blob sidecar from the transaction.
1223 fn take_blob(&mut self) -> EthBlobTransactionSidecar;
1224
1225 /// A specialization for the EIP-4844 transaction type.
1226 /// Tries to reattach the blob sidecar to the transaction.
1227 ///
1228 /// This returns an option, but callers should ensure that the transaction is an EIP-4844
1229 /// transaction: [`Typed2718::is_eip4844`].
1230 fn try_into_pooled_eip4844(
1231 self,
1232 sidecar: Arc<BlobTransactionSidecarVariant>,
1233 ) -> Option<Recovered<Self::Pooled>>;
1234
1235 /// Tries to convert the `Consensus` type with a blob sidecar into the `Pooled` type.
1236 ///
1237 /// Returns `None` if passed transaction is not a blob transaction.
1238 fn try_from_eip4844(
1239 tx: Recovered<Self::Consensus>,
1240 sidecar: BlobTransactionSidecarVariant,
1241 ) -> Option<Self>;
1242
1243 /// Validates the blob sidecar of the transaction with the given settings.
1244 fn validate_blob(
1245 &self,
1246 blob: &BlobTransactionSidecarVariant,
1247 settings: &KzgSettings,
1248 ) -> Result<(), BlobTransactionValidationError>;
1249}
1250
1251/// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum.
1252///
1253/// This type wraps a consensus transaction with additional cached data that's
1254/// frequently accessed by the pool for transaction ordering and validation:
1255///
1256/// - `cost`: Pre-calculated max cost (gas * price + value + blob costs)
1257/// - `encoded_length`: Cached RLP encoding length for size limits
1258/// - `blob_sidecar`: Blob data state (None/Missing/Present)
1259///
1260/// This avoids recalculating these values repeatedly during pool operations.
1261#[derive(Debug, Clone, PartialEq, Eq)]
1262pub struct EthPooledTransaction<T = TransactionSigned> {
1263 /// `EcRecovered` transaction, the consensus format.
1264 pub transaction: Recovered<T>,
1265
1266 /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1267 /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1268 /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1269 /// max_blob_fee_per_gas * blob_gas_used`.
1270 pub cost: U256,
1271
1272 /// This is the RLP length of the transaction, computed when the transaction is added to the
1273 /// pool.
1274 pub encoded_length: usize,
1275
1276 /// The blob side car for this transaction
1277 pub blob_sidecar: EthBlobTransactionSidecar,
1278}
1279
1280impl<T: SignedTransaction> EthPooledTransaction<T> {
1281 /// Create new instance of [Self].
1282 ///
1283 /// Caution: In case of blob transactions, this does marks the blob sidecar as
1284 /// [`EthBlobTransactionSidecar::Missing`]
1285 pub fn new(transaction: Recovered<T>, encoded_length: usize) -> Self {
1286 let mut blob_sidecar = EthBlobTransactionSidecar::None;
1287
1288 let gas_cost = U256::from(transaction.max_fee_per_gas())
1289 .saturating_mul(U256::from(transaction.gas_limit()));
1290
1291 let mut cost = gas_cost.saturating_add(transaction.value());
1292
1293 if let (Some(blob_gas_used), Some(max_fee_per_blob_gas)) =
1294 (transaction.blob_gas_used(), transaction.max_fee_per_blob_gas())
1295 {
1296 // Add max blob cost using saturating math to avoid overflow
1297 cost = cost.saturating_add(U256::from(
1298 max_fee_per_blob_gas.saturating_mul(blob_gas_used as u128),
1299 ));
1300
1301 // because the blob sidecar is not included in this transaction variant, mark it as
1302 // missing
1303 blob_sidecar = EthBlobTransactionSidecar::Missing;
1304 }
1305
1306 Self { transaction, cost, encoded_length, blob_sidecar }
1307 }
1308
1309 /// Return the reference to the underlying transaction.
1310 pub const fn transaction(&self) -> &Recovered<T> {
1311 &self.transaction
1312 }
1313}
1314
1315impl PoolTransaction for EthPooledTransaction {
1316 type TryFromConsensusError = ValueError<TransactionSigned>;
1317
1318 type Consensus = TransactionSigned;
1319
1320 type Pooled = PooledTransactionVariant;
1321
1322 fn clone_into_consensus(&self) -> Recovered<Self::Consensus> {
1323 self.transaction().clone()
1324 }
1325
1326 fn into_consensus(self) -> Recovered<Self::Consensus> {
1327 self.transaction
1328 }
1329
1330 fn from_pooled(tx: Recovered<Self::Pooled>) -> Self {
1331 let encoded_length = tx.encode_2718_len();
1332 let (tx, signer) = tx.into_parts();
1333 match tx {
1334 PooledTransactionVariant::Eip4844(tx) => {
1335 // include the blob sidecar
1336 let (tx, sig, hash) = tx.into_parts();
1337 let (tx, blob) = tx.into_parts();
1338 let tx = Signed::new_unchecked(tx, sig, hash);
1339 let tx = TransactionSigned::from(tx);
1340 let tx = Recovered::new_unchecked(tx, signer);
1341 let mut pooled = Self::new(tx, encoded_length);
1342 pooled.blob_sidecar = EthBlobTransactionSidecar::Present(blob);
1343 pooled
1344 }
1345 tx => {
1346 // no blob sidecar
1347 let tx = Recovered::new_unchecked(tx.into(), signer);
1348 Self::new(tx, encoded_length)
1349 }
1350 }
1351 }
1352
1353 /// Returns hash of the transaction.
1354 fn hash(&self) -> &TxHash {
1355 self.transaction.tx_hash()
1356 }
1357
1358 /// Returns the Sender of the transaction.
1359 fn sender(&self) -> Address {
1360 self.transaction.signer()
1361 }
1362
1363 /// Returns a reference to the Sender of the transaction.
1364 fn sender_ref(&self) -> &Address {
1365 self.transaction.signer_ref()
1366 }
1367
1368 /// Returns the cost that this transaction is allowed to consume:
1369 ///
1370 /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
1371 /// For legacy transactions: `gas_price * gas_limit + tx_value`.
1372 /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value +
1373 /// max_blob_fee_per_gas * blob_gas_used`.
1374 fn cost(&self) -> &U256 {
1375 &self.cost
1376 }
1377
1378 /// Returns the length of the rlp encoded object
1379 fn encoded_length(&self) -> usize {
1380 self.encoded_length
1381 }
1382}
1383
1384impl<T: Typed2718> Typed2718 for EthPooledTransaction<T> {
1385 fn ty(&self) -> u8 {
1386 self.transaction.ty()
1387 }
1388}
1389
1390impl<T: InMemorySize> InMemorySize for EthPooledTransaction<T> {
1391 fn size(&self) -> usize {
1392 self.transaction.size()
1393 }
1394}
1395
1396impl<T: alloy_consensus::Transaction> alloy_consensus::Transaction for EthPooledTransaction<T> {
1397 fn chain_id(&self) -> Option<alloy_primitives::ChainId> {
1398 self.transaction.chain_id()
1399 }
1400
1401 fn nonce(&self) -> u64 {
1402 self.transaction.nonce()
1403 }
1404
1405 fn gas_limit(&self) -> u64 {
1406 self.transaction.gas_limit()
1407 }
1408
1409 fn gas_price(&self) -> Option<u128> {
1410 self.transaction.gas_price()
1411 }
1412
1413 fn max_fee_per_gas(&self) -> u128 {
1414 self.transaction.max_fee_per_gas()
1415 }
1416
1417 fn max_priority_fee_per_gas(&self) -> Option<u128> {
1418 self.transaction.max_priority_fee_per_gas()
1419 }
1420
1421 fn max_fee_per_blob_gas(&self) -> Option<u128> {
1422 self.transaction.max_fee_per_blob_gas()
1423 }
1424
1425 fn priority_fee_or_price(&self) -> u128 {
1426 self.transaction.priority_fee_or_price()
1427 }
1428
1429 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1430 self.transaction.effective_gas_price(base_fee)
1431 }
1432
1433 fn is_dynamic_fee(&self) -> bool {
1434 self.transaction.is_dynamic_fee()
1435 }
1436
1437 fn kind(&self) -> TxKind {
1438 self.transaction.kind()
1439 }
1440
1441 fn is_create(&self) -> bool {
1442 self.transaction.is_create()
1443 }
1444
1445 fn value(&self) -> U256 {
1446 self.transaction.value()
1447 }
1448
1449 fn input(&self) -> &Bytes {
1450 self.transaction.input()
1451 }
1452
1453 fn access_list(&self) -> Option<&AccessList> {
1454 self.transaction.access_list()
1455 }
1456
1457 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1458 self.transaction.blob_versioned_hashes()
1459 }
1460
1461 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1462 self.transaction.authorization_list()
1463 }
1464}
1465
1466impl EthPoolTransaction for EthPooledTransaction {
1467 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
1468 if self.is_eip4844() {
1469 std::mem::replace(&mut self.blob_sidecar, EthBlobTransactionSidecar::Missing)
1470 } else {
1471 EthBlobTransactionSidecar::None
1472 }
1473 }
1474
1475 fn try_into_pooled_eip4844(
1476 self,
1477 sidecar: Arc<BlobTransactionSidecarVariant>,
1478 ) -> Option<Recovered<Self::Pooled>> {
1479 let (signed_transaction, signer) = self.into_consensus().into_parts();
1480 let pooled_transaction =
1481 signed_transaction.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar)).ok()?;
1482
1483 Some(Recovered::new_unchecked(pooled_transaction, signer))
1484 }
1485
1486 fn try_from_eip4844(
1487 tx: Recovered<Self::Consensus>,
1488 sidecar: BlobTransactionSidecarVariant,
1489 ) -> Option<Self> {
1490 let (tx, signer) = tx.into_parts();
1491 tx.try_into_pooled_eip4844(sidecar)
1492 .ok()
1493 .map(|tx| tx.with_signer(signer))
1494 .map(Self::from_pooled)
1495 }
1496
1497 fn validate_blob(
1498 &self,
1499 sidecar: &BlobTransactionSidecarVariant,
1500 settings: &KzgSettings,
1501 ) -> Result<(), BlobTransactionValidationError> {
1502 match self.transaction.inner().as_eip4844() {
1503 Some(tx) => tx.tx().validate_blob(sidecar, settings),
1504 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())),
1505 }
1506 }
1507}
1508
1509/// Represents the blob sidecar of the [`EthPooledTransaction`].
1510///
1511/// EIP-4844 blob transactions require additional data (blobs, commitments, proofs)
1512/// for validation that is not included in the consensus format. This enum tracks
1513/// the sidecar state throughout the transaction's lifecycle in the pool.
1514#[derive(Debug, Clone, PartialEq, Eq)]
1515pub enum EthBlobTransactionSidecar {
1516 /// This transaction does not have a blob sidecar
1517 /// (applies to all non-EIP-4844 transaction types)
1518 None,
1519 /// This transaction has a blob sidecar (EIP-4844) but it is missing.
1520 ///
1521 /// This can happen when:
1522 /// - The sidecar was extracted after the transaction was added to the pool
1523 /// - The transaction was re-injected after a reorg without its sidecar
1524 /// - The transaction was recovered from the consensus format (e.g., from a block)
1525 Missing,
1526 /// The EIP-4844 transaction was received from the network with its complete sidecar.
1527 ///
1528 /// This sidecar contains:
1529 /// - The actual blob data (large data per blob)
1530 /// - KZG commitments for each blob
1531 /// - KZG proofs for validation
1532 ///
1533 /// The sidecar is required for validating the transaction but is not included
1534 /// in blocks (only the blob hashes are included in the consensus format).
1535 Present(BlobTransactionSidecarVariant),
1536}
1537
1538impl EthBlobTransactionSidecar {
1539 /// Returns the blob sidecar if it is present
1540 pub const fn maybe_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
1541 match self {
1542 Self::Present(sidecar) => Some(sidecar),
1543 _ => None,
1544 }
1545 }
1546}
1547
1548/// Represents the current status of the pool.
1549#[derive(Debug, Clone, Copy, Default)]
1550pub struct PoolSize {
1551 /// Number of transactions in the _pending_ sub-pool.
1552 pub pending: usize,
1553 /// Reported size of transactions in the _pending_ sub-pool.
1554 pub pending_size: usize,
1555 /// Number of transactions in the _blob_ pool.
1556 pub blob: usize,
1557 /// Reported size of transactions in the _blob_ pool.
1558 pub blob_size: usize,
1559 /// Number of transactions in the _basefee_ pool.
1560 pub basefee: usize,
1561 /// Reported size of transactions in the _basefee_ sub-pool.
1562 pub basefee_size: usize,
1563 /// Number of transactions in the _queued_ sub-pool.
1564 pub queued: usize,
1565 /// Reported size of transactions in the _queued_ sub-pool.
1566 pub queued_size: usize,
1567 /// Number of all transactions of all sub-pools
1568 ///
1569 /// Note: this is the sum of ```pending + basefee + queued```
1570 pub total: usize,
1571}
1572
1573// === impl PoolSize ===
1574
1575impl PoolSize {
1576 /// Asserts that the invariants of the pool size are met.
1577 #[cfg(test)]
1578 pub(crate) fn assert_invariants(&self) {
1579 assert_eq!(self.total, self.pending + self.basefee + self.queued + self.blob);
1580 }
1581}
1582
1583/// Represents the current status of the pool.
1584#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
1585pub struct BlockInfo {
1586 /// Hash for the currently tracked block.
1587 pub last_seen_block_hash: B256,
1588 /// Currently tracked block.
1589 pub last_seen_block_number: u64,
1590 /// Current block gas limit for the latest block.
1591 pub block_gas_limit: u64,
1592 /// Currently enforced base fee: the threshold for the basefee sub-pool.
1593 ///
1594 /// Note: this is the derived base fee of the _next_ block that builds on the block the pool is
1595 /// currently tracking.
1596 pub pending_basefee: u64,
1597 /// Currently enforced blob fee: the threshold for eip-4844 blob transactions.
1598 ///
1599 /// Note: this is the derived blob fee of the _next_ block that builds on the block the pool is
1600 /// currently tracking
1601 pub pending_blob_fee: Option<u128>,
1602}
1603
1604/// The limit to enforce for [`TransactionPool::get_pooled_transaction_elements`].
1605#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1606pub enum GetPooledTransactionLimit {
1607 /// No limit, return all transactions.
1608 None,
1609 /// Enforce a size limit on the returned transactions, for example 2MB
1610 ResponseSizeSoftLimit(usize),
1611}
1612
1613impl GetPooledTransactionLimit {
1614 /// Returns true if the given size exceeds the limit.
1615 #[inline]
1616 pub const fn exceeds(&self, size: usize) -> bool {
1617 match self {
1618 Self::None => false,
1619 Self::ResponseSizeSoftLimit(limit) => size > *limit,
1620 }
1621 }
1622}
1623
1624/// A Stream that yields full transactions the subpool
1625#[must_use = "streams do nothing unless polled"]
1626#[derive(Debug)]
1627pub struct NewSubpoolTransactionStream<Tx: PoolTransaction> {
1628 st: Receiver<NewTransactionEvent<Tx>>,
1629 subpool: SubPool,
1630}
1631
1632// === impl NewSubpoolTransactionStream ===
1633
1634impl<Tx: PoolTransaction> NewSubpoolTransactionStream<Tx> {
1635 /// Create a new stream that yields full transactions from the subpool
1636 pub const fn new(st: Receiver<NewTransactionEvent<Tx>>, subpool: SubPool) -> Self {
1637 Self { st, subpool }
1638 }
1639
1640 /// Tries to receive the next value for this stream.
1641 pub fn try_recv(
1642 &mut self,
1643 ) -> Result<NewTransactionEvent<Tx>, tokio::sync::mpsc::error::TryRecvError> {
1644 loop {
1645 match self.st.try_recv() {
1646 Ok(event) => {
1647 if event.subpool == self.subpool {
1648 return Ok(event)
1649 }
1650 }
1651 Err(e) => return Err(e),
1652 }
1653 }
1654 }
1655}
1656
1657impl<Tx: PoolTransaction> Stream for NewSubpoolTransactionStream<Tx> {
1658 type Item = NewTransactionEvent<Tx>;
1659
1660 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1661 loop {
1662 match ready!(self.st.poll_recv(cx)) {
1663 Some(event) => {
1664 if event.subpool == self.subpool {
1665 return Poll::Ready(Some(event))
1666 }
1667 }
1668 None => return Poll::Ready(None),
1669 }
1670 }
1671 }
1672}
1673
1674#[cfg(test)]
1675mod tests {
1676 use super::*;
1677 use alloy_consensus::{
1678 EthereumTxEnvelope, SignableTransaction, TxEip1559, TxEip2930, TxEip4844, TxEip7702,
1679 TxEnvelope, TxLegacy,
1680 };
1681 use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
1682 use alloy_primitives::Signature;
1683
1684 #[test]
1685 fn test_pool_size_invariants() {
1686 let pool_size = PoolSize {
1687 pending: 10,
1688 pending_size: 1000,
1689 blob: 5,
1690 blob_size: 500,
1691 basefee: 8,
1692 basefee_size: 800,
1693 queued: 7,
1694 queued_size: 700,
1695 total: 10 + 5 + 8 + 7, // Correct total
1696 };
1697
1698 // Call the assert_invariants method to check if the invariants are correct
1699 pool_size.assert_invariants();
1700 }
1701
1702 #[test]
1703 #[should_panic]
1704 fn test_pool_size_invariants_fail() {
1705 let pool_size = PoolSize {
1706 pending: 10,
1707 pending_size: 1000,
1708 blob: 5,
1709 blob_size: 500,
1710 basefee: 8,
1711 basefee_size: 800,
1712 queued: 7,
1713 queued_size: 700,
1714 total: 10 + 5 + 8, // Incorrect total
1715 };
1716
1717 // Call the assert_invariants method, which should panic
1718 pool_size.assert_invariants();
1719 }
1720
1721 #[test]
1722 fn test_eth_pooled_transaction_new_legacy() {
1723 // Create a legacy transaction with specific parameters
1724 let tx = TxEnvelope::Legacy(
1725 TxLegacy {
1726 gas_price: 10,
1727 gas_limit: 1000,
1728 value: U256::from(100),
1729 ..Default::default()
1730 }
1731 .into_signed(Signature::test_signature()),
1732 );
1733 let transaction = Recovered::new_unchecked(tx, Default::default());
1734 let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1735
1736 // Check that the pooled transaction is created correctly
1737 assert_eq!(pooled_tx.transaction, transaction);
1738 assert_eq!(pooled_tx.encoded_length, 200);
1739 assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1740 assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1741 }
1742
1743 #[test]
1744 fn test_eth_pooled_transaction_new_eip2930() {
1745 // Create an EIP-2930 transaction with specific parameters
1746 let tx = TxEnvelope::Eip2930(
1747 TxEip2930 {
1748 gas_price: 10,
1749 gas_limit: 1000,
1750 value: U256::from(100),
1751 ..Default::default()
1752 }
1753 .into_signed(Signature::test_signature()),
1754 );
1755 let transaction = Recovered::new_unchecked(tx, Default::default());
1756 let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1757 let expected_cost = U256::from(100) + (U256::from(10 * 1000));
1758
1759 assert_eq!(pooled_tx.transaction, transaction);
1760 assert_eq!(pooled_tx.encoded_length, 200);
1761 assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1762 assert_eq!(pooled_tx.cost, expected_cost);
1763 }
1764
1765 #[test]
1766 fn test_eth_pooled_transaction_new_eip1559() {
1767 // Create an EIP-1559 transaction with specific parameters
1768 let tx = TxEnvelope::Eip1559(
1769 TxEip1559 {
1770 max_fee_per_gas: 10,
1771 gas_limit: 1000,
1772 value: U256::from(100),
1773 ..Default::default()
1774 }
1775 .into_signed(Signature::test_signature()),
1776 );
1777 let transaction = Recovered::new_unchecked(tx, Default::default());
1778 let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1779
1780 // Check that the pooled transaction is created correctly
1781 assert_eq!(pooled_tx.transaction, transaction);
1782 assert_eq!(pooled_tx.encoded_length, 200);
1783 assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1784 assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1785 }
1786
1787 #[test]
1788 fn test_eth_pooled_transaction_new_eip4844() {
1789 // Create an EIP-4844 transaction with specific parameters
1790 let tx = EthereumTxEnvelope::Eip4844(
1791 TxEip4844 {
1792 max_fee_per_gas: 10,
1793 gas_limit: 1000,
1794 value: U256::from(100),
1795 max_fee_per_blob_gas: 5,
1796 blob_versioned_hashes: vec![B256::default()],
1797 ..Default::default()
1798 }
1799 .into_signed(Signature::test_signature()),
1800 );
1801 let transaction = Recovered::new_unchecked(tx, Default::default());
1802 let pooled_tx = EthPooledTransaction::new(transaction.clone(), 300);
1803
1804 // Check that the pooled transaction is created correctly
1805 assert_eq!(pooled_tx.transaction, transaction);
1806 assert_eq!(pooled_tx.encoded_length, 300);
1807 assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::Missing);
1808 let expected_cost =
1809 U256::from(100) + U256::from(10 * 1000) + U256::from(5 * DATA_GAS_PER_BLOB);
1810 assert_eq!(pooled_tx.cost, expected_cost);
1811 }
1812
1813 #[test]
1814 fn test_eth_pooled_transaction_new_eip7702() {
1815 // Init an EIP-7702 transaction with specific parameters
1816 let tx = EthereumTxEnvelope::<TxEip4844>::Eip7702(
1817 TxEip7702 {
1818 max_fee_per_gas: 10,
1819 gas_limit: 1000,
1820 value: U256::from(100),
1821 ..Default::default()
1822 }
1823 .into_signed(Signature::test_signature()),
1824 );
1825 let transaction = Recovered::new_unchecked(tx, Default::default());
1826 let pooled_tx = EthPooledTransaction::new(transaction.clone(), 200);
1827
1828 // Check that the pooled transaction is created correctly
1829 assert_eq!(pooled_tx.transaction, transaction);
1830 assert_eq!(pooled_tx.encoded_length, 200);
1831 assert_eq!(pooled_tx.blob_sidecar, EthBlobTransactionSidecar::None);
1832 assert_eq!(pooled_tx.cost, U256::from(100) + U256::from(10 * 1000));
1833 }
1834
1835 #[test]
1836 fn test_pooled_transaction_limit() {
1837 // No limit should never exceed
1838 let limit_none = GetPooledTransactionLimit::None;
1839 // Any size should return false
1840 assert!(!limit_none.exceeds(1000));
1841
1842 // Size limit of 2MB (2 * 1024 * 1024 bytes)
1843 let size_limit_2mb = GetPooledTransactionLimit::ResponseSizeSoftLimit(2 * 1024 * 1024);
1844
1845 // Test with size below the limit
1846 // 1MB is below 2MB, should return false
1847 assert!(!size_limit_2mb.exceeds(1024 * 1024));
1848
1849 // Test with size exactly at the limit
1850 // 2MB equals the limit, should return false
1851 assert!(!size_limit_2mb.exceeds(2 * 1024 * 1024));
1852
1853 // Test with size exceeding the limit
1854 // 3MB is above the 2MB limit, should return true
1855 assert!(size_limit_2mb.exceeds(3 * 1024 * 1024));
1856 }
1857}