Surfaced by a Copilot review comment on #21293 (since split — the eth_getProof changes live in #22533); the behavior predates both, and #22533 only renames the variable on the guilty line.
Behavior
eth_getProof(addr, keys, "0x0") and eth_getProof(addr, keys, "earliest") return block not found even though block 0 exists and resolves successfully:
blockNumber, _, isLatest, err := rpchelper.GetCanonicalBlockNumber(ctx, blockNrOrHash, roTx, api._blockReader, nil)
if err != nil {
return nil, err
} else if blockNumber == 0 {
return nil, errors.New("block not found")
}
The sentinel is redundant for its apparent purpose: GetCanonicalBlockNumber already returns rpc.BlockNotFoundErr for unknown blocks (the !found path), so the only requests the blockNumber == 0 check actually affects are valid genesis/earliest ones. Geth serves genesis proofs.
Fix shape
Remove the sentinel — but as its own red→green cycle, not a drive-by: the historical-proof path needs validating at the block-0 edge first (txNumReader.Min(ctx, roTx, 1) for the rewind bound, commitment-history start, no-transactions block). Red-first test: eth_getProof at "earliest" on an unpruned test chain returns a proof that verifies against the genesis state root; on a node with pruned commitment history it returns the existing PrunedError, not block not found.
Surfaced by a Copilot review comment on #21293 (since split — the
eth_getProofchanges live in #22533); the behavior predates both, and #22533 only renames the variable on the guilty line.Behavior
eth_getProof(addr, keys, "0x0")andeth_getProof(addr, keys, "earliest")returnblock not foundeven though block 0 exists and resolves successfully:The sentinel is redundant for its apparent purpose:
GetCanonicalBlockNumberalready returnsrpc.BlockNotFoundErrfor unknown blocks (the!foundpath), so the only requests theblockNumber == 0check actually affects are valid genesis/earliestones. Geth serves genesis proofs.Fix shape
Remove the sentinel — but as its own red→green cycle, not a drive-by: the historical-proof path needs validating at the block-0 edge first (
txNumReader.Min(ctx, roTx, 1)for the rewind bound, commitment-history start, no-transactions block). Red-first test:eth_getProofat"earliest"on an unpruned test chain returns a proof that verifies against the genesis state root; on a node with pruned commitment history it returns the existingPrunedError, notblock not found.