Context
Follow-up from the #21293 split — the RPC piece is now #22533 (FcuBackgroundCommit groundwork). That PR routes head-sensitive RPC reads through the SharedDomains block overlay so the FCU response can return before the MDBX commit lands, but several callsites were intentionally reverted to plain tx in 79ce7d9 because their dependent reads touch SD-managed temporal data (state domains, history, inverted indexes) that the current overlay doesn't expose.
Current limitation
Filters.WithOverlay(tx) / WithTemporalOverlay(tx) wraps table-level reads (canonical hashes, headers, bodies, TxNums, stage progress, forkchoice markers) via MemoryMutation. But its temporal methods delegate straight to the passed-in temporal tx — see memory_mutation.go:836-889 for the plain view and memory_mutation.go:1041-1064 for OverlayTemporalReadView:
func (v *OverlayTemporalReadView) GetLatest(name kv.Domain, k []byte) ([]byte, kv.Step, error) {
return v.temporalTx.GetLatest(name, k)
}
// ... GetAsOf, RangeAsOf, HistorySeek, HistoryRange, IndexRange all delegate to v.temporalTx
The SD's mem (TemporalMemBatch) holds block N's accounts/storage/code/commitment writes and the log inverted indexes during the ~50ms bg-commit window, but the temporal view above delegates around it. So overlay-aware table reads see N's headers and canonical hashes, while temporal/domain reads return state at PSV=X (pre-commit, i.e. N-1's state).
This forces a head-vs-state choice at each callsite:
- Use overlay for the head lookup, plain for dependent reads → head says N, data is at N-1 → silently wrong (no error to retry on).
- Use plain everywhere → internally consistent at N-1; "latest" lags by one block for ~50ms across calls.
The PR picked option 2 wherever dependent reads need SD-temporal data; option 1 wherever they don't.
Affected callsites (the 79ce7d9 reverts)
Each of these would benefit from a proper SD-aware temporal view:
(debug_api.go SetHead and erigon_block.go GetBlockByTimestamp only touch block tables and were made overlay-consistent in #22533 itself — they are not waiting on this issue.)
Proposed solution
Build a real SD-aware temporal view that chains reads in the natural priority order:
SD.mem (latest in-flight writes for block N)
→ SD.blockOverlay (table-level writes for block N)
→ underlying committed MDBX TemporalTx (state ≤ N-1)
Concretely, replace the current OverlayTemporalReadView temporal-method delegations with logic that:
- Consults the SD's
mem (TemporalMemBatch) first for GetLatest / GetAsOf / RangeAsOf / HistorySeek / HistoryRange / IndexRange.
- Falls through to the underlying
temporalTx for anything not in mem.
- Carries the published SD's reference (via
Filters.LatestSD()) for the lifetime of the view so the read sequence stays coherent even if the overlay is unpublished mid-read.
Key implementation considerations:
- Lifecycle: today
bgSD.Close() rolls back the overlay's memTx. RPC readers holding a view via WithOverlay/WithTemporalOverlay are only protected by PublishOverlay(nil) happening before Close(). The SD-aware view should make this explicit — either refcounting SD.mem or copy-on-acquire of the relevant domain state.
- Concurrency:
SD.mem is accessed concurrently by execution (writers) and RPC (readers). The mem batch's existing internal sync needs to support reader concurrency; verify or add as needed.
- Range semantics:
RangeAsOf over a domain currently scans the underlying file/btree state. The SD-aware version needs to merge SD.mem entries into the iteration in tx-num order. This is non-trivial for streaming iterators.
- Independence from bg-commit timing: today the overlay is published from
dispatchNotificationsFromOverlay and unpublished by the bg goroutine. The SD-aware view should hold a stable snapshot regardless of where in the lifecycle the bg commit currently is.
Acceptance criteria
Related
Context
Follow-up from the #21293 split — the RPC piece is now #22533 (
FcuBackgroundCommitgroundwork). That PR routes head-sensitive RPC reads through theSharedDomainsblock overlay so the FCU response can return before the MDBX commit lands, but several callsites were intentionally reverted to plain tx in 79ce7d9 because their dependent reads touch SD-managed temporal data (state domains, history, inverted indexes) that the current overlay doesn't expose.Current limitation
Filters.WithOverlay(tx)/WithTemporalOverlay(tx)wraps table-level reads (canonical hashes, headers, bodies, TxNums, stage progress, forkchoice markers) viaMemoryMutation. But its temporal methods delegate straight to the passed-in temporal tx — seememory_mutation.go:836-889for the plain view andmemory_mutation.go:1041-1064forOverlayTemporalReadView:The SD's
mem(TemporalMemBatch) holds block N's accounts/storage/code/commitment writes and the log inverted indexes during the ~50ms bg-commit window, but the temporal view above delegates around it. So overlay-aware table reads see N's headers and canonical hashes, while temporal/domain reads return state at PSV=X (pre-commit, i.e. N-1's state).This forces a head-vs-state choice at each callsite:
The PR picked option 2 wherever dependent reads need SD-temporal data; option 1 wherever they don't.
Affected callsites (the 79ce7d9 reverts)
Each of these would benefit from a proper SD-aware temporal view:
rpc/jsonrpc/eth_call.go—getProof/getWitness:domains.SeekCommitment(roTx)reads the commitment domain; N's commitment writes are in SD.mem.rpc/jsonrpc/eth_receipts.go,erigon_receipts.go,overlay_api.go—GetLogs/getBeginEnd:getLogsV3scanskv.LogAddrIdx/kv.LogTopicIdx, both SD-managed inverted indexes.rpc/jsonrpc/eth_simulation.go—SimulateV1:NewSharedDomains(ctx, tx, ...)ties the simulator to the plain tx's state.rpc/jsonrpc/debug_execution_witness.go—buildExpectedPostState: txnum / commitment-seek branch reads through plain tx.rpc/jsonrpc/eth_call.goCallitself — already documented in code: "latest" header resolves to N (overlay) buteth_call(latest)evaluates state at N-1. This is the user-visible "header-vs-state lag".rpc/jsonrpc/parity_api.goListStorageKeys:tx.RangeAsOf(kv.StorageDomain, ...)over the storage domain history; block N's storage writes are in SD.mem.NewLatestStateReader(tx).ReadAccountData(...)at the top is also SD-temporal.(
debug_api.go SetHeadanderigon_block.go GetBlockByTimestamponly touch block tables and were made overlay-consistent in #22533 itself — they are not waiting on this issue.)Proposed solution
Build a real SD-aware temporal view that chains reads in the natural priority order:
Concretely, replace the current
OverlayTemporalReadViewtemporal-method delegations with logic that:mem(TemporalMemBatch) first forGetLatest/GetAsOf/RangeAsOf/HistorySeek/HistoryRange/IndexRange.temporalTxfor anything not in mem.Filters.LatestSD()) for the lifetime of the view so the read sequence stays coherent even if the overlay is unpublished mid-read.Key implementation considerations:
bgSD.Close()rolls back the overlay'smemTx. RPC readers holding a view viaWithOverlay/WithTemporalOverlayare only protected byPublishOverlay(nil)happening beforeClose(). The SD-aware view should make this explicit — either refcountingSD.memor copy-on-acquire of the relevant domain state.SD.memis accessed concurrently by execution (writers) and RPC (readers). The mem batch's existing internal sync needs to support reader concurrency; verify or add as needed.RangeAsOfover a domain currently scans the underlying file/btree state. The SD-aware version needs to merge SD.mem entries into the iteration in tx-num order. This is non-trivial for streaming iterators.dispatchNotificationsFromOverlayand unpublished by the bg goroutine. The SD-aware view should hold a stable snapshot regardless of where in the lifecycle the bg commit currently is.Acceptance criteria
Filters.WithTemporalOverlay(tx)returns a temporal view whoseGetLatest/GetAsOf/RangeAsOf/HistorySeek/HistoryRange/IndexRangereflect the SD's in-flight mem batch for the head FCU during the bg-commit window.eth_call(latest)evaluates against block N's state (not N-1) during the bg-commit window — no more documented "header-vs-state lag".-racewith concurrent FCU + RPC load).MemoryMutationtable-fallback machinery.Related
FcuBackgroundCommitby default)_GetBlockNumberoverlay-aware "latest" resolution — the prerequisite for this work)