Skip to content

Backport upstream #563: Detect reorgs that occur while the server is down#4

Merged
ValarDragon merged 1 commit into
masterfrom
backport/upstream-563-restart-reorg
Jun 11, 2026
Merged

Backport upstream #563: Detect reorgs that occur while the server is down#4
ValarDragon merged 1 commit into
masterfrom
backport/upstream-563-restart-reorg

Conversation

@ValarDragon

Copy link
Copy Markdown

Backports zcash#563 (single commit, cherry-picked clean).

Review

The bug is real and the fix is correct. Verified directly against the code:

  • NewBlockCache rebuilds the block index from disk but never sets latestHash, leaving it at the zero value hash32.Nil (common/cache.go).
  • HashMatch returns true unconditionally while latestHash == hash32.Nil, so the first block ingested after every restart skips the prev-hash continuity check against the cache tip.
  • Consequence: a reorg that happens while lightwalletd is down is silently papered over — the new chain gets appended on top of orphaned blocks, which stay in the append-only cache permanently, and the served block stream breaks prev-hash continuity at the join point.

The fix calls the existing setLatestHash() helper (previously only invoked from Reorg) at the end of NewBlockCache. Edge cases check out:

  • Empty cache → latestHash stays Nil → vacuous accept of the very first block, same as before (correct).
  • Corrupt tip block on disk → setLatestHash already routes through recoverFromCorruption.
  • Locking comment on setLatestHash is satisfied: NewBlockCache is documented single-threaded.
  • Side benefit confirmed: previously a caught-up server would do a spurious REORG: dropping block <tip> and re-fetch on every restart (Nil never matches getbestblockhash); now it syncs immediately.

Test verification: the new regression test fails on master (latestHash not initialized after restart) and passes with the fix; full go test ./... passes on this branch.

Interaction with #3 (parallel ingest backport): no file overlap, and the parallel ingestor uses the same HashMatch gate, so this fix protects both ingest paths.

🤖 Generated with Claude Code

NewBlockCache did not initialize latestHash from the last block in the
disk cache, so HashMatch vacuously accepted the first block ingested
after every restart. If the backing node reorged across a lightwalletd
restart, the new chain was appended on top of the orphaned blocks,
which then remained in the compact-block cache permanently, serving
wallets an internally inconsistent block stream.

Initialize latestHash in NewBlockCache (using the existing
setLatestHash helper, previously only called from Reorg) so the
ingestor walks back orphaned blocks on the first ingest after a
restart, the same way it does for reorgs detected while running.

This also avoids the unnecessary drop and re-fetch of the cache tip on
every restart when no reorg occurred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ValarDragon ValarDragon merged commit 67ddac6 into master Jun 11, 2026
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.

2 participants