Skip to content

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

Description

@yperbasis

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).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions