Skip to main content

reth_transaction_pool/pool/
best.rs

1use crate::{
2    error::{Eip4844PoolTransactionError, InvalidPoolTransactionError},
3    identifier::{SenderId, TransactionId},
4    pool::pending::PendingTransaction,
5    PoolTransaction, Priority, TransactionOrdering, ValidPoolTransaction,
6};
7use alloy_consensus::Transaction;
8use alloy_primitives::map::AddressSet;
9use core::fmt;
10use imbl::OrdMap;
11use reth_primitives_traits::transaction::error::InvalidTransactionError;
12use rustc_hash::FxHashSet;
13use std::{
14    collections::{BTreeSet, VecDeque},
15    sync::Arc,
16};
17use tokio::sync::broadcast::{error::TryRecvError, Receiver};
18use tracing::debug;
19
20const MAX_NEW_TRANSACTIONS_PER_BATCH: usize = 16;
21
22/// An iterator that returns transactions that can be executed on the current state (*best*
23/// transactions).
24///
25/// This is a wrapper around [`BestTransactions`] that also enforces a specific basefee.
26///
27/// This iterator guarantees that all transactions it returns satisfy both the base fee and blob
28/// fee!
29pub(crate) struct BestTransactionsWithFees<T: TransactionOrdering> {
30    pub(crate) best: BestTransactions<T>,
31    pub(crate) base_fee: u64,
32    pub(crate) base_fee_per_blob_gas: u64,
33}
34
35impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactionsWithFees<T> {
36    fn mark_invalid(&mut self, tx: &Self::Item, kind: InvalidPoolTransactionError) {
37        BestTransactions::mark_invalid(&mut self.best, tx, kind)
38    }
39
40    fn no_updates(&mut self) {
41        self.best.no_updates()
42    }
43
44    fn allow_updates_out_of_order(&mut self) {
45        self.best.allow_updates_out_of_order()
46    }
47
48    fn skip_blobs(&mut self) {
49        self.set_skip_blobs(true)
50    }
51
52    fn set_skip_blobs(&mut self, skip_blobs: bool) {
53        self.best.set_skip_blobs(skip_blobs)
54    }
55}
56
57impl<T: TransactionOrdering> Iterator for BestTransactionsWithFees<T> {
58    type Item = Arc<ValidPoolTransaction<T::Transaction>>;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        // find the next transaction that satisfies the base fee
62        loop {
63            let best = Iterator::next(&mut self.best)?;
64            // If both the base fee and blob fee (if applicable for EIP-4844) are satisfied, return
65            // the transaction
66            if best.transaction.max_fee_per_gas() >= self.base_fee as u128 &&
67                best.transaction
68                    .max_fee_per_blob_gas()
69                    .is_none_or(|fee| fee >= self.base_fee_per_blob_gas as u128)
70            {
71                return Some(best);
72            }
73            crate::traits::BestTransactions::mark_invalid(
74                self,
75                &best,
76                InvalidPoolTransactionError::Underpriced,
77            );
78        }
79    }
80
81    fn size_hint(&self) -> (usize, Option<usize>) {
82        let (_, upper) = self.best.size_hint();
83        (0, upper)
84    }
85}
86
87/// An iterator that returns transactions that can be executed on the current state (*best*
88/// transactions).
89///
90/// The [`PendingPool`](crate::pool::pending::PendingPool) contains transactions that *could* all
91/// be executed on the current state, but only yields transactions that are ready to be executed
92/// now. While it contains all gapless transactions of a sender, it _always_ only returns the
93/// transaction with the current on chain nonce.
94#[derive(Debug)]
95pub struct BestTransactions<T: TransactionOrdering> {
96    /// Contains a copy of _all_ transactions of the pending pool at the point in time this
97    /// iterator was created.
98    pub(crate) all: OrdMap<TransactionId, PendingTransaction<T>>,
99    /// Transactions that can be executed right away: these have the expected nonce.
100    ///
101    /// Once an `independent` transaction with the nonce `N` is returned, it unlocks `N+1`, which
102    /// then can be moved from the `all` set to the `independent` set.
103    pub(crate) independent: BTreeSet<PendingTransaction<T>>,
104    /// There might be the case where a yielded transactions is invalid, this will track it.
105    pub(crate) invalid: FxHashSet<SenderId>,
106    /// Used to receive any new pending transactions that have been added to the pool after this
107    /// iterator was static filtered
108    ///
109    /// These new pending transactions are inserted into this iterator's pool before yielding the
110    /// next value
111    pub(crate) new_transaction_receiver: Option<Receiver<PendingTransaction<T>>>,
112    /// The priority value of most recently yielded transaction.
113    ///
114    /// This is required if new pending transactions are fed in while it yields new values.
115    pub(crate) last_priority: Option<Priority<T::PriorityValue>>,
116    /// Flag to control whether to skip blob transactions (EIP4844).
117    pub(crate) skip_blobs: bool,
118    /// Whether live updates can be yielded after a lower-priority transaction.
119    pub(crate) allow_updates_out_of_order: bool,
120}
121
122impl<T: TransactionOrdering> BestTransactions<T> {
123    /// Mark the transaction and its descendants as invalid.
124    pub(crate) fn mark_invalid(
125        &mut self,
126        tx: &Arc<ValidPoolTransaction<T::Transaction>>,
127        _kind: InvalidPoolTransactionError,
128    ) {
129        self.invalid.insert(tx.sender_id());
130    }
131
132    /// Returns the ancestor the given transaction, the transaction with `nonce - 1`.
133    ///
134    /// Note: for a transaction with nonce higher than the current on chain nonce this will always
135    /// return an ancestor since all transactions in this pool are gapless.
136    pub(crate) fn ancestor(&self, id: &TransactionId) -> Option<&PendingTransaction<T>> {
137        self.all.get(&id.unchecked_ancestor()?)
138    }
139
140    /// Non-blocking read on the new pending transactions subscription channel
141    fn try_recv(&mut self) -> Option<IncomingTransaction<T>> {
142        loop {
143            match self.new_transaction_receiver.as_mut()?.try_recv() {
144                Ok(tx) => {
145                    if !self.allow_updates_out_of_order &&
146                        let Some(last_priority) = &self.last_priority &&
147                        &tx.priority > last_priority
148                    {
149                        // we skip transactions if we already yielded a transaction with lower
150                        // priority
151                        return Some(IncomingTransaction::Stash(tx))
152                    }
153                    return Some(IncomingTransaction::Process(tx))
154                }
155                // note TryRecvError::Lagged can be returned here, which is an error that attempts
156                // to correct itself on consecutive try_recv() attempts
157
158                // the cost of ignoring this error is allowing old transactions to get
159                // overwritten after the chan buffer size is met
160                Err(TryRecvError::Lagged(_)) => {
161                    // Handle the case where the receiver lagged too far behind.
162                    // `num_skipped` indicates the number of messages that were skipped.
163                }
164
165                // this case is still better than the existing iterator behavior where no new
166                // pending txs are surfaced to consumers
167                Err(_) => return None,
168            }
169        }
170    }
171
172    /// Removes the currently best independent transaction from the independent set and the total
173    /// set.
174    fn pop_best(&mut self) -> Option<PendingTransaction<T>> {
175        self.independent.pop_last().inspect(|best| {
176            self.all.remove(best.transaction.id());
177        })
178    }
179
180    /// Checks for new transactions that have come into the `PendingPool` after this iterator was
181    /// created and inserts them
182    fn add_new_transactions(&mut self) {
183        for _ in 0..MAX_NEW_TRANSACTIONS_PER_BATCH {
184            if let Some(pending_tx) = self.try_recv() {
185                //  same logic as PendingPool::add_transaction/PendingPool::best_with_unlocked
186
187                match pending_tx {
188                    IncomingTransaction::Process(tx) => {
189                        let tx_id = *tx.transaction.id();
190                        if self.ancestor(&tx_id).is_none() {
191                            self.independent.insert(tx.clone());
192                        }
193                        self.all.insert(tx_id, tx);
194                    }
195                    IncomingTransaction::Stash(tx) => {
196                        let tx_id = *tx.transaction.id();
197                        self.all.insert(tx_id, tx);
198                    }
199                }
200            } else {
201                break;
202            }
203        }
204    }
205
206    /// Returns the next best transaction and its priority value.
207    #[expect(clippy::type_complexity)]
208    pub fn next_tx_and_priority(
209        &mut self,
210    ) -> Option<(Arc<ValidPoolTransaction<T::Transaction>>, Priority<T::PriorityValue>)> {
211        loop {
212            self.add_new_transactions();
213            // Remove the next independent tx with the highest priority
214            let best = self.pop_best()?;
215            let sender_id = best.transaction.sender_id();
216
217            // skip transactions for which sender was marked as invalid
218            if self.invalid.contains(&sender_id) {
219                debug!(
220                    target: "txpool",
221                    "[{:?}] skipping invalid transaction",
222                    best.transaction.hash()
223                );
224                continue
225            }
226
227            // Insert transactions that just got unlocked.
228            if let Some(unlocked) = self.all.get(&best.unlocks()) {
229                self.independent.insert(unlocked.clone());
230            }
231
232            if self.skip_blobs && best.transaction.is_eip4844() {
233                // blobs should be skipped, marking them as invalid will ensure that no dependent
234                // transactions are returned
235                self.mark_invalid(
236                    &best.transaction,
237                    InvalidPoolTransactionError::Eip4844(
238                        Eip4844PoolTransactionError::NoEip4844Blobs,
239                    ),
240                )
241            } else {
242                if self.new_transaction_receiver.is_some() {
243                    self.last_priority = Some(best.priority.clone())
244                }
245                return Some((best.transaction, best.priority))
246            }
247        }
248    }
249}
250
251/// Result of attempting to receive a new transaction from the channel during iteration.
252///
253/// This enum determines how a newly received transaction should be handled based on its priority
254/// relative to transactions already yielded by the iterator.
255enum IncomingTransaction<T: TransactionOrdering> {
256    /// Process the transaction normally: add to both `all` map and potentially to `independent`
257    /// set (if it has no ancestor).
258    ///
259    /// This variant is used when the transaction's priority is lower than or equal to the last
260    /// yielded transaction, meaning it can be safely processed without breaking the descending
261    /// priority order.
262    Process(PendingTransaction<T>),
263
264    /// Stash the transaction: add only to the `all` map, but NOT to the `independent` set.
265    ///
266    /// This variant is used when the transaction has a higher priority than the last yielded
267    /// transaction. We cannot yield it immediately (to maintain strict priority ordering), but we
268    /// must still track it so that:
269    /// - Its descendants can find it via `ancestor()` lookups
270    /// - We prevent those descendants from being incorrectly promoted to `independent`
271    ///
272    /// Without stashing, if a child of this transaction arrives later, it would fail to find its
273    /// parent in `all`, be marked as `independent`, and be yielded out of order (before its
274    /// parent), causing nonce gaps.
275    Stash(PendingTransaction<T>),
276}
277
278impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactions<T> {
279    fn mark_invalid(&mut self, tx: &Self::Item, kind: InvalidPoolTransactionError) {
280        Self::mark_invalid(self, tx, kind)
281    }
282
283    fn no_updates(&mut self) {
284        self.new_transaction_receiver.take();
285        self.last_priority.take();
286    }
287
288    fn allow_updates_out_of_order(&mut self) {
289        self.allow_updates_out_of_order = true;
290    }
291
292    fn skip_blobs(&mut self) {
293        self.set_skip_blobs(true);
294    }
295
296    fn set_skip_blobs(&mut self, skip_blobs: bool) {
297        self.skip_blobs = skip_blobs;
298    }
299}
300
301impl<T: TransactionOrdering> Iterator for BestTransactions<T> {
302    type Item = Arc<ValidPoolTransaction<T::Transaction>>;
303
304    fn next(&mut self) -> Option<Self::Item> {
305        self.next_tx_and_priority().map(|(tx, _)| tx)
306    }
307
308    fn size_hint(&self) -> (usize, Option<usize>) {
309        (0, self.new_transaction_receiver.is_none().then_some(self.all.len()))
310    }
311}
312
313/// A [`BestTransactions`](crate::traits::BestTransactions) implementation that filters the
314/// transactions of iter with predicate.
315///
316/// Filter out transactions are marked as invalid:
317/// [`BestTransactions::mark_invalid`](crate::traits::BestTransactions::mark_invalid).
318pub struct BestTransactionFilter<I, P> {
319    pub(crate) best: I,
320    pub(crate) predicate: P,
321}
322
323impl<I, P> BestTransactionFilter<I, P> {
324    /// Create a new [`BestTransactionFilter`] with the given predicate.
325    pub const fn new(best: I, predicate: P) -> Self {
326        Self { best, predicate }
327    }
328}
329
330impl<I, P> Iterator for BestTransactionFilter<I, P>
331where
332    I: crate::traits::BestTransactions,
333    P: FnMut(&<I as Iterator>::Item) -> bool,
334{
335    type Item = <I as Iterator>::Item;
336
337    fn next(&mut self) -> Option<Self::Item> {
338        loop {
339            let best = self.best.next()?;
340            if (self.predicate)(&best) {
341                return Some(best)
342            }
343            self.best.mark_invalid(
344                &best,
345                InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported),
346            );
347        }
348    }
349
350    fn size_hint(&self) -> (usize, Option<usize>) {
351        let (_, upper) = self.best.size_hint();
352        (0, upper)
353    }
354}
355
356impl<I, P> crate::traits::BestTransactions for BestTransactionFilter<I, P>
357where
358    I: crate::traits::BestTransactions,
359    P: FnMut(&<I as Iterator>::Item) -> bool + Send,
360{
361    fn mark_invalid(&mut self, tx: &Self::Item, kind: InvalidPoolTransactionError) {
362        crate::traits::BestTransactions::mark_invalid(&mut self.best, tx, kind)
363    }
364
365    fn no_updates(&mut self) {
366        self.best.no_updates()
367    }
368
369    fn allow_updates_out_of_order(&mut self) {
370        self.best.allow_updates_out_of_order()
371    }
372
373    fn skip_blobs(&mut self) {
374        self.set_skip_blobs(true)
375    }
376
377    fn set_skip_blobs(&mut self, skip_blobs: bool) {
378        self.best.set_skip_blobs(skip_blobs)
379    }
380}
381
382impl<I: fmt::Debug, P> fmt::Debug for BestTransactionFilter<I, P> {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        f.debug_struct("BestTransactionFilter").field("best", &self.best).finish()
385    }
386}
387
388/// Wrapper over [`crate::traits::BestTransactions`] that prioritizes transactions of certain
389/// senders capping total gas used by such transactions.
390#[derive(Debug)]
391pub struct BestTransactionsWithPrioritizedSenders<I: Iterator> {
392    /// Inner iterator
393    inner: I,
394    /// A set of senders which transactions should be prioritized
395    prioritized_senders: AddressSet,
396    /// Maximum total gas limit of prioritized transactions
397    max_prioritized_gas: u64,
398    /// Buffer with transactions that are not being prioritized. Those will be the first to be
399    /// included after the prioritized transactions
400    buffer: VecDeque<I::Item>,
401    /// Tracker of total gas limit of prioritized transactions. Once it reaches
402    /// `max_prioritized_gas` no more transactions will be prioritized
403    prioritized_gas: u64,
404}
405
406impl<I: Iterator> BestTransactionsWithPrioritizedSenders<I> {
407    /// Constructs a new [`BestTransactionsWithPrioritizedSenders`].
408    pub fn new(prioritized_senders: AddressSet, max_prioritized_gas: u64, inner: I) -> Self {
409        Self {
410            inner,
411            prioritized_senders,
412            max_prioritized_gas,
413            buffer: Default::default(),
414            prioritized_gas: Default::default(),
415        }
416    }
417}
418
419impl<I, T> Iterator for BestTransactionsWithPrioritizedSenders<I>
420where
421    I: crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T>>>,
422    T: PoolTransaction,
423{
424    type Item = <I as Iterator>::Item;
425
426    fn next(&mut self) -> Option<Self::Item> {
427        // If we have space, try prioritizing transactions
428        if self.prioritized_gas < self.max_prioritized_gas {
429            for item in &mut self.inner {
430                if self.prioritized_senders.contains(&item.transaction.sender()) &&
431                    self.prioritized_gas + item.transaction.gas_limit() <=
432                        self.max_prioritized_gas
433                {
434                    self.prioritized_gas += item.transaction.gas_limit();
435                    return Some(item)
436                }
437                self.buffer.push_back(item);
438            }
439        }
440
441        if let Some(item) = self.buffer.pop_front() {
442            Some(item)
443        } else {
444            self.inner.next()
445        }
446    }
447
448    fn size_hint(&self) -> (usize, Option<usize>) {
449        let buffered = self.buffer.len();
450        let (inner_lower, inner_upper) = self.inner.size_hint();
451
452        (
453            buffered.saturating_add(inner_lower),
454            inner_upper.and_then(|upper| upper.checked_add(buffered)),
455        )
456    }
457}
458
459impl<I, T> crate::traits::BestTransactions for BestTransactionsWithPrioritizedSenders<I>
460where
461    I: crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T>>>,
462    T: PoolTransaction,
463{
464    fn mark_invalid(&mut self, tx: &Self::Item, kind: InvalidPoolTransactionError) {
465        self.inner.mark_invalid(tx, kind)
466    }
467
468    fn no_updates(&mut self) {
469        self.inner.no_updates()
470    }
471
472    fn allow_updates_out_of_order(&mut self) {
473        self.inner.allow_updates_out_of_order()
474    }
475
476    fn set_skip_blobs(&mut self, skip_blobs: bool) {
477        if skip_blobs {
478            self.buffer.retain(|tx| !tx.transaction.is_eip4844())
479        }
480        self.inner.set_skip_blobs(skip_blobs)
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487    use crate::{
488        pool::pending::PendingPool,
489        test_utils::{MockOrdering, MockTransaction, MockTransactionFactory},
490        BestTransactions, Priority,
491    };
492
493    #[test]
494    fn test_best_iter() {
495        let mut pool = PendingPool::new(MockOrdering::default());
496        let mut f = MockTransactionFactory::default();
497
498        let num_tx = 10;
499        // insert 10 gapless tx
500        let tx = MockTransaction::eip1559();
501        for nonce in 0..num_tx {
502            let tx = tx.clone().rng_hash().with_nonce(nonce);
503            let valid_tx = f.validated(tx);
504            pool.add_transaction(Arc::new(valid_tx), 0);
505        }
506
507        let mut best = pool.best();
508        assert_eq!(best.all.len(), num_tx as usize);
509        assert_eq!(best.independent.len(), 1);
510
511        // check tx are returned in order
512        for nonce in 0..num_tx {
513            assert_eq!(best.independent.len(), 1);
514            let tx = best.next().unwrap();
515            assert_eq!(tx.nonce(), nonce);
516        }
517    }
518
519    #[test]
520    fn test_best_transactions_size_hint() {
521        let mut pool = PendingPool::new(MockOrdering::default());
522        let mut f = MockTransactionFactory::default();
523
524        for nonce in 0..3 {
525            let tx = MockTransaction::eip1559().rng_hash().with_nonce(nonce);
526            pool.add_transaction(Arc::new(f.validated(tx)), 0);
527        }
528
529        let mut best = pool.best();
530        assert_eq!(best.size_hint(), (0, None));
531
532        best.no_updates();
533        assert_eq!(best.size_hint(), (0, Some(3)));
534
535        assert_eq!(best.next().unwrap().nonce(), 0);
536        assert_eq!(best.size_hint(), (0, Some(2)));
537    }
538
539    #[test]
540    fn test_best_transactions_with_fees_size_hint() {
541        let mut pool = PendingPool::new(MockOrdering::default());
542        let mut f = MockTransactionFactory::default();
543
544        for nonce in 0..3 {
545            let tx = MockTransaction::eip1559().rng_hash().with_nonce(nonce).with_max_fee(100);
546            pool.add_transaction(Arc::new(f.validated(tx)), 0);
547        }
548
549        let mut best = pool.best_with_basefee_and_blobfee(10, 0);
550        best.no_updates();
551
552        assert_eq!(best.size_hint(), (0, Some(3)));
553        assert_eq!(best.next().unwrap().nonce(), 0);
554        assert_eq!(best.size_hint(), (0, Some(2)));
555    }
556
557    #[test]
558    fn test_best_transaction_filter_size_hint() {
559        let mut pool = PendingPool::new(MockOrdering::default());
560        let mut f = MockTransactionFactory::default();
561
562        for nonce in 0..3 {
563            let tx = MockTransaction::eip1559().rng_hash().with_nonce(nonce);
564            pool.add_transaction(Arc::new(f.validated(tx)), 0);
565        }
566
567        let best = pool.best().without_updates();
568        let mut filter =
569            BestTransactionFilter::new(best, |_: &Arc<ValidPoolTransaction<MockTransaction>>| {
570                false
571            });
572
573        assert_eq!(filter.size_hint(), (0, Some(3)));
574        assert!(filter.next().is_none());
575        assert_eq!(filter.size_hint(), (0, Some(0)));
576    }
577
578    #[test]
579    fn test_best_transactions_with_prioritized_senders_size_hint() {
580        let mut pool = PendingPool::new(MockOrdering::default());
581        let mut f = MockTransactionFactory::default();
582
583        for gas_price in 0..5 {
584            let tx = MockTransaction::eip1559().with_gas_price((gas_price + 1) * 10);
585            pool.add_transaction(Arc::new(f.validated(tx)), 0);
586        }
587
588        let prioritized_tx = MockTransaction::eip1559().with_gas_price(5).with_gas_limit(200);
589        let prioritized_sender = prioritized_tx.sender();
590        pool.add_transaction(Arc::new(f.validated(prioritized_tx)), 0);
591
592        let mut best = BestTransactionsWithPrioritizedSenders::new(
593            AddressSet::from_iter([prioritized_sender]),
594            200,
595            pool.best().without_updates(),
596        );
597
598        assert_eq!(best.size_hint(), (0, Some(6)));
599        assert_eq!(best.next().unwrap().sender(), prioritized_sender);
600        assert_eq!(best.size_hint(), (5, Some(5)));
601    }
602
603    #[test]
604    fn test_best_iter_invalid() {
605        let mut pool = PendingPool::new(MockOrdering::default());
606        let mut f = MockTransactionFactory::default();
607
608        let num_tx = 10;
609        // insert 10 gapless tx
610        let tx = MockTransaction::eip1559();
611        for nonce in 0..num_tx {
612            let tx = tx.clone().rng_hash().with_nonce(nonce);
613            let valid_tx = f.validated(tx);
614            pool.add_transaction(Arc::new(valid_tx), 0);
615        }
616
617        let mut best = pool.best();
618
619        // mark the first tx as invalid
620        let invalid = best.independent.iter().next().unwrap();
621        best.mark_invalid(
622            &invalid.transaction.clone(),
623            InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported),
624        );
625
626        // iterator is empty
627        assert!(best.next().is_none());
628    }
629
630    #[test]
631    fn test_best_transactions_iter_invalid() {
632        let mut pool = PendingPool::new(MockOrdering::default());
633        let mut f = MockTransactionFactory::default();
634
635        let num_tx = 10;
636        // insert 10 gapless tx
637        let tx = MockTransaction::eip1559();
638        for nonce in 0..num_tx {
639            let tx = tx.clone().rng_hash().with_nonce(nonce);
640            let valid_tx = f.validated(tx);
641            pool.add_transaction(Arc::new(valid_tx), 0);
642        }
643
644        let mut best: Box<
645            dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<MockTransaction>>>,
646        > = Box::new(pool.best());
647
648        let tx = Iterator::next(&mut best).unwrap();
649        crate::traits::BestTransactions::mark_invalid(
650            &mut *best,
651            &tx,
652            InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported),
653        );
654        assert!(Iterator::next(&mut best).is_none());
655    }
656
657    #[test]
658    fn test_best_with_fees_iter_base_fee_satisfied() {
659        let mut pool = PendingPool::new(MockOrdering::default());
660        let mut f = MockTransactionFactory::default();
661
662        let num_tx = 5;
663        let base_fee: u64 = 10;
664        let base_fee_per_blob_gas: u64 = 15;
665
666        // Insert transactions with a max_fee_per_gas greater than or equal to the base fee
667        // Without blob fee
668        for nonce in 0..num_tx {
669            let tx = MockTransaction::eip1559()
670                .rng_hash()
671                .with_nonce(nonce)
672                .with_max_fee(base_fee as u128 + 5);
673            let valid_tx = f.validated(tx);
674            pool.add_transaction(Arc::new(valid_tx), 0);
675        }
676
677        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
678
679        for nonce in 0..num_tx {
680            let tx = best.next().expect("Transaction should be returned");
681            assert_eq!(tx.nonce(), nonce);
682            assert!(tx.transaction.max_fee_per_gas() >= base_fee as u128);
683        }
684    }
685
686    #[test]
687    fn test_best_with_fees_iter_base_fee_violated() {
688        let mut pool = PendingPool::new(MockOrdering::default());
689        let mut f = MockTransactionFactory::default();
690
691        let num_tx = 5;
692        let base_fee: u64 = 20;
693        let base_fee_per_blob_gas: u64 = 15;
694
695        // Insert transactions with a max_fee_per_gas less than the base fee
696        for nonce in 0..num_tx {
697            let tx = MockTransaction::eip1559()
698                .rng_hash()
699                .with_nonce(nonce)
700                .with_max_fee(base_fee as u128 - 5);
701            let valid_tx = f.validated(tx);
702            pool.add_transaction(Arc::new(valid_tx), 0);
703        }
704
705        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
706
707        // No transaction should be returned since all violate the base fee
708        assert!(best.next().is_none());
709    }
710
711    #[test]
712    fn test_best_with_fees_iter_blob_fee_satisfied() {
713        let mut pool = PendingPool::new(MockOrdering::default());
714        let mut f = MockTransactionFactory::default();
715
716        let num_tx = 5;
717        let base_fee: u64 = 10;
718        let base_fee_per_blob_gas: u64 = 20;
719
720        // Insert transactions with a max_fee_per_blob_gas greater than or equal to the base fee per
721        // blob gas
722        for nonce in 0..num_tx {
723            let tx = MockTransaction::eip4844()
724                .rng_hash()
725                .with_nonce(nonce)
726                .with_max_fee(base_fee as u128 + 5)
727                .with_blob_fee(base_fee_per_blob_gas as u128 + 5);
728            let valid_tx = f.validated(tx);
729            pool.add_transaction(Arc::new(valid_tx), 0);
730        }
731
732        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
733
734        // All transactions should be returned in order since they satisfy both base fee and blob
735        // fee
736        for nonce in 0..num_tx {
737            let tx = best.next().expect("Transaction should be returned");
738            assert_eq!(tx.nonce(), nonce);
739            assert!(tx.transaction.max_fee_per_gas() >= base_fee as u128);
740            assert!(
741                tx.transaction.max_fee_per_blob_gas().unwrap() >= base_fee_per_blob_gas as u128
742            );
743        }
744
745        // No more transactions should be returned
746        assert!(best.next().is_none());
747    }
748
749    #[test]
750    fn test_best_with_fees_iter_blob_fee_violated() {
751        let mut pool = PendingPool::new(MockOrdering::default());
752        let mut f = MockTransactionFactory::default();
753
754        let num_tx = 5;
755        let base_fee: u64 = 10;
756        let base_fee_per_blob_gas: u64 = 20;
757
758        // Insert transactions with a max_fee_per_blob_gas less than the base fee per blob gas
759        for nonce in 0..num_tx {
760            let tx = MockTransaction::eip4844()
761                .rng_hash()
762                .with_nonce(nonce)
763                .with_max_fee(base_fee as u128 + 5)
764                .with_blob_fee(base_fee_per_blob_gas as u128 - 5);
765            let valid_tx = f.validated(tx);
766            pool.add_transaction(Arc::new(valid_tx), 0);
767        }
768
769        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
770
771        // No transaction should be returned since all violate the blob fee
772        assert!(best.next().is_none());
773    }
774
775    #[test]
776    fn test_best_with_fees_iter_mixed_fees() {
777        let mut pool = PendingPool::new(MockOrdering::default());
778        let mut f = MockTransactionFactory::default();
779
780        let base_fee: u64 = 10;
781        let base_fee_per_blob_gas: u64 = 20;
782
783        // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas
784        let tx1 =
785            MockTransaction::eip1559().rng_hash().with_nonce(0).with_max_fee(base_fee as u128 + 5);
786        let tx2 = MockTransaction::eip4844()
787            .rng_hash()
788            .with_nonce(1)
789            .with_max_fee(base_fee as u128 + 5)
790            .with_blob_fee(base_fee_per_blob_gas as u128 + 5);
791        let tx3 = MockTransaction::eip4844()
792            .rng_hash()
793            .with_nonce(2)
794            .with_max_fee(base_fee as u128 + 5)
795            .with_blob_fee(base_fee_per_blob_gas as u128 - 5);
796        let tx4 =
797            MockTransaction::eip1559().rng_hash().with_nonce(3).with_max_fee(base_fee as u128 - 5);
798
799        pool.add_transaction(Arc::new(f.validated(tx1.clone())), 0);
800        pool.add_transaction(Arc::new(f.validated(tx2.clone())), 0);
801        pool.add_transaction(Arc::new(f.validated(tx3)), 0);
802        pool.add_transaction(Arc::new(f.validated(tx4)), 0);
803
804        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
805
806        let expected_order = vec![tx1, tx2];
807        for expected_tx in expected_order {
808            let tx = best.next().expect("Transaction should be returned");
809            assert_eq!(tx.transaction, expected_tx);
810        }
811
812        // No more transactions should be returned
813        assert!(best.next().is_none());
814    }
815
816    #[test]
817    fn test_best_add_transaction_with_next_nonce() {
818        let mut pool = PendingPool::new(MockOrdering::default());
819        let mut f = MockTransactionFactory::default();
820
821        // Add 5 transactions with increasing nonces to the pool
822        let num_tx = 5;
823        let tx = MockTransaction::eip1559();
824        for nonce in 0..num_tx {
825            let tx = tx.clone().rng_hash().with_nonce(nonce);
826            let valid_tx = f.validated(tx);
827            pool.add_transaction(Arc::new(valid_tx), 0);
828        }
829
830        // Create a BestTransactions iterator from the pool
831        let mut best = pool.best();
832
833        // Use a broadcast channel for transaction updates
834        let (tx_sender, tx_receiver) =
835            tokio::sync::broadcast::channel::<PendingTransaction<MockOrdering>>(1000);
836        best.new_transaction_receiver = Some(tx_receiver);
837
838        // Create a new transaction with nonce 5 and validate it
839        let new_tx = MockTransaction::eip1559().rng_hash().with_nonce(5);
840        let valid_new_tx = f.validated(new_tx);
841
842        // Send the new transaction through the broadcast channel
843        let pending_tx = PendingTransaction {
844            submission_id: 10,
845            transaction: Arc::new(valid_new_tx.clone()),
846            priority: Priority::Value(1000),
847        };
848        tx_sender.send(pending_tx.clone()).unwrap();
849
850        // Add new transactions to the iterator
851        best.add_new_transactions();
852
853        // Verify that the new transaction has been added to the 'all' map
854        assert_eq!(best.all.len(), 6);
855        assert!(best.all.contains_key(valid_new_tx.id()));
856
857        // Verify that the new transaction has been added to the 'independent' set
858        assert_eq!(best.independent.len(), 2);
859        assert!(best.independent.contains(&pending_tx));
860    }
861
862    #[test]
863    fn test_best_add_transaction_with_ancestor() {
864        // Initialize a new PendingPool with default MockOrdering and MockTransactionFactory
865        let mut pool = PendingPool::new(MockOrdering::default());
866        let mut f = MockTransactionFactory::default();
867
868        // Add 5 transactions with increasing nonces to the pool
869        let num_tx = 5;
870        let tx = MockTransaction::eip1559();
871        for nonce in 0..num_tx {
872            let tx = tx.clone().rng_hash().with_nonce(nonce);
873            let valid_tx = f.validated(tx);
874            pool.add_transaction(Arc::new(valid_tx), 0);
875        }
876
877        // Create a BestTransactions iterator from the pool
878        let mut best = pool.best();
879
880        // Use a broadcast channel for transaction updates
881        let (tx_sender, tx_receiver) =
882            tokio::sync::broadcast::channel::<PendingTransaction<MockOrdering>>(1000);
883        best.new_transaction_receiver = Some(tx_receiver);
884
885        // Create a new transaction with nonce 5 and validate it
886        let base_tx1 = MockTransaction::eip1559().rng_hash().with_nonce(5);
887        let valid_new_tx1 = f.validated(base_tx1.clone());
888
889        // Send the new transaction through the broadcast channel
890        let pending_tx1 = PendingTransaction {
891            submission_id: 10,
892            transaction: Arc::new(valid_new_tx1.clone()),
893            priority: Priority::Value(1000),
894        };
895        tx_sender.send(pending_tx1.clone()).unwrap();
896
897        // Add new transactions to the iterator
898        best.add_new_transactions();
899
900        // Verify that the new transaction has been added to the 'all' map
901        assert_eq!(best.all.len(), 6);
902        assert!(best.all.contains_key(valid_new_tx1.id()));
903
904        // Verify that the new transaction has been added to the 'independent' set
905        assert_eq!(best.independent.len(), 2);
906        assert!(best.independent.contains(&pending_tx1));
907
908        // Attempt to add a new transaction with a different nonce (not a duplicate)
909        let base_tx2 = base_tx1.with_nonce(6);
910        let valid_new_tx2 = f.validated(base_tx2);
911
912        // Send the new transaction through the broadcast channel
913        let pending_tx2 = PendingTransaction {
914            submission_id: 11, // Different submission ID
915            transaction: Arc::new(valid_new_tx2.clone()),
916            priority: Priority::Value(1000),
917        };
918        tx_sender.send(pending_tx2.clone()).unwrap();
919
920        // Add new transactions to the iterator
921        best.add_new_transactions();
922
923        // Verify that the new transaction has been added to 'all'
924        assert_eq!(best.all.len(), 7);
925        assert!(best.all.contains_key(valid_new_tx2.id()));
926
927        // Verify that the new transaction has not been added to the 'independent' set
928        assert_eq!(best.independent.len(), 2);
929        assert!(!best.independent.contains(&pending_tx2));
930    }
931
932    #[test]
933    fn test_best_transactions_filter_trait_object() {
934        // Initialize a new PendingPool with default MockOrdering and MockTransactionFactory
935        let mut pool = PendingPool::new(MockOrdering::default());
936        let mut f = MockTransactionFactory::default();
937
938        // Add 5 transactions with increasing nonces to the pool
939        let num_tx = 5;
940        let tx = MockTransaction::eip1559();
941        for nonce in 0..num_tx {
942            let tx = tx.clone().rng_hash().with_nonce(nonce);
943            let valid_tx = f.validated(tx);
944            pool.add_transaction(Arc::new(valid_tx), 0);
945        }
946
947        // Create a trait object of BestTransactions iterator from the pool
948        let best: Box<dyn crate::traits::BestTransactions<Item = _>> = Box::new(pool.best());
949
950        // Create a filter that only returns transactions with even nonces
951        let filter =
952            BestTransactionFilter::new(best, |tx: &Arc<ValidPoolTransaction<MockTransaction>>| {
953                tx.nonce().is_multiple_of(2)
954            });
955
956        // Verify that the filter only returns transactions with even nonces
957        for tx in filter {
958            assert_eq!(tx.nonce() % 2, 0);
959        }
960    }
961
962    #[test]
963    fn test_best_transactions_prioritized_senders() {
964        let mut pool = PendingPool::new(MockOrdering::default());
965        let mut f = MockTransactionFactory::default();
966
967        // Add 5 plain transactions from different senders with increasing gas price
968        for gas_price in 0..5 {
969            let tx = MockTransaction::eip1559().with_gas_price((gas_price + 1) * 10);
970            let valid_tx = f.validated(tx);
971            pool.add_transaction(Arc::new(valid_tx), 0);
972        }
973
974        // Add another transaction with 5 gas price that's going to be prioritized by sender
975        let prioritized_tx = MockTransaction::eip1559().with_gas_price(5).with_gas_limit(200);
976        let valid_prioritized_tx = f.validated(prioritized_tx.clone());
977        pool.add_transaction(Arc::new(valid_prioritized_tx), 0);
978
979        // Add another transaction with 3 gas price that should not be prioritized by sender because
980        // of gas limit.
981        let prioritized_tx2 = MockTransaction::eip1559().with_gas_price(3);
982        let valid_prioritized_tx2 = f.validated(prioritized_tx2.clone());
983        pool.add_transaction(Arc::new(valid_prioritized_tx2), 0);
984
985        let prioritized_senders =
986            AddressSet::from_iter([prioritized_tx.sender(), prioritized_tx2.sender()]);
987        let best =
988            BestTransactionsWithPrioritizedSenders::new(prioritized_senders, 200, pool.best());
989
990        // Verify that the prioritized transaction is returned first
991        // and the rest are returned in the reverse order of gas price
992        let mut iter = best.into_iter();
993        let top_of_block_tx = iter.next().unwrap();
994        assert_eq!(top_of_block_tx.max_fee_per_gas(), 5);
995        assert_eq!(top_of_block_tx.sender(), prioritized_tx.sender());
996        for gas_price in (0..5).rev() {
997            assert_eq!(iter.next().unwrap().max_fee_per_gas(), (gas_price + 1) * 10);
998        }
999
1000        // Due to the gas limit, the transaction from second-prioritized sender was not
1001        // prioritized.
1002        let top_of_block_tx2 = iter.next().unwrap();
1003        assert_eq!(top_of_block_tx2.max_fee_per_gas(), 3);
1004        assert_eq!(top_of_block_tx2.sender(), prioritized_tx2.sender());
1005    }
1006
1007    #[test]
1008    fn test_best_with_fees_iter_no_blob_fee_required() {
1009        // Tests transactions without blob fees where base fees are checked.
1010        let mut pool = PendingPool::new(MockOrdering::default());
1011        let mut f = MockTransactionFactory::default();
1012
1013        let base_fee: u64 = 10;
1014        let base_fee_per_blob_gas: u64 = 0; // No blob fee requirement
1015
1016        // Insert transactions with max_fee_per_gas above the base fee
1017        for nonce in 0..5 {
1018            let tx = MockTransaction::eip1559()
1019                .rng_hash()
1020                .with_nonce(nonce)
1021                .with_max_fee(base_fee as u128 + 5);
1022            let valid_tx = f.validated(tx);
1023            pool.add_transaction(Arc::new(valid_tx), 0);
1024        }
1025
1026        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
1027
1028        // All transactions should be returned as no blob fee requirement is imposed
1029        for nonce in 0..5 {
1030            let tx = best.next().expect("Transaction should be returned");
1031            assert_eq!(tx.nonce(), nonce);
1032        }
1033
1034        // Ensure no more transactions are left
1035        assert!(best.next().is_none());
1036    }
1037
1038    #[test]
1039    fn test_best_with_fees_iter_mix_of_blob_and_non_blob_transactions() {
1040        // Tests mixed scenarios with both blob and non-blob transactions.
1041        let mut pool = PendingPool::new(MockOrdering::default());
1042        let mut f = MockTransactionFactory::default();
1043
1044        let base_fee: u64 = 10;
1045        let base_fee_per_blob_gas: u64 = 15;
1046
1047        // Add a non-blob transaction that satisfies the base fee
1048        let tx_non_blob =
1049            MockTransaction::eip1559().rng_hash().with_nonce(0).with_max_fee(base_fee as u128 + 5);
1050        pool.add_transaction(Arc::new(f.validated(tx_non_blob.clone())), 0);
1051
1052        // Add a blob transaction that satisfies both base fee and blob fee
1053        let tx_blob = MockTransaction::eip4844()
1054            .rng_hash()
1055            .with_nonce(1)
1056            .with_max_fee(base_fee as u128 + 5)
1057            .with_blob_fee(base_fee_per_blob_gas as u128 + 5);
1058        pool.add_transaction(Arc::new(f.validated(tx_blob.clone())), 0);
1059
1060        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
1061
1062        // Verify both transactions are returned
1063        let tx = best.next().expect("Transaction should be returned");
1064        assert_eq!(tx.transaction, tx_non_blob);
1065
1066        let tx = best.next().expect("Transaction should be returned");
1067        assert_eq!(tx.transaction, tx_blob);
1068
1069        // Ensure no more transactions are left
1070        assert!(best.next().is_none());
1071    }
1072
1073    #[test]
1074    fn test_best_transactions_with_skipping_blobs() {
1075        // Tests the skip_blobs functionality to ensure blob transactions are skipped.
1076        let mut pool = PendingPool::new(MockOrdering::default());
1077        let mut f = MockTransactionFactory::default();
1078
1079        // Add a blob transaction
1080        let tx_blob = MockTransaction::eip4844().rng_hash().with_nonce(0).with_blob_fee(100);
1081        let valid_blob_tx = f.validated(tx_blob);
1082        pool.add_transaction(Arc::new(valid_blob_tx), 0);
1083
1084        // Add a non-blob transaction
1085        let tx_non_blob = MockTransaction::eip1559().rng_hash().with_nonce(1).with_max_fee(200);
1086        let valid_non_blob_tx = f.validated(tx_non_blob.clone());
1087        pool.add_transaction(Arc::new(valid_non_blob_tx), 0);
1088
1089        let mut best = pool.best();
1090        best.skip_blobs();
1091
1092        // Only the non-blob transaction should be returned
1093        let tx = best.next().expect("Transaction should be returned");
1094        assert_eq!(tx.transaction, tx_non_blob);
1095
1096        // Ensure no more transactions are left
1097        assert!(best.next().is_none());
1098    }
1099
1100    #[test]
1101    fn test_best_transactions_no_updates() {
1102        // Tests the no_updates functionality to ensure it properly clears the
1103        // new_transaction_receiver.
1104        let mut pool = PendingPool::new(MockOrdering::default());
1105        let mut f = MockTransactionFactory::default();
1106
1107        // Add a transaction
1108        let tx = MockTransaction::eip1559().rng_hash().with_nonce(0).with_max_fee(100);
1109        let valid_tx = f.validated(tx);
1110        pool.add_transaction(Arc::new(valid_tx), 0);
1111
1112        let mut best = pool.best();
1113
1114        // Use a broadcast channel for transaction updates
1115        let (_tx_sender, tx_receiver) =
1116            tokio::sync::broadcast::channel::<PendingTransaction<MockOrdering>>(1000);
1117        best.new_transaction_receiver = Some(tx_receiver);
1118
1119        // Ensure receiver is set
1120        assert!(best.new_transaction_receiver.is_some());
1121
1122        // Call no_updates to clear the receiver
1123        best.no_updates();
1124
1125        // Ensure receiver is cleared
1126        assert!(best.new_transaction_receiver.is_none());
1127    }
1128
1129    #[test]
1130    fn test_best_transactions_yields_updates_after_empty() {
1131        let mut pool = PendingPool::new(MockOrdering::default());
1132        let mut best = pool.best();
1133        best.allow_updates_out_of_order();
1134
1135        assert!(best.next().is_none());
1136
1137        let mut f = MockTransactionFactory::default();
1138        let tx = MockTransaction::eip1559().rng_hash();
1139        let valid_tx = Arc::new(f.validated(tx));
1140        let expected_hash = *valid_tx.hash();
1141        pool.add_transaction(valid_tx, 0);
1142
1143        assert_eq!(*best.next().expect("new transaction should be yielded").hash(), expected_hash);
1144    }
1145
1146    #[test]
1147    fn test_best_update_transaction_priority() {
1148        let mut pool = PendingPool::new(MockOrdering::default());
1149        let mut f = MockTransactionFactory::default();
1150
1151        // Add 5 transactions with increasing nonces to the pool
1152        let num_tx = 5;
1153        let tx = MockTransaction::eip1559();
1154        for nonce in 0..num_tx {
1155            let tx = tx.clone().rng_hash().with_nonce(nonce);
1156            let valid_tx = f.validated(tx);
1157            pool.add_transaction(Arc::new(valid_tx), 0);
1158        }
1159
1160        // Create a BestTransactions iterator from the pool
1161        let mut best = pool.best();
1162
1163        // Use a broadcast channel for transaction updates
1164        let (tx_sender, tx_receiver) =
1165            tokio::sync::broadcast::channel::<PendingTransaction<MockOrdering>>(1000);
1166        best.new_transaction_receiver = Some(tx_receiver);
1167
1168        // yield one tx, effectively locking in the highest prio
1169        let first = best.next().unwrap();
1170
1171        // Create a new transaction with nonce 5 and validate it
1172        let new_higher_fee_tx = MockTransaction::eip1559().with_nonce(0);
1173        let valid_new_higher_fee_tx = f.validated(new_higher_fee_tx);
1174
1175        // Send the new transaction through the broadcast channel
1176        let pending_tx = PendingTransaction {
1177            submission_id: 10,
1178            transaction: Arc::new(valid_new_higher_fee_tx.clone()),
1179            priority: Priority::Value(u128::MAX),
1180        };
1181        tx_sender.send(pending_tx).unwrap();
1182
1183        // ensure that the higher prio tx is skipped since we yielded a lower one
1184        for tx in best {
1185            assert_eq!(tx.sender_id(), first.sender_id());
1186            assert_ne!(tx.sender_id(), valid_new_higher_fee_tx.sender_id());
1187        }
1188    }
1189
1190    /// Reproduces the "Blob Transaction Ordering, Multiple Clients" Hive scenario.
1191    ///
1192    /// Sender A contributes 5-blob transactions while sender B contributes 1-blob transactions.
1193    /// A single payload build should be able to fill the block with 6 blobs total (5+1).
1194    #[test]
1195    fn test_blob_transaction_ordering_multiple_clients_shape() {
1196        let mut pool = PendingPool::new(MockOrdering::default());
1197        let mut f = MockTransactionFactory::default();
1198
1199        let base_fee: u64 = 10;
1200        let base_fee_per_blob_gas: u64 = 1;
1201        let max_blob_count: u64 = 6;
1202
1203        let sender_a = MockTransaction::eip4844()
1204            .with_blob_hashes(5)
1205            .with_max_fee(base_fee as u128 + 20)
1206            .with_priority_fee(base_fee as u128 + 20)
1207            .with_blob_fee(120);
1208        for nonce in 0..5u64 {
1209            let tx = sender_a.clone().rng_hash().with_nonce(nonce);
1210            pool.add_transaction(Arc::new(f.validated(tx)), 0);
1211        }
1212
1213        let sender_b = MockTransaction::eip4844()
1214            .with_blob_hashes(1)
1215            .with_max_fee(base_fee as u128 + 20)
1216            .with_priority_fee(base_fee as u128 + 20)
1217            .with_blob_fee(100);
1218        for nonce in 0..5u64 {
1219            let tx = sender_b.clone().rng_hash().with_nonce(nonce);
1220            pool.add_transaction(Arc::new(f.validated(tx)), 0);
1221        }
1222
1223        let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas);
1224        let mut block_blob_count = 0u64;
1225        let mut included_txs = 0u64;
1226
1227        while let Some(tx) = best.next() {
1228            if let Some(blob_hashes) = tx.transaction.blob_versioned_hashes() {
1229                let tx_blob_count = blob_hashes.len() as u64;
1230
1231                if block_blob_count + tx_blob_count > max_blob_count {
1232                    crate::traits::BestTransactions::mark_invalid(
1233                        &mut best,
1234                        &tx,
1235                        InvalidPoolTransactionError::Eip4844(
1236                            Eip4844PoolTransactionError::TooManyEip4844Blobs {
1237                                have: block_blob_count + tx_blob_count,
1238                                permitted: max_blob_count,
1239                            },
1240                        ),
1241                    );
1242                    continue;
1243                }
1244
1245                block_blob_count += tx_blob_count;
1246                included_txs += 1;
1247
1248                if block_blob_count == max_blob_count {
1249                    best.skip_blobs();
1250                    break;
1251                }
1252            }
1253        }
1254
1255        assert_eq!(
1256            block_blob_count, max_blob_count,
1257            "expected a full blob block (5+1 blobs across senders)"
1258        );
1259        assert_eq!(included_txs, 2, "expected one 5-blob tx and one 1-blob tx in the block");
1260    }
1261}