1use std::{
22 collections::HashSet,
23 future::Future,
24 iter,
25 ops::Bound,
26 pin::{pin, Pin},
27 task::{Context, Poll},
28};
29
30use chrono::Duration;
31use futures::{future::FutureExt, stream::Stream};
32use tokio::sync::{broadcast, mpsc, oneshot};
33use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service};
34
35use zebra_chain::{
36 block::{self, Height},
37 chain_sync_status::ChainSyncStatus,
38 chain_tip::ChainTip,
39 parameters::{Network, NetworkUpgrade},
40 transaction::UnminedTxId,
41};
42use zebra_consensus::{error::TransactionError, transaction};
43use zebra_network as zn;
44use zebra_node_services::mempool::{
45 CreatedOrSpent, Gossip, MempoolChange, MempoolTxSubscriber, Request, Response,
46};
47use zebra_state as zs;
48use zebra_state::{ChainTipChange, TipAction};
49
50use crate::components::sync::SyncStatus;
51
52pub mod config;
53mod crawler;
54pub mod downloads;
55mod error;
56pub mod gossip;
57mod pending_outputs;
58mod queue_checker;
59mod storage;
60
61#[cfg(test)]
62mod tests;
63
64pub use crate::BoxError;
65
66pub use config::Config;
67pub use crawler::Crawler;
68pub use error::MempoolError;
69pub use gossip::gossip_mempool_transaction_id;
70pub use queue_checker::QueueChecker;
71pub use storage::{
72 ExactTipRejectionError, SameEffectsChainRejectionError, SameEffectsTipRejectionError, Storage,
73};
74
75#[cfg(test)]
76pub use self::tests::UnboxMempoolError;
77
78use downloads::{
79 Downloads as TxDownloads, TransactionDownloadVerifyError, TRANSACTION_DOWNLOAD_TIMEOUT,
80 TRANSACTION_VERIFY_TIMEOUT,
81};
82
83type Outbound = Buffer<BoxService<zn::Request, zn::Response, zn::BoxError>, zn::Request>;
84type State = Buffer<BoxService<zs::Request, zs::Response, zs::BoxError>, zs::Request>;
85type TxVerifier = Buffer<
86 BoxService<transaction::MempoolRequest, transaction::MempoolResponse, TransactionError>,
87 transaction::MempoolRequest,
88>;
89type InboundTxDownloads = TxDownloads<Timeout<Outbound>, Timeout<TxVerifier>, State>;
90
91const BRANCH_ID_GRACE_PERIOD: Duration = Duration::minutes(50);
94
95fn adjusted_mempool_misbehavior_score(
97 error: &TransactionError,
98 transaction_upgrade: Option<NetworkUpgrade>,
99 verification_height: Height,
100 network: &Network,
101) -> u32 {
102 let is_grace_mismatch = matches!(error, TransactionError::WrongConsensusBranchId)
103 && transaction_upgrade.is_some_and(|transaction_upgrade| {
104 is_in_branch_id_grace_period(transaction_upgrade, verification_height, network)
105 });
106
107 let is_tip_relative_lock_time = matches!(
110 error,
111 TransactionError::LockedUntilAfterBlockHeight(_)
112 | TransactionError::LockedUntilAfterBlockTime(_)
113 );
114
115 if is_grace_mismatch || is_tip_relative_lock_time {
116 0
117 } else {
118 error.mempool_misbehavior_score()
119 }
120}
121
122fn is_in_branch_id_grace_period(
125 transaction_upgrade: NetworkUpgrade,
126 height: Height,
127 network: &Network,
128) -> bool {
129 let activations = network.activation_list();
130 let Some((¤t_height, ¤t)) = activations.range(..=height).next_back() else {
131 return false;
132 };
133 let grace_blocks =
134 BRANCH_ID_GRACE_PERIOD.num_seconds() / current.target_spacing().num_seconds();
135
136 let previous = activations.range(..current_height).next_back();
137 let next = activations
138 .range((Bound::Excluded(height), Bound::Unbounded))
139 .next();
140
141 previous.is_some_and(|(_, &previous)| {
142 previous == transaction_upgrade && height - current_height < grace_blocks
143 }) || next.is_some_and(|(&next_height, &next)| {
144 next == transaction_upgrade && next_height - height <= grace_blocks
145 })
146}
147
148#[allow(clippy::large_enum_variant)]
155#[derive(Default)]
156enum ActiveState {
157 #[default]
159 Disabled,
160
161 Enabled {
163 storage: Storage,
170
171 tx_downloads: Pin<Box<InboundTxDownloads>>,
173
174 last_seen_tip_hash: block::Hash,
178 },
179}
180
181impl ActiveState {
182 fn take(&mut self) -> Self {
184 std::mem::take(self)
185 }
186
187 fn transaction_retry_requests(&self) -> Vec<Gossip> {
189 match self {
190 ActiveState::Disabled => Vec::new(),
191 ActiveState::Enabled {
192 storage,
193 tx_downloads,
194 ..
195 } => {
196 let mut transactions = Vec::new();
197
198 let storage = storage
199 .transactions()
200 .values()
201 .map(|tx| tx.transaction.clone().into());
202 transactions.extend(storage);
203
204 let pending = tx_downloads.transaction_requests().cloned();
205 transactions.extend(pending);
206
207 transactions
208 }
209 }
210 }
211
212 #[cfg(feature = "progress-bar")]
215 fn queued_transaction_count(&self) -> usize {
216 match self {
217 ActiveState::Disabled => 0,
218 ActiveState::Enabled { tx_downloads, .. } => tx_downloads.in_flight(),
219 }
220 }
221
222 #[cfg(feature = "progress-bar")]
224 fn transaction_count(&self) -> usize {
225 match self {
226 ActiveState::Disabled => 0,
227 ActiveState::Enabled { storage, .. } => storage.transaction_count(),
228 }
229 }
230
231 #[cfg(feature = "progress-bar")]
234 fn total_cost(&self) -> u64 {
235 match self {
236 ActiveState::Disabled => 0,
237 ActiveState::Enabled { storage, .. } => storage.total_cost(),
238 }
239 }
240
241 #[cfg(feature = "progress-bar")]
246 pub fn total_serialized_size(&self) -> usize {
247 match self {
248 ActiveState::Disabled => 0,
249 ActiveState::Enabled { storage, .. } => storage.total_serialized_size(),
250 }
251 }
252
253 #[cfg(feature = "progress-bar")]
256 fn rejected_transaction_count(&mut self) -> usize {
257 match self {
258 ActiveState::Disabled => 0,
259 ActiveState::Enabled { storage, .. } => storage.rejected_transaction_count(),
260 }
261 }
262}
263
264pub struct Mempool {
270 network: Network,
272
273 config: Config,
275
276 active_state: ActiveState,
278
279 sync_status: SyncStatus,
281
282 debug_enable_at_height: Option<Height>,
284
285 latest_chain_tip: zs::LatestChainTip,
287
288 chain_tip_change: ChainTipChange,
291
292 outbound: Outbound,
295
296 state: State,
299
300 tx_verifier: TxVerifier,
303
304 transaction_sender: broadcast::Sender<MempoolChange>,
307
308 misbehavior_sender: mpsc::Sender<zn::MisbehaviorReport>,
310
311 #[cfg(feature = "progress-bar")]
316 queued_count_bar: Option<howudoin::Tx>,
317
318 #[cfg(feature = "progress-bar")]
321 transaction_count_bar: Option<howudoin::Tx>,
322
323 #[cfg(feature = "progress-bar")]
326 transaction_cost_bar: Option<howudoin::Tx>,
327
328 #[cfg(feature = "progress-bar")]
331 rejected_count_bar: Option<howudoin::Tx>,
332}
333
334impl Mempool {
335 #[allow(clippy::too_many_arguments)]
340 pub(crate) fn new(
341 network: &Network,
342 config: &Config,
343 outbound: Outbound,
344 state: State,
345 tx_verifier: TxVerifier,
346 sync_status: SyncStatus,
347 latest_chain_tip: zs::LatestChainTip,
348 chain_tip_change: ChainTipChange,
349 misbehavior_sender: mpsc::Sender<zn::MisbehaviorReport>,
350 ) -> (Self, MempoolTxSubscriber) {
351 let (transaction_sender, _) =
352 tokio::sync::broadcast::channel(gossip::MAX_CHANGES_BEFORE_SEND * 2);
353 let transaction_subscriber = MempoolTxSubscriber::new(transaction_sender.clone());
354
355 let mut service = Mempool {
356 network: network.clone(),
357 config: config.clone(),
358 active_state: ActiveState::Disabled,
359 sync_status,
360 debug_enable_at_height: config.debug_enable_at_height.map(Height),
361 latest_chain_tip,
362 chain_tip_change,
363 outbound,
364 state,
365 tx_verifier,
366 transaction_sender,
367 misbehavior_sender,
368 #[cfg(feature = "progress-bar")]
369 queued_count_bar: None,
370 #[cfg(feature = "progress-bar")]
371 transaction_count_bar: None,
372 #[cfg(feature = "progress-bar")]
373 transaction_cost_bar: None,
374 #[cfg(feature = "progress-bar")]
375 rejected_count_bar: None,
376 };
377
378 let is_caught_up_to_start = service.is_caught_up_to_start();
381 service.update_state(None, is_caught_up_to_start);
382
383 (service, transaction_subscriber)
384 }
385
386 fn is_enabled_by_debug(&self) -> bool {
388 let mut is_debug_enabled = false;
389
390 if self.debug_enable_at_height.is_none() {
392 return is_debug_enabled;
393 }
394
395 let enable_at_height = self
396 .debug_enable_at_height
397 .expect("unexpected debug_enable_at_height: just checked for None");
398
399 if let Some(best_tip_height) = self.latest_chain_tip.best_tip_height() {
400 is_debug_enabled = best_tip_height >= enable_at_height;
401
402 if is_debug_enabled && !self.is_enabled() {
403 info!(
404 ?best_tip_height,
405 ?enable_at_height,
406 "enabling mempool for debugging"
407 );
408 }
409 }
410
411 is_debug_enabled
412 }
413
414 fn is_caught_up_to_start(&self) -> bool {
416 self.sync_status.is_close_to_tip() || self.is_enabled_by_debug()
417 }
418
419 fn enable_at_tip(&mut self, tip_action: &TipAction) {
422 let (last_seen_tip_hash, _) = tip_action.best_tip_hash_and_height();
423
424 let tx_downloads = Box::pin(TxDownloads::new(
425 Timeout::new(self.outbound.clone(), TRANSACTION_DOWNLOAD_TIMEOUT),
426 Timeout::new(self.tx_verifier.clone(), TRANSACTION_VERIFY_TIMEOUT),
427 self.state.clone(),
428 ));
429 self.active_state = ActiveState::Enabled {
430 storage: storage::Storage::new(&self.config),
431 tx_downloads,
432 last_seen_tip_hash,
433 };
434 }
435
436 fn update_state(
448 &mut self,
449 tip_action: Option<&TipAction>,
450 is_caught_up_to_start: bool,
451 ) -> bool {
452 match (is_caught_up_to_start, self.is_enabled(), tip_action) {
455 (false, false, _) | (true, true, _) | (true, false, None) => return false,
457
458 (true, false, Some(tip_action)) => {
460 info!(
461 tip_height = ?tip_action.best_tip_height(),
462 "activating mempool: Zebra is close to the tip"
463 );
464
465 self.enable_at_tip(tip_action);
466 }
467
468 (false, true, _) => {
477 return false;
478 }
479 };
480
481 true
482 }
483
484 pub fn is_enabled(&self) -> bool {
486 match self.active_state {
487 ActiveState::Disabled => false,
488 ActiveState::Enabled { .. } => true,
489 }
490 }
491
492 fn remove_expired_from_peer_list(
494 send_to_peers_ids: &HashSet<UnminedTxId>,
495 expired_transactions: &HashSet<UnminedTxId>,
496 ) -> HashSet<UnminedTxId> {
497 send_to_peers_ids
498 .iter()
499 .filter(|id| !expired_transactions.contains(id))
500 .copied()
501 .collect()
502 }
503
504 fn update_metrics(&mut self) {
506 #[cfg(feature = "progress-bar")]
508 if matches!(howudoin::cancelled(), Some(true)) {
509 self.disable_metrics();
510 return;
511 }
512
513 #[cfg(feature = "progress-bar")]
515 if self.is_enabled()
516 && (self.queued_count_bar.is_none()
517 || self.transaction_count_bar.is_none()
518 || self.transaction_cost_bar.is_none()
519 || self.rejected_count_bar.is_none())
520 {
521 let _max_transaction_count = self.config.tx_cost_limit
522 / zebra_chain::transaction::MEMPOOL_TRANSACTION_COST_THRESHOLD;
523
524 let transaction_count_bar = *howudoin::new_root()
525 .label("Mempool Transactions")
526 .set_pos(0u64);
527 let transaction_cost_bar = howudoin::new_with_parent(transaction_count_bar.id())
530 .label("Mempool Cost")
531 .set_pos(0u64)
532 .fmt_as_bytes(true);
534
535 let queued_count_bar = *howudoin::new_with_parent(transaction_cost_bar.id())
536 .label("Mempool Queue")
537 .set_pos(0u64);
538 let rejected_count_bar = *howudoin::new_with_parent(queued_count_bar.id())
543 .label("Mempool Rejects")
544 .set_pos(0u64);
545 self.transaction_count_bar = Some(transaction_count_bar);
550 self.transaction_cost_bar = Some(transaction_cost_bar);
551 self.queued_count_bar = Some(queued_count_bar);
552 self.rejected_count_bar = Some(rejected_count_bar);
553 }
554
555 #[cfg(feature = "progress-bar")]
557 if let (
558 Some(queued_count_bar),
559 Some(transaction_count_bar),
560 Some(transaction_cost_bar),
561 Some(rejected_count_bar),
562 ) = (
563 self.queued_count_bar,
564 self.transaction_count_bar,
565 self.transaction_cost_bar,
566 self.rejected_count_bar,
567 ) {
568 let queued_count = self.active_state.queued_transaction_count();
569 let transaction_count = self.active_state.transaction_count();
570
571 let transaction_cost = self.active_state.total_cost();
572 let transaction_size = self.active_state.total_serialized_size();
573 let transaction_size =
574 indicatif::HumanBytes(transaction_size.try_into().expect("fits in u64"));
575
576 let rejected_count = self.active_state.rejected_transaction_count();
577
578 queued_count_bar.set_pos(u64::try_from(queued_count).expect("fits in u64"));
579
580 transaction_count_bar.set_pos(u64::try_from(transaction_count).expect("fits in u64"));
581
582 transaction_cost_bar
587 .set_pos(transaction_cost)
588 .desc(format!("Actual size {transaction_size}"));
589
590 rejected_count_bar.set_pos(u64::try_from(rejected_count).expect("fits in u64"));
591 }
592 }
593
594 fn disable_metrics(&self) {
596 #[cfg(feature = "progress-bar")]
597 {
598 if let Some(bar) = self.queued_count_bar {
599 bar.close()
600 }
601 if let Some(bar) = self.transaction_count_bar {
602 bar.close()
603 }
604 if let Some(bar) = self.transaction_cost_bar {
605 bar.close()
606 }
607 if let Some(bar) = self.rejected_count_bar {
608 bar.close()
609 }
610 }
611 }
612}
613
614impl Service<Request> for Mempool {
615 type Response = Response;
616 type Error = BoxError;
617 type Future =
618 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
619
620 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
621 let is_caught_up_to_start = self.is_caught_up_to_start();
622 let should_check_tip = self.is_enabled() || is_caught_up_to_start;
623 let tip_action = should_check_tip
624 .then(|| self.chain_tip_change.last_tip_change())
625 .flatten();
626
627 let is_state_changed = self.update_state(tip_action.as_ref(), is_caught_up_to_start);
629
630 tracing::trace!(is_enabled = ?self.is_enabled(), ?is_state_changed, "started polling the mempool...");
631
632 if !self.is_enabled() {
635 self.update_metrics();
636
637 return Poll::Ready(Ok(()));
638 }
639
640 let reset_tip_action = match tip_action.as_ref() {
645 Some(reset_tip_action @ TipAction::Reset { .. }) if !is_state_changed => {
646 Some(reset_tip_action)
647 }
648 _ => None,
649 };
650
651 if let Some(reset_tip_action) = reset_tip_action {
652 info!(
653 tip_height = ?reset_tip_action.best_tip_height(),
654 "resetting mempool: switched best chain, skipped blocks, or activated network upgrade"
655 );
656
657 let previous_state = self.active_state.take();
658 let tx_retries = previous_state.transaction_retry_requests();
659
660 std::mem::drop(previous_state);
667
668 self.enable_at_tip(reset_tip_action);
676
677 if let ActiveState::Enabled { tx_downloads, .. } = &mut self.active_state {
680 info!(
681 transactions = tx_retries.len(),
682 "re-verifying mempool transactions after a chain fork"
683 );
684
685 for tx in tx_retries {
686 let _result = tx_downloads.download_if_needed_and_verify(tx, None, None);
689 }
690 }
691
692 self.update_metrics();
693
694 return Poll::Ready(Ok(()));
695 }
696
697 if let ActiveState::Enabled {
698 storage,
699 tx_downloads,
700 last_seen_tip_hash,
701 } = &mut self.active_state
702 {
703 let mut send_to_peers_ids = HashSet::<_>::new();
705 let mut invalidated_ids = HashSet::<_>::new();
706 let mut mined_mempool_ids = HashSet::<_>::new();
707
708 let best_tip_height = self.latest_chain_tip.best_tip_height();
709
710 while let Poll::Ready(Some(result)) = pin!(&mut *tx_downloads).poll_next(cx) {
712 match result {
713 Ok(Ok((tx, spent_mempool_outpoints, expected_tip_height, rsp_tx))) => {
714 if best_tip_height == expected_tip_height {
721 let tx_id = tx.transaction.id;
722 let insert_result =
723 storage.insert(tx, spent_mempool_outpoints, best_tip_height);
724
725 tracing::trace!(
726 ?insert_result,
727 "got Ok(_) transaction verify, tried to store",
728 );
729
730 if let Ok(inserted_id) = insert_result {
731 send_to_peers_ids.insert(inserted_id);
733 } else {
734 invalidated_ids.insert(tx_id);
735 }
736
737 if let Some(rsp_tx) = rsp_tx {
739 let _ = rsp_tx
740 .send(insert_result.map(|_| ()).map_err(|err| err.into()));
741 }
742 } else {
743 tracing::trace!("chain grew during tx verification, retrying ..",);
744
745 let _result = tx_downloads.download_if_needed_and_verify(
747 tx.transaction.into(),
748 None,
749 rsp_tx,
750 );
751 }
752 }
753 Ok(Err(boxed_err)) => {
754 let (tx_id, error) = *boxed_err;
755 if let TransactionDownloadVerifyError::Invalid {
756 error,
757 advertiser_addr: Some(advertiser_addr),
758 transaction_upgrade,
759 verification_height,
760 } = &error
761 {
762 let score = adjusted_mempool_misbehavior_score(
763 error,
764 *transaction_upgrade,
765 *verification_height,
766 &self.network,
767 );
768
769 if score != 0 {
770 let _ =
771 self.misbehavior_sender.try_send(zn::MisbehaviorReport::new(
772 *advertiser_addr,
773 score,
774 format!("invalid mempool transaction {tx_id}: {error}"),
775 ));
776 }
777 };
778
779 tracing::debug!(?tx_id, ?error, "mempool transaction failed to verify");
780
781 metrics::counter!("mempool.failed.verify.tasks.total", "reason" => error.to_string()).increment(1);
782
783 invalidated_ids.insert(tx_id);
784 storage.reject_if_needed(tx_id, error);
785 }
786 Err((tx_id, _elapsed)) => {
787 tracing::info!(
788 ?tx_id,
789 "mempool transaction failed to verify due to timeout"
790 );
791
792 invalidated_ids.insert(tx_id);
793
794 metrics::counter!("mempool.failed.verify.tasks.total", "reason" => "timeout").increment(1);
795 }
796 };
797 }
798
799 if let Some(TipAction::Grow { block }) = tip_action {
801 tracing::trace!(block_height = ?block.height, "handling blocks added to tip");
802 *last_seen_tip_hash = block.hash;
803
804 let mined_ids = block.transaction_hashes.iter().cloned().collect();
807 tx_downloads.cancel(&mined_ids);
808 storage.clear_mined_dependencies(&mined_ids);
809
810 let storage::RemovedTransactionIds { mined, invalidated } =
811 storage.reject_and_remove_same_effects(&mined_ids, block.transactions);
812
813 storage.clear_tip_rejections();
816
817 mined_mempool_ids.extend(mined);
818 invalidated_ids.extend(invalidated);
819 }
820
821 if let Some(tip_height) = best_tip_height {
826 let expired_transactions = storage.remove_expired_transactions(tip_height);
827 send_to_peers_ids =
829 Self::remove_expired_from_peer_list(&send_to_peers_ids, &expired_transactions);
830
831 if !expired_transactions.is_empty() {
832 tracing::debug!(
833 ?expired_transactions,
834 "removed expired transactions from the mempool",
835 );
836
837 invalidated_ids.extend(expired_transactions);
838 }
839 }
840
841 if !send_to_peers_ids.is_empty() {
843 tracing::trace!(
844 ?send_to_peers_ids,
845 "sending new transactions to peers and RPC listeners"
846 );
847
848 let _ = self
856 .transaction_sender
857 .send(MempoolChange::added(send_to_peers_ids));
858 }
859
860 if !invalidated_ids.is_empty() {
862 tracing::trace!(
863 ?invalidated_ids,
864 "sending invalidated transactions to RPC listeners"
865 );
866
867 let _ = self
870 .transaction_sender
871 .send(MempoolChange::invalidated(invalidated_ids));
872 }
873
874 if !mined_mempool_ids.is_empty() {
876 tracing::trace!(
877 ?mined_mempool_ids,
878 "sending mined transactions to RPC listeners"
879 );
880
881 let _ = self
884 .transaction_sender
885 .send(MempoolChange::mined(mined_mempool_ids));
886 }
887 }
888
889 self.update_metrics();
890
891 Poll::Ready(Ok(()))
892 }
893
894 #[instrument(name = "mempool", skip(self, req))]
899 fn call(&mut self, req: Request) -> Self::Future {
900 match &mut self.active_state {
901 ActiveState::Enabled {
902 storage,
903 tx_downloads,
904 last_seen_tip_hash,
905 } => match req {
906 Request::TransactionIds => {
908 trace!(?req, "got mempool request");
909
910 let res: HashSet<_> = storage.tx_ids().collect();
911
912 trace!(?req, res_count = ?res.len(), "answered mempool request");
913
914 async move { Ok(Response::TransactionIds(res)) }.boxed()
915 }
916
917 Request::TransactionsById(ref ids) => {
918 trace!(?req, "got mempool request");
919
920 let res: Vec<_> = storage.transactions_exact(ids.clone()).cloned().collect();
921
922 trace!(?req, res_count = ?res.len(), "answered mempool request");
923
924 async move { Ok(Response::Transactions(res)) }.boxed()
925 }
926 Request::TransactionsByMinedId(ref ids) => {
927 trace!(?req, "got mempool request");
928
929 let res: Vec<_> = storage
930 .transactions_same_effects(ids.clone())
931 .cloned()
932 .collect();
933
934 trace!(?req, res_count = ?res.len(), "answered mempool request");
935
936 async move { Ok(Response::Transactions(res)) }.boxed()
937 }
938 Request::TransactionWithDepsByMinedId(tx_id) => {
939 trace!(?req, "got mempool request");
940
941 let res = if let Some((transaction, dependencies)) =
942 storage.transaction_with_deps(tx_id)
943 {
944 Ok(Response::TransactionWithDeps {
945 transaction,
946 dependencies,
947 })
948 } else {
949 Err("transaction not found in mempool".into())
950 };
951
952 trace!(?req, ?res, "answered mempool request");
953
954 async move { res }.boxed()
955 }
956
957 Request::AwaitOutput(outpoint) => {
958 trace!(?req, "got mempool request");
959
960 let response_fut = storage.pending_outputs.queue(outpoint);
961
962 if let Some(output) = storage.created_output(&outpoint) {
963 storage.pending_outputs.respond(&outpoint, output)
964 }
965
966 trace!("answered mempool request");
967
968 response_fut.boxed()
969 }
970
971 Request::FullTransactions => {
972 trace!(?req, "got mempool request");
973
974 let transactions: Vec<_> = storage.transactions().values().cloned().collect();
975 let transaction_dependencies = storage.transaction_dependencies().clone();
976
977 trace!(?req, transactions_count = ?transactions.len(), "answered mempool request");
978
979 let response = Response::FullTransactions {
980 transactions,
981 transaction_dependencies,
982 last_seen_tip_hash: *last_seen_tip_hash,
983 };
984
985 async move { Ok(response) }.boxed()
986 }
987
988 Request::RejectedTransactionIds(ref ids) => {
989 trace!(?req, "got mempool request");
990
991 let res = storage.rejected_transactions(ids.clone()).collect();
992
993 trace!(?req, ?res, "answered mempool request");
994
995 async move { Ok(Response::RejectedTransactionIds(res)) }.boxed()
996 }
997
998 Request::Queue(gossiped_txs) => {
1000 trace!(req_count = ?gossiped_txs.len(), "got mempool Queue request");
1001
1002 let rsp: Vec<Result<oneshot::Receiver<Result<(), BoxError>>, BoxError>> =
1003 gossiped_txs
1004 .into_iter()
1005 .map(
1006 |gossiped_tx| -> Result<
1007 oneshot::Receiver<Result<(), BoxError>>,
1008 MempoolError,
1009 > {
1010 let (rsp_tx, rsp_rx) = oneshot::channel();
1011 storage.should_download_or_verify(gossiped_tx.id())?;
1012 tx_downloads.download_if_needed_and_verify(
1013 gossiped_tx,
1014 None,
1015 Some(rsp_tx),
1016 )?;
1017
1018 Ok(rsp_rx)
1019 },
1020 )
1021 .map(|result| result.map_err(BoxError::from))
1022 .collect();
1023
1024 self.update_metrics();
1026
1027 async move { Ok(Response::Queued(rsp)) }.boxed()
1028 }
1029
1030 Request::QueueFromPeer { candidates, source } => {
1034 trace!(req_count = ?candidates.len(), ?source, "got mempool QueueFromPeer request");
1035
1036 for candidate in candidates {
1037 if storage.should_download_or_verify(candidate.id()).is_err() {
1038 continue;
1039 }
1040 let _ = tx_downloads.download_if_needed_and_verify(
1041 candidate,
1042 Some(source),
1043 None,
1044 );
1045 }
1046
1047 self.update_metrics();
1048
1049 async move { Ok(Response::Queued(Vec::new())) }.boxed()
1050 }
1051
1052 Request::CheckForVerifiedTransactions => {
1054 trace!(?req, "got mempool request");
1055
1056 async move { Ok(Response::CheckedForVerifiedTransactions) }.boxed()
1058 }
1059
1060 Request::QueueStats => {
1064 trace!(?req, "got mempool request");
1065
1066 let size = storage.transaction_count();
1067
1068 let bytes = storage.total_serialized_size();
1069
1070 let usage = bytes; let fully_notified = None;
1074
1075 trace!(size, bytes, usage, "answered mempool request");
1076
1077 async move {
1078 Ok(Response::QueueStats {
1079 size,
1080 bytes,
1081 usage,
1082 fully_notified,
1083 })
1084 }
1085 .boxed()
1086 }
1087 Request::UnspentOutput(outpoint) => {
1088 trace!(?req, "got mempool request");
1089
1090 if storage.has_spent_outpoint(&outpoint) {
1091 trace!(?req, "answered mempool request");
1092
1093 return async move {
1094 Ok(Response::TransparentOutput(Some(CreatedOrSpent::Spent)))
1095 }
1096 .boxed();
1097 }
1098
1099 if let Some((tx_version, output)) = storage
1100 .transactions()
1101 .get(&outpoint.hash)
1102 .map(|tx| tx.transaction.transaction.clone())
1103 .and_then(|tx| {
1104 tx.outputs()
1105 .get(outpoint.index as usize)
1106 .map(|output| (tx.version(), output.clone()))
1107 })
1108 {
1109 trace!(?req, "answered mempool request");
1110
1111 let last_seen_hash = *last_seen_tip_hash;
1112 return async move {
1113 Ok(Response::TransparentOutput(Some(CreatedOrSpent::Created {
1114 output,
1115 tx_version,
1116 last_seen_hash,
1117 })))
1118 }
1119 .boxed();
1120 }
1121
1122 trace!(?req, "answered mempool request");
1123
1124 async move { Ok(Response::TransparentOutput(None)) }.boxed()
1125 }
1126 },
1127 ActiveState::Disabled => {
1128 trace!("got mempool request while mempool is disabled");
1131
1132 let resp = match req {
1137 Request::TransactionIds => Response::TransactionIds(Default::default()),
1139
1140 Request::TransactionsById(_) => Response::Transactions(Default::default()),
1141 Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
1142 Request::TransactionWithDepsByMinedId(_)
1143 | Request::AwaitOutput(_)
1144 | Request::UnspentOutput(_) => {
1145 return async move {
1146 Err("mempool is not active: wait for Zebra to sync to the tip".into())
1147 }
1148 .boxed()
1149 }
1150
1151 Request::FullTransactions => {
1152 return async move {
1153 Err("mempool is not active: wait for Zebra to sync to the tip".into())
1154 }
1155 .boxed()
1156 }
1157
1158 Request::RejectedTransactionIds(_) => {
1159 Response::RejectedTransactionIds(Default::default())
1160 }
1161
1162 Request::Queue(gossiped_txs) => Response::Queued(
1164 iter::repeat_n(MempoolError::Disabled, gossiped_txs.len())
1167 .map(BoxError::from)
1168 .map(Err)
1169 .collect(),
1170 ),
1171
1172 Request::QueueFromPeer { .. } => Response::Queued(Vec::new()),
1174
1175 Request::CheckForVerifiedTransactions => {
1178 Response::CheckedForVerifiedTransactions
1180 }
1181
1182 Request::QueueStats => Response::QueueStats {
1184 size: 0,
1185 bytes: 0,
1186 usage: 0,
1187 fully_notified: None,
1188 },
1189 };
1190
1191 async move { Ok(resp) }.boxed()
1192 }
1193 }
1194 }
1195}
1196
1197impl Drop for Mempool {
1198 fn drop(&mut self) {
1199 self.disable_metrics();
1200 }
1201}