//! Unmined Zcash transaction identifiers and transactions. //! //! Transaction version 5 is uniquely identified by [`WtxId`] when unmined, //! and [`Hash`] in the blockchain. The effects of a v5 transaction (spends and outputs) //! are uniquely identified by the same [`Hash`] in both cases. //! //! Transaction versions 1-4 are uniquely identified by legacy [`Hash`] transaction IDs, //! whether they have been mined or not. So Zebra, and the Zcash network protocol, //! don't use witnessed transaction IDs for them. //! //! Zebra's [`UnminedTxId`] and [`UnminedTx`] enums provide the correct unique ID for //! unmined transactions. They can be used to handle transactions regardless of version, //! and get the [`WtxId`] or [`Hash`] when required. use std::{fmt, sync::Arc}; #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; use super::{ AuthDigest, Hash, Transaction::{self, *}, WtxId, }; use UnminedTxId::*; /// A unique identifier for an unmined transaction, regardless of version. /// /// "The transaction ID of a version 4 or earlier transaction is the SHA-256d hash /// of the transaction encoding in the pre-v5 format described above. /// /// The transaction ID of a version 5 transaction is as defined in [ZIP-244]. /// /// A v5 transaction also has a wtxid (used for example in the peer-to-peer protocol) /// as defined in [ZIP-239]." /// [Spec: Transaction Identifiers] /// /// [ZIP-239]: https://zips.z.cash/zip-0239 /// [ZIP-244]: https://zips.z.cash/zip-0244 /// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub enum UnminedTxId { /// A legacy unmined transaction identifier. /// /// Used to uniquely identify unmined version 1-4 transactions. /// (After v1-4 transactions are mined, they can be uniquely identified /// using the same [`transaction::Hash`].) Legacy(Hash), /// A witnessed unmined transaction identifier. /// /// Used to uniquely identify unmined version 5 transactions. /// (After v5 transactions are mined, they can be uniquely identified /// using only the [`transaction::Hash`] in their `WtxId.id`.) /// /// For more details, see [`WtxId`]. Witnessed(WtxId), } impl From for UnminedTxId { fn from(transaction: Transaction) -> Self { // use the ref implementation, to avoid cloning the transaction UnminedTxId::from(&transaction) } } impl From<&Transaction> for UnminedTxId { fn from(transaction: &Transaction) -> Self { match transaction { V1 { .. } | V2 { .. } | V3 { .. } | V4 { .. } => Legacy(transaction.into()), V5 { .. } => Witnessed(transaction.into()), } } } impl From> for UnminedTxId { fn from(transaction: Arc) -> Self { transaction.as_ref().into() } } impl From for UnminedTxId { fn from(wtx_id: WtxId) -> Self { Witnessed(wtx_id) } } impl From<&WtxId> for UnminedTxId { fn from(wtx_id: &WtxId) -> Self { (*wtx_id).into() } } impl fmt::Display for UnminedTxId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Legacy(hash) => hash.fmt(f), Witnessed(id) => id.fmt(f), } } } impl UnminedTxId { /// Create a new `UnminedTxId` using a v1-v4 legacy transaction ID. /// /// # Correctness /// /// This method must only be used for v1-v4 transaction IDs. /// [`Hash`] does not uniquely identify unmined v5 transactions. #[allow(dead_code)] pub fn from_legacy_id(legacy_tx_id: Hash) -> UnminedTxId { Legacy(legacy_tx_id) } /// Return the unique ID that will be used if this transaction gets mined into a block. /// /// # Correctness /// /// For v1-v4 transactions, this method returns an ID which changes /// if this transaction's effects (spends and outputs) change, or /// if its authorizing data changes (signatures, proofs, and scripts). /// /// But for v5 transactions, this ID uniquely identifies the transaction's effects. #[allow(dead_code)] pub fn mined_id(&self) -> Hash { match self { Legacy(legacy_id) => *legacy_id, Witnessed(wtx_id) => wtx_id.id, } } /// Return the digest of this transaction's authorizing data, /// (signatures, proofs, and scripts), if it is a v5 transaction. #[allow(dead_code)] pub fn auth_digest(&self) -> Option { match self { Legacy(_) => None, Witnessed(wtx_id) => Some(wtx_id.auth_digest), } } } /// An unmined transaction, and its pre-calculated unique identifying ID. #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct UnminedTx { /// A unique identifier for this unmined transaction. pub id: UnminedTxId, /// The unmined transaction itself. pub transaction: Arc, } // Each of these conversions is implemented slightly differently, // to avoid cloning the transaction where possible. impl From for UnminedTx { fn from(transaction: Transaction) -> Self { Self { id: (&transaction).into(), transaction: Arc::new(transaction), } } } impl From<&Transaction> for UnminedTx { fn from(transaction: &Transaction) -> Self { Self { id: transaction.into(), transaction: Arc::new(transaction.clone()), } } } impl From> for UnminedTx { fn from(transaction: Arc) -> Self { Self { id: transaction.as_ref().into(), transaction, } } } impl From<&Arc> for UnminedTx { fn from(transaction: &Arc) -> Self { Self { id: transaction.as_ref().into(), transaction: transaction.clone(), } } }