Skip to content

fix(ensindexer): make bridged-resolver subregistry write order-independent#2175

Merged
shrugs merged 1 commit into
mainfrom
fix/bridged-resolver-subregistry
May 21, 2026
Merged

fix(ensindexer): make bridged-resolver subregistry write order-independent#2175
shrugs merged 1 commit into
mainfrom
fix/bridged-resolver-subregistry

Conversation

@shrugs
Copy link
Copy Markdown
Member

@shrugs shrugs commented May 21, 2026

Summary

Completes the bridged-resolver canonicality fix (#2164) for the origin side. base.eth / linea.eth subregistryId was being left NULL, leaving the entire Basenames/Lineanames subtree non-canonical despite the target-side canonicalDomainId being set correctly.

Root cause — a cross-plugin handler-ordering hazard:

  • handleBridgedResolverChange read the "previous" resolver from the Domain-Resolver Relation (DRR).
  • Protocol Acceleration handles the same NewResolver log and overwrites the DRR with the new resolver.
  • Ponder gives two handlers on one log identical checkpoints (block, txIndex, eventType, logIndex — no source discriminator) and sorts with an inconsistent comparator (a.cp < b.cp ? -1 : 1), so the tie order is implementation-defined. In practice Protocol Acceleration's write landed first.
  • Result: prevResolver === nextResolver === the bridged resolver → the equality guard early-returned → subregistryId was never written.

Fix — derive the previous bridged target from the Domain's own subregistryId (via isBridgedTargetRegistry) instead of the PA-owned DRR. The helper now reads only state it owns, so it's independent of handler ordering. Verified against a captured live trace at base.eth's BasenamesL1Resolver event.

Also corrects the now-false ordering comments in register-handlers.ts and node-migration.ts: attach-order does not control Ponder dispatch order; same-log handlers can't be ordered deterministically, while cross-log ordering (NodeMigration) is safe by checkpoint.

@shrugs shrugs requested a review from a team as a code owner May 21, 2026 21:05
Copilot AI review requested due to automatic review settings May 21, 2026 21:05
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 21, 2026

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

Project Deployment Actions Updated (UTC)
enskit-react-example.ensnode.io Ready Ready Preview, Comment May 21, 2026 9:07pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
admin.ensnode.io Skipped Skipped May 21, 2026 9:07pm
ensnode.io Skipped Skipped May 21, 2026 9:07pm
ensrainbow.io Skipped Skipped May 21, 2026 9:07pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

⚠️ No Changeset found

Latest commit: 39660fe

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Review Change Stack

Warning

Rate limit exceeded

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

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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: ffd1e367-08da-47a8-8fc4-f8f134f34b26

📥 Commits

Reviewing files that changed from the base of the PR and between 44cd86a and 39660fe.

📒 Files selected for processing (3)
  • apps/ensindexer/ponder/src/register-handlers.ts
  • apps/ensindexer/src/lib/ensv2/canonicality-db-helpers.ts
  • apps/ensindexer/src/plugins/protocol-acceleration/handlers/node-migration.ts
📝 Walkthrough

Walkthrough

This PR fixes bridged-resolver canonicality by removing handler-ordering dependence. handleBridgedResolverChange now derives the previous bridged target from the Domain's own subregistryId instead of from cross-plugin data, and documentation is updated to clarify how handler dispatch is deterministic by Ponder checkpoint rather than registration order.

Changes

Bridged resolver canonicality fix

Layer / File(s) Summary
Bridged resolver canonicality fix in handleBridgedResolverChange
apps/ensindexer/src/lib/ensv2/canonicality-db-helpers.ts
handleBridgedResolverChange derives the Domain's previous bridged target from domain.subregistryId via isBridgedTargetRegistry instead of from domainResolverRelation, eliminating cross-plugin ordering dependence. Import statement and inline comment updated.
Handler ordering clarification
apps/ensindexer/ponder/src/register-handlers.ts, apps/ensindexer/src/plugins/protocol-acceleration/handlers/node-migration.ts
Documentation updated to remove the "REQUIRED ORDER" framing and explain that handler dispatch is deterministic by Ponder checkpoint, not by attach_*() call order, with tie-breaking rules for same-log vs. different-log handlers.
Patch release changelog
.changeset/bridged-resolver-subregistry-ordering.md
New changeset entry marks ensindexer for a patch release documenting the bridged-resolver canonicality fix.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • namehash/ensnode#2061: Introduces and manages bridged resolver canonicality invariants that are directly affected by the handleBridgedResolverChange fix in this PR.

Poem

🐰 Handler order no more—
subregistryId unlocks the door!
Canonicality flows so free,
checkpoint-ordered, decree by decree. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the main fix: making bridged-resolver subregistry writes independent of handler ordering, directly addressing the core issue resolved in this PR.
Description check ✅ Passed The PR description fully addresses the template requirements: Summary explains the fix clearly, Why section provides root cause and solution details with issue reference, and Testing section describes verification approach.
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.

✏️ 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 fix/bridged-resolver-subregistry

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.

…ndent

handleBridgedResolverChange read the previous resolver from the
Domain-Resolver Relation, which Protocol Acceleration overwrites for the
same NewResolver event. Two handlers on one log get identical Ponder
checkpoints with no deterministic tie-break, so the relation could already
hold the new resolver, the equality guard would early-return, and the
origin Domain's subregistryId was never set (base.eth/linea.eth subtree
left non-canonical).

Derive the previous bridged target from the Domain's own subregistryId
instead, so the helper no longer depends on cross-plugin handler ordering.
Correct the now-false ordering comments in register-handlers.ts and
node-migration.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shrugs shrugs force-pushed the fix/bridged-resolver-subregistry branch from 44cd86a to 39660fe Compare May 21, 2026 21:07
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 21, 2026 21:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 21, 2026 21:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 21, 2026 21:07 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 fixes a canonicality edge case in ensindexer where bridged-resolver origin domains (e.g. base.eth, linea.eth) could fail to get their Domain.subregistryId set due to same-log cross-plugin handler write ordering. The fix makes handleBridgedResolverChange derive the “previous bridged target” from the Domain’s own subregistryId (state it owns), removing dependence on the Protocol Acceleration Domain-Resolver Relation write timing.

Changes:

  • Update handleBridgedResolverChange to compute the previous bridged target from Domain.subregistryId via isBridgedTargetRegistry, instead of reading the Domain-Resolver Relation.
  • Correct ordering-related comments in handler registration and node-migration docs to reflect that same-log handler ordering can’t be relied on.
  • Add a Changeset entry documenting the patch fix.

Reviewed changes

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

File Description
apps/ensindexer/src/plugins/protocol-acceleration/handlers/node-migration.ts Updates documentation to clarify correctness relies on cross-log checkpoint ordering, not attach order.
apps/ensindexer/src/lib/ensv2/canonicality-db-helpers.ts Makes bridged-resolver subregistry reconciliation order-independent by reading prior state from Domain.subregistryId.
apps/ensindexer/ponder/src/register-handlers.ts Updates comments to warn against relying on same-log handler ordering and explains the new independence model.
.changeset/bridged-resolver-subregistry-ordering.md Adds a patch Changeset describing the canonicality fix and why it’s needed.

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

Comment thread apps/ensindexer/src/lib/ensv2/canonicality-db-helpers.ts
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 21, 2026

Greptile Summary

