erigontech / erigontech/erigon
CodeStore: TblCodeCache size counter drifts when a transaction rolls back after Evict
- Dominant language
- Go
- Stars
- 3.6k
- Forks
- 1.5k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 465
Description
Found while reviewing #23033's code-store eviction wiring (codex review, point 3). The eviction mechanics themselves are fine; the size accounting is not rollback-safe.
## Mechanism
`CodeStore.Evict` enforces the `TblCodeCache` byte cap using an in-memory counter (`tableSizeBytes`), seeded from the table once per process and decremented as `DeleteCurrent` walks. The decrements happen inside the caller's transaction, but the counter is not transactional: if the enclosing tx rolls back after `Evict` ran, MDBX restores the deleted rows while the counter keeps the decrements.
Result: the counter under-counts, so future evictions fire only when the true size exceeds `cap + drift` — the cap becomes slack, not unenforced. One failure costs roughly one eviction's worth (~10% of cap). It compounds only under repeated evict-then-rollback cycles (e.g. a prune failing every FCU), which is pathological but possible.
## Where it triggers
- **Pre-existing on `main`**: the forkchoice prune path — `Evict` runs inside `agg.CollateAndPrune`'s callback; an error there rolls back the tx after the deletes.
- #23033 adds the same trigger class on the frozen-block catchup prune path.
A related accepted-transient, for completeness: catchup's in-loop eviction runs at the start of each cycle, so the final batch's code overshoots the cap until the first forkchoice prune evicts it — minutes of bounded overshoot, no action needed.
## Fix direction
Not reseed-on-rollback call sites (a "remember to call X on every error path" contract is the forgettable-wiring pattern #23033 spends several commits eliminating). The proper fix makes drift impossible by construction: **store the table size transactionally** — a metadata row in `TblCodeCache` (or a sibling table) read and written inside the eviction's own transaction, so the size commits and rolls back together with the deletes. The once-per-process seed scan then disappears too.
The write direction needs nothing: a rolled-back `PutByHash` leaves the counter over-counting, which over-evicts — the safe direction.
Contributor guide
Assessment
This issue has not been assessed yet.