fix(zebrad): handle EvictionList re-insertion gracefully instead of asserting - #11227
Open
natalieesk wants to merge 2 commits into
Open
fix(zebrad): handle EvictionList re-insertion gracefully instead of asserting#11227natalieesk wants to merge 2 commits into
natalieesk wants to merge 2 commits into
Conversation
…nstead of asserting `EvictionList::insert` asserted that the inserted key was never already present. That invariant holds only through a subtle combination of prune-before-insert, insertion-ordered expiry, and the `contains_key` re-entry gate — none of which the comment mentioned — so a future caller or refactor could turn it into a node panic. Replace the `assert_eq!` with graceful handling: the timestamp is refreshed in place and a new `ordered_entries` slot is appended only for a genuinely new key, keeping the two backing collections consistent. Correct the doc comment to state the real invariant. Adds unit tests covering re-insertion of a live key (no panic, collections stay consistent) and re-insertion followed by evictions past `max_size` (exercises `pop_front` over the re-inserted key). Updates the existing `eviction_list_refresh` prop test, which asserted the old panic behavior, to assert the new graceful path.
And two more auto-invalidated findings. Analyzed one file, diff |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #10690.
zebrad/src/components/mempool/storage/eviction_list.rs:EvictionList::insertno longerassert_eq!s that the key is absent; a still-present key refreshes its timestamp in place and only a genuinely new key appends anordered_entriesslot. The doc comment now states the real invariant (the old# Panicsnote is gone).eviction_list.rs, and the existingeviction_list_refreshprop test (storage/tests/prop.rs) is converted from#[should_panic]to asserting the graceful path.Approach & Key Decisions
The assert enforced "key never already present," but that only held via three undocumented properties acting together:
prune_oldruns before the insert, entries expire front-to-back in insertion order, and a live entry keeps the transaction rejected viacontains_keyso it can't re-enter the mempool to be re-evicted. A new caller, a reordered prune, or a re-inserted still-rejected key would turn it into a node panic.The graceful path guards the one invariant that actually matters for this structure — that
unique_entriesandordered_entrieshave identical membership.push_backruns only whenold_value.is_none()(key absent fromunique_entries, therefore absent fromordered_entries), so no phantom duplicate is ever pushed andpop_front'sassert!(removed.is_some())cannot be tripped. The refresh deliberately does not move the key to the back ofordered_entries: on this not-expected path that can briefly leaveordered_entriesout of timestamp order, whose only effect is that entries behind a refreshed key are pruned no earlier than the refreshed key — harmless, and cheaper than an O(n) reorder for a path the mempool doesn't reach. The comment at eviction_list.rs:58 documents this. Theassert!/panic!inpop_front/prune_oldare left as-is: they guard the membership invariant that this change preserves, not attacker/caller input.Testing & Verification
reinsert_live_key_is_graceful: inserts the same key twice while live; asserts no panic,contains_key, andunique_entries.len() == ordered_entries.len() == 1.reinsert_then_fill_past_max_size_does_not_panic: re-inserts a live key then evicts pastmax_size, sopop_frontruns over the re-inserted key; asserts the two collections stay equal-length.insertpanics on the oldassert_eq!) and pass after.cargo fmtandcargo clippy -p zebrad --lib --all-featuresare clean.Risk & Impact
Low, and confined to the mempool eviction list. No consensus, network, RPC, state-format, or config surface changes; no DB format bump. The only behavior change is that a re-insert of an already-present key updates the timestamp instead of panicking — a path not reached through the normal mempool flow today.
Changelog
No changelog entry: the assert is not currently triggerable through the mempool, so there is no operator-visible behavior change (latent-robustness fix). Per the changelog guidelines, changes with no operator-visible effect are excluded from the zebrad changelog.
AI Disclosure
Claude (Claude Code) wrote the fix and the tests.
PR Checklist
CHANGELOG.mdintentionally not updated (not operator-visible; rationale above)