Skip to content

Commit

Permalink
zcash_primitives: Allow tx serialization with omitted bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed May 13, 2024
1 parent 70bea27 commit 443855e
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 62 deletions.
4 changes: 2 additions & 2 deletions zcash_client_sqlite/src/wallet/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ mod tests {
consensus::{
self, BlockHeight, BranchId, Network, NetworkConstants, NetworkUpgrade, Parameters,
},
transaction::{TransactionData, TxVersion},
transaction::{Transaction, TransactionData, TxVersion},
zip32::AccountId,
};

Expand Down Expand Up @@ -1266,7 +1266,7 @@ mod tests {
[],
)?;

let tx = TransactionData::from_parts(
let tx: Transaction = TransactionData::from_parts(
TxVersion::Sapling,
BranchId::Canopy,
0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ mod tests {
transparent::{self, Authorized, OutPoint},
Amount, TxIn, TxOut,
},
TransactionData, TxVersion,
Transaction, TransactionData, TxVersion,
},
},
};
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion zcash_extensions/src/transparent/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 5 additions & 11 deletions zcash_primitives/src/transaction/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,7 @@ impl<A: tze::Authorization> TzePart for Tze<A> {
type Authorization = A;
}

pub(super) trait TransparentEnc:
TransparentPart<Authorization = transparent::Authorized>
{
pub trait TransparentEnc: TransparentPart<Authorization = transparent::Authorized> {
fn read<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;

fn write<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
Expand Down Expand Up @@ -295,7 +293,7 @@ impl TransparentEnc for NoTransparent {
}
}

pub(super) trait SproutEnc: SproutPart {
pub trait SproutEnc: SproutPart {
fn read_v4_bundle<R: io::Read>(
reader: R,
tx_has_sprout: bool,
Expand Down Expand Up @@ -341,9 +339,7 @@ impl SproutEnc for NoSprout {
}
}

pub(super) trait SaplingEnc:
SaplingPart<Authorization = ::sapling::bundle::Authorized>
{
pub trait SaplingEnc: SaplingPart<Authorization = ::sapling::bundle::Authorized> {
type V4Components;

fn read_v4_components<R: io::Read>(
Expand Down Expand Up @@ -463,9 +459,7 @@ impl SaplingEnc for NoSapling {
}
}

pub(super) trait OrchardEnc:
OrchardPart<Authorization = ::orchard::bundle::Authorized>
{
pub trait OrchardEnc: OrchardPart<Authorization = ::orchard::bundle::Authorized> {
fn read_v5_bundle<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;

fn write_v5_bundle<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
Expand All @@ -488,7 +482,7 @@ impl OrchardEnc for NoOrchard {
}

#[cfg(zcash_unstable = "zfuture")]
pub(super) trait TzeEnc: TzePart<Authorization = tze::Authorized> {
pub trait TzeEnc: TzePart<Authorization = tze::Authorized> {
fn read_bundle<R: io::Read>(reader: R, tx_has_tze: bool) -> io::Result<Option<Self::Bundle>>;

fn write_bundle<W: io::Write>(
Expand Down
156 changes: 122 additions & 34 deletions zcash_primitives/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,22 +299,69 @@ impl Authorization for Unauthorized {
}

/// A Zcash transaction.
#[derive(Debug)]
pub struct Transaction {
pub type Transaction = TransactionWith<AllBundles<Authorized>>;

/// 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<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
txid: TxId,
data: TransactionData<AllBundles<Authorized>>,
data: TransactionData<B>,
}

impl Deref for Transaction {
type Target = TransactionData<AllBundles<Authorized>>;
impl<B> fmt::Debug for TransactionWith<B>
where
B: Bundles + fmt::Debug + FutureDebug,
B::Transparent: TransparentPart<Authorization = transparent::Authorized> + fmt::Debug,
B::Sprout: SproutPart + fmt::Debug,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized> + fmt::Debug,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized> + fmt::Debug,
<B::Transparent as TransparentPart>::Bundle: fmt::Debug,
<B::Sprout as SproutPart>::Bundle: fmt::Debug,
<B::Sapling as SaplingPart>::Bundle: fmt::Debug,
<B::Orchard as OrchardPart>::Bundle: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionWith")
.field("txid", &self.txid)
.field("data", &self.data)

Check warning on line 338 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L335-L338

Added lines #L335 - L338 were not covered by tests
.finish()
}
}

fn deref(&self) -> &TransactionData<AllBundles<Authorized>> {
impl<B> Deref for TransactionWith<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
type Target = TransactionData<B>;

fn deref(&self) -> &TransactionData<B> {
&self.data
}
}

impl PartialEq for Transaction {
fn eq(&self, other: &Transaction) -> bool {
impl<B> PartialEq for TransactionWith<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
fn eq(&self, other: &Self) -> bool {

Check warning on line 364 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L364

Added line #L364 was not covered by tests
self.txid == other.txid
}
}
Expand Down Expand Up @@ -620,14 +667,47 @@ impl<SA: sapling::bundle::Authorization, B: Bundles<Sapling = Sapling<SA>>> Tran
}
}

impl TransactionData<AllBundles<Authorized>> {
pub fn freeze(self) -> io::Result<Transaction> {
Transaction::from_data(self)
/// Trait marker for bounds that are not yet required by consensus rules.
#[cfg(not(zcash_unstable = "zfuture"))]
pub trait FutureEnc {}
#[cfg(not(zcash_unstable = "zfuture"))]
impl<B: Bundles> FutureEnc for B {}

#[cfg(zcash_unstable = "zfuture")]
pub trait FutureEnc: txid::FutureBundles<FutureTze = Self::FutureEncTze> {
type FutureEncTze: TzeEnc;
}
#[cfg(zcash_unstable = "zfuture")]
impl<B> FutureEnc for B
where
B: txid::FutureBundles,
B::FutureTze: TzeEnc,
{
type FutureEncTze = B::FutureTze;
}

impl<B> TransactionData<B>
where
B: Bundles + FutureEnc,
B::Transparent: TransparentEnc + txid::TransparentDigester,
B::Sprout: SproutEnc,
B::Sapling: SaplingEnc + txid::SaplingDigester,
B::Orchard: OrchardEnc + txid::OrchardDigester,
{
pub fn freeze(self) -> io::Result<TransactionWith<B>> {
TransactionWith::from_data(self)
}
}

impl Transaction {
fn from_data(data: TransactionData<AllBundles<Authorized>>) -> io::Result<Self> {
impl<B> TransactionWith<B>
where
B: Bundles + FutureEnc,
B::Transparent: TransparentEnc + txid::TransparentDigester,
B::Sprout: SproutEnc,
B::Sapling: SaplingEnc + txid::SaplingDigester,
B::Orchard: OrchardEnc + txid::OrchardDigester,
{
fn from_data(data: TransactionData<B>) -> io::Result<Self> {
match data.version {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
Self::from_data_v4(data)
Expand All @@ -638,8 +718,8 @@ impl Transaction {
}
}

fn from_data_v4(data: TransactionData<AllBundles<Authorized>>) -> io::Result<Self> {
let mut tx = Transaction {
fn from_data_v4(data: TransactionData<B>) -> io::Result<Self> {
let mut tx = TransactionWith {
txid: TxId([0; 32]),
data,
};
Expand All @@ -649,17 +729,17 @@ impl Transaction {
Ok(tx)
}

fn from_data_v5(data: TransactionData<AllBundles<Authorized>>) -> Self {
fn from_data_v5(data: TransactionData<B>) -> 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<AllBundles<Authorized>> {
pub fn into_data(self) -> TransactionData<B> {

Check warning on line 742 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L742

Added line #L742 was not covered by tests
self.data
}

Expand Down Expand Up @@ -687,7 +767,7 @@ impl Transaction {
version: TxVersion,
consensus_branch_id: BranchId,
) -> io::Result<Self> {
let transparent_bundle = Transparent::read(&mut reader)?;
let transparent_bundle = B::Transparent::read(&mut reader)?;

let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height: BlockHeight = if version.has_overwinter() {
Expand All @@ -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,
Expand All @@ -735,12 +815,12 @@ impl Transaction {
fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
let (consensus_branch_id, lock_time, expiry_height) =
Self::read_v5_header_fragment(&mut reader)?;
let transparent_bundle = Transparent::read(&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(&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())?;

Check warning on line 823 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L823

Added line #L823 was not covered by tests

let data = TransactionData {
version,
Expand Down Expand Up @@ -799,19 +879,19 @@ impl Transaction {
writer.write_u32::<LittleEndian>(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(),
Expand All @@ -828,7 +908,7 @@ impl Transaction {
}

pub fn write_transparent<W: Write>(&self, writer: W) -> io::Result<()> {
Transparent::write(self.transparent_bundle.as_ref(), writer)
B::Transparent::write(self.transparent_bundle.as_ref(), writer)
}

pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
Expand All @@ -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(())
Expand All @@ -864,14 +944,22 @@ impl Transaction {
}

pub fn write_v5_sapling<W: Write>(&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<W: Write>(&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())

Check warning on line 952 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L951-L952

Added lines #L951 - L952 were not covered by tests
}
}

impl<B> TransactionWith<B>
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)
Expand Down
Loading

0 comments on commit 443855e

Please sign in to comment.