Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create types for unmined transactions and their IDs #2634

Merged
merged 8 commits into from
Aug 18, 2021
5 changes: 3 additions & 2 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod memo;
mod serialize;
mod sighash;
mod txid;
mod unmined;

#[cfg(any(test, feature = "proptest-impl"))]
pub mod arbitrary;
Expand All @@ -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},
Expand Down
22 changes: 22 additions & 0 deletions zebra-chain/src/transaction/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
//! (transactions that are sent by wallets or stored in node mempools).
//!
//! Transaction versions 1-4 are uniquely identified by narrow transaction IDs,
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
//! 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},
Expand All @@ -16,6 +21,7 @@ use std::{

#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;

use serde::{Deserialize, Serialize};

use crate::serialization::{
Expand All @@ -29,6 +35,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]);
Expand Down Expand Up @@ -114,6 +129,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 {
Expand Down
171 changes: 171 additions & 0 deletions zebra-chain/src/transaction/unmined.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! Unmined Zcash transaction identifiers and transactions.
//!
//! Transaction versions 1-4 are uniquely identified by narrow transaction IDs,
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
//! whether they have been mined or not,
//! so Zebra and the Zcash network protocol don't use wide transaction IDs for them.
//!
jvff marked this conversation as resolved.
Show resolved Hide resolved
//! 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::*;
jvff marked this conversation as resolved.
Show resolved Hide resolved

/// 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<Transaction> 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<WtxId> 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 {
/// Return a new `UnminedTxId` using a v1-v4 legacy transaction ID.
jvff marked this conversation as resolved.
Show resolved Hide resolved
///
/// # 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 and authorizing data for v1-v4 transactions.
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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,
/// if it is a v5 transaction.
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
#[allow(dead_code)]
pub fn auth_digest(&self) -> Option<AuthDigest> {
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<Transaction>,
}

// Each of these conversions is implemented slightly differently,
// to avoid cloning the transaction where possible.

impl From<Transaction> 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<Arc<Transaction>> for UnminedTx {
fn from(transaction: Arc<Transaction>) -> Self {
Self {
id: transaction.as_ref().into(),
transaction,
}
}
}

impl From<&Arc<Transaction>> for UnminedTx {
fn from(transaction: &Arc<Transaction>) -> Self {
Self {
id: transaction.as_ref().into(),
transaction: transaction.clone(),
}
}
}