Skip to content

feat: high-signal comparison output with expected-difference rules - #24

Merged
cbermudez97 merged 3 commits into
mainfrom
feat/comparison-update
Jul 21, 2026
Merged

feat: high-signal comparison output with expected-difference rules#24
cbermudez97 merged 3 commits into
mainfrom
feat/comparison-update

Conversation

@cbermudez97

Copy link
Copy Markdown
Contributor

Summary

Implements the COMPARISON-IMPROVEMENTS.md handoff brief in full, motivated by a real archive-vs-archive Nethermind correctness run (v1.39.1 vs v1.40) where three problems dominated: expected differences (e.g. totalDifficulty, sub-1% eth_estimateGas drift) flooded the report, a single transport blip discarded an 800-call run, and the report was unopenable (~769 MB HTML).

Everything is backward compatible: new config blocks and flags are optional, and defaults reproduce prior behavior aside from the additive retry.

What's included

  • Expected-difference rules — a unified comparison.rules list: ignore (with [*] array wildcards), numeric_tolerance (abs/rel on hex quantities), error_code_only, error_presence_only. eth_estimateGas gets a built-in 10% relative tolerance by default (diffs ≤10% treated as correct).
  • Error classification — buckets namespace_disabled / no_state / range_cap so capability errors are filtered as config, not correctness.
  • Retry + non-fatal transport — bounded retry with exponential backoff for transport errors and 5xx (honors clients.yaml max_retries, plus --max-retries / --retry-base-delay); a final transport failure is recorded per-call instead of aborting the whole run.
  • Lean output--diff-only, --omit-matching-responses, --max-response-bytes gate what comparison-results.json and the HTML serialize.
  • Summary + CI gate — end-of-run category tally and --fail-on-diff exit code.
  • Sync-gap awareness--skip-above-head skips calls pinned above the lowest client head.
  • Block pinningblock_override / --block-override rewrites latest/pending to a static block and appends a block arg to calls that omit one.
  • Corpus helper--from-jsonl / --sample builds a config from rpc-calls/*.jsonl with deterministic sampling and proof/head-dependent/debug exclusions.
  • Provenance — the effective config is written to a comparison-provenance.json sidecar and surfaced in the report.

Files

New: runner/comparator/{rules,block_override,corpus}.go (+ tests). Modified: diff engine (threads a rules context), orchestrator, transport, config loader, report, and both CLI commands. Documented comparison: example added to config/compare/example.yaml.

Testing

  • go build ./..., go vet ./... clean; comparator unit + integration tests pass (tolerance boundaries, ignore [*], error rules, classification, block-override rewriting, corpus sampling, retry-recovers, non-fatal transport, diff-only serialization).
  • End-to-end CLI run against mock nodes: --diff-only kept only the real mismatch, the estimateGas drift was absorbed by the tolerance, provenance sidecar written, HTML small, --fail-on-diff exited non-zero.

Notes for review

  • Gas tolerance direction: within 10% ⇒ equal/correct; >10% ⇒ reported.
  • Rules / block_override / --from-jsonl are compare-only (they live in the compare YAML); retry, --diff-only, summary, --fail-on-diff, --skip-above-head apply to both compare and compare-openrpc.
  • If the baseline client (Clients[0]) transport-fails on a call, the reference falls back to the first client that answered.

Make `runner compare` produce smaller, higher-signal output and stay
robust against flaky/throttling endpoints.

- comparison rules: a unified `comparison.rules` list declares expected
  differences (ignore paths with `[*]` wildcards, numeric_tolerance for
  hex quantities, error_code_only / error_presence_only). eth_estimateGas
  gets a built-in 10% relative tolerance by default.
- error classification: bucket namespace_disabled / no_state / range_cap
  so capability errors are filtered as config, not correctness.
- retry + non-fatal transport: bounded retry with exponential backoff for
  transport errors and 5xx (honors clients.yaml max_retries and new
  --max-retries / --retry-base-delay); a final transport failure is
  recorded per-call instead of discarding the whole run.
- lean output: --diff-only, --omit-matching-responses, --max-response-bytes
  gate what results.json and the HTML report serialize.
- summary + CI gate: end-of-run category tally and --fail-on-diff exit code.
- sync-gap awareness: --skip-above-head skips calls pinned above the lowest
  client head.
- block pinning: `block_override` / --block-override rewrites latest/pending
  tags to a static block and appends a block arg to calls that omit one.
- corpus helper: --from-jsonl / --sample builds a config from rpc-calls/*.jsonl
  with deterministic sampling and proof/head-dependent/debug exclusions.
- provenance: the effective config is written to a comparison-provenance.json
  sidecar and surfaced in the report.

All new config blocks and flags are optional; defaults reproduce the prior
behavior aside from the additive retry.
Bump the builder to golang:1.25.12-alpine (fixes the Go stdlib os.Root
symlink CVE in the runner binary) and add a documented .trivyignore for
the same CVE in the upstream grafana/k6 base binary, which we cannot
recompile until grafana/k6 ships a Go >=1.26.5 build.
@cbermudez97
cbermudez97 merged commit 276a980 into main Jul 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant