Skip to content

fix(beacon-node): protect just-inserted entry from pruneToMaxSize eviction - #9490

Closed
lodekeeper wants to merge 1 commit into
ChainSafe:nflaig/fix-payload-envelope-cache-oomfrom
lodekeeper:fix/payload-envelope-prune-exclude-root
Closed

fix(beacon-node): protect just-inserted entry from pruneToMaxSize eviction#9490
lodekeeper wants to merge 1 commit into
ChainSafe:nflaig/fix-payload-envelope-cache-oomfrom
lodekeeper:fix/payload-envelope-prune-exclude-root

Conversation

@lodekeeper

Copy link
Copy Markdown
Contributor

Summary

Addresses Gemini's two-part review on #9489 (correctness + perf) by:

  • Passing props.blockRootHex as excludeRoot from each add / addFromBid call to pruneToMaxSize.
  • Filtering excludeRoot out of the eviction-candidate set inside pruneToMaxSize.
  • Adding an O(N) fast path for the common itemsToDelete === 1 case (per-insertion call), keeping the sort-based path for multi-item evictions.

Why

pruneToMaxSize() fires from inside add() / addFromBid() and evicts the lowest-slot entry when over cap. If the just-inserted entry has a lower slot than every existing entry (out-of-order range-sync response, reorg-recovery insert), the prune evicts the entry the caller just added — defeating the insertion and surfacing as a Missing PayloadEnvelopeInput throw downstream at downloadByRange.ts:230.

Same correctness gap Codex flagged in discussion_r3380314898 for the single-add case. Gemini's review (discussion_r3380335185) also flagged the O(N log N) sort cost on every insertion when only one eviction is needed.

Scope notes

This protects the single-add case (gossip from a non-canonical fork that happens to be at a slot lower than everything in the cache, or a reorg-driven re-insert). The multi-add batch-seed scenario Codex described in discussion_r3380314898cacheByRangeResponses() seeding 32 slots back-to-back with later iterations evicting earlier same-batch entries — is not addressed here; that requires either a skipPrune flag plumbed through batch seeding or a local seed-map fallback in cacheByRangeResponses. Happy to follow up with that if you want it in scope here too.

Test plan

  • CI: Type Checks (24) passes.
  • CI: Unit Tests — the new test pruneToMaxSize does not evict the just-inserted entry even if it has the lowest slot fills the cache with high-slot entries (range-sync look-ahead state), inserts a low-slot entry, and asserts the new entry survives while the previously-lowest existing entry is evicted.
  • CI: existing pruneToMaxSize bounds the cache on insertion, evicting the lowest-slot entries test still passes.

🤖 Generated with Claude Code

…ction

When `pruneToMaxSize()` fires from inside `add()` / `addFromBid()` and the
cache is at cap, the lowest-slot entry is evicted. If the just-inserted
entry has a lower slot than every existing entry (out-of-order range-sync
response, reorg-recovery insert, etc.), the prune evicts the entry the
caller just added — defeating the insertion.

Pass `props.blockRootHex` as `excludeRoot` so the just-inserted entry is
filtered out of eviction candidates. Adds a fast O(N) min-finding path for
the per-insertion `itemsToDelete === 1` case to avoid an O(N log N) sort
of the whole cache on the hot path; the multi-item path keeps the existing
sort-based approach.

Includes a regression test that fills the cache with high-slot entries and
inserts a lower-slot entry to verify the new entry survives and the
previously-lowest entry is evicted instead.

🤖 Generated with AI assistance

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the pruneToMaxSize method in SeenPayloadEnvelopeInput to accept an optional excludeRoot parameter. This prevents a newly inserted entry from being immediately evicted during a cache prune if its slot is lower than existing entries (e.g., during reorgs or out-of-order syncs). Additionally, an O(N) fast-path optimization is introduced for single-item evictions to avoid sorting the entire cache. A corresponding unit test has been added to verify this behavior. I have no further feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@lodekeeper

Copy link
Copy Markdown
Contributor Author

Closing — superseded by nflaig's commit e5274f2 on #9489 which incorporates the same excludeRoot approach (simpler while + single-pass min-find impl vs the dual-path one here) and the regression test. Approved #9489 at pullrequestreview-4458684843.

@lodekeeper lodekeeper closed this Jun 9, 2026
@ensi321 ensi321 mentioned this pull request Jul 22, 2026
32 tasks
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