1use 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#[allow(clippy::large_enum_variant)]
95#[derive(Default)]
96enum ActiveState {
97 #[default]
99 Disabled,
100
101 Enabled {
103 storage: Storage,
110
111 tx_downloads: Pin<Box<InboundTxDownloads>>,
113
114 last_seen_tip_hash: block::Hash,
118 },
119}
120
121impl ActiveState {
122 fn take(&mut self) -> Self {
124 std::mem::take(self)
125 }
126
127 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 #[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 #[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 #[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 #[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 #[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
204pub struct Mempool {
210 config: Config,
212
213 active_state: ActiveState,
215
216 sync_status: SyncStatus,
218
219 debug_enable_at_height: Option<Height>,
221
222 latest_chain_tip: zs::LatestChainTip,
224
225 chain_tip_change: ChainTipChange,
228
229 outbound: Outbound,
232
233 state: State,
236
237 tx_verifier: TxVerifier,
240
241 transaction_sender: broadcast::Sender<MempoolChange>,
244
245 misbehavior_sender: mpsc::Sender<(PeerSocketAddr, u32)>,
247
248 #[cfg(feature = "progress-bar")]
253 queued_count_bar: Option<howudoin::Tx>,
254
255 #[cfg(feature = "progress-bar")]
258 transaction_count_bar: Option<howudoin::Tx>,
259
260 #[cfg(feature = "progress-bar")]
263 transaction_cost_bar: Option<howudoin::Tx>,
264
265 #[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 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 fn is_enabled_by_debug(&self) -> bool {
319 let mut is_debug_enabled = false;
320
321 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 fn is_caught_up_to_start(&self) -> bool {
347 self.sync_status.is_close_to_tip() || self.is_enabled_by_debug()
348 }
349
350 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 fn update_state(
379 &mut self,
380 tip_action: Option<&TipAction>,
381 is_caught_up_to_start: bool,
382 ) -> bool {
383 match (is_caught_up_to_start, self.is_enabled(), tip_action) {
386 (false, false, _) | (true, true, _) | (true, false, None) => return false,
388
389 (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 (false, true, _) => {
408 return false;
409 }
410 };
411
412 true
413 }
414
415 pub fn is_enabled(&self) -> bool {
417 match self.active_state {
418 ActiveState::Disabled => false,
419 ActiveState::Enabled { .. } => true,
420 }
421 }
422
423 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 fn update_metrics(&mut self) {
437 #[cfg(feature = "progress-bar")]
439 if matches!(howudoin::cancelled(), Some(true)) {
440 self.disable_metrics();
441 return;
442 }
443
444 #[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 let transaction_cost_bar = howudoin::new_with_parent(transaction_count_bar.id())
461 .label("Mempool Cost")
462 .set_pos(0u64)
463 .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 let rejected_count_bar = *howudoin::new_with_parent(queued_count_bar.id())
474 .label("Mempool Rejects")
475 .set_pos(0u64);
476 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 #[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 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 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 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 if !self.is_enabled() {
566 self.update_metrics();
567
568 return Poll::Ready(Ok(()));
569 }
570
571 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 std::mem::drop(previous_state);
598
599 self.enable_at_tip(reset_tip_action);
607
608 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 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 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 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 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 send_to_peers_ids.insert(inserted_id);
664 } else {
665 invalidated_ids.insert(tx_id);
666 }
667
668 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 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 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 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 storage.clear_tip_rejections();
736
737 mined_mempool_ids.extend(mined);
738 invalidated_ids.extend(invalidated);
739 }
740
741 if let Some(tip_height) = best_tip_height {
746 let expired_transactions = storage.remove_expired_transactions(tip_height);
747 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 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 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 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 #[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 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 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 self.update_metrics();
932
933 async move { Ok(Response::Queued(rsp)) }.boxed()
934 }
935
936 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 Request::CheckForVerifiedTransactions => {
960 trace!(?req, "got mempool request");
961
962 async move { Ok(Response::CheckedForVerifiedTransactions) }.boxed()
964 }
965
966 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; 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 trace!("got mempool request while mempool is disabled");
1037
1038 let resp = match req {
1043 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 Request::Queue(gossiped_txs) => Response::Queued(
1070 iter::repeat_n(MempoolError::Disabled, gossiped_txs.len())
1073 .map(BoxError::from)
1074 .map(Err)
1075 .collect(),
1076 ),
1077
1078 Request::QueueFromPeer { .. } => Response::Queued(Vec::new()),
1080
1081 Request::CheckForVerifiedTransactions => {
1084 Response::CheckedForVerifiedTransactions
1086 }
1087
1088 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}