erigontech / erigontech/erigon

Code and account domain writes should be atomic at the IBS/writer layer

Open
#22,103 2 comments 0 reactions 0 assignees View on GitHub
ErigonDB
Dominant language
Go
Stars
3.6k
Forks
1.5k
Avg merge
1d 16h
Merged PRs (30d)
455

Description

## Code and account domain writes should be atomic at the IBS / writer layer

### Summary

`CodeDomain` and `AccountsDomain` are written as two **independent, sequentially-applied** `DomainPut`s for the same address (e.g. `execution/state/rw_v3.go` writes the account record, then the code). Because they are not atomic, there is a transient window — between the account write and the code write — where the account record already carries the new `codeHash` but `CodeDomain` does not yet hold the code for that address. That window is the **root cause** of a class of cache-on/parallel hazards, not just the individual symptoms patched in #21386.

### Why it bites

The account-codeHash → code resolution (used by the now-removed L2b `GetLatest` bypass, and still by the read-only `GetCodeSize`/`GetCode` fast paths) is content-addressed and shared across every address with the same code. In the window above, resolving an address's code via its (already-advanced) account `codeHash` returns the *new* code while `CodeDomain` still holds the old/empty value.

- **prevVal poisoning (fixed in #21386 by removing the bypass):** code writes pass `prevVal=nil`, so `DomainPut` resolves prevVal via `GetLatest(CodeDomain, addr)`. With the bypass, a later EIP-7702 authority delegating to an already-seen delegate read the *about-to-be-written* designator as its "previous" value → `bytes.Equal(prevVal, v)` dropped the write → wrong trie root / gas-used mismatch. (Full bisection: @sudeepdino008 in #21386, and #21675.)
- **read-path coupling (contained in #21386):** `GetCodeSize`/`GetCode` only avoid this because they are pure getters that never feed a prevVal and the EVM only ever reads applied/consistent state — a constraint that has to be documented and preserved by hand.

### The durable fix

If the IBS/writer layer never put code independently of its account — wrote `(account, code)` as one unit, supplying the code's prevVal (which the writer knows) so `DomainPut` never resolves it from a possibly-advanced account — then:

- the account-ahead-of-code window does not exist;
- `GetLatest`'s internal prevVal read cannot time-travel — the lost-write is structurally impossible;
- the account→codeHash shortcut becomes safe on **every** path (reads, `EXTCODESIZE`, *and* prevVal), so the dedup fast path no longer needs the "read-only only" caveat and the removed L2b bypass could even return.

This generalizes @sudeepdino008's "writers pass explicit prevVal for CodeDomain puts" (option 3) into an invariant: **code is a dependent of its account and is never written independently.**

### Scope

Writer-side change in `execution/state` (the `rw_v3.go` apply paths and the parallel-apply path), coupling the account + code `DomainPut`s and threading the prior code as prevVal. Not a blocker for #21386 (whose contained fixes are correct and sufficient), but the principled root fix.

### Refs
- #21386 — StateCache LRU + (txNum,epoch); removed the L2b bypass, documented the read-only contract.
- #21675 — gas-used mismatch surfaced while testing #21386.

Contributor guide

Open the contributing guide

Research direction

Start in execution/state/rw_v3.go and trace the account and code apply paths, including the parallel-apply path and their DomainPut calls. Verify how the writer obtains the prior code value; done means account and code are coupled at the IBS/writer layer, code receives its explicit prevVal, and no independent code write leaves an account-ahead-of-code window.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.