Create types for unmined transactions and their IDs (#2634)

* Create new types for unmined transactions and their IDs

* Add accessor methods for the parts of an unmined transaction ID
This commit is contained in:
teor 2021-08-18 15:52:42 +10:00 committed by GitHub
parent 5c5abf6171
commit 84c5f6189d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 199 additions and 2 deletions

View File

@ -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},

View File

@ -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 {

View File

@ -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<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 {
/// 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<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(),
}
}
}