From 47189947d2b57a8ca72e2beaf0d4c104c45adc19 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 11 May 2024 01:24:42 +0000 Subject: [PATCH] zcash_primitives: Allow tx serialization with omitted bundles --- zcash_client_sqlite/src/wallet/init.rs | 4 +- .../init/migrations/add_transaction_views.rs | 4 +- zcash_extensions/src/transparent/demo.rs | 2 +- zcash_primitives/src/transaction/mod.rs | 156 ++++++++++++++---- 4 files changed, 127 insertions(+), 39 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9fff7e3a3f..2394cc5686 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -340,7 +340,7 @@ mod tests { consensus::{ self, BlockHeight, BranchId, Network, NetworkConstants, NetworkUpgrade, Parameters, }, - transaction::{TransactionData, TxVersion}, + transaction::{Transaction, TransactionData, TxVersion}, zip32::AccountId, }; @@ -1266,7 +1266,7 @@ mod tests { [], )?; - let tx = TransactionData::from_parts( + let tx: Transaction = TransactionData::from_parts( TxVersion::Sapling, BranchId::Canopy, 0, diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs index a84f99a05b..08174aa0f5 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs @@ -295,7 +295,7 @@ mod tests { transparent::{self, Authorized, OutPoint}, Amount, TxIn, TxOut, }, - TransactionData, TxVersion, + Transaction, TransactionData, TxVersion, }, }, }; @@ -413,7 +413,7 @@ mod tests { .unwrap(); // create a UTXO to spend - let tx = TransactionData::from_parts( + let tx: Transaction = TransactionData::from_parts( TxVersion::Sapling, BranchId::Canopy, 0, diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 40389cbb31..03d53b536a 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -673,7 +673,7 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), }; - let tx_a = TransactionData::from_parts_zfuture( + let tx_a: Transaction = TransactionData::from_parts_zfuture( TxVersion::ZFuture, BranchId::ZFuture, 0, diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 1b37c31921..40803a12e6 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -299,22 +299,69 @@ impl Authorization for Unauthorized { } /// A Zcash transaction. -#[derive(Debug)] -pub struct Transaction { +pub type Transaction = TransactionWith>; + +/// An authorized Zcash transaction with configurable support in the type system for the +/// various transparent and shielded pools. +/// +/// If a particular pool is disabled (by using e.g. [`components::NoTransparent`]): +/// - The pool-specific APIs will be inaccessible. +/// - The serialization APIs will encode an empty bundle for that pool. +/// - The parsing APIs will return an error when parsing a transaction with spends or +/// outputs in that pool. +pub struct TransactionWith +where + B: Bundles, + B::Transparent: AuthorizedTransparentPart, + B::Sapling: AuthorizedSaplingPart, + B::Orchard: AuthorizedOrchardPart, +{ txid: TxId, - data: TransactionData>, + data: TransactionData, } -impl Deref for Transaction { - type Target = TransactionData>; +impl fmt::Debug for TransactionWith +where + B: Bundles + fmt::Debug + FutureDebug, + B::Transparent: AuthorizedTransparentPart + fmt::Debug, + B::Sprout: AuthorizedSproutPart + fmt::Debug, + B::Sapling: AuthorizedSaplingPart + fmt::Debug, + B::Orchard: AuthorizedOrchardPart + fmt::Debug, + ::Bundle: fmt::Debug, + ::Bundle: fmt::Debug, + ::Bundle: fmt::Debug, + ::Bundle: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TransactionWith") + .field("txid", &self.txid) + .field("data", &self.data) + .finish() + } +} - fn deref(&self) -> &TransactionData> { +impl Deref for TransactionWith +where + B: Bundles, + B::Transparent: AuthorizedTransparentPart, + B::Sapling: AuthorizedSaplingPart, + B::Orchard: AuthorizedOrchardPart, +{ + type Target = TransactionData; + + fn deref(&self) -> &TransactionData { &self.data } } -impl PartialEq for Transaction { - fn eq(&self, other: &Transaction) -> bool { +impl PartialEq for TransactionWith +where + B: Bundles, + B::Transparent: AuthorizedTransparentPart, + B::Sapling: AuthorizedSaplingPart, + B::Orchard: AuthorizedOrchardPart, +{ + fn eq(&self, other: &Self) -> bool { self.txid == other.txid } } @@ -620,14 +667,47 @@ impl>> Tran } } -impl TransactionData> { - pub fn freeze(self) -> io::Result { - Transaction::from_data(self) +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +pub trait FutureAuthorized {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureAuthorized for B {} + +#[cfg(zcash_unstable = "zfuture")] +pub trait FutureAuthorized: txid::FutureBundles { + type FutureEncTze: AuthorizedTzePart; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureAuthorized for B +where + B: txid::FutureBundles, + B::FutureTze: AuthorizedTzePart, +{ + type FutureEncTze = B::FutureTze; +} + +impl TransactionData +where + B: Bundles + FutureAuthorized, + B::Transparent: AuthorizedTransparentPart + txid::TransparentDigester, + B::Sprout: AuthorizedSproutPart, + B::Sapling: AuthorizedSaplingPart + txid::SaplingDigester, + B::Orchard: AuthorizedOrchardPart + txid::OrchardDigester, +{ + pub fn freeze(self) -> io::Result> { + TransactionWith::from_data(self) } } -impl Transaction { - fn from_data(data: TransactionData>) -> io::Result { +impl TransactionWith +where + B: Bundles + FutureAuthorized, + B::Transparent: AuthorizedTransparentPart + txid::TransparentDigester, + B::Sprout: AuthorizedSproutPart, + B::Sapling: AuthorizedSaplingPart + txid::SaplingDigester, + B::Orchard: AuthorizedOrchardPart + txid::OrchardDigester, +{ + fn from_data(data: TransactionData) -> io::Result { match data.version { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { Self::from_data_v4(data) @@ -638,8 +718,8 @@ impl Transaction { } } - fn from_data_v4(data: TransactionData>) -> io::Result { - let mut tx = Transaction { + fn from_data_v4(data: TransactionData) -> io::Result { + let mut tx = TransactionWith { txid: TxId([0; 32]), data, }; @@ -649,17 +729,17 @@ impl Transaction { Ok(tx) } - fn from_data_v5(data: TransactionData>) -> Self { + fn from_data_v5(data: TransactionData) -> Self { let txid = to_txid( data.version, data.consensus_branch_id, &data.digest(TxIdDigester), ); - Transaction { txid, data } + TransactionWith { txid, data } } - pub fn into_data(self) -> TransactionData> { + pub fn into_data(self) -> TransactionData { self.data } @@ -687,7 +767,7 @@ impl Transaction { version: TxVersion, consensus_branch_id: BranchId, ) -> io::Result { - let transparent_bundle = Transparent::read_bundle(&mut reader)?; + let transparent_bundle = B::Transparent::read_bundle(&mut reader)?; let lock_time = reader.read_u32::()?; let expiry_height: BlockHeight = if version.has_overwinter() { @@ -696,19 +776,19 @@ impl Transaction { 0u32.into() }; - let components = Sapling::read_v4_components(&mut reader, version.has_sapling())?; + let components = B::Sapling::read_v4_components(&mut reader, version.has_sapling())?; let sprout_bundle = - Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?; + B::Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?; let sapling_bundle = - Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?; + B::Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?; let mut txid = [0; 32]; let hash_bytes = reader.into_hash(); txid.copy_from_slice(&hash_bytes); - Ok(Transaction { + Ok(TransactionWith { txid: TxId(txid), data: TransactionData { version, @@ -735,12 +815,12 @@ impl Transaction { fn read_v5(mut reader: R, version: TxVersion) -> io::Result { let (consensus_branch_id, lock_time, expiry_height) = Self::read_v5_header_fragment(&mut reader)?; - let transparent_bundle = Transparent::read_bundle(&mut reader)?; - let sapling_bundle = Sapling::read_v5_bundle(&mut reader)?; - let orchard_bundle = Orchard::read_v5_bundle(&mut reader)?; + let transparent_bundle = B::Transparent::read_bundle(&mut reader)?; + let sapling_bundle = B::Sapling::read_v5_bundle(&mut reader)?; + let orchard_bundle = B::Orchard::read_v5_bundle(&mut reader)?; #[cfg(zcash_unstable = "zfuture")] - let tze_bundle = Tze::read_bundle(&mut reader, version.has_tze())?; + let tze_bundle = B::Tze::read_bundle(&mut reader, version.has_tze())?; let data = TransactionData { version, @@ -799,19 +879,19 @@ impl Transaction { writer.write_u32::(u32::from(self.expiry_height))?; } - Sapling::write_v4_components( + B::Sapling::write_v4_components( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), )?; - Sprout::write_v4_bundle( + B::Sprout::write_v4_bundle( self.sprout_bundle.as_ref(), &mut writer, self.version.has_sprout(), )?; - Sapling::write_v4_binding_sig( + B::Sapling::write_v4_binding_sig( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), @@ -828,7 +908,7 @@ impl Transaction { } pub fn write_transparent(&self, writer: W) -> io::Result<()> { - Transparent::write_bundle(self.transparent_bundle.as_ref(), writer) + B::Transparent::write_bundle(self.transparent_bundle.as_ref(), writer) } pub fn write_v5(&self, mut writer: W) -> io::Result<()> { @@ -841,7 +921,7 @@ impl Transaction { self.write_v5_header(&mut writer)?; self.write_transparent(&mut writer)?; self.write_v5_sapling(&mut writer)?; - Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?; + B::Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?; #[cfg(zcash_unstable = "zfuture")] self.write_tze(&mut writer)?; Ok(()) @@ -864,14 +944,22 @@ impl Transaction { } pub fn write_v5_sapling(&self, writer: W) -> io::Result<()> { - Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer) + B::Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer) } #[cfg(zcash_unstable = "zfuture")] pub fn write_tze(&self, writer: W) -> io::Result<()> { - Tze::write_bundle(self.tze_bundle.as_ref(), writer, self.version.has_tze()) + B::Tze::write_bundle(self.tze_bundle.as_ref(), writer, self.version.has_tze()) } +} +impl TransactionWith +where + B: Bundles + txid::FutureWitnesses, + B::Transparent: txid::TransparentWitnessDigester, + B::Sapling: txid::SaplingWitnessDigester, + B::Orchard: txid::OrchardWitnessDigester, +{ // TODO: should this be moved to `from_data` and stored? pub fn auth_commitment(&self) -> Blake2bHash { self.data.digest(BlockTxCommitmentDigester)