Skip to content

fix(node): repair committed leaves whose vertex blob was never stored - #647

Open
blacks1ne wants to merge 1 commit into
QuilibriumNetwork:v2.1.0.25from
blacks1ne:blacks1ne/repair-missing-vertex-blobs
Open

blacks1ne wants to merge 1 commit into
QuilibriumNetwork:v2.1.0.25from
blacks1ne:blacks1ne/repair-missing-vertex-blobs

Conversation

@blacks1ne

@blacks1ne blacks1ne commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Pre-atomic sync could commit a forest leaf before storing its blob. Once roots match, normal diff sync never fetches that missing blob.

Check live leaves for missing blobs, fetch them, and verify commitment ‖ size before storing. The repair is bounded and skips sized tombstones.

Base: 4eaf1f79f4eb022b9a9d3ccc072064050e5781b6

The forest tree and the readable blob keyspace are separate stores and only
the tree is diffed by sync. The pre-atomic install committed the tree first
and fetched blobs after, so an error in the fetch loop left a complete tree
with no data behind it. That state is self-perpetuating: the root now matches
the peer's, so the diff carries nothing and the hole never closes — only the
vertices a later frame happens to rewrite recover. QuilibriumNetwork#645 stops new holes from
forming; it cannot heal one that already exists.

Make "every committed leaf has a blob that hashes to its commitment‖size" a
checked invariant.

- `Forest::for_each_shard_phase_leaf` — O(1)-memory, version-exact sweep of a
  phase's live leaves. Writes of one key in the JMT value column are
  contiguous and version-ascending, so the newest value at or below the target
  version is found in one pass.
- `missing_vertex_blobs` — leaves whose stored blob does not recompute to the
  committed leaf value, so a stale or empty blob is a gap and not just an
  absent one. Leaves committing to the empty-blob commitment are skipped: a
  sized tombstone stores no data by construction, and reporting those would
  make the audit permanently dirty.
- `repair_missing_blobs` — fetch each gap, verify it against OUR leaf value,
  install without touching the tree. That is the same binding the sync path
  applies, so the repair channel cannot introduce data unbound to this node's
  authenticated root. Best-effort: it never fails a sync whose tree is already
  correct.
- Bounded. At most 2048 blobs per pass, since the fetch is one sequential RPC
  per gap and runs inline in the sync. A leaf a peer refuses is recorded
  against the exact leaf value it was refused at, so it is not re-requested
  every tick — and it returns to the list if the tree later moves it on.

Measured on a node carrying such a hole: 5124 gaps, 4915 filled in one pass.
Before the repair that node had a synthesized zero-byte prover key in every
app-shard committee and no app-frame materialization at all; after it, no
malformed members and every worker materializing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K67csMrXEr4ovegwy2Fmq
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