Skip to content

Parity tests: indexer vs archive-node-api on devnet (events/actions/blocks differential) #22

@dkijania

Description

@dkijania

Summary

Add differential ("parity") tests that compare the indexer's answers against the legacy archive-node-api on devnet, to (a) catch indexer correctness regressions against a known-good oracle and (b) build the evidence that the indexer can replace the archive-node-api for downstream consumers (notably o1js fetchEvents / fetchActions).

Two production surfaces (probed 2026-06-18):

  • Indexer: https://devnet-indexer.gcp.o1test.net (REST /summary + GraphQL /graphql)
  • Archive node API: https://devnet-archive-node-api.gcp.o1test.net (GraphQL only)

Comparable surface (what overlaps)

The archive-node-api is narrow — its entire query surface is blocks, events, actions, networkState. The indexer is a superset (block(s), events, actions, accounts, transaction(s), internalCommands, snarks, stakes, tokens, …). So the parity scope is exactly the overlap:

Target indexer archive-node-api Priority
zkApp events per account events events ★ highest — the archive-node-api's whole purpose; backs o1js fetchEvents
zkApp actions per account actions actions ★ highest — backs o1js fetchActions
block by height / state-hash (+ its commands) block/blocks blocks high
tip / network state /summary (loosely) networkState liveness sanity only

Note: the indexer's /summary has no real archive-node-api counterpart beyond a loose networkState match — it is not a good comparison target. Don't anchor the tests on /summary vs archive.

Test design

Property (differential equivalence) over random valid inputs:

∀ height h in [genesis+1, min(tip_indexer, tip_archive) − MARGIN],
∀ account a in {discovered zkApp accounts}:
    normalize(indexer.query(a, h)) == normalize(archive.query(a, h))
  • Generators: random finalized h; random a from a discovered set of zkApp accounts active on devnet.
  • Use proptest for the in-repo (hermetic) version — shrinking yields the minimal failing (h, a). Seeded RNG for the live monitor.

The flake-prone parts to handle explicitly:

  1. Tip skew / reorgs — both follow the tip independently and will disagree at the head. Only compare at min(tip) − MARGIN with MARGIN ≥ k (290) (finalized). This single rule removes ~all false positives.
  2. Schema normalization — different schemas: map field names, sort events/actions deterministically (order not guaranteed equal), normalize units (nanomina vs decimal), null-vs-empty, pagination. Project both into one canonical struct, compare that.
  3. Canonical-only — filter both to the canonical chain (the indexer also serves non-canonical blocks).
  4. Gentle on prod — read-only, low concurrency, bounded request budget. This is a correctness probe, not a load test.

Placement — split into two halves (they have opposite requirements)

A. Hermetic golden parity → this repo (fold into #19)

Ingest a fixed devnet block range, capture archive-node-api responses for that range once as golden fixtures, assert the indexer reproduces them. Deterministic, offline, no live dependency → safe to gate every PR. Belongs here because it tests this code in this CI. Reuses the e2e harness proposed in #19.

B. Live randomized differential vs prod → integration/monitoring repo, scheduled

Comparing two live deployments is tip-sensitive and depends on both services being up — a cross-service acceptance/monitoring concern, not per-PR CI (external flakiness must not gate indexer PRs). Home: a neutral mina-indexer-parity repo (or an org integration-tests repo) running a cron against devnet prod, alerting on divergence. This is the right place for the "minaprotocol/integration-tests"-style live check.

Deliverables

  • Canonical normalization adapter mapping indexer ↔ archive-node-api for events / actions / blocks.
  • (A) proptest-based hermetic parity tests in mina-indexer, driven by recorded golden archive responses over a fixed devnet range (under Testing investment plan: coverage, PCB-driven e2e correctness, feature & security tests #19's harness).
  • (B) Live differential script (discover both tips → pick finalized height → pull events/actions for known zkApp accounts from both → normalize → diff), seeded + bounded, in a monitoring repo on a schedule.
  • Decision recorded on where (B) lives (new mina-indexer-parity repo vs existing integration-tests).

Out of scope

Notes

  • Highest ROI is events/actions parity: it's the archive-node-api's reason to exist and the concrete blocker to pointing o1js at the indexer.
  • Probed surfaces confirm the overlap above; archive-node-api root fields = {events, actions, networkState, blocks}.

Related: #19 (testing investment plan — the hermetic half lives there).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions