Skip to content

Fix indexing invariants logic#2350

Merged
tk-o merged 7 commits into
mainfrom
hotfix/22f5cb7
Jul 13, 2026
Merged

Fix indexing invariants logic#2350
tk-o merged 7 commits into
mainfrom
hotfix/22f5cb7

Conversation

@tk-o

@tk-o tk-o commented Jul 13, 2026

Copy link
Copy Markdown
Member

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • Updated isRegistrationFullyExpired and isRegistrationInGracePeriod helpers to accurately represent the logic implemented on chain: the very last second of grace period is still a valid moment to renew a registration.
  • Updated handleRegistrationOrReservation event handler to accurately represent the logic implemented onchain: the LabelRegistered event can be emitted multiple times for an existing registration of a reverse name. Such registrations never expire.

Why

  • ENSIndexer Alpha and ENSIndexer V2 Sepolia could not continue indexing due to indexing logic mismatch with onchain logic.

Testing

  • Updated unit tests, and executed them successfully.
  • Tested updates in the green env (used docker images from a preview release).

Notes for Reviewer (Optional)


Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

Align  and  helpers with the indexed smart contracts' logic.
Copilot AI review requested due to automatic review settings July 13, 2026 05:17
@changeset-bot

changeset-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3a4db6

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

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

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

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The SDK updates expiration helper types and grace-period boundaries, with tests for timestamp and null cases. API and indexer consumers now use derived expiry information and numeric timestamps. ENSv2 registry handling exempts validated reverse-name registrations from the existing expiration invariant.

Changes

Registration Expiration and Reverse-Name Handling

Layer / File(s) Summary
Expiration contracts and boundary behavior
packages/ensnode-sdk/src/registrars/registration-expiration.ts, packages/ensnode-sdk/src/registrars/registration-expiration.test.ts, .changeset/modern-groups-occur.md
Expiry information and helper timestamps use UnixTimestamp; grace-period boundary comparisons are updated and tested, including null values.
API expiration context and resolvers
apps/ensapi/src/omnigraph-api/context.ts, apps/ensapi/src/omnigraph-api/schema/registration.ts
The API context stores now as a number, and registration resolvers build expiry information before evaluating expiration status.
Indexer expiry-check plumbing
apps/ensindexer/src/plugins/unigraph/handlers/ensv1/*, apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ETHRegistrar.ts, apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts
ENSv1 and ENSv2 handlers derive expiry information and convert block timestamps before expiration and grace-period invariant checks.
Reverse-name registration invariant
apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts, .changeset/two-corners-tease.md
Validated reverse-name registrations are identified from the label and registrant identifiers and excluded from the existing-registration expiration invariant.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: javascript

Poem

I’m a rabbit with a registry key,
Boundary clocks now tick precisely.
Reverse names hop past the gate,
Graceful tests confirm their state.
Two patch notes bloom—what a tidy spree!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately reflects the main change: fixing indexing invariant logic.
Description check ✅ Passed The description follows the template and includes Summary, Why, Testing, Notes, and the blocking checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/22f5cb7

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.

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Jul 13, 2026 3:27pm
enskit-react-example.ensnode.io Ready Ready Preview, Comment Jul 13, 2026 3:27pm
ensnode.io Ready Ready Preview, Comment Jul 13, 2026 3:27pm
ensrainbow.io Ready Ready Preview, Comment Jul 13, 2026 3:27pm

@tk-o

tk-o commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@greptile review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 aligns ENS registration expiry/grace-period helpers (used across SDK, indexer, and API layers) with onchain boundary semantics, and relaxes an ENSIndexer ENSv2 invariant to handle repeated LabelRegistered events for reverse-name reservations without halting indexing.

Changes:

  • Updated isRegistrationFullyExpired and isRegistrationInGracePeriod to treat expiry + gracePeriod as an inclusive grace endpoint (renewal still valid at the last second).
  • Added unit tests covering expiry/grace boundary conditions for the registration expiration helpers.
  • Updated ENSv2 registry handler invariant logic to tolerate existing reverse-name registrations that should not be considered expirable.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/ensnode-sdk/src/registrars/registration-expiration.ts Adjusts expiry/grace boundary comparisons to match onchain semantics.
packages/ensnode-sdk/src/registrars/registration-expiration.test.ts Adds coverage for boundary conditions and null-handling behavior.
apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Updates invariant handling to account for repeated reverse-name LabelRegistered events.
.changeset/modern-groups-occur.md Publishes SDK helper behavior alignment as a patch changeset.

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

Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Outdated
Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.ts
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two off-by-one boundary conditions in the ENS registration expiration helpers and adds a special-case handler for reverse-name registrations that can be re-registered without expiring.

  • registration-expiration.ts: Changes >=> in isRegistrationFullyExpired and >>= in isRegistrationInGracePeriod so that the exact last second of the grace period is correctly classified as "in grace period" (renewable) rather than "fully expired". New unit tests cover the boundary explicitly.
  • ENSv2Registry.ts: Splits the former else branch into else if (registration) (fixing the missing null guard) and adds isReverseNameRegistration detection so that re-emitted LabelRegistered events for address-label reverse names bypass the now-expired invariant check without throwing.

Confidence Score: 5/5

Safe to merge — the changes are well-scoped boundary fixes backed by new unit tests and confirmed against onchain behaviour.

Both boundary corrections are minimal and consistent with each other: the last second of the grace period is now correctly non-expired in both helpers. The ENSv2Registry handler correctly adds a null guard and a narrowly scoped reverse-name bypass that is gated on two independent conditions (label is a valid normalized address AND the previous registrant equals that address). The new test suite exercises every relevant boundary value, including null inputs and exact-boundary timestamps.

No files require special attention.

Important Files Changed

Filename Overview
packages/ensnode-sdk/src/registrars/registration-expiration.ts Fixes two boundary comparisons: >= → > in isRegistrationFullyExpired and > → >= in isRegistrationInGracePeriod, making the last second of the grace period correctly non-expired and in-grace simultaneously.
packages/ensnode-sdk/src/registrars/registration-expiration.test.ts New comprehensive unit tests covering all boundary cases for isRegistrationExpired, isRegistrationFullyExpired, and isRegistrationInGracePeriod; boundary values and null inputs are explicitly tested.
apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Fixes the null guard (else → else if registration) and adds isReverseNameRegistration detection to allow re-registration of address-label reverse names without triggering the unexpired-registration invariant.
.changeset/modern-groups-occur.md Patch changeset for ensnode-sdk reflecting the expiration helper alignment with onchain logic.
.changeset/two-corners-tease.md Patch changeset for ensindexer reflecting the reverse-name registration handling update.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[LabelRegistered / LabelReserved event] --> B{isReservation?}
    B -- Yes --> C{existing registration?}
    C -- No --> G[Insert new Registration]
    C -- Yes --> D{isRegistrationFullyExpired?\nnow > expiry + grace}
    D -- Yes\nexpired --> G
    D -- No\nnot expired --> ERR1[Invariant Error:\nUnexpired registration found]
    B -- No --> E{existing registration?}
    E -- No --> G
    E -- Yes --> F{isReverseNameRegistration?\nlabel==address AND registrantId==label}
    F -- Yes\nreverse name --> G
    F -- No --> H{type != Reservation\nAND not fully expired?}
    H -- Yes --> ERR2[Invariant Error:\nUnexpired registration found]
    H -- No --> G
    G --> DONE[Upsert Domain / Insert Registration]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[LabelRegistered / LabelReserved event] --> B{isReservation?}
    B -- Yes --> C{existing registration?}
    C -- No --> G[Insert new Registration]
    C -- Yes --> D{isRegistrationFullyExpired?\nnow > expiry + grace}
    D -- Yes\nexpired --> G
    D -- No\nnot expired --> ERR1[Invariant Error:\nUnexpired registration found]
    B -- No --> E{existing registration?}
    E -- No --> G
    E -- Yes --> F{isReverseNameRegistration?\nlabel==address AND registrantId==label}
    F -- Yes\nreverse name --> G
    F -- No --> H{type != Reservation\nAND not fully expired?}
    H -- Yes --> ERR2[Invariant Error:\nUnexpired registration found]
    H -- No --> G
    G --> DONE[Upsert Domain / Insert Registration]
Loading

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

Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Outdated
Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Outdated
tk-o added 2 commits July 13, 2026 07:29
Update invariant logic to meet the relevant smart contract implementation which allows reverse name registration overrides.
…tionInGracePeriod` helpers with onchain logic.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Outdated
Comment thread .changeset/modern-groups-occur.md
Copilot AI review requested due to automatic review settings July 13, 2026 05:33
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io July 13, 2026 05:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io July 13, 2026 05:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io July 13, 2026 05:33 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 5 out of 5 changed files in this pull request and generated no new comments.

@tk-o tk-o marked this pull request as ready for review July 13, 2026 06:47
@tk-o tk-o requested a review from a team as a code owner July 13, 2026 06:47

@lightwalker-eth lightwalker-eth left a comment

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.

@tk-o Shared some suggestions. Please take the lead to merge when ready 👍

Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.ts
Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.ts
Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 10 out of 10 changed files in this pull request and generated 4 comments.

Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.ts Outdated
Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.ts Outdated
Comment thread packages/ensnode-sdk/src/registrars/registration-expiration.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts (1)

105-111: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Missing null guard for registration in the reservation branch causes false invariant failure.

When getLatestRegistration returns null (no existing registration) and isReservation is true, buildRegistrationExpiryInfo(null) produces { expiry: null, gracePeriod: null }. isRegistrationFullyExpired then returns false (no expiry = never expired), so !false evaluates to true and the error is thrown — even though there is no existing registration. This contradicts the error message itself ("expected none or expired") and would reject LabelReserved events for any previously-unregistered label, halting ENSv2 indexing.

Compare with BaseRegistrar.ts line 131 which correctly uses if (registration && !isFullyExpired).

🐛 Proposed fix: add `registration &&` guard
     if (isReservation) {
       // Invariant: if this is a Reservation, any existing Registration should be fully expired
-      if (!isRegistrationFullyExpired(registrationExpiryInfo, blockTimestamp)) {
+      if (registration && !isRegistrationFullyExpired(registrationExpiryInfo, blockTimestamp)) {
         throw new Error(
           `Invariant(ENSv2Registry:Label[Registered|Reserved]): Existing unexpired Registration found, expected none or expired.\n${toJson(registration, { pretty: true })}`,
         );
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts` around
lines 105 - 111, Update the reservation branch in ENSv2Registry to only evaluate
the expiry invariant when registration exists: guard the
isRegistrationFullyExpired(registrationExpiryInfo, blockTimestamp) check with
registration, while preserving the existing error for present but unexpired
registrations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts`:
- Around line 105-111: Update the reservation branch in ENSv2Registry to only
evaluate the expiry invariant when registration exists: guard the
isRegistrationFullyExpired(registrationExpiryInfo, blockTimestamp) check with
registration, while preserving the existing error for present but unexpired
registrations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 45832028-9cc5-4be6-9600-4cd75c441bc2

📥 Commits

Reviewing files that changed from the base of the PR and between c3a4db6 and 6969874.

📒 Files selected for processing (8)
  • apps/ensapi/src/omnigraph-api/context.ts
  • apps/ensapi/src/omnigraph-api/schema/registration.ts
  • apps/ensindexer/src/plugins/unigraph/handlers/ensv1/BaseRegistrar.ts
  • apps/ensindexer/src/plugins/unigraph/handlers/ensv1/NameWrapper.ts
  • apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ENSv2Registry.ts
  • apps/ensindexer/src/plugins/unigraph/handlers/ensv2/ETHRegistrar.ts
  • packages/ensnode-sdk/src/registrars/registration-expiration.test.ts
  • packages/ensnode-sdk/src/registrars/registration-expiration.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread .changeset/modern-groups-occur.md
Comment thread apps/ensindexer/src/plugins/unigraph/handlers/ensv1/NameWrapper.ts Outdated
Copilot AI review requested due to automatic review settings July 13, 2026 15:36
@tk-o tk-o force-pushed the hotfix/22f5cb7 branch from a74a09c to c3a4db6 Compare July 13, 2026 15:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 5 out of 5 changed files in this pull request and generated no new comments.

@tk-o tk-o merged commit 566cab2 into main Jul 13, 2026
33 checks passed
@tk-o tk-o deleted the hotfix/22f5cb7 branch July 13, 2026 15:50
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026
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.

3 participants