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 5e25ae5 commit d185e6f
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 46 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
12 changes: 3 additions & 9 deletions zcash_primitives/src/transaction/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ impl<A: ::orchard::bundle::Authorization> OrchardPart for Orchard<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 @@ -218,9 +216,7 @@ impl TransparentEnc for NoTransparent {
}
}

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 @@ -340,9 +336,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 Down
119 changes: 89 additions & 30 deletions zcash_primitives/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,22 +300,67 @@ 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,
B::Transparent: TransparentPart<Authorization = transparent::Authorized> + 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::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 337 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L334-L337

Added lines #L334 - L337 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 363 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L363

Added line #L363 was not covered by tests
self.txid == other.txid
}
}
Expand Down Expand Up @@ -593,14 +638,26 @@ 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)
impl<B> TransactionData<B>
where
B: Bundles,
B::Transparent: TransparentEnc + txid::TransparentDigester,
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,
B::Transparent: TransparentEnc + txid::TransparentDigester,
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 @@ -611,8 +668,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 @@ -622,17 +679,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 692 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L692

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

Expand Down Expand Up @@ -660,7 +717,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 @@ -669,7 +726,7 @@ 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 = if version.has_sprout() {
let joinsplits = Vector::read(&mut reader, |r| {
Expand All @@ -693,13 +750,13 @@ impl Transaction {
};

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 @@ -726,9 +783,9 @@ 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 = if version.has_tze() {
Expand Down Expand Up @@ -809,7 +866,7 @@ 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(),
Expand All @@ -825,7 +882,7 @@ impl Transaction {
}
}

Sapling::write_v4_binding_sig(
B::Sapling::write_v4_binding_sig(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
Expand All @@ -842,7 +899,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 @@ -855,7 +912,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 @@ -878,7 +935,7 @@ 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")]
Expand All @@ -893,7 +950,9 @@ impl Transaction {

Ok(())
}
}

impl Transaction {
// TODO: should this be moved to `from_data` and stored?
pub fn auth_commitment(&self) -> Blake2bHash {
self.data.digest(BlockTxCommitmentDigester)
Expand Down
6 changes: 3 additions & 3 deletions zcash_primitives/src/transaction/txid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>])
h.finalize()
}

trait TransparentDigester: TransparentPart {
pub trait TransparentDigester: TransparentPart {
fn digest(transparent_bundle: Option<&Self::Bundle>)
-> Option<TransparentDigests<Blake2bHash>>;
}
Expand Down Expand Up @@ -275,7 +275,7 @@ pub(crate) fn hash_transparent_txid_data(
h.finalize()
}

trait SaplingDigester: SaplingPart {
pub trait SaplingDigester: SaplingPart {
fn digest(sapling_bundle: Option<&Self::Bundle>) -> Option<Blake2bHash>;
}

Expand Down Expand Up @@ -312,7 +312,7 @@ fn hash_sapling_txid_empty() -> Blake2bHash {
hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize()
}

trait OrchardDigester: OrchardPart {
pub trait OrchardDigester: OrchardPart {
fn digest(orchard_bundle: Option<&Self::Bundle>) -> Option<Blake2bHash>;
}

Expand Down

0 comments on commit d185e6f

Please sign in to comment.