From 42ab384ac1f647821a65a54c2d2969729016b184 Mon Sep 17 00:00:00 2001 From: Nejc Drobnic Date: Fri, 29 May 2026 16:37:01 +0200 Subject: [PATCH 1/6] feat(ensindexer): parse per-chain ETH_GET_LOGS_BLOCK_RANGE env config Add an ENSIndexer-local mechanism, parallel to RPC_URL_, that parses optional per-chain ETH_GET_LOGS_BLOCK_RANGE_ environment variables into config.ethGetLogsBlockRanges (Map), validated as positive integers and surfaced in the redacted config serialization. Kept separate from the shared SDK RpcConfig (used by ENSApi, which runs no Ponder). Not yet consumed by the Ponder config; that follows in a later commit. --- apps/ensindexer/src/config/config.schema.ts | 9 ++- apps/ensindexer/src/config/config.test.ts | 18 ++++++ apps/ensindexer/src/config/environment.ts | 14 ++++- .../config/eth-get-logs-block-ranges.test.ts | 43 +++++++++++++ .../src/config/eth-get-logs-block-ranges.ts | 61 +++++++++++++++++++ apps/ensindexer/src/config/serialize.ts | 16 +++++ .../ensindexer/src/config/serialized-types.ts | 10 ++- apps/ensindexer/src/config/types.ts | 25 ++++++++ 8 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts create mode 100644 apps/ensindexer/src/config/eth-get-logs-block-ranges.ts diff --git a/apps/ensindexer/src/config/config.schema.ts b/apps/ensindexer/src/config/config.schema.ts index a72fc1b3a5..b300982bb9 100644 --- a/apps/ensindexer/src/config/config.schema.ts +++ b/apps/ensindexer/src/config/config.schema.ts @@ -17,6 +17,10 @@ import type { ENSIndexerEnvironment } from "@/config/environment"; import { applyDefaults, EnvironmentDefaults } from "@/config/environment-defaults"; import { derive_indexedChainIds } from "./derived-params"; +import { + buildEthGetLogsBlockRangesFromEnv, + EthGetLogsBlockRangesSchema, +} from "./eth-get-logs-block-ranges"; import type { EnsIndexerConfig } from "./types"; import { invariant_globalBlockrange, @@ -89,6 +93,7 @@ const IsSubgraphCompatibleSchema = const ENSIndexerConfigSchema = z .object({ rpcConfigs: RpcConfigsSchema, + ethGetLogsBlockRanges: EthGetLogsBlockRangesSchema, namespace: ENSNamespaceSchema, plugins: PluginsSchema, @@ -163,14 +168,16 @@ export function buildConfigFromEnvironment(_env: ENSIndexerEnvironment): EnsInde // apply the partial defaults to the provided env const env = applyDefaults(_env, environmentDefaults); - // and use that to generate rpcConfigs + // and use that to build the per-chain rpcConfigs and eth_getLogs block ranges const namespace = ENSNamespaceSchema.parse(env.NAMESPACE); const rpcConfigs = buildRpcConfigsFromEnv(env, namespace); + const ethGetLogsBlockRanges = buildEthGetLogsBlockRangesFromEnv(env, namespace); // parse/validate with ENSIndexerConfigSchema return ENSIndexerConfigSchema.parse({ namespace: env.NAMESPACE, rpcConfigs, + ethGetLogsBlockRanges, plugins: env.PLUGINS, isSubgraphCompatible: env.SUBGRAPH_COMPAT, diff --git a/apps/ensindexer/src/config/config.test.ts b/apps/ensindexer/src/config/config.test.ts index 7bd1e3aff1..a6989f96fc 100644 --- a/apps/ensindexer/src/config/config.test.ts +++ b/apps/ensindexer/src/config/config.test.ts @@ -299,6 +299,24 @@ describe("config (with base env)", () => { }); }); + describe(".ethGetLogsBlockRanges", () => { + it("defaults to an empty Map when no ETH_GET_LOGS_BLOCK_RANGE_* var is set", async () => { + const config = await getConfig(); + expect(config.ethGetLogsBlockRanges).toStrictEqual(new Map()); + }); + + it("includes a chain's configured eth_getLogs block range", async () => { + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE_1", "1000"); + const config = await getConfig(); + expect(config.ethGetLogsBlockRanges).toStrictEqual(new Map([[1, 1000]])); + }); + + it("throws if a configured eth_getLogs block range is not a positive integer", async () => { + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE_1", "abc"); + await expect(getConfig()).rejects.toThrow(/positive integer/i); + }); + }); + describe(".chains", () => { it("returns the chains if it is a valid object (one HTTP protocol URL)", async () => { vi.stubEnv("RPC_URL_1", VALID_RPC_URL); diff --git a/apps/ensindexer/src/config/environment.ts b/apps/ensindexer/src/config/environment.ts index a764c271c5..2c8ec60e05 100644 --- a/apps/ensindexer/src/config/environment.ts +++ b/apps/ensindexer/src/config/environment.ts @@ -1,5 +1,16 @@ import type { EnsDbEnvironment, RpcEnvironment } from "@ensnode/ensnode-sdk/internal"; +/** + * Environment variables for per-chain `eth_getLogs` block range overrides. + * + * Each `ETH_GET_LOGS_BLOCK_RANGE_${chainId}`, if specified, overrides Ponder's auto-determined + * maximum `eth_getLogs` block range for that chain. + * @see https://ponder.sh/docs/config/chains#eth_getlogs-block-range + */ +export interface EthGetLogsBlockRangeEnvironment { + [x: `ETH_GET_LOGS_BLOCK_RANGE_${number}`]: string | undefined; +} + /** * Represents the raw, unvalidated environment variables for the ENSIndexer application. * @@ -8,7 +19,8 @@ import type { EnsDbEnvironment, RpcEnvironment } from "@ensnode/ensnode-sdk/inte * mapped/parsed into a structured configuration object like `ENSIndexerConfig`. */ export type ENSIndexerEnvironment = EnsDbEnvironment & - RpcEnvironment & { + RpcEnvironment & + EthGetLogsBlockRangeEnvironment & { NAMESPACE?: string; PLUGINS?: string; SUBGRAPH_COMPAT?: string; diff --git a/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts b/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts new file mode 100644 index 0000000000..e86a93878b --- /dev/null +++ b/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; + +import { + buildEthGetLogsBlockRangesFromEnv, + EthGetLogsBlockRangesSchema, +} from "./eth-get-logs-block-ranges"; + +describe("buildEthGetLogsBlockRangesFromEnv", () => { + it("returns an empty record when no ETH_GET_LOGS_BLOCK_RANGE_* vars are set", () => { + expect(buildEthGetLogsBlockRangesFromEnv({}, "mainnet")).toStrictEqual({}); + }); + + it("collects ETH_GET_LOGS_BLOCK_RANGE_ for chains in the namespace", () => { + expect( + buildEthGetLogsBlockRangesFromEnv({ ETH_GET_LOGS_BLOCK_RANGE_1: "1000" }, "mainnet"), + ).toStrictEqual({ "1": "1000" }); + }); + + it("ignores ETH_GET_LOGS_BLOCK_RANGE_ for chains outside the namespace", () => { + expect( + buildEthGetLogsBlockRangesFromEnv({ ETH_GET_LOGS_BLOCK_RANGE_999999: "1000" }, "mainnet"), + ).toStrictEqual({}); + }); +}); + +describe("EthGetLogsBlockRangesSchema", () => { + it("parses an empty record to an empty Map", () => { + expect(EthGetLogsBlockRangesSchema.parse({})).toStrictEqual(new Map()); + }); + + it("parses chain-id strings to numeric block ranges", () => { + expect(EthGetLogsBlockRangesSchema.parse({ "1": "1000", "8453": "500" })).toStrictEqual( + new Map([ + [1, 1000], + [8453, 500], + ]), + ); + }); + + it.each(["abc", "0", "-5", "1.5"])("rejects invalid block range %j", (value) => { + expect(() => EthGetLogsBlockRangesSchema.parse({ "1": value })).toThrow(); + }); +}); diff --git a/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts b/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts new file mode 100644 index 0000000000..bdf3c3bad6 --- /dev/null +++ b/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts @@ -0,0 +1,61 @@ +import type { ChainId, ChainIdString } from "enssdk"; +import { z } from "zod/v4"; + +import { type Datasource, type ENSNamespaceId, getENSNamespace } from "@ensnode/datasources"; +import { deserializeChainId, serializeChainId } from "@ensnode/ensnode-sdk"; +import { makeChainIdStringSchema } from "@ensnode/ensnode-sdk/internal"; + +import type { EthGetLogsBlockRangeEnvironment } from "@/config/environment"; + +/** + * Builds the raw, per-chain `eth_getLogs` block range overrides from the environment, scoped to the + * chain IDs that appear in the specified `namespace`. + * + * Mirrors `buildRpcConfigsFromEnv`: for each chain in the namespace, reads the optional + * `ETH_GET_LOGS_BLOCK_RANGE_${chainId}` environment variable. Variables for chains outside the + * namespace are ignored (same behavior as `RPC_URL_*`). + * + * NOTE: returns raw (unvalidated) string values; validation happens in {@link EthGetLogsBlockRangesSchema}. + */ +export function buildEthGetLogsBlockRangesFromEnv( + env: EthGetLogsBlockRangeEnvironment, + namespace: ENSNamespaceId, +): Record { + const chainsInNamespace = Object.entries(getENSNamespace(namespace)).map( + ([, datasource]) => (datasource as Datasource).chain, + ); + + const ethGetLogsBlockRanges: Record = {}; + + for (const chain of chainsInNamespace) { + const value = env[`ETH_GET_LOGS_BLOCK_RANGE_${chain.id}`]; + if (value !== undefined && value !== "") { + ethGetLogsBlockRanges[serializeChainId(chain.id)] = value; + } + } + + return ethGetLogsBlockRanges; +} + +const EthGetLogsBlockRangeValueSchema = z.coerce + .number({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }) + .int({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }) + .min(1, { error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }); + +/** + * Parses the raw per-chain `eth_getLogs` block range overrides into a `Map`. + */ +export const EthGetLogsBlockRangesSchema = z + .record(makeChainIdStringSchema("ETH_GET_LOGS_BLOCK_RANGE"), EthGetLogsBlockRangeValueSchema, { + error: + "ETH_GET_LOGS_BLOCK_RANGE configuration must be an object mapping valid chain IDs to positive integers.", + }) + .transform((records) => { + const ethGetLogsBlockRanges = new Map(); + + for (const [chainIdString, blockRange] of Object.entries(records)) { + ethGetLogsBlockRanges.set(deserializeChainId(chainIdString), blockRange); + } + + return ethGetLogsBlockRanges; + }); diff --git a/apps/ensindexer/src/config/serialize.ts b/apps/ensindexer/src/config/serialize.ts index 563970289e..7d3ea24126 100644 --- a/apps/ensindexer/src/config/serialize.ts +++ b/apps/ensindexer/src/config/serialize.ts @@ -30,6 +30,21 @@ function serializeRpcConfigs( return serializedRpcConfigs; } +/** + * Serialize eth_getLogs block ranges {@link ENSIndexerConfig.ethGetLogsBlockRanges}. + */ +function serializeEthGetLogsBlockRanges( + ethGetLogsBlockRanges: ENSIndexerConfig["ethGetLogsBlockRanges"], +): SerializedENSIndexerConfig["ethGetLogsBlockRanges"] { + const serializedEthGetLogsBlockRanges: SerializedENSIndexerConfig["ethGetLogsBlockRanges"] = {}; + + for (const [chainId, blockRange] of ethGetLogsBlockRanges.entries()) { + serializedEthGetLogsBlockRanges[serializeChainId(chainId)] = blockRange; + } + + return serializedEthGetLogsBlockRanges; +} + /** * Serialize redacted {@link ENSIndexerConfig} object. * @@ -51,5 +66,6 @@ export function serializeRedactedENSIndexerConfig( namespace: redactedConfig.namespace, plugins: redactedConfig.plugins, rpcConfigs: serializeRpcConfigs(redactedConfig.rpcConfigs), + ethGetLogsBlockRanges: serializeEthGetLogsBlockRanges(redactedConfig.ethGetLogsBlockRanges), } satisfies SerializedENSIndexerConfig; } diff --git a/apps/ensindexer/src/config/serialized-types.ts b/apps/ensindexer/src/config/serialized-types.ts index 167b4d572e..4f5d29deb3 100644 --- a/apps/ensindexer/src/config/serialized-types.ts +++ b/apps/ensindexer/src/config/serialized-types.ts @@ -26,7 +26,10 @@ export interface SerializedRpcConfig extends Omit { + extends Omit< + ENSIndexerConfig, + "ensRainbowUrl" | "indexedChainIds" | "rpcConfigs" | "ethGetLogsBlockRanges" | "plugins" + > { /** * Serialized representation of {@link ENSIndexerConfig.ensRainbowUrl}. */ @@ -47,6 +50,11 @@ export interface SerializedENSIndexerConfig */ rpcConfigs: Record; + /** + * Serialized representation of {@link ENSIndexerConfig.ethGetLogsBlockRanges}. + */ + ethGetLogsBlockRanges: Record; + /** * Serialized representation of {@link ENSIndexerConfig.plugins}. * diff --git a/apps/ensindexer/src/config/types.ts b/apps/ensindexer/src/config/types.ts index c41b68b951..7ee73a400d 100644 --- a/apps/ensindexer/src/config/types.ts +++ b/apps/ensindexer/src/config/types.ts @@ -6,6 +6,13 @@ import type { BlockNumberRange, PluginName } from "@ensnode/ensnode-sdk"; import { RpcConfig, type RpcConfigs } from "@ensnode/ensnode-sdk/internal"; import type { EnsRainbowClientLabelSet } from "@ensnode/ensrainbow-sdk"; +/** + * Per-chain override of Ponder's maximum `eth_getLogs` block range, keyed by chain id. + * + * @invariant Each value is a positive integer. + */ +export type EthGetLogsBlockRanges = Map; + /** * The complete runtime configuration for an ENSIndexer instance. */ @@ -104,6 +111,24 @@ export interface EnsIndexerConfig { */ rpcConfigs: RpcConfigs; + /** + * Optional per-chain override of Ponder's maximum `eth_getLogs` block range, keyed by chain id + * (configured via the `ETH_GET_LOGS_BLOCK_RANGE_` environment variable). + * + * Ponder auto-determines a safe `eth_getLogs` block range per chain; this lets operators set a + * manual cap for RPC providers that reject Ponder's default range. + * @see https://ponder.sh/docs/config/chains#eth_getlogs-block-range + * + * Like {@link rpcConfigs}, this is a performance/connection tuning knob only: it does NOT affect + * indexed data and is intentionally excluded from the indexing-behavior Build ID. Changing it does + * not trigger a re-index. + * + * Invariants: + * - Keys are a subset of the chains in the configured {@link namespace}. Chains without an entry + * use Ponder's auto-determined range. + */ + ethGetLogsBlockRanges: EthGetLogsBlockRanges; + /** * Indexed Chain IDs * From 91cebf8b3aa4bcf54056529ea032174193993ccd Mon Sep 17 00:00:00 2001 From: Nejc Drobnic Date: Fri, 29 May 2026 16:37:13 +0200 Subject: [PATCH 2/6] feat(ensindexer): apply per-chain ethGetLogsBlockRange to Ponder chains Thread config.ethGetLogsBlockRanges into the Ponder chains config. The two chain-config helpers now take an EnsIndexerConfig slice instead of just rpcConfigs, and chainsConnectionConfig sets chains[id].ethGetLogsBlockRange only when an override is configured (otherwise Ponder auto-determines the range). Because Ponder's Build ID excludes config.chains, configuring a block range does not change the Build ID and does not trigger a re-index, just like RPC_URL. --- .../ensindexer/src/lib/ponder-helpers.test.ts | 29 ++++++++++++++++++- apps/ensindexer/src/lib/ponder-helpers.ts | 19 +++++++----- .../plugins/protocol-acceleration/plugin.ts | 6 +--- .../src/plugins/registrars/plugin.ts | 6 +--- .../subgraph/plugins/basenames/plugin.ts | 6 +--- .../subgraph/plugins/lineanames/plugin.ts | 6 +--- .../subgraph/plugins/subgraph/plugin.ts | 6 +--- .../subgraph/plugins/threedns/plugin.ts | 6 +--- .../src/plugins/tokenscope/plugin.ts | 12 ++++---- .../ensindexer/src/plugins/unigraph/plugin.ts | 6 +--- 10 files changed, 52 insertions(+), 50 deletions(-) diff --git a/apps/ensindexer/src/lib/ponder-helpers.test.ts b/apps/ensindexer/src/lib/ponder-helpers.test.ts index 11dd145292..65089b783f 100644 --- a/apps/ensindexer/src/lib/ponder-helpers.test.ts +++ b/apps/ensindexer/src/lib/ponder-helpers.test.ts @@ -1,8 +1,10 @@ +import type { ChainId } from "enssdk"; import { describe, expect, it } from "vitest"; +import type { RpcConfig } from "@ensnode/ensnode-sdk/internal"; import { buildBlockNumberRange } from "@ensnode/ponder-sdk"; -import { constrainBlockrange } from "./ponder-helpers"; +import { chainsConnectionConfig, constrainBlockrange } from "./ponder-helpers"; const UNDEFINED_BLOCKRANGE = buildBlockNumberRange(undefined, undefined); const BLOCKRANGE_WITH_END = buildBlockNumberRange(undefined, 1234); @@ -90,4 +92,29 @@ describe("ponder helpers", () => { }); }); }); + + describe("chainsConnectionConfig", () => { + const CHAIN_ID = 1 as ChainId; + const rpcConfigs = new Map([ + [CHAIN_ID, { httpRPCs: [new URL("https://rpc.example/1")], websocketRPC: undefined }], + ]); + + it("omits ethGetLogsBlockRange when the chain has no configured override", () => { + const chains = chainsConnectionConfig( + { rpcConfigs, ethGetLogsBlockRanges: new Map() }, + CHAIN_ID, + ); + + expect(chains[CHAIN_ID.toString()]).not.toHaveProperty("ethGetLogsBlockRange"); + }); + + it("includes ethGetLogsBlockRange when the chain has a configured override", () => { + const chains = chainsConnectionConfig( + { rpcConfigs, ethGetLogsBlockRanges: new Map([[CHAIN_ID, 1000]]) }, + CHAIN_ID, + ); + + expect(chains[CHAIN_ID.toString()]).toMatchObject({ ethGetLogsBlockRange: 1000 }); + }); + }); }); diff --git a/apps/ensindexer/src/lib/ponder-helpers.ts b/apps/ensindexer/src/lib/ponder-helpers.ts index 732a187bd1..b8fd4d2e8e 100644 --- a/apps/ensindexer/src/lib/ponder-helpers.ts +++ b/apps/ensindexer/src/lib/ponder-helpers.ts @@ -89,31 +89,35 @@ export const constrainBlockrange = ( /** * Builds a ponder#Config["chains"] for a single, specific chain in the context of the ENSIndexerConfig. * - * @param rpcConfigs - The RPC configuration object from ENSIndexerConfig, keyed by chain ID. + * @param config - The ENSIndexerConfig slice providing per-chain RPC configs and eth_getLogs block ranges. * @param chainId - The numeric chain ID for which to build the chain config. * @returns a ponder#Config["chains"] */ export function chainsConnectionConfig( - rpcConfigs: ENSIndexerConfig["rpcConfigs"], + config: Pick, chainId: ChainId, ) { - const rpcConfig = rpcConfigs.get(chainId); + const rpcConfig = config.rpcConfigs.get(chainId); if (!rpcConfig) { throw new Error( - `chainsConnectionConfig called for chain id ${chainId} but no associated rpcConfig is available. rpcConfig specifies the following chain ids: [${Object.keys(rpcConfigs).join(", ")}].`, + `chainsConnectionConfig called for chain id ${chainId} but no associated rpcConfig is available. rpcConfig specifies the following chain ids: [${Object.keys(config.rpcConfigs).join(", ")}].`, ); } // NOTE: disable cache on ens-test-env const disableCache = chainId === ensTestEnvChain.id; + const ethGetLogsBlockRange = config.ethGetLogsBlockRanges.get(chainId); + return { [chainId.toString()]: { id: chainId, rpc: rpcConfig.httpRPCs.map((httpRPC) => httpRPC.toString()), ws: rpcConfig.websocketRPC?.toString(), disableCache, + // only set when explicitly configured; otherwise Ponder auto-determines the range + ...(ethGetLogsBlockRange !== undefined ? { ethGetLogsBlockRange } : {}), } satisfies ChainConfig, }; } @@ -221,18 +225,17 @@ export function mergedChainConfigForContracts( * TODO */ export function chainsConnectionConfigForDatasources( - namespace: ENSNamespaceId, - rpcConfigs: ENSIndexerConfig["rpcConfigs"], + config: Pick, datasourceNames: DatasourceName[], ) { return datasourceNames - .map((datasourceName) => maybeGetDatasource(namespace, datasourceName)) + .map((datasourceName) => maybeGetDatasource(config.namespace, datasourceName)) .filter((ds) => !!ds) .map((datasource) => datasource.chain) .reduce>( (memo, chain) => ({ ...memo, - ...chainsConnectionConfig(rpcConfigs, chain.id), + ...chainsConnectionConfig(config, chain.id), }), {}, ); diff --git a/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts b/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts index 67291ce4d9..b8806aced2 100644 --- a/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts +++ b/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts @@ -75,11 +75,7 @@ export default createPlugin({ } = maybeGetDatasources(config.namespace, ALL_DATASOURCE_NAMES); return createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - ALL_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, ALL_DATASOURCE_NAMES), contracts: { ////////////////////// // Resolver Contracts diff --git a/apps/ensindexer/src/plugins/registrars/plugin.ts b/apps/ensindexer/src/plugins/registrars/plugin.ts index dde5173d19..c807dae61f 100644 --- a/apps/ensindexer/src/plugins/registrars/plugin.ts +++ b/apps/ensindexer/src/plugins/registrars/plugin.ts @@ -41,11 +41,7 @@ export default createPlugin({ } = getRequiredDatasources(config.namespace, REQUIRED_DATASOURCE_NAMES); return createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - REQUIRED_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, REQUIRED_DATASOURCE_NAMES), contracts: { ////////////////////// // Ethnames Registrar diff --git a/apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts b/apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts index 8d90744d30..e192af89e5 100644 --- a/apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts +++ b/apps/ensindexer/src/plugins/subgraph/plugins/basenames/plugin.ts @@ -28,11 +28,7 @@ export default createPlugin({ } = getRequiredDatasources(config.namespace, REQUIRED_DATASOURCE_NAMES); return ponder.createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - REQUIRED_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, REQUIRED_DATASOURCE_NAMES), contracts: { [namespaceContract(pluginName, "Registry")]: { chain: chainConfigForContract(config.globalBlockrange, chain.id, contracts.Registry), diff --git a/apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts b/apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts index 59f39f93d1..57e8556e59 100644 --- a/apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts +++ b/apps/ensindexer/src/plugins/subgraph/plugins/lineanames/plugin.ts @@ -29,11 +29,7 @@ export default createPlugin({ } = getRequiredDatasources(config.namespace, REQUIRED_DATASOURCE_NAMES); return ponder.createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - REQUIRED_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, REQUIRED_DATASOURCE_NAMES), contracts: { [namespaceContract(pluginName, "Registry")]: { chain: chainConfigForContract(config.globalBlockrange, chain.id, contracts.Registry), diff --git a/apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts b/apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts index e0cb6eb84b..ffda6c0d1e 100644 --- a/apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts +++ b/apps/ensindexer/src/plugins/subgraph/plugins/subgraph/plugin.ts @@ -31,11 +31,7 @@ export default createPlugin({ } = getRequiredDatasources(config.namespace, REQUIRED_DATASOURCE_NAMES); return ponder.createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - REQUIRED_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, REQUIRED_DATASOURCE_NAMES), contracts: { [namespaceContract(pluginName, "ENSv1RegistryOld")]: { chain: chainConfigForContract( diff --git a/apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts b/apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts index 725b3f174a..a15fc3e4c5 100644 --- a/apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts +++ b/apps/ensindexer/src/plugins/subgraph/plugins/threedns/plugin.ts @@ -29,11 +29,7 @@ export default createPlugin({ } = getRequiredDatasources(config.namespace, REQUIRED_DATASOURCE_NAMES); return ponder.createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - REQUIRED_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, REQUIRED_DATASOURCE_NAMES), contracts: { // multi-chain ThreeDNSToken indexing config [namespaceContract(pluginName, "ThreeDNSToken")]: { diff --git a/apps/ensindexer/src/plugins/tokenscope/plugin.ts b/apps/ensindexer/src/plugins/tokenscope/plugin.ts index e844ca10e7..ac28131acd 100644 --- a/apps/ensindexer/src/plugins/tokenscope/plugin.ts +++ b/apps/ensindexer/src/plugins/tokenscope/plugin.ts @@ -55,12 +55,12 @@ export default createPlugin({ return ponder.createConfig({ chains: { - ...chainsConnectionConfig(config.rpcConfigs, seaport.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, ensroot.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, basenames.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, lineanames.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, threednsOptimism.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, threednsBase.chain.id), + ...chainsConnectionConfig(config, seaport.chain.id), + ...chainsConnectionConfig(config, ensroot.chain.id), + ...chainsConnectionConfig(config, basenames.chain.id), + ...chainsConnectionConfig(config, lineanames.chain.id), + ...chainsConnectionConfig(config, threednsOptimism.chain.id), + ...chainsConnectionConfig(config, threednsBase.chain.id), }, contracts: { [namespaceContract(pluginName, "Seaport")]: { diff --git a/apps/ensindexer/src/plugins/unigraph/plugin.ts b/apps/ensindexer/src/plugins/unigraph/plugin.ts index 2f66ac865b..a92aaf37fc 100644 --- a/apps/ensindexer/src/plugins/unigraph/plugin.ts +++ b/apps/ensindexer/src/plugins/unigraph/plugin.ts @@ -57,11 +57,7 @@ export default createPlugin({ const DATASOURCES_WITH_ENSV2_CONTRACTS = getDatasourcesWithENSv2Contracts(config.namespace); return createConfig({ - chains: chainsConnectionConfigForDatasources( - config.namespace, - config.rpcConfigs, - ALL_DATASOURCE_NAMES, - ), + chains: chainsConnectionConfigForDatasources(config, ALL_DATASOURCE_NAMES), contracts: { //////////////////////////// From 138e8ff6c2387791033bb4c3b90e6b824b88d65e Mon Sep 17 00:00:00 2001 From: Nejc Drobnic Date: Fri, 29 May 2026 16:37:21 +0200 Subject: [PATCH 3/6] docs(ensindexer): document ETH_GET_LOGS_BLOCK_RANGE env var Document the per-chain ETH_GET_LOGS_BLOCK_RANGE_ override in .env.local.example (embedded into the configuration docs) and the docker env example, and add a changeset. --- .changeset/eth-getlogs-block-range.md | 5 +++++ apps/ensindexer/.env.local.example | 17 +++++++++++++++++ docker/envs/.env.docker.example | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 .changeset/eth-getlogs-block-range.md diff --git a/.changeset/eth-getlogs-block-range.md b/.changeset/eth-getlogs-block-range.md new file mode 100644 index 0000000000..ad3006b5e3 --- /dev/null +++ b/.changeset/eth-getlogs-block-range.md @@ -0,0 +1,5 @@ +--- +"ensindexer": minor +--- + +Add a per-chain `ETH_GET_LOGS_BLOCK_RANGE_` environment variable (analogous to `RPC_URL_`) that overrides Ponder's auto-determined `eth_getLogs` block range for a specific chain. This is useful for RPC providers that reject Ponder's default range. Like RPC connection settings, it is a performance-only knob: changing it does not affect indexed data and does not trigger a re-index. diff --git a/apps/ensindexer/.env.local.example b/apps/ensindexer/.env.local.example index ebd79f777f..d713e72a20 100644 --- a/apps/ensindexer/.env.local.example +++ b/apps/ensindexer/.env.local.example @@ -109,6 +109,23 @@ # provided HTTP/HTTPS RPC. More details at: https://ponder.sh/docs/config/chains#rpc-endpoints # +# eth_getLogs block range configuration +# Optional. Each ETH_GET_LOGS_BLOCK_RANGE_${chainId} environment variable, if specified, overrides +# the maximum block range Ponder uses for eth_getLogs requests on that chain. +# +# Ponder auto-determines a safe eth_getLogs block range per chain. Set this only when an RPC provider +# rejects Ponder's default range (for example, when it requires a smaller window). The value is a +# positive integer: the number of blocks per eth_getLogs request. Chains without an override +# continue to use Ponder's auto-determined range. +# More details at: https://ponder.sh/docs/config/chains#eth_getlogs-block-range +# +# Changing this value does not trigger a re-index (it is not part of Ponder's build id), the same as +# changing RPC_URL_${chainId}. +# +# Example: +# ETH_GET_LOGS_BLOCK_RANGE_1=1000 +# ETH_GET_LOGS_BLOCK_RANGE_8453=500 + # === ENS Namespace: Mainnet === # Ethereum Mainnet # - required if the configured namespace is mainnet diff --git a/docker/envs/.env.docker.example b/docker/envs/.env.docker.example index 4ee7174e47..a65a2b9d49 100644 --- a/docker/envs/.env.docker.example +++ b/docker/envs/.env.docker.example @@ -25,3 +25,5 @@ DB_SCHEMA_VERSION=3 # RPC_URL_42161= # RPC_URL_59144= # RPC_URL_534352= +# ETH_GET_LOGS_BLOCK_RANGE_1= +# ETH_GET_LOGS_BLOCK_RANGE_8453= From d651ca46290cec80937ae8f8c56a28ca5037d5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nejc=20Drobni=C4=8D?= Date: Fri, 29 May 2026 20:50:03 +0200 Subject: [PATCH 4/6] Update apps/ensindexer/src/lib/ponder-helpers.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/ensindexer/src/lib/ponder-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ensindexer/src/lib/ponder-helpers.ts b/apps/ensindexer/src/lib/ponder-helpers.ts index b8fd4d2e8e..4b367c82de 100644 --- a/apps/ensindexer/src/lib/ponder-helpers.ts +++ b/apps/ensindexer/src/lib/ponder-helpers.ts @@ -101,7 +101,7 @@ export function chainsConnectionConfig( if (!rpcConfig) { throw new Error( - `chainsConnectionConfig called for chain id ${chainId} but no associated rpcConfig is available. rpcConfig specifies the following chain ids: [${Object.keys(config.rpcConfigs).join(", ")}].`, + `chainsConnectionConfig called for chain id ${chainId} but no associated rpcConfig is available. rpcConfig specifies the following chain ids: [${[...config.rpcConfigs.keys()].join(", ")}].`, ); } From a6ba5b977578e928ebf3bca42182f5250bbaed72 Mon Sep 17 00:00:00 2001 From: Nejc Drobnic Date: Sat, 30 May 2026 18:51:48 +0200 Subject: [PATCH 5/6] feat(ensindexer): global ETH_GET_LOGS_BLOCK_RANGE default and 0-disable Add a global ETH_GET_LOGS_BLOCK_RANGE applied to every chain, with each ETH_GET_LOGS_BLOCK_RANGE_ overriding it per chain and a value of 0 disabling the override for that chain (so Ponder auto-determines its range). ETH_GET_LOGS_BLOCK_RANGE=0 is equivalent to unset. Values resolve into the existing per-chain effective map, so the Ponder chains wiring is unchanged. --- .changeset/eth-getlogs-block-range.md | 2 +- apps/ensindexer/.env.local.example | 26 ++++++++------ apps/ensindexer/src/config/config.test.ts | 26 ++++++++++++-- apps/ensindexer/src/config/environment.ts | 9 +++-- .../config/eth-get-logs-block-ranges.test.ts | 34 +++++++++++++++++-- .../src/config/eth-get-logs-block-ranges.ts | 33 +++++++++++------- apps/ensindexer/src/config/types.ts | 22 ++++++++---- docker/envs/.env.docker.example | 1 + 8 files changed, 115 insertions(+), 38 deletions(-) diff --git a/.changeset/eth-getlogs-block-range.md b/.changeset/eth-getlogs-block-range.md index ad3006b5e3..11d275f74c 100644 --- a/.changeset/eth-getlogs-block-range.md +++ b/.changeset/eth-getlogs-block-range.md @@ -2,4 +2,4 @@ "ensindexer": minor --- -Add a per-chain `ETH_GET_LOGS_BLOCK_RANGE_` environment variable (analogous to `RPC_URL_`) that overrides Ponder's auto-determined `eth_getLogs` block range for a specific chain. This is useful for RPC providers that reject Ponder's default range. Like RPC connection settings, it is a performance-only knob: changing it does not affect indexed data and does not trigger a re-index. +ENSIndexer now supports tuning Ponder's `eth_getLogs` block range per chain. Set `ETH_GET_LOGS_BLOCK_RANGE` to apply a default cap across all chains, or `ETH_GET_LOGS_BLOCK_RANGE_` to override it for a specific chain (use `0` to opt a chain out and let Ponder auto-determine its range). This makes it easy to work with RPC providers that require a smaller `eth_getLogs` window. Like RPC connection settings, these are performance-only knobs: changing them does not affect indexed data and does not trigger a re-index. diff --git a/apps/ensindexer/.env.local.example b/apps/ensindexer/.env.local.example index d713e72a20..4271b16780 100644 --- a/apps/ensindexer/.env.local.example +++ b/apps/ensindexer/.env.local.example @@ -110,21 +110,27 @@ # # eth_getLogs block range configuration -# Optional. Each ETH_GET_LOGS_BLOCK_RANGE_${chainId} environment variable, if specified, overrides -# the maximum block range Ponder uses for eth_getLogs requests on that chain. +# Optional. Caps the maximum block range Ponder uses for eth_getLogs requests. # -# Ponder auto-determines a safe eth_getLogs block range per chain. Set this only when an RPC provider -# rejects Ponder's default range (for example, when it requires a smaller window). The value is a -# positive integer: the number of blocks per eth_getLogs request. Chains without an override -# continue to use Ponder's auto-determined range. +# Ponder auto-determines a safe eth_getLogs block range per chain. Set these only when an RPC provider +# rejects Ponder's default range (for example, when it requires a smaller window). Each value is a +# positive integer: the number of blocks per eth_getLogs request. +# +# - ETH_GET_LOGS_BLOCK_RANGE sets a default applied to every chain. +# - ETH_GET_LOGS_BLOCK_RANGE_${chainId} overrides that default for a specific chain. +# - ETH_GET_LOGS_BLOCK_RANGE_${chainId}=0 disables the cap for that chain (ignoring the default), so +# Ponder auto-determines its range. ETH_GET_LOGS_BLOCK_RANGE=0 is equivalent to leaving it unset. +# +# Chains without an effective value continue to use Ponder's auto-determined range. # More details at: https://ponder.sh/docs/config/chains#eth_getlogs-block-range # -# Changing this value does not trigger a re-index (it is not part of Ponder's build id), the same as -# changing RPC_URL_${chainId}. +# Changing these values does not trigger a re-index (they are not part of Ponder's build id), the +# same as changing RPC_URL_${chainId}. # -# Example: -# ETH_GET_LOGS_BLOCK_RANGE_1=1000 +# Example (default 1000 for all chains, 500 for Base, disabled for mainnet): +# ETH_GET_LOGS_BLOCK_RANGE=1000 # ETH_GET_LOGS_BLOCK_RANGE_8453=500 +# ETH_GET_LOGS_BLOCK_RANGE_1=0 # === ENS Namespace: Mainnet === # Ethereum Mainnet diff --git a/apps/ensindexer/src/config/config.test.ts b/apps/ensindexer/src/config/config.test.ts index a6989f96fc..9990bc6fd8 100644 --- a/apps/ensindexer/src/config/config.test.ts +++ b/apps/ensindexer/src/config/config.test.ts @@ -300,7 +300,7 @@ describe("config (with base env)", () => { }); describe(".ethGetLogsBlockRanges", () => { - it("defaults to an empty Map when no ETH_GET_LOGS_BLOCK_RANGE_* var is set", async () => { + it("defaults to an empty Map when no ETH_GET_LOGS_BLOCK_RANGE var is set", async () => { const config = await getConfig(); expect(config.ethGetLogsBlockRanges).toStrictEqual(new Map()); }); @@ -311,9 +311,29 @@ describe("config (with base env)", () => { expect(config.ethGetLogsBlockRanges).toStrictEqual(new Map([[1, 1000]])); }); - it("throws if a configured eth_getLogs block range is not a positive integer", async () => { + it("applies the global ETH_GET_LOGS_BLOCK_RANGE default to indexed chains", async () => { + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE", "1000"); + const config = await getConfig(); + expect(Object.fromEntries(config.ethGetLogsBlockRanges)).toMatchObject({ "1": 1000 }); + }); + + it("lets a chain-specific override take precedence over the global default", async () => { + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE", "1000"); + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE_1", "500"); + const config = await getConfig(); + expect(Object.fromEntries(config.ethGetLogsBlockRanges)).toMatchObject({ "1": 500 }); + }); + + it("omits a chain disabled with 0 even when a global default is set", async () => { + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE", "1000"); + vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE_1", "0"); + const config = await getConfig(); + expect(Object.fromEntries(config.ethGetLogsBlockRanges)).not.toHaveProperty("1"); + }); + + it("throws if a configured eth_getLogs block range is not a non-negative integer", async () => { vi.stubEnv("ETH_GET_LOGS_BLOCK_RANGE_1", "abc"); - await expect(getConfig()).rejects.toThrow(/positive integer/i); + await expect(getConfig()).rejects.toThrow(/non-negative integer/i); }); }); diff --git a/apps/ensindexer/src/config/environment.ts b/apps/ensindexer/src/config/environment.ts index 2c8ec60e05..e8a0d61419 100644 --- a/apps/ensindexer/src/config/environment.ts +++ b/apps/ensindexer/src/config/environment.ts @@ -1,13 +1,16 @@ import type { EnsDbEnvironment, RpcEnvironment } from "@ensnode/ensnode-sdk/internal"; /** - * Environment variables for per-chain `eth_getLogs` block range overrides. + * Environment variables for `eth_getLogs` block range overrides. * - * Each `ETH_GET_LOGS_BLOCK_RANGE_${chainId}`, if specified, overrides Ponder's auto-determined - * maximum `eth_getLogs` block range for that chain. + * `ETH_GET_LOGS_BLOCK_RANGE` sets a default applied to every chain, and each + * `ETH_GET_LOGS_BLOCK_RANGE_${chainId}` overrides that default for a specific chain (a value of `0` + * disables the override for that chain, so Ponder auto-determines its range). These cap Ponder's + * auto-determined maximum `eth_getLogs` block range. * @see https://ponder.sh/docs/config/chains#eth_getlogs-block-range */ export interface EthGetLogsBlockRangeEnvironment { + ETH_GET_LOGS_BLOCK_RANGE?: string; [x: `ETH_GET_LOGS_BLOCK_RANGE_${number}`]: string | undefined; } diff --git a/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts b/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts index e86a93878b..5c7bea8ee0 100644 --- a/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts +++ b/apps/ensindexer/src/config/eth-get-logs-block-ranges.test.ts @@ -6,7 +6,7 @@ import { } from "./eth-get-logs-block-ranges"; describe("buildEthGetLogsBlockRangesFromEnv", () => { - it("returns an empty record when no ETH_GET_LOGS_BLOCK_RANGE_* vars are set", () => { + it("returns an empty record when no ETH_GET_LOGS_BLOCK_RANGE or ETH_GET_LOGS_BLOCK_RANGE_ vars are set", () => { expect(buildEthGetLogsBlockRangesFromEnv({}, "mainnet")).toStrictEqual({}); }); @@ -16,6 +16,30 @@ describe("buildEthGetLogsBlockRangesFromEnv", () => { ).toStrictEqual({ "1": "1000" }); }); + it("applies the global ETH_GET_LOGS_BLOCK_RANGE default to every chain in the namespace", () => { + expect( + buildEthGetLogsBlockRangesFromEnv({ ETH_GET_LOGS_BLOCK_RANGE: "1000" }, "mainnet"), + ).toMatchObject({ "1": "1000", "8453": "1000" }); + }); + + it("lets a chain-specific value override the global default", () => { + expect( + buildEthGetLogsBlockRangesFromEnv( + { ETH_GET_LOGS_BLOCK_RANGE: "1000", ETH_GET_LOGS_BLOCK_RANGE_8453: "500" }, + "mainnet", + ), + ).toMatchObject({ "1": "1000", "8453": "500" }); + }); + + it("lets a chain-specific 0 take precedence over the global default", () => { + expect( + buildEthGetLogsBlockRangesFromEnv( + { ETH_GET_LOGS_BLOCK_RANGE: "1000", ETH_GET_LOGS_BLOCK_RANGE_1: "0" }, + "mainnet", + ), + ).toMatchObject({ "1": "0", "8453": "1000" }); + }); + it("ignores ETH_GET_LOGS_BLOCK_RANGE_ for chains outside the namespace", () => { expect( buildEthGetLogsBlockRangesFromEnv({ ETH_GET_LOGS_BLOCK_RANGE_999999: "1000" }, "mainnet"), @@ -37,7 +61,13 @@ describe("EthGetLogsBlockRangesSchema", () => { ); }); - it.each(["abc", "0", "-5", "1.5"])("rejects invalid block range %j", (value) => { + it("treats 0 as a disable sentinel and omits that chain", () => { + expect(EthGetLogsBlockRangesSchema.parse({ "1": "0", "8453": "1000" })).toStrictEqual( + new Map([[8453, 1000]]), + ); + }); + + it.each(["abc", "-5", "1.5"])("rejects invalid block range %j", (value) => { expect(() => EthGetLogsBlockRangesSchema.parse({ "1": value })).toThrow(); }); }); diff --git a/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts b/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts index bdf3c3bad6..2673ea6521 100644 --- a/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts +++ b/apps/ensindexer/src/config/eth-get-logs-block-ranges.ts @@ -11,16 +11,20 @@ import type { EthGetLogsBlockRangeEnvironment } from "@/config/environment"; * Builds the raw, per-chain `eth_getLogs` block range overrides from the environment, scoped to the * chain IDs that appear in the specified `namespace`. * - * Mirrors `buildRpcConfigsFromEnv`: for each chain in the namespace, reads the optional - * `ETH_GET_LOGS_BLOCK_RANGE_${chainId}` environment variable. Variables for chains outside the - * namespace are ignored (same behavior as `RPC_URL_*`). + * For each chain in the namespace, a chain-specific `ETH_GET_LOGS_BLOCK_RANGE_${chainId}` takes + * precedence over the global `ETH_GET_LOGS_BLOCK_RANGE` default (a chain-specific `0` wins over the + * default and, once validated, disables the override for that chain). Variables for chains outside + * the namespace are ignored (same behavior as `RPC_URL_*`). * - * NOTE: returns raw (unvalidated) string values; validation happens in {@link EthGetLogsBlockRangesSchema}. + * NOTE: returns raw (unvalidated) string values; validation and the `0` disable semantics are + * applied in {@link EthGetLogsBlockRangesSchema}. */ export function buildEthGetLogsBlockRangesFromEnv( env: EthGetLogsBlockRangeEnvironment, namespace: ENSNamespaceId, ): Record { + const defaultValue = env.ETH_GET_LOGS_BLOCK_RANGE || undefined; + const chainsInNamespace = Object.entries(getENSNamespace(namespace)).map( ([, datasource]) => (datasource as Datasource).chain, ); @@ -28,8 +32,9 @@ export function buildEthGetLogsBlockRangesFromEnv( const ethGetLogsBlockRanges: Record = {}; for (const chain of chainsInNamespace) { - const value = env[`ETH_GET_LOGS_BLOCK_RANGE_${chain.id}`]; - if (value !== undefined && value !== "") { + // a chain-specific value (including "0" to disable) takes precedence over the global default + const value = (env[`ETH_GET_LOGS_BLOCK_RANGE_${chain.id}`] || undefined) ?? defaultValue; + if (value !== undefined) { ethGetLogsBlockRanges[serializeChainId(chain.id)] = value; } } @@ -38,23 +43,27 @@ export function buildEthGetLogsBlockRangesFromEnv( } const EthGetLogsBlockRangeValueSchema = z.coerce - .number({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }) - .int({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }) - .min(1, { error: "ETH_GET_LOGS_BLOCK_RANGE must be a positive integer." }); + .number({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a non-negative integer." }) + .int({ error: "ETH_GET_LOGS_BLOCK_RANGE must be a non-negative integer." }) + .min(0, { error: "ETH_GET_LOGS_BLOCK_RANGE must be a non-negative integer." }); /** - * Parses the raw per-chain `eth_getLogs` block range overrides into a `Map`. + * Parses the raw per-chain `eth_getLogs` block range overrides into a `Map`, + * dropping any chain configured with `0` (the disable sentinel) so Ponder auto-determines its range. */ export const EthGetLogsBlockRangesSchema = z .record(makeChainIdStringSchema("ETH_GET_LOGS_BLOCK_RANGE"), EthGetLogsBlockRangeValueSchema, { error: - "ETH_GET_LOGS_BLOCK_RANGE configuration must be an object mapping valid chain IDs to positive integers.", + "ETH_GET_LOGS_BLOCK_RANGE configuration must be an object mapping valid chain IDs to non-negative integers.", }) .transform((records) => { const ethGetLogsBlockRanges = new Map(); for (const [chainIdString, blockRange] of Object.entries(records)) { - ethGetLogsBlockRanges.set(deserializeChainId(chainIdString), blockRange); + // 0 disables the override for a chain, so it is omitted (Ponder auto-determines the range) + if (blockRange > 0) { + ethGetLogsBlockRanges.set(deserializeChainId(chainIdString), blockRange); + } } return ethGetLogsBlockRanges; diff --git a/apps/ensindexer/src/config/types.ts b/apps/ensindexer/src/config/types.ts index 7ee73a400d..571afa4ec0 100644 --- a/apps/ensindexer/src/config/types.ts +++ b/apps/ensindexer/src/config/types.ts @@ -7,7 +7,8 @@ import { RpcConfig, type RpcConfigs } from "@ensnode/ensnode-sdk/internal"; import type { EnsRainbowClientLabelSet } from "@ensnode/ensrainbow-sdk"; /** - * Per-chain override of Ponder's maximum `eth_getLogs` block range, keyed by chain id. + * Effective per-chain cap on Ponder's maximum `eth_getLogs` block range (after resolving the global + * default and per-chain overrides), keyed by chain id. * * @invariant Each value is a positive integer. */ @@ -112,11 +113,17 @@ export interface EnsIndexerConfig { rpcConfigs: RpcConfigs; /** - * Optional per-chain override of Ponder's maximum `eth_getLogs` block range, keyed by chain id - * (configured via the `ETH_GET_LOGS_BLOCK_RANGE_` environment variable). + * Effective per-chain cap on Ponder's maximum `eth_getLogs` block range, keyed by chain id, after + * resolving environment configuration. * - * Ponder auto-determines a safe `eth_getLogs` block range per chain; this lets operators set a - * manual cap for RPC providers that reject Ponder's default range. + * Configured via environment variables: + * - `ETH_GET_LOGS_BLOCK_RANGE` sets a default applied to every chain. + * - `ETH_GET_LOGS_BLOCK_RANGE_` overrides that default for a specific chain. + * - `ETH_GET_LOGS_BLOCK_RANGE_=0` disables the cap for that chain (ignoring the default), + * so Ponder auto-determines its range. `ETH_GET_LOGS_BLOCK_RANGE=0` is equivalent to unset. + * + * Ponder auto-determines a safe `eth_getLogs` block range per chain; these overrides let operators + * set a manual cap for RPC providers that reject Ponder's default range. * @see https://ponder.sh/docs/config/chains#eth_getlogs-block-range * * Like {@link rpcConfigs}, this is a performance/connection tuning knob only: it does NOT affect @@ -124,8 +131,9 @@ export interface EnsIndexerConfig { * not trigger a re-index. * * Invariants: - * - Keys are a subset of the chains in the configured {@link namespace}. Chains without an entry - * use Ponder's auto-determined range. + * - Keys are a subset of the chains in the configured {@link namespace}. A chain is absent when it + * has no effective cap (unset, or disabled with `0`), in which case Ponder auto-determines its + * range. */ ethGetLogsBlockRanges: EthGetLogsBlockRanges; diff --git a/docker/envs/.env.docker.example b/docker/envs/.env.docker.example index a65a2b9d49..9cb7984461 100644 --- a/docker/envs/.env.docker.example +++ b/docker/envs/.env.docker.example @@ -25,5 +25,6 @@ DB_SCHEMA_VERSION=3 # RPC_URL_42161= # RPC_URL_59144= # RPC_URL_534352= +# ETH_GET_LOGS_BLOCK_RANGE= # ETH_GET_LOGS_BLOCK_RANGE_1= # ETH_GET_LOGS_BLOCK_RANGE_8453= From a6ab09048904faf73b515cddb2c2fdf6b94b5b11 Mon Sep 17 00:00:00 2001 From: Nejc Drobnic Date: Sat, 30 May 2026 20:28:38 +0200 Subject: [PATCH 6/6] docs(ensindexer): clarify ETH_GET_LOGS_BLOCK_RANGE accepts 0 The value-type line said "positive integer", which contradicted the documented 0-disables behavior. State that the value is a non-negative integer (0 included) and that 0 disables the override so Ponder auto-determines the range. --- apps/ensindexer/.env.local.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ensindexer/.env.local.example b/apps/ensindexer/.env.local.example index 4271b16780..ce95548c85 100644 --- a/apps/ensindexer/.env.local.example +++ b/apps/ensindexer/.env.local.example @@ -114,7 +114,8 @@ # # Ponder auto-determines a safe eth_getLogs block range per chain. Set these only when an RPC provider # rejects Ponder's default range (for example, when it requires a smaller window). Each value is a -# positive integer: the number of blocks per eth_getLogs request. +# non-negative integer (0 included): a positive value is the number of blocks per eth_getLogs request, +# and 0 disables the override so Ponder auto-determines the range. # # - ETH_GET_LOGS_BLOCK_RANGE sets a default applied to every chain. # - ETH_GET_LOGS_BLOCK_RANGE_${chainId} overrides that default for a specific chain.