Skip to main content

zakura_node_services/
mempool.rs

1//! The Zakura mempool.
2//!
3//! A service that manages known unmined Zcash transactions.
4
5use std::{collections::HashSet, fmt, net::SocketAddr};
6
7use tokio::sync::oneshot;
8use zakura_chain::{
9    block,
10    transaction::{self, UnminedTx, UnminedTxId, VerifiedUnminedTx},
11    transparent,
12};
13
14use crate::BoxError;
15
16mod gossip;
17mod mempool_change;
18mod service_trait;
19mod transaction_dependencies;
20
21pub use self::{
22    gossip::Gossip,
23    mempool_change::{MempoolChange, MempoolChangeKind, MempoolTxSubscriber},
24    service_trait::MempoolService,
25    transaction_dependencies::TransactionDependencies,
26};
27
28/// The mempool is disabled until Zakura is close to the chain tip.
29#[derive(Debug)]
30pub struct MempoolDisabledError;
31
32impl fmt::Display for MempoolDisabledError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str("mempool is not active: wait for Zakura to sync to the tip")
35    }
36}
37
38impl std::error::Error for MempoolDisabledError {}
39
40#[cfg(test)]
41mod tests {
42    use super::MempoolDisabledError;
43
44    #[test]
45    fn mempool_disabled_error_uses_zakura_branding() {
46        assert_eq!(
47            MempoolDisabledError.to_string(),
48            "mempool is not active: wait for Zakura to sync to the tip"
49        );
50    }
51}
52
53/// A peer source for per-peer mempool download accounting.
54#[derive(Clone, Debug, Eq, Hash, PartialEq)]
55pub enum QueueSource {
56    /// A transaction advertisement from the legacy TCP transport.
57    LegacySocket(SocketAddr),
58
59    /// A transaction advertisement from an authenticated Zakura peer.
60    ///
61    /// Stored as the encoded Zakura peer id to keep this service-interface crate
62    /// independent from the `zakura-network` transport types.
63    Zakura(Vec<u8>),
64}
65
66impl From<SocketAddr> for QueueSource {
67    fn from(source: SocketAddr) -> Self {
68        Self::LegacySocket(source)
69    }
70}
71
72/// A mempool service request.
73///
74/// Requests can query the current set of mempool transactions,
75/// queue transactions to be downloaded and verified, or
76/// run the mempool to check for newly verified transactions.
77///
78/// Requests can't modify the mempool directly,
79/// because all mempool transactions must be verified.
80#[derive(Debug, Eq, PartialEq)]
81pub enum Request {
82    /// Query all [`UnminedTxId`]s in the mempool.
83    TransactionIds,
84
85    /// Return and clear up to `limit` transaction IDs awaiting proactive
86    /// advertisement through the peer set.
87    ///
88    /// This pending set is separate from the full mempool inventory returned
89    /// by [`Request::TransactionIds`] in response to peer `mempool` requests.
90    TakePendingGossipTransactionIds {
91        /// Maximum number of transaction IDs to return.
92        limit: usize,
93    },
94
95    /// Query matching [`UnminedTx`] in the mempool,
96    /// using a unique set of [`UnminedTxId`]s.
97    TransactionsById(HashSet<UnminedTxId>),
98
99    /// Query matching [`UnminedTx`] in the mempool,
100    /// using a unique set of [`transaction::Hash`]es. Pre-V5 transactions are matched
101    /// directly; V5 transaction are matched just by the Hash, disregarding
102    /// the [`AuthDigest`](zakura_chain::transaction::AuthDigest).
103    TransactionsByMinedId(HashSet<transaction::Hash>),
104
105    /// Request a [`transparent::Output`] identified by the given [`OutPoint`](transparent::OutPoint),
106    /// waiting until it becomes available if it is unknown.
107    ///
108    /// This request is purely informational, and there are no guarantees about
109    /// whether the UTXO remains unspent or is on the best chain, or any chain.
110    /// Its purpose is to allow orphaned mempool transaction verification.
111    ///
112    /// # Correctness
113    ///
114    /// Output requests should be wrapped in a timeout, so that
115    /// out-of-order and invalid requests do not hang indefinitely.
116    ///
117    /// Outdated requests are pruned on a regular basis.
118    AwaitOutput(transparent::OutPoint),
119
120    /// Request a [`VerifiedUnminedTx`] and its dependencies by its mined id.
121    TransactionWithDepsByMinedId(transaction::Hash),
122
123    /// Get all the [`VerifiedUnminedTx`] in the mempool.
124    ///
125    /// Equivalent to `TransactionsById(TransactionIds)`,
126    /// but each transaction also includes the `miner_fee` and `legacy_sigop_count` fields.
127    //
128    // TODO: make the Transactions response return VerifiedUnminedTx,
129    //       and remove the FullTransactions variant
130    FullTransactions,
131
132    /// Query matching cached rejected transaction IDs in the mempool,
133    /// using a unique set of [`UnminedTxId`]s.
134    RejectedTransactionIds(HashSet<UnminedTxId>),
135
136    /// Queue a list of gossiped transactions or transaction IDs, or
137    /// crawled transaction IDs.
138    ///
139    /// The transaction downloader checks for duplicates across IDs and transactions.
140    Queue(Vec<Gossip>),
141
142    /// Queue transactions or transaction IDs received from a specific peer,
143    /// tagging each one with the peer so the downloader can enforce a per-peer
144    /// queue cap. See `GHSA-4fc2-h7jh-287c`.
145    QueueFromPeer {
146        /// The gossiped transaction candidates received from the peer.
147        transactions: Vec<Gossip>,
148        /// The peer that advertised them.
149        source: QueueSource,
150    },
151
152    /// Check for newly verified transactions.
153    ///
154    /// The transaction downloader does not push transactions into the mempool.
155    /// So a task should send this request regularly (every 5-10 seconds).
156    ///
157    /// These checks also happen for other request variants,
158    /// but we can't rely on peers to send queries regularly,
159    /// and crawler queue requests depend on peer responses.
160    /// Also, crawler requests aren't frequent enough for transaction propagation.
161    ///
162    /// # Correctness
163    ///
164    /// This request is required to avoid hangs in the mempool.
165    ///
166    /// The queue checker task can't call `poll_ready` directly on the mempool
167    /// service, because the service is wrapped in a `Buffer`. Calling
168    /// `Buffer::poll_ready` reserves a buffer slot, which can cause hangs
169    /// when too many slots are reserved but unused:
170    /// <https://docs.rs/tower/0.4.10/tower/buffer/struct.Buffer.html#a-note-on-choosing-a-bound>
171    CheckForVerifiedTransactions,
172
173    /// Request summary statistics from the mempool for `getmempoolinfo`.
174    QueueStats,
175
176    /// Check whether a transparent output is spent in the mempool.
177    UnspentOutput(transparent::OutPoint),
178}
179
180/// A response to a mempool service request.
181///
182/// Responses can read the current set of mempool transactions,
183/// check the queued status of transactions to be downloaded and verified, or
184/// confirm that the mempool has been checked for newly verified transactions.
185#[derive(Debug)]
186pub enum Response {
187    /// Returns all [`UnminedTxId`]s from the mempool.
188    TransactionIds(HashSet<UnminedTxId>),
189
190    /// Returns matching [`UnminedTx`] from the mempool.
191    ///
192    /// Since the [`Request::TransactionsById`] request is unique,
193    /// the response transactions are also unique. The same applies to
194    /// [`Request::TransactionsByMinedId`] requests, since the mempool does not allow
195    /// different transactions with different mined IDs.
196    Transactions(Vec<UnminedTx>),
197
198    /// Response to [`Request::AwaitOutput`] with the transparent output
199    UnspentOutput(transparent::Output),
200
201    /// Response to [`Request::TransactionWithDepsByMinedId`].
202    TransactionWithDeps {
203        /// The queried transaction
204        transaction: VerifiedUnminedTx,
205        /// A list of dependencies of the queried transaction.
206        dependencies: HashSet<transaction::Hash>,
207    },
208
209    /// Returns all [`VerifiedUnminedTx`] in the mempool.
210    //
211    // TODO: make the Transactions response return VerifiedUnminedTx,
212    //       and remove the FullTransactions variant
213    FullTransactions {
214        /// All [`VerifiedUnminedTx`]s in the mempool
215        transactions: Vec<VerifiedUnminedTx>,
216
217        /// All transaction dependencies in the mempool
218        transaction_dependencies: TransactionDependencies,
219
220        /// Last seen chain tip hash by mempool service
221        last_seen_tip_hash: zakura_chain::block::Hash,
222    },
223
224    /// Returns matching cached rejected [`UnminedTxId`]s from the mempool,
225    RejectedTransactionIds(HashSet<UnminedTxId>),
226
227    /// Returns a list of initial queue checks results and a oneshot receiver
228    /// for awaiting download and/or verification results.
229    ///
230    /// Each result matches the request at the corresponding vector index.
231    Queued(Vec<Result<oneshot::Receiver<Result<(), BoxError>>, BoxError>>),
232
233    /// Confirms that the mempool has checked for recently verified transactions.
234    CheckedForVerifiedTransactions,
235
236    /// Summary statistics for the mempool: count, total size, memory usage, and regtest info.
237    QueueStats {
238        /// Number of transactions currently in the mempool
239        size: usize,
240        /// Total size in bytes of all transactions
241        bytes: usize,
242        /// Estimated memory usage in bytes
243        usage: usize,
244        /// Whether all transactions have been fully notified (regtest only)
245        fully_notified: Option<bool>,
246    },
247
248    /// Returns whether a transparent output is created or spent in the mempool, if present.
249    TransparentOutput(Option<CreatedOrSpent>),
250}
251
252/// Indicates whether an output was created or spent by a mempool transaction.
253#[derive(Debug)]
254pub enum CreatedOrSpent {
255    /// An unspent output that was created by a transaction in the mempool and not spent by any other mempool tx.
256    Created {
257        /// The output
258        output: transparent::Output,
259        /// The version
260        tx_version: u32,
261        /// The last seen hash
262        last_seen_hash: block::Hash,
263    },
264    /// Indicates that an output was spent by a mempool transaction.
265    Spent,
266}