erigontech / erigontech/erigon

db/integrity: ReceiptRootIntegrity cannot tell a correctly-empty block from a missing-receipts block

Open
#23,916 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.