Skip to content

feat(ensindexer): per-chain end blocks via END_BLOCK_<chainId> - #2176

Merged
shrugs merged 8 commits into
mainfrom
feat/per-chain-end-blocks
Jun 19, 2026
Merged

feat(ensindexer): per-chain end blocks via END_BLOCK_<chainId>#2176
shrugs merged 8 commits into
mainfrom
feat/per-chain-end-blocks

Conversation

@shrugs

@shrugs shrugs commented May 21, 2026

Copy link
Copy Markdown
Member

Replace the global START_BLOCK/END_BLOCK indexing-range configuration with per-chain end blocks via END_BLOCK_<chainId> env vars (e.g. END_BLOCK_1, END_BLOCK_8453), mirroring the RPC_URL_<chainId> convention. They are parsed into config.chainEndBlocks: Map<ChainId, number>; each value constrains its own chain's indexing end block independently and MAY be set across any number of indexed chains.

The legacy global blockrange was restricted to single-chain indexing — per-chain end blocks remove that restriction. The new invariant_chainEndBlocks only checks that each END_BLOCK_<chainId> targets a chain an active plugin actually indexes. Per-chain ranges are resolved via blockrangeForChain() in ponder-helpers, and buildIndexedBlockranges (@ensnode/ensnode-sdk) now takes the per-chain end-block map instead of a single global end block, so indexing-status reaches omnichain-completed for bounded multichain runs.

This enables deterministic, reproducible multichain checkpoints where every indexed chain stops at a block corresponding to a shared timestamp.

@shrugs
shrugs requested a review from a team as a code owner May 21, 2026 21:09
Copilot AI review requested due to automatic review settings May 21, 2026 21:09
@vercel

vercel Bot commented May 21, 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 Jun 19, 2026 6:33pm
enskit-react-example.ensnode.io Ready Ready Preview, Comment Jun 19, 2026 6:33pm
ensnode.io Ready Ready Preview, Comment Jun 19, 2026 6:33pm
ensrainbow.io Ready Ready Preview, Comment Jun 19, 2026 6:33pm

@changeset-bot

changeset-bot Bot commented May 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4d83b62

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

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

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 May 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@shrugs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 14 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 09d04bae-f3d5-42a1-a13a-d65b52c87fb7

📥 Commits

Reviewing files that changed from the base of the PR and between a27c926 and 4d83b62.

📒 Files selected for processing (24)
  • .changeset/ensindexer-per-chain-end-blocks.md
  • .changeset/ensindexer-sdk-indexed-blockranges.md
  • apps/ensindexer/src/config/config.schema.ts
  • apps/ensindexer/src/config/config.test.ts
  • apps/ensindexer/src/config/environment.ts
  • apps/ensindexer/src/config/serialize.ts
  • apps/ensindexer/src/config/serialized-types.ts
  • apps/ensindexer/src/config/types.ts
  • apps/ensindexer/src/config/validations.ts
  • apps/ensindexer/src/lib/__test__/mockConfig.ts
  • apps/ensindexer/src/lib/local-ponder-client.ts
  • apps/ensindexer/src/lib/ponder-helpers.test.ts
  • apps/ensindexer/src/lib/ponder-helpers.ts
  • apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts
  • apps/ensindexer/src/plugins/registrars/plugin.ts
  • apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts
  • apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts
  • apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts
  • apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts
  • apps/ensindexer/src/plugins/tokenscope/plugin.ts
  • apps/ensindexer/src/plugins/unigraph/plugin.ts
  • apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts
  • packages/ensnode-sdk/src/shared/config/indexed-blockranges.test.ts
  • packages/ensnode-sdk/src/shared/config/indexed-blockranges.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/per-chain-end-blocks

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.

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

Adds support to ENSIndexer for per-chain indexing end blocks via END_BLOCK_<chainId> environment variables, enabling deterministic multi-chain checkpointing by constraining each chain independently (layered on top of the existing global START_BLOCK/END_BLOCK behavior).

Changes:

  • Introduces config.chainEndBlocks: Map<ChainId, number> parsed from END_BLOCK_<chainId> env vars, validated via a new invariant_chainEndBlocks.
  • Adds blockrangeForChain() and threads chainEndBlocks through chainConfigForContract() / mergedChainConfigForContracts() so plugin contract configs respect per-chain end blocks.
  • Updates plugins and tests to use the per-chain effective blockrange where needed (including Resolver configs built via constrainBlockrange(...)).

Reviewed changes

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

