Skip to content

fix(node): keep slot_index canonical across side-branch imports and reorgs - #1202

Open
adust09 wants to merge 1 commit into
leanEthereum:mainfrom
adust09:fix/slot-index-canonical-20260815
Open

fix(node): keep slot_index canonical across side-branch imports and reorgs#1202
adust09 wants to merge 1 commit into
leanEthereum:mainfrom
adust09:fix/slot-index-canonical-20260815

Conversation

@adust09

@adust09 adust09 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1201.

get_block_root_by_slot documents slot_index as returning the canonical block root at a slot, but _persist_block wrote put_block_root_by_slot(block.slot, block_root) for every imported block. Because the table is keyed by slot INTEGER PRIMARY KEY with INSERT OR REPLACE, a side-branch import silently overwrote the canonical entry at its slot, and nothing repaired the index when the head moved.

Changes

  • SyncService now tracks the head root the database last committed (_persisted_head_root, seeded from get_head_root() at wiring time so a restarted node continues from the persisted chain). Inside the existing persistence batch, _reindex_canonical_slots diffs the old and new head branches down to their fork point: slots on the new canonical branch are (re)written, slots only the old branch filled are deleted, and a block that did not move the head no longer touches the index. The tracker advances only after the batch commits, so a rollback leaves it consistent with disk.
  • Database / SQLiteDatabase: new delete_block_root_by_slot(slot) for the vacated-slot case (a reorg onto a branch with an empty slot).
  • Testing doubles: MockForkchoiceStore gains an advance_head_on_block knob to simulate side-branch imports; RecordingSyncDatabase gains get_head_root / delete_block_root_by_slot.

A block that does not move the head can never be a head ancestor — on_block only accepts blocks whose parent is already in the store, so every head ancestor is present before import. Skipping the index write for such blocks is therefore always safe (no canonical entry is missed).

Tests

  • Side-branch import leaves the slot index untouched while still persisting the head pointer.
  • A reorg rewrites the differing slots and deletes entries for slots the new branch leaves empty (and never deletes a refilled slot).
  • delete_block_root_by_slot removes only the targeted slot; deleting an absent slot is a no-op.
  • Full suite: 2948 passed; just check clean.

…eorgs

The storage interface documents get_block_root_by_slot as returning the
canonical block root at a slot, but _persist_block wrote the index for
every imported block: a side-branch import silently overwrote the
canonical entry at its slot, and nothing repaired the index when the
head moved.

Track the head the database last committed and diff it against the
store's head inside the persistence batch: slots on the new canonical
branch are (re)written, slots only the old branch filled are deleted,
and a block that did not move the head no longer touches the index.

Closes leanEthereum#1201
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.

slot_index is documented as canonical but maintained as last-writer-wins per slot

1 participant