Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
ec77c91
execution/cache, db/state/execctx: linearize snapshot read fills
yperbasis Jul 14, 2026
f99e10e
execution/cache, db/state: use exclusive snapshot frontiers
yperbasis Jul 15, 2026
421d0d3
execution/cache: partition admission locks by coherence group
yperbasis Jul 15, 2026
dc3ab95
db/state: deduplicate inverted index progress lookup
yperbasis Jul 15, 2026
a4f833d
Merge remote-tracking branch 'origin/main' into test/statecache-delet…
yperbasis Jul 15, 2026
4a38f91
execution/cache: derive cached code and its hash from the same copy i…
yperbasis Jul 15, 2026
cb2cdde
execution/cache: drop derived addr→codeHash on code deletion in Apply
yperbasis Jul 15, 2026
fdf367f
execution/cache: pin account-delete → stale code-fill rejection
yperbasis Jul 15, 2026
c557351
execution/cache: drop stale call-site inventory from PutAddrCodeHash …
yperbasis Jul 15, 2026
436ae1c
db, execution: collapse DomainProgressAndVisibleEnd to DomainVisibleEnd
yperbasis Jul 15, 2026
e976b63
execution/cache: unify admission into a single RWMutex
yperbasis Jul 15, 2026
82ee60d
execution/cache: avoid advancing code frontier on account delete
yperbasis Jul 16, 2026
1ff29c1
exec, execctx: fix negative cache unwind stamp
yperbasis Jul 16, 2026
616b269
Merge remote-tracking branch 'origin/main' into test/statecache-delet…
yperbasis Jul 16, 2026
fd1c8d1
db, execution: align cache frontier terminology
yperbasis Jul 16, 2026
87ddb96
db/kv/temporal: cache domain visible ends
yperbasis Jul 16, 2026
e1eb581
db/kv/temporal: compact visible end cache
yperbasis Jul 16, 2026
4ff164e
execution/cache, exec, execctx: centralize snapshot fills
yperbasis Jul 17, 2026
576b787
execution/cache: remove unreachable commitment guard
yperbasis Jul 17, 2026
7b4efea
db/state: simplify inverted index progress lookup
yperbasis Jul 17, 2026
60eecc1
execution/exec: remove redundant read-ahead temporary
yperbasis Jul 17, 2026
748f6f8
execution/exec: simplify read-ahead frontier check
yperbasis Jul 17, 2026
14d5f7a
execution/cache: inline address code hash fill
yperbasis Jul 17, 2026
d662dcd
execution/cache: reuse resolved cache for fills
yperbasis Jul 17, 2026
9fba079
execution/cache: make code fill modes explicit
yperbasis Jul 17, 2026
04e35ec
execution/exec: centralize read-ahead getter selection
yperbasis Jul 17, 2026
70ffa64
execution/exec: inline read-ahead getter construction
yperbasis Jul 17, 2026
ba58d75
execution/cache: reset applied frontiers directly
yperbasis Jul 17, 2026
33af7de
execution/cache: clamp applied frontiers on unwind
yperbasis Jul 17, 2026
28e36cf
execution/exec: embed read-ahead temporal getter
yperbasis Jul 17, 2026
0f1b26e
execution/exec: drop redundant warmup cache check
yperbasis Jul 17, 2026
57fbc1a
execution/cache: reuse domain delete path
yperbasis Jul 17, 2026
ca3f6ea
execution/exec: remove impossible frontier state
yperbasis Jul 17, 2026
6b784ea
Merge remote-tracking branch 'origin/main' into test/statecache-delet…
yperbasis Jul 17, 2026
bb25293
db/state/execctx: seed addr→codeHash mapping only from snapshot-sourc…
yperbasis Jul 17, 2026
d2ffe0f
db/kv/temporal: re-derive memoized domain frontiers on files-tx reopen
yperbasis Jul 17, 2026
446621d
db/kv/temporal: static-assert the visible-ends bitmask capacity
yperbasis Jul 17, 2026
653f289
db/kv/temporal: race-test the DomainVisibleEnd memo fast path
yperbasis Jul 17, 2026
f98dc13
execution/cache: inline StateCache.Put's sole remaining insert path
yperbasis Jul 17, 2026
6619446
db/state/execctx: run codehash routing tests in short mode
yperbasis Jul 17, 2026
25934ef
db, execution: rename snapshot to read view in fill admission
yperbasis Jul 28, 2026
cc3066b
Merge remote-tracking branch 'origin/main' into test/statecache-delet…
yperbasis Jul 28, 2026
3f7945f
execution/cache, db/kv: clarify fill-admission comments
yperbasis Jul 28, 2026
588b5f7
db/state/execctx: cache writable domain visible ends
yperbasis Jul 28, 2026
f53ee44
db/state/execctx: reset visible-end memo on unwind
yperbasis Jul 28, 2026
4004aaa
db/state/execctx: make writable visible-end memo concurrency-safe
yperbasis Jul 28, 2026
ba5263b
db/state/execctx: drop redundant unwind memo reset
yperbasis Jul 28, 2026
02d2eaa
db/state/execctx: keep visible-end memo coherent across views
yperbasis Jul 28, 2026
dcff899
db/kv/temporal: make memoized visible ends atomic
yperbasis Jul 28, 2026
58db5ab
Merge branch 'main' into test/statecache-delete-rpc-repro
yperbasis Jul 28, 2026
0d4ae69
Merge branch 'main' into test/statecache-delete-rpc-repro
yperbasis Aug 4, 2026
576b086
execution/cache, db/state/execctx, execution/exec: access StateCache …
yperbasis Aug 4, 2026
6ace158
execution/cache: package doc with the StateCache contract
yperbasis Aug 4, 2026
cc96203
execution/execmodule, db/state/execctx: build the RPC CacheView gette…
yperbasis Aug 4, 2026
30f2ac2
execution/cache: STATE_CACHE_FILLS switch for apply-only mode
yperbasis Aug 4, 2026
d34d775
execution/cache: keep the admission frontier across Clear
yperbasis Aug 4, 2026
91ccbe5
execution/cache, execution/execmodule: apply-only switch covers every…
yperbasis Aug 4, 2026
f3d8b0d
db/kv/temporal, db/state/execctx: name the visible-end memo bits
yperbasis Aug 4, 2026
15756de
execution/execmodule: drain read-ahead only before an actual unwind
yperbasis Aug 4, 2026
610e3e6
execution/execmodule: keep the FCU-entry drain, correct its comment
yperbasis Aug 4, 2026
e50f801
execution/cache: gate code fills on the accounts frontier too
yperbasis Aug 4, 2026
706b091
execution/cache: failing test for the unwind fill-readmission window
AskAlexSharov Aug 4, 2026
055812b
db/kv: document DomainProgress as best-effort, DomainVisibleEnd as exact
yperbasis Aug 4, 2026
b059321
Revert "execution/cache: failing test for the unwind fill-readmission…
yperbasis Aug 4, 2026
a3cc38c
execution/cache, execution/execmodule, db/state: thread the getter's …
yperbasis Aug 4, 2026
a0bd162
execution/cache, db/kv/temporal: state the frontier monotonicity inva…
yperbasis Aug 4, 2026
e3279d2
db/state, execution/cache, common/dbg: enforce the no-cache-with-visi…
yperbasis Aug 4, 2026
f8c965f
db/state, execution/cache, common/dbg: narrow and complete the visibi…
yperbasis Aug 4, 2026
d426502
db/state/execctx: correct the Flush cache contract
yperbasis Aug 4, 2026
2b9c831
execution/cache, execution/execmodule, db: comment precision pass
yperbasis Aug 4, 2026
07d03c7
db/state, execution/cache, execution/execmodule, cmd/integration: gua…
yperbasis Aug 4, 2026
e8a1422
db/state: gofmt aggregator.go
yperbasis Aug 4, 2026
4523761
common/dbg: restore setter block formatting
yperbasis Aug 4, 2026
4ab8a36
db/state/execctx, execution/execmodule, cmd/integration: one home for…
yperbasis Aug 4, 2026
bb75019
db/state/execctx: GuardAggregatorForCache mirrors SetStateCache's gate
yperbasis Aug 4, 2026
25c1e91
db/state/execctx, execution/execmodule: fail closed on guard shape mi…
yperbasis Aug 4, 2026
982cc8e
execution/execmodule, node/eth: module owns state-cache construction;…
yperbasis Aug 4, 2026
756cd2b
node/eth, execution/cache: release the exec module's cache on Stop
yperbasis Aug 4, 2026
7af4d0f
execution/cache, db/state: shorten state-cache lock windows; teardown…
yperbasis Aug 5, 2026
87321bb
execution, db/state/execctx: pin budget release on node close; post-c…
yperbasis Aug 5, 2026
a880acc
execution/tests: remove accidentally committed legacy-tests gitlink
yperbasis Aug 5, 2026
6aa368e
execution/engineapi: close the tester on assert failure in the budget…
yperbasis Aug 5, 2026
f3cb104
db/state: frontier never overstates values coverage; guard the histor…
yperbasis Aug 5, 2026
522c7da
execution/cache, db/state, db/kv: state-cache review follow-ups
yperbasis Aug 5, 2026
3adae16
db/state: drop the history-II segment in memory in the lowering test
yperbasis Aug 5, 2026
cf06963
Merge branch 'test/statecache-delete-rpc-repro' into yperbasis/statec…
yperbasis Aug 5, 2026
58ae694
db/state: a dependency-clamped values view has no exact frontier
yperbasis Aug 5, 2026
55bc6e6
db/state: a dependency-clamped values view has no exact frontier
yperbasis Aug 5, 2026
f6b4177
Merge branch 'test/statecache-delete-rpc-repro' into yperbasis/statec…
yperbasis Aug 5, 2026
f256418
db/state/execctx: pin the incoherence the Flush rejection prevents
yperbasis Aug 5, 2026
7ca7ec7
execution/cache, db/state/execctx, cmd/integration: bind the aggregat…
yperbasis Aug 5, 2026
3c98005
execution/execmodule: wire frozen-block startup processing to the sta…
yperbasis Aug 5, 2026
9b60631
db/state/execctx, execution/cache: drop the dead domain field from th…
yperbasis Aug 5, 2026
458807a
Merge branch 'main' into yperbasis/statecache-review-followups
yperbasis Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/integration/commands/stages.go
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,9 @@ func execBlocksBatch(ctx context.Context, db kv.TemporalRwDB, st *stagedsync.Syn
}
defer doms.Close()
doms.SetInMemHistoryReads(false)
stateCache.BindAggregator(db)
doms.SetStateCache(stateCache)
doms.SetCodeStore(codeStore)
execctx.GuardAggregatorForCache(db, stateCache)

