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

ZSA integration (step 3): Add initial Transaction V6 support to Zebra (currently copies V5 behavior) #16

Open
wants to merge 39 commits into
base: zsa-integration-zsadeps
Choose a base branch
from

Conversation

dmidem
Copy link

@dmidem dmidem commented Oct 17, 2024

This PR introduces initial support for Transaction version 6 (Tx V6) in the Zebra codebase as part of the broader effort to add ZSA (Zcash Shielded Assets) support.

Key changes

  • Added a new V6 variant to the Transaction enum in the zebra-chain crate, currently replicating the behavior of V5.
  • Updated relevant code paths across the project to handle the new V6 variant, ensuring basic compatibility.
  • Tx V6 currently functions as a copy of Tx V5 (including tests); its behavior will be updated in future PRs to reflect the actual V6 specification, incorporating ZSA-specific features and changes.
  • Placeholder/dummy values are used for activation height, group ID, etc., which will be replaced with proper values in future updates.

Notes

  • This implementation provides a foundational structure for Tx V6 but does not yet include ZSA-specific features, new transaction structures, or detailed serialization/deserialization changes.
  • Future work will focus on completing Tx V6 support with ZSA-specific elements, proper configuration values, and full integration into the consensus layer.

Next steps

  • Address any FIXME comments in the code.
  • Converted zebra-chain::orchard::ShieldedData struct to be a generic that supports both V5 and V6.
  • Implement serialization/deserialization for V6 (previous work from the /tx_v6_with_ser_1.8.0 branch can be utilized).
  • Update Tx V6 to perform its intended behaviors distinct from V5 in subsequent PRs.
  • Implement verification processes for Tx V6 transactions.
  • Add tests for Tx V6 transactions.

dmidem added 25 commits August 18, 2024 21:47
This commit introduces basic support for Transaction version 6 (Tx V6). This initial implementation treats Tx V6
as a simple copy of Tx V5, without yet integrating ZSA-specific features or the new transaction structure.

- Added a new V6 variant to the Transaction enum in the zebra-chain crate.
- Updated relevant code to handle the new V6 variant.

Note: Tests and additional adjustments are still pending, and will be addressed in subsequent commits.
@dmidem dmidem changed the title Step3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Stepv3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Oct 17, 2024
@dmidem dmidem changed the title Stepv3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Step 3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Oct 17, 2024
@dmidem dmidem requested a review from PaulLaux October 17, 2024 09:11
@dmidem dmidem changed the title Step 3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) ZSA integration, step 3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Oct 18, 2024
@dmidem dmidem changed the title ZSA integration, step 3: Add initial Transaction V6 support to Zebra (currently copies V5 behavior) ZSA integration (step 3): Add initial Transaction V6 support to Zebra (currently copies V5 behavior) Oct 18, 2024
@dmidem dmidem deleted the branch zsa-integration-zsadeps October 29, 2024 12:28
@dmidem dmidem closed this Oct 29, 2024
@dmidem dmidem reopened this Oct 29, 2024
@PaulLaux PaulLaux requested a review from arya2 October 31, 2024 15:06
Copy link

@PaulLaux PaulLaux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some comments

/// Orchard transactions must use transaction version 5 and this version
/// group ID.
// FIXME: use a proper value!
pub const TX_V6_VERSION_GROUP_ID: u32 = 0x26A7_270B;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a random number in the meanwhile.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: It's 0x124A69F8 in zcash_primitives, if that was random, let's use that so it won't need an update.

Copy link
Author

@dmidem dmidem Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to 0x124A69F8 in #37. Link to this comment copied to #37.

Copy link
Author

@dmidem dmidem Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporarily changed to 0x7777_7777 to align with the a new value used in librustzcash (zcash_primitives) and test vectors. Kept a FIXME comment to change it in the future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporarily changed to 0x124A_69F8 to align with the a value used in OLD version of librustzcash (zcash_primitives) and test vectors. Kept a FIXME comment to change it in the future.

@@ -142,6 +142,28 @@ pub enum Transaction {
/// The orchard data for this transaction, if any.
orchard_shielded_data: Option<orchard::ShieldedData>,
},
// FIXME: implement V6 properly (now it's just a coipy of V5)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a copy

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the changed-files and codespell lint jobs from here may be trivial and help catch typos.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

@@ -142,6 +142,28 @@ pub enum Transaction {
/// The orchard data for this transaction, if any.
orchard_shielded_data: Option<orchard::ShieldedData>,
},
// FIXME: implement V6 properly (now it's just a coipy of V5)
/// A `version = 6` transaction , which supports Orchard ZSA, Orchard Vanille, Sapling and

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which supports OrchardZSA, Orchard, Sapling ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

// FIXME: implement V6 properly (now it's just a coipy of V5)
/// A `version = 6` transaction , which supports Orchard ZSA, Orchard Vanille, Sapling and
/// transparent, but not Sprout.
V6 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we put this behind a feature flag?
(this and all subsequent V6 handling)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #37.

}
| Transaction::V6 {
sapling_shielded_data: None,
..

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify the entire function:

   /// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
    /// regardless of version.
    pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
        // This function returns a boxed iterator because the different
        // transaction variants end up having different iterator types
        match self {
            Transaction::V4 {
                sapling_shielded_data: Some(sapling_shielded_data),
                ..
            } => Box::new(sapling_shielded_data.anchors()),

            Transaction::V5 {
                sapling_shielded_data: Some(sapling_shielded_data),
                ..
            } => Box::new(sapling_shielded_data.anchors()),

            Transaction::V6 {
                sapling_shielded_data: Some(sapling_shielded_data),
                ..
            } => Box::new(sapling_shielded_data.anchors()),

        // No Spends (V1, V2, V3 and V4, V5, V6 without sapling shielded data)
        _ => Box::new(iter::empty()),
    }
}

(x4 for all sapling functions)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arya2 what do you think about this simplification across the entire file?
This will significantly reduce boilerplate without affecting semantics.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplification looks nice, I'd lean towards whatever's easiest since it all needs to be refactored to a new-type around the zcash_primitives Transaction type soon, and if there's no difference in difficulty, I'd go with:

use Transaction::*;

match self {
    V4 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    } => Box::new(sapling_shielded_data.anchors()),

    V5 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    }
    | V6 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    } => Box::new(sapling_shielded_data.anchors()),

    // No Spends
    V1 { .. } | V2 { .. } | V3 { .. } | V4 { .. } | V5 { .. } | V6 { .. } => {
        Box::new(std::iter::empty())
    }
}

because it currently seems excessively explicit/verbose, but avoiding a catch-all arm makes it easier to see that it needs an update when a new variant is added. If only Rust supported enums in range patterns we could have the best of both here and elsewhere in Zebra (I think there's a crate for it but adding a crate seems worse than being verbose). Stable support for if let guards would've been nice here and elsewhere in this file too 🤷.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such simplification would work here and in similar places. However, if we add a feature flag for the V6 feature as we discussed:

V5 { ... } |
#[cfg(feature = "tx-v6")]
V6 { ... } => { ... }

It wouldn't compile. Here's a playground example to illustrate the issue.

I introduced the "tx-v6" feature flag in the next PR (#17, "Step 4"). However, it would definitely be better to include it in this PR ("Step 3") since I introduced V6 here (the absence of the "tx-v6" feature flag in this PR makes the review process confusing).

Anyway, we can't afford to use the | operator for such simplification during the transition period when the "tx-v6" feature flag is needed. Although I'm not completely sure if Rust doesn't have workarounds to achieve that.

And it was one of the reasons why I added the tx_v5_and_v6 macro here – to avoid such code duplication. However, the macro itself can be confusing because its usage doesn't follow the existing style for processing transaction version matches. I might need to remove it, and we can discuss this further.

// auto-detects the transaction version by the first byte, so the same function
// can be used here for both V5 and V6.
// FIXME: fix spec refs below for V6
/// Compute the Transaction ID for a V5/V6 transaction in the given network upgrade.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Compute the Transaction ID for transactions V5 to V6.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

Comment on lines +46 to +48
// FIXME: it looks like the updated zcash_primitives in librustzcash
// auto-detects the transaction version by the first byte, so the same function
// can be used here for both V5 and V6.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove these 3 lines

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txid_v5_v6()'s approach would work for all transactions, but it's an unnecessary memory/cache allocation when txid_v1_to_v4() is so simple and already implemented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

@@ -63,6 +63,7 @@ pub fn transparent_spend(
finalized_state,
)?;

// FIXME: what about v6?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment and fix line 69 to be
// This is a particular issue for v5 and v6 transactions,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

&None,
&None,
sapling_shielded_data,
orchard_shielded_data,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to deal with issue bundle here?
(x2)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is probably the best place to update the issued_assets map for the non-finalized part of the chain, same for revert_chain_with() when rolling back the chain for a fork.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

@@ -446,6 +446,7 @@ enum SpendConflictTestInput {

conflict: SpendConflictForTransactionV5,
},
// FIXME: add and use V6?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// FIXME: add V6 test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.


/// The version group ID for version 6 transactions.
///
/// Orchard transactions must use transaction version 5 and this version
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Orchard transactions must use transaction version 5 and this version
/// Orchard ZSA transactions must use transaction version 6 and this version

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

@@ -142,6 +142,28 @@ pub enum Transaction {
/// The orchard data for this transaction, if any.
orchard_shielded_data: Option<orchard::ShieldedData>,
},
// FIXME: implement V6 properly (now it's just a coipy of V5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the changed-files and codespell lint jobs from here may be trivial and help catch typos.

}
| Transaction::V6 {
sapling_shielded_data: None,
..
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplification looks nice, I'd lean towards whatever's easiest since it all needs to be refactored to a new-type around the zcash_primitives Transaction type soon, and if there's no difference in difficulty, I'd go with:

use Transaction::*;

match self {
    V4 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    } => Box::new(sapling_shielded_data.anchors()),

    V5 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    }
    | V6 {
        sapling_shielded_data: Some(sapling_shielded_data),
        ..
    } => Box::new(sapling_shielded_data.anchors()),

    // No Spends
    V1 { .. } | V2 { .. } | V3 { .. } | V4 { .. } | V5 { .. } | V6 { .. } => {
        Box::new(std::iter::empty())
    }
}

