Description
The Transaction Arbitrary strategy dispatch in zebra-chain/src/transaction/arbitrary.rs never generates v6 transactions, for any network upgrade:
NetworkUpgrade::Nu5
| NetworkUpgrade::Nu6
| NetworkUpgrade::Nu6_1
| NetworkUpgrade::Nu6_2
| NetworkUpgrade::Nu6_3
| NetworkUpgrade::Nu7 => prop_oneof![
Self::v4_strategy(ledger_state.clone()),
Self::v5_strategy(ledger_state)
]
.boxed(),
git log -L over the arm shows how this happened: the commit that introduced v6 and Ironwood support (ZcashFoundation#10762) appended | NetworkUpgrade::Nu6_3 to the pre-existing v4/v5 arm without adding a v6 strategy — the same append-only pattern as the earlier NU6.1/NU6.2/NU7 scaffolding commits. There is no v6_strategy, no Arbitrary impl for orchard::ShieldedDataV6 or ironwood::ShieldedData, and transaction_version_override(6) hits unreachable!.
Impact
Every property test built on this generator passes vacuously with respect to v6/Ironwood:
- Downstream consumers: zaino's block-consistency proptest ("ironwood tree-size delta must equal served ironwood action count per block") was green as
0 == 0 at every block, because the generator cannot produce an ironwood action. Its explicit non-vacuity guard (total_source_ironwood > 0) is what exposed the gap. The current workaround — injecting fake_v6_transaction into generated blocks — breaks the header's commitments over the transactions, which limits it to serving paths that don't validate them.
- Zebra's own suites: the only v6 coverage in-repo is deterministic (
fake_v6_transaction vectors). Additionally, any::<Transaction>() uses a Mainnet-tip LedgerState and NU6.3 is testnet-only so far, so transaction_roundtrip never exercises the v6 wire codec (including its fail-closed librustzcash round-trip).
- The Ironwood plumbing in
partial_chain_strategy (ironwood note-commitment tree threading through the V3 history node) has never executed — the fixup code even documents "Generated blocks have no Ironwood data".
The valargroup fork inherits the identical arm for NU7, so its NU7 test surface has the same silent hole.
Proposed fix
Transaction::v6_strategy (mirroring v5_strategy) wired into a split Nu6_3 | Nu7 arm alongside v4/v5, plus transaction_version_override(6) support.
Arbitrary impls for orchard::ShieldedDataV6 (reuses the base Orchard strategy; the v6 Orchard-pool bundle reserves enableCrossAddress like v5) and ironwood::ShieldedData (re-generates the flag byte so the Ironwood-only enableCrossAddress flag exercises the FlagsV6 codec).
nu6_3_branch_id_strategy emitting only Nu6_3 (Nu7's branch ID is a placeholder librustzcash doesn't recognise, so Nu7-tagged transactions can't round-trip to_librustzcash).
- Ironwood value-balance handling in the proptest value fixups, which otherwise panic when a generated Ironwood bundle drains the empty ironwood chain value pool.
- A non-vacuity guard test asserting each network-upgrade era generates exactly its valid transaction versions, and that NU6.3+ generation produces both v6 Orchard and Ironwood bundles — so the next upgrade appended to the arm cannot silently reintroduce this class of gap.
- An NU6.3-specific round-trip proptest, since
transaction_roundtrip cannot see v6 until NU6.3 activates on Mainnet.
A branch with this fix is ready; PR to follow.
Description
The
TransactionArbitrary strategy dispatch inzebra-chain/src/transaction/arbitrary.rsnever generates v6 transactions, for any network upgrade:git log -Lover the arm shows how this happened: the commit that introduced v6 and Ironwood support (ZcashFoundation#10762) appended| NetworkUpgrade::Nu6_3to the pre-existing v4/v5 arm without adding a v6 strategy — the same append-only pattern as the earlier NU6.1/NU6.2/NU7 scaffolding commits. There is nov6_strategy, noArbitraryimpl fororchard::ShieldedDataV6orironwood::ShieldedData, andtransaction_version_override(6)hitsunreachable!.Impact
Every property test built on this generator passes vacuously with respect to v6/Ironwood:
0 == 0at every block, because the generator cannot produce an ironwood action. Its explicit non-vacuity guard (total_source_ironwood > 0) is what exposed the gap. The current workaround — injectingfake_v6_transactioninto generated blocks — breaks the header's commitments over the transactions, which limits it to serving paths that don't validate them.fake_v6_transactionvectors). Additionally,any::<Transaction>()uses a Mainnet-tipLedgerStateand NU6.3 is testnet-only so far, sotransaction_roundtripnever exercises the v6 wire codec (including its fail-closed librustzcash round-trip).partial_chain_strategy(ironwood note-commitment tree threading through the V3 history node) has never executed — the fixup code even documents "Generated blocks have no Ironwood data".The valargroup fork inherits the identical arm for NU7, so its NU7 test surface has the same silent hole.
Proposed fix
Transaction::v6_strategy(mirroringv5_strategy) wired into a splitNu6_3 | Nu7arm alongside v4/v5, plustransaction_version_override(6)support.Arbitraryimpls fororchard::ShieldedDataV6(reuses the base Orchard strategy; the v6 Orchard-pool bundle reservesenableCrossAddresslike v5) andironwood::ShieldedData(re-generates the flag byte so the Ironwood-onlyenableCrossAddressflag exercises theFlagsV6codec).nu6_3_branch_id_strategyemitting onlyNu6_3(Nu7's branch ID is a placeholder librustzcash doesn't recognise, so Nu7-tagged transactions can't round-tripto_librustzcash).transaction_roundtripcannot see v6 until NU6.3 activates on Mainnet.A branch with this fix is ready; PR to follow.