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