Skip to content

fix(zakura): only advance and gossip NewBlocks that land on the best chain#500

Merged
evan-forbes merged 18 commits into
ironwood-mainfrom
zakura/newblock-best-chain-gate-ironwood
Jul 7, 2026
Merged

fix(zakura): only advance and gossip NewBlocks that land on the best chain#500
evan-forbes merged 18 commits into
ironwood-mainfrom
zakura/newblock-best-chain-gate-ironwood

Conversation

@p0mvn

@p0mvn p0mvn commented Jul 7, 2026

Copy link
Copy Markdown

Motivation

Original motivation (see #478 for the live incident detail): an inbound Zakura NewBlock accepted by the block pipeline can land outside the best chain (contextually valid, lower work). The driver still advanced the header/verified frontiers and forwarded the block to peers without checking which chain it landed on — observed live as the whole fleet advertising and re-forwarding a min-difficulty losing branch over Zakura while each node's own chain stayed honest, stranding the zakura-only node that trusted the gossip.

Solution

After the verifier accepts a NewBlock, the driver checks the committed hash against the best chain with ReadRequest::Depth (Some only for best-chain blocks, so it also covers a reorg the block just won):

  • Best chain → unchanged: NewBlockAccepted advances the frontiers and forwards to eligible peers.

  • Non-best chainHeaderSyncEvent::NewBlockAcceptedNonBestChain: dedup-only — no frontier advance, no height-covered marking, no forwarding. Counted in sync.header.tip.new_block.non_best_chain.

  • Read failures are conservatively treated as non-best-chain: the node's own frontier still advances through the chain-tip mirror, so a false negative only skips one gossip forward.

p0mvn added 10 commits July 6, 2026 16:35
Close the three write-path corruption bugs proven by the Phase-1
header-store coherence suite (PR #490):

1. prepare_header_range_batch_with_roots now rejects ranges whose first
   header does not link to the anchor or whose headers are not
   internally contiguous (new CommitHeaderRangeError::UnlinkedRange,
   classified as a local non-scoring failure in zebrad).
2. The range insert loop skips heights that already have a committed
   block, so re-deliveries can no longer strand provisional zakura rows
   below the body tip.
3. prepare_zakura_header_from_committed_block refuses seeds that do not
   link to the stored row below them as silent no-ops; header-range
   sync converges the store instead (scenario s11).

Flip the four *_upholds_invariants twins to permanent regression gates,
delete the corruption_repro_* tests, remove the discovery masking, and
un-ignore the random invariant sweep (clean at PROPTEST_CASES=4096).
Pillar 2 of the REORG_PLAN: a corrupted header store can no longer poison
difficulty validation — readers surface the storage fault explicitly.

- recent_header_context verifies, at every step of its walk, that the
  stored header is the block its hash row names (HeaderHashMismatch),
  that it links to the row below (BrokenLinkage), and that no row is
  missing below a stored one (Gap), returning the new StoreIncoherentError
  instead of a poisoned or silently shortened difficulty window.
- The range writer's anchor round-trip distinguishes a bijection
  violation in our own indexes (StoreIncoherent::BijectionMismatch) from
  a genuinely unknown anchor (UnknownAnchor).
- CommitHeaderRangeError::StoreIncoherent is classified as a local,
  non-peer-scoring commit failure in zebrad: the range was rejected
  because our store cannot supply trustworthy context, not because the
  peer's range was shown invalid.

New reads.rs suite in header_store_coherence hand-corrupts the column
families and pins the reader/writer behavior for all four fault shapes,
including side-effect freedom of the rejections.
…chain

An accepted NewBlock could have committed to a side chain; advancing the
header/verified frontiers and forwarding it made the whole Zakura layer
follow and propagate losing branches (e.g. testnet min-difficulty forks)
while each node's own chain stayed honest, stranding zakura-only peers.
The driver now checks the committed hash against the best chain
(ReadRequest::Depth) and routes side-chain accepts to a dedup-only event.
@v12-auditor

v12-auditor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 did not find any issues that need review.

Open the full results here.

Analyzed five files, diff 4459656...d81a284.

@p0mvn
p0mvn marked this pull request as ready for review July 7, 2026 06:09
# Conflicts:
#	CHANGELOG.md
#	zebra-state/src/service/finalized_state/zebra_db/block/tests/header_store_coherence/README.md
#	zebrad/src/commands/start.rs
#	zebrad/src/commands/start/zakura/header_sync_driver.rs

@evan-forbes evan-forbes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!! makes a lot sense to do what block prop does here and not gossip

side node: I've never liked having the "drivers" for headersync or blocksync just due keeping important logic or failures in different spots. would love cleaning separation there. naively seems like we could do so with better api's to state/storage

we might be able to have a header_chain api for best_tip etc like we do for blocks as well

@evan-forbes
evan-forbes changed the base branch from zakura-linkage-verified-reads to ironwood-main July 7, 2026 11:27
@evan-forbes
evan-forbes force-pushed the zakura/newblock-best-chain-gate-ironwood branch from a24b630 to c31fc4f Compare July 7, 2026 11:33
@evan-forbes
evan-forbes merged commit 9228c61 into ironwood-main Jul 7, 2026
51 checks passed
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