Skip to main content

anvil/eth/pool/
mod.rs

1//! # Transaction Pool implementation
2//!
3//! The transaction pool is responsible for managing a set of transactions that can be included in
4//! upcoming blocks.
5//!
6//! The main task of the pool is to prepare an ordered list of transactions that are ready to be
7//! included in a new block.
8//!
9//! Each imported block can affect the validity of transactions already in the pool.
10//! The miner expects the most up-to-date transactions when attempting to create a new block.
11//! After being included in a block, a transaction should be removed from the pool, this process is
12//! called _pruning_ and due to separation of concerns is triggered externally.
13//! The pool essentially performs following services:
14//!   * import transactions
15//!   * order transactions
16//!   * provide ordered set of transactions that are ready for inclusion
17//!   * prune transactions
18//!
19//! Each transaction in the pool contains markers that it _provides_ or _requires_. This property is
20//! used to determine whether it can be included in a block (transaction is ready) or whether it
21//! still _requires_ other transactions to be mined first (transaction is pending).
22//! A transaction is associated with the nonce of the account it's sent from. A unique identifying
23//! marker for a transaction is therefore the pair `(nonce + account)`. An incoming transaction with
24//! a `nonce > nonce on chain` will _require_ `(nonce -1, account)` first, before it is ready to be
25//! included in a block.
26//!
27//! This implementation is adapted from <https://github.com/paritytech/substrate/tree/master/client/transaction-pool>
28
29use crate::{
30    eth::{
31        error::PoolError,
32        fees::FeeManager,
33        pool::transactions::{
34            PendingPoolTransaction, PendingTransactions, PoolTransaction, ReadyTransactions,
35            TransactionsIterator, TxMarker,
36        },
37    },
38    mem::storage::MinedBlockOutcome,
39};
40use alloy_consensus::Transaction;
41use alloy_primitives::{Address, TxHash};
42use alloy_rpc_types::txpool::TxpoolStatus;
43use anvil_core::eth::transaction::PendingTransaction;
44use futures::channel::mpsc::{Receiver, Sender, channel};
45use parking_lot::{Mutex, RwLock};
46use std::{collections::VecDeque, fmt, sync::Arc};
47
48#[cfg(feature = "base")]
49use alloy_consensus::Typed2718;
50
51pub mod transactions;
52
53/// Transaction pool that performs validation.
54pub struct Pool<T> {
55    /// processes all pending transactions
56    inner: RwLock<PoolInner<T>>,
57    /// listeners for new ready transactions
58    transaction_listener: Mutex<Vec<ReadyListener>>,
59    /// source of the base fee that ready transactions are ranked at.
60    fees: Option<FeeManager>,
61}
62
63struct ReadyListener {
64    sender: Sender<TxHash>,
65    close_on_full: bool,
66}
67
68/// An independent snapshot of a transaction pool.
69#[derive(Debug)]
70pub(crate) struct PoolSnapshot<T> {
71    inner: PoolInner<T>,
72}
73
74impl<T> Default for Pool<T> {
75    fn default() -> Self {
76        Self {
77            inner: RwLock::new(PoolInner::default()),
78            transaction_listener: Default::default(),
79            fees: None,
80        }
81    }
82}
83
84// == impl Pool ==
85
86impl<T> Pool<T> {
87    /// Creates a pool that ranks ready transactions at the base fee of the given fee manager.
88    pub fn new(fees: FeeManager) -> Self {
89        Self { fees: Some(fees), ..Default::default() }
90    }
91
92    /// Returns the base fee of the next block.
93    fn base_fee(&self) -> u64 {
94        self.fees.as_ref().map_or(0, FeeManager::base_fee)
95    }
96
97    /// Returns an independent snapshot of the pool.
98    pub(crate) fn snapshot(&self) -> PoolSnapshot<T> {
99        let pool = self.inner.read();
100        PoolSnapshot {
101            inner: PoolInner {
102                ready_transactions: pool.ready_transactions.snapshot(),
103                pending_transactions: pool.pending_transactions.snapshot(),
104            },
105        }
106    }
107
108    /// Restores the pool to a previous snapshot.
109    pub(crate) fn restore(&self, snapshot: PoolSnapshot<T>) {
110        let ready = {
111            let mut pool = self.inner.write();
112            *pool = snapshot.inner;
113            pool.ready_transactions(self.base_fee()).map(|tx| tx.hash()).collect::<Vec<_>>()
114        };
115        for hash in ready {
116            self.notify_listener(hash);
117        }
118    }
119
120    /// Returns an iterator that yields all transactions that are currently ready
121    pub fn ready_transactions(&self) -> TransactionsIterator<T> {
122        self.inner.read().ready_transactions(self.base_fee())
123    }
124
125    /// Returns all transactions that are not ready to be included in a block yet
126    pub fn pending_transactions(&self) -> Vec<Arc<PoolTransaction<T>>> {
127        self.inner.read().pending_transactions.transactions().collect()
128    }
129
130    /// Returns every ready and queued transaction.
131    #[cfg(feature = "base")]
132    pub fn all_transactions(&self) -> Vec<Arc<PoolTransaction<T>>> {
133        let pool = self.inner.read();
134        pool.pending_transactions
135            .transactions()
136            .chain(pool.ready_transactions(self.base_fee()))
137            .collect()
138    }
139
140    /// Returns the number of tx that are ready and queued for further execution
141    pub fn txpool_status(&self) -> TxpoolStatus {
142        // Note: naming differs here compared to geth's `TxpoolStatus`
143        let pending: u64 = self.inner.read().ready_transactions.len().try_into().unwrap_or(0);
144        let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0);
145        TxpoolStatus { pending, queued }
146    }
147
148    /// Adds a new transaction listener to the pool that gets notified about every new ready
149    /// transaction
150    pub fn add_ready_listener(&self) -> Receiver<TxHash> {
151        self.add_ready_listener_with_policy(false)
152    }
153
154    /// Adds a subscription listener that closes instead of dropping notifications on overflow.
155    pub(crate) fn add_ready_subscription_listener(&self) -> Receiver<TxHash> {
156        self.add_ready_listener_with_policy(true)
157    }
158
159    fn add_ready_listener_with_policy(&self, close_on_full: bool) -> Receiver<TxHash> {
160        const TX_LISTENER_BUFFER_SIZE: usize = 2048;
161        let (tx, rx) = channel(TX_LISTENER_BUFFER_SIZE);
162        self.transaction_listener.lock().push(ReadyListener { sender: tx, close_on_full });
163        rx
164    }
165
166    /// Returns true if this pool already contains the transaction
167    pub fn contains(&self, tx_hash: &TxHash) -> bool {
168        self.inner.read().contains(tx_hash)
169    }
170
171    /// Returns true if this pool contains a transaction from `sender` with `nonce`.
172    pub fn contains_sender_nonce(&self, sender: Address, nonce: u64) -> bool
173    where
174        T: Transaction,
175    {
176        self.inner
177            .read()
178            .transactions_by_sender(sender)
179            .any(|tx| tx.pending_transaction.nonce() == nonce)
180    }
181
182    /// Returns a transaction from `sender` that provides exactly `markers`.
183    #[cfg(feature = "base")]
184    pub fn transaction_with_markers(
185        &self,
186        sender: Address,
187        markers: &[TxMarker],
188    ) -> Option<Arc<PoolTransaction<T>>> {
189        self.inner.read().transactions_by_sender(sender).find(|tx| tx.provides == markers)
190    }
191
192    /// Removes all transactions from the pool
193    pub fn clear(&self) {
194        let mut pool = self.inner.write();
195        pool.clear();
196    }
197
198    /// Remove the given transactions from the pool
199    pub fn remove_invalid(&self, tx_hashes: Vec<TxHash>) -> Vec<Arc<PoolTransaction<T>>> {
200        self.inner.write().remove_invalid(tx_hashes)
201    }
202
203    /// Remove transactions by sender
204    pub fn remove_transactions_by_address(&self, sender: Address) -> Vec<Arc<PoolTransaction<T>>> {
205        self.inner.write().remove_transactions_by_address(sender)
206    }
207
208    /// Removes a single transaction from the pool
209    ///
210    /// This is similar to `[Pool::remove_invalid()]` but for a single transaction.
211    ///
212    /// **Note**: this will also drop any transaction that depend on the `tx`
213    pub fn drop_transaction(&self, tx: TxHash) -> Option<Arc<PoolTransaction<T>>> {
214        trace!(target: "txpool", "Dropping transaction: [{:?}]", tx);
215        let removed = {
216            let mut pool = self.inner.write();
217            let mut removed = pool.ready_transactions.remove_with_markers(vec![tx], None);
218            let invalidated = removed.iter().flat_map(|tx| tx.provides.iter().cloned());
219            removed.extend(pool.pending_transactions.remove_with_dependents(vec![tx], invalidated));
220            removed
221        };
222        trace!(target: "txpool", "Dropped transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
223
224        if removed.is_empty() {
225            None
226        } else {
227            removed.into_iter().find(|t| *t.pending_transaction.hash() == tx)
228        }
229    }
230
231    /// Notifies listeners if the transaction was added to the ready queue.
232    fn notify_ready(&self, tx: &AddedTransaction<T>) {
233        if let AddedTransaction::Ready(ready) = tx {
234            self.notify_listener(ready.hash);
235            for promoted in ready.promoted.iter().copied() {
236                self.notify_listener(promoted);
237            }
238        }
239    }
240
241    /// notifies all listeners about the transaction
242    fn notify_listener(&self, hash: TxHash) {
243        let mut listener = self.transaction_listener.lock();
244        // this is basically a retain but with mut reference
245        for n in (0..listener.len()).rev() {
246            let mut listener_tx = listener.swap_remove(n);
247            let retain = match listener_tx.sender.try_send(hash) {
248                Ok(()) => true,
249                Err(e) => {
250                    if e.is_full() {
251                        warn!(
252                            target: "txpool",
253                            "[{:?}] Failed to send tx notification because channel is full",
254                            hash,
255                        );
256                        !listener_tx.close_on_full
257                    } else {
258                        false
259                    }
260                }
261            };
262            if retain {
263                listener.push(listener_tx)
264            }
265        }
266    }
267}
268
269impl<T: Clone> Pool<T> {
270    /// Returns the _pending_ transaction for that `hash` if it exists in the mempool
271    pub fn get_transaction(&self, hash: TxHash) -> Option<PendingTransaction<T>> {
272        self.inner.read().get_transaction(hash)
273    }
274}
275
276impl<T: Transaction> Pool<T> {
277    /// Invoked when a set of transactions ([Self::ready_transactions()]) was executed.
278    ///
279    /// This will remove the transactions from the pool.
280    ///
281    /// Returns `true` if ready transactions left behind by the block can be included by mining
282    /// again right away, e.g. because the block hit `max_transactions` or ran out of gas.
283    pub fn on_mined_block(self: &Arc<Self>, outcome: MinedBlockOutcome<T>) -> bool {
284        let MinedBlockOutcome { block_number, included, stale, invalid, not_yet_valid } = outcome;
285        // Requiring txs to leave the pool keeps this retry from mining empty blocks for txs that
286        // can never be included. Not-yet-valid txs and their dependents are retried by the delayed
287        // re-notify.
288        let made_progress = !included.is_empty() || !stale.is_empty() || !invalid.is_empty();
289        let retry_ready = made_progress && not_yet_valid.is_empty();
290
291        // remove invalid transactions from the pool
292        self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect());
293
294        // Prune mined and stale markers; both are satisfied by the resulting state.
295        let res = self.prune_markers(
296            block_number,
297            included.into_iter().chain(stale).flat_map(|tx| tx.provides.clone()),
298        );
299        trace!(target: "txpool", "pruned transaction markers {:?}", res);
300
301        // Re-notify the miner about not-yet-valid transactions so they'll be retried.
302        // Delay by 1 second to let time advance before the next mining attempt.
303        if !not_yet_valid.is_empty() {
304            let tx_hashes: Vec<_> = not_yet_valid.iter().map(|tx| tx.hash()).collect();
305            let pool = Arc::clone(self);
306            tokio::spawn(async move {
307                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
308                for hash in tx_hashes {
309                    trace!(target: "txpool", "re-notifying for not-yet-valid tx: {:?}", hash);
310                    pool.notify_listener(hash);
311                }
312            });
313        }
314
315        retry_ready && !self.inner.read().ready_transactions.is_empty()
316    }
317
318    /// Removes ready transactions for the given iterator of identifying markers.
319    ///
320    /// For each marker we can remove transactions in the pool that either provide the marker
321    /// directly or are a dependency of the transaction associated with that marker.
322    pub fn prune_markers(
323        &self,
324        block_number: u64,
325        markers: impl IntoIterator<Item = TxMarker>,
326    ) -> PruneResult<T> {
327        debug!(target: "txpool", ?block_number, "pruning transactions");
328        let res = self.inner.write().prune_markers(markers);
329        for tx in &res.promoted {
330            self.notify_ready(tx);
331        }
332        res
333    }
334
335    /// Adds a new transaction to the pool
336    pub fn add_transaction(
337        &self,
338        tx: PoolTransaction<T>,
339    ) -> Result<AddedTransaction<T>, PoolError> {
340        let added = self.inner.write().add_transaction(tx)?;
341        self.notify_ready(&added);
342        Ok(added)
343    }
344}
345
346#[cfg(feature = "base")]
347impl<T: Typed2718> Pool<T> {
348    /// Removes every transaction with the given EIP-2718 type.
349    pub fn clear_transaction_type(&self, tx_type: u8) -> Vec<Arc<PoolTransaction<T>>> {
350        let hashes = {
351            let pool = self.inner.read();
352            pool.pending_transactions
353                .transactions()
354                .chain(pool.ready_transactions(self.base_fee()))
355                .filter_map(|tx| {
356                    (tx.pending_transaction.transaction.ty() == tx_type).then_some(tx.hash())
357                })
358                .collect()
359        };
360        self.remove_invalid(hashes)
361    }
362}
363
364/// A Transaction Pool
365///
366/// Contains all transactions that are ready to be executed
367#[derive(Debug)]
368struct PoolInner<T> {
369    ready_transactions: ReadyTransactions<T>,
370    pending_transactions: PendingTransactions<T>,
371}
372
373impl<T> Default for PoolInner<T> {
374    fn default() -> Self {
375        Self { ready_transactions: Default::default(), pending_transactions: Default::default() }
376    }
377}
378
379// == impl PoolInner ==
380
381impl<T> PoolInner<T> {
382    /// Returns an iterator over transactions that are ready.
383    fn ready_transactions(&self, base_fee: u64) -> TransactionsIterator<T> {
384        self.ready_transactions.get_transactions(base_fee)
385    }
386
387    /// Clears
388    fn clear(&mut self) {
389        self.ready_transactions.clear();
390        self.pending_transactions.clear();
391    }
392
393    /// Returns an iterator over all transactions in the pool filtered by the sender
394    pub fn transactions_by_sender(
395        &self,
396        sender: Address,
397    ) -> impl Iterator<Item = Arc<PoolTransaction<T>>> + '_ {
398        let pending_txs = self
399            .pending_transactions
400            .transactions()
401            .filter(move |tx| tx.pending_transaction.sender().eq(&sender));
402
403        let ready_txs = self
404            .ready_transactions
405            // Only membership matters here, so the ranking base fee is irrelevant.
406            .get_transactions(0)
407            .filter(move |tx| tx.pending_transaction.sender().eq(&sender));
408
409        pending_txs.chain(ready_txs)
410    }
411
412    /// Returns true if this pool already contains the transaction
413    fn contains(&self, tx_hash: &TxHash) -> bool {
414        self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash)
415    }
416
417    /// Remove the given transactions from the pool
418    fn remove_invalid(&mut self, tx_hashes: Vec<TxHash>) -> Vec<Arc<PoolTransaction<T>>> {
419        // early exit in case there is no invalid transactions.
420        if tx_hashes.is_empty() {
421            return vec![];
422        }
423        trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes);
424
425        let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None);
426        removed.extend(self.pending_transactions.remove(tx_hashes));
427
428        trace!(target: "txpool", "Removed invalid transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
429
430        removed
431    }
432
433    /// Remove transactions by sender address
434    fn remove_transactions_by_address(&mut self, sender: Address) -> Vec<Arc<PoolTransaction<T>>> {
435        let tx_hashes =
436            self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::<Vec<TxHash>>();
437
438        if tx_hashes.is_empty() {
439            return vec![];
440        }
441
442        trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes);
443
444        let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None);
445        removed.extend(self.pending_transactions.remove(tx_hashes));
446
447        trace!(target: "txpool", "Removed transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
448
449        removed
450    }
451}
452
453impl<T: Clone> PoolInner<T> {
454    /// checks both pools for the matching transaction
455    ///
456    /// Returns `None` if the transaction does not exist in the pool
457    fn get_transaction(&self, hash: TxHash) -> Option<PendingTransaction<T>> {
458        if let Some(pending) = self.pending_transactions.get(&hash) {
459            return Some(pending.transaction.pending_transaction.clone());
460        }
461        Some(
462            self.ready_transactions.get(&hash)?.transaction.transaction.pending_transaction.clone(),
463        )
464    }
465}
466
467impl<T: Transaction> PoolInner<T> {
468    fn add_transaction(
469        &mut self,
470        tx: PoolTransaction<T>,
471    ) -> Result<AddedTransaction<T>, PoolError> {
472        if self.contains(&tx.hash()) {
473            debug!(target: "txpool", "[{:?}] Already imported", tx.hash());
474            return Err(PoolError::AlreadyImported(tx.hash()));
475        }
476
477        let tx = PendingPoolTransaction::new(tx, self.ready_transactions.provided_markers());
478        trace!(target: "txpool", "[{:?}] ready={}", tx.transaction.hash(), tx.is_ready());
479
480        // If all markers are not satisfied import to future
481        if !tx.is_ready() {
482            let hash = tx.transaction.hash();
483            self.pending_transactions.add_transaction(tx)?;
484            return Ok(AddedTransaction::Pending { hash });
485        }
486        self.add_ready_transaction(tx)
487    }
488
489    /// Adds the transaction to the ready queue
490    fn add_ready_transaction(
491        &mut self,
492        tx: PendingPoolTransaction<T>,
493    ) -> Result<AddedTransaction<T>, PoolError> {
494        let hash = tx.transaction.hash();
495        trace!(target: "txpool", "adding ready transaction [{:?}]", hash);
496        let mut ready = ReadyTransaction::new(hash);
497
498        let mut tx_queue = VecDeque::from([tx]);
499        // tracks whether we're processing the given `tx`
500        let mut is_new_tx = true;
501
502        // take first transaction from the list
503        while let Some(current_tx) = tx_queue.pop_front() {
504            // also add the transaction that the current transaction unlocks
505            tx_queue.extend(
506                self.pending_transactions.mark_and_unlock(&current_tx.transaction.provides),
507            );
508
509            let current_hash = current_tx.transaction.hash();
510            // try to add the transaction to the ready pool
511            match self.ready_transactions.add_transaction(current_tx) {
512                Ok(replaced_transactions) => {
513                    if !is_new_tx {
514                        ready.promoted.push(current_hash);
515                    }
516                    // tx removed from ready pool
517                    ready.removed.extend(replaced_transactions);
518                }
519                Err(err) => {
520                    // failed to add transaction
521                    if is_new_tx {
522                        debug!(target: "txpool", "[{:?}] Failed to add tx: {:?}", current_hash,
523        err);
524                        return Err(err);
525                    }
526                    ready.discarded.push(current_hash);
527                }
528            }
529            is_new_tx = false;
530        }
531
532        // check for a cycle where importing a transaction resulted in pending transactions to be
533        // added while removing current transaction. in which case we move this transaction back to
534        // the pending queue
535        if ready.removed.iter().any(|tx| *tx.hash() == hash) {
536            self.ready_transactions.clear_transactions(&ready.promoted);
537            return Err(PoolError::CyclicTransaction);
538        }
539
540        Ok(AddedTransaction::Ready(ready))
541    }
542
543    /// Prunes the transactions that provide the given markers
544    ///
545    /// This will effectively remove those transactions that satisfy the markers and transactions
546    /// from the pending queue might get promoted to if the markers unlock them.
547    pub fn prune_markers(&mut self, markers: impl IntoIterator<Item = TxMarker>) -> PruneResult<T> {
548        let mut imports = vec![];
549        let mut pruned = vec![];
550
551        for marker in markers {
552            // mark as satisfied and store the transactions that got unlocked
553            imports.extend(self.pending_transactions.mark_and_unlock(Some(&marker)));
554            // prune transactions
555            pruned.extend(self.ready_transactions.prune_tags(marker.clone()));
556        }
557
558        let mut promoted = vec![];
559        let mut failed = vec![];
560        for tx in imports {
561            let hash = tx.transaction.hash();
562            match self.add_ready_transaction(tx) {
563                Ok(res) => promoted.push(res),
564                Err(e) => {
565                    warn!(target: "txpool", "Failed to promote tx [{:?}] : {:?}", hash, e);
566                    failed.push(hash)
567                }
568            }
569        }
570
571        PruneResult { pruned, failed, promoted }
572    }
573}
574
575/// Represents the outcome of a prune
576pub struct PruneResult<T> {
577    /// a list of added transactions that a pruned marker satisfied
578    pub promoted: Vec<AddedTransaction<T>>,
579    /// all transactions that  failed to be promoted and now are discarded
580    pub failed: Vec<TxHash>,
581    /// all transactions that were pruned from the ready pool
582    pub pruned: Vec<Arc<PoolTransaction<T>>>,
583}
584
585impl<T> fmt::Debug for PruneResult<T> {
586    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
587        write!(fmt, "PruneResult {{ ")?;
588        write!(
589            fmt,
590            "promoted: {:?}, ",
591            self.promoted.iter().map(|tx| *tx.hash()).collect::<Vec<_>>()
592        )?;
593        write!(fmt, "failed: {:?}, ", self.failed)?;
594        write!(
595            fmt,
596            "pruned: {:?}, ",
597            self.pruned.iter().map(|tx| *tx.pending_transaction.hash()).collect::<Vec<_>>()
598        )?;
599        write!(fmt, "}}")?;
600        Ok(())
601    }
602}
603
604#[derive(Clone, Debug)]
605pub struct ReadyTransaction<T> {
606    /// the hash of the submitted transaction
607    hash: TxHash,
608    /// transactions promoted to the ready queue
609    promoted: Vec<TxHash>,
610    /// transaction that failed and became discarded
611    discarded: Vec<TxHash>,
612    /// Transactions removed from the Ready pool
613    removed: Vec<Arc<PoolTransaction<T>>>,
614}
615
616impl<T> ReadyTransaction<T> {
617    pub fn new(hash: TxHash) -> Self {
618        Self {
619            hash,
620            promoted: Default::default(),
621            discarded: Default::default(),
622            removed: Default::default(),
623        }
624    }
625}
626
627#[derive(Clone, Debug)]
628pub enum AddedTransaction<T> {
629    /// transaction was successfully added and being processed
630    Ready(ReadyTransaction<T>),
631    /// Transaction was successfully added but not yet queued for processing
632    Pending {
633        /// the hash of the submitted transaction
634        hash: TxHash,
635    },
636}
637
638impl<T> AddedTransaction<T> {
639    pub const fn hash(&self) -> &TxHash {
640        match self {
641            Self::Ready(tx) => &tx.hash,
642            Self::Pending { hash } => hash,
643        }
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650    use alloy_primitives::U256;
651    use futures::StreamExt;
652    use std::task::{Context, Poll, Waker};
653
654    #[test]
655    fn subscription_listener_closes_on_overflow_without_closing_ordinary_listener() {
656        let pool = Pool::<()>::default();
657        let mut ordinary = pool.add_ready_listener();
658        let mut subscription = pool.add_ready_subscription_listener();
659        let hashes = (0..4_096).map(|index| TxHash::from(U256::from(index))).collect::<Vec<_>>();
660
661        // Overflow both listeners without draining either.
662        for &hash in &hashes {
663            pool.notify_listener(hash);
664        }
665
666        let mut cx = Context::from_waker(Waker::noop());
667        let mut expected = Vec::new();
668        while let Poll::Ready(Some(hash)) = ordinary.poll_next_unpin(&mut cx) {
669            expected.push(hash);
670        }
671        assert!(!expected.is_empty());
672        assert!(expected.len() < hashes.len());
673        assert_eq!(expected, hashes[..expected.len()]);
674        assert_eq!(ordinary.poll_next_unpin(&mut cx), Poll::Pending);
675
676        let mut received = Vec::new();
677        while let Poll::Ready(Some(hash)) = subscription.poll_next_unpin(&mut cx) {
678            received.push(hash);
679        }
680        assert_eq!(received, expected);
681        assert_eq!(subscription.poll_next_unpin(&mut cx), Poll::Ready(None));
682
683        let hash = TxHash::from(U256::MAX);
684        pool.notify_listener(hash);
685        assert_eq!(ordinary.poll_next_unpin(&mut cx), Poll::Ready(Some(hash)));
686    }
687}