fix(api): serve tx receipt as soon as its block is queryable#4913
Draft
raullenchai wants to merge 1 commit into
Draft
fix(api): serve tx receipt as soon as its block is queryable#4913raullenchai wants to merge 1 commit into
raullenchai wants to merge 1 commit into
Conversation
A block is written to the block store (advancing the blockchain tip) before
its actions and receipts are written to the action indexer, and by default
that indexing runs asynchronously (EnableAsyncIndexWrite). This opened a brief
window where a block was already the "latest" block returned by
eth_getBlockByNumber("latest") but its receipts could not yet be resolved by
hash, so eth_getTransactionReceipt returned nil until the indexer caught up.
Cap the API-visible tip (coreService.TipHeight) at the action-indexer height so
a block becomes "latest" only once its actions/receipts are indexed and can be
served. The block and its receipts then appear atomically to API clients. The
indexer's Height() is an in-memory read guarded by the same lock as its writes,
so once it reports height N the action index for block N is committed. This
changes only the API's view of the tip; consensus reads blockchain.TipHeight()
directly and is unaffected.
Closes #4677
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Member
Author
|
Converting to draft — an independent adversarial codex review found the global
Direction for a v2: don't cap the global tip. Instead fix it locally where the bug actually is — have the receipt/action-by-hash lookups fall back to reading from the block's own stored receipts (which are present as soon as the block is queryable) when the action index lags, leaving |
envestcc
approved these changes
Jul 13, 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.



Problem
Closes #4677.
A transaction's receipt was returned as
nilbyeth_getTransactionReceiptfor a brief window right after its block became the latest block (typically during syncing), then became available a few seconds later. The block was queryable viaeth_getBlockByNumber("latest")/eth_getBlockByHashbefore its receipt/action index was ready.Root cause
Inside
blockdao.PutBlock, the block is written to the block store first — which advances the blockchain tip and makes the block queryable — and only afterwards are its actions/receipts written to the action indexer. The block store must be written before the indexers because crash recovery (BlockIndexerChecker) requires the indexers to never be ahead of the block store.By default
EnableAsyncIndexWriteistrue, so the action indexer is driven by a block subscriber (IndexBuilder) on a separate goroutine. This widens the gap: the block is alreadylatestwhile itsactionHash → blockHeightindex (used by bothActionByActionHashandReceiptByActionHash) has not been committed yet, so the receipt cannot be resolved by hash.The receipt data itself is in the block store as soon as the block is queryable; the only missing piece during the window is the action index.
Fix
Cap the API-visible tip (
coreService.TipHeight()) at the action-indexer height:A block therefore becomes
latestonly once its actions/receipts are indexed and can be served, so the block and its receipts appear atomically to API clients.Why it's safe
blockchain.TipHeight()directly and is untouched, as are hardfork/chain-id gating checks (core.bc.TipHeight()).Height()shares the same lock as itsPutBlock, so once it reports heightNthe action index for blockNis committed and queryable.Height()is an in-memoryRLockread of a counting-index size.min(...)is non-decreasing. IfHeight()errors, it falls back to the chain tip (no worse than before).Tests
Added
TestTipHeightcovering: no indexer, indexer behind, indexer caught up, and indexer error fallback. Codex review: CONVERGED, no blocking issues.🤖 Generated with Claude Code