This PR fixes a handler-ordering bug where base.eth/linea.eth subregistryId was left NULL, keeping the entire Basenames/Lineanames subtree non-canonical. The root cause was that handleBridgedResolverChange read the "previous" resolver from the Domain-Resolver Relation (DRR), which Protocol Acceleration could overwrite first on the same log event due to Ponder's non-deterministic same-checkpoint tie-breaking.

  • Core fix: handleBridgedResolverChange now derives the previous bridged state from Domain.subregistryId (a field the helper itself owns) via isBridgedTargetRegistry, eliminating the cross-plugin read-before-write dependency on the DRR.
  • Comment corrections: Updates misleading ordering comments in register-handlers.ts and node-migration.ts to accurately document that same-log handler ordering is non-deterministic, while cross-log ordering (NodeMigration's ENSv1Registry:NewOwner vs ENSv1RegistryOld:*) remains safe by checkpoint.

Confidence Score: 5/5

The change is safe to merge; the logic fix is minimal, well-reasoned, and verified against a live trace.

The fix replaces a fragile DRR read with a read of the field the helper itself writes, a narrowly scoped change with a clear correctness argument. The two other files are comment-only. The invariant the fix relies on — that Domain.subregistryId on a bridge origin is exclusively owned by handleBridgedResolverChange — is explicitly documented in the SDK and enforced by the existing isBridgedOriginDomain guard used elsewhere in the unigraph plugin.

No files require special attention; all changes are either the targeted one-function fix or documentation corrections.

Important Files Changed

Filename Overview
apps/ensindexer/src/lib/ensv2/canonicality-db-helpers.ts Core fix: replaces DRR-based prevResolver lookup with Domain.subregistryId + isBridgedTargetRegistry to make handleBridgedResolverChange order-independent of Protocol Acceleration's same-event writes
apps/ensindexer/ponder/src/register-handlers.ts Comment-only update replacing misleading REQUIRED ORDER note with accurate explanation of Ponder's checkpoint-based dispatch and its non-deterministic same-log tie-break
apps/ensindexer/src/plugins/protocol-acceleration/handlers/node-migration.ts Comment-only update clarifying that NodeMigration correctness relies on cross-log checkpoint ordering (ENSv1Registry:NewOwner vs ENSv1RegistryOld:*), not handler registration order

Sequence Diagram

sequenceDiagram
    participant Ponder
    participant Unigraph as Unigraph Handler<br/>(handleBridgedResolverChange)
    participant PA as Protocol Acceleration<br/>(NewResolver handler)
    participant DB as Database

    Note over Ponder: Same NewResolver log — identical checkpoints<br/>Tie-break order is non-deterministic

    rect rgb(255, 220, 220)
        Note over Ponder,DB: BEFORE fix (PA wins tie-break — bug)
        Ponder->>PA: dispatch NewResolver (base.eth — BasenamesL1Resolver)
        PA->>DB: "write DRR: resolver = BasenamesL1Resolver"
        Ponder->>Unigraph: dispatch NewResolver
        Unigraph->>DB: "read DRR — prevResolver = BasenamesL1Resolver (PA already wrote it)"
        Note over Unigraph: prevBridged.targetRegistryId == nextBridged.targetRegistryId — early return, subregistryId never written
    end

    rect rgb(220, 255, 220)
        Note over Ponder,DB: AFTER fix (order-independent)
        Ponder->>PA: dispatch NewResolver
        PA->>DB: "write DRR: resolver = BasenamesL1Resolver"
        Ponder->>Unigraph: dispatch NewResolver
        Unigraph->>DB: read Domain.subregistryId — null (no prior bridged target)
        Note over Unigraph: prevBridged = null, nextBridged = Basenames target — handleSubregistryUpdated called
        Unigraph->>DB: "write Domain.subregistryId = Basenames target registry"
        Note over DB: base.eth canonical edge established
    end
Loading

Reviews (1): Last reviewed commit: "fix(ensindexer): make bridged-resolver s..." | Re-trigger Greptile

@shrugs shrugs merged commit 49400ce into main May 21, 2026
20 checks passed
@shrugs shrugs deleted the fix/bridged-resolver-subregistry branch May 21, 2026 21:20
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.

2 participants