Skip to content

Commit 15e30b3

Browse files
arya2upbqdnnuttycomconradoplg
authored
feat!: Support Ironwood and v6 transactions (#10762)
* feat(chain): add NU6.3 (Ironwood) network upgrade scaffolding Introduce the `NetworkUpgrade::Nu6_3` variant (Ironwood) between Nu6_2 and Nu7, mirroring how Nu7 is wired as an unscheduled upgrade: - enum variant with `serde` rename "NU6.3" and a test-gated placeholder consensus branch id (0xfffffffe); no mainnet/testnet activation height yet - testnet `ConfiguredActivationHeights.nu6_3` field and all builder paths - extend every exhaustive NetworkUpgrade match across zebra-chain, zebra-consensus, zebra-network, and zebra-rpc (history tree, commitment, tx version gating, halo2 verifier routing, peer protocol versions, block-template commitments) - v4/v5 remain valid at NU6.3; NU6.3 routes to the post-NU6.2 Halo2 key - update unscheduled-upgrade tests to account for two unscheduled upgrades Scaffolding only: no v6/Ironwood bundle, value pool, or consensus rules yet (see NU6.3-IRONWOOD-PLAN.md). Builds clean across the workspace with fmt and clippy -D warnings; parameter, network, and halo2 tests pass. * feat(chain): add NU6.3 ironwood chain value pool Add `ironwood` as the sixth chain value pool (ZIP 209, NU6.3) alongside transparent/sprout/sapling/orchard/deferred: - `ValueBalance<C>` field plus `from_ironwood_amount`, `ironwood_amount`, `set_ironwood_value_balance`, and inclusion in zero/constrain/Add/Sub/Neg, the Arbitrary impl, and `remaining_transaction_value` - serialization appends ironwood at bytes 40..48: `to_bytes` is now [u8; 48] and `from_bytes` accepts 32/40/48-byte records, so databases written by earlier Zebra versions still parse (ironwood defaults to zero) - on-disk consumers updated: `IntoDisk for ValueBalance` ([u8; 48]) and `BlockInfo` (handles 44-byte old and 52-byte new layouts) - bump database format version to 27.1.0 (minor: compatible read code) - regenerate value-pool raw-data snapshots (ironwood-zero widening only; all existing pool values and block sizes preserved) The pool is always zero until NU6.3 activates. Reporting ironwood in the getblockchaininfo valuePools array is deferred to the RPC phase. Workspace builds across all targets; fmt and clippy -D warnings clean; value_balance and disk_format tests pass. * feat(chain): add ironwood transaction accessors and value-balance wiring Add the Ironwood (NU6.3) accessor surface on `Transaction`, mirroring the orchard accessors: `ironwood_shielded_data`, `ironwood_actions`, `ironwood_nullifiers`, `ironwood_note_commitments`, `ironwood_flags`, `has_ironwood_shielded_data`, and `ironwood_value_balance`. The last is folded into `value_balance_from_outputs` so the ironwood chain value pool is wired end-to-end. Ironwood reuses the Orchard `ShieldedData`/tree/nullifier types (identical Pallas/Sinsemilla machinery), differing only by storage instance, so no separate tree module is introduced. `ironwood_shielded_data` returns `None` for every version until `Transaction::V6` gains the Ironwood bundle field (a later change), so all derived accessors are empty and the value balance is zero today. Builds clean; fmt and clippy -D warnings pass; transaction and value_balance tests pass. * feat(chain): reshape v6 transaction to the Ironwood format Redefine the (gated) v6 transaction format for NU6.3 / Ironwood as "v5 plus an Ironwood bundle", replacing the older NU7/ZIP-233 burn draft: - `Transaction::V6`: drop `zip233_amount`, add `ironwood_shielded_data: Option<orchard::ShieldedData>`, serialized after the orchard bundle with the identical Orchard wire layout - wire `ironwood_shielded_data()` to the v6 field; count ironwood actions in `has_shielded_inputs`/`has_shielded_outputs` so an ironwood-only v6 tx is considered to have inputs/outputs - remove the `zip233_amount()`/`has_zip233_amount()` accessors and the burn subtraction in the transaction-fee path; simplify `has_inputs_and_outputs` - neutralize the orphaned ZIP-235 NSM burn enforcement in block subsidy checks (burn treated as zero, with a TODO to re-source it) and remove the three obsolete zip233 NSM tests All v6/Ironwood data-model changes are behind `zcash_unstable = "nu7"` + `tx_v6`. The default build is unaffected. Known upstream gaps: `to_librustzcash` is a serialize/parse roundtrip, so v6 txid/auth/sighash fail at runtime until zcash_primitives' v6 parser matches the Ironwood format (Zebra's v6 wire (de)serialization is self-contained). The zebra-rpc tx_v6 block-template path is pre-broken on main and left untouched. Default workspace builds across all targets with fmt and clippy -D warnings; zebra-chain/-consensus tests pass; nu7+tx_v6 compiles for chain/state/consensus. * test(chain): cover the NU6.3 ironwood chain value pool rule Add fixed-vector tests confirming the ironwood pool participates in the ZIP-209 chain value pool non-negativity rule (`add_chain_value_pool_change` rejects a negative ironwood balance with an ironwood-specific error) and that the ironwood value balance is included in `remaining_transaction_value`. This locks in the ironwood ZIP-209 rule, which rides the generic `ValueBalance` machinery added earlier. The remaining Phase 4 state storage (nullifier sets and note-commitment trees) is deferred pending upstream finalization of the orchard Ironwood pool types (see NU6.3-IRONWOOD-PLAN.md). * build(deps): pin librustzcash + orchard to the Ironwood forks (spike) Add a [patch.crates-io] block pointing the librustzcash family at the valargroup/librustzcash `adam/ironwood-split-2-v6-txid` branch (which adds v6 Ironwood txid/sighash under `zcash_unstable="nu6.3"`) and `orchard` at the matching `qr_orchard` rev. The fork declares the same crate versions as our crates.io pins (zcash_primitives 0.28, zcash_protocol 0.9, orchard 0.14), so this is a source-only patch with no version drift. Adapt the Orchard Halo2 batch verifier to the new `qr_orchard` API: - `VerifyingKey::build_for_version(v)`/`build()` -> `VerifyingKey::build(v)` - `BatchValidator::default()` -> `BatchValidator::new(&vk)` (now borrows the key, so `Verifier.batch` is `BatchValidator<'static>`); `take` rebuilds it - `validate(vk, rng)` -> `validate(rng)` (key is held by the validator) - `add_bundle` now returns `Result`; a queued item whose cross-address restriction is unsupported by the era's key is rejected on its own without poisoning the batch - add the `OrchardCircuitVersion::PostNu6_3` verifying key and route NU6.3+ (and ZFuture) to a new `VERIFIER_POST_NU6_3`, leaving NU6.2 on the fixed key Default workspace builds across all targets against the forks; fmt and clippy -D warnings clean; halo2 and consensus transaction tests pass (historical pre-NU6.2 Orchard proofs still verify). This patch is experimental: the fork branch is force-pushed, so pin to a rev before relying on it. * feat(chain): gate v6/Ironwood on nu6.3 and wire it to the fork Align Zebra's v6 transaction support with the librustzcash Ironwood fork, which gates v6 txid/sighash under `zcash_unstable = "nu6.3"`: - migrate every `cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))` gate to `"nu6.3"` across zebra-chain/-consensus/-state/-rpc/zebrad (the v6 format is an NU6.3 feature; the lone genuinely-NU7 `From<zcash_protocol::Nu7>` arm stays) - add the `From<zcash_protocol::NetworkUpgrade::Nu6_3>` conversion (gated nu6.3) - set the Nu6_3 consensus branch id to the fork's `BranchId::Nu6_3` placeholder (0xffff_ffff) so `to_librustzcash` resolves v6 transactions to the fork's NU6.3 branch id; move the Nu7 test placeholder to 0xffff_fffe to keep ids unique - register `nu6.3` in the workspace `unexpected_cfgs` check-cfg list - add a v6 round-trip + txid test that drives the fork's v6 (ZIP-244) digest With the fork patched in and built under `--cfg zcash_unstable="nu6.3"` plus the `tx_v6` feature, v6 Ironwood transactions now compute a txid end-to-end. The default build (no cfg) is unaffected; fmt and clippy -D warnings are clean in both the default and the nu6.3+tx_v6 configurations. * fix(chain): give ZFuture a distinct test branch-id placeholder The NU6.3 branch-id placeholder is `0xffffffff` (matching the librustzcash Ironwood fork), and the `Nu6_3` entry is gated on `test`/`zebra-test` independently of `zcash_unstable`. The `ZFuture` placeholder was also `0xffffffff`, so building tests with `--cfg zcash_unstable="zfuture"` put two `0xffffffff` entries in `CONSENSUS_BRANCH_IDS` and failed `branch_id_bijective`. (This collision pre-existed between `Nu7` and `ZFuture`; the NU6.3 branch-id swap relocated it onto `Nu6_3`.) Give `ZFuture` a distinct `0xfffffffd` placeholder so all three future-upgrade placeholders are unique. `branch_id_bijective` now passes under the default, `nu6.3`, and `zfuture` configurations. * fix(consensus): route Orchard verifier by transaction version A v5 transaction's Orchard bundle commits to the pre-NU6.3 circuit even when mined at NU6.3 (the v5 format predates the NU6.3 cross-address circuit), so it must verify under the fixed (post-NU6.2) key — not the NU6.3 key. The previous `verifier_for(network_upgrade)` routed ALL NU6.3 Orchard bundles to the NU6.3 key, which would reject valid v5 Orchard bundles mined at NU6.3. Split the routing by transaction version: - `orchard_v5_verifier_for(nu)`: v5 Orchard bundle → InsecurePreNu6_2 (<NU6.2) or FixedPostNu6_2 (NU6.2 onward, incl. NU6.3/NU7). - `orchard_v6_verifier()`: v6 Orchard + Ironwood bundles → PostNu6_3. `verify_orchard_bundle` now takes the resolved verifier; `verify_v6_transaction` is a real function (no longer a passthrough to v5) that routes its Orchard bundle to the NU6.3 key and gates v6 on NU6.3+. (Ironwood bundle proof verification is wired in a follow-up.) Regression test asserts v5 Orchard at NU6.3 routes to the fixed key. Default + nu6.3/tx_v6 builds, fmt, clippy -D warnings clean; halo2 tests pass. * feat(chain): accept the NU6.3 enableCrossAddress flag in v6 bundles Per the v6 transaction format (ZIP 1301), the Orchard flag byte gains bit 2 `enableCrossAddress` at NU6.3, shared by the v6 Orchard and Ironwood bundles; before NU6.3 (v5 Orchard) that bit is reserved and MUST be zero. - add `Flags::ENABLE_CROSS_ADDRESS` (bit 2) and a `FlagFormat { PreNu6_3, Nu6_3 }` selector, with `Flags::from_byte`/`zcash_deserialize_with_format` enforcing the per-format reserved bits (PreNu6_3 reserves bits 2..7; Nu6_3 reserves bits 3..7) - the default `ZcashDeserialize for Flags` now uses the PreNu6_3 format, so v5 Orchard bundles still reject bit 2 (unchanged consensus behavior, including in nu6.3 builds where the bit is defined) - extract `deserialize_orchard_shielded_data(reader, flag_format)`; the v6 transaction deserializer parses its Orchard and Ironwood bundles with the Nu6_3 format so cross-address-enabled bundles round-trip All v6 code is behind `zcash_unstable = "nu6.3"` + `tx_v6`; the default build is unaffected. Unit tests cover both flag formats. Default + nu6.3 builds, fmt, and clippy -D warnings clean. * refactor(consensus): split Orchard bundle verification into v5/v6 methods Encapsulate the verifying-key choice in the verify methods instead of threading a `&VerifierService` through the transaction-verification path: - `verify_orchard_bundle(bundle, sighash, network_upgrade)` verifies a v5 Orchard bundle, resolving its key from the upgrade via `orchard_v5_verifier_for`. - new `verify_orchard_v6(bundle, sighash)` verifies a v6 Orchard bundle against the NU6.3 key via `orchard_v6_verifier`. Also completes the verifying-key static rename (`VERIFYING_KEY_V5_PRE_NU6_2` / `VERIFYING_KEY_V5_POST_NU6_2` / `VERIFYING_KEY_V6`) across the verifier statics, tests, and bench, so default and nu6.3+tx_v6 builds compile again. Default + nu6.3 builds, fmt, clippy -D warnings clean; halo2 tests pass. * refactor(consensus): address PR review feedback - rename the Orchard verifier statics to match the verifying keys: `VERIFIER_PRE_NU6_2`/`POST_NU6_2`/`POST_NU6_3` → `VERIFIER_V5_PRE_NU6_2`/`VERIFIER_V5_POST_NU6_2`/`VERIFIER_V6`. - remove the always-zero `zip233_amount` term from the coinbase balance equation in `subsidy_is_valid` (the ZIP-233 burn field was removed from v6; the term added nothing). NSM will re-source the burn when it's specified. - clarify the `BlockInfo::from_bytes` length match: records are exactly 44 (pre-NU6.3) or 52 (NU6.3) bytes; use an open `44..` range for the pre-NU6.3 arm to keep the original forward-compatible behavior now that the `52..` arm takes every NU6.3 record. Default + nu6.3 builds, fmt, clippy -D warnings clean; halo2 tests pass. * refactor(chain): add orchard::ShieldedDataV6 newtype for v6 bundle (de)serialization Encode the v6/NU6.3 Orchard flag-byte format in the type system instead of threading a `FlagFormat` through the v6 deserializer: - add `orchard::ShieldedDataV6(orchard::ShieldedData)` with a `ZcashDeserialize` impl that parses the NU6.3 flag format (permitting `enableCrossAddress`). - the v6 transaction parses its Orchard and Ironwood bundles as `Option<ShieldedDataV6>`, then unwraps to the in-memory `orchard::ShieldedData`, so the transaction fields, accessors, and downstream state code are unchanged (minimal diff — no field-type ripple). - v6 bundles encode identically to v5 on the wire, so serialization reuses the existing `Option<orchard::ShieldedData>` serializer (no duplicate serialize impl). Also print the ironwood action count after the orchard action count in the `Transaction` Debug output (review feedback), replacing the ad-hoc `has_ironwood_shielded_data` field. Default + nu6.3 builds, fmt, clippy -D warnings clean; v6 round-trip, flag, and zebra-chain lib tests pass. * refactor(consensus): rename verify_orchard_v6 to verify_orchard_v6_bundle Match the `verify_orchard_bundle` (v5) naming, per PR review feedback. * feat(consensus): verify the v6 Ironwood bundle's Halo2 proof The librustzcash Ironwood fork exposes the v6 transaction's Ironwood bundle, so extract it (like the Orchard bundle) and verify it. The Ironwood bundle reuses the Orchard Action proof system and commits to the NU6.3 circuit, so it verifies under the same NU6.3 key as the v6 Orchard bundle (`verify_orchard_v6_bundle`). - add `ironwood_bundle()` accessors to `PrecomputedTxData` and `SigHasher` (gated on `zcash_unstable = "nu6.3"` + `tx_v6`). - `verify_v6_transaction` now verifies the Ironwood bundle's proof alongside the Orchard bundle, on the shared transaction-verification path (so it applies to both block validation and the mempool). Default + nu6.3 builds, fmt, clippy -D warnings clean; v6 tests pass. * feat(consensus): enforce NU6.3 coinbase, flag, and cross-address rules Add the NU6.3 / Ironwood transaction consensus rules, on the shared transaction-verification path (so they apply to both block validation and the mempool): - coinbase: the `enableSpendsIronwood` flag MUST be 0 (extends the existing `coinbase_tx_no_prevout_joinsplit_spend` check) → `CoinbaseHasEnableSpendsIronwood`. - a transaction with Ironwood actions MUST set at least one Ironwood flag (`has_enough_ironwood_flags`, mirroring the Orchard rule) → `NotEnoughIronwoodFlags`. - the Orchard pool MUST NOT enable cross-address transfers at NU6.3 (`orchard_cross_address_disabled`): no new value may enter Orchard, so only the Ironwood pool may cross-address → `OrchardHasEnableCrossAddress`. The Orchard/Ironwood chain value pool non-negativity rule (ZIP-209) is already enforced generically via `ValueBalance`. The new flag checks are no-ops for pre-v6 transactions (no Ironwood actions, and v5 Orchard bundles can't set bit 2). Default + nu6.3 builds, fmt, clippy -D warnings clean; consensus tests pass. * docs(chain): note v6 Sapling reuses the v5 wire codec (digest changes are in librustzcash) * refactor(chain): add orchard::FlagsV6 newtype for the v6 flag-byte format Replace the runtime `FlagFormat` parameter with a `FlagsV6` newtype (parallel to `ShieldedDataV6`), per PR review feedback: - `orchard::FlagsV6(orchard::Flags)` with a `ZcashDeserialize` impl that uses the NU6.3 flag-byte format (bit 2 `enableCrossAddress` valid, bits 3..7 reserved); the bare `Flags` codec keeps the pre-NU6.3 format (bits 2..7 reserved). - remove the `FlagFormat` enum and `Flags::zcash_deserialize_with_format`; the reserved-bit check is a private `Flags::from_byte(byte, reserved_mask)` helper. - `deserialize_orchard_shielded_data` is now generic over the flag type (`F: ZcashDeserialize + Into<Flags>`): v5 bundles parse `Flags`, v6 Orchard and Ironwood bundles parse `FlagsV6`. The format is selected by the type, not a runtime arg. Default + nu6.3 builds, fmt, clippy -D warnings clean; flag, prop, and v6 round-trip tests pass. * feat(ironwood): type v6 shielded fields with ShieldedDataV6/ironwood newtypes Make the v6 transaction's shielded-data fields carry their newtypes directly instead of unwrapping to the bare orchard::ShieldedData: - orchard_shielded_data: Option<orchard::ShieldedDataV6> - ironwood_shielded_data: Option<ironwood::ShieldedData> Add a new zebra-chain ironwood module housing ironwood::ShieldedData, a newtype around orchard::ShieldedDataV6. The Ironwood bundle shares the v6 Orchard wire format but commits into a separate note commitment tree and nullifier set, so a distinct type keeps the two pools from being interchanged. Dedup the (de)serialization: a single zcash_serialize_optional_orchard_bundle helper backs the v5 Orchard, v6 Orchard, and Ironwood serializers; the v6 and Ironwood deserializers delegate to the v5 Orchard codec, only wrapping their newtypes. The transaction accessors still expose Option<&orchard::ShieldedData>, so consensus/state callers are unchanged except the non-finalized orchard nullifier UpdateWith, which now takes Option<&orchard::ShieldedData>. All v6 code stays behind the zcash_unstable="nu6.3" + tx_v6 gates. * refactor(ironwood): privatize v6 newtype fields; add ironwood::Nullifier Make the inner fields of the v6 newtypes private and expose them through methods instead of tuple access: - orchard::FlagsV6(Flags): private; read via the existing From<FlagsV6> for Flags - orchard::ShieldedDataV6: private; ShieldedDataV6::new / .data() / .data_mut() - ironwood::ShieldedData: private; ShieldedData::new / .data() Also lay the groundwork for the Ironwood state layer: - The ironwood module and its Nullifier newtype are now always compiled (only the v6 ShieldedData bundle stays behind the nu6.3/tx_v6 gates), so the on-disk database format is stable across build flags — matching ValueBalance's always-present ironwood pool. ironwood::Nullifier wraps orchard::Nullifier and keeps the Ironwood nullifier set type-disjoint from Orchard's. - Transaction::ironwood_nullifiers / Block::ironwood_nullifiers now yield owned ironwood::Nullifier values for the state layer. * feat(state): add the Ironwood nullifier set (NU6.3) Track Ironwood-pool nullifiers in their own set, disjoint from Orchard's, to reject Ironwood double-spends: - New finalized `ironwood_nullifiers` column family (always registered, empty until NU6.3) with contains_ironwood_nullifier / ironwood_revealing_tx_loc reads, the prepare_nullifier_batch write path, and an IntoDisk impl reusing the Orchard nullifier byte encoding. - Non-finalized Chain gains an ironwood_nullifiers map. Because Ironwood reuses orchard::ShieldedData, its nullifiers can't go through a distinct UpdateWith impl (it would collide with Orchard's), so they are added/removed inline. - check::nullifier rejects duplicate Ironwood nullifiers within the non-finalized and finalized chains, via a DuplicateNullifierError impl for ironwood::Nullifier (new ValidateContextError::DuplicateIronwoodNullifier). - Spend::Ironwood + From for the indexer's spending-transaction lookups. Database format bumped to 28.0.0: a major bump that is restorable from the previous major version (NoMigration upgrade; the new column family is created empty and the widened value-pool records are read in place — no resync, no data migration). Snapshots regenerated. The Ironwood note commitment tree, anchors, and subtrees follow in a subsequent commit. * fix(consensus): subtract the Ironwood value balance from coinbase output value The coinbase output-value equation subtracted the Sapling and Orchard value balances but not the Ironwood one. Ironwood is a shielded pool, so [NU6.3 onward] `vbalanceIronwood` must be subtracted too (value shielded into Ironwood by a coinbase counts toward its output value, exactly like Orchard). This is a no-op before NU6.3, since pre-v6 coinbase transactions have no Ironwood bundle and the balance is zero. Addresses a PR review comment on block/check.rs. * feat(state): add the Ironwood note commitment tree, anchors, and subtrees (NU6.3) Maintain the Ironwood note commitment tree alongside Orchard's, so Ironwood anchors can be validated against prior treestates. Ironwood reuses the Orchard tree types (orchard::tree::{NoteCommitmentTree, Root, Node}) but commits into separate column families and indexes. - zebra-chain: NoteCommitmentTrees gains ironwood + ironwood_subtree fields and an update_ironwood_note_commitment_tree() spawned in update_trees_parallel; Block::ironwood_note_commitments(); a distinct NoteCommitmentTreeError::Ironwood. - Finalized state: three new column families (ironwood_anchors, ironwood_note_commitment_tree, ironwood_note_commitment_subtree) with the tree/ anchor/subtree reads, note_commitment_trees_for_tip, create_ironwood_tree / insert_ironwood_subtree, and prepare_trees_batch wiring; contains_ironwood_anchor. - Non-finalized Chain: ironwood tree/anchor/subtree indexes with the matching accessors, add/remove_ironwood_tree_and_anchor, treestate, pop_root/pop_tip, parallel-update, and revert wiring; Treestate::new + Chain::new take the Ironwood tip tree. - check::anchors rejects Ironwood actions whose anchor is unknown (ValidateContextError::UnknownIronwoodAnchor). The new column families are registered under the existing 28.0.0 format bump and hold only the genesis empty-tree root/tree until NU6.3 activates. Snapshots regenerated (ironwood genesis anchor root is identical to Orchard's empty tree). The IronwoodTree / IronwoodSubtrees read requests and RPC follow separately. * fix(rpc): drop the abandoned zip233_amount block-template plumbing ZIP-233 was dropped from the v6 transaction format, but a gated zip233_amount parameter was left threaded through new_coinbase / new_internal / select_mempool_transactions. No caller ever passed it, and its body referenced an undefined miner_fee, so the nu6.3 + tx_v6 build of zebra-rpc/zebrad did not compile. Remove the parameter, the dead zip235 branch, and the set_zip233_amount call (the coinbase builder no longer sets a burn amount; the Network Sustainability Mechanism can re-plumb one when it is specified), plus the now-redundant gated imports and the call-site arguments in tests. The default build is unchanged (all removed code was gated); the experimental nu6.3 + tx_v6 build of zebrad now compiles end-to-end. * feat(chain): add the ZIP-221 V3 history node for Ironwood (NU6.3) At NU6.3 the chain history MMR commits to the Ironwood note commitment tree, via the V3 history node format (zcash_history::V3 / NodeDataV3, which extends V2 with start/end Ironwood roots and an Ironwood transaction count). - Thread an ironwood_root through the history-tree API: the Version trait's block_to_history_node, Entry::new_leaf, Tree::new_from_block / append_leaf, and both HistoryTree types' from_block / push / try_extend. V1 and V2 ignore it. - Add a Version impl for zcash_history::V3 building NodeDataV3 from the V2 node data plus the Ironwood root and Block::ironwood_transactions_count(); the fork's zcash_history handles V3 hashing/combining. - Select the tree version by network upgrade: Heartwood/Canopy -> V1, NU5..NU6.2 -> V2, NU6.3 onward (and ZFuture) -> V3. Pre-NU6.3 behaviour is unchanged (existing V1/V2 history tests pass), so this only affects NU6.3 once activated. - Wire the Ironwood tip root into the finalized and non-finalized history-tree updates, and into the proptest block generator so generated NU6.3 commitments match validation. The V3 path is dormant until NU6.3 activates (no activation height yet). * fix(state): backfill the genesis Ironwood tree on database upgrade The 28.0.0 NoMigration was insufficient: a node upgrading a pre-28 database ends up with an empty ironwood_note_commitment_tree / ironwood_anchors column family (its genesis block was committed long ago, before Ironwood existed). That makes ironwood_tree_for_tip() panic on the first block after restart, and leaves the genesis Ironwood anchor missing for NU6.3 anchor validation — a consensus split versus genesis-synced nodes once NU6.3 activates. Replace NoMigration(28.0.0) with a real add_ironwood_tree::Upgrade that backfills the empty Ironwood tree and its anchor at the genesis height, matching what a genesis-synced v28 node writes. It is a no-op for genesis-synced and already-upgraded databases, and remains a reusable major upgrade (restorable from the previous major format version, no resync). Adds ironwood_tree_by_height_range for the existence check and the upgrade's validate(). Addresses a code-review finding (confirmed against valargroup's add_ironwood_tree). * feat(consensus): enforce Ironwood proof size, nullifier uniqueness, coinbase flags Three NU6.3 consensus rules that were missing for the Ironwood bundle (found by code review + spec-conformance against ZIP-1301): - The canonical Halo2 proof-size rule was enforced only for the Orchard bundle. Apply it to the Ironwood bundle too (TransactionError::IronwoodProofSize). Ironwood only exists from NU6.3 onward, so it is enforced unconditionally — no legacy lenient period as there was for Orchard (GHSA-jfw5-j458-pfv6). - spend_conflicts() now checks intra-transaction duplicate Ironwood nullifiers (TransactionError::DuplicateIronwoodNullifier), like the other pools. - [NU6.3] A v6 coinbase transaction MUST have flagsOrchard == 0 (no new value may be shielded into the Orchard pool at NU6.3; new shielded value goes to Ironwood). Previously only enableSpends was rejected (TransactionError::CoinbaseHasNonZeroOrchardFlags). Adds an Arbitrary impl for ironwood::Nullifier (used by the error enum's derived Arbitrary under proptest). * fix(chain): reject pre-NU6.3 consensus branch IDs when parsing v6 transactions v6 transactions are only valid from NU6.3 onward, but the deserializer rejected only pre-NU5 branch IDs. Tighten the wire-layer check to reject anything below NU6.3 (the exact tx-vs-block network-upgrade match is still re-checked during verification by consensus_branch_id). Addresses a code-review / spec-conformance finding. * refactor(state): apply Ironwood nullifiers via UpdateWith; drop per-tx Vec allocs Update the non-finalized chain's Ironwood nullifier set through the UpdateWith trait (keyed on the ironwood::ShieldedData newtype, so it doesn't collide with the Orchard impl), exactly like the other shielded pools, instead of the previous inline add/remove calls. To enable this and remove the per-transaction Vec allocations (the accessor yields owned ironwood::Nullifier), the shared nullifier helpers (add_to_non_finalized_chain_unique, remove_from_non_finalized_chain, find_duplicate_nullifier) now take owned Copy nullifiers instead of references. Sprout/Sapling/Orchard callers pass .copied(); Ironwood passes its owned nullifiers directly. No pool now allocates a throwaway Vec on the commit, revert, or duplicate-check paths. Addresses code-review finding #9 and the altitude/consistency note about Ironwood not using UpdateWith. * feat(rpc,chain): count Ironwood actions for ZIP-317 fees and expose its value pool Two NU6.3 completeness gaps found by spec-conformance review against ZIP-1301: - ZIP-317 conventional_actions() now counts Ironwood actions alongside Orchard actions (identical structure and cost). Previously v6 transactions with Ironwood actions were under-charged the conventional fee and under-counted unpaid actions for mempool/relay. Zero for pre-v6 transactions. - getblockchaininfo / getblock 'valuePools' now include the Ironwood chain value pool (the spec defines it; it is zero before NU6.3 activates). The pool array grows from 5 to 6 entries; snapshots regenerated. Both are no-ops before NU6.3. * docs(changelog): note the NU6.3 DB format bump and experimental Ironwood support * style(ironwood): align with existing codebase conventions Code-review style-consistency pass against the orchard/sapling precedent: - error.rs: rename CoinbaseHasNonZeroOrchardFlags -> CoinbaseHasOrchardFlags (matches the CoinbaseHas<Flag> sibling pattern); fix message casing (EnableSpendsIronwood, not enableSpendsIronwood) to match the Orchard sibling; drop the [NU6.3 onward] tag/parenthetical from the OrchardHasEnableCrossAddress message for terse-imperative consistency; move NotEnoughIronwoodFlags beside NotEnoughFlags (and out of the score=100 coinbase cluster, matching its unprefixed orchard sibling's score). - check.rs / anchors.rs: restore the # Consensus + spec-link form on the Ironwood flag/cross-address/anchor checks to match the adjacent orchard/sapling blocks. - nullifier.rs: update the disjoint-pools doc quote to include Ironwood. - value_balance.rs: order the ironwood accessors last, matching the field order. - transaction.rs: fold into the existing use crate::{...} group. - ironwood.rs: move the Nullifier Arbitrary impl into ironwood/arbitrary.rs, like every other pool's arbitrary submodule. - drop a stray insta assertion_line header from one regenerated snapshot. No behavior change. * feat(consensus): freeze the Orchard pool at NU6.3 and drop the tx_v6 feature Addresses review feedback on #10762. - Remove the `tx_v6` cargo feature: NU6.3 code now compiles under `--cfg zcash_unstable="nu6.3"` alone. All 94 `cfg(all(zcash_unstable="nu6.3", feature="tx_v6"))` gates are simplified to `cfg(zcash_unstable="nu6.3")` and the feature is deleted from every crate's Cargo.toml (it was only ever used by these gates on this branch). New build command drops `--features tx_v6`. - Enforce `[NU6.3 onward] valueBalanceOrchard >= 0` (`TransactionError::NegativeOrchardValueBalance`). The Orchard pool is frozen against new inflows from NU6.3, since newly shielded value is routed to Ironwood; spends (Orchard-to-transparent) and note management (Orchard-to-Orchard) stay valid. Applies to both v5 and v6 Orchard bundles. - Keep v5 Orchard bundles valid after NU6.3 (no rejection), so non-upgraded hardware wallets can keep authorizing Orchard spends. The existing version-based verifier routing (v5 -> FixedPostNu6_2, v6 -> PostNu6_3) is already correct. - Add `NotEnoughIronwoodFlags` to the misbehavior-score list. - Drop the stale ZIP-233 `None` arguments to `new_coinbase`, fix the run-command doc comment, and rename `nu7_nsm_transactions` -> `nu6_3_block_template_proposal`. - Reword two disk-format comments and fix comma spacing in the non-finalized state property tests. - Fix a pre-existing NU6.3 compile error in the mempool property tests by adding `orchard::ShieldedDataV6::into_inner` and unwrapping/rewrapping the v6 Orchard bundle. * docs(changelog): add NU6.3 Ironwood pool entries * test(consensus): expand NU6.3/Ironwood v6 test coverage Previously every v6 path was exercised only with empty bundles, because there was no way to build a populated v6 Orchard/Ironwood bundle in tests. Add the missing generators and cover the NU6.3 consensus rules and the v6 wire codec. - Add `fake_v6_orchard_shielded_data` / `fake_v6_transaction` test helpers (zebra-chain) that build populated, structurally-valid-but-cryptographically-fake v6 Orchard and Ironwood bundles (mirroring `insert_fake_orchard_shielded_data`). These unblock all the structural v6 tests below; they must not be used where proof verification or a canonical proof size is required. - Consensus unit tests for the NU6.3 rules (zebra-consensus): - `v6_transaction_with_ironwood_actions_must_have_flags` (NotEnoughIronwoodFlags) - `v6_orchard_bundle_must_not_enable_cross_address` (OrchardHasEnableCrossAddress) - `v6_transaction_with_duplicate_ironwood_nullifier_is_rejected` (spend_conflicts / DuplicateIronwoodNullifier, and Ironwood/Orchard nullifier sets stay disjoint) - Round-trip a v6 transaction carrying populated Orchard-v6 and Ironwood bundles through Zebra's own v6 (de)serializer (zebra-chain). It does not compute a txid, which would drive the librustzcash fork's parser (it does not accept fake proof bytes); the empty-bundle txid path is already covered. - Make the `nu6_3_block_template_proposal` integration test actually activate NU6.3 (it was activating NU7), so it exercises the NU6.3 block-template -> proposal -> submit pipeline end-to-end. A positive proof-validity end-to-end test (valid Ironwood proofs verified through full block validation) is still deferred: it needs an Ironwood prover to generate valid blocks, the same dependency QEDIT's OrchardZSA tests have on zcash_tx_tool. * refactor(consensus): rename NotEnoughFlags to NotEnoughOrchardFlags The variant is implicitly about Orchard flags; rename it for symmetry with the new NotEnoughIronwoodFlags variant. * docs(chain): document the reserved Orchard-flag consensus rule on the deserializers The generic Flags::from_byte helper enforces whatever mask the caller passes, so the reserved-bits consensus rule belongs on the ZcashDeserialize impls for Flags (v5, pre-NU6.3 format) and FlagsV6 (NU6.3 format) where the specific mask is chosen, not on the helper. * refactor(consensus): surface the halo2 batch-queue error instead of discarding it Match the known BatchError::RestrictionUnsupportedByKey variant explicitly with an unreachable! documenting why routing makes it impossible (v5 verifiers only ever see v5 bundles, which always have cross_address_enabled = true; v6 bundles go to VERIFIER_V6 whose key supports the restriction), and keep a graceful arm for any future variant of the #[non_exhaustive] enum. * feat(consensus): require an empty Orchard component in NU6.3 coinbase transactions From NU6.3, newly shielded coinbase value is routed to the Ironwood pool, so a coinbase transaction must have an empty Orchard component (ZIP-229). This applies to every transaction version, so a v5 coinbase mined at NU6.3 is constrained too and the rule cannot be bypassed with an older format. The new height-gated check coinbase_orchard_component_empty subsumes the previous v6-only flagsOrchard==0 check; the error variant is renamed CoinbaseHasOrchardFlags -> CoinbaseHasOrchardActions. * chore: remove the local NU6.3 Ironwood plan file The planning doc was a working artifact and should not be part of the PR. * fix(consensus): verify v5 Orchard bundles at NU6.3 under the NU6.3 circuit orchard_v5_verifier_for routed every upgrade from NU6.2 onward (Nu6_2, Nu6_3, Nu7, ZFuture) to the NU6.2 fixed-circuit key. That is wrong from NU6.3: the Orchard Action circuit changes again at NU6.3 to add the disableCrossAddress constraint that enforces the Orchard-pool cross-address restriction, and the fixed key cannot constrain that public input. Per ZIP-229 the restriction is enforced for every Orchard-pool Action mined from NU6.3 onward "regardless of transaction version ... so that it cannot be bypassed by using a version 5 transaction", and ZIP-258 spells out that the verifying key is selected by block height, so it applies to v5 transactions as well as v6. So an honest v5 Orchard bundle at NU6.3 proves under the NU6.3 circuit and would be rejected by the fixed key (different key) — a consensus split — while the fixed key could not enforce the restriction at all. Route NU6.3 onward (Nu6_3, Nu7, ZFuture) to the NU6.3 key, leaving Nu6_2 alone on the fixed key, so a v5 Orchard bundle at NU6.3 shares the key with v6 Orchard and Ironwood. Keys are still named V5_PRE_NU6_2 / V5_POST_NU6_2 / V6, but the docs now make clear the key is a function of block era, not transaction version. Adapts valargroup commit 85a0a38 to this branch's verifier-key names. * refactor(consensus): name the Orchard verifiers by circuit era, not transaction version The Orchard Action verifying key is selected by block era (network upgrade), not by transaction version — a v5 Orchard bundle at NU6.3 uses the NU6.3 key, the same as v6 Orchard and Ironwood. The version-flavored names (V5_PRE_NU6_2 / V5_POST_NU6_2 / V6) fought that routing and read as if v5 bundles always used a "v5" key. Rename the keys and verifiers to circuit-era names: VERIFYING_KEY_V5_PRE_NU6_2 -> VERIFYING_KEY_PRE_NU6_2 VERIFYING_KEY_V5_POST_NU6_2 -> VERIFYING_KEY_NU6_2 VERIFYING_KEY_V6 -> VERIFYING_KEY_NU6_3_ONWARD VERIFIER_V5_PRE_NU6_2 -> VERIFIER_PRE_NU6_2 VERIFIER_V5_POST_NU6_2 -> VERIFIER_NU6_2 VERIFIER_V6 -> VERIFIER_NU6_3_ONWARD The dispatch functions (orchard_v5_verifier_for / orchard_v6_verifier) keep their names: they select a verifier for the v5 vs v6 bundle path. Also refreshes the now-stale 'two eras' docs to three eras, and updates the changelog. * chore(deps): pin librustzcash crates to NU6.3 testnet-supporting pre-release version. (#10789) chore(deps): pin librustzcash crates to NU6.3-testnet supporting versions Add [patch.crates-io] directives pinning equihash, f4jumble, transparent (zcash_transparent), and the zcash_* crates to librustzcash main (rev 703fe3e6608fab8be0dedbbcbf684f030a1ea451). Removes the `zcash_unstable=nu6.3` config flag gates and updates the miner-reward transaction builders in zebra-rpc to adapt to the removed lifetime parameter on zcash_primitives' transaction `Builder` (now `Builder<P, U>` instead of `Builder<'a, P, U>`). * chore: Set the NU6.3 consensus constants to match librustzcash (#10875) feat(chain): set the NU6.3 consensus parameters to match zcash_protocol Match zcash_protocol (librustzcash rev 703fe3e): - TX_V6_VERSION_GROUP_ID = 0xD884_B698 (was the 0xFFFF_FFFF placeholder) - NU6.3 consensus branch id = 0x37a5165b (was a test-only 0xffffffff placeholder) - Testnet NU6.3 activation height = 4_134_000 Mainnet gets no NU6.3 activation height (zcash_protocol MainNetwork returns None), so only the Testnet table gains an entry. * chore(deps): Update to librustzcash 4b13be3c (#10876) chore(deps): Update to librustzcash 4b13be3c2ce6f91a4b386508aed0799358daadb1 Updates to librustzcash 4b13be3c2ce6f91a4b386508aed0799358daadb1 to fix an error in the flagging for Sprout support in NU6.3. * fix tests; bump protocol version * handle HistoryTree size increase * docs(network): scope NU7 TODO and reflow protocol-version comments NU6.3 is now fully specified (branch id, activation heights, protocol version 170_160), so drop it from the "provisional" list. Move the NU7 alternates above the active line and unindent them — the trailing-comment alignment was pushing lines past 160 chars. * chore: Update to published librustzcash NU6.3 crates. (#10877) * test(rpc): bump protocolversion snapshots to 170_160 Follow-up to ed53e03 "fix tests; bump protocol version", which raised CURRENT_NETWORK_PROTOCOL_VERSION to 170_160 for NU6.3 (Ironwood) but didn't touch the four `get_info` / `get_network_info` snapshots. * test(state): move disk_format::chain tests into their own file Per project convention (see disk_format/tests.rs, disk_db/tests.rs), tests sit in their own file rather than inline in the production module. * fix(ci): unbreak docs and cargo-vet after librustzcash bump - shielded_data.rs: `FlagFormat::Nu6_3` is stale (never existed); the type is `FlagsV6`. Drop the redundant explicit target on the `ShieldedDataV6` link while at it (rustdoc's redundant_explicit_links). - supply-chain/config.toml: bump exemption pins for orchard and the seven `zcash_*` crates to match the versions in Cargo.toml. * fix(rpc): drop dead tx_v6 cfg from coinbase_cache test after merge The merge from main brought in `coinbase_cache_reuses_built_coinbase` calling `TransactionTemplate::new_coinbase(&net, height, &miner_params, fee, #[cfg(... tx_v6)] None)`. On nu63-ironwood that function only takes four args, and the `tx_v6` feature no longer exists in `zebra-rpc/Cargo.toml`, so the cfg was permanently dead. Under `RUSTFLAGS=-D warnings` (used by clippy, check-cargo- lock, and unused-deps CI jobs), `unexpected_cfgs` promotes to an error and cascades to eight red jobs. * refactor(chain): make ironwood::Nullifier's inner field private Mirrors the encapsulation of `ironwood::ShieldedData` in the same module. Adds `impl From<Nullifier> for [u8; 32]` for byte extraction (matching `orchard::Nullifier`'s public API); callers that constructed via the tuple form switch to `Nullifier::from(orchard_nullifier)`, and the `IntoDisk` byte encoder uses `(*self).into()`. * feat(rpc): mine shielded coinbase to Ironwood pool on NU6.3+ (#10880) At NU6.3 the coinbase MUST have an empty Orchard component (ZIP-229) and newly shielded value routes to the Ironwood pool instead. Ironwood outputs use the same `orchard::Address` recipient shape as Orchard, so a Unified miner address with an Orchard receiver just gets routed to `Builder::add_ironwood_output` from NU6.3 onward. Pre-NU6.3 behavior is unchanged (Orchard output). Adds a test at the NU6.3 activation height on Testnet confirming the resulting coinbase is v6, carries Ironwood shielded data, and has no Orchard component. * fix(state): gate HistoryTreeParts legacy fallback on UnexpectedEof Addresses copilot review on #10762: `HistoryTreeParts::from_bytes` previously fell back to the pre-NU6.3 bincode layout on *any* deserialization error, so a corrupted current-format record could be silently reinterpreted as a legacy history tree. Restricts the fallback to the specific `Io(UnexpectedEof)` a narrower legacy record produces when the wider current-format deserializer runs out of bytes; any other error propagates and hits the `expect`. --------- Co-authored-by: Marek <m@rek.onl> Co-authored-by: Kris Nuttycombe <kris@nutty.land> Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
1 parent 08de689 commit 15e30b3

