Skip to content

Arbitrary Transaction strategies never generate v6 transactions for NU6.3/NU7 #5

Description

@zancas

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions