feat(consensus): implement ZIP 233 behind a build feature - #857
Closed
evan-forbes wants to merge 13 commits into
Closed
feat(consensus): implement ZIP 233 behind a build feature#857evan-forbes wants to merge 13 commits into
evan-forbes wants to merge 13 commits into
Conversation
Implements ZIP 218 "25-second Block Target Spacing" (zcash/zips#1215), gated on the new `zip218` cargo feature so a default build keeps today's consensus. Even in a `zip218` build the rules stay dormant until NU7 has an activation height on the configured network, which no production network sets yet. Block target spacing and issuance: - `PostNU7PoWTargetSpacing` = 25 seconds, an NU7 era in `target_spacings`, and a `NU7PoWTargetSpacingRatio` of 3. - `halving` folds over the per-era spacings instead of branching on Blossom, and `block_subsidy` scales by `era_spacing / pre_blossom_spacing`. Both are the spec's segmented sum with the common denominator factored out, so a future spacing change is one tuple, not another branch. Issuance per unit of wall-clock time is unchanged. Difficulty: - `PostNU7PoWAveragingWindow` = 102, and `PoWAveragingWindow` becomes a height-dependent function that `AdjustedDifficulty` consults for the mean target and the median timespan. - `POW_ADJUSTMENT_BLOCK_SPAN` now covers the widest window the build can use, so a `zip218` build carries 113 predecessors of difficulty context from genesis and ignores the entries past the window in force at the candidate height. The span stays 28 in a default build. Shielded action limits: - Per-block limits of 330 Orchard actions, 300 Sapling spends + outputs, and 25 Sprout JoinSplits, plus a global shielded budget of 330 over `orchard + sapling_ios + 2 * joinsplits`. - `shielded_action_limits_are_valid` runs over any iterator of transactions: the block verifier passes the whole block, and the transaction verifier passes one transaction, so the mempool rejects a transaction whose own counts can never be mined. - `getblocktemplate` tracks the same limits through a `BlockTemplateLimits` struct, which replaces the three loose counters and the `TryUpdateBlockLimits` trait, so template selection cannot build a block the verifier would reject. The non-consensus constants ZIP 218 recommends scaling (the expiry delta, `MAX_REORG_LENGTH`, and the block download and retention windows) are left for a follow-up.
`linked_validation_context` hardcoded 27 predecessors, duplicating `POW_PREDECESSOR_CONTEXT_SPAN` rather than reading it. The recovery audit requires exactly `min(finalized_height, POW_PREDECESSOR_CONTEXT_SPAN)` context rows, so the two disagree as soon as the span changes, and full state initialization fails its own audit. Read the constant instead, and derive the boundary test's expectations from it. Also generalize the tests that hardcoded the 28-block difficulty adjustment span or the 17-block averaging window, and bump zakura-consensus to 8.0.0 for the new `TransactionError` variants.
…eight Implements ZIP 2003 "Disallow version 4 transactions". Zakura already rejected V4 transactions at NU7, but the height was welded to the NU7 activation height. The NU7 scope polls offer three deprecation dates, so `V4Deprecation` names the choice instead: - `AtNu7`, the ZIP 2003 default and today's behaviour, resolving to the NU7 activation height. A network that does not activate NU7 keeps accepting V4 transactions. - `AtHeight(height)`, for a network that activates NU7 and deprecates V4 transactions later. - `Never`, for a network that keeps accepting V4 transactions. `verify_v4_transaction_network_upgrade` now rejects on the resolved height rather than on the NU7 arm of its network upgrade match, so Mainnet and the default Testnet keep the rule they have today. Testnet configures the choice through `testnet_parameters.v4_deprecation`, which accepts `"nu7"`, `"never"`, or a block height.
Implements ZIP 233 "Network Sustainability Mechanism: Removing Funds From
Circulation", gated on the new `zip233` cargo feature so a default build
keeps today's v6 transaction format and consensus.
Transaction format:
- `Transaction::V6` gains `zip233_amount`, serialized as an 8-byte
little-endian amount after `nExpiryHeight`, and committed to by the ZIP
244 header digest as T.1f. `zakura-primitives` implements the same two
changes behind its own `zip-233` feature, which the `zip233` feature
turns on, so the native ZIP 244 digests and `librustzcash` agree.
- That `zakura-primitives` code is also gated on the `zcash_unstable =
"nu7"` cfg, which no cargo feature can set. A `zip233` build without
`RUSTFLAGS='--cfg zcash_unstable="nu7"'` would disagree with
`librustzcash` about the v6 wire format, which is a consensus split
rather than a compile error, so `compile_error!` fails the build
instead.
- A `zcash_unstable = "nu7"` build gives NU7 the branch ID
`zcash_protocol` maps it to, because `Transaction::to_librustzcash`
round-trips the branch ID through it. The placeholder is unchanged in
any other build.
Consensus:
- `zip233_amount_is_valid` rejects a transaction that removes value from
circulation before NU7, which is when ZIP 233 activates.
`Amount<NonNegative>` already bounds the amount to {0 .. MAX_MONEY}.
- `miner_fee` subtracts the burn from the transaction's remaining value.
The coinbase is checked against the block subsidy plus these fees, so
subtracting it here is what stops the coinbase reclaiming the burn and
makes the value leave the chain value pools for good.
- `has_inputs_and_outputs` accepts a transaction whose only effect is a
burn, because the burn is somewhere for its value to go.
Deferred: the RPC transaction JSON does not expose `zip233Amount` yet.
A `zcash_unstable = "nu7"` build gives NU7 the branch ID `zcash_protocol` recognises, so that case no longer offers an unrecognized branch ID to librustzcash. The other two cases still cover a failed conversion.
This was referenced Sep 1, 2026
…reak Cargo features have to be additive: `cargo check --all-features` must compile. The `compile_error!` that fired when the `zip233` feature was set without `RUSTFLAGS='--cfg zcash_unstable="nu7"'` broke that check. `ZIP233_ENABLED` now requires the cfg as well as the feature, which is exactly what `zakura-primitives` gates its half of ZIP 233 on. The two sides are therefore on and off together and cannot disagree about the v6 wire format, so the split the `compile_error!` detected is now impossible to express. A `zip233` build without the flag follows today's consensus rather than half of ZIP 233. Adding `zip233_amount` to `Transaction::V6` is `enum_struct_variant_field_added`, so `zakura-chain` takes a major version bump, as does `zakura-consensus` for its new `TransactionError` variant.
`zakura-chain`, `zakura-header-chain` and `zakura-state` each gained a `zip218` feature, and each sits at a version already on crates.io without it. Publishing their dependents at bumped versions leaves `<crate>/zip218` unresolvable on the index, which `scripts/check-crate-publish-graph.sh` rejects. Adding a feature is a minor change, so bump `zakura-chain` to 6.2.0, `zakura-header-chain` to 1.2.0 and `zakura-state` to 7.2.0, and move every in-workspace requirement with them. crates.io published `zakura-header-chain` 1.1.0 today, which is what turned this latent gap into a red check.
# Conflicts: # Cargo.lock # crates/zakura-chain/Cargo.toml # crates/zakura-consensus/Cargo.toml # crates/zakura-header-chain/Cargo.toml # crates/zakura-network/Cargo.toml # crates/zakura-node-services/Cargo.toml # crates/zakura-rpc/Cargo.toml # crates/zakura-script/Cargo.toml # crates/zakura-state/Cargo.toml # crates/zakura-utils/Cargo.toml # crates/zakurad/Cargo.toml
evan-forbes
force-pushed
the
feat/zip2003-disallow-v4
branch
from
September 5, 2026 05:22
0ba4e21 to
81b6ba0
Compare
Contributor
Author
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.
Stacked on #856 (ZIP 2003) → #851 (ZIP 218). Third of the remaining NU7 consensus changes.
Implements ZIP 233 "Network Sustainability Mechanism: Removing Funds From Circulation" behind the off-by-default
zip233cargo feature. Ported from the merged upstream zebra#8930, extended here with the ZIP 244 commitment, the activation rule, and the build-flag guard.ZIP 233 is one of the two ZIPs behind Q1 of the ZCAP and coinholder polls, and it is what makes ZIP 234's reissuance and ZIP 235's fee burning expressible.
Transaction format
Transaction::V6gainszip233_amount, serialized as an 8-byte little-endian amount afternExpiryHeightand committed to by the ZIP 244 header digest as T.1f.zakura-primitivesalready implements the same two changes behind its ownzip-233feature, whichzip233turns on, so the native ZIP 244 digests andlibrustzcashstay in lockstep. The existingnative_zip244_matches_test_vectors,v6_zip244_sighash_matches_librustzcash, andscript_code_commitment_is_version_specificcross-checks all pass with the feature on, which is what shows the two implementations agree byte for byte.The build flag
The
zakura-primitiveshalf is also gated oncfg(zcash_unstable = "nu7"), which no cargo feature can set on a dependency. Azip233build withoutRUSTFLAGS='--cfg zcash_unstable="nu7"'would disagree withlibrustzcashabout the v6 wire format — a consensus split, not a compile error.Cargo features have to be additive, so this cannot be a
compile_error!:cargo check --all-featuresruns in CI and would fail. InsteadZIP233_ENABLEDrequires the cfg as well as the feature, so a build missing the flag follows today's consensus rather than half of ZIP 233. The two sides are on and off together by construction. The CI job sets the flag.A
zcash_unstable = "nu7"build also gives NU7 the branch IDzcash_protocolmaps it to (0xffffffff) rather than Zakura's0xfffffffeplaceholder, becauseTransaction::to_librustzcashround-trips the branch ID through it. Any other build is unchanged.Consensus
zip233_amount_is_validrejects a burn before NU7, which is when ZIP 233 activates.Amount<NonNegative>already bounds the amount to{0 .. MAX_MONEY}.miner_feesubtracts the burn from the transaction's remaining value. The coinbase is checked against the block subsidy plus these fees, so this one subtraction is what stops the coinbase reclaiming the burn: the block's net transparent pool change becomessubsidy − burn, and the money reserve ZIP 234 reads grows by the burned amount.has_inputs_and_outputsaccepts a transaction whose only effect is a burn.Tests
zip233_amount_is_committed_to_by_the_txid— two transactions differing only in the burn must have different txids, and both must round-trip and matchlibrustzcash. Without this commitment the amount would be malleable.zip233_amount_activates_at_nu7_and_is_not_a_fee— the activation boundary and the fee exclusion.zip233CI job mirroring thezip218one, with the requiredRUSTFLAGS.Default build: 1270 tests pass across
zakura-chain,zakura-consensus,zakura-rpc, andzakura-state; clippy clean.Follow-ups
zip233Amount.zip233build changes the v6 wire format from NU6.3 onward, not from NU7, because that is whatzakura-primitivesdoes today (TxVersion::V6 => has_zip233() == true). A real deployment needs the format gated on the branch ID so NU6.3-era v6 transactions keep parsing, which is a change in the fork first.0x77190ad8) still needs coordinating across Zakura andlibrustzcash.