s, err := st.StageState(stages.Execution, tx, initialCycle, false)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions db/kv/kv_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,9 @@ type TemporalRwDB interface {
BeginTemporalRw(ctx context.Context) (TemporalRwTx, error)
BeginTemporalRwNosync(ctx context.Context) (TemporalRwTx, error)
UpdateTemporal(ctx context.Context, f func(tx TemporalRwTx) error) error
// Agg returns the DB's state-files aggregator as `any` (the concrete type
// lives above the kv layer); nil when the DB has none.
Agg() any
Comment on lines +661 to +663
}

// ---- non-important utilities
Expand Down
2 changes: 2 additions & 0 deletions db/kv/membatchwithdb/memory_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,8 @@ func (td temporaldb) BeginTemporalRwNosync(ctx context.Context) (kv.TemporalRwTx
return td.memoryMutation, nil
}

func (td temporaldb) Agg() any { return nil }

func (td temporaldb) Debug() kv.TemporalDebugDB {
panic("not implemented")
}
Expand Down
135 changes: 60 additions & 75 deletions db/state/execctx/domain_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (m *domainVisibleEndMemo) load(tx kv.TemporalTx, domain kv.Domain, viewID u
state = 0
m.viewID.Store(viewID)
}
end, ok := tx.Debug().DomainVisibleEnd(domain)
end, ok := debugDomainVisibleEnd(tx, domain)
m.ends[domain].Store(end)
state |= loadedBit
if ok {
Expand All @@ -157,7 +157,17 @@ func (sd *SharedDomains) domainVisibleEnd(tx kv.TemporalTx, domain kv.Domain) (u
if _, ok := tx.(kv.TemporalRwTx); ok {
return sd.visibleEnds.get(tx, domain)
}
return tx.Debug().DomainVisibleEnd(domain)
return debugDomainVisibleEnd(tx, domain)
}

// debugDomainVisibleEnd tolerates txs without a debug backend (MemoryMutation
// over a nil db): no exact frontier means no fills, reads still work.
func debugDomainVisibleEnd(tx kv.TemporalTx, domain kv.Domain) (uint64, bool) {
dbgTx := tx.Debug()
if dbgTx == nil {
return 0, false
}
return dbgTx.DomainVisibleEnd(domain)
}

// sdFrontier adapts one (SharedDomains, tx) pair to cache.Frontier: writable
Expand Down Expand Up @@ -848,33 +858,13 @@ func (sd *SharedDomains) SetStateCache(stateCache *cache.StateCache) {
if !dbg.UseStateCache || stateCache == nil {
return
}
if stateCache.FillsEnabled() && !stateCache.AggregatorBound() {
panic("assert: fill-enabled StateCache wired before BindAggregator — the visibility-lowering guard is not bound")
}
sd.stateCache = stateCache
sd.cacheApplier = stateCache.Applier()
}

// GuardAggregatorForCache forbids visibility lowering on db's aggregator when
// sc is a fill-enabled StateCache: fill admission relies on view frontiers
// never decreasing. This is the one place that binds the invariant — call it
// wherever a fill-enabled cache is wired over a DB. Duck-typed so the storage
// layer need not know the cache type (and vice versa) — but load-bearing, so
// a db that cannot produce its aggregator fails loudly instead of silently
// dropping the guard. A nil or apply-only cache needs no guard.
func GuardAggregatorForCache(db any, sc *cache.StateCache) {
if sc == nil || !sc.FillsEnabled() {
return
}
h, ok := db.(interface{ Agg() any })
if !ok {
panic(fmt.Sprintf("assert: fill-enabled StateCache wired over %T, which cannot produce its aggregator — the visibility-lowering guard would be silently dropped", db))
}
agg := h.Agg()
f, ok := agg.(interface{ ForbidVisibilityLowering() })
if !ok {
panic(fmt.Sprintf("assert: aggregator %T lacks ForbidVisibilityLowering — the visibility-lowering guard would be silently dropped", agg))
}
f.ForbidVisibilityLowering()
}

// SetCodeStore sets the persistent codehash-keyed code cache.
func (sd *SharedDomains) SetCodeStore(codeStore *cache.CodeStore) {
sd.codeStore = codeStore
Expand Down Expand Up @@ -979,22 +969,14 @@ func (sd *SharedDomains) Close() {
// admission.

// Flush writes the in-memory batch into tx without committing. It deliberately
// does NOT touch the caches: plain Flush leaves the commit to the caller (who
// may still roll back), so it must not warm a cache with state that could be
// rolled back. Cache entries are populated elsewhere — by Commit after a
// successful commit, and by reads (GetLatest) — each stamped with a
// conservative upper-bound txNum. It is that txNum stamp, not population
// timing, that keeps the cache correct: an unwind lowers the floor so every
// entry reflecting a now-dead fork is evicted, and mem-first masking means a
// later in-memory write shadows a stale cached read.
//
// An SD with an attached state cache must route every flush through Commit:
// Flush neither applies nor invalidates, so a populated cache would keep
// serving pre-flush values for the flushed keys after the caller's own
// commit — and Commit collects its cache updates only from its own flush, so
// an earlier plain Flush's keys would never be applied. Cache-less callers
// may Flush and commit themselves.
// does not touch the caches — the caller may still roll back. An SD with a
// state cache must route every flush through Commit: a plain Flush would
// leave the cache serving pre-flush values for the flushed keys forever, so
// it is rejected here.
func (sd *SharedDomains) Flush(ctx context.Context, tx kv.RwTx) error {
if sd.stateCache != nil {
return errors.New("SharedDomains with a state cache must flush through Commit")
}
defer mxFlushTook.ObserveDuration(time.Now())
return sd.flushMem(ctx, tx)
}
Expand All @@ -1016,12 +998,11 @@ func (sd *SharedDomains) flushMem(ctx context.Context, tx kv.RwTx, opts ...kv.Fl
return sd.mem.Flush(ctx, tx, opts...)
}

type cacheUpdate struct {
domain kv.Domain
key []byte
val []byte
step kv.Step
txN uint64
type branchUpdate struct {
key []byte
val []byte
step kv.Step
txN uint64
}

// Commit flushes the in-memory batch into tx, commits tx, and only then applies
Expand Down Expand Up @@ -1066,24 +1047,31 @@ func (sd *SharedDomains) Commit(ctx context.Context, tx kv.RwTx, validate ...fun
// no cache apply ever runs ahead of durable MDBX state. (Reads through
// this SD between flush and a failed commit can still fill flushed
// values; a failed commit is fatal, so they die with the process.)
var pending []cacheUpdate
stash := func(domain kv.Domain) kv.FlushOption {
var pendingBranch []branchUpdate
var pendingState []cache.Update
stashState := func(domain kv.Domain) kv.FlushOption {
return kv.WithFlushCallback(domain, func(k []byte, v []byte, step kv.Step, txNum uint64) {
pending = append(pending, cacheUpdate{
domain: domain,
key: append([]byte(nil), k...),
val: append([]byte(nil), v...),
step: step,
txN: txNum,
pendingState = append(pendingState, cache.Update{
Domain: domain,
Key: append([]byte(nil), k...),
Val: append([]byte(nil), v...),
TxNum: txNum,
})
})
}
var opts []kv.FlushOption
if sd.branchCache != nil {
opts = append(opts, stash(kv.CommitmentDomain))
opts = append(opts, kv.WithFlushCallback(kv.CommitmentDomain, func(k []byte, v []byte, step kv.Step, txNum uint64) {
pendingBranch = append(pendingBranch, branchUpdate{
key: append([]byte(nil), k...),
val: append([]byte(nil), v...),
step: step,
txN: txNum,
})
}))
}
if sd.stateCache != nil {
opts = append(opts, stash(kv.AccountsDomain), stash(kv.StorageDomain))
opts = append(opts, stashState(kv.AccountsDomain), stashState(kv.StorageDomain))
}
// CodeDomain flush stashes state-cache updates and collects code for the
// persistent store. The code-store MDBX write is deferred to after flushMem —
Expand All @@ -1096,12 +1084,11 @@ func (sd *SharedDomains) Commit(ctx context.Context, tx kv.RwTx, validate ...fun
codeStoreWrites = append(codeStoreWrites, [2][]byte{crypto.Keccak256(v), append([]byte(nil), v...)})
}
if sd.stateCache != nil {
pending = append(pending, cacheUpdate{
domain: kv.CodeDomain,
key: append([]byte(nil), k...),
val: append([]byte(nil), v...),
step: step,
txN: txNum,
pendingState = append(pendingState, cache.Update{
Domain: kv.CodeDomain,
Key: append([]byte(nil), k...),
Val: append([]byte(nil), v...),
TxNum: txNum,
})
}
}))
Expand Down Expand Up @@ -1172,18 +1159,15 @@ func (sd *SharedDomains) Commit(ctx context.Context, tx kv.RwTx, validate ...fun
if err := tx.Commit(); err != nil {
return err
}
for i := range pending {
u := &pending[i]
if u.domain == kv.CommitmentDomain {
if len(u.val) == 0 {
sd.branchCache.Invalidate(u.key)
} else {
sd.branchCache.Put(u.key, u.val, uint64(u.step), u.txN)
}
continue
for i := range pendingBranch {
u := &pendingBranch[i]
if len(u.val) == 0 {
sd.branchCache.Invalidate(u.key)
} else {
sd.branchCache.Put(u.key, u.val, uint64(u.step), u.txN)
}
sd.cacheApplier.Apply(u.domain, u.key, u.val, u.txN)
}
sd.cacheApplier.ApplyAll(pendingState)
return nil
}

Expand Down Expand Up @@ -1345,8 +1329,9 @@ func (sd *SharedDomains) getLatestMetered(domain kv.Domain, tx kv.TemporalTx, k
}

// View freshness is rechecked while the fill is serialized against
// committed cache updates.
if sd.stateCache != nil && sd.stateCache.Caches(domain) {
// committed cache updates. Apply-only mode skips the block: binding a
// frontier for a fill that will no-op is a wasted allocation.
if sd.stateCache != nil && sd.stateCache.FillsEnabled() && sd.stateCache.Caches(domain) {
readTxNum := (uint64(step)+1)*sd.StepSize() - 1
fillView := view
if !fillView.CanFill() {
Expand Down Expand Up @@ -1541,7 +1526,7 @@ func (sd *SharedDomains) codeHashForAddr(tx kv.TemporalTx, view cache.ReadView,
}

h, fromReadView := resolve()
if fromReadView && sd.stateCache != nil {
if fromReadView && sd.stateCache != nil && sd.stateCache.FillsEnabled() {
var fixed [32]byte
if len(h) == 32 {
copy(fixed[:], h)
Expand Down
Loading
Loading