Skip to main content

reth_transaction_pool/pool/
listener.rs

1//! Listeners for the transaction-pool
2
3use crate::{
4    pool::{
5        events::{FullTransactionEvent, NewTransactionEvent, TransactionEvent},
6        QueuedReason,
7    },
8    traits::{NewBlobSidecar, PropagateKind},
9    PoolTransaction, ValidPoolTransaction,
10};
11use alloy_primitives::{
12    map::{hash_map::Entry, B256Map},
13    TxHash, B256,
14};
15use futures_util::Stream;
16use std::{
17    pin::Pin,
18    sync::Arc,
19    task::{Context, Poll},
20};
21use tokio::sync::mpsc::{
22    self as mpsc, error::TrySendError, Receiver, Sender, UnboundedReceiver, UnboundedSender,
23};
24use tracing::debug;
25
26/// The size of the event channel used to propagate transaction events.
27const TX_POOL_EVENT_CHANNEL_SIZE: usize = 1024;
28
29/// A Stream that receives [`TransactionEvent`] only for the transaction with the given hash.
30#[derive(Debug)]
31#[must_use = "streams do nothing unless polled"]
32pub struct TransactionEvents {
33    hash: TxHash,
34    events: UnboundedReceiver<TransactionEvent>,
35}
36
37impl TransactionEvents {
38    /// Create a new instance of this stream.
39    pub const fn new(hash: TxHash, events: UnboundedReceiver<TransactionEvent>) -> Self {
40        Self { hash, events }
41    }
42
43    /// The hash for this transaction
44    pub const fn hash(&self) -> TxHash {
45        self.hash
46    }
47}
48
49impl Stream for TransactionEvents {
50    type Item = TransactionEvent;
51
52    fn poll_next(
53        self: std::pin::Pin<&mut Self>,
54        cx: &mut std::task::Context<'_>,
55    ) -> std::task::Poll<Option<Self::Item>> {
56        self.get_mut().events.poll_recv(cx)
57    }
58}
59
60/// A Stream that receives [`FullTransactionEvent`] for _all_ transaction.
61#[derive(Debug)]
62#[must_use = "streams do nothing unless polled"]
63pub struct AllTransactionsEvents<T: PoolTransaction> {
64    pub(crate) events: Receiver<FullTransactionEvent<T>>,
65}
66
67impl<T: PoolTransaction> AllTransactionsEvents<T> {
68    /// Create a new instance of this stream.
69    pub const fn new(events: Receiver<FullTransactionEvent<T>>) -> Self {
70        Self { events }
71    }
72}
73
74impl<T: PoolTransaction> Stream for AllTransactionsEvents<T> {
75    type Item = FullTransactionEvent<T>;
76
77    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
78        self.get_mut().events.poll_recv(cx)
79    }
80}
81
82/// A type that broadcasts [`TransactionEvent`] to installed listeners.
83///
84/// This is essentially a multi-producer, multi-consumer channel where each event is broadcast to
85/// all active receivers.
86#[derive(Debug)]
87pub struct PoolEventBroadcast<T: PoolTransaction> {
88    /// All listeners for all transaction events.
89    all_events_broadcaster: AllPoolEventsBroadcaster<T>,
90    /// All listeners for events for a certain transaction hash.
91    broadcasters_by_hash: B256Map<PoolEventBroadcaster>,
92}
93
94impl<T: PoolTransaction> Default for PoolEventBroadcast<T> {
95    fn default() -> Self {
96        Self {
97            all_events_broadcaster: AllPoolEventsBroadcaster::default(),
98            broadcasters_by_hash: B256Map::default(),
99        }
100    }
101}
102
103impl<T: PoolTransaction> PoolEventBroadcast<T> {
104    /// Calls the broadcast callback with the `PoolEventBroadcaster` that belongs to the hash.
105    fn broadcast_event(
106        &mut self,
107        hash: &TxHash,
108        event: TransactionEvent,
109        pool_event: FullTransactionEvent<T>,
110    ) {
111        // Broadcast to all listeners for the transaction hash.
112        if let Entry::Occupied(mut sink) = self.broadcasters_by_hash.entry(*hash) {
113            sink.get_mut().broadcast(event.clone());
114
115            if sink.get().is_empty() || event.is_final() {
116                sink.remove();
117            }
118        }
119
120        // Broadcast to all listeners for all transactions.
121        self.all_events_broadcaster.broadcast(pool_event);
122    }
123
124    /// Returns true if no listeners are installed
125    #[inline]
126    pub fn is_empty(&self) -> bool {
127        self.all_events_broadcaster.is_empty() && self.broadcasters_by_hash.is_empty()
128    }
129
130    /// Create a new subscription for the given transaction hash.
131    pub fn subscribe(&mut self, tx_hash: TxHash) -> TransactionEvents {
132        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
133
134        match self.broadcasters_by_hash.entry(tx_hash) {
135            Entry::Occupied(mut entry) => {
136                entry.get_mut().senders.push(tx);
137            }
138            Entry::Vacant(entry) => {
139                entry.insert(PoolEventBroadcaster { senders: vec![tx] });
140            }
141        };
142        TransactionEvents { hash: tx_hash, events: rx }
143    }
144
145    /// Create a new subscription for all transactions.
146    pub fn subscribe_all(&mut self) -> AllTransactionsEvents<T> {
147        let (tx, rx) = tokio::sync::mpsc::channel(TX_POOL_EVENT_CHANNEL_SIZE);
148        self.all_events_broadcaster.senders.push(tx);
149        AllTransactionsEvents::new(rx)
150    }
151
152    /// Notify listeners about a transaction that was added to the pending queue.
153    pub fn pending(&mut self, tx: &TxHash, replaced: Option<Arc<ValidPoolTransaction<T>>>) {
154        self.broadcast_event(tx, TransactionEvent::Pending, FullTransactionEvent::Pending(*tx));
155
156        if let Some(replaced) = replaced {
157            // notify listeners that this transaction was replaced
158            self.replaced(replaced, *tx);
159        }
160    }
161
162    /// Notify listeners about a transaction that was replaced.
163    pub fn replaced(&mut self, tx: Arc<ValidPoolTransaction<T>>, replaced_by: TxHash) {
164        let transaction = Arc::clone(&tx);
165        self.broadcast_event(
166            tx.hash(),
167            TransactionEvent::Replaced(replaced_by),
168            FullTransactionEvent::Replaced { transaction, replaced_by },
169        );
170    }
171
172    /// Notify listeners about a transaction that was added to the queued pool.
173    pub fn queued(&mut self, tx: &TxHash, reason: Option<QueuedReason>) {
174        self.broadcast_event(
175            tx,
176            TransactionEvent::Queued,
177            FullTransactionEvent::Queued(*tx, reason),
178        );
179    }
180
181    /// Notify listeners about a transaction that was propagated.
182    pub fn propagated(&mut self, tx: &TxHash, peers: Vec<PropagateKind>) {
183        let peers = Arc::new(peers);
184        self.broadcast_event(
185            tx,
186            TransactionEvent::Propagated(Arc::clone(&peers)),
187            FullTransactionEvent::Propagated(peers),
188        );
189    }
190
191    /// Notify listeners about all discarded transactions.
192    #[inline]
193    pub fn discarded_many(&mut self, discarded: &[Arc<ValidPoolTransaction<T>>]) {
194        if self.is_empty() {
195            return
196        }
197        for tx in discarded {
198            self.discarded(tx.hash());
199        }
200    }
201
202    /// Notify listeners about a transaction that was discarded.
203    pub fn discarded(&mut self, tx: &TxHash) {
204        self.broadcast_event(tx, TransactionEvent::Discarded, FullTransactionEvent::Discarded(*tx));
205    }
206
207    /// Notify listeners about a transaction that was invalid.
208    pub fn invalid(&mut self, tx: &TxHash) {
209        self.broadcast_event(tx, TransactionEvent::Invalid, FullTransactionEvent::Invalid(*tx));
210    }
211
212    /// Notify listeners that the transaction was mined
213    pub fn mined(&mut self, tx: &TxHash, block_hash: B256) {
214        self.broadcast_event(
215            tx,
216            TransactionEvent::Mined(block_hash),
217            FullTransactionEvent::Mined { tx_hash: *tx, block_hash },
218        );
219    }
220}
221
222/// All Sender half(s) of the event channels for all transactions.
223///
224/// This mimics [`tokio::sync::broadcast`] but uses separate channels.
225#[derive(Debug)]
226struct AllPoolEventsBroadcaster<T: PoolTransaction> {
227    /// Corresponding sender half(s) for event listener channel
228    senders: Vec<Sender<FullTransactionEvent<T>>>,
229}
230
231impl<T: PoolTransaction> Default for AllPoolEventsBroadcaster<T> {
232    fn default() -> Self {
233        Self { senders: Vec::new() }
234    }
235}
236
237impl<T: PoolTransaction> AllPoolEventsBroadcaster<T> {
238    // Broadcast an event to all listeners. Dropped listeners are silently evicted.
239    fn broadcast(&mut self, event: FullTransactionEvent<T>) {
240        self.senders.retain(|sender| match sender.try_send(event.clone()) {
241            Ok(_) | Err(TrySendError::Full(_)) => true,
242            Err(TrySendError::Closed(_)) => false,
243        })
244    }
245
246    /// Returns true if there are no listeners installed.
247    #[inline]
248    const fn is_empty(&self) -> bool {
249        self.senders.is_empty()
250    }
251}
252
253/// All Sender half(s) of the event channels for a specific transaction.
254///
255/// This mimics [`tokio::sync::broadcast`] but uses separate channels and is unbounded.
256#[derive(Default, Debug)]
257struct PoolEventBroadcaster {
258    /// Corresponding sender half(s) for event listener channel
259    senders: Vec<UnboundedSender<TransactionEvent>>,
260}
261
262impl PoolEventBroadcaster {
263    /// Returns `true` if there are no more listeners remaining.
264    const fn is_empty(&self) -> bool {
265        self.senders.is_empty()
266    }
267
268    // Broadcast an event to all listeners. Dropped listeners are silently evicted.
269    fn broadcast(&mut self, event: TransactionEvent) {
270        self.senders.retain(|sender| sender.send(event.clone()).is_ok())
271    }
272}
273
274/// An active listener for new pending transactions.
275#[derive(Debug)]
276pub struct PendingTransactionHashListener {
277    /// The sender of the channel to send transaction hashes to.
278    pub sender: mpsc::Sender<TxHash>,
279    /// Whether to include transactions that should not be propagated over the network.
280    pub kind: TransactionListenerKind,
281}
282
283impl PendingTransactionHashListener {
284    /// Attempts to send all hashes to the listener.
285    ///
286    /// Returns false if the channel is closed (receiver dropped)
287    pub fn send_all(&self, hashes: impl IntoIterator<Item = TxHash>) -> bool {
288        for tx_hash in hashes {
289            match self.sender.try_send(tx_hash) {
290                Ok(()) => {}
291                Err(err) => {
292                    return if matches!(err, mpsc::error::TrySendError::Full(_)) {
293                        debug!(
294                            target: "txpool",
295                            "[{:?}] failed to send pending tx; channel full",
296                            tx_hash,
297                        );
298                        true
299                    } else {
300                        false
301                    }
302                }
303            }
304        }
305        true
306    }
307}
308
309/// An active listener for new pending transactions.
310#[derive(Debug)]
311pub struct TransactionListener<T: PoolTransaction> {
312    /// The sender of the channel to send new transaction events to.
313    pub sender: mpsc::Sender<NewTransactionEvent<T>>,
314    /// Whether to include transactions that should not be propagated over the network.
315    pub kind: TransactionListenerKind,
316}
317
318impl<T: PoolTransaction> TransactionListener<T> {
319    /// Attempts to send the event to the listener.
320    ///
321    /// Returns false if the channel is closed (receiver dropped)
322    pub fn send(&self, event: NewTransactionEvent<T>) -> bool {
323        self.send_all(std::iter::once(event))
324    }
325
326    /// Attempts to send all events to the listener.
327    ///
328    /// Returns false if the channel is closed (receiver dropped)
329    pub fn send_all(&self, events: impl IntoIterator<Item = NewTransactionEvent<T>>) -> bool {
330        for event in events {
331            match self.sender.try_send(event) {
332                Ok(()) => {}
333                Err(err) => {
334                    return if let mpsc::error::TrySendError::Full(event) = err {
335                        debug!(
336                            target: "txpool",
337                            "[{:?}] failed to send pending tx; channel full",
338                            event.transaction.hash(),
339                        );
340                        true
341                    } else {
342                        false
343                    }
344                }
345            }
346        }
347        true
348    }
349}
350
351/// An active listener for new blobs
352#[derive(Debug)]
353pub(crate) struct BlobTransactionSidecarListener {
354    pub(crate) sender: mpsc::Sender<NewBlobSidecar>,
355}
356
357/// Determines what kind of new transactions should be emitted by a stream of transactions.
358///
359/// This gives control whether to include transactions that are allowed to be propagated.
360#[derive(Debug, Copy, Clone, PartialEq, Eq)]
361pub enum TransactionListenerKind {
362    /// Any new pending transactions
363    All,
364    /// Only transactions that are allowed to be propagated.
365    ///
366    /// See also [`ValidPoolTransaction`]
367    PropagateOnly,
368}
369
370impl TransactionListenerKind {
371    /// Returns true if we're only interested in transactions that are allowed to be propagated.
372    #[inline]
373    pub const fn is_propagate_only(&self) -> bool {
374        matches!(self, Self::PropagateOnly)
375    }
376}