[Bug] eth_getBlockReceipts("latest") pairs the txs of block H with the results of block H+1 (panic / wrong receipts)
- Dominant language
- Go
- Stars
- 164
- Forks
- 213
- Avg merge
- 3d 58m
- Merged PRs (30d)
- 12
Description
## Type
- [x] Bug
- [ ] Feature
- [ ] Proposal / Discussion
## Summary
`eth_getBlockReceipts("latest")` resolves the block by a concrete height H, but then fetches the block results with `blockNum.CmtHeight()`, which is `nil` for `latest` and makes CometBFT return the results of its *current* head. If a block is committed between the two calls, the txs of block H are paired with the results of block H+1:
https://github.com/cosmos/evm/blob/469142db41aee479102cf878f8ac2bbe2b0c7a22/rpc/backend/blocks.go#L210-L224
```go
resBlock, err := b.CometBlockByNumber(ctx, blockNum) // block H
...
blockRes, err := b.RPCClient.BlockResults(ctx, blockNum.CmtHeight()) // nil -> latest, may be H+1
...
msgs := b.EthMsgsFromCometBlock(ctx, resBlock, blockRes) // block.Txs[i] <-> txResults[i]
```
`EthMsgsFromCometBlock` indexes `txResults[i]` for every tx of block H: if H+1 has fewer txs it panics (index out of range, surfaced to the client as `method handler crashed`); if it has as many or more, the receipts come back with the status/gasUsed/logs of the wrong block. Every other block query in the backend (`GetBlockByNumber`, `HeaderByNumber`, `getBlockTransactionCount`, ...) uses `&resBlock.Block.Height`; this is the only one that forwards the sentinel. The `resBlock == nil` branch also dereferences `*blockNum.CmtHeight()`, which is nil for `latest`.
## Reproduction (for bugs)
Deterministic repro with the backend mocks (`tests/integration/rpc/backend`), `main` (`469142d`): register `Block(&10)` returning a block with one eth tx, and `BlockResults(nil)` returning an empty result set for height 11 (a newer block):
```go
client.EXPECT().Block(mock.Anything, mock.MatchedBy(func(h *int64) bool { return h != nil && *h == 10 })).
Return(&cmtrpctypes.ResultBlock{Block: blockWithOneTx}, nil)
client.EXPECT().BlockResults(mock.Anything, mock.MatchedBy(func(h *int64) bool { return h == nil })).
Return(&cmtrpctypes.ResultBlockResults{Height: 11, TxsResults: []*abci.ExecTxResult{}}, nil)
latest := rpctypes.EthLatestBlockNumber
_, err := s.backend.GetBlockReceipts(s.Ctx(), rpctypes.BlockNumberOrHash{BlockNumber: &latest})
```
```
panic: runtime error: index out of range [0] with length 0
rpc/backend/comet_to_eth.go EthMsgsFromCometBlock
```
On a live node this shows up as sporadic `method handler crashed` responses from `eth_getBlockReceipts("latest")` on chains with ~1s blocks, or receipts whose `gasUsed`/`status` do not match `eth_getTransactionReceipt`.
## Impact
Indexers and explorers typically poll `eth_getBlockReceipts("latest")`; they either get a 500 or, worse, silently wrong receipts. One-line fix (`BlockResults(ctx, &resBlock.Block.Height)`) plus a nil-block guard; PR with a regression test ready.
## Related
- #1047 (previous receipt fix in the same code path)
## Checklist
- [x] Linked to a GitHub Issue (or this is the Issue)
- [x] Repro steps included (for bugs)
- [x] Impact described
- [x] I understand minor typo/style doc fixes will not be accepted
Contributor guide
Research direction
Start in rpc/backend/blocks.go at GetBlockReceipts and compare its block-results height handling with the other block queries. Review EthMsgsFromCometBlock in rpc/backend/comet_to_eth.go and the backend integration mocks, then run the relevant tests. Done means latest receipts use the resolved block height, nil results are guarded, and the regression no longer panics or mismatches transactions and results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100