149 files changed

Lines changed: 3406 additions & 884 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12+
- Support for the NU6.3 "Ironwood" shielded pool and v6 transaction format,
13+
activating on Testnet at height 4,134,000. The consensus parameters (v6 version
14+
group ID, consensus branch ID, and Testnet activation height) match
15+
`zcash_protocol`. No Mainnet activation height is set yet.
1216
- Zebra now tags the coinbase input of every block it mines with a `🦓`. The
1317
`mining.extra_coinbase_data` option is now limited to 86 bytes (was 94); Zebra
1418
refuses to start if it is exceeded.
@@ -36,6 +40,13 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3640

3741
### Changed
3842

43+
- The state database format is bumped to 28.0.0 for the NU6.3 "Ironwood" shielded
44+
pool. This is a major-version bump that is restorable in place from the previous
45+
major format version (no resync): an in-place migration backfills the genesis
46+
Ironwood note commitment tree and anchor, four new (initially empty) `ironwood_*`
47+
column families are created, and the chain value pool record is widened to include
48+
the Ironwood pool. The `getblockchaininfo` and `getblock` `valuePools` now include
49+
the (zero, until NU6.3 activates) `ironwood` pool.
3950
- Opening a Zebra state read-only (for example, as a secondary instance over a
4051
running node's database) now fails with a clear error instead of panicking when
4152
the cache directory is missing or unreadable, when no database exists at the

Cargo.lock

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3569,9 +3569,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
35693569

35703570
[[package]]
35713571
name = "orchard"
3572-
version = "0.14.0"
3572+
version = "0.15.0-pre.1"
35733573
source = "registry+https://github.com/rust-lang/crates.io-index"
3574-
checksum = "a54f8d29bfb1e76a9d4e868a1a08cce2e57dd2bdc66232982822ad3114b91ab3"
3574+
checksum = "e8e277dd4b46f5d06deae3ffb8af1a951e8622368f028c2a4d6fe59339566403"
35753575
dependencies = [
35763576
"aes",
35773577
"bitvec",
@@ -7111,9 +7111,9 @@ dependencies = [
71117111

71127112
[[package]]
71137113
name = "zcash_address"
7114-
version = "0.12.0"
7114+
version = "0.13.0-pre.0"
71157115
source = "registry+https://github.com/rust-lang/crates.io-index"
7116-
checksum = "58342d0aaa8e2fa98849636f52800ac4bf020574c944c974742fc933db58cac2"
7116+
checksum = "8cf2918e73eff76388cda87695a6e7398f96a2e3383a0de7b77729a204ec00a0"
71177117
dependencies = [
71187118
"bech32",
71197119
"bs58",
@@ -7136,9 +7136,9 @@ dependencies = [
71367136

71377137
[[package]]
71387138
name = "zcash_history"
7139-
version = "0.4.0"
7139+
version = "0.5.0-pre.0"
71407140
source = "registry+https://github.com/rust-lang/crates.io-index"
7141-
checksum = "2fde17bf53792f9c756b313730da14880257d7661b5bfc69d0571c3a7c11a76d"
7141+
checksum = "82e8634d011026cb181cb67b2a412e601dc344a2827b9c576fe3a727fdebf441"
71427142
dependencies = [
71437143
"blake2b_simd",
71447144
"byteorder",
@@ -7147,9 +7147,9 @@ dependencies = [
71477147

71487148
[[package]]
71497149
name = "zcash_keys"
7150-
version = "0.14.0"
7150+
version = "0.15.0-pre.0"
71517151
source = "registry+https://github.com/rust-lang/crates.io-index"
7152-
checksum = "2fbcdfbb5c8edb247439d72a397abaae9b7dd14a1c070e7e4fc3536924f9065f"
7152+
checksum = "0a0bac3a9e5b0d954684ba1f07386d55b5ae4588b3ba2d93cad05ee09f3e0947"
71537153
dependencies = [
71547154
"bech32",
71557155
"blake2b_simd",
@@ -7188,9 +7188,9 @@ dependencies = [
71887188

71897189
[[package]]
71907190
name = "zcash_primitives"
7191-
version = "0.28.0"
7191+
version = "0.29.0-pre.0"
71927192
source = "registry+https://github.com/rust-lang/crates.io-index"
7193-
checksum = "c69e07f5eb3f682a6467b4b08ee4956f1acd1e886d70b21c4766953b3a1beba2"
7193+
checksum = "ba7bfe66975658f44dba87d535f9ebb9fb4c9c0c4fecca3f5c40cbe1583dece6"
71947194
dependencies = [
71957195
"blake2b_simd",
71967196
"block-buffer 0.11.0-rc.3",
@@ -7219,9 +7219,9 @@ dependencies = [
72197219

72207220
[[package]]
72217221
name = "zcash_proofs"
7222-
version = "0.28.0"
7222+
version = "0.29.0-pre.0"
72237223
source = "registry+https://github.com/rust-lang/crates.io-index"
7224-
checksum = "3de6b0ca82e08a9d38b1121f87c5b180b5feac19fecba074cb582882210d2371"
7224+
checksum = "b39b90964ffe6bdc314368c7b849aaa6dcc2b495249a3bf1b9bfbdc92ba4e4f6"
72257225
dependencies = [
72267226
"bellman",
72277227
"blake2b_simd",
@@ -7242,9 +7242,9 @@ dependencies = [
72427242

72437243
[[package]]
72447244
name = "zcash_protocol"
7245-
version = "0.9.0"
7245+
version = "0.10.0-pre.0"
72467246
source = "registry+https://github.com/rust-lang/crates.io-index"
7247-
checksum = "5bec496a0bd62dae98c4b26f51c5dab112d0c5350bbc2ccfdfd05bb3454f714d"
7247+
checksum = "97f339a9801c7f70295732a5cf822346f194202cea6425fc2fc14a5af679b004"
72487248
dependencies = [
72497249
"corez",
72507250
"document-features",
@@ -7281,9 +7281,9 @@ dependencies = [
72817281

72827282
[[package]]
72837283
name = "zcash_transparent"
7284-
version = "0.8.0"
7284+
version = "0.9.0-pre.0"
72857285
source = "registry+https://github.com/rust-lang/crates.io-index"
7286-
checksum = "15df1908b428d4edeb7c7caae5692e05e2e92e5c38007a40b20ac098efdffd96"
7286+
checksum = "4e941230f67056aad41c8fa9b926f9cc4d9d1a321f32e95c39c0b0e38e85f79b"
72877287
dependencies = [
72887288
"bip32",
72897289
"bs58",
@@ -7603,6 +7603,7 @@ dependencies = [
76037603
"sapling-crypto",
76047604
"semver",
76057605
"serde",
7606+
"serde-big-array",
76067607
"serde_json",
76077608
"spandoc",
76087609
"tempfile",

Cargo.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ edition = "2021"
3030

3131
[workspace.dependencies]
3232
incrementalmerkletree = { version = "0.8.2", features = ["legacy-api"] }
33-
orchard = "0.14"
33+
orchard = "0.15.0-pre.1"
3434
sapling-crypto = "0.7"
35-
zcash_address = "0.12"
35+
zcash_address = "0.13.0-pre.0"
3636
zcash_encoding = "0.4"
37-
zcash_history = "0.4"
38-
zcash_keys = "0.14"
39-
zcash_primitives = "0.28"
40-
zcash_proofs = "0.28"
41-
zcash_transparent = "0.8"
42-
zcash_protocol = "0.9"
37+
zcash_history = "0.5.0-pre.0"
38+
zcash_keys = "0.15.0-pre.0"
39+
zcash_primitives = "0.29.0-pre.0"
40+
zcash_proofs = "0.29.0-pre.0"
41+
zcash_transparent = "0.9.0-pre.0"
42+
zcash_protocol = "0.10.0-pre.0"
4343
zip32 = "0.2"
4444
abscissa_core = "0.7"
4545
base64 = "0.22.1"

supply-chain/config.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ version = "0.4.0"
10881088
criteria = "safe-to-deploy"
10891089

10901090
[[exemptions.orchard]]
1091-
version = "0.14.0"
1091+
version = "0.15.0-pre.1"
10921092
criteria = "safe-to-deploy"
10931093

10941094
[[exemptions.ordered-map]]
@@ -2112,31 +2112,31 @@ version = "0.10.4"
21122112
criteria = "safe-to-deploy"
21132113

21142114
[[exemptions.zcash_address]]
2115-
version = "0.12.0"
2115+
version = "0.13.0-pre.0"
21162116
criteria = "safe-to-deploy"
21172117

21182118
[[exemptions.zcash_encoding]]
21192119
version = "0.4.0"
21202120
criteria = "safe-to-deploy"
21212121

21222122
[[exemptions.zcash_history]]
2123-
version = "0.4.0"
2123+
version = "0.5.0-pre.0"
21242124
criteria = "safe-to-deploy"
21252125

21262126
[[exemptions.zcash_keys]]
2127-
version = "0.14.0"
2127+
version = "0.15.0-pre.0"
21282128
criteria = "safe-to-deploy"
21292129

21302130
[[exemptions.zcash_primitives]]
2131-
version = "0.28.0"
2131+
version = "0.29.0-pre.0"
21322132
criteria = "safe-to-deploy"
21332133

21342134
[[exemptions.zcash_proofs]]
2135-
version = "0.28.0"
2135+
version = "0.29.0-pre.0"
21362136
criteria = "safe-to-deploy"
21372137

21382138
[[exemptions.zcash_protocol]]
2139-
version = "0.9.0"
2139+
version = "0.10.0-pre.0"
21402140
criteria = "safe-to-deploy"
21412141

21422142
[[exemptions.zcash_script]]
@@ -2148,7 +2148,7 @@ version = "0.2.1"
21482148
criteria = "safe-to-deploy"
21492149

21502150
[[exemptions.zcash_transparent]]
2151-
version = "0.8.0"
2151+
version = "0.9.0-pre.0"
21522152
criteria = "safe-to-deploy"
21532153

21542154
[[exemptions.zebra-test]]

zebra-chain/CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- `parameters::NetworkUpgrade::Nu6_3`
13+
- `parameters::testnet::ConfiguredActivationHeights::nu6_3`
1214
- `parameters::testnet::RegtestParameters::should_allow_unshielded_coinbase_spends`:
1315
optional override for whether Regtest allows coinbase outputs to be spent into
1416
transparent outputs. Defaults to allowing them, and does not affect
1517
`Network::is_regtest()`.
18+
- `ironwood` module
19+
- `orchard::shielded_data::Flags::ENABLE_CROSS_ADDRESS`
20+
- `block::Block::{ironwood_note_commitments, ironwood_nullifiers, ironwood_transactions_count}`
21+
- `transaction::Transaction`:
22+
- `ironwood_actions`
23+
- `ironwood_flags`
24+
- `ironwood_shielded_data`
25+
- `ironwood_note_commitments`
26+
- `ironwood_nullifiers`
27+
- `ironwood_value_balance`
28+
- `has_ironwood_shielded_data`
29+
- `has_enough_ironwood_flags`
30+
- `value_balance::ValueBalance::{from_ironwood_amount, ironwood_amount, set_ironwood_value_balance}`
31+
- `value_balance::ValueBalanceError::Ironwood`
32+
- `parallel::tree::NoteCommitmentTrees`:
33+
- `ironwood`
34+
- `ironwood_subtree`
35+
- `update_ironwood_note_commitment_tree`
36+
- `parallel::tree::NoteCommitmentTreeError::Ironwood`
37+
- `primitives::zcash_history::V3` (the ZIP-221 Ironwood history node).
38+
- `impl Version for zcash_history::version::V3`
39+
40+
### Changed
41+
42+
- The following history-tree functions now take an additional
43+
`ironwood_root: &orchard::tree::Root` parameter:
44+
- `history_tree::HistoryTree::{from_block, push}`
45+
- `history_tree::NonEmptyHistoryTree::{from_block, push, try_extend}`
46+
- `primitives::zcash_history::Tree::{append_leaf, new_from_block}`
47+
- `primitives::zcash_history::Version::block_to_history_node`
48+
- `value_balance::ValueBalance<NonNegative>::to_bytes` now returns `[u8; 48]`
49+
(was `[u8; 40]`), to include the Ironwood pool balance.
50+
51+
### Removed
52+
53+
- `transaction::Transaction::zip233_amount` (the abandoned ZIP-233 burn amount).
1654

1755
## [10.1.0] - 2026-06-18
1856

zebra-chain/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ proptest-impl = [
4949

5050
bench = ["zebra-test"]
5151

52-
tx_v6 = []
53-
5452
[dependencies]
5553

5654
derive-getters = { workspace = true, features = ["auto_copy_getters"] }

zebra-chain/src/block.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
amount::{DeferredPoolBalanceChange, NegativeAllowed},
99
block::merkle::AuthDataRoot,
1010
fmt::DisplayToDebug,
11-
orchard,
11+
ironwood, orchard,
1212
parameters::{Network, NetworkUpgrade},
1313
sapling,
1414
serialization::TrustedPreallocate,
@@ -161,6 +161,13 @@ impl Block {
161161
.flat_map(|transaction| transaction.orchard_nullifiers())
162162
}
163163

164+
/// Access the [`ironwood::Nullifier`]s from all transactions in this block.
165+
pub fn ironwood_nullifiers(&self) -> impl Iterator<Item = ironwood::Nullifier> + '_ {
166+
self.transactions
167+
.iter()
168+
.flat_map(|transaction| transaction.ironwood_nullifiers())
169+
}
170+
164171
/// Access the [`sprout::NoteCommitment`]s from all transactions in this block.
165172
pub fn sprout_note_commitments(&self) -> impl Iterator<Item = &sprout::NoteCommitment> {
166173
self.transactions
@@ -185,6 +192,13 @@ impl Block {
185192
.flat_map(|transaction| transaction.orchard_note_commitments())
186193
}
187194

195+
/// Access the [ironwood note commitments](pallas::Base) from all transactions in this block.
196+
pub fn ironwood_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
197+
self.transactions
198+
.iter()
199+
.flat_map(|transaction| transaction.ironwood_note_commitments())
200+
}
201+
188202
/// Count how many Sapling transactions exist in a block,
189203
/// i.e. transactions "where either of vSpendsSapling or vOutputsSapling is non-empty"
190204
/// <https://zips.z.cash/zip-0221#tree-node-specification>.
@@ -209,6 +223,17 @@ impl Block {
209223
.expect("number of transactions must fit u64")
210224
}
211225

226+
/// Count how many Ironwood transactions exist in a block,
227+
/// i.e. transactions where the Ironwood bundle is non-empty (NU6.3 onward).
228+
pub fn ironwood_transactions_count(&self) -> u64 {
229+
self.transactions
230+
.iter()
231+
.filter(|tx| tx.has_ironwood_shielded_data())
232+
.count()
233+
.try_into()
234+
.expect("number of transactions must fit u64")
235+
}
236+
212237
/// Returns the overall chain value pool change in this block---the negative sum of the
213238
/// transaction value balances in this block.
214239
///

zebra-chain/src/block/arbitrary.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ impl Block {
429429
let mut chain_value_pools = ValueBalance::zero();
430430
let mut sapling_tree = sapling::tree::NoteCommitmentTree::default();
431431
let mut orchard_tree = orchard::tree::NoteCommitmentTree::default();
432+
// Ironwood reuses the Orchard note commitment tree type. Generated blocks have no
433+
// Ironwood data, so this stays empty, but it must be threaded through the V3 history
434+
// node (NU6.3+) using its real empty-tree root so commitments match validation.
435+
let mut ironwood_tree = orchard::tree::NoteCommitmentTree::default();
432436
// The history tree usually takes care of "creating itself". But this
433437
// only works when blocks are pushed into it starting from genesis
434438
// (or at least pre-Heartwood, where the tree is not required).
@@ -468,6 +472,10 @@ impl Block {
468472
for orchard_note_commitment in transaction.orchard_note_commitments() {
469473
orchard_tree.append(*orchard_note_commitment).unwrap();
470474
}
475+
for ironwood_note_commitment in transaction.ironwood_note_commitments()
476+
{
477+
ironwood_tree.append(*ironwood_note_commitment).unwrap();
478+
}
471479
}
472480
new_transactions.push(Arc::new(transaction));
473481
}
@@ -531,6 +539,7 @@ impl Block {
531539
Arc::new(block.clone()),
532540
&sapling_tree.root(),
533541
&orchard_tree.root(),
542+
&ironwood_tree.root(),
534543
)
535544
.unwrap();
536545
} else {
@@ -540,6 +549,7 @@ impl Block {
540549
Arc::new(block.clone()),
541550
&sapling_tree.root(),
542551
&orchard_tree.root(),
552+
&ironwood_tree.root(),
543553
)
544554
.unwrap(),
545555
);
@@ -598,7 +608,6 @@ where
598608
sapling_shielded_data,
599609
..
600610
} => *sapling_shielded_data = None,
601-
#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
602611
Transaction::V6 {
603612
sapling_shielded_data,
604613
..

zebra-chain/src/block/commitment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl Commitment {
139139
}
140140
}
141141
(Heartwood | Canopy, _) => Ok(ChainHistoryRoot(ChainHistoryMmrRootHash(bytes))),
142-
(Nu5 | Nu6 | Nu6_1 | Nu6_2 | Nu7, _) => Ok(ChainHistoryBlockTxAuthCommitment(
142+
(Nu5 | Nu6 | Nu6_1 | Nu6_2 | Nu6_3 | Nu7, _) => Ok(ChainHistoryBlockTxAuthCommitment(
143143
ChainHistoryBlockTxAuthCommitmentHash(bytes),
144144
)),
145145

0 commit comments

Comments
 (0)