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