Skip to content

[codex] isolate Ironwood history tree changes#208

Draft
ValarDragon wants to merge 1 commit into
ironwood-mainfrom
isolate-ironwood-history-tree
Draft

[codex] isolate Ironwood history tree changes#208
ValarDragon wants to merge 1 commit into
ironwood-mainfrom
isolate-ironwood-history-tree

Conversation

@ValarDragon

Copy link
Copy Markdown

Motivation

Isolate the consensus-critical ZIP-221/MMR changes for Ironwood from the larger Ironwood integration branch so reviewers can inspect the history-tree behavior independently.

Solution

  • Add chain-level Ironwood transaction/type accessors required by history nodes.
  • Add zcash_history::V3 history tree support from NU6.3 onward.
  • Thread the Ironwood note commitment root through history tree construction and updates.
  • Add minimal in-memory Ironwood note tree tracking needed for MMR root computation.
  • Leave finalized-state Ironwood root persistence as explicit TODO markers where this isolated branch still uses the empty/default root.

Validation

  • cargo fmt --all -- --check
  • cargo test -p zebra-chain history_tree --locked
  • cargo test -p zebra-state history_tree --locked
  • cargo test -p zebra-chain --locked --no-run
  • cargo test -p zebra-state --locked --no-run
  • cargo test -p zebra-consensus --locked --no-run
  • git diff --check

Notes

This is intentionally draft. It is not meant to be merged as-is until the TODOs for finalized-state Ironwood roots are handled or explicitly scoped for a follow-up.

AI Disclosure

Used Codex to isolate the history-tree diff, wire compile-required call sites, run local validation, and prepare this PR description.

@v12-auditor

v12-auditor Bot commented Jun 22, 2026

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 found two issues worth reviewing.

Open the full results here.

FindingSeverityDetails
F-91013 🟠 High
Ironwood pool spends bypass double-spend tracking

The new NU6.3 ironwood shielded pool shares Orchard's action and nullifier shape, but zebra-state only integrated its note-commitment tree, not its nullifier set. ChainInner declares sprout_nullifiers, sapling_nullifiers, and orchard_nullifiers, and the per-transaction update dispatch calls add_to_non_finalized_chain_unique for those three pools only. No ironwood_nullifiers map exists, the Spend match in revealed_spend has no Ironwood arm, and the finalized DB nullifier batch/contains-checks likewise omit Ironwood. The data is available (Transaction::ironwood_nullifiers() / Block::ironwood_nullifiers() were added) but never consumed by the uniqueness checks. Unlike the related tree-wiring gaps, this omission has no TODO marker.

F-91017 🟡 Medium
Ironwood tree state is rebuilt from empty or wrong starting points

The codebase does not treat the Ironwood note-commitment tree as persistent cumulative state, even though its root is fed into ZIP-221 chain-history computations. Multiple paths substitute Default::default() for ironwood, including note_commitment_trees_for_tip(), Chain::new seeding, and Treestate construction, so post-restart or newly forked chains rebuild from an empty tree rather than the finalized tip's accumulated state. The rollback path has the same underlying flaw: rebuild_history_tree_from_upgrade_activation starts from an empty Ironwood tree at the current upgrade activation height and replays only later blocks, even though Ironwood remains cumulative from NU6.3 onward. In all of these cases, the resulting ironwood_root is then pushed into the history tree as if it were correct. The implementation work to fix these behaviors is the same: persist and reload the cumulative Ironwood tree/root in state, and use that canonical value whenever resuming, seeding, or rebuilding history.

And one more auto-invalidated finding.

Analyzed 24 files, diff db4082a...db68e11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant