Skip to content

Expand RPC coverage: historical sampling, state/execution methods, deeper tracing, error parity #29

Description

@mattsse

Currently the tester only runs against the last --num-blocks (default 32) from tip and covers block/tx/receipt retrieval, getLogs, trace_block, and debug_traceTransaction with callTracer. This is a good smoke test of the read path near tip, but it misses several dimensions where clients actually diverge: historical state, EVM execution on historical state, deeper tracing, and error-path parity. Below is a list of proposed additions, roughly by priority.

1. Historical sampling mode

Everything runs against recently-cached, near-tip state. Bugs in static files, pruned history, tx/receipt index rebuilding, and pre-fork-era encodings are invisible.

  • Add a --historical mode that samples blocks at log-spaced offsets from head (head-128, head-1k, head-10k, head-100k, head-1M, capped at genesis) plus a few uniform-random blocks, and runs the existing per-block suite there. Log-spacing is cheap and crosses cache/static-file/db boundaries.
  • Support a pinned corpus of "interesting" blocks per chain (e.g. fork transition blocks, the merge block, first 4844 block, first 7702 tx, empty blocks, blocks with uncles, one block per tx type). Deterministic and catches encoding edge cases random sampling misses. Could be a simple --blocks <list|file> flag.

2. State methods

Only eth_getBalance and eth_getTransactionCount (for tx senders) are covered today.

  • eth_getCode and eth_getStorageAt for addresses derived from receipt logs at that block
  • eth_getProof at historical blocks — the single best archive-correctness probe

3. Execution methods

No EVM execution is exercised at all.

  • Replay a block's own transactions as eth_call at the parent block and diff the results — classic archive-node differential test, inputs are free
  • eth_estimateGas / eth_createAccessList on the same synthesized calls
  • Optionally eth_simulateV1 for a block's tx list

4. Deeper tracing

Only callTracer and parity trace_block today. callTracer is the least sensitive tracer.

  • debug_traceBlockByNumber / debug_traceBlockByHash — also cross-checks block-level vs tx-level tracing consistency
  • prestateTracer — surfaces state-read divergence that callTracer hides
  • Default structlog tracer on a small sample of txs (expensive, but the strongest differ)
  • trace_replayBlockTransactions with stateDiff
  • trace_filter over the block range — the range-indexed counterpart to getLogs and historically a rich source of client bugs

5. Error/miss-path parity

All queries use data known to exist. Clients diverge a lot on the miss path (null vs error, error codes per EIP-1474).

  • Nonexistent tx hash, future block number, out-of-range tx index — assert both sides return the same null/error shape
  • A few invalid-params calls asserting matching error codes

6. getLogs improvements

  • Multi-topic filters (topic1-3, OR lists) and blockHash-based filters
  • Surface when the "max results exceeded" pagination fired on only one side. The retry helper transparently paginates per node, so a server-side ordering/limit bug can be laundered into a passing comparison of client-side concatenations.

7. Block ID/tag coverage

Only numeric block IDs are used.

  • earliest (exercises genesis retrieval)
  • safe / finalized comparison when both nodes share a CL view
  • Block-hash BlockId with requireCanonical

8. Misc cheap additions

  • eth_feeHistory over a historical range (fully deterministic)
  • eth_getUncleByBlockHashAndIndex (the count is already fetched)
  • debug_getRawBlock / debug_getRawHeader / debug_getRawReceipts — raw-encoding parity catches bugs JSON normalization hides
  • One-shot eth_chainId sanity check at startup
  • Filter APIs (eth_newFilter / eth_getFilterLogs) if the stateful surface matters

9. Structural improvements

  • Smarter default tx sampling: instead of the first tx per block (disproportionately the same searcher on mainnet), sample one tx per type present in the block (legacy/2930/1559/4844/7702). Better coverage than --use-all-txes at a fraction of the cost.
  • Reorg guard: fetch_block treats rpc2 as truth with no canonical-agreement check, so a tip reorg mid-run diffs everything downstream with a confusing report. Re-fetch by hash from both sides and skip/retry on mismatch.
  • wait_for_readiness underflows on young chains (common - (block_size_range - 1)) and re-parses CliArgs internally.

The rpc!/rpc_raw! macros make most of the method additions one-liners, and the historical mode is essentially calling run() with additional ranges.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions