Skip to content

Ensure valid ENSRainbow connection before indexing starts#1850

Open
tk-o wants to merge 22 commits intomainfrom
feat/ensure-valid-ensrainbow-connection
Open

Ensure valid ENSRainbow connection before indexing starts#1850
tk-o wants to merge 22 commits intomainfrom
feat/ensure-valid-ensrainbow-connection

Conversation

@tk-o
Copy link
Copy Markdown
Contributor

@tk-o tk-o commented Mar 31, 2026

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • ENSDb SDK: added support for storing and reading the EnsRainbowPublicConfig object to/from ENSNode Metadata table.
    • See packages/ensdb-sdk/src/client/*
  • ENSDb Writer Worker: made the "Unstarted" Indexing Status a valid one to be stored in ENSNode Metadata table.
    • See apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts
  • Created ensureValidEnsRainbowConnection function to cover all business logic required as precondition for starting indexing.
    • See apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts and apps/ensindexer/src/lib/indexing-engines/ponder.ts

Why


Testing

Scenario 1: cold start

Simulated the scenarion when ENSIndexer was running for the first time and ENSRainbow instance was temporarily unavailable.

Result: ENSIndexer was able to:

  1. See that no public config was stored in ENSDb.
  2. Enforce invariant about the "unstarted" Indexing Status.
  3. Wait for the ENSRainbow instance to be ready.
  4. Fetch the public config from ENSRainbow instance.
  5. Upsert the fetched config into ENSDb.
Details
Running database migrations for ENSNode Schema in ENSDb.
16:50:34.362 INFO  Created HTTP server port=42069 (6ms)
16:50:34.362 INFO  Started returning 200 responses endpoint=/health
Database migrations for ENSNode Schema in ENSDb completed successfully.
[EnsDbWriterWorker]: Upserting ENSDb version into ENSDb...
[EnsDbWriterWorker]: ENSDb version upserted successfully: 1.9.0
[EnsDbWriterWorker]: Upserting ENSIndexer Public Config into ENSDb...
[EnsDbWriterWorker]: ENSIndexer Public Config upserted successfully
16:50:34.595 INFO  Started backfill indexing chain=11155420 block_range=[23770766,41583016]
16:50:34.595 INFO  Started backfill indexing chain=11155111 block_range=[3702721,10561159]
16:50:34.595 INFO  Started backfill indexing chain=534351 block_range=[8175276,17502787]
16:50:34.595 INFO  Started backfill indexing chain=84532 block_range=[13012458,39600142]
16:50:34.595 INFO  Started backfill indexing chain=421614 block_range=[123142726,255366915]
16:50:34.595 INFO  Started backfill indexing chain=59141 block_range=[2395094,27976800]
16:50:34.596 INFO  Started fetching backfill JSON-RPC data chain=11155420 cached_block=41582963 cache_rate=100%
16:50:34.597 INFO  Started fetching backfill JSON-RPC data chain=11155111 cached_block=10561150 cache_rate=100%
16:50:34.597 INFO  Started fetching backfill JSON-RPC data chain=534351 cached_block=17502764 cache_rate=100%
16:50:34.597 INFO  Started fetching backfill JSON-RPC data chain=84532 cached_block=39600089 cache_rate=100%
16:50:34.597 INFO  Started fetching backfill JSON-RPC data chain=421614 cached_block=255366545 cache_rate=100%
16:50:34.597 INFO  Started fetching backfill JSON-RPC data chain=59141 cached_block=27976713 cache_rate=100%
16:50:34.693 INFO  Finished fetching backfill JSON-RPC data chain=534351 (97ms)
No stored ENSRainbow Public Config found in ENSDb. Validating the omnichain indexing status is "Unstarted"...
Indexing Status snapshot is unavailable. Attempt 1 failed (Indexing Status snapshot was not found in ENSDb.). 3 retries left.
16:50:34.899 INFO  Finished fetching backfill JSON-RPC data chain=11155111 (303ms)
16:50:34.938 INFO  Finished fetching backfill JSON-RPC data chain=11155420 (342ms)
16:50:35.112 INFO  Finished fetching backfill JSON-RPC data chain=84532 (516ms)
16:50:35.121 INFO  Finished fetching backfill JSON-RPC data chain=421614 (525ms)
Indexing Status snapshot is unavailable. Attempt 2 failed (Indexing Status snapshot was not found in ENSDb.). 2 retries left.
16:50:36.138 INFO  Finished fetching backfill JSON-RPC data chain=59141 (1s)
Successfully loaded Indexing Status snapshot from ENSDb:
Omnichain indexing status is "Unstarted".
Waiting for ENSRainbow instance to be ready at 'http://localhost:3223/'...
Attempt 1 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 12 retries left.
Attempt 2 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 11 retries left.
16:50:39.594 INFO  Updated backfill indexing progress progress=0.0%
Attempt 3 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 10 retries left.
16:50:44.595 INFO  Updated backfill indexing progress progress=0.0%
Attempt 4 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 9 retries left.
16:50:49.596 INFO  Updated backfill indexing progress progress=0.0%
ENSRainbow instance is ready at 'http://localhost:3223/'.
Upserting the validated ENSRainbow Public Config into ENSDb...
Successfully upserted the validated ENSRainbow Public Config into ENSDb.
16:50:54.361 INFO  Indexed block range chain=11155111 event_count=2872 block_range=[3702721,4095260] (19s)
16:50:54.595 INFO  Updated backfill indexing progress progress=6.5%
16:50:54.837 INFO  Indexed block range chain=11155111 event_count=2892 block_range=[4095261,4212765] (472ms)
16:50:55.551 INFO  Indexed block range chain=11155111 event_count=2758 block_range=[4212766,4282224] (712ms)
16:50:55.933 INFO  Indexed block range chain=11155111 event_count=3021 block_range=[4282225,4364838] (380ms)
16:50:56.290 INFO  Indexed block range chain=11155111 event_count=3030 block_range=[4364839,4430864] (354ms)
16:50:56.663 INFO  Indexed block range chain=11155111 event_count=3018 block_range=[4430865,4520808] (371ms)

Scenario 2: warm start

Simulated the scenario when ENSIndexer was restarted and ENSRainbow instance was temporarily unavailable.

Result: ENSIndexer was able to

  1. Get the existing public config stored in ENSDb.
  2. Wait for the ENSRainbow instance to be ready.
  3. Fetch the public config from ENSRainbow instance.
  4. Have the fetched config validated against the one stored in ENSDb.
  5. Upsert the fetched config into ENSDb.
Details
16:39:26.130 INFO  Finished fetching backfill JSON-RPC data chain=534351 (306ms)
16:39:26.131 INFO  Finished fetching backfill JSON-RPC data chain=11155111 (306ms)
Waiting for ENSRainbow instance to be ready at 'http://localhost:3223/'...
Attempt 1 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 12 retries left.
16:39:26.171 INFO  Finished fetching backfill JSON-RPC data chain=11155420 (347ms)
16:39:26.247 INFO  Finished fetching backfill JSON-RPC data chain=84532 (423ms)
16:39:26.329 INFO  Finished fetching backfill JSON-RPC data chain=421614 (504ms)
Attempt 2 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 11 retries left.
16:39:27.650 INFO  Finished fetching backfill JSON-RPC data chain=59141 (1s)
Attempt 3 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 10 retries left.
16:39:30.823 INFO  Updated backfill indexing progress progress=47.5%
Attempt 4 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 9 retries left.
16:39:35.823 INFO  Updated backfill indexing progress progress=47.5%
16:39:40.823 INFO  Updated backfill indexing progress progress=47.5%
Attempt 5 failed for the ENSRainbow health check at 'http://localhost:3223/' (fetch failed). 8 retries left.
16:39:45.824 INFO  Updated backfill indexing progress progress=47.5%
16:39:50.824 INFO  Updated backfill indexing progress progress=47.5%
16:39:55.824 INFO  Updated backfill indexing progress progress=47.5%
ENSRainbow instance is ready at 'http://localhost:3223/'.
Upserting the validated ENSRainbow Public Config into ENSDb...
Successfully upserted the validated ENSRainbow Public Config into ENSDb.
16:39:58.862 INFO  Indexed block range chain=11155111 event_count=2940 block_range=[5617656,5618726] (32s)

Notes for Reviewer (Optional)


Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
    • This PR introduces updates in the indexing "hot path".
  • Relevant changesets are included (or are not required)

tk-o and others added 16 commits March 30, 2026 08:46
This function will enbale ENSIndexer modules to wait for when the ENSRainbow instance is ready to serve traffic.
Allows to wait with indexing onchain events until ENSRainbow instance is ready.
Co-authored-by: lightwalker.eth <126201998+lightwalker-eth@users.noreply.github.com>
…` object

This enable storing the public config of the connected ENSRainbow instance in ENSDb. The ENSDb record will be used to read the public config for ENSRainbow instance from other ENSNode apps.
@tk-o tk-o requested a review from a team as a code owner March 31, 2026 14:55
Copilot AI review requested due to automatic review settings March 31, 2026 14:55
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 31, 2026

🦋 Changeset detected

Latest commit: 4a80427

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@ensnode/ensdb-sdk Major
ensapi Major
ensindexer Major
@ensnode/integration-test-env Patch
ensadmin Major
ensrainbow Major
fallback-ensapi Major
enssdk Major
enscli Major
enskit Major
ensskills Major
@ensnode/datasources Major
@ensnode/ensrainbow-sdk Major
@ensnode/ensnode-react Major
@ensnode/ensnode-sdk Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@docs/ensnode Major
@docs/ensrainbow Major
@docs/mintlify Major
@namehash/ens-referrals Major
@namehash/namehash-ui Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@shrugs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 10 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 10 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7dc07ff6-46e9-4a6f-8056-107326b4e710

📥 Commits

Reviewing files that changed from the base of the PR and between 3d7bfb3 and 4a80427.

📒 Files selected for processing (5)
  • apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts
  • apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts
  • apps/ensindexer/src/lib/ensrainbow/singleton.ts
  • apps/ensindexer/src/lib/indexing-engines/ponder.ts
  • packages/ensdb-sdk/src/client/ensdb-reader.ts
📝 Walkthrough

Walkthrough

Extended ENSDb SDK with ENSRainbow Public Config storage and retrieval methods. Updated ENSIndexer to validate ENSRainbow configuration compatibility before indexing initialization, including stored config comparison, omnichain status checks, and label set version validation.

Changes

Cohort / File(s) Summary
Changesets Documentation
.changeset/whole-lines-smoke.md
Documented minor version bump for @ensnode/ensdb-sdk extending ENSNode Metadata to include ENSRainbow Public Config.
ENSDb SDK — Reader/Writer Extensions
packages/ensdb-sdk/src/client/ensdb-reader.ts, packages/ensdb-sdk/src/client/ensdb-writer.ts
Added getEnsRainbowPublicConfig() method to EnsDbReader and upsertEnsRainbowPublicConfig() method to EnsDbWriter for persisting ENSRainbow config in ENSDb.
ENSDb SDK — Metadata Types
packages/ensdb-sdk/src/client/ensnode-metadata.ts, packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts
Extended ENSNode metadata keys and types to support EnsRainbowPublicConfig, adding new metadata key variant and serialized type union member.
ENSDb SDK — Test Coverage
packages/ensdb-sdk/src/client/ensdb-reader.test.ts, packages/ensdb-sdk/src/client/ensdb-writer.test.ts
Added test suites for getEnsRainbowPublicConfig() and upsertEnsRainbowPublicConfig() with assertions for metadata retrieval and upsert operations.
ENSIndexer — Precondition Logic
apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts
New module implementing ensureValidEnsRainbowConnection() that validates ENSRainbow config exists when omnichain status is Unstarted, compares stored vs. fetched configs for labelSetId compatibility, and enforces version constraints.
ENSIndexer — Initialization & Singleton
apps/ensindexer/src/lib/indexing-engines/ponder.ts, apps/ensindexer/src/lib/ensrainbow/singleton.ts
Replaced waitForEnsRainbowToBeReady() with ensureValidEnsRainbowConnection() in indexing activation; parameterized retry schedule in singleton using typed Duration with adaptive interval-based retry counts and reduced log frequency.
ENSIndexer — Worker Refactoring
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts, apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts
Removed private getValidatedIndexingStatusSnapshot() helper and validation invariant check; updated upsert to fetch omnichain snapshot directly. Test updated to validate snapshots with different omnichain statuses across multiple calls.
ENSIndexer — Ponder Test Expansion
apps/ensindexer/src/lib/indexing-engines/ponder.test.ts
Significantly expanded test coverage (+447 lines) with mocks for ENSRainbow and ENSDb clients, added assertions for precondition validation logic including readiness waits, config fetching, labelSetId/version constraints, idempotency, setup event handling, and error propagation.

Sequence Diagram

sequenceDiagram
    actor Indexer as ENSIndexer<br/>(Activation)
    participant ENSDb as ENSDb Client
    participant Retry as Retry Engine<br/>(p-retry)
    participant Status as Indexing Status<br/>Builder
    participant RBow as ENSRainbow<br/>Service
    
    Indexer->>ENSDb: getEnsRainbowPublicConfig()
    alt Stored Config Exists
        ENSDb-->>Indexer: return stored config
        Indexer->>RBow: waitForEnsRainbowToBeReady()
        RBow-->>Indexer: ready
        Indexer->>RBow: config()
        RBow-->>Indexer: return live config
        Indexer->>Indexer: validate labelSetId match
        Indexer->>Indexer: validate highestLabelSetVersion ≥ stored
        Indexer->>ENSDb: upsertEnsRainbowPublicConfig(validated)
        ENSDb-->>Indexer: ✓ upserted
    else No Stored Config
        ENSDb-->>Indexer: undefined
        Indexer->>Retry: retry getIndexingStatusSnapshot()
        Retry->>Status: getIndexingStatusSnapshot()
        Status-->>Retry: snapshot
        Retry-->>Indexer: snapshot
        Indexer->>Indexer: verify status === Unstarted
        Indexer->>RBow: waitForEnsRainbowToBeReady()
        RBow-->>Indexer: ready
        Indexer->>RBow: config()
        RBow-->>Indexer: return config
        Indexer->>ENSDb: upsertEnsRainbowPublicConfig(config)
        ENSDb-->>Indexer: ✓ upserted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A hop, a skip through ENSRainbow's config
We store and validate with precise logic locks
Label sets matched, versions aligned true
ENSDb now remembers what Rainbow once knew 🌈

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main objective: ensuring valid ENSRainbow connection before indexing starts, which aligns with the core purpose of the entire changeset.
Description check ✅ Passed The PR description follows the Lite PR template with all required sections completed: Summary (3 bullets explaining changes), Why (links to related GitHub discussion and Slack feedback), Testing (2 detailed scenarios with logs), Notes for Reviewer, and Pre-Review Checklist (acknowledging risk level).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ensure-valid-ensrainbow-connection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 31, 2026 14:56 Inactive
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Apr 3, 2026 5:11pm
ensnode.io Ready Ready Preview, Comment Apr 3, 2026 5:11pm
ensrainbow.io Ready Ready Preview, Comment Apr 3, 2026 5:11pm

@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 31, 2026 14:56 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 31, 2026 14:56 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens ENSIndexer startup/indexing preconditions around ENSRainbow by persisting ENSRainbow’s public config in ENSDb metadata and enforcing a “valid ENSRainbow connection + compatible config” gate before onchain event handlers run.

Changes:

  • ENSDb SDK: add ENSNode metadata key/type + reader/writer helpers for EnsRainbowPublicConfig.
  • ENSIndexer: introduce ensureValidEnsRainbowConnection() precondition that loads/validates/upserts ENSRainbow public config and enforces “Unstarted” status on first-ever config write.
  • ENSDb writer worker: allow persisting Unstarted indexing status snapshots.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts Adds serialized union support for ENSRainbow public config metadata.
packages/ensdb-sdk/src/client/ensnode-metadata.ts Introduces ensrainbow_public_config metadata key + typed variant.
packages/ensdb-sdk/src/client/ensdb-writer.ts Adds upsertEnsRainbowPublicConfig() writer API.
packages/ensdb-sdk/src/client/ensdb-writer.test.ts Tests ENSRainbow public config upsert writes correct metadata.
packages/ensdb-sdk/src/client/ensdb-reader.ts Adds getEnsRainbowPublicConfig() reader API.
packages/ensdb-sdk/src/client/ensdb-reader.test.ts Tests ENSRainbow public config read behavior (undefined vs stored).
apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts New precondition implementing ENSRainbow readiness + config validation/upsert logic.
apps/ensindexer/src/lib/indexing-engines/ponder.ts Switches onchain precondition from ENSRainbow readiness to full ensureValidEnsRainbowConnection().
apps/ensindexer/src/lib/indexing-engines/ponder.test.ts Expands tests to cover new ENSRainbow config gating behaviors.
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts Removes prior guard preventing Unstarted snapshot upserts.
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts Updates snapshot upsert test to expect writes across Unstarted and started statuses.
.changeset/whole-lines-smoke.md Publishes a minor bump for @ensnode/ensdb-sdk due to new metadata support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 31, 2026

Greptile Summary

This PR introduces a startup precondition gate (ensureValidEnsRainbowConnection) that must resolve before any onchain event handler executes. It handles two scenarios: cold start (no stored ENSRainbow config in ENSDb — enforces "Unstarted" indexing status before waiting for ENSRainbow and writing the initial config) and warm start (stored config exists — validates compatibility of label-set ID and version before refreshing the stored config). The gate is wired into ponder.ts via a memoised indexingActivationPromise, replacing a previous boolean-flag approach and addressing the concern raised in #1843.

Key changes:

  • valid-ensrainbow-connection.ts (new): orchestrates the full precondition sequence with two invariant checks and retry logic.
  • ponder.ts: swaps the activation boolean for a shared Promise<void> so concurrent onchain events correctly share one in-flight precondition call instead of racing on a mutable flag.
  • ensdb-sdk: adds getEnsRainbowPublicConfig / upsertEnsRainbowPublicConfig, a new metadata key, and the corresponding serialized type alias.
  • ensdb-writer-worker.ts: fixes cold-start by allowing the "Unstarted" indexing status to be persisted in ENSDb (previously rejected silently, blocking the invariant check).
  • singleton.ts: throttles ENSRainbow health-check failure logging to once per minute to reduce noise during cold start.

Confidence Score: 5/5

  • Safe to merge — all remaining findings are P2 style/UX suggestions with no correctness or reliability impact.
  • Prior P0/P1 concerns from earlier review rounds (activation flag, typos, formatting) have been addressed. The new precondition architecture is sound: the promise-caching idiom in ponder.ts is idempotent and correct, the cold-start invariant check is properly guarded with retries, warm-start config validation covers both the label-set ID and version downgrade cases, and test coverage is comprehensive. The three remaining comments are P2: a first-attempt log for debuggability, a pattern-consistency note for deserialization, and a note about the p-retry mock not exercising retry counts.
  • No files require special attention for merge readiness.

Important Files Changed

Filename Overview
apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts New module implementing the full precondition gate: reads stored config, enforces "Unstarted" invariant on cold start with p-retry, waits for ENSRainbow health, fetches and validates the live config, then upserts it into ENSDb. Logic is sound with clear invariant functions and descriptive error messages.
apps/ensindexer/src/lib/indexing-engines/ponder.ts Replaced ad-hoc preparedIndexingActivation boolean flag with a promise-caching pattern (indexingActivationPromise) that correctly shares a single in-flight call across all concurrent onchain events, eliminating the prior risk of the flag being set before the async precondition resolves.
apps/ensindexer/src/lib/ensrainbow/singleton.ts Changed onFailedAttempt to log only every 12th attempt (~1 min). First 11 failures are silently suppressed — adding a log on attempt 1 would improve debuggability without reintroducing noise.
packages/ensdb-sdk/src/client/ensdb-reader.ts New getEnsRainbowPublicConfig method reads the JSONB value and returns it with only a type cast (no deserialization), which is correct today but inconsistent with the serialize/deserialize pattern used for all other metadata types.
apps/ensindexer/src/lib/indexing-engines/ponder.test.ts Good coverage of the new ENSRainbow precondition paths (cold start, warm start, invariant violations, idempotency). The blanket p-retry mock means retry-count and backoff configuration are not exercised.

Sequence Diagram

sequenceDiagram
    participant P as Ponder (onchain event)
    participant IAP as indexingActivationPromise
    participant VERC as ensureValidEnsRainbowConnection
    participant ENSDb as ENSDb (ensDbClient)
    participant ERW as ENSRainbow (singleton)

    P->>IAP: await eventHandlerPreconditions(Onchain)
    note over IAP: First call only — promise cached thereafter
    IAP->>VERC: initializeIndexingActivation()

    VERC->>ENSDb: getEnsRainbowPublicConfig()
    ENSDb-->>VERC: storedConfig (or undefined)

    alt Cold start (no storedConfig)
        loop pRetry (max 3)
            VERC->>ENSDb: getIndexingStatusSnapshot()
            ENSDb-->>VERC: snapshot (or undefined → retry)
        end
        VERC->>VERC: invariant_indexingStatusUnstarted(snapshot)
    end

    VERC->>ERW: waitForEnsRainbowToBeReady()
    note over ERW: pRetry health check, up to ~1 hour
    ERW-->>VERC: ready

    VERC->>ERW: ensRainbowClient.config()
    ERW-->>VERC: fetchedConfig

    alt Warm start (storedConfig exists)
        VERC->>VERC: invariant_labelSetIdCompatibility
        VERC->>VERC: invariant_highestLabelSetVersionCompatibility
    end

    VERC->>ENSDb: upsertEnsRainbowPublicConfig(fetchedConfig)
    ENSDb-->>VERC: ok

    VERC-->>IAP: resolved
    IAP-->>P: preconditions met — handler executes
Loading

Reviews (2): Last reviewed commit: "Apply PR feedback" | Re-trigger Greptile

@vercel vercel bot temporarily deployed to Preview – ensnode.io April 1, 2026 03:44 Inactive
Also, allow for async preconditions for the indexing setup events. This will be handy for ensuring certain pg extensions were enabled before indexing starts.
Copilot AI review requested due to automatic review settings April 1, 2026 09:10
@tk-o tk-o force-pushed the feat/ensure-valid-ensrainbow-connection branch from d498f8d to 4e2454b Compare April 1, 2026 09:10
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io April 1, 2026 09:10 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io April 1, 2026 09:10 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io April 1, 2026 09:10 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Thanks for updates. Shared some small feedback 👍

* @returns Validated Omnichain Indexing Status Snapshot.
* @throws Error if the Omnichain Indexing Status is not in expected status yet.
*/
private async getValidatedIndexingStatusSnapshot(): Promise<OmnichainIndexingStatusSnapshot> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not confident it's right to completely remove this logic.

My worry is: how do we tell the difference between the following situations:

  1. Omnichain status is Unstarted because no indexing has started in ENSDb yet.
  2. Indexing has started in ENSDb with an earlier instance of ENSIndexer, but now ENSIndexer is being restarted and it's still working to discover it's true omnichain indexing status from the state in ENSDb. During this case I'm worried that ENSIndexer also thinks it's "Unstarted"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omnichain status is "unstarted" if an only if no indexing has started. This is related to the fact that the omnichain status can only be "unstarted" if and only if all chains are queued. And a chain is queued if and only if its config.startBlock is equal to its checkpointBlock (checkpointBlock is sourced from Ponder Indexing Status, which is sourced from _ponder_checkpoint table in the ENSIndexer Schema).

If indexing has started and ENSIndexer has written any indexed data into the ENSIndexer Schema, the checkpointBlock for some indexed chain has also been stored in the _ponder_checkpoint table in the ENSIndexer Schema in ENSDb. Therefore, if ENSIndexer instance restarts, some of the indexed chains will not be "queued" anyomore as checkpointBlock will be ahead of config.startBlock for that chain. That leads us to the fact that the omnichain status cannot be "unstarted" anymore, and goes straight to "backfill".

}

/**
* Ensure that we have a valid connection to ENSRainbow and
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to refine our terminology here.

Why are we using the word "valid"? I assume the goal is to describe something about "compatible" instead? And assuming the key idea here is "compatible", can you please help to make it more explicit exactly what "compatible" means?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that the connection is valid after it was validated that the connected ENSRainbow instance can be used to serve the ENSIndexer instance. The "compatibility" term was suggested to reference a relation between two EnsRainbowPublicConfig objects, where two of such objects are "compatbile if, and only if, both conditions are true:

  1. The label set ID is the same between these two objects.
  2. The highest label set version of the EnsRainbowPublicConfig object fetched from ENSRainbow instance is greater than or equal to the highest label set version of the EnsRainbowPublicConfig object stored in ENSDb.

The compatibility of EnsRainbowPublicConfig objects is enforced in the form of invariants. And when both invariants are OK, we can say that the ENSIndexer has a valid connection to ENSRainbow instance. In other words, there's a valid connection to ENSRainbow.

Does that make sense?

Base automatically changed from feat/onchain-event-handler-preconditions to main April 2, 2026 15:32
Copy link
Copy Markdown
Collaborator

@shrugs shrugs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on the indexing status terminology or logic, but otherwise looks good to me

@tk-o
Copy link
Copy Markdown
Contributor Author

tk-o commented Apr 2, 2026

@greptile review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts (1)

283-315: ⚠️ Potential issue | 🟡 Minor

Assert the builder inputs, not only the mocked outputs.

Because buildCrossChainIndexingStatusSnapshotOmnichain(...) is preprogrammed with sequential return values, this test still passes if the worker calls the builder with the wrong omnichain snapshot on either tick. Add toHaveBeenNthCalledWith(...) assertions on the builder so the regression coverage is tied to the new Unstarted flow, not just to mocked return order.

🧪 Suggested assertion tightening
       vi.mocked(buildCrossChainIndexingStatusSnapshotOmnichain)
         .mockReturnValueOnce(unstartedCrossChainSnapshot)
         .mockReturnValueOnce(validCrossChainSnapshot);
@@
       // assert
       expect(indexingStatusBuilder.getOmnichainIndexingStatusSnapshot).toHaveBeenCalledTimes(2);
+      expect(buildCrossChainIndexingStatusSnapshotOmnichain).toHaveBeenNthCalledWith(
+        1,
+        unstartedSnapshot,
+        expect.any(Number),
+      );
+      expect(buildCrossChainIndexingStatusSnapshotOmnichain).toHaveBeenNthCalledWith(
+        2,
+        validSnapshot,
+        expect.any(Number),
+      );
       expect(ensDbClient.upsertIndexingStatusSnapshot).toHaveBeenCalledTimes(2);
       expect(ensDbClient.upsertIndexingStatusSnapshot).toHaveBeenNthCalledWith(
         1,
         unstartedCrossChainSnapshot,
       );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts`
around lines 283 - 315, The test currently only asserts the mocked outputs from
buildCrossChainIndexingStatusSnapshotOmnichain and
ensDbClient.upsertIndexingStatusSnapshot, so add assertions that verify the
worker actually called the builder with the expected omnichain snapshots on each
tick: use
expect(buildCrossChainIndexingStatusSnapshotOmnichain).toHaveBeenNthCalledWith(1,
unstartedSnapshot) and .toHaveBeenNthCalledWith(2, validSnapshot) (or, if the
builder method under test is
indexingStatusBuilder.getOmnichainIndexingStatusSnapshot, assert that one
instead) to ensure the worker passed the correct input snapshots to
buildCrossChainIndexingStatusSnapshotOmnichain/indexingStatusBuilder.getOmnichainIndexingStatusSnapshot
on the first and second interval ticks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensindexer/src/lib/indexing-engines/ponder.test.ts`:
- Around line 321-336: The test currently accepts any error (/.*/), which is too
broad; update the assertion in the it("throws when indexing status snapshot is
missing") case to match the specific error thrown when
mockGetIndexingStatusSnapshot resolves to undefined—capture the actual error
message produced by the indexing code and replace the generic regex with a
targeted one (for example matching "indexing status snapshot" or the exact
phrase thrown) in the expectHandlerThrows call that wraps
getRegisteredCallback(); ensure you still set
mockGetIndexingStatusSnapshot.mockResolvedValue(undefined) and call
addOnchainEventListener/getRegisteredCallback as before so the test validates
the correct failure path.

In
`@apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts`:
- Around line 146-166: The pRetry call that loads the indexing status snapshot
(wrapping ensDbClient.getIndexingStatusSnapshot) only sets retries and relies on
default exponential backoff; update that pRetry invocation to include explicit
timing options (e.g., minTimeout and maxTimeout or a timeout and factor) to make
retry timing deterministic, matching the backoff configuration used by
waitForEnsRainbowToBeReady(); adjust the options object passed to pRetry for the
indexingStatusSnapshot block so it includes the explicit timing fields alongside
retries and onFailedAttempt.

---

Outside diff comments:
In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts`:
- Around line 283-315: The test currently only asserts the mocked outputs from
buildCrossChainIndexingStatusSnapshotOmnichain and
ensDbClient.upsertIndexingStatusSnapshot, so add assertions that verify the
worker actually called the builder with the expected omnichain snapshots on each
tick: use
expect(buildCrossChainIndexingStatusSnapshotOmnichain).toHaveBeenNthCalledWith(1,
unstartedSnapshot) and .toHaveBeenNthCalledWith(2, validSnapshot) (or, if the
builder method under test is
indexingStatusBuilder.getOmnichainIndexingStatusSnapshot, assert that one
instead) to ensure the worker passed the correct input snapshots to
buildCrossChainIndexingStatusSnapshotOmnichain/indexingStatusBuilder.getOmnichainIndexingStatusSnapshot
on the first and second interval ticks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b2e6925-5d95-4fec-9821-1feaa55211c3

📥 Commits

Reviewing files that changed from the base of the PR and between 9c5f68b and 3d7bfb3.

📒 Files selected for processing (13)
  • .changeset/whole-lines-smoke.md
  • apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts
  • apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts
  • apps/ensindexer/src/lib/ensrainbow/singleton.ts
  • apps/ensindexer/src/lib/indexing-engines/ponder.test.ts
  • apps/ensindexer/src/lib/indexing-engines/ponder.ts
  • apps/ensindexer/src/lib/indexing-engines/preconditions/valid-ensrainbow-connection.ts
  • packages/ensdb-sdk/src/client/ensdb-reader.test.ts
  • packages/ensdb-sdk/src/client/ensdb-reader.ts
  • packages/ensdb-sdk/src/client/ensdb-writer.test.ts
  • packages/ensdb-sdk/src/client/ensdb-writer.ts
  • packages/ensdb-sdk/src/client/ensnode-metadata.ts
  • packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts

Copilot AI review requested due to automatic review settings April 3, 2026 17:10
Copy link
Copy Markdown
Collaborator

@shrugs shrugs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +134 to +137
if (!storedConfig) {
console.log(
"No stored ENSRainbow Public Config found in ENSDb. Validating the omnichain indexing status is 'Unstarted'...",
);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This precondition module uses console.log/console.warn for operational logging. In ENSIndexer, other runtime components use the shared structured logger (e.g. apps/ensindexer/src/lib/ensrainbow/singleton.ts) so logs carry level/module context and are routed consistently. Please switch these console calls to logger.info/logger.warn/logger.error (including a module field) to keep startup/indexing diagnostics consistent and avoid losing logs in production environments that don’t capture stdout/stderr the same way.

Copilot uses AI. Check for mistakes.
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.

4 participants