rpc, execution, db: FcuBackgroundCommit groundwork, coherent cache fixes - #21293
rpc, execution, db: FcuBackgroundCommit groundwork, coherent cache fixes#21293yperbasis wants to merge 51 commits into
Conversation
Routes head-sensitive RPC reads (ReadCurrentHeader, ReadHeadHeaderHash, GetLatestBlockNumber) through the SharedDomains overlay via existing Filters.WithOverlay/WithTemporalOverlay, so the FCU response can return before the MDBX commit lands without RPC consumers seeing stale chain heads. Coherent cache for remote rpcdaemon already receives pre-commit StateChanges and serves the matching state-version root. See #21008 for the motivating regression (~+80ms p50 FCU latency + multi-second burst tails on main vs release/3.4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Enables FcuBackgroundCommit by default and updates JSON-RPC handlers to read head-sensitive data through the SharedDomains overlay so RPC responses can reflect the new FCU head before the MDBX commit/fsync lands.
Changes:
- Flip
ethconfig.Defaults.FcuBackgroundCommittotrueand update the rationale comment to reflect current overlay + notification/coherent-cache wiring. - Wrap a set of head-sensitive
rawdb.Read*/rpchelper.Get{Latest,Safe,Finalized}BlockNumbercall sites withfilters.WithOverlay/WithTemporalOverlay. - Minor call-site refactors (introducing
overlayTxlocals) to reuse overlay-backed transactions.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| rpc/jsonrpc/txpool_api.go | Read current header via overlay for txpool content endpoints. |
| rpc/jsonrpc/trace_filtering.go | Use overlay-backed head hash/number when ToBlock is omitted. |
| rpc/jsonrpc/parity_api.go | Read current block number via overlay for parity storage-key listing. |
| rpc/jsonrpc/overlay_api.go | Use overlay-backed latest block number when capping ranges. |
| rpc/jsonrpc/graphql_api.go | Resolve “latest” block number via overlay for GraphQL API. |
| rpc/jsonrpc/eth_txs.go | Read current header via overlay for pending-tx responses. |
| rpc/jsonrpc/eth_system.go | Use temporal overlay for gas oracle backend and overlay for head header reads (base fee/blob base fee). |
| rpc/jsonrpc/eth_simulation.go | Compare requested block to overlay-backed latest head to avoid “future block” false positives. |
| rpc/jsonrpc/eth_receipts.go | Use overlay-backed latest block number in max-uint32 capping path. |
| rpc/jsonrpc/eth_call.go | Use overlay-backed latest block number when building proof/witness preconditions. |
| rpc/jsonrpc/eth_block.go | Use overlay-backed latest block number for transaction-count-by-number guard. |
| rpc/jsonrpc/erigon_receipts.go | Use overlay-backed latest block number for log range defaults. |
| rpc/jsonrpc/erigon_block.go | Read current header via overlay for timestamp-based block lookup. |
| rpc/jsonrpc/debug_execution_witness.go | Use overlay-backed latest block number when building expected post-state. |
| rpc/jsonrpc/debug_api.go | Use overlay-backed latest block number for debug_setHead baseline. |
| rpc/jsonrpc/bor_api_impl.go | Use overlay-backed latest header/number in several Bor RPCs. |
| node/ethconfig/config.go | Default FcuBackgroundCommit to true and expand explanatory comment. |
Comments suppressed due to low confidence (4)
rpc/jsonrpc/parity_api.go:80
bnis read through the overlay, but subsequent reads (_txNumReader.Minandtx.RangeAsOf) still use the original temporal tx. During a background-commit window this can leaveparity_listStorageKeysquerying state/txnums from the committed DB while targeting the overlay head number, producing stale or inconsistent results. Consider wrapping the temporal tx once (e.g., viafilters.WithTemporalOverlay) and using that wrapped tx consistently for the latest-state reader, txnum lookup, and theRangeAsOfscan.
bn := rawdb.ReadCurrentBlockNumber(api.filters.WithOverlay(tx))
minTxNum, err := api._txNumReader.Min(ctx, tx, *bn)
if err != nil {
return nil, err
}
rpc/jsonrpc/trace_filtering.go:351
toBlockis now derived from an overlay-backed head hash/number, but the rest of the method still passes the originaldbtxintofilterV3, which computes txnum bounds and scans indexes against the committed DB view. This means the requested range can reference an overlay head while the underlying trace scan is still anchored to the pre-commit state (and the block-range limit check uses the overlay height). Consider using a temporal overlay tx (filters.WithTemporalOverlay(dbtx)) and passing that through tofilterV3(and any txnum/index reads) so the head number and the scanned data come from the same view.
if req.ToBlock == nil {
overlayTx := api.filters.WithOverlay(dbtx)
headNumber, err := api._blockReader.HeaderNumber(ctx, overlayTx, rawdb.ReadHeadHeaderHash(overlayTx))
if err != nil {
return err
}
toBlock = *headNumber
rpc/jsonrpc/bor_api_impl.go:111
latestBlockNumis resolved using an overlay-backed tx, but the subsequentHeaderByNumbercall still uses the originaltx. During background commit this can cause the latest header lookup to miss the overlay head (returningerrUnknownBlockeven though the overlay has the header). Consider reusing the same overlay-wrapped tx for theHeaderByNumber/HeaderByHashreads in the “latest” path.
//nolint:nestif
if blockNrOrHash == nil {
latestBlockNum, err2 := rpchelper.GetLatestBlockNumber(api.filters.WithOverlay(tx))
if err2 != nil {
return accounts.NilAddress, err2
}
header, err = api._blockReader.HeaderByNumber(ctx, tx, latestBlockNum)
} else {
rpc/jsonrpc/eth_block.go:383
- This method now uses an overlay-backed tx to compute
latestBlockNumber, but it still reads the block body/tx count through the originaltxlater in the function. In a background-commit window,blockNumcan be the overlay head while_blockReader.Bodyon the committed tx returns nil, so the RPC may still returnnullforlatest. Consider using the same overlay-wrapped view for the subsequent body read when serving head-sensitive queries.
latestBlockNumber, err := rpchelper.GetLatestBlockNumber(api.filters.WithOverlay(tx))
if err != nil {
return nil, err
}
if blockNum > latestBlockNumber {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The skip dated to PR #20195 when bg-commit had a documented "FCU N+1 reads stale state from DB" race. The actual race is in the tester helper, not the FCU: insertPoSBlocks waits on pre-commit state-change events from the gRPC stream, then InsertChain opens an roTx and reads ReadHeader/HeadBlockHash before the bg goroutine has committed. Fix in InsertChain itself by calling ExecModule.WaitIdle (acquires then releases the FCU semaphore — the bg goroutine releases it only after commit). With foreground commit the semaphore is already free, so WaitIdle is an instant no-op. FCU sequencing was never the issue: forkchoice.go:158's TryAcquire + retryBusy-on-Busy serialises consecutive FCUs against the prior bg goroutine. The test passes under -race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…BlockHash When erigon runs in import mode under FcuBackgroundCommit=true, the post-UpdateForkChoice write at the end of import_cmd.go::InsertChain races the bg-commit goroutine: it can land WriteHeadBlockHash(lvh) in MDBX before the bg goroutine flushes the overlay containing Headers / BlockHash entries for the same block. Result on next startup: ReadHeadBlockHash returns the hash, HeaderNumber(hash) returns nil, BlockReader.CurrentBlock dereferences the nil and panics (freezeblocks/block_reader.go:1409). This made hive ethereum/rpc-compat fail at runner startup of the second erigon process. The existing wait on the state-change stream only ensures the dispatcher fired (events are dispatched pre-commit from the overlay), not that MDBX has flushed. Add ExecutionModule().WaitIdle() before the Update — WaitIdle acquires/releases the FCU semaphore, blocking until the bg goroutine completes its flush+commit. No-op when bg-commit is off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Manually dispatched the three CI workflows that were skipped because the PR is in draft state:
Why I want them: this PR flips |
Copilot review caught a class of bugs in the FcuBackgroundCommit migrations where the head lookup was overlay-wrapped but the dependent read on the same tx was not. During the ~50ms bg-commit window the head returns N+1 (from the SD overlay) while MDBX is still at N, so the dependent read references a head the rest of its view can't see. - bor_api_impl.go (GetAuthor): pass the same overlayTx to _blockReader.HeaderByNumber. Previously latestBlockNum was N+1 but HeaderByNumber read MDBX for N+1's canonical hash → nil → errUnknownBlock. - eth_block.go (GetBlockTransactionCountByNumber): pass overlayTx to _blockReader.Body. Without this, blockNum can be N+1 while Body returns nil from committed MDBX, dropping the RPC to (nil, nil). - parity_api.go (parity_listStorageKeys): revert to plain tx. There is no good overlay-aware version: the block overlay exposes table writes (TxNums included) but not the SD domain mem batch, so a RangeAsOf over kv.StorageDomain would still bypass the pending storage writes for an overlay-derived block number, yielding an inconsistent view. - trace_filtering.go: revert the toBlock-via-overlay change. filterV3 scans receipts/logs against dbtx; routing only the toBlock through the overlay leaves the upper bound past what the scan can see. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweep of the remaining migrated callsites for the same head-vs-data inconsistency Copilot flagged in the first review pass: the head lookup was routed through the SD overlay but the downstream reads (log scans, witness/proof builds, setHead, blockWithSenders, binary search) still operate on the plain committed tx, so during the bg-commit window the function references a head its own follow-up reads cannot see. Reverted to plain tx (consistent committed view) in: - eth_call.go (GetProof / CreateAccessList witness paths) - overlay_api.go and erigon_receipts.go and eth_receipts.go (log range upper bounds — getLogsV3 scans against plain tx; eth_getLogs additionally guards on GetLatestExecutedBlockNumber(tx) which would otherwise fire "node is still syncing" for an overlay-derived upper bound) - eth_simulation.go (guard followed by blockWithSenders on plain tx) - debug_api.go (debug_setHead guard; SetHead acts on committed DB) - debug_execution_witness.go (latestBlock gates a branch that reads txnums + seeks commitment on plain tx) - erigon_block.go (GetBlockByTimestamp binary search uses HeaderByNumber on plain tx) Kept overlay-aware where the function uses the result in-memory and does no dependent DB read, or where the dependent read was extended in the previous commit (bor_api_impl.go GetAuthor, eth_block.go GetBlockTransactionCountByNumber): eth_system.go BlockNumber/GasPrice/BaseFee/BlobBaseFee (in-memory) and the GasPriceOracleBackend tx (wrapped once at construction so all b.tx reads are overlay-aware), bor_api_impl.go header-only paths, txpool_api.go Content/ContentFrom, eth_txs.go pool fallback, graphql_api.go GetLatestBlockNumber, trace_filtering.go was already reverted in the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Re-dispatched the three skipped workflows against HEAD
|
Pre-existing latent panic: rawdb.ReadCurrentBlockNumber returns *uint64 which is nil when HeadHeaderHash isn't set (fresh DB / pre-head state), and the *bn dereference on the next line would crash the RPC handler. Surface a user-facing error instead. Flagged by Copilot review of the nearby diff in 79ce7d9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eth_getLogs, trace_filter.Filter, and overlay_api.getBeginEnd previously resolved the implicit `latest` reference through nil filters (plain tx, returns N-1 during the bg-commit window) but routed user-passed ToBlock/FromBlock="latest" through api.filters (overlay-aware, returns N). The resulting `end > latest` guard would false-positive errBlockRangeIntoFuture for ~50ms after every FCU. Pass nil filters consistently in the explicit-tag resolution so the upper bound, the lower bound, and the underlying getLogsV3/filterV3 scan all agree on the committed view. Pre-existing in foreground-commit mode too, but only observable during the brief commit window when the CL hadn't yet received the FCU response; becomes user-facing with FcuBackgroundCommit=true defaulting on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ByTimestamp and debug_setHead Two callsites previously reverted to plain tx in 79ce7d9 only touch block tables (no SD-temporal reads), so overlay-aware reads are fully consistent with their dependent reads. - erigon_block.GetBlockByTimestamp: wrap tx once at the top; route ReadCurrentHeader, the inner _blockReader.HeaderByNumber binary-search probe, and the three buildBlockResponse callsites through overlayTx. - debug_setHead: read currentHead through the overlay so debug_setHead(N) during the bg-commit window doesn't false-positive "block N is in the future" for what is effectively a no-op rollback. The SD-temporal reverts (eth_call/getProof, log scans, simulation, commitment seek, parity_listStorageKeys) still need the SD-aware temporal view tracked in #21314. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mmit semantics and known limitation Document the trade-offs of routing head reads through the SD block overlay during the bg-commit window: - node/ethconfig/config.go: expand the FcuBackgroundCommit doc block to cover the FCU semaphore sequencing, embedded vs. remote rpcdaemon semantics, and the eth_call(latest) header-vs-state lag (~50ms). Link to #21314 for the proper SD-aware view follow-up. - rpc/jsonrpc/eth_call.go: note that BlockOverlayTemporalTx wraps table reads but delegates temporal methods to roTx; link to #21314. - rpc/jsonrpc/eth_simulation.go: comment was wrong — blockWithSenders auto-wraps. The real reason latest stays on plain tx is NewSharedDomains ties the simulator to the plain tx's domain state. - rpc/jsonrpc/eth_system.go: explain why GasPriceOracleBackend.Fork opens a non-overlay-wrapped tx (downstream helpers re-wrap internally). - execution/execmodule/exec_module_test.go: replace stale "subsequent blocks may fail validation" comment on TestNotificationDispatchBackgroundCommit — the semaphore serializes FCUs so N+1 always reads N's committed state. - cmd/utils/flags.go + docs (configuring-erigon.mdx, llms-full.txt): update --fcu.background.commit usage and default; flag a concise rpcdaemon caveat in the description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ay safe-close invariant and assert memStore backing RPC readers acquire views via Filters.WithOverlay / WithTemporalOverlay / SharedDomains.BlockOverlayTemporalTx that share memTx with the published SD's BlockOverlay. The FCU bg-commit goroutine closes that SD while those readers are still iterating, and the only thing keeping the pattern safe today is that the BlockOverlay is backed by membatchwithdb.NewMemoryBatch — a pure-Go memStore whose Rollback/Close are no-ops on the in-memory data (memory_store.go), and that views hold their own caller-supplied backing tx. Both properties were load-bearing but undocumented; flagged in the FcuBackgroundCommit=true review as a latent risk. Make the invariant explicit: - MemoryMutation.Rollback (memory_mutation.go): comment explaining the no-op semantics for memStore-backed batches and the consequence for concurrent read views. - MemoryMutation.NewReadView (memory_mutation.go): concurrency note pointing to newReadViewMut. - MemoryMutation.newReadViewMut (memory_mutation.go): runtime panic if m.memTx is not *memStore. Catches the case where someone swaps NewMemoryBatch for NewMemoryBatchMDBX (which DOES invalidate cursors on Rollback) and forgets that the safe-concurrent-close property vanishes. The panic message points at the required follow-up (refcount/drain) for any MDBX-backed overlay with concurrent readers. - SharedDomains.Close (domain_shared.go): comment walking through each of sd.mem / sd.blockOverlay / sd.sdCtx and why concurrent RPC views remain safe under each close path. - Filters.WithOverlay / WithTemporalOverlay (filters.go): public-API documentation of the safe-concurrent-close property and the standard caller-tx-lifecycle constraint that still applies. No behaviour change for the supported (memStore-backed) path; the panic fires only if an unsupported backing store is wired in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NewMemoryBatch is the only constructor left, so the read-view safety invariant newReadViewMut asserted at runtime is now compile-time: type the field *memStore and drop the panic.
Reverts a202073. Resolving a canonical block and supporting its state boundary in eth_getProof are separate concerns; this implementation does not support proofs at block zero.
rpc-daemon.md still described the removed NewSimple fallback ("fall back
to a non-versioned last-block cache"); use the config.go wording and
regenerate llms-full.txt via generate-llms.py.
At a zero budget the eviction loop drains every insert straight back out, so Get/GetCode took the global lock twice and churned the btree and eviction list per read-through, and OnNewBlock fed-then-evicted every batch entry. Skip the lookup and the add when the budget is zero; reads fall through to the caller's tx snapshot as before. Zero budget is the standalone rpcdaemon default until #22269 raises it. Behavior-preserving: TestZeroBudgetRetainsNothing pins the contract (nothing retained, reads resolve on the tx snapshot rather than announced batch data) and was confirmed green before the change too.
resolveLogsRange only needs the block number, but BlockByHash decodes the full body and recovers senders on every eth_getLogs/overlay_getLogs call with a blockHash filter. A header without a body (sync window, pruned bodies) now resolves and hits the clearer downstream guards (latest-executed check, checkReceiptsAvailable) instead of a generic "block not found".
Resolves a semantic conflict with #22467, which deleted SharedDomains.DetachBranchCache on main: drop the getProof call site. Isolation from concurrent commits now comes from the shared branch cache's bound gating (servableUnderBound) instead of detaching; TestGetProofPinsReadSnapshot pins that the proof still resolves on the caller's RO snapshot.
Tags resolve against the view tx exposes — nil filters does not force the committed view; wrap-once callers pass an overlay tx with nil filters. filters only controls the internal overlay wrap and whether "pending" may resolve via LastPendingBlock.
|
Splitting this PR into independently-mergeable pieces, all based on current main (none stacked), kept as drafts:
The union of the four reproduces this PR's diff exactly; only |
Moved from #21293: with the FCU response returning pre-commit, the version-keyed Coherent cache is what keeps a standalone daemon's reads consistent with its committed view instead of serving pre-commit state-change data against an older head. --state.cache=0 remains the no-retention escape hatch (snapshot-consistent, nothing cached).
|
Closing in favor of the split (map in the comment above): #22532 (coherent version-keyed state cache), #22533 (RPC overlay/committed view split), #22534 (membatchwithdb safe-close invariant), #22535 (bg-commit consumers + flag docs) — with #22269 (default flip) and #22278 (SimpleCache rename) re-stacked onto #22532. Together they reproduce this branch's diff exactly. The "Interplay with #21414" checklist from this description now lives as a comment on #21414, mapped to the split PRs. The branch stays in place for reference. |
Summary
Groundwork for enabling
FcuBackgroundCommit— the default flip and the standalone cache-retention increase from0MBto128MBare in the stacked #22269; defaults are unchanged here. With the flag on, the FCU response returns to the consensus client before the MDBX flush+commit lands: the commit runs in a background goroutine, and the ExecModule semaphore serializes successive FCUs, so FCU N+1 always reads FCU N's committed state. This PR makes RPC reads, the version-keyed state cache, and post-FCU consumers coherent under that mode. Several of the fixes matter already today, because state-change notifications are dispatched pre-commit.RPC: head-sensitive reads resolve on a well-defined view
They see the pre-commit head everywhere or nowhere — never a mix.
Overlay view — the head and every dependent block-table read are served from the published
BlockOverlay: borgetSnapshot/getAuthor/getSigners/getSnapshotProposer{,Sequence}/latest-block,eth_getBlockTransactionCountBy{Number,Hash}, graphql latest-block,debug_setHead,debug_getRawHeader,eth_getTransactionByHash,txpool_content{,From}, anderigon_getBlockByTimestamp. Behavior change on Polygon: for borgetSnapshot/getSigners/getSnapshotProposer{,Sequence}, nil/latestnow resolves to the overlay-aware forkchoice/executed head instead of the header-stage tip (ReadCurrentHeader), so a catching-up node answers for its executed position rather than the downloaded-header tip — consistent witheth_blockNumber;getAuthoradditionally fixes explicit-tag resolution (negative tags were cast touint64and returned "unknown block").BaseAPI.headerByHashis overlay-aware, covering every by-hash consumer that stays on block tables. Each request pins one overlay read view up front and reuses it for all dependent reads, so an overlay unpublished mid-request cannot drop the request onto the older MDBX snapshot.Committed view — the dependent reads use SD-temporal data, which the overlay does not expose, so tags resolve with
nilfilters and the bounds agree with the data:eth_getLogs/overlay_*range resolution (includingeth_getLogsblock-hash filters),trace_filter,eth_getProof,eth_simulateV1,debug_traceBlockBy*,debug_storageRangeAt,debug_accountRange,debug_accountAt(by-hash included — an overlay-resolved head would have no committed history),eth_getWitness. The filters-param contract is documented onrpchelper.GetBlockNumber.eth_getProofadditionally keeps header lookup, commitment reconstruction, and state reads on the caller's single RO snapshot; shared branch-cache reads are bound-gated (servableUnderBound, #22467), so a concurrent commit cannot mix snapshots. Nil guards coverparity_listStorageKeys,trace_filter, and theeth_getProofheader lookup.Compatibility: on payload-building nodes,
"pending"resolves to the latest executed block in every committed-view method that accepts the tag (eth_getLogs/overlay_*,trace_filter,debug_traceBlockByNumber,eth_simulateV1,eth_getProof,eth_getWitness). This keeps tag resolution aligned with the committed data those methods read.debug_accountRangekeeps its explicit pending rejection.Known embedded-daemon limitation: generic latest-state calls (
eth_call,eth_getBalance,eth_getStorageAt,eth_getCode) resolve the overlay head while their temporal state reads stay on the committed snapshot — head N with state N-1 for the commit duration. The SD-aware temporal view needed to close this is tracked in #21314.Coherent state cache: roots keyed by the version readers observe
db/kv/kvcache,execution/execmodule/notification_dispatcher.go. The pre-commit dispatch announces the post-commitPlainStateVersion, so version-keyed roots match exactly the readers that should see them: post-commit transactions hit the new canonical root, pre-commit transactions keep the previous one. Batch-fed storage and code entries use the key shapes readers look up (address+location; code by address). A view that outlivesKeepViewsversion advances falls back to its own tx snapshot instead of erroringtoo old ViewID; reads through non-latest views are not cached (they bypass eviction accounting and would grow without bound). Retained roots share the configured memory budgets: state and code entries in older roots are cleared on version advance, and views pinned to them fall back to their own snapshots. Canonical roots start from their own batch, with no carry-over: the state-change producers don't announce every mutation, so a carried entry could stay stale — at a deliberate hit-rate cost: every version starts cold except the batch's changed keys, so hot-but-unchanged keys miss once per key per block, and steady-state hit rates are much lower than with carry-over. Completing the producers and restoring carry-over (which recovers the warmth) is tracked in #22276. The standalone daemon uses this coherent cache at every configured budget, in remote and--datadirmodes alike. Positive budgets retain entries;0MBretains none and disables new-block waiting, so reads fall back to the caller's tx snapshot while remaining coherent. The default remains0MBuntil #22269 raises it to128MB.Engine: busy-tolerant batch execution
A background FCU commit briefly holds the exec semaphore, so
ValidateChain/UpdateForkChoicecan routinely returnBusy.execDownloadedBatchwaits it out viaretryBusy: a ctx-aware 50 ms poll with a periodic debug log so a stuck commit surfaces.Overlay safe-close invariant
db/kv/membatchwithdb. Shared-tx read views are safe only on the pure-GomemStorebacking, whoseRollback/Closeare no-ops on the data — that is what lets the bg-commit goroutine close the published overlay while RPC readers still iterate views. ThememTxfield's type restricts overlays to that backing.Post-FCU readers wait for the commit
State-change events are dispatched pre-commit, so the notification stream alone does not guarantee MDBX contains the head.
import_cmdand the execmodule tester callExecModule.WaitIdlebefore opening a fresh tx afterUpdateForkChoice.Tests
TestStateChangeVersionMatchesCommittedpins announce-vs-committed version parity across {fg, bg} × {1-by-1, batched} (the batched case crosses the initial-cycle threshold, covering mid-FCU version bumps). kvcache tests pin fresh roots, retained-root aggregate budgets, reader-shaped feed keys, the evicted-view fallback, and non-latest no-cache.TestZeroBudgetRemoteCachePinsCommittedStatepins snapshot-consistent reads with entry retention disabled. Overlay tests pin view selection inTestGetBlockTransactionCountByHash_SeesOverlayHead,TestDebugAccountAt_OverlayHeadHash_CommittedView, andTestGetLogsBlockHashUsesCommittedView, plus view lifetime under concurrent unpublish (the three*_PinsOverlayViewtests).TestGetProofPinsReadSnapshotpins all proof reads to one RO snapshot;TestGetProofMissingHeaderpins a clean error for a missing header.TestNotificationDispatchBackgroundCommitcovers notification dispatch under background commit; multi-block bg-commit coverage lives inTestReorgBackAndForwardIntoCanonicalChain(bg mode) andTestInsertBlocksWithBatchedFCU_BadBlockRecovery_Background.Safety (with the flag enabled)
FCU sequencing. The bg goroutine releases the exec semaphore only after the commit completes and
PublishOverlay(nil).AssembleBlock/ValidateChainshare the semaphore, soengine_newPayload/engine_getPayloadserialize behind a pending commit — ~commit-duration added latency; aSYNCINGresponse in a pipelined burst is spec-valid and self-heals.Embedded rpcdaemon. Overlay-aware paths read FCU N's block-table writes pre-commit. In-flight readers keep their pinned view across unpublish; new readers cascade to MDBX, which by then contains N. No stale window for block-table paths; latest-state calls carry the #21314 limitation above.
Standalone rpcdaemon (remote or with datadir). With a positive entry-retention budget, cache entries are keyed by
PlainStateVersion, bumped inside the commit batch and announced at the post-commit value — a daemon tx opened during the commit window resolves the N-1 root, so pre-commit batch data is never served against the older head. At0MB, no entries are retained and each view falls back to its own tx snapshot, preserving the same consistency without serving batch data. Incomplete producer batches can still leave a retained entry stale for one version (#22276).Interplay with #21414 (FCU semaphore decouple)
#21414 releases the exec semaphore as soon as
updateForkChoicereturns and moves the commit to a FIFO background worker, chaining FCU N+1's in-memory state onto FCU N's in-flight commit generation. Whichever PR lands second must re-validate the premises stated above in semaphore terms:TestStateChangeVersionMatchesCommittedand the cache's pre-commit-window reasoning are the regression net.WaitIdlecurrently implies the commit has landed;import_cmdand the execmodule tester rely on that.PublishOverlay(nil)happening only after the commit lands; the worker must preserve that ordering.BusywindowretryBusywaits out mostly disappears (the helper stays correct, just rarely loops).Flag gating composes: #21414 inherits
--fcu.background.commit(default false); the flip stays with #22269.