fix(chain): generate v6 transactions in NU6.3+ arbitrary strategies#6
Open
zancas wants to merge 361 commits into
Open
fix(chain): generate v6 transactions in NU6.3+ arbitrary strategies#6zancas wants to merge 361 commits into
zancas wants to merge 361 commits into
Conversation
conditional sentry
Make testnet integration test jobs continue on error in CI workflow
…ion#10259) * fix(ci): make GCP instance template creation idempotent Add a check to skip instance template creation if it already exists. GCP instance templates are immutable resources - they cannot be updated or deleted while referenced by a MIG. Since the template name includes the commit SHA, the same commit produces the same template configuration, making it safe to reuse an existing template. Without this check, workflow re-runs or concurrent triggers on the same commit fail with "already exists" error. * style(ci): remove backtick comment from gcloud command
…edBlock (ZcashFoundation#9979) * refactor(state): improve error propagation for CommitCheckpointVerifiedError * Update zebra-state/src/error.rs implements review feedback Co-authored-by: Arya <aryasolhi@gmail.com> * refactor(state): align checkpoint commit error handling with semantic commit flow * - Condenses error types and variants - Updates `KnownBlock` request handling to check for block hashes in the state's sent hashes, fixing a bug where it's possible for part of a chain to be duplicated in the non-finalized state if a block is sent to the state via a `CommitSemanticallyVerifiedBlock` request while it's in the block write channel or before the non-finalized state channel has been updated * Updates `write_block()` to return a `CommitCheckpointVerifiedBlock` error and removes the `CloneError` variant * Panic if Zebra fails to write a block to rocksdb * fix: clippy lints and remove forgotten unwrap after error refactor * fix Request docs and Duplicate error message * fixes fmt whitespace * sync: consolidate duplicate/commit checks in should_restart_sync * should_restart_sync: make duplicate/commit checks more defensive * should_restart_sync: revert previous changes and propagate CommitBlockError * sync: clean up should_restart_sync and add unit test for CommitBlockError::Duplicate * refactor(zebra-state): box ValidateContextError and eliminate redundant clone --------- Co-authored-by: Arya <aryasolhi@gmail.com>
…stDifficulty (ZcashFoundation#10056) * feature: add new bounded vec type and apply in AdjustDifficulty * feat: Bounded now enforces a minimum length and improved documentation * refactor: implement AtLeastOne type as a specialized version of Bounded * refactor(zebra-chain): refactor AtLeastOne to BoundedVec type alias
…on#10185) * Refactor subsidy constants into dedicated modules * Use qualified names instead of name suffixes * Restrict subsidy constants modules to crate visibility --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…w calls (ZcashFoundation#10264) * fix(ci): move continue-on-error inside reusable workflow for testnet jobs `continue-on-error` is not supported at the caller job level for reusable workflows. Move it inside the reusable workflow via a new `allow_failure` input parameter. This was introduced in ZcashFoundation#10251 and caused workflow parsing errors. * fix(ci): truncate branch name and make disk creation idempotent - Add slug-maxlength: 23 to github-slug-action to keep instance names under 63 chars - Restore original disk naming (test_id-sha) without branch for shared data disks - Make disk creation idempotent: reuse existing disk if present from failed runs The data disk architecture is designed to share disks across branches for the same commit SHA (same code = same state). Instance names include branch for parallel execution across branches.
…oundation#10270) * fix(docker): replace gosu with setpriv for privilege dropping The tianon/gosu:trixie image recently lost arm64 support (2026-02-04), breaking our multi-arch Docker builds. Replace gosu with setpriv, which: - Is part of util-linux, already included in debian:trixie-slim - Requires no external Docker image dependency - Provides the same privilege dropping functionality - Has native multi-arch support Add explicit privilege drop test to CI that verifies the entrypoint correctly drops from root to uid=10001(zebra). * fix(ci): escape parentheses in grep pattern for privilege drop test * docs: add ADR-0004 for setpriv, mark ADR-0002 as superseded
Update vergen-git2 dependency to version 9 in Cargo.toml
Update Cargo.lock with latest dependency versions
* Update Mainnet checkpoints up to height 3228806 * Add new Testnet checkpoints up to block height 3824000
* Update changelog with Zebra 4.1.0 release notes and contributor list * hotfix: do major bumps for every crate that depends on zebra-chain (ZcashFoundation#10269) * do major bumps for every crate that depends on zebra-chain * Update .github/PULL_REQUEST_TEMPLATE/hotfix-release-checklist.md Co-authored-by: Marek <mail@marek.onl> * Apply suggestions from code review Co-authored-by: Daira-Emma Hopwood <daira@jacaranda.org> --------- Co-authored-by: Marek <mail@marek.onl> Co-authored-by: Daira-Emma Hopwood <daira@jacaranda.org> * Update crate versions * Update estimated release height to 3231000 * Apply suggestions from code review Co-authored-by: Conrado <conrado@zfnd.org> * Remove subsidy constants module entry from changelog --------- Co-authored-by: Conrado <conrado@zfnd.org> Co-authored-by: Daira-Emma Hopwood <daira@jacaranda.org>
* set MSRV to 1.85 and actually test it in CI * revert the MSRV build to do each crate, skip zebrad
* Refactor subsidy constants into dedicated modules * Use qualified names instead of name suffixes * Restrict subsidy constants modules to crate visibility * Add Mainnet founder address constants to subsidy parameters * Add Testnet founder address constants to subsidy parameters --------- Co-authored-by: Arya <aryasolhi@gmail.com>
* initial approach * apply spent filter, do fixes * code improvments * fix some differences with zcashd * fix mempool path * add spend check in mempool, improve script type display * fix test * remove closure * fix unreachable message * fmt * Suggestions for "feat(rpc): Implement gettxout RPC method ZcashFoundation#10235" (ZcashFoundation#10237) * - Removes boxed response - Condenses two mempool requests into one - Adds a TODO about ensuring the returned tip hash is valid in all responses * remove mempool test, fix minor doc failures --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com> * add network argument to method * remove unnecessary wrapper * fix some comments * change default mempool * add changelog entries --------- Co-authored-by: Arya <aryasolhi@gmail.com>
…10298) * remove openapi * changelog * Apply suggestion from @gustavovalverde Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com> * Apply suggestion from @oxarbitrage --------- Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com>
…ADES_IN_ORDER` (ZcashFoundation#10096) * refactor(zebra-chain): use strum EnumIter instead of NETWORK_UPGRADES_IN_ORDER * refactor(zebra-chain): update changelog and move tests * Update zebra-chain/CHANGELOG.md Co-authored-by: Marek <m@rek.onl> * Update zebra-chain/CHANGELOG.md Co-authored-by: Marek <m@rek.onl> * chore: update Cargo.lock * add note about order to `NetworkUpgrade` enum --------- Co-authored-by: Marek <m@rek.onl> Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…10302) * fix: clarify TransparentInputNotFound error message (ZcashFoundation#10214) The previous message "could not find a mempool transaction input UTXO in the best chain" was misleading in two ways: - "mempool transaction" implied the transaction was already in the mempool, when in fact it is being submitted to the mempool - "in the best chain" was incomplete, since the lookup also checks unconfirmed transactions in the mempool (zero-conf spending) The new message "could not find transparent input UTXO in the chain or mempool" accurately describes the lookup scope. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update zebra-consensus/src/error.rs --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…ZcashFoundation#10263) * fix(state): wait for RocksDB background jobs before releasing database lock Add `wait_for_compact()` to the database shutdown sequence to ensure all background compaction and flush jobs complete before the database is dropped. This prevents "Database likely already open" panics when another process tries to open the same database immediately after this one closes. The issue was causing intermittent CI failures in `rpc_submit_block` and `lwd_rpc_send_tx` integration tests where a second zebrad process would try to open the database before the first one had fully released the lock. * fix(tests): wait for process termination after kill() before reopening database Replace arbitrary sleep delays with explicit wait_with_output() calls after kill() to ensure zebrad processes have fully terminated before attempting to reopen the database. This prevents "Database likely already open" errors that occurred when the 3-second sleep wasn't enough time for the OS to release file locks. The kill() function only sends SIGKILL but doesn't wait for the process to terminate. Without waiting, the database lock might not be released yet when the test tries to open a new database instance. * fix(tests): use init_read_only to avoid orphaned metrics task holding database lock The StateService::new() function spawns an infinite metrics export task that holds a database clone forever. When load_tip_height_from_state_directory() used zebra_state::init(), this task would keep the database lock even after the function returned, causing "Database likely already open" panics when zebrad #2 tried to open the same database. Changed load_tip_height_from_state_directory() to use init_read_only() instead of init(). The init_read_only() function creates a minimal read-only state without spawning background tasks, so the database is properly released when the function returns. * fix(tests): address PR review feedback for database lock fixes Changes based on PR review: - Rename `load_tip_height_from_state_directory` to `load_finalized_tip_height_from_state_directory` to accurately reflect that it returns the finalized tip (which may be behind best tip) - Improve error message for spawn_blocking JoinError to cover panic/cancellation cases - Propagate errors from `wait_with_output()` instead of ignoring them - ignoring defeats the purpose of the fix since we wouldn't actually wait if it fails - Keep `#![allow(unused_imports)]` in disk_db/tests.rs with explanatory comment - this module is included via `proptest-impl` feature where imports appear unused (test functions are only compiled in test mode) * revert(state): remove wait_for_compact() from shutdown sequence The CI failures were caused by not waiting for the zebrad process to terminate, not by RocksDB's internal background work. The test fixes (wait_with_output instead of sleep) address the actual root cause. Removing wait_for_compact() because: - It can block indefinitely during shutdown - The OS could halt the thread anyway, making it unreliable - The new reopen test already passes on main without it - The WAL already ensures recoverability of in-flight writes * fix(tests): keep sleep after wait_with_output as safety margin Add back a short sleep after wait_with_output() calls. As noted in review, wait() returning Ok doesn't guarantee the OS has fully released all resources (file locks, etc.) in every edge case.
* fix: clean up banned peers in PeerSet queued state * add test * add test for cancel handle --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
* Refactor subsidy constants into dedicated modules * Use qualified names instead of name suffixes * Restrict subsidy constants modules to crate visibility * Add Mainnet founder address constants to subsidy parameters * Add Testnet founder address constants to subsidy parameters * Add From implementation for Height to u64 conversion * Add full support for calculating block subsidies * Add full support for subsidies in getblocksubsidy RPC * Add is_zero method to Amount type * Update funding stream calculation for zero subsidy blocks * Add From implementation for u32 conversion from block Height * Refactor subsidy module * Add From implementation for amount::Error to BlockError * Add founder address list getter to Network implementation * Update SubsidyError variants and rename SumOverflow to Overflow Added new SubsidyError variants FoundersRewardNotFound and Underflow. Renamed SumOverflow to Overflow and updated related error mappings. Updated CHANGELOG to reflect these changes. * Rename num_halvings function and references to halving * Add founders_reward function for initial block subsidy distribution * Add founders_reward_address function to subsidy module * Support checking pre-Canopy block rewards * Apply suggestions from code review --------- Co-authored-by: arya2 <aryasolhi@gmail.com>
…ation#10318) Add AGENTS.md, CLAUDE.md, copilot-instructions.md, update CONTRIBUTING.md and PR template to establish clear policies for AI-assisted contributions and external PR management.
* fix: getrawtransaction compat with zcashd (ZcashFoundation#10137) Two fixes for zcashd compatibility in the getrawtransaction RPC response: 1. Always include `expiryheight` field (zcashd returns 0 for transactions without expiry, Zebra was omitting the field entirely via skip_serializing_if) 2. Decode sighash type flags in scriptSig asm output (change `.to_asm(false)` to `.to_asm(true)` so signatures display with `[ALL]`, `[NONE]`, etc. suffixes matching zcashd) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: match zcashd expiryheight behavior for pre-Overwinter transactions zcashd only includes `expiryheight` in RPC responses for Overwinter+ transactions (when tx.fOverwintered is true). Pre-Overwinter V1/V2 transactions omit the field entirely. The previous commit incorrectly always included `expiryheight: 0` for all transactions. Now uses `tx.is_overwintered()` to match zcashd: - Overwinter+ (V3/V4/V5): include expiryheight (0 = no expiry per ZIP-203) - Pre-Overwinter (V1/V2): omit expiryheight zcashd ref: https://github.com/zcash/zcash/blob/cfcfcd93b/src/rpc/rawtransaction.cpp#L224-L226 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address review feedback from oxarbitrage - Use release tag v6.11.0 instead of commit hash in zcashd references - Add zcashd reference comments for .to_asm(true) and .to_asm(false) calls - Remove duplicate zcashd link from implementation comment - Move test comments to above the expiry_height fields they describe Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * make doc description a link --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
* use operpsee * use openrpsee crate * remove testing and demo schemas * fix dependencies * add todo link
…on#10326) add changelogs for openerpc
…in side chains (ZcashFoundation#10325) * adds an `AnyChainBlock` state request * Updates changelog * Avoids cloning any extra data in the non-finalized state when handling some state requests that only query the best non-finalized chain (perhaps at the expense of holding read locks slightly longer if the added traversals are worse than the copying, probably insignicant either way but this makes it easier to add fields to NonFinalizedState without worrying) Adds a `cloned_mapped_watch_data()` method on `WatchReceiver`. * Applies docs corrections, renames methods/fns, and unifies implementations for `block` and `any_chain_block` read fns (latter renamed to `any_block()`) * Factors `spawn_blocking()` and `in_scope()` calls into a new trait method. * Update zebra-state/src/request.rs * Add basic tests for `AnyChainBlock` (ZcashFoundation#10327) generate some tests * Update zebra-state/src/request.rs --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…0056 and ZcashFoundation#9979 (ZcashFoundation#10273) * chore: update CHANGELOGs to reflect changes made in ZcashFoundation#10056 * chore(zebra-state): update the CHANGELOG to reflect changes made in ZcashFoundation#9979 and ZcashFoundation#9999 * Update zebra-state/CHANGELOG.md Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com> * Fix typo in CommitCheckpointVerifiedError entry --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…nt (ZcashFoundation#10859) feat(ci): add Claude /changelog command and release-notes enhancement Add a `/changelog` issue-comment command, maintainer-gated and comment-only, that drafts Keep-a-Changelog entries for a pull request and posts them as a suggestion to paste in. Enhance the zebrad GitHub Release body by wrapping the extracted CHANGELOG section with an update-priority line and an operator summary. Both run anthropics/claude-code-action gated on a CLAUDE_ENABLED variable and a CLAUDE_CODE_OAUTH_TOKEN secret, and fall back to deterministic output (the raw PR-diff suggestion, the awk-extracted notes) when Claude is disabled or fails, so neither feature can block a release or a PR.
…cashFoundation#10864) fix(ci): pass github_token to claude-code-action so it skips OIDC The Claude steps for the /changelog command and the release-notes enhancement failed at startup trying to fetch an OIDC token (id-token: write is not granted), and because the steps are continue-on-error the failure surfaced as the deterministic fallback instead of a Claude draft. Passing github_token sets the action's OVERRIDE_GITHUB_TOKEN, so it uses that token and skips the OIDC exchange, keeping the workflows least-privilege without id-token: write.
…undation#10865) * fix(ci): check out the repo for the /changelog Claude step The claude-code-action performs git operations (configure user, fetch origin main) that assume a checked-out repo. The changelog-command workflow had no checkout, so the step failed with git exit code 128 and, being continue-on-error, silently posted the fallback comment instead of a draft. Add a checkout of the default branch (never PR head, persist-credentials off); the PR content is still supplied only as the diff passed to the prompt. * fix(ci): check out the repo for the /changelog Claude step The claude-code-action performs git operations (configure user, fetch origin main) that assume a checked-out repo. The changelog-command workflow had no checkout, so the step failed with git exit code 128 and, being continue-on-error, silently posted the fallback comment instead of a draft. Add a checkout of the default branch (never PR head, persist-credentials off), and grant contents: read so the job token can fetch; the PR content is still supplied only as the diff.
…10866) The Claude changelog and release-notes steps run with --max-turns 1, so the model spends its single turn on a reasoning preamble and hits the turn limit before emitting the forced structured output. The action reports error_max_turns (exit 1), and being continue-on-error the steps post the deterministic fallback instead of a draft. Allow enough turns for the model to reason and then return the structured result.
…ation#10849) * build(deps): bump anyhow to 1.0.103 for RUSTSEC-2026-0190 Patches an unsoundness in `anyhow::Error::downcast_mut()` (affects < 1.0.103). Lockfile-only; the workspace requirement stays `1.0`. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * build(deps): update anyhow cargo-vet exemption to 1.0.103 Carry the `safe-to-deploy` exemption forward to the bumped version so `cargo vet check` passes; otherwise 1.0.103 is unaudited. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7
…dation#10860) `integration::sync::sync_one_checkpoint_mainnet` and `restart_stop_at_height` sync mainnet to genesis from live peers and normally finish in ~10-30s, but a slow/unreachable peer occasionally stalls them toward the 30m hard limit (15m x 2), flaking on timeout. Lower the slow-timeout to 10m (still far above the ~30s norm) so a stall fails in minutes, and add retries so the transient stall recovers -- mirroring the existing disconnects_from_misbehaving_peers override.
…logs (ZcashFoundation#10853) Rewrite the legacy fallback checklist so the changelog and crate-changelog steps finalize the per-PR `[Unreleased]` entries instead of curating them from scratch at release time, and reframe the manual semver-checks step as a verification backstop behind the per-PR cargo-semver-checks gate. Fix two dead references in the hotfix checklist: the nonexistent `check_no_git_refs_in_cargo_lock` check name becomes `check-no-git-dependencies`, and the nonexistent `ci-tests.yml` workflow link becomes `zfnd-ci-integration-tests-gcp.yml`.
…cashFoundation#10869) Nightly rustc from 2026-06-30 ICEs while type-checking the two return-position `impl Service<..>` test helpers, breaking the required Lint check's nightly `unused-deps` job. Return the concrete `Buffer<BoxService<..>>` and `ReadStateService` types these helpers already build, removing the opaque-type obligation that rust-lang/rust#158477 mishandles.
…cashFoundation#10869) Nightly rustc from 2026-06-30 ICEs while type-checking the two return-position `impl Service<..>` test helpers, breaking the required Lint check's nightly `unused-deps` job. Return the concrete `Buffer<BoxService<..>>` and `ReadStateService` types these helpers already build, removing the opaque-type obligation that rust-lang/rust#158477 mishandles.
…shFoundation#10867) * fix(ci): align the /changelog prompt with the changelog guidelines The drafted entries drifted from CHANGELOG_GUIDELINES: inconsistent section headings, missing PR links, em dashes, and a broken code fence, and one enum addition was filed under Added when it is breaking. The prompt inverted the breaking-enum rule (it named non-exhaustive enums, the safe case) and never described the operator-plus-per-crate changelog structure, so the model reinvented the layout each run. State the two-changelog structure and output shape, require the PR link and exact `### ` headings, correct the breaking rule to a new variant on an enum that is not `#[non_exhaustive]`, separate operator-breaking from crate-breaking, add the Fixed-versus-Changed test, and forbid em dashes and extra code fences. * docs: add changelog guidelines and wire references to them CONTRIBUTING.md, AGENTS.md, and the PR template pointed at a root CHANGELOG_GUIDELINES.md that never existed. Add the guidelines as a book dev page next to the release process, list it in SUMMARY.md, and repoint the references. A note on the /changelog prompt marks the page as the source its rules distill, so the two stay in sync. * fix(ci): draft per-change entries and dual-list breaking additions Address review feedback that one bullet per changelog file contradicts the repo's own convention: a single PR routinely produces several entries in one file, and a breaking-yet-additive change (a new variant on an enum that is not non_exhaustive) is listed under both Breaking Changes and Added, as zebra-state does for the AnyChainBlock variants. Draft one bullet per distinct change, dual-list such additions, record the same convention in the guidelines, and point the release notes prompt at the GitHub Release Format section so both prompts track the doc. * docs: make one-entry-per-change the rule in the guidelines page The page still said one entry per PR and one section only, which the prompt (now per distinct change) and the repo contradict: PR ZcashFoundation#10798 alone carries three entries across Fixed and Security. Relax the page to one entry per distinct change, with a PR of several independent changes getting one entry each, so the page, the prompt, and the existing changelogs agree. The anti-padding and anti-grouping examples stand, and section priority now scopes to a single change. * docs(ci): scope the release-notes prompt comment to what it distills The step produces the summary and Update Priority parts of the GitHub Release Format, not the full format; How to Upgrade and Compatibility are added at release time. Correct the comment so it does not overstate the step. * fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ion#10847) * fix(rpc): cache the getblocktemplate coinbase per block `getblocktemplate` rebuilt the coinbase on every request, re-running the Sapling/Orchard proof for shielded miner addresses. The existing precompute only feeds long-poll clients, so short-polling pools (which re-request without long polling) re-proved the coinbase on every poll — pegging the CPU and making each template take seconds. Cache the built coinbase keyed on `(height, fees)` so repeated requests within a block reuse it, and clear the cache when the coinbase data is randomized. * fix(rpc): reuse the coinbase cache for the ZIP-317 fake coinbase Transaction selection builds a fake coinbase to size the block, which re-proved the shielded coinbase on every getblocktemplate call, outside the per-block cache. Route it through the same cache: only the first call per block proves. * fix(rpc): recover poisoned coinbase cache lock instead of expect The `Option`-returning `CoinbaseCache::get` tripped `clippy::unwrap_in_result` (denied via `-D warnings` in the test-crates MSRV build). Recover the guard on poison at all three lock sites; a coinbase cache need not crash the node over an unrelated thread's panic. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * docs(changelog): note the getblocktemplate coinbase cache in the top-level CHANGELOG
* ci: drop Windows and macOS runners Remove the Windows/macOS unit-test matrix and the brew/choco install branches from setup-zebra-build. Unnecessary CI overhead: no relevant node operators run those OSes, and other enthusiasts can use Docker. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * docs: demote Windows and macOS to tier 3 CI no longer builds or tests them, so they no longer meet the tier 2 "guaranteed to build" bar. Tier 3 = supported in code, untested, may or may not work — use Docker. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * test: drop Windows and macOS test exclusions Remove the `cfg(not(windows))` / `cfg(not(macos))` skips and the nextest `delete_old_databases` Windows filter, now that neither platform runs in CI. For feature-combined gates, keep the `#[cfg(feature = ...)]` and drop only the OS predicate. Genuine platform-abstraction code (`stop()`, `random_known_port`, `default_lwd_cache_dir`) is left intact. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * test: drop Windows guard from rpc.rs test imports The `metrics_endpoint` / `tracing_endpoint` helper imports kept `not(target_os = "windows")` after the tests themselves became feature-only gated, so a Windows build with `prometheus` or `filter-reload` would compile the tests without their imports and fail. Match the imports to the tests' gating. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * test: merge config.rs imports after dropping the macOS gate Addresses review: the two `crate::common` import blocks can be one now that the `#[cfg(not(target_os = "macos"))]` gate is gone. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7 * ci: keep the Linux guard on setup-zebra-build's apt step Addresses review: the composite action is Linux-only, but guarding the apt-get step keeps it inert if ever reused on another runner. Claude-Session: https://claude.ai/code/session_01VUPwpe81Ws8c5MeXWWgZb7
…undation#10767) * fix(ci): repair GCP resource cleanup so it deletes old disks The daily cleanup deleted nothing: it passed each disk to gcloud as a single quoted "<name> --zone=<zone>" argument, which gcloud rejected as underspecified. The swallowed failure let orphaned per-commit test disks pile up until the regional SSD_TOTAL_GB quota filled and integration tests failed at instance creation. Pass the name and --zone/--region as separate arguments, add --quiet, and delete only unattached disks. Scope the cleanup to the dev project. * fix(ci): keep persistent zebrad-cache disks out of cleanup The "zebrad-" disk filter matched zebrad-cache-* chain-state disks, which the -users:* guard only protects while attached. Exclude them so an unattached cached-state disk is never deleted. --------- Co-authored-by: andres-pcg <androdriguez14@gmail.com>
…ag (ZcashFoundation#10818) The co-located read-state syncer (`TrustedChainSync`, used by Zaino's `ReadStateService` backend) shares its secondary RocksDB instance with the `update_finalized_chain_tip` task. While the secondary lags the primary, that task calls `try_catch_up_with_primary` ~1/sec, advancing the secondary's view concurrently with `sync()`'s commits. A block can become finalized between `sync()`'s pre-commit check and the commit itself, so the commit fails as a duplicate-effects error (`Duplicate*Nullifier { in_finalized_state: true }`), drops the subscription, and re-subscribes — the per-second churn in ZcashFoundation#10803. Stop `update_finalized_chain_tip` as soon as `sync()` receives its first parseable block from the `non_finalized_state_change` stream, via a dedicated watch signal. From then on `sync()` is the sole caller of catch-up on the secondary, so its view can't advance between a check and a commit. The task also yields the published chain tip to `sync()`'s (higher) non-finalized tip at that point, so the handover no longer drags the reported tip backwards. Also skip re-inserting an emptied side chain into the chain set during finalization, which could otherwise leave a zero-length chain behind.
…hFoundation#10726) * feat(zebrad): run a command on chain tip changes (block notify) Add a `[notify]` config section with a `block_notify_command` option, Zebra's equivalent of zcashd's `-blocknotify`. Whenever the best chain tip changes and the node is close to the network tip, the command is run via the system shell with every `%s` replaced by the new tip's block hash. The command is detached and never blocks block validation. * test(zebrad): gate Unix-shell block notify test to non-Windows The spawn integration test uses /bin/sh command syntax, which fails under the Windows cmd /C branch. Gate it to non-Windows; render_command and the non-zero exit test still cover Windows. * refactor(zebrad): reap notify child with wait() not wait_with_output() stdout/stderr are redirected to `/dev/null`, so there is nothing to collect; `wait()` only needs the exit status and avoids the extra `Output` buffers. * fix(zebrad): instrument block-notify reaper instead of entering span across await
* 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 ZcashFoundation#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 ZcashFoundation#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. (ZcashFoundation#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 (ZcashFoundation#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 (ZcashFoundation#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. (ZcashFoundation#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+ (ZcashFoundation#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 ZcashFoundation#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>
…ZcashFoundation#10889) * test(rpc): add regression test for getblock side-chain panic (GHSA-x6v8-c2xp-928m) * fix(rpc): use i64 for transaction confirmations to avoid side-chain panic (GHSA-x6v8-c2xp-928m) * fix(rpc): resolve clippy and rustfmt warnings Remove unused `mut` on mempool mock and apply rustfmt formatting. --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
…cashFoundation#10890) * fix(mempool): enforce per-peer admission cap on directly pushed transactions Direct P2P `tx` messages were turned into an internal push request that dropped the sending peer's address, so they entered the mempool download queue with `source = None` and skipped the per-peer admission cap (`MAX_INBOUND_CONCURRENCY_PER_PEER`). A single inbound peer could hold more than its intended slots by pushing full transactions instead of advertising txids, crowding out honest peers' relay. The global cap still bounded total in-flight work, so this is a per-peer fairness bypass, not exhaustion. Thread the sending peer through the push path and route both advertised txids and pushed transactions through the same per-peer accounting: - `Request::PushTransaction` now carries `Option<PeerSocketAddr>`, mirroring `AdvertiseTransactionIds`; the `Message::Tx` handler tags it with the peer's transient address. - `QueueFromPeer` is generalized to `{ candidates: Vec<Gossip>, source }` and handles advertised IDs and full pushed transactions alike. Per-peer accounting converges at the downloader for every peer-originated candidate. Direct counterpart of GHSA-4fc2-h7jh-287c (advertisement path). Fixes GHSA-m9xx-8rcj-vmgp. * test(mempool): cover inbound routing of peer-pushed transactions Add an inbound-service-level regression test for GHSA-m9xx-8rcj-vmgp. A directly pushed transaction tagged with a sending peer must route through `mempool::Request::QueueFromPeer` (per-peer capped); a source-less local/internal push stays on the uncapped `Queue` path. The existing downloader-level test exercised the per-peer cap, which already worked before the fix — the regression lived in this routing decision in `inbound.rs`. Addresses review feedback on ZcashFoundation#153. Claude-Session: https://claude.ai/code/session_01Mci1w8MDU9yfvKX1dc14M6
…, and state (ZcashFoundation#10884) * fix(consensus): enforce coinbase output-decryptability for the Ironwood pool NU6.3+ routes shielded coinbase value into the Ironwood pool, but `decrypts_successfully` only iterated the Sapling and Orchard bundles, so the all-zero-OVK coinbase decryptability rule (ZIP-213) was never applied to Ironwood coinbase outputs. Add the Ironwood arm using `IronwoodDomain`. * fix(mempool): track Ironwood nullifiers for spend-conflict detection `VerifiedSet` tracked sprout/sapling/orchard nullifiers but not Ironwood, so two mempool txs revealing the same Ironwood nullifier were both admitted, and `reject_and_remove_same_effects` never evicted a mempool tx double-spending a just-mined Ironwood nullifier. Either lets `getblocktemplate` build a block template that every node then rejects. Mirror the Orchard handling for the Ironwood pool. * fix(consensus): fail closed instead of panicking on unexpected halo2 batch error The `RestrictionUnsupportedByKey` arm called `unreachable!` inside the shared, long-lived halo2 verifier service, so a future routing change or new `#[non_exhaustive]` `BatchError` variant would crash the node instead of rejecting one proof. Collapse it into the existing graceful per-item arm; the routing invariant is kept as a comment. * fix(state): include the Ironwood pool in supply metrics and spend indexing `value_pool_metrics`/`chain_supply.total` omitted the Ironwood pool, so total supply under-reported it every block post-NU6.3; and the `indexer` back-fill's spend-sampling chain skipped Ironwood-only v6 txs. Add the Ironwood gauge, the supply sum term, and Ironwood to the sampled spends. * feat(rpc): expose Ironwood bundles in verbose transaction output `getrawtransaction <txid> 1` / `getblock <hash> 2` serialized an `orchard` section but no `ironwood` one, so v6 Ironwood bundles (including Zebra's own Ironwood coinbase) were invisible to explorers and wallets. Add an `ironwood` field reusing the Orchard-shaped object, and factor the shared construction into `orchard_shaped_object` (also dropping an incidental O(n^2) action scan). * refactor: deduplicate Ironwood/Orchard tree-update and bundle-verify paths Ironwood reuses the Orchard tree and bundle types, so `update_ironwood_note_commitment_tree` now delegates to the Orchard version (re-tagging only the error variant), and `verify_orchard_v6_bundle` / `verify_orchard_bundle` share a `queue_orchard_bundle` helper. No behavior change. * chore: remove the dead ZIP-235 cfg and error variant The ZIP-235 burn validation and its tests were removed, but the `zip235` `cfg` value and `SubsidyError::InvalidZip233Amount` (gated on it) remained, so a `--cfg zcash_unstable="zip235"` build compiled yet enforced nothing. Drop both; they can return with the NU7 NSM implementation. * fix(state): fall back to the empty Ironwood tree before the genesis backfill `ironwood_tree_by_height` panicked when the Ironwood column family was still empty, which happens in the brief window after a v27->v28 upgrade before the background `add_ironwood_tree` migration backfills genesis. That window is always pre-NU6.3, when the Ironwood tree at every height is in fact empty, so return the empty tree instead of panicking. * refactor(state): share the Orchard/Ironwood tree-and-anchor add/remove logic `add_/remove_{orchard,ironwood}_tree_and_anchor` were verbatim copies over identically-typed field triples, including the reorg `RevertPosition::Root` invariant-restoration. Extract shared `add_/remove_note_commitment_tree_and_anchor` helpers parameterized on the target fields and a pool label. No behavior change (non-finalized-state prop + reorg tests pass). * refactor(chain): pass block commitment tree roots in a named struct The history-tree functions took `sapling_root`, `orchard_root`, and `ironwood_root` as adjacent positional arguments; the Orchard and Ironwood roots reuse the same `orchard::tree::Root` type, so a swapped pair compiled cleanly and would silently corrupt the ZIP-221 chain-history commitment. Group them in a named `BlockCommitmentTreeRoots` struct so a swap can't happen. * refactor(state): share the Orchard/Ironwood note-commitment DB accessors The Ironwood tree-range, subtree-list, subtree-for-tip, tree-create, and subtree-insert accessors were verbatim copies of their Orchard counterparts, differing only in the column-family name (both pools reuse the `orchard::tree` types). Route them through CF-parameterized private helpers; the public signatures are unchanged.
…and state (ZcashFoundation#10886) * fix: correct Ironwood v6 flag, history-tree, and empty-tree rules - chain: reject the enableCrossAddress flag (bit 2) on the v6 Orchard bundle at deserialization, matching orchard::Flags::from_byte, which reserves it for the Orchard pool in every tx version. Only the Ironwood bundle uses the FlagsV6 codec. Without this a crafted v6 tx parsed but then aborted the node in the txid-path expect() when to_librustzcash rejected the flag. - state: fall back to the legacy history-tree entry width on any current-width parse error, not just UnexpectedEof. A multi-peak legacy record misaligns at the wider width and bincode fails with a varint error, which the EOF-only gate turned into an upgrade crash-loop. - state: serve the empty Ironwood tree only before NU6.3 activation; from activation onward a missing tree is a real invariant violation, so fail loudly like the Orchard accessor instead of masking corruption. * refactor(state): thread Ironwood data and trees through named types - Yield ironwood_shielded_data from the per-transaction version match in both update_chain_tip_with_block and revert_chain_with, replacing the two hand-synced `if let V6` blocks. A future tx version now cannot silently skip Ironwood nullifier tracking in one direction only. - Pass the note commitment trees to Chain::new and Treestate::new in a NoteCommitmentTrees struct instead of adjacent positional Arc<orchard:: tree::NoteCommitmentTree> arguments (orchard and ironwood share the type), so an orchard/ironwood swap is a compile error rather than silent tree corruption. Drops two too_many_arguments allows. * perf(consensus): cut per-transaction network-upgrade and bundle work - Compute NetworkUpgrade::current once in check_structure_and_network_rules and pass it into orchard_value_balance_non_negative and coinbase_orchard_component_empty, instead of each rebuilding the activation-height map per transaction. - Store the halo2 Item bundle in an Arc so the eager clone tower-fallback makes for every request shares the bundle instead of deep-copying its actions and multi-KB proof; add_bundle only needs a reference. * refactor(chain): deduplicate history-tree rebuild and Ironwood checks - Share the per-variant InnerHistoryTree rebuild between prune() and the Clone impl via rebuilt_inner(), so a new history-tree version adds its arm once. - Test Ironwood-bundle presence with has_ironwood_shielded_data() instead of ironwood_actions().count(), which walked the AtLeastOne action list. - Delegate insert_fake_orchard_shielded_data to fake_v6_orchard_shielded_data. * fix(state): keep the indexer Spend enum's nullifier imports The refactor(state) commit dropped sprout/sapling/orchard from the shared zebra_chain import block (unused without the indexer feature), but the indexer-only Spend enum and its From impls still reference them, breaking `cargo check --features indexer` (9 x E0433). Re-add them alongside the ironwood import, cfg-gated to the indexer feature. * docs(chain): fix stale v6 Orchard flag comment The call-site comment still said the v6 Orchard bundle permits enableCrossAddress; the flag-mask fix reserves it for the Orchard pool (only Ironwood permits it). * refactor(chain): imply the v6 Orchard flag codec from the bundle type Address @arya2 review: tie the flagsOrchard codec to each v6 bundle newtype via a `V6FlagCodec` associated type (orchard::ShieldedDataV6 -> Flags, ironwood::ShieldedData -> FlagsV6) so the deserializers imply it instead of naming it explicitly. No behavior change.
…shFoundation#10888) Add the Ironwood read path that mirrors Orchard, so wallets and indexers can obtain Ironwood frontiers and subtree roots after NU6.3: - state: ReadRequest/ReadResponse IronwoodTree and IronwoodSubtrees variants, read::ironwood_tree / ironwood_subtrees, and the ZebraDb::ironwood_tree_by_hash_or_height accessor. - rpc: an ironwood treestate in z_gettreestate (present from NU6.3), the ironwood pool in z_getsubtreesbyindex, and the ironwood tree size in verbose getblock. All are skipped/omitted when empty, so pre-NU6.3 responses and fixtures are unchanged.
…ashFoundation#10895) fix(state): bound the empty-Ironwood-tree fallback to before NU6.3 `ironwood_tree_by_height` falls back to the empty tree when the Ironwood column family has no entry at or below the queried height, covering the brief window after a v27->v28 upgrade before the `add_ironwood_tree` background backfill seeds the genesis tree. That fallback was unbounded, so it also masked a missing tree *after* NU6.3 activation, where every committed block writes its tree and a missing one is a real invariant violation (corruption or an interrupted backfill). Bound the fallback to pre-activation heights — matching the comment's own reasoning that the window is necessarily pre-NU6.3 — and panic loudly from activation onward.
…alize (ZcashFoundation#10892) Restore the `to_librustzcash` guard on the v6 deserialize arm, matching the v5 arm. Zebra's and librustzcash's parsers are not identical, so a divergence not caught at the wire layer resurfaces as a panic in the txid/auth-digest `expect(...)` (`Hash::from`); failing closed at parse prevents that on attacker-supplied input. librustzcash's parser enforces the canonical Orchard proof size, so the v6 test helpers now emit a canonically-sized (still cryptographically invalid) proof instead of an empty one, so the round-trip accepts the fixtures.
* Update changelogs for NU6.3 Ironwood support and dependency upgrades * Bump Zebra to version 6.0.0-rc.0 and update dependent crate versions * Update end_of_support tests to use EOS_WARN_AFTER constant
Allow new release of `zebra-test` It's a crate from this workspace.
…il inferno updates (ZcashFoundation#10899) Co-authored-by: Conrado <conrado@zfnd.org>
…ehind tonic feature (ZcashFoundation#10819) * feat(tower-batch-control): impl RequestWeight for tonic::Request<T> behind tonic feature Consumers cannot implement the foreign `RequestWeight` trait for the foreign `tonic::Request<T>` type due to the orphan rule. Add the impl here, gated behind a new optional `tonic` feature, so gRPC requests can be used as the batch request type without consumer-side newtype wrappers. Uses the default request weight of 1, correct for gRPC services where each request has equal weight. Closes ZcashFoundation#10667 * Fix dependency order in Cargo.toml Move `tonic` to before `tower`. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ses at the chain tip (ZcashFoundation#10732) * Add `chain_tip` getter to `MinimumPeerVersion` In some places the `MinimumPeerVersion` becomes the way to access the `ChainTip`, so we should make that explicit instead of adding unrelated methods to `MinimumPeerVersion`. * Remove `MinimumPeerVersion::chain_tip_height` Refactor to keep `MinimumPeerVersion` more focused. * Add `ChainTip::is_at_or_near_network_tip` A helper method to check if the node can be considered to be synced. * Only count stalls while syncing Empty find block or find headers responses are expected once nodes are synced. * Test if stalls aren't tracked when close to tip Ensure that the stall tracker only disconnects peers while the node is syncing. * Test disconnection after empty find responses Empty find blocks or find headers responses can slow down synchronization, so the peers should be penalized with a disconnection. * Also test if the chain tip state is empty Ensure that the initial behavior is to disconnect from peers that return empty find blocks or headers responses. * Test stall counts are preserved after sync Ensure that if the node falls behind, it will still track stalls from peers. * Add a regression test for the disconnection issue Simulate the scenario described in the issue, where an internal Zebra node disconnects from its upstream (and sole peer) because it has finished syncing. * Update changelog List the fix to not disconnect from peers missing blocks when synced.
The `Transaction` Arbitrary dispatch only generated v4/v5 transactions for every upgrade from NU5 onward: the commit that introduced v6 and Ironwood support (ZcashFoundation#10762) appended `Nu6_3` to the existing v4/v5 arm without adding a v6 strategy. Property tests built on the generator — zebra's own and downstream consumers' — could therefore never observe a v6 transaction or an Ironwood bundle, and passed vacuously. The gap was found when a downstream (zaino) consistency test's non-vacuity guard fired: its "ironwood tree-size delta equals served action count" check was trivially 0 == 0 at every block. Changes: - Add `Transaction::v6_strategy`, generating v6 transactions with optional Sapling, v6 Orchard, and Ironwood bundles, and wire it into the `Nu6_3 | Nu7` dispatch arm alongside v4/v5. Accept a transaction version override of 6. - Add `Arbitrary` impls for `orchard::ShieldedDataV6` (reuses the base Orchard strategy: the v6 Orchard-pool bundle reserves `enableCrossAddress` exactly like v5) and `ironwood::ShieldedData` (re-generates the flag byte over all three defined bits, so the Ironwood-only `enableCrossAddress` flag and the `FlagsV6` codec path are exercised). - Add `NetworkUpgrade::nu6_3_branch_id_strategy`, emitting only `Nu6_3`: Nu7's consensus branch ID is still a placeholder that librustzcash does not recognise, so Nu7-tagged transactions cannot round-trip through `to_librustzcash`. - Handle Ironwood value balances in the proptest value fixups (`fix_chain_value_pools`, `fix_remaining_value`, `input_value_pool`, `for_each_value_balance_mut`, new `ironwood_value_balance_mut`), which would otherwise panic when a generated Ironwood bundle drains the empty ironwood chain value pool. Tests: - Add `arbitrary_transaction_versions_cover_each_era`, a non-vacuity guard asserting that each network-upgrade era generates exactly its valid transaction versions, and that NU6.3+ generation produces both v6 Orchard and Ironwood bundles. Fails against the previous dispatch arm; protects future upgrades appended to the arm. - Add `transaction_roundtrip_nu6_3`: `any::<Transaction>()` uses a mainnet-tip ledger state and NU6.3 is testnet-only, so the existing round-trip proptest never exercised v6 (including the fail-closed librustzcash round-trip in the v6 deserializer). - Extend `arbitrary_transaction_version_strategy` to versions 5 and 6 (its `1..5` range also silently excluded v5). Closes #5
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.
Motivation
The
TransactionArbitrary dispatch only generated v4/v5 transactions for every upgrade from NU5 onward: the commit that introduced v6 and Ironwood support (ZcashFoundation#10762 upstream) appendedNu6_3to the existing v4/v5 arm without adding a v6 strategy. Property tests built on the generator — zebra's own and downstream consumers' — could never observe a v6 transaction or an Ironwood bundle, and passed vacuously. The gap was found when zaino's block-consistency test added a non-vacuity guard ("ironwood tree-size delta equals served action count" was trivially0 == 0at every block).Closes #5. Also reported upstream as ZcashFoundation#10911.
Solution
Transaction::v6_strategy, generating v6 transactions with optional Sapling, v6 Orchard, and Ironwood bundles; wire it into a splitNu6_3 | Nu7dispatch arm alongside v4/v5; accepttransaction_version_override(6).Arbitraryimpls fororchard::ShieldedDataV6(reuses the base Orchard strategy — the v6 Orchard-pool bundle reservesenableCrossAddressexactly like v5) andironwood::ShieldedData(re-generates the flag byte over all three defined bits, so the Ironwood-onlyenableCrossAddressflag and theFlagsV6codec path are exercised).NetworkUpgrade::nu6_3_branch_id_strategy, emitting onlyNu6_3: Nu7's consensus branch ID is still a placeholder that librustzcash does not recognise, so Nu7-tagged transactions cannot round-trip throughto_librustzcash.fix_chain_value_pools,fix_remaining_value,input_value_pool,for_each_value_balance_mut, newironwood_value_balance_mut), which would otherwise panic when a generated Ironwood bundle drains the empty ironwood chain value pool.orchard::Flagsstrategy,partial_chain_strategyironwood tree), and thezebra-chainchangelog.Tests
arbitrary_transaction_versions_cover_each_era: a non-vacuity guard asserting each network-upgrade era generates exactly its valid transaction versions, and that NU6.3+ generation produces both v6 Orchard and Ironwood bundles. Mutation-checked: it fails against the previous dispatch arm.transaction_roundtrip_nu6_3:any::<Transaction>()uses a mainnet-tip ledger state and NU6.3 is testnet-only, so the existing round-trip proptest never exercised v6 (including the fail-closed librustzcash round-trip in the v6 deserializer).arbitrary_transaction_version_strategyto versions 5 and 6 (its1..5range also silently excluded v5).zebra-chain261/261,zebra-state+zebra-consensus267/267 (these now exercise the previously-dormant ironwood history-tree path inpartial_chain_strategy),cargo fmt --all -- --checkandcargo clippy --workspace --all-targets -- -D warningsclean.Specifications & References
Follow-up Work
fake_v6_transactioninjection workaround and generate v6 natively once this ships in a zebra-chain release.AI Disclosure
Claude Code (Fable 5) was used for the investigation, implementation, tests, and this PR description, under review and direction of the author.