erigontech / erigontech/erigon

execution/state, stagedsync: state-change notifications miss mutations — version-keyed caches can serve stale data

Open
#22,276 1 comment 0 reactions 1 assignee Claimed by @yperbasis View on GitHub
ErigonDB
Dominant language
Go
Stars
3.6k
Forks
1.5k
Avg merge
1d 16h
Merged PRs (30d)
455

Description

## Context

kvcache.Coherent becomes the default state cache for the standalone rpcdaemon in #22269 (the `--state.cache` `128MB` default), stacked on #22532 (the kvcache split of #21293) which fixes the announced-vs-committed `PlainStateVersion` parity — so canonical cache roots are actually served to readers for the first time. The state-change producers were never hardened for a consumer that trusts batches as a complete statement of what changed. Four gaps:

## Gaps

1. **Forward account deletions are never announced.** `Writer.DeleteAccount` (`execution/state/rw_v3.go`) deletes from `AccountsDomain` but its `accumulator.DeleteAccount` call is commented out ("basically this will always panic. keeping this out should be fine anyway"). A selfdestructed (EIP-6780 same-tx) or cleared account keeps its old value in any cache fed by the batches.
2. **Incarnation re-creation wipes code and storage without announcing it.** The del-before-create branch of `Writer.UpdateAccountData` does `DomainDel(Code)` + `DomainDelPrefix(Storage)` but only emits `ChangeAccount`.
3. **Unwind never restores code.** `unwindExec3State` (`execution/stagedsync/stage_execute.go`) emits account and storage restores but carries `//TODO: why we don't call accumulator.ChangeCode???`.
4. **Unwind emits spurious deletions across step boundaries.** In-source comment in the same function: values for previous steps "will pass nil values here which will look like a delete" — consumers receive REMOVE actions for accounts that still exist.

## Consequences

With cross-block carry-over in the cache, gaps 1–3 produce **indefinite** staleness (no later batch corrects an untouched key) and gap 4 caches false absence markers. As mitigation, #22532 disables cross-block carry-over in `kvcache.Coherent.advanceRoot` (each canonical root starts from its own batch), bounding any producer gap to one version at the cost of cross-block cache reuse.

## Acceptance

- Fix the four producer sites (each with its own test).
- Add a reorg/deletion round-trip test at the execmodule level: insert → cache serves state → selfdestruct + unwind/re-org → cache serves the restored/deleted state.
- Then re-enable cross-block cloning in `kvcache.Coherent.advanceRoot` (the workaround comment there references this issue) to recover cross-block hit rates.

Nice-to-have while in the area: `kvcache.Element.Size()` counts only `len(K)+len(V)`, so configured cache budgets undercount real RSS (no btree/struct overhead).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.