Show a summary per file
File Description
apps/ensindexer/src/plugins/unigraph/plugin.ts Passes chainEndBlocks into chain config helpers and applies per-chain blockrange when constraining Resolver ranges.
apps/ensindexer/src/plugins/tokenscope/plugin.ts Passes chainEndBlocks into chainConfigForContract for all configured contracts/chains.
apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts Threads chainEndBlocks into chain config helper calls for 3DNS contracts on multiple chains.
apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts Updates subgraph plugin contract configs to respect per-chain end blocks.
apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts Updates lineanames subgraph plugin configs to include per-chain end blocks.
apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts Updates basenames subgraph plugin configs to include per-chain end blocks.
apps/ensindexer/src/plugins/registrars/plugin.ts Threads chainEndBlocks into registrar-related contract config helpers, including merged registrar controller config.
apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts Passes chainEndBlocks into chain config helpers and applies per-chain blockrange to Resolver constraints.
apps/ensindexer/src/lib/ponder-helpers.ts Adds blockrangeForChain() and layers per-chain end blocks into chainConfigForContract and mergedChainConfigForContracts.
apps/ensindexer/src/lib/ponder-helpers.test.ts Adds focused unit tests for blockrangeForChain().
apps/ensindexer/src/config/validations.ts Adds invariant_chainEndBlocks to ensure each END_BLOCK_<chainId> corresponds to an indexed chain.
apps/ensindexer/src/config/types.ts Extends EnsIndexerConfig with chainEndBlocks and documents its semantics/invariants.
apps/ensindexer/src/config/serialized-types.ts Excludes chainEndBlocks from the serialized config type (since it’s a Map).
apps/ensindexer/src/config/environment.ts Extends the environment type to allow END_BLOCK_<number> keys.
apps/ensindexer/src/config/config.test.ts Adds config parsing/validation tests for END_BLOCK_<chainId> (multi-chain allowed, non-indexed chain rejected, negative/non-numeric rejected).
apps/ensindexer/src/config/config.schema.ts Implements buildChainEndBlocksFromEnv, adds schema support for chainEndBlocks, and wires in the new invariant.
.changeset/ensindexer-per-chain-end-blocks.md Changeset describing the new per-chain end block feature and semantics.

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

Comment thread apps/ensindexer/src/config/config.schema.ts Outdated
@greptile-apps

greptile-apps Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the single global START_BLOCK/END_BLOCK configuration with per-chain end blocks via END_BLOCK_<chainId> env vars (e.g. END_BLOCK_1, END_BLOCK_8453), stored as config.chainEndBlocks: Map<ChainId, number> and mirroring the existing RPC_URL_<chainId> convention.

  • buildChainEndBlocksFromEnv parses the dynamic env keys with a strict /^\d+$/ regex to reject lax inputs Number() would accept; a new invariant_chainEndBlocks replaces the old single-chain guard and verifies each configured chain ID is actually indexed.
  • blockrangeForChain in ponder-helpers is the single resolution point for per-chain end blocks, used by chainConfigForContract and mergedChainConfigForContracts; buildIndexedBlockranges in ensnode-sdk is updated to accept the per-chain map and cap contract ranges with Math.min so the derived indexed view stays consistent with the Ponder config path.
  • The Ponder build-ID injection serialises the Map to a key-sorted plain object (Maps serialise to {}) ensuring deterministic checksums regardless of env-var order; SerializedENSIndexerConfig now includes chainEndBlocks: Record<ChainIdString, number> so the active configuration is visible in config output.

Confidence Score: 5/5

Safe to merge — the change is a well-scoped replacement of a global config field with a per-chain map, validated at parse time and applied consistently across all plugin call sites.

Every contract call site is mechanically updated from globalBlockrange to chainEndBlocks. The new buildChainEndBlocksFromEnv function guards against lax numeric coercion, the invariant catches misconfigured chain IDs before indexing starts, and the Ponder build-ID serialisation correctly converts the Map to a sorted plain object. Both the Ponder config path and the SDK indexed-blockranges path produce consistent results, confirmed by new tests including the previously-missing case where a contract's explicit endBlock exceeds the chain end block.

No files require special attention.

Important Files Changed

