Skip to main content

zebrad/components/
mempool.rs

1//! Zebra mempool.
2//!
3//! A zebrad application component that manages the active collection, reception,
4//! gossip, verification, in-memory storage, eviction, and rejection of unmined Zcash
5//! transactions (those that have not been confirmed in a mined block on the
6//! blockchain).
7//!
8//! Major parts of the mempool include:
9//!  * [Mempool Service][`Mempool`]
10//!    * activates when the syncer is near the chain tip
11//!    * spawns [download and verify tasks][`downloads::Downloads`] for each crawled or gossiped transaction
12//!    * handles in-memory [storage][`storage::Storage`] of unmined transactions
13//!  * [Crawler][`crawler::Crawler`]
14//!    * runs in the background to periodically poll peers for fresh unmined transactions
15//!  * [Queue Checker][`queue_checker::QueueChecker`]
16//!    * runs in the background, polling the mempool to store newly verified transactions
17//!  * [Transaction Gossip Task][`gossip::gossip_mempool_transaction_id`]
18//!    * runs in the background and gossips newly added mempool transactions
19//!      to peers
20
21use std::{
22    collections::HashSet,
23    future::Future,
24    iter,
25    pin::{pin, Pin},
26    task::{Context, Poll},
27};
28
29use futures::{future::FutureExt, stream::Stream};
30use tokio::sync::{broadcast, mpsc, oneshot};
31use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service};
32
33use zebra_chain::{
34    block::{self, Height},
35    chain_sync_status::ChainSyncStatus,
36    chain_tip::ChainTip,
37    transaction::UnminedTxId,
38};
39use zebra_consensus::{error::TransactionError, transaction};
40use zebra_network::{self as zn, PeerSocketAddr};
41use zebra_node_services::mempool::{
42    CreatedOrSpent, Gossip, MempoolChange, MempoolTxSubscriber, Request, Response,
43};
44use zebra_state as zs;
45use zebra_state::{ChainTipChange, TipAction};
46
47use crate::components::sync::SyncStatus;
48
49pub mod config;
50mod crawler;
51pub mod downloads;
52mod error;
53pub mod gossip;
54mod pending_outputs;
55mod queue_checker;
56mod storage;
57
58#[cfg(test)]
59mod tests;
60
61pub use crate::BoxError;
62
63pub use config::Config;
64pub use crawler::Crawler;
65pub use error::MempoolError;
66pub use gossip::gossip_mempool_transaction_id;
67pub use queue_checker::QueueChecker;
68pub use storage::{
69    ExactTipRejectionError, SameEffectsChainRejectionError, SameEffectsTipRejectionError, Storage,
70};
71
72#[cfg(test)]
73pub use self::tests::UnboxMempoolError;
74
75use downloads::{
76    Downloads as TxDownloads, TransactionDownloadVerifyError, TRANSACTION_DOWNLOAD_TIMEOUT,
77    TRANSACTION_VERIFY_TIMEOUT,
78};
79
80type Outbound = Buffer<BoxService<zn::Request, zn::Response, zn::BoxError>, zn::Request>;
81type State = Buffer<BoxService<zs::Request, zs::Response, zs::BoxError>, zs::Request>;
82type TxVerifier = Buffer<
83    BoxService<transaction::Request, transaction::Response, TransactionError>,
84    transaction::Request,
85>;
86type InboundTxDownloads = TxDownloads<Timeout<Outbound>, Timeout<TxVerifier>, State>;
87
88/// The state of the mempool.
89///
90/// Indicates whether it is enabled or disabled and, if enabled, contains
91/// the necessary data to run it.
92//
93// Zebra only has one mempool, so the enum variant size difference doesn't matter.
94#[allow(clippy::large_enum_variant)]
95#[derive(Default)]
96enum ActiveState {
97    /// The Mempool is disabled.
98    #[default]
99    Disabled,
100
101    /// The Mempool is enabled.
102    Enabled {
103        /// The Mempool storage itself.
104        ///
105        /// # Correctness
106        ///
107        /// Only components internal to the [`Mempool`] struct are allowed to
108        /// inject transactions into `storage`, as transactions must be verified beforehand.
109        storage: Storage,
110
111        /// The transaction download and verify stream.
112        tx_downloads: Pin<Box<InboundTxDownloads>>,
113
114        /// Last seen chain tip hash that mempool transactions have been verified against.
115        ///
116        /// In some tests, this is initialized to the latest chain tip, then updated in `poll_ready()` before each request.
117        last_seen_tip_hash: block::Hash,
118    },
119}
120
121impl ActiveState {
122    /// Returns the current state, leaving [`Self::Disabled`] in its place.
123    fn take(&mut self) -> Self {
124        std::mem::take(self)
125    }
126
127    /// Returns a list of requests that will retry every stored and pending transaction.
128    fn transaction_retry_requests(&self) -> Vec<Gossip> {
129        match self {
130            ActiveState::Disabled => Vec::new(),
131            ActiveState::Enabled {
132                storage,
133                tx_downloads,
134                ..
135            } => {
136                let mut transactions = Vec::new();
137
138                let storage = storage
139                    .transactions()
140                    .values()
141                    .map(|tx| tx.transaction.clone().into());
142                transactions.extend(storage);
143
144                let pending = tx_downloads.transaction_requests().cloned();
145                transactions.extend(pending);
146
147                transactions
148            }
149        }
150    }
151
152    /// Returns the number of pending transactions waiting for download or verify,
153    /// or zero if the mempool is disabled.
154    #[cfg(feature = "progress-bar")]
155    fn queued_transaction_count(&self) -> usize {
156        match self {
157            ActiveState::Disabled => 0,
158            ActiveState::Enabled { tx_downloads, .. } => tx_downloads.in_flight(),
159        }
160    }
161
162    /// Returns the number of transactions in storage, or zero if the mempool is disabled.
163    #[cfg(feature = "progress-bar")]
164    fn transaction_count(&self) -> usize {
165        match self {
166            ActiveState::Disabled => 0,
167            ActiveState::Enabled { storage, .. } => storage.transaction_count(),
168        }
169    }
170
171    /// Returns the cost of the transactions in the mempool, according to ZIP-401.
172    /// Returns zero if the mempool is disabled.
173    #[cfg(feature = "progress-bar")]
174    fn total_cost(&self) -> u64 {
175        match self {
176            ActiveState::Disabled => 0,
177            ActiveState::Enabled { storage, .. } => storage.total_cost(),
178        }
179    }
180
181    /// Returns the total serialized size of the verified transactions in the set,
182    /// or zero if the mempool is disabled.
183    ///
184    /// See [`Storage::total_serialized_size()`] for details.
185    #[cfg(feature = "progress-bar")]
186    pub fn total_serialized_size(&self) -> usize {
187        match self {
188            ActiveState::Disabled => 0,
189            ActiveState::Enabled { storage, .. } => storage.total_serialized_size(),
190        }
191    }
192
193    /// Returns the number of rejected transaction hashes in storage,
194    /// or zero if the mempool is disabled.
195    #[cfg(feature = "progress-bar")]
196    fn rejected_transaction_count(&mut self) -> usize {
197        match self {
198            ActiveState::Disabled => 0,
199            ActiveState::Enabled { storage, .. } => storage.rejected_transaction_count(),
200        }
201    }
202}
203
204/// Mempool async management and query service.
205///
206/// The mempool is the set of all verified transactions that this node is aware
207/// of that have yet to be confirmed by the Zcash network. A transaction is
208/// confirmed when it has been included in a block ('mined').
209pub struct Mempool {
210    /// The configurable options for the mempool, persisted between states.
211    config: Config,
212
213    /// The state of the mempool.
214    active_state: ActiveState,
215
216    /// Allows checking if we are near the tip to enable/disable the mempool.
217    sync_status: SyncStatus,
218
219    /// If the state's best chain tip has reached this height, always enable the mempool.
220    debug_enable_at_height: Option<Height>,
221
222    /// Allows efficient access to the best tip of the blockchain.
223    latest_chain_tip: zs::LatestChainTip,
224
225    /// Allows the detection of newly added chain tip blocks,
226    /// and chain tip resets.
227    chain_tip_change: ChainTipChange,
228
229    /// Handle to the outbound service.
230    /// Used to construct the transaction downloader.
231    outbound: Outbound,
232
233    /// Handle to the state service.
234    /// Used to construct the transaction downloader.
235    state: State,
236
237    /// Handle to the transaction verifier service.
238    /// Used to construct the transaction downloader.
239    tx_verifier: TxVerifier,
240
241    /// Sender part of a gossip transactions channel.
242    /// Used to broadcast transaction ids to peers.
243    transaction_sender: broadcast::Sender<MempoolChange>,
244
245    /// Sender for reporting peer addresses that advertised unexpectedly invalid transactions.
246    misbehavior_sender: mpsc::Sender<(PeerSocketAddr, u32)>,
247
248    // Diagnostics
249    //
250    /// Queued transactions pending download or verification transmitter.
251    /// Only displayed after the mempool's first activation.
252    #[cfg(feature = "progress-bar")]
253    queued_count_bar: Option<howudoin::Tx>,
254
255    /// Number of mempool transactions transmitter.
256    /// Only displayed after the mempool's first activation.
257    #[cfg(feature = "progress-bar")]
258    transaction_count_bar: Option<howudoin::Tx>,
259
260    /// Mempool transaction cost transmitter.
261    /// Only displayed after the mempool's first activation.
262    #[cfg(feature = "progress-bar")]
263    transaction_cost_bar: Option<howudoin::Tx>,
264
265    /// Rejected transactions transmitter.
266    /// Only displayed after the mempool's first activation.
267    #[cfg(feature = "progress-bar")]
268    rejected_count_bar: Option<howudoin::Tx>,
269}
270
271impl Mempool {
272    #[allow(clippy::too_many_arguments)]
273    pub(crate) fn new(
274        config: &Config,
275        outbound: Outbound,
276        state: State,
277        tx_verifier: TxVerifier,
278        sync_status: SyncStatus,
279        latest_chain_tip: zs::LatestChainTip,
280        chain_tip_change: ChainTipChange,
281        misbehavior_sender: mpsc::Sender<(PeerSocketAddr, u32)>,
282    ) -> (Self, MempoolTxSubscriber) {
283        let (transaction_sender, _) =
284            tokio::sync::broadcast::channel(gossip::MAX_CHANGES_BEFORE_SEND * 2);
285        let transaction_subscriber = MempoolTxSubscriber::new(transaction_sender.clone());
286
287        let mut service = Mempool {
288            config: config.clone(),
289            active_state: ActiveState::Disabled,
290            sync_status,
291            debug_enable_at_height: config.debug_enable_at_height.map(Height),
292            latest_chain_tip,
293            chain_tip_change,
294            outbound,
295            state,
296            tx_verifier,
297            transaction_sender,
298            misbehavior_sender,
299            #[cfg(feature = "progress-bar")]
300            queued_count_bar: None,
301            #[cfg(feature = "progress-bar")]
302            transaction_count_bar: None,
303            #[cfg(feature = "progress-bar")]
304            transaction_cost_bar: None,
305            #[cfg(feature = "progress-bar")]
306            rejected_count_bar: None,
307        };
308
309        // Make sure `is_enabled` is accurate.
310        // Otherwise, it is only updated in `poll_ready`, right before each service call.
311        let is_caught_up_to_start = service.is_caught_up_to_start();
312        service.update_state(None, is_caught_up_to_start);
313
314        (service, transaction_subscriber)
315    }
316
317    /// Is the mempool enabled by a debug config option?
318    fn is_enabled_by_debug(&self) -> bool {
319        let mut is_debug_enabled = false;
320
321        // optimise non-debug performance
322        if self.debug_enable_at_height.is_none() {
323            return is_debug_enabled;
324        }
325
326        let enable_at_height = self
327            .debug_enable_at_height
328            .expect("unexpected debug_enable_at_height: just checked for None");
329
330        if let Some(best_tip_height) = self.latest_chain_tip.best_tip_height() {
331            is_debug_enabled = best_tip_height >= enable_at_height;
332
333            if is_debug_enabled && !self.is_enabled() {
334                info!(
335                    ?best_tip_height,
336                    ?enable_at_height,
337                    "enabling mempool for debugging"
338                );
339            }
340        }
341
342        is_debug_enabled
343    }
344
345    /// Returns `true` if Zebra is caught up enough to start the mempool.
346    fn is_caught_up_to_start(&self) -> bool {
347        self.sync_status.is_close_to_tip() || self.is_enabled_by_debug()
348    }
349
350    /// Replaces the active state with a freshly-initialised [`ActiveState::Enabled`],
351    /// using `tip_action`'s best tip hash as the `last_seen_tip_hash`.
352    fn enable_at_tip(&mut self, tip_action: &TipAction) {
353        let (last_seen_tip_hash, _) = tip_action.best_tip_hash_and_height();
354
355        let tx_downloads = Box::pin(TxDownloads::new(
356            Timeout::new(self.outbound.clone(), TRANSACTION_DOWNLOAD_TIMEOUT),
357            Timeout::new(self.tx_verifier.clone(), TRANSACTION_VERIFY_TIMEOUT),
358            self.state.clone(),
359        ));
360        self.active_state = ActiveState::Enabled {
361            storage: storage::Storage::new(&self.config),
362            tx_downloads,
363            last_seen_tip_hash,
364        };
365    }
366
367    /// Activate the mempool once Zebra is close enough to the tip.
368    ///
369    /// Sync status only gates initial activation. Once the mempool is active,
370    /// this method does not disable it.
371    ///
372    /// Accepts an optional [`TipAction`] for setting the `last_seen_tip_hash`
373    /// field when enabling the mempool state. It will not enable the mempool if
374    /// this is [`None`]. `is_caught_up_to_start` is supplied by the caller, which
375    /// already computes it, to avoid evaluating the sync-status predicate twice.
376    ///
377    /// Returns `true` if the state changed.
378    fn update_state(
379        &mut self,
380        tip_action: Option<&TipAction>,
381        is_caught_up_to_start: bool,
382    ) -> bool {
383        // TODO: revisit these state transitions when sync status can prove
384        // whether Zebra is behind the network tip.
385        match (is_caught_up_to_start, self.is_enabled(), tip_action) {
386            // the active state is up to date, or there is no tip action to activate the mempool
387            (false, false, _) | (true, true, _) | (true, false, None) => return false,
388
389            // Enable state - there should be a chain tip when Zebra is close to the network tip
390            (true, false, Some(tip_action)) => {
391                info!(
392                    tip_height = ?tip_action.best_tip_height(),
393                    "activating mempool: Zebra is close to the tip"
394                );
395
396                self.enable_at_tip(tip_action);
397            }
398
399            // TODO: only disable an already-active mempool when validated sync
400            // state proves Zebra is behind a higher-work chain that follows
401            // this node's consensus rules.
402            //
403            // The sync status can be triggered by lower-work forks,
404            // stale peers, or peers on incompatible consensus rules, so
405            // it is strong enough to delay initial activation but not to shut
406            // down a working mempool.
407            (false, true, _) => {
408                return false;
409            }
410        };
411
412        true
413    }
414
415    /// Return whether the mempool is enabled or not.
416    pub fn is_enabled(&self) -> bool {
417        match self.active_state {
418            ActiveState::Disabled => false,
419            ActiveState::Enabled { .. } => true,
420        }
421    }
422
423    /// Remove expired transaction ids from a given list of inserted ones.
424    fn remove_expired_from_peer_list(
425        send_to_peers_ids: &HashSet<UnminedTxId>,
426        expired_transactions: &HashSet<UnminedTxId>,
427    ) -> HashSet<UnminedTxId> {
428        send_to_peers_ids
429            .iter()
430            .filter(|id| !expired_transactions.contains(id))
431            .copied()
432            .collect()
433    }
434
435    /// Update metrics for the mempool.
436    fn update_metrics(&mut self) {
437        // Shutdown if needed
438        #[cfg(feature = "progress-bar")]
439        if matches!(howudoin::cancelled(), Some(true)) {
440            self.disable_metrics();
441            return;
442        }
443
444        // Initialize if just activated
445        #[cfg(feature = "progress-bar")]
446        if self.is_enabled()
447            && (self.queued_count_bar.is_none()
448                || self.transaction_count_bar.is_none()
449                || self.transaction_cost_bar.is_none()
450                || self.rejected_count_bar.is_none())
451        {
452            let _max_transaction_count = self.config.tx_cost_limit
453                / zebra_chain::transaction::MEMPOOL_TRANSACTION_COST_THRESHOLD;
454
455            let transaction_count_bar = *howudoin::new_root()
456                .label("Mempool Transactions")
457                .set_pos(0u64);
458            // .set_len(max_transaction_count);
459
460            let transaction_cost_bar = howudoin::new_with_parent(transaction_count_bar.id())
461                .label("Mempool Cost")
462                .set_pos(0u64)
463                // .set_len(self.config.tx_cost_limit)
464                .fmt_as_bytes(true);
465
466            let queued_count_bar = *howudoin::new_with_parent(transaction_cost_bar.id())
467                .label("Mempool Queue")
468                .set_pos(0u64);
469            // .set_len(
470            //     u64::try_from(downloads::MAX_INBOUND_CONCURRENCY).expect("fits in u64"),
471            // );
472
473            let rejected_count_bar = *howudoin::new_with_parent(queued_count_bar.id())
474                .label("Mempool Rejects")
475                .set_pos(0u64);
476            // .set_len(
477            //     u64::try_from(storage::MAX_EVICTION_MEMORY_ENTRIES).expect("fits in u64"),
478            // );
479
480            self.transaction_count_bar = Some(transaction_count_bar);
481            self.transaction_cost_bar = Some(transaction_cost_bar);
482            self.queued_count_bar = Some(queued_count_bar);
483            self.rejected_count_bar = Some(rejected_count_bar);
484        }
485
486        // Update if the mempool has ever been active
487        #[cfg(feature = "progress-bar")]
488        if let (
489            Some(queued_count_bar),
490            Some(transaction_count_bar),
491            Some(transaction_cost_bar),
492            Some(rejected_count_bar),
493        ) = (
494            self.queued_count_bar,
495            self.transaction_count_bar,
496            self.transaction_cost_bar,
497            self.rejected_count_bar,
498        ) {
499            let queued_count = self.active_state.queued_transaction_count();
500            let transaction_count = self.active_state.transaction_count();
501
502            let transaction_cost = self.active_state.total_cost();
503            let transaction_size = self.active_state.total_serialized_size();
504            let transaction_size =
505                indicatif::HumanBytes(transaction_size.try_into().expect("fits in u64"));
506
507            let rejected_count = self.active_state.rejected_transaction_count();
508
509            queued_count_bar.set_pos(u64::try_from(queued_count).expect("fits in u64"));
510
511            transaction_count_bar.set_pos(u64::try_from(transaction_count).expect("fits in u64"));
512
513            // Display the cost and cost limit, with the actual size as a description.
514            //
515            // Costs can be much higher than the transaction size due to the
516            // MEMPOOL_TRANSACTION_COST_THRESHOLD minimum cost.
517            transaction_cost_bar
518                .set_pos(transaction_cost)
519                .desc(format!("Actual size {transaction_size}"));
520
521            rejected_count_bar.set_pos(u64::try_from(rejected_count).expect("fits in u64"));
522        }
523    }
524
525    /// Disable metrics for the mempool.
526    fn disable_metrics(&self) {
527        #[cfg(feature = "progress-bar")]
528        {
529            if let Some(bar) = self.queued_count_bar {
530                bar.close()
531            }
532            if let Some(bar) = self.transaction_count_bar {
533                bar.close()
534            }
535            if let Some(bar) = self.transaction_cost_bar {
536                bar.close()
537            }
538            if let Some(bar) = self.rejected_count_bar {
539                bar.close()
540            }
541        }
542    }
543}
544
545impl Service<Request> for Mempool {
546    type Response = Response;
547    type Error = BoxError;
548    type Future =
549        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
550
551    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
552        let is_caught_up_to_start = self.is_caught_up_to_start();
553        let should_check_tip = self.is_enabled() || is_caught_up_to_start;
554        let tip_action = should_check_tip
555            .then(|| self.chain_tip_change.last_tip_change())
556            .flatten();
557
558        // TODO: Consider broadcasting a `MempoolChange` when the mempool is disabled.
559        let is_state_changed = self.update_state(tip_action.as_ref(), is_caught_up_to_start);
560
561        tracing::trace!(is_enabled = ?self.is_enabled(), ?is_state_changed, "started polling the mempool...");
562
563        // When the mempool is disabled we still return that the service is ready.
564        // Otherwise, callers could block waiting for the mempool to be enabled.
565        if !self.is_enabled() {
566            self.update_metrics();
567
568            return Poll::Ready(Ok(()));
569        }
570
571        // Clear the mempool and cancel downloads if there has been a chain tip reset.
572        //
573        // But if the mempool was just freshly enabled,
574        // skip resetting and removing mined transactions for this tip.
575        let reset_tip_action = match tip_action.as_ref() {
576            Some(reset_tip_action @ TipAction::Reset { .. }) if !is_state_changed => {
577                Some(reset_tip_action)
578            }
579            _ => None,
580        };
581
582        if let Some(reset_tip_action) = reset_tip_action {
583            info!(
584                tip_height = ?reset_tip_action.best_tip_height(),
585                "resetting mempool: switched best chain, skipped blocks, or activated network upgrade"
586            );
587
588            let previous_state = self.active_state.take();
589            let tx_retries = previous_state.transaction_retry_requests();
590
591            // Use the same code for dropping and resetting the mempool,
592            // to avoid subtle bugs.
593            //
594            // Drop the current contents of the state,
595            // cancelling any pending download tasks,
596            // and dropping completed verification results.
597            std::mem::drop(previous_state);
598
599            // Re-initialise an empty state.
600            //
601            // This deliberately bypasses the initial-activation gate in `update_state()`:
602            // the mempool was already active when the reset arrived, and a
603            // far-from-tip sync status must not disable an already-active mempool
604            // (it can be triggered by lower-work forks, stale peers, or peers on
605            // incompatible consensus rules).
606            self.enable_at_tip(reset_tip_action);
607
608            // Re-verify the transactions that were pending or valid at the previous tip.
609            // This saves us the time and data needed to re-download them.
610            if let ActiveState::Enabled { tx_downloads, .. } = &mut self.active_state {
611                info!(
612                    transactions = tx_retries.len(),
613                    "re-verifying mempool transactions after a chain fork"
614                );
615
616                for tx in tx_retries {
617                    // This is just an efficiency optimisation, so we don't care if queueing
618                    // transaction requests fails.
619                    let _result = tx_downloads.download_if_needed_and_verify(tx, None, None);
620                }
621            }
622
623            self.update_metrics();
624
625            return Poll::Ready(Ok(()));
626        }
627
628        if let ActiveState::Enabled {
629            storage,
630            tx_downloads,
631            last_seen_tip_hash,
632        } = &mut self.active_state
633        {
634            // Collect inserted transaction ids.
635            let mut send_to_peers_ids = HashSet::<_>::new();
636            let mut invalidated_ids = HashSet::<_>::new();
637            let mut mined_mempool_ids = HashSet::<_>::new();
638
639            let best_tip_height = self.latest_chain_tip.best_tip_height();
640
641            // Clean up completed download tasks and add to mempool if successful.
642            while let Poll::Ready(Some(result)) = pin!(&mut *tx_downloads).poll_next(cx) {
643                match result {
644                    Ok(Ok((tx, spent_mempool_outpoints, expected_tip_height, rsp_tx))) => {
645                        // # Correctness:
646                        //
647                        // It's okay to use tip height here instead of the tip hash since
648                        // chain_tip_change.last_tip_change() returns a `TipAction::Reset` when
649                        // the best chain changes (which is the only way to stay at the same height), and the
650                        // mempool re-verifies all pending tx_downloads when there's a `TipAction::Reset`.
651                        if best_tip_height == expected_tip_height {
652                            let tx_id = tx.transaction.id;
653                            let insert_result =
654                                storage.insert(tx, spent_mempool_outpoints, best_tip_height);
655
656                            tracing::trace!(
657                                ?insert_result,
658                                "got Ok(_) transaction verify, tried to store",
659                            );
660
661                            if let Ok(inserted_id) = insert_result {
662                                // Save transaction ids that we will send to peers
663                                send_to_peers_ids.insert(inserted_id);
664                            } else {
665                                invalidated_ids.insert(tx_id);
666                            }
667
668                            // Send the result to responder channel if one was provided.
669                            if let Some(rsp_tx) = rsp_tx {
670                                let _ = rsp_tx
671                                    .send(insert_result.map(|_| ()).map_err(|err| err.into()));
672                            }
673                        } else {
674                            tracing::trace!("chain grew during tx verification, retrying ..",);
675
676                            // We don't care if re-queueing the transaction request fails.
677                            let _result = tx_downloads.download_if_needed_and_verify(
678                                tx.transaction.into(),
679                                None,
680                                rsp_tx,
681                            );
682                        }
683                    }
684                    Ok(Err(boxed_err)) => {
685                        let (tx_id, error) = *boxed_err;
686                        if let TransactionDownloadVerifyError::Invalid {
687                            error,
688                            advertiser_addr: Some(advertiser_addr),
689                        } = &error
690                        {
691                            if error.mempool_misbehavior_score() != 0 {
692                                let _ = self.misbehavior_sender.try_send((
693                                    *advertiser_addr,
694                                    error.mempool_misbehavior_score(),
695                                ));
696                            }
697                        };
698
699                        tracing::debug!(?tx_id, ?error, "mempool transaction failed to verify");
700
701                        metrics::counter!("mempool.failed.verify.tasks.total", "reason" => error.to_string()).increment(1);
702
703                        invalidated_ids.insert(tx_id);
704                        storage.reject_if_needed(tx_id, error);
705                    }
706                    Err((tx_id, _elapsed)) => {
707                        tracing::info!(
708                            ?tx_id,
709                            "mempool transaction failed to verify due to timeout"
710                        );
711
712                        invalidated_ids.insert(tx_id);
713
714                        metrics::counter!("mempool.failed.verify.tasks.total", "reason" => "timeout").increment(1);
715                    }
716                };
717            }
718
719            // Handle best chain tip changes
720            if let Some(TipAction::Grow { block }) = tip_action {
721                tracing::trace!(block_height = ?block.height, "handling blocks added to tip");
722                *last_seen_tip_hash = block.hash;
723
724                // Cancel downloads/verifications/storage of transactions
725                // with the same mined IDs as recently mined transactions.
726                let mined_ids = block.transaction_hashes.iter().cloned().collect();
727                tx_downloads.cancel(&mined_ids);
728                storage.clear_mined_dependencies(&mined_ids);
729
730                let storage::RemovedTransactionIds { mined, invalidated } =
731                    storage.reject_and_remove_same_effects(&mined_ids, block.transactions);
732
733                // Clear any transaction rejections if they might have become valid after
734                // the new block was added to the tip.
735                storage.clear_tip_rejections();
736
737                mined_mempool_ids.extend(mined);
738                invalidated_ids.extend(invalidated);
739            }
740
741            // Remove expired transactions from the mempool.
742            //
743            // Lock times never expire, because block times are strictly increasing.
744            // So we don't need to check them here.
745            if let Some(tip_height) = best_tip_height {
746                let expired_transactions = storage.remove_expired_transactions(tip_height);
747                // Remove transactions that are expired from the peers list
748                send_to_peers_ids =
749                    Self::remove_expired_from_peer_list(&send_to_peers_ids, &expired_transactions);
750
751                if !expired_transactions.is_empty() {
752                    tracing::debug!(
753                        ?expired_transactions,
754                        "removed expired transactions from the mempool",
755                    );
756
757                    invalidated_ids.extend(expired_transactions);
758                }
759            }
760
761            // Send transactions that were not rejected nor expired to peers and RPC listeners.
762            if !send_to_peers_ids.is_empty() {
763                tracing::trace!(
764                    ?send_to_peers_ids,
765                    "sending new transactions to peers and RPC listeners"
766                );
767
768                self.transaction_sender
769                    .send(MempoolChange::added(send_to_peers_ids))?;
770            }
771
772            // Send transactions that were rejected to RPC listeners.
773            if !invalidated_ids.is_empty() {
774                tracing::trace!(
775                    ?invalidated_ids,
776                    "sending invalidated transactions to RPC listeners"
777                );
778
779                self.transaction_sender
780                    .send(MempoolChange::invalidated(invalidated_ids))?;
781            }
782
783            // Send transactions that were mined onto the best chain to RPC listeners.
784            if !mined_mempool_ids.is_empty() {
785                tracing::trace!(
786                    ?mined_mempool_ids,
787                    "sending mined transactions to RPC listeners"
788                );
789
790                self.transaction_sender
791                    .send(MempoolChange::mined(mined_mempool_ids))?;
792            }
793        }
794
795        self.update_metrics();
796
797        Poll::Ready(Ok(()))
798    }
799
800    /// Call the mempool service.
801    ///
802    /// Errors indicate that the peer has done something wrong or unexpected,
803    /// and will cause callers to disconnect from the remote peer.
804    #[instrument(name = "mempool", skip(self, req))]
805    fn call(&mut self, req: Request) -> Self::Future {
806        match &mut self.active_state {
807            ActiveState::Enabled {
808                storage,
809                tx_downloads,
810                last_seen_tip_hash,
811            } => match req {
812                // Queries
813                Request::TransactionIds => {
814                    trace!(?req, "got mempool request");
815
816                    let res: HashSet<_> = storage.tx_ids().collect();
817
818                    trace!(?req, res_count = ?res.len(), "answered mempool request");
819
820                    async move { Ok(Response::TransactionIds(res)) }.boxed()
821                }
822
823                Request::TransactionsById(ref ids) => {
824                    trace!(?req, "got mempool request");
825
826                    let res: Vec<_> = storage.transactions_exact(ids.clone()).cloned().collect();
827
828                    trace!(?req, res_count = ?res.len(), "answered mempool request");
829
830                    async move { Ok(Response::Transactions(res)) }.boxed()
831                }
832                Request::TransactionsByMinedId(ref ids) => {
833                    trace!(?req, "got mempool request");
834
835                    let res: Vec<_> = storage
836                        .transactions_same_effects(ids.clone())
837                        .cloned()
838                        .collect();
839
840                    trace!(?req, res_count = ?res.len(), "answered mempool request");
841
842                    async move { Ok(Response::Transactions(res)) }.boxed()
843                }
844                Request::TransactionWithDepsByMinedId(tx_id) => {
845                    trace!(?req, "got mempool request");
846
847                    let res = if let Some((transaction, dependencies)) =
848                        storage.transaction_with_deps(tx_id)
849                    {
850                        Ok(Response::TransactionWithDeps {
851                            transaction,
852                            dependencies,
853                        })
854                    } else {
855                        Err("transaction not found in mempool".into())
856                    };
857
858                    trace!(?req, ?res, "answered mempool request");
859
860                    async move { res }.boxed()
861                }
862
863                Request::AwaitOutput(outpoint) => {
864                    trace!(?req, "got mempool request");
865
866                    let response_fut = storage.pending_outputs.queue(outpoint);
867
868                    if let Some(output) = storage.created_output(&outpoint) {
869                        storage.pending_outputs.respond(&outpoint, output)
870                    }
871
872                    trace!("answered mempool request");
873
874                    response_fut.boxed()
875                }
876
877                Request::FullTransactions => {
878                    trace!(?req, "got mempool request");
879
880                    let transactions: Vec<_> = storage.transactions().values().cloned().collect();
881                    let transaction_dependencies = storage.transaction_dependencies().clone();
882
883                    trace!(?req, transactions_count = ?transactions.len(), "answered mempool request");
884
885                    let response = Response::FullTransactions {
886                        transactions,
887                        transaction_dependencies,
888                        last_seen_tip_hash: *last_seen_tip_hash,
889                    };
890
891                    async move { Ok(response) }.boxed()
892                }
893
894                Request::RejectedTransactionIds(ref ids) => {
895                    trace!(?req, "got mempool request");
896
897                    let res = storage.rejected_transactions(ids.clone()).collect();
898
899                    trace!(?req, ?res, "answered mempool request");
900
901                    async move { Ok(Response::RejectedTransactionIds(res)) }.boxed()
902                }
903
904                // Queue mempool candidates
905                Request::Queue(gossiped_txs) => {
906                    trace!(req_count = ?gossiped_txs.len(), "got mempool Queue request");
907
908                    let rsp: Vec<Result<oneshot::Receiver<Result<(), BoxError>>, BoxError>> =
909                        gossiped_txs
910                            .into_iter()
911                            .map(
912                                |gossiped_tx| -> Result<
913                                    oneshot::Receiver<Result<(), BoxError>>,
914                                    MempoolError,
915                                > {
916                                    let (rsp_tx, rsp_rx) = oneshot::channel();
917                                    storage.should_download_or_verify(gossiped_tx.id())?;
918                                    tx_downloads.download_if_needed_and_verify(
919                                        gossiped_tx,
920                                        None,
921                                        Some(rsp_tx),
922                                    )?;
923
924                                    Ok(rsp_rx)
925                                },
926                            )
927                            .map(|result| result.map_err(BoxError::from))
928                            .collect();
929
930                    // We've added transactions to the queue
931                    self.update_metrics();
932
933                    async move { Ok(Response::Queued(rsp)) }.boxed()
934                }
935
936                // Queue candidates received from a specific peer (advertised IDs
937                // or a directly pushed transaction). Per-peer accounting is
938                // enforced inside the downloader.
939                Request::QueueFromPeer { candidates, source } => {
940                    trace!(req_count = ?candidates.len(), ?source, "got mempool QueueFromPeer request");
941
942                    for candidate in candidates {
943                        if storage.should_download_or_verify(candidate.id()).is_err() {
944                            continue;
945                        }
946                        let _ = tx_downloads.download_if_needed_and_verify(
947                            candidate,
948                            Some(source),
949                            None,
950                        );
951                    }
952
953                    self.update_metrics();
954
955                    async move { Ok(Response::Queued(Vec::new())) }.boxed()
956                }
957
958                // Store successfully downloaded and verified transactions in the mempool
959                Request::CheckForVerifiedTransactions => {
960                    trace!(?req, "got mempool request");
961
962                    // all the work for this request is done in poll_ready
963                    async move { Ok(Response::CheckedForVerifiedTransactions) }.boxed()
964                }
965
966                // Summary statistics for the mempool: count, total size, and memory usage.
967                //
968                // Used by the `getmempoolinfo` RPC method
969                Request::QueueStats => {
970                    trace!(?req, "got mempool request");
971
972                    let size = storage.transaction_count();
973
974                    let bytes = storage.total_serialized_size();
975
976                    let usage = bytes; // TODO: Placeholder, should be fixed later
977
978                    // TODO: Set to Some(true) on regtest once network info is available.
979                    let fully_notified = None;
980
981                    trace!(size, bytes, usage, "answered mempool request");
982
983                    async move {
984                        Ok(Response::QueueStats {
985                            size,
986                            bytes,
987                            usage,
988                            fully_notified,
989                        })
990                    }
991                    .boxed()
992                }
993                Request::UnspentOutput(outpoint) => {
994                    trace!(?req, "got mempool request");
995
996                    if storage.has_spent_outpoint(&outpoint) {
997                        trace!(?req, "answered mempool request");
998
999                        return async move {
1000                            Ok(Response::TransparentOutput(Some(CreatedOrSpent::Spent)))
1001                        }
1002                        .boxed();
1003                    }
1004
1005                    if let Some((tx_version, output)) = storage
1006                        .transactions()
1007                        .get(&outpoint.hash)
1008                        .map(|tx| tx.transaction.transaction.clone())
1009                        .and_then(|tx| {
1010                            tx.outputs()
1011                                .get(outpoint.index as usize)
1012                                .map(|output| (tx.version(), output.clone()))
1013                        })
1014                    {
1015                        trace!(?req, "answered mempool request");
1016
1017                        let last_seen_hash = *last_seen_tip_hash;
1018                        return async move {
1019                            Ok(Response::TransparentOutput(Some(CreatedOrSpent::Created {
1020                                output,
1021                                tx_version,
1022                                last_seen_hash,
1023                            })))
1024                        }
1025                        .boxed();
1026                    }
1027
1028                    trace!(?req, "answered mempool request");
1029
1030                    async move { Ok(Response::TransparentOutput(None)) }.boxed()
1031                }
1032            },
1033            ActiveState::Disabled => {
1034                // TODO: add the name of the request, but not the content,
1035                //       like the command() or Display impls of network requests
1036                trace!("got mempool request while mempool is disabled");
1037
1038                // We can't return an error since that will cause a disconnection
1039                // by the peer connection handler. Therefore, return successful
1040                // empty responses.
1041
1042                let resp = match req {
1043                    // Return empty responses for queries.
1044                    Request::TransactionIds => Response::TransactionIds(Default::default()),
1045
1046                    Request::TransactionsById(_) => Response::Transactions(Default::default()),
1047                    Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
1048                    Request::TransactionWithDepsByMinedId(_)
1049                    | Request::AwaitOutput(_)
1050                    | Request::UnspentOutput(_) => {
1051                        return async move {
1052                            Err("mempool is not active: wait for Zebra to sync to the tip".into())
1053                        }
1054                        .boxed()
1055                    }
1056
1057                    Request::FullTransactions => {
1058                        return async move {
1059                            Err("mempool is not active: wait for Zebra to sync to the tip".into())
1060                        }
1061                        .boxed()
1062                    }
1063
1064                    Request::RejectedTransactionIds(_) => {
1065                        Response::RejectedTransactionIds(Default::default())
1066                    }
1067
1068                    // Don't queue mempool candidates, because there is no queue.
1069                    Request::Queue(gossiped_txs) => Response::Queued(
1070                        // Special case; we can signal the error inside the response,
1071                        // because the inbound service ignores inner errors.
1072                        iter::repeat_n(MempoolError::Disabled, gossiped_txs.len())
1073                            .map(BoxError::from)
1074                            .map(Err)
1075                            .collect(),
1076                    ),
1077
1078                    // Drop peer-advertised txids when the mempool is disabled.
1079                    Request::QueueFromPeer { .. } => Response::Queued(Vec::new()),
1080
1081                    // Check if the mempool should be enabled.
1082                    // This request makes sure mempools are debug-enabled in the acceptance tests.
1083                    Request::CheckForVerifiedTransactions => {
1084                        // all the work for this request is done in poll_ready
1085                        Response::CheckedForVerifiedTransactions
1086                    }
1087
1088                    // Return empty mempool stats
1089                    Request::QueueStats => Response::QueueStats {
1090                        size: 0,
1091                        bytes: 0,
1092                        usage: 0,
1093                        fully_notified: None,
1094                    },
1095                };
1096
1097                async move { Ok(resp) }.boxed()
1098            }
1099        }
1100    }
1101}
1102
1103impl Drop for Mempool {
1104    fn drop(&mut self) {
1105        self.disable_metrics();
1106    }
1107}