Repository navigation
Conversation
StoreBlock clears the SQL response cache only after its commit, so a "not found" cached before the commit can be served, for the moment between commit and reset, for a block that is already stored. Block validation sends a child whose parent reads as missing to catchup, so even that short window is not worth the cache. Only a found result is cached now; a not-found lookup is one indexed query.
|
🤖 Claude Code Review Status: Complete Current Review: The change is small and correct. Non-blocking tradeoff: every lookup of an unknown hash (e.g. peer announcements of new blocks) now queries the DB instead of hitting the cache. The query is a single indexed lookup on |
|
Benchmark Comparison ReportBaseline: Current: Summary
All benchmark results (sec/op)
Threshold: >10% with p < 0.05 | Generated: 2026-10-26 14:45 UTC |
freemans13
left a comment
There was a problem hiding this comment.
Approved. No actionable findings at 118e316.
Removing negative caching closes the window between a block's commit and the response-cache reset. Positive caching retains its generation checks, and the blockchain clients and block-validation callers preserve the same boolean/error contract.
Validation:
go test -race -tags testtxmetacache -count=2 ./stores/blockchain/...passed.- Ran
TestSQLGetBlockExists_NeverCachesNotFoundagainst the pre-fix implementation through a Go overlay. It failed on the cached-false assertion in both repetitions, confirming that the test detects the removed behavior.
Repeated lookups of the same absent hash now perform an indexed database query. This is the intended tradeoff; I did not benchmark its cost.



Problem
SQL.GetBlockExistscached a not-found result in the response cache.StoreBlockresets that cache only after its commit, so a "not found" cached before the commit can still be served, in the window between commit and reset, for a block that is already stored. The generation counter stops aSet(false)that races the reset, but not a read of afalsecached earlier.Block validation decides from
GetBlockExists(parent)whether a child goes to catchup (queueFetchedBlock), and a needless catchup is expensive: on the scaling env on 2026-10-26 one meant moving to CATCHINGBLOCKS and re-downloading a 274 GB block. That incident was a different race (#1997 covers it, #2053 limits the damage). This closes the store-side window for the same decision.Change
Only a found result is cached. A not-found lookup is one indexed query on
blocks.hash.BlockValidation.blockExistsCachealready caches only positives, so both layers now agree.Cost
Every caller looks up a block per block or per announcement (block validation parent checks, catchup entry checks, legacy netsync). The asset handler only reaches it from the admin invalidate/revalidate routes. Lookups of distinct unknown hashes already missed the cache. Only repeated lookups of the same absent hash now go to the database.
Tests
TestSQLGetBlockExists_NeverCachesNotFound: a not-found result leaves no cache entry, and a found result is still cached. It fails with the oldSet(false).go test -race ./stores/blockchain/...passes, andmake lintreports 0 issues.Follow-ups (not in this PR)
GetBlockHeaderscaches an empty result for an unknown start hash, inchainWalkCache(10 min TTL), which a plain extend does not clear.GetBlockHeaderIDsalready guards against this.LocateBlockHeadersdiscards theGetBlockExistsbool and always takeslocator[0].