Repository navigation
CSHLD-1419: Fix CapNodeMissing issue on shielded build - #460
Conversation
|
@claude review for logical correctness |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. It changes how the Zcash Orchard commitment-tree cap is saved, restored and repaired after a reorg, and @ BitGo/coinsshield owns that path, so a human look from them would still be worthwhile.
What was reviewed:
materialize_cap/rebuild_cap_node: completed shards become single leaves, bootstrap ommers are kept as-is, the in-progress shard is embedded, and ranges pastleaf_countstay Nil.verify_cap_root: the cap is hashed on its own, truncated atleaf_count, and checked against the latest checkpoint root.extract_statenow returns an error when the latest checkpoint is not at the tip, which is a change to a public function's behavior.sanitize_store_capinrestore_state, and the newrefresh_store_capcall intruncate_to_checkpoint: I checked the reorg path. Truncation leaves the checkpoint at the tip, so the verify step there holds.
Extended reasoning...
The change adds about 900 lines to packages/wasm-privacy-coin/src/zcash/tree.rs, most of them new tests, plus two .gitignore entries. It changes how the Orchard note-commitment tree cap is persisted, restored and repaired after reorgs. That state feeds witness building for shielded spends, so a wrong cap could break spends or quietly persist stale roots. There is no injection, auth or data-exposure surface. I deferred because the change is large and sits on a cryptographic state path that a CODEOWNERS team (coinsshield) owns, and the PR's own test plan still lists an ignored staging-fixture test that should be run before merge.
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
The persisted cap was the shardtree crate's internal cache persisted as data: two bootstrap ommer hashes written once at indexer bootstrap, never updated, because ShardTree::append never writes completed-shard roots into the cap. The witness consumer has no shard store behind the cap, so the first z→z spend on staging TZEC died with CapNodeMissing. extract_state now materializes a complete cap on every save — completed-shard roots as single leaves, bootstrap ommers reused verbatim, the in-progress shard embedded as live structure — and asserts it hashes standalone, the way the witness builder walks it, to the tree's current root. Guard the store cap as well: restore_state sanitizes the materialized cap before installing it (sub-shard structure made reorgs panic inside shardtree's root walk), truncate_to_checkpoint heals the cap after rewinding (stale completed-shard roots would otherwise serve pre-reorg roots to a divergent chain), and verify_cap_root fails precisely when the latest checkpoint is not at the tip. Ticket: CSHLD-1419
9f64886 to
e9917b7
Compare
Summary
Z→z spends on staging TZEC failed with
CapNodeMissingbecause the persisted cap was the shardtree crate's internal cache (two bootstrap ommer hashes, never updated) rather than a complete, consumer-walkable structure. This PR makesextract_statematerialize a full cap on every save — completed-shard roots as single cached leaves, bootstrap ommers reused verbatim, the in-progress shard embedded as live structure — and verifies it hashes standalone, exactly the way the witness builder walks it, against the tree's current root.Linear: CSHLD-1419
Changes
materialize_cap/rebuild_cap_node: build the persisted-state cap — completed shards cached as singleLeafroots, in-progress shard embedded as a live tree copy,Nilbeyond the tip — reusing valid cached entries (bootstrap ommers are the only record of never-retained shard ranges)verify_cap_root: new save-time assertion — the cap must hash standalone, truncated atleaf_count, to the root at the most recent checkpoint; fails with a precise error if the latest checkpoint is not at the tipsanitize_store_cap/restore_state: strip sub-shard structure from the cap before installing it as the store cap (sub-shard structure made reorgs panic inside shardtree'sroot_internalleaf-split branch); completed-shard roots and ommers are kept as valid cacherefresh_store_cap/truncate_to_checkpoint: heal the store cap after a reorg rewind — stale completed-shard roots would otherwise make a divergent chain's saves silently persist pre-reorg roots (the reference root goes through the same poisoned cache)rebuild_cap_node: annotations are dropped on re-derived (straddling) subtrees so stale cached hashes can never be persisted.gitignore: ignore.DS_Storeand*.tgzTest Plan
cargo test --manifest-path packages/wasm-privacy-coin/Cargo.toml --lib— 14 passed, 1 ignored (14/14 relevant)ZEC_STAGING_STATE_JSON=<exported PersistedShardTreeState>) should be run once against the staging indexer export before merge — it replays the exact production failure state through the fixed save path