Skip to main content

zebra_chain/transaction/
unmined.rs

1//! Unmined Zcash transaction identifiers and transactions.
2//!
3//! Transaction version 5 is uniquely identified by [`WtxId`] when unmined, and
4//! [`struct@Hash`] in the blockchain. The effects of a v5 transaction
5//! (spends and outputs) are uniquely identified by the same
6//! [`struct@Hash`] in both cases.
7//!
8//! Transaction versions 1-4 are uniquely identified by legacy
9//! [`struct@Hash`] transaction IDs, whether they have been mined or not.
10//! So Zebra, and the Zcash network protocol, don't use witnessed transaction
11//! IDs for them.
12//!
13//! Zebra's [`UnminedTxId`] and [`UnminedTx`] enums provide the correct unique
14//! ID for unmined transactions. They can be used to handle transactions
15//! regardless of version, and get the [`WtxId`] or [`struct@Hash`] when
16//! required.
17
18use std::{fmt, sync::Arc};
19
20use crate::{
21    amount::{Amount, NonNegative},
22    block::Height,
23    serialization::ZcashSerialize,
24    transaction::{AuthDigest, Hash, Transaction, WtxId},
25    transparent,
26};
27
28use UnminedTxId::*;
29
30#[cfg(any(test, feature = "proptest-impl"))]
31use proptest_derive::Arbitrary;
32
33// Documentation-only
34#[allow(unused_imports)]
35use crate::block::MAX_BLOCK_BYTES;
36
37pub mod zip317;
38
39/// The minimum cost value for a transaction in the mempool.
40///
41/// Contributes to the randomized, weighted eviction of transactions from the
42/// mempool when it reaches a max size, also based on the total cost.
43///
44/// # Standard Rule
45///
46/// > Each transaction has a cost, which is an integer defined as:
47/// >
48/// > max(memory size in bytes, 10000)
49/// >
50/// > The memory size is an estimate of the size that a transaction occupies in the
51/// > memory of a node. It MAY be approximated as the serialized transaction size in
52/// > bytes.
53/// >
54/// > ...
55/// >
56/// > The threshold 10000 for the cost function is chosen so that the size in bytes of
57/// > a minimal fully shielded Orchard transaction with 2 shielded actions (having a
58/// > serialized size of 9165 bytes) will fall below the threshold. This has the effect
59/// > of ensuring that such transactions are not evicted preferentially to typical
60/// > transparent or Sapling transactions because of their size.
61///
62/// [ZIP-401]: https://zips.z.cash/zip-0401
63pub const MEMPOOL_TRANSACTION_COST_THRESHOLD: u64 = 10_000;
64
65/// When a transaction pays a fee less than the conventional fee,
66/// this low fee penalty is added to its cost for mempool eviction.
67///
68/// See [VerifiedUnminedTx::eviction_weight()] for details.
69///
70/// [ZIP-401]: https://zips.z.cash/zip-0401
71const MEMPOOL_TRANSACTION_LOW_FEE_PENALTY: u64 = 40_000;
72
73/// A unique identifier for an unmined transaction, regardless of version.
74///
75/// "The transaction ID of a version 4 or earlier transaction is the SHA-256d hash
76/// of the transaction encoding in the pre-v5 format described above.
77///
78/// The transaction ID of a version 5 transaction is as defined in [ZIP-244].
79///
80/// A v5 transaction also has a wtxid (used for example in the peer-to-peer protocol)
81/// as defined in [ZIP-239]."
82/// [Spec: Transaction Identifiers]
83///
84/// [ZIP-239]: https://zips.z.cash/zip-0239
85/// [ZIP-244]: https://zips.z.cash/zip-0244
86/// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers
87#[derive(Copy, Clone, Eq, PartialEq, Hash)]
88#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
89pub enum UnminedTxId {
90    /// A legacy unmined transaction identifier.
91    ///
92    /// Used to uniquely identify unmined version 1-4 transactions.
93    /// (After v1-4 transactions are mined, they can be uniquely identified
94    /// using the same [`struct@Hash`].)
95    Legacy(Hash),
96
97    /// A witnessed unmined transaction identifier.
98    ///
99    /// Used to uniquely identify unmined version 5 transactions.
100    /// (After v5 transactions are mined, they can be uniquely identified
101    /// using only the [`struct@Hash`] in their `WtxId.id`.)
102    ///
103    /// For more details, see [`WtxId`].
104    Witnessed(WtxId),
105}
106
107impl fmt::Debug for UnminedTxId {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            // Logging unmined transaction IDs can leak sensitive user information,
111            // particularly when Zebra is being used as a `lightwalletd` backend.
112            Self::Legacy(_hash) => f.debug_tuple("Legacy").field(&self.to_string()).finish(),
113            Self::Witnessed(_id) => f.debug_tuple("Witnessed").field(&self.to_string()).finish(),
114        }
115    }
116}
117
118impl fmt::Display for UnminedTxId {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Legacy(_hash) => f
122                .debug_tuple("transaction::Hash")
123                .field(&"private")
124                .finish(),
125            Witnessed(_id) => f.debug_tuple("WtxId").field(&"private").finish(),
126        }
127    }
128}
129
130// `From<&Transaction>` and `From<Arc<Transaction>>` for `UnminedTxId`
131// are defined in transaction.rs, where the version dispatch lives.
132
133impl From<Transaction> for UnminedTxId {
134    fn from(transaction: Transaction) -> Self {
135        // use the ref implementation, to avoid cloning the transaction
136        UnminedTxId::from(&transaction)
137    }
138}
139
140impl From<WtxId> for UnminedTxId {
141    fn from(wtx_id: WtxId) -> Self {
142        Witnessed(wtx_id)
143    }
144}
145
146impl From<&WtxId> for UnminedTxId {
147    fn from(wtx_id: &WtxId) -> Self {
148        (*wtx_id).into()
149    }
150}
151
152impl UnminedTxId {
153    /// Create a new [`UnminedTxId`] using a v1-v4 legacy transaction ID.
154    ///
155    /// # Correctness
156    ///
157    /// This method must only be used for v1-v4 transaction IDs.
158    /// [`struct@Hash`] does not uniquely identify unmined v5
159    /// transactions.
160    pub fn from_legacy_id(legacy_tx_id: Hash) -> UnminedTxId {
161        Legacy(legacy_tx_id)
162    }
163
164    /// Return the unique ID that will be used if this transaction gets mined into a block.
165    ///
166    /// # Correctness
167    ///
168    /// For v1-v4 transactions, this method returns an ID which changes
169    /// if this transaction's effects (spends and outputs) change, or
170    /// if its authorizing data changes (signatures, proofs, and scripts).
171    ///
172    /// But for v5 transactions, this ID uniquely identifies the transaction's effects.
173    pub fn mined_id(&self) -> Hash {
174        match self {
175            Legacy(legacy_id) => *legacy_id,
176            Witnessed(wtx_id) => wtx_id.id,
177        }
178    }
179
180    /// Returns a mutable reference to the unique ID
181    /// that will be used if this transaction gets mined into a block.
182    ///
183    /// See [`Self::mined_id`] for details.
184    #[cfg(any(test, feature = "proptest-impl"))]
185    pub fn mined_id_mut(&mut self) -> &mut Hash {
186        match self {
187            Legacy(legacy_id) => legacy_id,
188            Witnessed(wtx_id) => &mut wtx_id.id,
189        }
190    }
191
192    /// Return the digest of this transaction's authorizing data,
193    /// (signatures, proofs, and scripts), if it is a v5 transaction.
194    pub fn auth_digest(&self) -> Option<AuthDigest> {
195        match self {
196            Legacy(_) => None,
197            Witnessed(wtx_id) => Some(wtx_id.auth_digest),
198        }
199    }
200
201    /// Returns a mutable reference to the digest of this transaction's authorizing data,
202    /// (signatures, proofs, and scripts), if it is a v5 transaction.
203    #[cfg(any(test, feature = "proptest-impl"))]
204    pub fn auth_digest_mut(&mut self) -> Option<&mut AuthDigest> {
205        match self {
206            Legacy(_) => None,
207            Witnessed(wtx_id) => Some(&mut wtx_id.auth_digest),
208        }
209    }
210}
211
212/// An unmined transaction, and its pre-calculated unique identifying ID.
213///
214/// This transaction has been structurally verified.
215/// (But it might still need semantic or contextual verification.)
216#[derive(Clone, Eq, PartialEq)]
217pub struct UnminedTx {
218    /// The unmined transaction itself.
219    pub transaction: Arc<Transaction>,
220
221    /// A unique identifier for this unmined transaction.
222    pub id: UnminedTxId,
223
224    /// The size in bytes of the serialized transaction data
225    pub size: usize,
226
227    /// The conventional fee for this transaction, as defined by [ZIP-317].
228    ///
229    /// [ZIP-317]: https://zips.z.cash/zip-0317#fee-calculation
230    pub conventional_fee: Amount<NonNegative>,
231}
232
233impl fmt::Debug for UnminedTx {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        // Logging unmined transactions can leak sensitive user information,
236        // particularly when Zebra is being used as a `lightwalletd` backend.
237        f.debug_tuple("UnminedTx").field(&"private").finish()
238    }
239}
240
241impl fmt::Display for UnminedTx {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_tuple("UnminedTx").field(&"private").finish()
244    }
245}
246
247impl From<Arc<Transaction>> for UnminedTx {
248    fn from(transaction: Arc<Transaction>) -> Self {
249        let size = transaction.zcash_serialized_size();
250        let conventional_fee = zip317::conventional_fee(&transaction);
251
252        Self {
253            id: transaction.as_ref().into(),
254            size,
255            conventional_fee,
256            transaction,
257        }
258    }
259}
260
261impl From<&Arc<Transaction>> for UnminedTx {
262    fn from(transaction: &Arc<Transaction>) -> Self {
263        let size = transaction.zcash_serialized_size();
264        let conventional_fee = zip317::conventional_fee(transaction);
265
266        Self {
267            id: transaction.as_ref().into(),
268            size,
269            conventional_fee,
270            transaction: transaction.clone(),
271        }
272    }
273}
274
275/// A verified unmined transaction, and the corresponding transaction fee.
276///
277/// This transaction has been fully verified, in the context of the mempool.
278//
279// This struct can't be `Eq`, because it contains a `f32`.
280#[derive(Clone, PartialEq)]
281pub struct VerifiedUnminedTx {
282    /// The unmined transaction.
283    pub transaction: UnminedTx,
284
285    /// The transaction fee for this unmined transaction.
286    pub miner_fee: Amount<NonNegative>,
287
288    /// The number of legacy transparent signature operations in this transaction.
289    ///
290    /// This is the legacy sigop count only (`GetLegacySigOpCount()`).
291    /// The mempool adds P2SH sigops (`GetP2SHSigOpCount()`) when checking
292    /// `MAX_STANDARD_TX_SIGOPS`.
293    pub legacy_sigop_count: u32,
294
295    /// The number of P2SH redeem-script signature operations in this transaction.
296    ///
297    /// This mirrors zcashd's `GetP2SHSigOpCount()`. It must be added to `legacy_sigop_count` for
298    /// the block-level `MAX_BLOCK_SIGOPS` check and for `getblocktemplate` sigop budgeting,
299    /// matching zcashd's consensus behavior.
300    pub p2sh_sigop_count: u32,
301
302    /// The number of conventional actions for `transaction`, as defined by [ZIP-317].
303    ///
304    /// The number of actions is limited by [`MAX_BLOCK_BYTES`], so it fits in a u32.
305    ///
306    /// [ZIP-317]: https://zips.z.cash/zip-0317#block-production
307    pub conventional_actions: u32,
308
309    /// The number of unpaid actions for `transaction`,
310    /// as defined by [ZIP-317] for block production.
311    ///
312    /// The number of actions is limited by [`MAX_BLOCK_BYTES`], so it fits in a u32.
313    ///
314    /// [ZIP-317]: https://zips.z.cash/zip-0317#block-production
315    pub unpaid_actions: u32,
316
317    /// The fee weight ratio for `transaction`, as defined by [ZIP-317] for block production.
318    ///
319    /// This is not consensus-critical, so we use `f32` for efficient calculations
320    /// when the mempool holds a large number of transactions.
321    ///
322    /// [ZIP-317]: https://zips.z.cash/zip-0317#block-production
323    pub fee_weight_ratio: f32,
324
325    /// The time the transaction was added to the mempool, or None if it has not
326    /// reached the mempool yet.
327    pub time: Option<chrono::DateTime<chrono::Utc>>,
328
329    /// The tip height when the transaction was added to the mempool, or None if
330    /// it has not reached the mempool yet.
331    pub height: Option<Height>,
332
333    /// The spent outputs for this transaction's transparent inputs.
334    ///
335    /// Used by mempool policy checks (`AreInputsStandard`, `GetP2SHSigOpCount`).
336    /// Empty for transactions with no transparent inputs or in test contexts.
337    pub spent_outputs: Arc<Vec<transparent::Output>>,
338}
339
340impl fmt::Debug for VerifiedUnminedTx {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        // Logging unmined transactions can leak sensitive user information,
343        // particularly when Zebra is being used as a `lightwalletd` backend.
344        f.debug_tuple("VerifiedUnminedTx")
345            .field(&"private")
346            .finish()
347    }
348}
349
350impl fmt::Display for VerifiedUnminedTx {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        f.debug_tuple("VerifiedUnminedTx")
353            .field(&"private")
354            .finish()
355    }
356}
357
358impl VerifiedUnminedTx {
359    /// Create a new verified unmined transaction from an unmined transaction, its miner fee, its
360    /// legacy and P2SH sigop counts, and the spent outputs for its transparent inputs.
361    pub fn new(
362        transaction: UnminedTx,
363        miner_fee: Amount<NonNegative>,
364        legacy_sigop_count: u32,
365        p2sh_sigop_count: u32,
366        spent_outputs: Arc<Vec<transparent::Output>>,
367    ) -> Result<Self, zip317::Error> {
368        let fee_weight_ratio = zip317::conventional_fee_weight_ratio(&transaction, miner_fee);
369        let conventional_actions = zip317::conventional_actions(&transaction.transaction);
370        let unpaid_actions = zip317::unpaid_actions(&transaction, miner_fee);
371
372        zip317::mempool_checks(unpaid_actions, miner_fee, transaction.size)?;
373
374        Ok(Self {
375            transaction,
376            miner_fee,
377            legacy_sigop_count,
378            p2sh_sigop_count,
379            fee_weight_ratio,
380            conventional_actions,
381            unpaid_actions,
382            time: None,
383            height: None,
384            spent_outputs,
385        })
386    }
387
388    /// The total number of transparent signature operations for block-level accounting: legacy +
389    /// P2SH.
390    ///
391    /// This is the value that must be used for the consensus `MAX_BLOCK_SIGOPS` limit and for
392    /// `getblocktemplate` sigop budgeting.
393    pub fn block_sigop_count(&self) -> u32 {
394        self.legacy_sigop_count
395            .saturating_add(self.p2sh_sigop_count)
396    }
397
398    /// Returns `true` if the transaction pays at least the [ZIP-317] conventional fee.
399    ///
400    /// [ZIP-317]: https://zips.z.cash/zip-0317#mempool-size-limiting
401    pub fn pays_conventional_fee(&self) -> bool {
402        self.miner_fee >= self.transaction.conventional_fee
403    }
404
405    /// The cost in bytes of the transaction, as defined in [ZIP-401].
406    ///
407    /// A reflection of the work done by the network in processing them (proof
408    /// and signature verification; networking overheads; size of in-memory data
409    /// structures).
410    ///
411    /// > Each transaction has a cost, which is an integer defined as...
412    ///
413    /// [ZIP-401]: https://zips.z.cash/zip-0401
414    pub fn cost(&self) -> u64 {
415        std::cmp::max(
416            u64::try_from(self.transaction.size).expect("fits in u64"),
417            MEMPOOL_TRANSACTION_COST_THRESHOLD,
418        )
419    }
420
421    /// The computed _eviction weight_ of a verified unmined transaction as part
422    /// of the mempool set, as defined in [ZIP-317] and [ZIP-401].
423    ///
424    /// # Standard Rule
425    ///
426    /// > Each transaction also has an *eviction weight*, which is *cost* + *low_fee_penalty*,
427    /// > where *low_fee_penalty* is 40000 if the transaction pays a fee less than the
428    /// > conventional fee, otherwise 0. The conventional fee is currently defined in
429    /// > [ZIP-317].
430    ///
431    /// > zcashd and zebrad limit the size of the mempool as described in [ZIP-401].
432    /// > This specifies a low fee penalty that is added to the "eviction weight" if the transaction
433    /// > pays a fee less than the conventional transaction fee. This threshold is
434    /// > modified to use the new conventional fee formula.
435    ///
436    /// [ZIP-317]: https://zips.z.cash/zip-0317#mempool-size-limiting
437    /// [ZIP-401]: https://zips.z.cash/zip-0401
438    pub fn eviction_weight(&self) -> u64 {
439        let mut cost = self.cost();
440
441        if !self.pays_conventional_fee() {
442            cost += MEMPOOL_TRANSACTION_LOW_FEE_PENALTY
443        }
444
445        cost
446    }
447}