Skip to content

fix(ensindexer): reject ENSRainbow heals that don't hash back to the requested labelHash#2181

Merged
shrugs merged 10 commits into
mainfrom
hotfix/bad-ensrainbow-heal
May 22, 2026
Merged

fix(ensindexer): reject ENSRainbow heals that don't hash back to the requested labelHash#2181
shrugs merged 10 commits into
mainfrom
hotfix/bad-ensrainbow-heal

Conversation

@shrugs
Copy link
Copy Markdown
Member

@shrugs shrugs commented May 22, 2026

what

EnsRainbowApiClient.heal() now verifies a healed label hashes back to the requested labelHash (compared against the client's normalized labelHash). A mismatch — a malformed rainbow record — is returned as NotFound, so consumers fall back gracefully via the existing unhealable path instead of keying data under the wrong labelHash.

why

A v1.14.0-alpha reindex crashed at ~78.6% backfill on unigraph/ENSv1Registry:NewOwner (chain 1, block 15546441):

Invariant(ensureDomainInRegistry): Label '0x00677002…' must exist before linking Domain '…'.

Root cause — a malformed ENSRainbow record:

  • The event registers "007".eth — the on-chain label is the literal 5-char string "007" (quotes included), and keccak("007"-with-quotes) == 0x00677002….
  • The alpha's ENSRainbow label set heals 0x00677002… to 007 (quotes stripped — " is the CSV quote char, so a CSV/SQL ingest step mangled the label while preserving the original labelHash key). ENSRainbow re-validates neither at ingest nor at heal time.
  • ensureUnknownLabelensureLabel re-keys on the healed label, writing the row under keccak("007") = 0xadafce… instead of 0x00677002…. The requested labelHash is never persisted.
  • ensureDomainInRegistry sees the .eth virtual registry is canonical, does find(label, { labelHash: 0x00677002… }), gets null, and throws — a fail-fast that kills the indexer.

notes

  • The upstream label set should also be rebuilt with CSV-safe label handling so "007" and similar quoted/special-char labels are recoverable rather than silently dropped to Encoded LabelHashes.

🤖 Generated with Claude Code

…requested labelHash

A poisoned rainbow record (a label whose bytes were mangled while its
labelHash key was preserved — e.g. CSV processing stripping the quotes of
`"007"` to `007`) heals to a label whose labelhash differs from the request.
`ensureUnknownLabel`/`ensureLabel` re-key on the healed label, so the row
was written under the wrong primary key and the requested labelHash was
never persisted — tripping the canonical-name materialization invariant in
`ensureDomainInRegistry` and terminating the indexer.

`labelByLabelHash` now verifies `labelhashLiteralLabel(label) === labelHash`
(mirroring graph-node's `ens.nameByHash`) and treats a mismatch as
unhealable, logging the bad record. The label then falls back to an Encoded
LabelHash keyed by the requested labelHash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shrugs shrugs requested a review from a team as a code owner May 22, 2026 14:57
Copilot AI review requested due to automatic review settings May 22, 2026 14:57
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 22, 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 May 22, 2026 4:50pm
enskit-react-example.ensnode.io Ready Ready Preview, Comment May 22, 2026 4:50pm
ensnode.io Ready Ready Preview, Comment May 22, 2026 4:50pm
ensrainbow.io Ready Ready Preview, Comment May 22, 2026 4:50pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 22, 2026

🦋 Changeset detected

Latest commit: b963025

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

This PR includes changesets to release 22 packages
Name Type
@ensnode/ensrainbow-sdk Patch
ensindexer Patch
ensrainbow Patch
ensadmin Patch
ensapi Patch
fallback-ensapi Patch
enssdk Patch
enscli Patch
enskit Patch
ensskills Patch
@ensnode/datasources Patch
@ensnode/ensdb-sdk Patch
@ensnode/ensnode-sdk Patch
@ensnode/integration-test-env Patch
@ensnode/ponder-sdk Patch
@ensnode/ponder-subgraph Patch
@ensnode/shared-configs Patch
@docs/ensnode Patch
@docs/ensrainbow Patch
@namehash/ens-referrals Patch
@namehash/namehash-ui 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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 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 7 minutes and 26 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: b68b5319-be33-4c9e-83fb-d38fc8ce41e5

📥 Commits

Reviewing files that changed from the base of the PR and between 9135c6a and b963025.

📒 Files selected for processing (2)
  • .changeset/reject-malformed-rainbow-heals.md
  • apps/ensindexer/src/lib/graphnode-helpers.test.ts
📝 Walkthrough

Walkthrough

The PR adds validation to the ENS rainbow SDK's heal() method: when a heal succeeds, the returned label is re-hashed and compared against the originally requested normalized labelHash. Mismatches trigger a Label not found error. Tests verify this behavior and adapt to consume the new validation.

Changes

Malformed rainbow heal validation

Layer / File(s) Summary
Heal validation logic and test
packages/ensrainbow-sdk/src/client.ts, packages/ensrainbow-sdk/src/client.test.ts
EnsRainbowApiClient.heal() imports additional label-hashing helpers and adds post-response validation: re-hashes the returned label and compares it to the requested normalized labelHash; on mismatch, downgrades to a NotFound error. A new test case verifies that a quote-stripped label (mismatched hash) returns Label not found with error code 404.
Indexer test updates
apps/ensindexer/src/lib/graphnode-helpers.test.ts
Imports label-hashing helpers, introduces a getTestLabel() generator to produce unique labels per test and reduce caching interference, and updates normalization and retry tests to use generated labels while verifying the retry logic still succeeds with the new validation.
Release documentation
.changeset/reject-malformed-rainbow-heals.md
Changeset entry documents a patch version bump for @ensnode/ensrainbow-sdk and describes the new handling of malformed rainbow records where mismatched healed label hashes are rejected as unhealable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • namehash/ensnode#1688: Both PRs modify EnsRainbowApiClient.heal() in packages/ensrainbow-sdk/src/client.ts around labelHash handling—feat: add labelhash parsing and normalization utilities #1688 adds normalization/parsing via parseLabelHashOrEncodedLabelHash, while this PR adds post-heal validation by re-hashing the returned label and downgrading inconsistent heals to Label not found.
  • namehash/ensnode#1735: This PR adds ENSRainbow heal() post-validation that turns malformed healed labels into HealNotFound/404 errors, while #1735 adds/adjusts heal() retry logic and related tests in the indexer—both are coupled around heal() error/behavior handling and retry-related test expectations.

Poem

🐰 A rainbow heal that's true,
With hashes checked through and through,
Quote-stripped labels now say "no,"
Mismatches face the 404 flow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: validating that ENSRainbow healed labels hash back to the requested labelHash, with a specific focus on the fix's scope (ensindexer).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description check ✅ Passed The PR description covers all required sections: Summary (concise bullet), Why (with detailed root cause and context), Testing (implicit in raw_summary), and Notes for Reviewer. The description is complete and informative.

✏️ 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 hotfix/bad-ensrainbow-heal

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

Guards EnsRainbowApiClient.heal() against malformed ENSRainbow records by verifying the healed label's keccak256 matches the canonical normalizedLabelHash before returning it; mismatches are rewritten to HealNotFoundError so callers fall back via the existing unhealable path. The fix directly addresses a real indexer crash caused by a CSV-ingestion bug that stripped quotes from label "007", associating the wrong label with the stored labelHash.

  • client.ts adds a post-fetch sanity check comparing labelhashLiteralLabel(asLiteralLabel(label)) against the already-normalized normalizedLabelHash; the comparison is between two canonical lowercase hex strings, so it is safe.
  • Tests in both client.test.ts and graphnode-helpers.test.ts are updated to use labels whose keccak256 actually matches the provided labelHash, and a getTestLabel() helper ensures LRU cache isolation between retry-path tests.

Confidence Score: 5/5

Safe to merge; the integrity check is a narrow, well-tested addition that only changes behavior for records the server was already returning incorrectly.

The fix is tightly scoped: a single guard in client.ts comparing two canonical lowercase hex strings, gated behind an existing success status check. The comparison uses normalizedLabelHash (output of parseLabelHashOrEncodedLabelHash) so casing and padding edge cases are already handled. The rewritten HealNotFoundError is cacheable, matching existing NotFound caching behavior. Tests are correctly updated to supply labels whose keccak256 matches the requested hash.

No files require special attention.

Important Files Changed

Filename Overview
packages/ensrainbow-sdk/src/client.ts Adds a post-fetch integrity check that rejects healed labels whose keccak256 doesn't match the normalized requested labelHash, treating them as NotFound; comparison is against the already-canonicalized normalizedLabelHash so casing/padding edge cases are handled.
packages/ensrainbow-sdk/src/client.test.ts Adds a targeted test for the CSV-mangled "007" scenario, verifying the client returns HealNotFoundError when the healed label does not hash back to the requested labelHash.
apps/ensindexer/src/lib/graphnode-helpers.test.ts Updates existing tests to use labels whose keccak256 actually matches the provided labelHash (required now that the client enforces heal integrity), and introduces getTestLabel() to produce unique labels across tests so the LRU cache doesn't suppress fetch calls.
.changeset/reject-malformed-rainbow-heals.md Changeset entry marking this as a patch version bump for @ensnode/ensrainbow-sdk.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["heal(labelHash)"] --> B["parseLabelHashOrEncodedLabelHash\nnormalizedLabelHash"]
    B --> C{Cache hit?}
    C -- Yes --> D[Return cached response]
    C -- No --> E[fetch /v1/heal/normalizedLabelHash]
    E --> F{status == Success?}
    F -- No --> G[Return error as-is]
    F -- Yes --> H{"labelhash(label)\n== normalizedLabelHash?"}
    H -- Yes --> I[Return HealSuccess]
    H -- No --> J["Rewrite as HealNotFoundError\n(malformed record)"]
    I --> K{isCacheable?}
    J --> K
    G --> K
    K -- Yes --> L[cache.set]
    K -- No --> M[Return uncached]
    L --> N[Return response]
Loading

Reviews (12): Last reviewed commit: "fix: changeset clarity" | Re-trigger Greptile

Comment thread apps/ensindexer/src/lib/graphnode-helpers.ts Outdated
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 hardens ENSIndexer’s ENSRainbow label healing path by rejecting “poisoned” heals where the returned label does not hash back to the requested labelHash, preventing downstream invariants from crashing the indexer and ensuring the fallback path stores an Encoded LabelHash under the correct primary key.

Changes:

  • Add a post-heal verification in labelByLabelHash to ensure keccak(label) === requested labelHash; mismatches are logged and treated as unhealable (null).
  • Update labelByLabelHash tests to cover the poisoned-record scenario and adjust retry success fixtures to satisfy the new hash-back requirement.
  • Add a Changeset documenting the patch-level behavior change for ensindexer.

Reviewed changes

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

File Description
apps/ensindexer/src/lib/graphnode-helpers.ts Add hash-back validation for healed labels and log+return null on mismatch.
apps/ensindexer/src/lib/graphnode-helpers.test.ts Update tests to cover poisoned heal rejection and align retry success cases with new validation.
.changeset/reject-poisoned-rainbow-heals.md Publish a patch Changeset describing the robustness fix and its motivation.

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

Comment thread apps/ensindexer/src/lib/graphnode-helpers.ts Outdated
…shes, "malformed" wording

- retry tests use ordinary labels (nick/alice) instead of misleading "networkretry"/"servererror"
- tests derive the labelHash via labelhashLiteralLabel instead of hardcoding hex
- rename "poisoned" → "malformed"/"inconsistent" in docs/comments/log + changeset
- concise changeset wording

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread apps/ensindexer/src/lib/graphnode-helpers.ts Outdated
Per review, the validation that a healed label hashes back to the requested
labelHash now lives in EnsRainbowApiClient.heal() instead of
labelByLabelHash. The client compares against its own normalized labelHash
(parseLabelHashOrEncodedLabelHash), so non-canonical-but-valid inputs are no
longer falsely rejected — addressing the Greptile/Copilot/Vercel notes. A
malformed record is returned as NotFound (404) so consumers fall back
gracefully via the existing unhealable path.

- graphnode-helpers.ts/.test.ts reverted to base; 3 retry/normalization test
  fixtures made hash-consistent (they exercise the real client, which now
  rejects inconsistent heals)
- new malformed-record test in ensrainbow-sdk client.test.ts
- changeset retargeted to @ensnode/ensrainbow-sdk

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented May 22, 2026

@greptile review

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

Comment thread packages/ensrainbow-sdk/src/client.ts Outdated
Comment thread .changeset/reject-malformed-rainbow-heals.md Outdated
Comment thread .changeset/reject-malformed-rainbow-heals.md Outdated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 16:04
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 22, 2026 16:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 22, 2026 16:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 22, 2026 16:04 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 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread packages/ensrainbow-sdk/src/client.ts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 22, 2026 16:14 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 22, 2026 16:14 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 22, 2026 16:15 Inactive
… (loop)

Converts all success/null result assertions to the await expect(...).resolves
form (matching the file's existing .rejects throw-tests), per CodeRabbit and
repo convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 16:38
@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 22, 2026 16:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 22, 2026 16:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 22, 2026 16:38 Inactive
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented May 22, 2026

@greptile review

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

Comment thread packages/ensrainbow-sdk/src/client.ts
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