Filename Overview
apps/ensindexer/src/config/config.schema.ts Replaces BlockrangeSchema/makeBlockNumberSchema/globalBlockrange with buildChainEndBlocksFromEnv and ChainEndBlocksSchema; manual regex validation guards against lax Number() parsing; wired correctly into the config build pipeline
apps/ensindexer/src/config/validations.ts invariant_globalBlockrange removed; new invariant_chainEndBlocks checks that each END_BLOCK_ key targets an actually-indexed chain; stale "in the future" wording from the old multi-chain error is gone
apps/ensindexer/src/config/environment.ts START_BLOCK/END_BLOCK removed; replaced with template-literal index signature mirroring the RPC_URL_ convention
apps/ensindexer/src/config/serialize.ts serializeChainEndBlocks helper added mirroring serializeRpcConfigs; wired into serializeRedactedENSIndexerConfig; globalBlockrange correctly removed from serialized output
apps/ensindexer/src/lib/ponder-helpers.ts New blockrangeForChain helper centralises per-chain end-block resolution; chainConfigForContract and mergedChainConfigForContracts signatures updated to accept chainEndBlocks map; all call sites updated consistently
packages/ensnode-sdk/src/shared/config/indexed-blockranges.ts buildIndexedBlockranges now accepts ReadonlyMap<ChainId,number> instead of a single globalBlockrangeEndBlock; effectiveEndBlock computed with Math.min to correctly cap contracts with explicit endBlocks beyond the chain endpoint
apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts chainEndBlocks Map serialised to a key-sorted plain object so it contributes to the Ponder build ID; sort ensures canonical checksum regardless of env-var order
apps/ensindexer/src/config/config.test.ts Test suite updated with comprehensive chainEndBlocks tests covering empty default, multi-chain parsing, non-indexed chain rejection, and all invalid-input formats

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["ENV: END_BLOCK_<chainId>=N"] --> B["buildChainEndBlocksFromEnv(env)\n/^\\d+$/ validation"]
    B --> C["Map<ChainId, number>\nchainEndBlocks"]
    C --> D["ENSIndexerConfigSchema.parse()\ninvariant_chainEndBlocks"]
    D -->|"chainId not indexed"| E["Error: no active plugin indexes chain N"]
    D -->|"all keys valid"| F["EnsIndexerConfig.chainEndBlocks"]
    F --> G["blockrangeForChain(chainEndBlocks, chainId)\nbuildBlockNumberRange(undefined, endBlock)"]
    G --> H["chainConfigForContract / mergedChainConfigForContracts"]
    H --> I["Ponder contract chain config\nstartBlock / endBlock"]
    F --> J["buildIndexedBlockranges(namespace, chainEndBlocks, plugins)\neffectiveEndBlock = Math.min(contract.endBlock, chainEndBlock)"]
    J --> K["Map<ChainId, BlockNumberRange>\nindexed blockranges for status"]
    F --> L["indexingBehaviorDependencies\nObject.fromEntries sorted to Ponder build ID"]
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["ENV: END_BLOCK_<chainId>=N"] --> B["buildChainEndBlocksFromEnv(env)\n/^\\d+$/ validation"]
    B --> C["Map<ChainId, number>\nchainEndBlocks"]
    C --> D["ENSIndexerConfigSchema.parse()\ninvariant_chainEndBlocks"]
    D -->|"chainId not indexed"| E["Error: no active plugin indexes chain N"]
    D -->|"all keys valid"| F["EnsIndexerConfig.chainEndBlocks"]
    F --> G["blockrangeForChain(chainEndBlocks, chainId)\nbuildBlockNumberRange(undefined, endBlock)"]
    G --> H["chainConfigForContract / mergedChainConfigForContracts"]
    H --> I["Ponder contract chain config\nstartBlock / endBlock"]
    F --> J["buildIndexedBlockranges(namespace, chainEndBlocks, plugins)\neffectiveEndBlock = Math.min(contract.endBlock, chainEndBlock)"]
    J --> K["Map<ChainId, BlockNumberRange>\nindexed blockranges for status"]
    F --> L["indexingBehaviorDependencies\nObject.fromEntries sorted to Ponder build ID"]
Loading

Reviews (7): Last reviewed commit: "fix(ensnode-sdk): cap contract end block..." | Re-trigger Greptile

Comment thread apps/ensindexer/src/config/serialized-types.ts
Replace the global START_BLOCK/END_BLOCK indexing-range config with per-chain end
blocks via END_BLOCK_<chainId> env vars (mirroring RPC_URL_<chainId>), parsed into
config.chainEndBlocks. Each constrains its chain's indexing end block independently
and may be set across any number of indexed chains.

blockrangeForChain() resolves the per-chain range; buildIndexedBlockranges
(@ensnode/ensnode-sdk) now takes a per-chain end-block map so indexing-status reaches
omnichain-completed for bounded multichain runs. Enables deterministic, reproducible
multichain checkpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

Comment thread apps/ensindexer/src/config/config.schema.ts Outdated
Reject lax Number() coercions in buildChainEndBlocksFromEnv ("", "1e2",
"0x10") via a base-10 integer regex. Add chainEndBlocks to the serialized
config so per-chain end blocks are visible in serialized/logged output,
mirroring rpcConfigs.

Addresses Copilot and Greptile review feedback on #2176.
@vercel
vercel Bot temporarily deployed to Preview – ensrainbow.io June 13, 2026 15:44 Inactive
@vercel
vercel Bot temporarily deployed to Preview – ensnode.io June 13, 2026 15:44 Inactive
@shrugs

shrugs commented Jun 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

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

Comment thread packages/ensnode-sdk/src/shared/config/indexed-blockranges.ts
Comment thread .changeset/ensindexer-per-chain-end-blocks.md
Comment thread apps/ensindexer/src/lib/ponder-helpers.ts
Comment thread apps/ensindexer/src/lib/ponder-helpers.ts
shrugs added 2 commits June 19, 2026 11:28
…chainId types

Address Copilot review on PR #2176:
- buildIndexedBlockranges now caps a contract's explicit endBlock at END_BLOCK_<chainId> (min, not fallback), mirroring constrainBlockrange on the Ponder path; add regression test
- chainConfigForContract/mergedChainConfigForContracts take ChainId instead of number
- add @ensnode/ensnode-sdk changeset for the buildIndexedBlockranges signature change
@shrugs

shrugs commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@greptile review

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