because it currently seems excessively explicit/verbose, but avoiding a catch-all arm makes it easier to see that it needs an update when a new variant is added. If only Rust supported enums in range patterns we could have the best of both here and elsewhere in Zebra (I think there's a crate for it but adding a crate seems worse than being verbose). Stable support for if let guards would've been nice here and elsewhere in this file too 🤷.

Comment on lines +863 to +866
Transaction::V6 {
sapling_shielded_data: Some(sapling_shielded_data),
..
} => Box::new(sapling_shielded_data.spends_per_anchor()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick/Optional: Transaction::V5 | Transaction::V6 would work here and for all of the sapling methods below too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a good simplification, but it's probably not compatible with the "tx-v6" feature flag. I’ve provided more details in this response.

@@ -918,6 +918,22 @@ pub fn transaction_to_fake_v5(
orchard_shielded_data: None,
},
v5 @ V5 { .. } => v5.clone(),
V6 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename the function to something like transaction_to_fake_min_v5() instead of converting v6 transactions to v5 here, since there aren't any v6 transactions in the test blocks (zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS}, accessed via network.block_iter() in some places), and if they're added later, it would improve test coverage to keep them as v6 transactions in the tests where this is used (a total of ~20 places counting indirect calls, and only v5_transaction_is_accepted_after_nu5_activation_for_network looks like it would need an update other than a rename).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to this comment copied to #37.

} => {
// Transaction V6 spec:
// FIXME: specify a proper ref
// https://zips.z.cash/protocol/protocol.pdf#txnencoding
Copy link
Collaborator

@arya2 arya2 Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: Leave both so it can be referenced while it's only in the ZIP and won't need an update if the encoding is meant to eventually be added to the protocol specification, though I'm not sure if that's the intention, I asked here.

Update: I've only found the version group IDs for V5 in the protocol spec so far, I think we'll probably want to keep this reference here (or add it back later).

/// Orchard transactions must use transaction version 5 and this version
/// group ID.
// FIXME: use a proper value!
pub const TX_V6_VERSION_GROUP_ID: u32 = 0x26A7_270B;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: It's 0x124A69F8 in zcash_primitives, if that was random, let's use that so it won't need an update.

Comment on lines +46 to +48
// FIXME: it looks like the updated zcash_primitives in librustzcash
// auto-detects the transaction version by the first byte, so the same function
// can be used here for both V5 and V6.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txid_v5_v6()'s approach would work for all transactions, but it's an unnecessary memory/cache allocation when txid_v1_to_v4() is so simple and already implemented.

&None,
&None,
sapling_shielded_data,
orchard_shielded_data,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is probably the best place to update the issued_assets map for the non-finalized part of the chain, same for revert_chain_with() when rolling back the chain for a fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants