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        pool::transactions::{
33            PendingPoolTransaction, PendingTransactions, PoolTransaction, ReadyTransactions,
34            TransactionsIterator, TxMarker,
35        },
36    },
37    mem::storage::MinedBlockOutcome,
38};
39use alloy_consensus::Transaction;
40use alloy_primitives::{Address, TxHash};
41use alloy_rpc_types::txpool::TxpoolStatus;
42use anvil_core::eth::transaction::PendingTransaction;
43use futures::channel::mpsc::{Receiver, Sender, channel};
44use parking_lot::{Mutex, RwLock};
45use std::{collections::VecDeque, fmt, sync::Arc};
46
47pub mod transactions;
48
49/// Transaction pool that performs validation.
50pub struct Pool<T> {
51    /// processes all pending transactions
52    inner: RwLock<PoolInner<T>>,
53    /// listeners for new ready transactions
54    transaction_listener: Mutex<Vec<Sender<TxHash>>>,
55}
56
57impl<T> Default for Pool<T> {
58    fn default() -> Self {
59        Self { inner: RwLock::new(PoolInner::default()), transaction_listener: Default::default() }
60    }
61}
62
63// == impl Pool ==
64
65impl<T> Pool<T> {
66    /// Returns an iterator that yields all transactions that are currently ready
67    pub fn ready_transactions(&self) -> TransactionsIterator<T> {
68        self.inner.read().ready_transactions()
69    }
70
71    /// Returns all transactions that are not ready to be included in a block yet
72    pub fn pending_transactions(&self) -> Vec<Arc<PoolTransaction<T>>> {
73        self.inner.read().pending_transactions.transactions().collect()
74    }
75
76    /// Returns the number of tx that are ready and queued for further execution
77    pub fn txpool_status(&self) -> TxpoolStatus {
78        // Note: naming differs here compared to geth's `TxpoolStatus`
79        let pending: u64 = self.inner.read().ready_transactions.len().try_into().unwrap_or(0);
80        let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0);
81        TxpoolStatus { pending, queued }
82    }
83
84    /// Adds a new transaction listener to the pool that gets notified about every new ready
85    /// transaction
86    pub fn add_ready_listener(&self) -> Receiver<TxHash> {
87        const TX_LISTENER_BUFFER_SIZE: usize = 2048;
88        let (tx, rx) = channel(TX_LISTENER_BUFFER_SIZE);
89        self.transaction_listener.lock().push(tx);
90        rx
91    }
92
93    /// Returns true if this pool already contains the transaction
94    pub fn contains(&self, tx_hash: &TxHash) -> bool {
95        self.inner.read().contains(tx_hash)
96    }
97
98    /// Returns true if this pool contains a transaction from `sender` with `nonce`.
99    pub fn contains_sender_nonce(&self, sender: Address, nonce: u64) -> bool
100    where
101        T: Transaction,
102    {
103        self.inner
104            .read()
105            .transactions_by_sender(sender)
106            .any(|tx| tx.pending_transaction.nonce() == nonce)
107    }
108
109    /// Removes all transactions from the pool
110    pub fn clear(&self) {
111        let mut pool = self.inner.write();
112        pool.clear();
113    }
114
115    /// Remove the given transactions from the pool
116    pub fn remove_invalid(&self, tx_hashes: Vec<TxHash>) -> Vec<Arc<PoolTransaction<T>>> {
117        self.inner.write().remove_invalid(tx_hashes)
118    }
119
120    /// Remove transactions by sender
121    pub fn remove_transactions_by_address(&self, sender: Address) -> Vec<Arc<PoolTransaction<T>>> {
122        self.inner.write().remove_transactions_by_address(sender)
123    }
124
125    /// Removes a single transaction from the pool
126    ///
127    /// This is similar to `[Pool::remove_invalid()]` but for a single transaction.
128    ///
129    /// **Note**: this will also drop any transaction that depend on the `tx`
130    pub fn drop_transaction(&self, tx: TxHash) -> Option<Arc<PoolTransaction<T>>> {
131        trace!(target: "txpool", "Dropping transaction: [{:?}]", tx);
132        let removed = {
133            let mut pool = self.inner.write();
134            pool.ready_transactions.remove_with_markers(vec![tx], None)
135        };
136        trace!(target: "txpool", "Dropped transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
137
138        if removed.is_empty() {
139            None
140        } else {
141            removed.into_iter().find(|t| *t.pending_transaction.hash() == tx)
142        }
143    }
144
145    /// Notifies listeners if the transaction was added to the ready queue.
146    fn notify_ready(&self, tx: &AddedTransaction<T>) {
147        if let AddedTransaction::Ready(ready) = tx {
148            self.notify_listener(ready.hash);
149            for promoted in ready.promoted.iter().copied() {
150                self.notify_listener(promoted);
151            }
152        }
153    }
154
155    /// notifies all listeners about the transaction
156    fn notify_listener(&self, hash: TxHash) {
157        let mut listener = self.transaction_listener.lock();
158        // this is basically a retain but with mut reference
159        for n in (0..listener.len()).rev() {
160            let mut listener_tx = listener.swap_remove(n);
161            let retain = match listener_tx.try_send(hash) {
162                Ok(()) => true,
163                Err(e) => {
164                    if e.is_full() {
165                        warn!(
166                            target: "txpool",
167                            "[{:?}] Failed to send tx notification because channel is full",
168                            hash,
169                        );
170                        true
171                    } else {
172                        false
173                    }
174                }
175            };
176            if retain {
177                listener.push(listener_tx)
178            }
179        }
180    }
181}
182
183impl<T: Clone> Pool<T> {
184    /// Returns the _pending_ transaction for that `hash` if it exists in the mempool
185    pub fn get_transaction(&self, hash: TxHash) -> Option<PendingTransaction<T>> {
186        self.inner.read().get_transaction(hash)
187    }
188}
189
190impl<T: Transaction> Pool<T> {
191    /// Invoked when a set of transactions ([Self::ready_transactions()]) was executed.
192    ///
193    /// This will remove the transactions from the pool.
194    pub fn on_mined_block(self: &Arc<Self>, outcome: MinedBlockOutcome<T>) -> PruneResult<T> {
195        let MinedBlockOutcome { block_number, included, invalid, not_yet_valid } = outcome;
196
197        // remove invalid transactions from the pool
198        self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect());
199
200        // prune all the markers the mined transactions provide
201        let res = self
202            .prune_markers(block_number, included.into_iter().flat_map(|tx| tx.provides.clone()));
203        trace!(target: "txpool", "pruned transaction markers {:?}", res);
204
205        // Re-notify the miner about not-yet-valid transactions so they'll be retried.
206        // Delay by 1 second to let time advance before the next mining attempt.
207        if !not_yet_valid.is_empty() {
208            let tx_hashes: Vec<_> = not_yet_valid.iter().map(|tx| tx.hash()).collect();
209            let pool = Arc::clone(self);
210            tokio::spawn(async move {
211                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
212                for hash in tx_hashes {
213                    trace!(target: "txpool", "re-notifying for not-yet-valid tx: {:?}", hash);
214                    pool.notify_listener(hash);
215                }
216            });
217        }
218
219        res
220    }
221
222    /// Removes ready transactions for the given iterator of identifying markers.
223    ///
224    /// For each marker we can remove transactions in the pool that either provide the marker
225    /// directly or are a dependency of the transaction associated with that marker.
226    pub fn prune_markers(
227        &self,
228        block_number: u64,
229        markers: impl IntoIterator<Item = TxMarker>,
230    ) -> PruneResult<T> {
231        debug!(target: "txpool", ?block_number, "pruning transactions");
232        let res = self.inner.write().prune_markers(markers);
233        for tx in &res.promoted {
234            self.notify_ready(tx);
235        }
236        res
237    }
238
239    /// Adds a new transaction to the pool
240    pub fn add_transaction(
241        &self,
242        tx: PoolTransaction<T>,
243    ) -> Result<AddedTransaction<T>, PoolError> {
244        let added = self.inner.write().add_transaction(tx)?;
245        self.notify_ready(&added);
246        Ok(added)
247    }
248}
249
250/// A Transaction Pool
251///
252/// Contains all transactions that are ready to be executed
253#[derive(Debug)]
254struct PoolInner<T> {
255    ready_transactions: ReadyTransactions<T>,
256    pending_transactions: PendingTransactions<T>,
257}
258
259impl<T> Default for PoolInner<T> {
260    fn default() -> Self {
261        Self { ready_transactions: Default::default(), pending_transactions: Default::default() }
262    }
263}
264
265// == impl PoolInner ==
266
267impl<T> PoolInner<T> {
268    /// Returns an iterator over transactions that are ready.
269    fn ready_transactions(&self) -> TransactionsIterator<T> {
270        self.ready_transactions.get_transactions()
271    }
272
273    /// Clears
274    fn clear(&mut self) {
275        self.ready_transactions.clear();
276        self.pending_transactions.clear();
277    }
278
279    /// Returns an iterator over all transactions in the pool filtered by the sender
280    pub fn transactions_by_sender(
281        &self,
282        sender: Address,
283    ) -> impl Iterator<Item = Arc<PoolTransaction<T>>> + '_ {
284        let pending_txs = self
285            .pending_transactions
286            .transactions()
287            .filter(move |tx| tx.pending_transaction.sender().eq(&sender));
288
289        let ready_txs = self
290            .ready_transactions
291            .get_transactions()
292            .filter(move |tx| tx.pending_transaction.sender().eq(&sender));
293
294        pending_txs.chain(ready_txs)
295    }
296
297    /// Returns true if this pool already contains the transaction
298    fn contains(&self, tx_hash: &TxHash) -> bool {
299        self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash)
300    }
301
302    /// Remove the given transactions from the pool
303    fn remove_invalid(&mut self, tx_hashes: Vec<TxHash>) -> Vec<Arc<PoolTransaction<T>>> {
304        // early exit in case there is no invalid transactions.
305        if tx_hashes.is_empty() {
306            return vec![];
307        }
308        trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes);
309
310        let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None);
311        removed.extend(self.pending_transactions.remove(tx_hashes));
312
313        trace!(target: "txpool", "Removed invalid transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
314
315        removed
316    }
317
318    /// Remove transactions by sender address
319    fn remove_transactions_by_address(&mut self, sender: Address) -> Vec<Arc<PoolTransaction<T>>> {
320        let tx_hashes =
321            self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::<Vec<TxHash>>();
322
323        if tx_hashes.is_empty() {
324            return vec![];
325        }
326
327        trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes);
328
329        let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None);
330        removed.extend(self.pending_transactions.remove(tx_hashes));
331
332        trace!(target: "txpool", "Removed transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::<Vec<_>>());
333
334        removed
335    }
336}
337
338impl<T: Clone> PoolInner<T> {
339    /// checks both pools for the matching transaction
340    ///
341    /// Returns `None` if the transaction does not exist in the pool
342    fn get_transaction(&self, hash: TxHash) -> Option<PendingTransaction<T>> {
343        if let Some(pending) = self.pending_transactions.get(&hash) {
344            return Some(pending.transaction.pending_transaction.clone());
345        }
346        Some(
347            self.ready_transactions.get(&hash)?.transaction.transaction.pending_transaction.clone(),
348        )
349    }
350}
351
352impl<T: Transaction> PoolInner<T> {
353    fn add_transaction(
354        &mut self,
355        tx: PoolTransaction<T>,
356    ) -> Result<AddedTransaction<T>, PoolError> {
357        if self.contains(&tx.hash()) {
358            debug!(target: "txpool", "[{:?}] Already imported", tx.hash());
359            return Err(PoolError::AlreadyImported(tx.hash()));
360        }
361
362        let tx = PendingPoolTransaction::new(tx, self.ready_transactions.provided_markers());
363        trace!(target: "txpool", "[{:?}] ready={}", tx.transaction.hash(), tx.is_ready());
364
365        // If all markers are not satisfied import to future
366        if !tx.is_ready() {
367            let hash = tx.transaction.hash();
368            self.pending_transactions.add_transaction(tx)?;
369            return Ok(AddedTransaction::Pending { hash });
370        }
371        self.add_ready_transaction(tx)
372    }
373
374    /// Adds the transaction to the ready queue
375    fn add_ready_transaction(
376        &mut self,
377        tx: PendingPoolTransaction<T>,
378    ) -> Result<AddedTransaction<T>, PoolError> {
379        let hash = tx.transaction.hash();
380        trace!(target: "txpool", "adding ready transaction [{:?}]", hash);
381        let mut ready = ReadyTransaction::new(hash);
382
383        let mut tx_queue = VecDeque::from([tx]);
384        // tracks whether we're processing the given `tx`
385        let mut is_new_tx = true;
386
387        // take first transaction from the list
388        while let Some(current_tx) = tx_queue.pop_front() {
389            // also add the transaction that the current transaction unlocks
390            tx_queue.extend(
391                self.pending_transactions.mark_and_unlock(&current_tx.transaction.provides),
392            );
393
394            let current_hash = current_tx.transaction.hash();
395            // try to add the transaction to the ready pool
396            match self.ready_transactions.add_transaction(current_tx) {
397                Ok(replaced_transactions) => {
398                    if !is_new_tx {
399                        ready.promoted.push(current_hash);
400                    }
401                    // tx removed from ready pool
402                    ready.removed.extend(replaced_transactions);
403                }
404                Err(err) => {
405                    // failed to add transaction
406                    if is_new_tx {
407                        debug!(target: "txpool", "[{:?}] Failed to add tx: {:?}", current_hash,
408        err);
409                        return Err(err);
410                    }
411                    ready.discarded.push(current_hash);
412                }
413            }
414            is_new_tx = false;
415        }
416
417        // check for a cycle where importing a transaction resulted in pending transactions to be
418        // added while removing current transaction. in which case we move this transaction back to
419        // the pending queue
420        if ready.removed.iter().any(|tx| *tx.hash() == hash) {
421            self.ready_transactions.clear_transactions(&ready.promoted);
422            return Err(PoolError::CyclicTransaction);
423        }
424
425        Ok(AddedTransaction::Ready(ready))
426    }
427
428    /// Prunes the transactions that provide the given markers
429    ///
430    /// This will effectively remove those transactions that satisfy the markers and transactions
431    /// from the pending queue might get promoted to if the markers unlock them.
432    pub fn prune_markers(&mut self, markers: impl IntoIterator<Item = TxMarker>) -> PruneResult<T> {
433        let mut imports = vec![];
434        let mut pruned = vec![];
435
436        for marker in markers {
437            // mark as satisfied and store the transactions that got unlocked
438            imports.extend(self.pending_transactions.mark_and_unlock(Some(&marker)));
439            // prune transactions
440            pruned.extend(self.ready_transactions.prune_tags(marker.clone()));
441        }
442
443        let mut promoted = vec![];
444        let mut failed = vec![];
445        for tx in imports {
446            let hash = tx.transaction.hash();
447            match self.add_ready_transaction(tx) {
448                Ok(res) => promoted.push(res),
449                Err(e) => {
450                    warn!(target: "txpool", "Failed to promote tx [{:?}] : {:?}", hash, e);
451                    failed.push(hash)
452                }
453            }
454        }
455
456        PruneResult { pruned, failed, promoted }
457    }
458}
459
460/// Represents the outcome of a prune
461pub struct PruneResult<T> {
462    /// a list of added transactions that a pruned marker satisfied
463    pub promoted: Vec<AddedTransaction<T>>,
464    /// all transactions that  failed to be promoted and now are discarded
465    pub failed: Vec<TxHash>,
466    /// all transactions that were pruned from the ready pool
467    pub pruned: Vec<Arc<PoolTransaction<T>>>,
468}
469
470impl<T> fmt::Debug for PruneResult<T> {
471    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
472        write!(fmt, "PruneResult {{ ")?;
473        write!(
474            fmt,
475            "promoted: {:?}, ",
476            self.promoted.iter().map(|tx| *tx.hash()).collect::<Vec<_>>()
477        )?;
478        write!(fmt, "failed: {:?}, ", self.failed)?;
479        write!(
480            fmt,
481            "pruned: {:?}, ",
482            self.pruned.iter().map(|tx| *tx.pending_transaction.hash()).collect::<Vec<_>>()
483        )?;
484        write!(fmt, "}}")?;
485        Ok(())
486    }
487}
488
489#[derive(Clone, Debug)]
490pub struct ReadyTransaction<T> {
491    /// the hash of the submitted transaction
492    hash: TxHash,
493    /// transactions promoted to the ready queue
494    promoted: Vec<TxHash>,
495    /// transaction that failed and became discarded
496    discarded: Vec<TxHash>,
497    /// Transactions removed from the Ready pool
498    removed: Vec<Arc<PoolTransaction<T>>>,
499}
500
501impl<T> ReadyTransaction<T> {
502    pub fn new(hash: TxHash) -> Self {
503        Self {
504            hash,
505            promoted: Default::default(),
506            discarded: Default::default(),
507            removed: Default::default(),
508        }
509    }
510}
511
512#[derive(Clone, Debug)]
513pub enum AddedTransaction<T> {
514    /// transaction was successfully added and being processed
515    Ready(ReadyTransaction<T>),
516    /// Transaction was successfully added but not yet queued for processing
517    Pending {
518        /// the hash of the submitted transaction
519        hash: TxHash,
520    },
521}
522
523impl<T> AddedTransaction<T> {
524    pub const fn hash(&self) -> &TxHash {
525        match self {
526            Self::Ready(tx) => &tx.hash,
527            Self::Pending { hash } => hash,
528        }
529    }
530}