refactor(chain): replace Transaction enum with zcash_primitives newtype wrapper#10461
Open
arya2 wants to merge 17 commits into
Open
refactor(chain): replace Transaction enum with zcash_primitives newtype wrapper#10461arya2 wants to merge 17 commits into
arya2 wants to merge 17 commits into
Conversation
Add the foundation needed to replace Zebra's Transaction enum with a zcash_primitives newtype wrapper: - Cargo.toml: patch zcash_primitives to zebra-js-accessors branch (adds Clone + JsDescription accessors) - transaction/compat.rs: bidirectional conversions between zebra and librustzcash types (transparent inputs/outputs, lock_time, heights, consensus branch IDs / network upgrades) - serialization.rs: add ZcashDeserializeWithContext trait for types that need additional context during deserialization (e.g., BranchId for pre-V5 transactions) Depends on zcash/librustzcash#2275.
…pe wrapper Replace Zebra's ~1800-line Transaction enum (V1-V5 variants with manual serialization) with `pub struct Transaction(pub(crate) zp_tx::Transaction)`, a newtype wrapper around `zcash_primitives::transaction::Transaction`. - Transaction is now a newtype with Deref<Target = TransactionData<Authorized>> - All field access replaced with method-based API (inputs(), outputs(), sapling_spends(), sprout_joinsplit_descriptions(), etc.) - ZcashSerialize delegates to zcash_primitives; ZcashDeserialize adds coinbase height validation and V4 value balance check - serde::Serialize produces structured RON output for human-readable snapshots - Deleted transaction/txid.rs (TxIdBuilder no longer needed) Behavioral notes: - expiry_height() returns None when nExpiryHeight == 0 (no expiry per spec) - lock_time_is_time() checks sequence numbers (disabled lock time → false)
…ransaction type Update all crates to use the new Transaction newtype API: zebra-consensus: - groth16.rs: add joinsplit_to_item() for sprout verification using JsDescription accessors instead of JoinSplit<Groth16Proof> fields - transaction.rs and check.rs: use method-based access (has_sprout_joinsplit_data, sapling_spends_count, inputs, outputs, etc.) zebra-state: - anchors.rs: use sprout_joinsplit_descriptions() + Root::from() for anchors - nullifier.rs: use sprout_nullifiers(), sapling_nullifiers(), orchard_nullifiers() - utxo.rs, chain.rs, block.rs, shielded.rs: use method access throughout zebra-rpc: - Use Transaction methods instead of pattern matching for RPC responses zebra-network: - codec.rs: use Transaction methods for protocol message handling zebra-script: - Use inner() to access zcash_primitives transaction for script verification zebrad: - mempool storage/verified_set: use method-based access for conflict detection
Update tests across the entire workspace to work with the Transaction
struct wrapping zcash_primitives instead of the old enum with V1-V5 variants.
Key changes:
- Replace Transaction::V1/V4/V5 { ... } enum construction with
Transaction::test_v1()/test_v4()/test_v5() builder methods
- Replace pattern matching on Transaction variants with version() checks
and method-based access (has_sapling_shielded_data(), sapling_spends(), etc.)
- Replace to_librustzcash() calls (Transaction IS the zcash_primitives type)
- Replace TxIdBuilder with tx.hash()
- Fix UnminedTx::from(tx) → UnminedTx::from(Arc::new(tx))
- Fix lock_time proptest: cap heights at MAX_EXPIRY_HEIGHT, filter timestamps
>= 500_000_000 to avoid height/time misinterpretation after u32 round-trip
- Fix v5_strategy: fall back to Nu5 when ledger upgrade has no branch ID
- Rewrite binding_signatures test to compute bvk from sapling bundle CVs
- Rewrite sprout groth16 test with actual vpub_old/vpub_new values
- Build V4 txs with shielded data via byte-level serialization/patching
- Fix sapling anchor test: strip transparent inputs from patched transactions
- Fix mempool spend conflict tests: fall back to transparent conflicts when
shielded mutation is not possible
27 tests marked #[ignore] pending Transaction mutation support:
- 17 in zebra-consensus (orchard flags, joinsplit/sapling/orchard mutation,
expiry_height_mut, update_network_upgrade, insert_fake_orchard_shielded_data)
- 9 in zebrad (outputs_mut, inputs_mut, expiry_height_mut)
- 1 in zebrad/inbound (expiry_height_mut)
No test functions were removed.
…Data clone, dead code 1. Transaction::with_branch_id(): new method to correct the stored consensus_branch_id for V1-V4 transactions after the mined height and network are known. Documents that ZcashDeserialize uses a default BranchId::Canopy which may be wrong for V1-V4. 2. PrecomputedTxData: replace serialize/re-parse with clone + from_parts. Transaction now implements Clone, so we clone the inner transaction and reconstruct TransactionData with the correct branch_id instead of the expensive serialize→deserialize round-trip. 3. Gate height_to_block_height with #[cfg(any(test, feature = "proptest-impl"))] since it is only used by test builder methods. 4. Document the branch_id limitation in Block::zcash_deserialize and Transaction::zcash_deserialize with guidance on when to use ZcashDeserializeWithContext<BranchId> instead.
Add mutation methods to Transaction for use in tests, using the public TransactionData API (from_parts + freeze) to rebuild transactions with modified fields: - set_expiry_height(height): rebuild with a different expiry height - set_network_upgrade(nu): rebuild with a different consensus branch ID - set_output(index, output): replace a single transparent output - set_outputs(outputs): replace all transparent outputs These enable restoring several ignored tests that need to construct specially-crafted transactions for negative testing. All methods are gated behind #[cfg(any(test, feature = "proptest-impl"))]. Also updates Cargo.lock and cargo-vet policy for the latest librustzcash commit (which adds complementary test-gated mutable accessors to TransactionData and TxIn, usable by downstream crates that enable zcash_primitives/test-dependencies).
Restore tests that were marked #[ignore] during the Transaction type migration, using the new mutation methods and byte-level construction: Group 1 - outputs_mut/inputs_mut (8 tests restored): mempool_reject_non_standard, mempool_accept_standard_op_return, mempool_reject_op_return_too_large, mempool_reject_multi_op_return, mempool_reject_non_standard_scriptpubkey, mempool_reject_bare_multisig, mempool_reject_large_multisig, mempool_reject_large_scriptsig Group 2 - insert_fake_orchard_shielded_data (2 of 5 restored): v5_coinbase_transaction_without_enable_spends_flag_passes_validation, v5_coinbase_transaction_with_enable_spends_flag_fails_validation Group 3 - expiry_height_mut / update_network_upgrade (4 tests restored): v5_coinbase_transaction_expiry_height, v5_consensus_branch_ids, mempool_expired_basic, mempool_transaction_expiration Group 4 - V4 joinsplit data construction (3 tests restored): v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected, v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejected, v4_with_modified_joinsplit_is_rejected Group 5 - shielded data mutation (4 tests restored): v5_transaction_with_orchard_actions_has_inputs_and_outputs, v5_transaction_with_orchard_actions_has_flags, v4_with_duplicate_sapling_spends, v5_with_duplicate_sapling_spends 4 tests remain ignored (require constructing complete orchard bundles from scratch, which needs the orchard crate's testing module): v5_with_duplicate_orchard_action, coinbase_outputs_are_decryptable_for_fake_v5_blocks, shielded_outputs_are_not_decryptable_for_fake_v5_blocks, mempool_skip_accepts_block_with_garbage_orchard_proofs
…argo-vet - Replace unimplemented!() with TODO comments in 4 ignored orchard tests so they don't panic when CI runs with --run-ignored=all - Fix broken rustdoc link to JsDescription in groth16.rs - Update cargo-vet policy hash for latest librustzcash commit
Contributor
Author
|
Claude comparison between old and current logic: Logic Review: Old Transaction Enum vs New zcash_primitives NewtypeEquivalent (no behavioral difference)
Different (behavioral change, but safe in practice)
|
- Extract V4 byte-level transaction builder to shared Transaction::test_v4_with_joinsplit_data() method, replacing 4 duplicate ~25-line inline builders across zebra-consensus and zebra-state test files - Simplify Transaction Display from 28-line debug_struct to one-liner showing version, id, and input/output counts Net reduction: -113 lines
Rebuild the orchard bundle via orchard::Bundle::from_parts + TransactionData::from_parts (both already public) instead of byte-level manipulation. Adds a cfg-gated with_orchard_bundle helper on Transaction and a nonempty dev-dependency for constructing the new action list.
Contributor
|
Arya has thoroughly reviewed the large changes and is confident this works. |
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TODO: Get the tests to compile.
Motivation
Zebra maintained its own
Transactionenum with V1-V6 variants that duplicated the representation inzcash_primitives::transaction::Transaction. This resulted in ~1800 lines of pattern matching across variants, a separate serialization/deserialization implementation, and ato_librustzcash()serialize-roundtrip conversion every time the librustzcash type was needed (sighash computation, note encryption, txid for V5+).This PR replaces the enum with a newtype wrapper around the librustzcash type, eliminating the duplication and the conversion overhead.
Solution
Replace
pub enum Transaction { V1 { .. }, V2 { .. }, ... V6 { .. } }withpub struct Transaction(zcash_primitives::transaction::Transaction).The new type preserves the public API through accessor methods that delegate to the inner librustzcash type, with conversions at the boundary where Zebra uses its own types (nullifiers, note commitments, tree roots). A
Deref<Target = TransactionData<Authorized>>impl gives direct access to bundle accessors (sprout_bundle(),sapling_bundle(),orchard_bundle(), etc.).Key changes by crate:
compatmodule for bidirectional type conversions. NewZcashDeserializeWithContext<C>trait. Serialization delegates tozcash_primitives. Coinbase builders useTransactionData::from_parts().freeze().TxVersionenum. Sprout JoinSplit Groth16 proof and Ed25519 signature verification reimplemented usingJsDescriptionpublic accessors from the upstreamzebra-js-accessorsbranch.UpdateWithtrait dispatch on old enum fields). Sprout anchor validation usesJsDescriptionaccessors.Sigops::scripts()returnsVec<Vec<u8>>(inputs/outputs now return owned values).Upstream dependency: Requires
zcash/librustzcash#zebra-js-accessorsbranch which adds public accessors toJsDescription(nullifiers(),commitments(),anchor(),vpub_old(),vpub_new(),random_seed(),macs(),groth_proof_bytes()).Tests
cargo check --workspacepasses with 0 errors and 0 warningsTransactionvariants directly — inarbitrary.rs,tests/vectors.rs, and consensus/state test files — needs updating in a follow-up)Specifications & References
Verified against the Zcash protocol specification (
protocol.tex) and active ZIPs:zcash_proofs/src/sprout.rsreference implementationFollow-up Work
Transactionenum variants (arbitrary.rs, test vectors)Vec::new())PrecomputedTxDatasighash computation to avoid serialize/deserialize roundtrip (blocked on upstreamTransaction: Clone)Cloneonzcash_primitives::transaction::TransactionAI Disclosure
PR Checklist