erigontech / erigontech/erigon
db/integrity: ReceiptRootIntegrity cannot tell a correctly-empty block from a missing-receipts block
- Dominant language
- Go
- Stars
- 3.6k
- Forks
- 1.5k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 455
Description
`CheckRCacheRootAtBlk` / `CheckRCacheRootAtBlkRange` compare a root derived from the receipts they
streamed out of RCache against `header.ReceiptHash`:
```go
// db/integrity/rcache_receipt_root.go
computedRoot := types.DeriveSha(receipts)
if computedRoot != header.ReceiptHash { ... }
```
There is no cross-check that the block actually has transactions. For a block with none, `receipts`
is empty, `DeriveSha` returns the empty-trie root, and `header.ReceiptHash` is that same root — so
the comparison passes whether or not RCache returned anything for that block. A missing-receipts
block and a correctly-empty block are indistinguishable.
## Why this matters beyond the edge case
`ReceiptRootIntegrity` is in `FastChecks`, so it runs by default, and the runner samples. On a chain
with a meaningful share of empty blocks, every sampled empty block is a guaranteed pass that carries
no information, so the effective sample is smaller than the configured one and a real hole is easy
to walk straight past.
## Observed
On a gnosis archive datadir with a known RCache hole spanning roughly blocks 46,833,000-46,853,000
(one txNum step, `step_size=390625`, inside the merged `v3.0-rcache.1152-1216.kv`), single-block
probes across the band gave:
```
46832000 OK 29 txs
46834000 BAD 11 txs
46836000 BAD 1 tx
46837000 OK 0 txs <-- header root IS the empty root; probe carries no information
46837750 BAD 6 txs computed=0x56e81f17...b421 (empty), header=0x4bdb4714...
46852000 BAD 13 txs
46854000 OK 17 txs
```
Every failing block reports `computed` = the empty-trie root, i.e. RCache yielded nothing. The
`46837000 OK` sits inside the bad band and is purely an artifact of that block having no
transactions — it initially looked like the hole was discontiguous.
The same datadir's earlier 1%-sampled full run flagged exactly one aligned 100-block chunk out of
~4,792 sampled, which is consistent with most sampled blocks in the band being uninformative.
## Suggested fix
Compare the number of receipts streamed for a block against that block's transaction count, and
report a mismatch independently of the root comparison. That catches the empty-block blind spot and
also catches a partial hole where the count is short but the derived root happens not to be
compared. The transaction count is already reachable from the txnum range the check computes per
block.
## Related
- #23611 — with `--failFast=false` the same command logs every mismatch, returns nil, prints
"success" and exits 0, so a clean exit is not evidence of a clean datadir. The two together mean a
sampled, non-failFast run can report success over a datadir with a real receipt hole.
Contributor guide
Research direction
Start in db/integrity/rcache_receipt_root.go and read CheckRCacheRootAtBlk and CheckRCacheRootAtBlkRange, including how each block’s transaction range is computed. Verify the streamed receipt count against the block transaction count independently of the derived root. Test the empty-block and missing-receipts cases, including the observed hole range, and confirm mismatches are reported.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- blockchain, databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100