diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 1f268b7d2..cc002d511 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -11,6 +11,7 @@ mod memo; mod serialize; mod sighash; mod txid; +mod unmined; #[cfg(any(test, feature = "proptest-impl"))] pub mod arbitrary; @@ -23,8 +24,8 @@ pub use joinsplit::JoinSplitData; pub use lock_time::LockTime; pub use memo::Memo; pub use sapling::FieldNotPresent; -pub use sighash::HashType; -pub use sighash::SigHash; +pub use sighash::{HashType, SigHash}; +pub use unmined::{UnminedTx, UnminedTxId}; use crate::{ amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative}, diff --git a/zebra-chain/src/transaction/hash.rs b/zebra-chain/src/transaction/hash.rs index c1291adbb..9d1c57b21 100644 --- a/zebra-chain/src/transaction/hash.rs +++ b/zebra-chain/src/transaction/hash.rs @@ -6,8 +6,14 @@ //! * [`WtxId`]: a 64-byte wide transaction ID, which uniquely identifies unmined transactions //! (transactions that are sent by wallets or stored in node mempools). //! +//! Transaction version 5 is uniquely identified by [`WtxId`] when unmined, and [`Hash`] in the blockchain. //! Transaction versions 1-4 are uniquely identified by narrow transaction IDs, +//! whether they have been mined or not, //! so Zebra and the Zcash network protocol don't use wide 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::{ convert::{TryFrom, TryInto}, @@ -16,6 +22,7 @@ use std::{ #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; + use serde::{Deserialize, Serialize}; use crate::serialization::{ @@ -29,6 +36,15 @@ use super::{txid::TxIdBuilder, AuthDigest, Transaction}; /// /// Note: Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. +/// +/// "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]." +/// [Spec: Transaction Identifiers] +/// +/// [ZIP-244]: https://zips.z.cash/zip-0244 +/// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers #[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Hash(pub [u8; 32]); @@ -114,6 +130,13 @@ impl ZcashDeserialize for Hash { /// A wide transaction ID, which uniquely identifies unmined v5 transactions. /// /// Wide transaction IDs are not used for transaction versions 1-4. +/// +/// "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 +/// [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 struct WtxId { diff --git a/zebra-chain/src/transaction/unmined.rs b/zebra-chain/src/transaction/unmined.rs new file mode 100644 index 000000000..e5b9afa42 --- /dev/null +++ b/zebra-chain/src/transaction/unmined.rs @@ -0,0 +1,173 @@ +//! Unmined Zcash transaction identifiers and transactions. +//! +//! Transaction version 5 is uniquely identified by [`WtxId`] when unmined, and [`Hash`] in the blockchain. +//! Transaction versions 1-4 are uniquely identified by narrow transaction IDs, +//! whether they have been mined or not, +//! so Zebra and the Zcash network protocol don't use wide 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::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 narrow 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`].) + Narrow(Hash), + + /// A wide unmined transaction identifier. + /// + /// Used to uniquely identify unmined version 5 transactions. + /// (After v5 transactions are mined, they can be uniquely identified using only their `WtxId.id`.) + /// + /// For more details, see [`WtxId`]. + Wide(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 { .. } => Narrow(transaction.into()), + V5 { .. } => Wide(transaction.into()), + } + } +} + +impl From for UnminedTxId { + fn from(wtx_id: WtxId) -> Self { + Wide(wtx_id) + } +} + +impl From<&WtxId> for UnminedTxId { + fn from(wtx_id: &WtxId) -> Self { + (*wtx_id).into() + } +} + +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 { + Narrow(legacy_tx_id) + } + + /// Return the unique ID for this transaction's effects. + /// + /// # Correctness + /// + /// This method returns an ID which uniquely identifies + /// the effects (spends and outputs) and + /// authorizing data (signatures, proofs, and scripts) for v1-v4 transactions. + /// + /// But for v5 transactions, this ID only identifies the transaction's effects. + #[allow(dead_code)] + pub fn effect_id(&self) -> Hash { + match self { + Narrow(effect_id) => *effect_id, + Wide(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 { + Narrow(_effect_id) => None, + Wide(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(), + } + } +}