Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-friends-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
Comment thread
shrugs marked this conversation as resolved.
---

Fixes issue with derivation of `EnsIndexerConfig.indexedChainIds` in plugins that conditionally index multiple chains (ex: 'protocol-acceleration').
93 changes: 71 additions & 22 deletions apps/ensindexer/src/config/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ensTestEnvChain } from "@ensnode/datasources";
import { type ENSNamespaceId, ensTestEnvChain, getENSNamespace } from "@ensnode/datasources";
import { buildBlockNumberRange, ENSNamespaceIds, PluginName } from "@ensnode/ensnode-sdk";
import type { RpcConfig } from "@ensnode/ensnode-sdk/internal";

Expand Down Expand Up @@ -36,6 +36,17 @@ async function stubEnv(env: ENSIndexerEnvironment) {
Object.entries(env).forEach(([key, value]) => vi.stubEnv(key, value));
}

/**
* Stubs RPC_URL env vars for all chains defined in the given namespace's datasources.
*/
function stubRpcUrlsForNamespace(namespace: ENSNamespaceId) {
const datasources = getENSNamespace(namespace);
const chainIds = new Set(Object.values(datasources).map((ds) => ds.chain.id));
for (const chainId of chainIds) {
vi.stubEnv(`RPC_URL_${chainId}`, VALID_RPC_URL);
}
}

describe("config (with base env)", () => {
beforeEach(() => {
stubEnv(BASE_ENV);
Expand Down Expand Up @@ -203,7 +214,7 @@ describe("config (with base env)", () => {
describe(".namespace", () => {
it("returns the NAMESPACE if set", async () => {
vi.stubEnv("NAMESPACE", "sepolia");
vi.stubEnv("RPC_URL_11155111", VALID_RPC_URL);
stubRpcUrlsForNamespace("sepolia");
const config = await getConfig();
expect(config.namespace).toBe("sepolia");
});
Expand Down Expand Up @@ -243,10 +254,7 @@ describe("config (with base env)", () => {

it("has default plugins", async () => {
vi.stubEnv("PLUGINS", undefined);
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
vi.stubEnv("RPC_URL_59144", VALID_RPC_URL);
vi.stubEnv("RPC_URL_10", VALID_RPC_URL);
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");

await expect(getConfig()).resolves.toMatchObject({
plugins: EnvironmentDefaults.alpha.PLUGINS.split(","),
Expand All @@ -256,14 +264,14 @@ describe("config (with base env)", () => {

it("returns the PLUGINS if it is a valid array", async () => {
vi.stubEnv("PLUGINS", "subgraph,basenames");
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");
const config = await getConfig();
expect(config.plugins).toEqual(["subgraph", "basenames"]);
});

it("returns a single plugin if only one is provided", async () => {
vi.stubEnv("PLUGINS", "basenames");
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");
const config = await getConfig();
expect(config.plugins).toEqual(["basenames"]);
});
Expand Down Expand Up @@ -469,19 +477,68 @@ describe("config (with base env)", () => {

it("throws when PLUGINS does not include subgraph", async () => {
vi.stubEnv("PLUGINS", "basenames");
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");

await expect(getConfig()).rejects.toThrow(/isSubgraphCompatible/);
});

it("throws when PLUGINS includes subgraph along with other plugins", async () => {
vi.stubEnv("PLUGINS", "subgraph,basenames");
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");

await expect(getConfig()).rejects.toThrow(/isSubgraphCompatible/);
});
});

describe(".indexedChainIds", () => {
it("derives chain id 1 for subgraph plugin on mainnet", async () => {
vi.stubEnv("PLUGINS", "subgraph");
const config = await getConfig();
expect(config.indexedChainIds).toEqual(new Set([1]));
});

it("derives chain ids 1 and 8453 for subgraph,basenames on mainnet", async () => {
vi.stubEnv("PLUGINS", "subgraph,basenames");
stubRpcUrlsForNamespace("mainnet");
const config = await getConfig();
expect(config.indexedChainIds).toEqual(new Set([1, 8453]));
});

it("derives all expected chain ids for protocol-acceleration on mainnet", async () => {
vi.stubEnv("PLUGINS", "protocol-acceleration");
stubRpcUrlsForNamespace("mainnet");
const config = await getConfig();
// mainnet(1), base(8453), linea(59144), optimism(10), arbitrum(42161), scroll(534352)
expect(config.indexedChainIds).toEqual(new Set([1, 8453, 59144, 10, 42161, 534352]));
});
Comment thread
shrugs marked this conversation as resolved.

it("unions chain ids across multiple plugins", async () => {
vi.stubEnv("PLUGINS", "subgraph,basenames");
stubRpcUrlsForNamespace("mainnet");
const config = await getConfig();
// subgraph contributes 1, basenames contributes 1 and 8453
expect(config.indexedChainIds).toEqual(new Set([1, 8453]));
expect(config.indexedChainIds.size).toBe(2);
});
Comment thread
shrugs marked this conversation as resolved.
Outdated

// This test asserts that protocol-acceleration derives different chain ids per namespace,
// because available datasources differ (e.g. sepolia has no ThreeDNS datasources).
// If this test fails after updating datasources for a namespace, update the expected
// chain ids or remove this test if the distinction no longer holds.
it("derives different chain ids for protocol-acceleration on sepolia vs mainnet", async () => {
vi.stubEnv("PLUGINS", "protocol-acceleration");

stubRpcUrlsForNamespace("mainnet");
const mainnetConfig = await getConfig();

vi.stubEnv("NAMESPACE", "sepolia");
stubRpcUrlsForNamespace("sepolia");
const sepoliaConfig = await getConfig();

expect(mainnetConfig.indexedChainIds).not.toEqual(sepoliaConfig.indexedChainIds);
});
});

describe("additional checks", () => {
it("requires available datasources", async () => {
vi.stubEnv("NAMESPACE", "ens-test-env");
Expand All @@ -496,7 +553,7 @@ describe("config (with base env)", () => {

it("cannot constrain blockrange with multiple chains", async () => {
vi.stubEnv("PLUGINS", "subgraph,basenames");
vi.stubEnv("RPC_URL_8453", VALID_RPC_URL);
stubRpcUrlsForNamespace("mainnet");
vi.stubEnv("END_BLOCK", "1");
await expect(getConfig()).rejects.toThrow(/multiple chains/i);
});
Expand Down Expand Up @@ -628,24 +685,16 @@ describe("config (minimal base env)", () => {
});

it("provides default plugins", async () => {
stubEnv({
RPC_URL_8453: VALID_RPC_URL,
RPC_URL_59144: VALID_RPC_URL,
RPC_URL_10: VALID_RPC_URL,
});
stubRpcUrlsForNamespace("mainnet");

await expect(getConfig()).resolves.toMatchObject({
plugins: EnvironmentDefaults.alpha.PLUGINS.split(","),
});
});

it("allows override of default plugins", async () => {
stubEnv({
PLUGINS: "tokenscope",
RPC_URL_8453: VALID_RPC_URL,
RPC_URL_59144: VALID_RPC_URL,
RPC_URL_10: VALID_RPC_URL,
});
stubEnv({ PLUGINS: "tokenscope" });
stubRpcUrlsForNamespace("mainnet");

await expect(getConfig()).resolves.toMatchObject({ plugins: [PluginName.TokenScope] });
});
Expand Down
27 changes: 11 additions & 16 deletions apps/ensindexer/src/config/derived-params.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import { type ENSNamespace, getENSNamespace } from "@ensnode/datasources";
import type { Chain } from "viem";

import type { ChainId } from "@ensnode/ensnode-sdk";

import type { ENSIndexerConfig } from "@/config/types";
import type { EnsIndexerConfig } from "@/config/types";
import { getPlugin } from "@/plugins";

/**
* Derive `indexedChainIds` configuration parameter and include it in
* configuration.
* Derive `indexedChainIds` configuration parameter and include it in configuration.
*
* @param config partial configuration
* @returns extended configuration
*/
export const derive_indexedChainIds = <
CONFIG extends Pick<ENSIndexerConfig, "namespace" | "plugins">,
>(
export const derive_indexedChainIds = <CONFIG extends Omit<EnsIndexerConfig, "indexedChainIds">>(
config: CONFIG,
): CONFIG & { indexedChainIds: ENSIndexerConfig["indexedChainIds"] } => {
): CONFIG & { indexedChainIds: EnsIndexerConfig["indexedChainIds"] } => {
const indexedChainIds = new Set<ChainId>();

const datasources = getENSNamespace(config.namespace) as ENSNamespace;

for (const pluginName of config.plugins) {
const datasourceNames = getPlugin(pluginName).requiredDatasourceNames;

for (const datasourceName of datasourceNames) {
const datasource = datasources[datasourceName];
if (datasource) indexedChainIds.add(datasource.chain.id);
const plugins = config.plugins.map(getPlugin);
for (const plugin of plugins) {
const ponderConfig = plugin.createPonderConfig(config);
for (const chain of Object.values(ponderConfig.chains) as Chain[]) {
Comment thread
shrugs marked this conversation as resolved.
Outdated
indexedChainIds.add(chain.id);
Comment thread
shrugs marked this conversation as resolved.
Outdated
Comment thread
shrugs marked this conversation as resolved.
Outdated
}
}

Expand Down
14 changes: 4 additions & 10 deletions apps/ensindexer/src/lib/plugin-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { createConfig as createPonderConfig } from "ponder";
import type { DatasourceName } from "@ensnode/datasources";
import { PluginName, uniq } from "@ensnode/ensnode-sdk";
Comment thread
shrugs marked this conversation as resolved.
Outdated

import type { ENSIndexerConfig } from "@/config/types";
import type { EnsIndexerConfig } from "@/config/types";
import { getPlugin } from "@/plugins";

/**
Expand Down Expand Up @@ -67,10 +67,10 @@ export interface ENSIndexerPlugin<
/**
* Create Ponder Config for the plugin.
*
* @param {ENSIndexerConfig} config
* @param {EnsIndexerConfig} config
*/
createPonderConfig(
config: ENSIndexerConfig,
config: Omit<EnsIndexerConfig, "indexedChainIds">,
): PonderConfigResult<CHAINS, CONTRACTS, ACCOUNTS, BLOCKS>;
Comment thread
shrugs marked this conversation as resolved.
Outdated
Comment thread
shrugs marked this conversation as resolved.
}

Expand Down Expand Up @@ -105,7 +105,7 @@ export interface BuildPluginOptions<
* nested factory functions, i.e. to ensure that the ponder configuration
* is only created for this plugin when it is activated.
*/
createPonderConfig(config: ENSIndexerConfig): PONDER_CONFIG_RESULT;
createPonderConfig(config: EnsIndexerConfig): PONDER_CONFIG_RESULT;
Comment thread
shrugs marked this conversation as resolved.
Comment thread
shrugs marked this conversation as resolved.
}

/**
Expand All @@ -129,12 +129,6 @@ export function createPlugin<
return options;
}
Comment thread
shrugs marked this conversation as resolved.

export function getRequiredDatasourceNames(plugins: ENSIndexerPlugin[]): DatasourceName[] {
const requiredDatasourceNames = plugins.flatMap((plugin) => plugin.requiredDatasourceNames);

return uniq(requiredDatasourceNames);
}

/**
* Gets a mapping of plugin names to their required datasource names.
*
Expand Down
Loading