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