db/snapshotsync, rpc/jsonrpc: three review follow-ups from #23322 - #23812
Merged
Conversation
FrozenBlocks ran its refresh on the calling goroutine and never stamped a failed one, so against an unresponsive backend every caller paid the full timeout with a read transaction open. The refresh now runs on a goroutine of its own, a caller with a value to serve returns without waiting, and a failed attempt counts, so an unreachable backend costs one attempt per TTL. Caching a failure is only safe once callers can tell a default from an observation: FrozenBlocksObserved reports the count together with whether the backend ever answered. PostStateCalculated and the eth_simulateV1 commitment path read it, so an unobserved zero no longer stands for "no snapshots" and no longer sends pre-Byzantium blocks down re-execution. The bookkeeping the getter and the pre-merge probe each kept by hand moves to common/concurrent.CachedValue: one TTL, one dedup, one rule for what a failed pass leaves behind. Each site keeps its own waiting policy, because the probe reads through the caller's transaction and cannot refresh behind it.
lupin012
marked this pull request as ready for review
September 5, 2026 19:49
lupin012
requested review from
AskAlexSharov,
mh0lt,
sudeepdino008 and
yperbasis
as code owners
September 5, 2026 19:50
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The shared cache preserves caller-owned transaction lifetimes, deduplicates refreshes, handles failures safely, and has focused concurrency coverage.
Pull request overview
This PR unifies TTL-cached, deduplicated value production and distinguishes an unobserved remote frozen-block count from a confirmed zero.
Changes:
- Adds generic
concurrent.CachedValue[T]with synchronous and background refresh modes. - Migrates frozen-block fetching and pre-merge snapshot probing to the shared cache.
- Updates receipt and simulation paths plus tests to use observation-aware frozen-block counts.
File summaries
| File | Description |
|---|---|
common/concurrent/cached_value.go |
Adds the shared TTL cache and producer deduplication. |
common/concurrent/cached_value_test.go |
Tests caching, failures, cancellation, deduplication, and panic handling. |
db/dbservices/interfaces.go |
Extends block readers with observation-aware frozen counts. |
db/snapshotsync/freezeblocks/block_reader.go |
Migrates local and remote readers to the new cache behavior. |
db/snapshotsync/freezeblocks/block_reader_test.go |
Updates and expands remote-reader concurrency tests. |
execution/blockreplay/memreader.go |
Implements the new reader method. |
cmd/rpcdaemon/rpcservices/eth_backend.go |
Delegates the observation-aware method. |
rpc/jsonrpc/eth_api.go |
Replaces the hand-rolled pre-merge probe cache. |
rpc/jsonrpc/eth_simulation.go |
Avoids treating an unobserved count as zero frozen blocks. |
rpc/jsonrpc/pre_merge_probe_test.go |
Adapts probe tests to CachedValue. |
rpc/jsonrpc/check_prune_gates_test.go |
Adapts TTL-control tests to CachedValue. |
rpc/jsonrpc/receipts/receipts_generator.go |
Uses observation-aware frozen counts for post-state decisions. |
rpc/jsonrpc/receipts/post_state_calculated_test.go |
Covers the unobserved-count sentinel case. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
AskAlexSharov
approved these changes
Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #23322. #23690 closed seven of its deferred review notes and #23759 two more; this closes the three that are about
RemoteBlockReader.FrozenBlocksand the mechanism behind it.block_reader.goblock_reader.goblock_reader.go,eth_api.goChange
FrozenBlocksObserved() (uint64, bool)reports the count together with whether the backend ever answered.receipts.PostStateCalculatedand theeth_simulateV1commitment path both read it; those are the two sentinel readers a remote reader can reach.common/concurrent.CachedValue[T]holds what the getter andholdsPreMergeBlockDataeach kept by hand: one TTL, one dedup, one rule for what a failed pass leaves behind. Each site keeps its own waiting policy —Produceruns on the caller's goroutine,Gorefreshes behind it.For review
singleflightwas tried and dropped.DoChancannot join the pass in flight without possibly starting one of its own, and it runs the producer on a goroutine the caller does not own. That is unsafe for the pre-merge probe, which reads through the caller'skv.Tx: a caller released byctx.Done()rolls that transaction back under a live reader.Produceruns the pass on the caller's goroutine instead.DoChanalso turns a producer panic intogo panic(e), which ends the process rather than the request.An unobserved count now reads as "snapshots exist". For a pre-Byzantium block on a remote rpcdaemon whose backend has not answered yet,
PostStateCalculatedreturns false, so the stored receipt is served — possibly without itsrootfield — where before the block was re-executed and the gate could answerPrunedErrorfor receipts that are on disk. That is the direction the note asks for, and the window is one TTL after a failed round trip.Panics.
Producepublishes the failed pass and re-panics, so the RPC server logs it as before;Gocontains and logs it with the stack, since its goroutine has no caller to recover it.Tests
common/concurrent/cached_value_test.go(11) pins the contract, including that a caller running the pass finishes it before returning while one that only waits is released by its own context.TestRemoteBlockReaderFrozenBlocks*cover the getter,rpc/jsonrpc/receipts/post_state_calculated_test.gothe sentinel. Theeth_simulateV1leg has no dedicated test — the branch sits inside the commitment path.make lintclean;./common/concurrent/...,./db/snapshotsync/freezeblocks/...,./rpc/jsonrpc/...,./execution/blockreplay/...,./cmd/rpcdaemon/...green, with-raceon the concurrency tests.