diff --git a/.changeset/fast-bats-train.md b/.changeset/fast-bats-train.md new file mode 100644 index 000000000..89d39b3cb --- /dev/null +++ b/.changeset/fast-bats-train.md @@ -0,0 +1,9 @@ +--- +"@ensnode/ensrainbow-sdk": minor +"ensindexer": minor +"@docs/ensnode": minor +"ensadmin": minor +"ensapi": minor +--- + +Altered code references accordingly to the updated `EnsIndexerPublicConfig` data model. diff --git a/.changeset/lovely-teeth-hunt.md b/.changeset/lovely-teeth-hunt.md new file mode 100644 index 000000000..588fd8e99 --- /dev/null +++ b/.changeset/lovely-teeth-hunt.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-sdk": minor +--- + +Added `ensRainbowPublicConfig` field to `EnsIndexerPublicConfig`. diff --git a/apps/ensadmin/src/app/mock/config-api.mock.ts b/apps/ensadmin/src/app/mock/config-api.mock.ts index 8edbde34c..54d0244f9 100644 --- a/apps/ensadmin/src/app/mock/config-api.mock.ts +++ b/apps/ensadmin/src/app/mock/config-api.mock.ts @@ -7,6 +7,14 @@ export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig({ }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352, 567], databaseSchemaName: "alphaSchema0.34.0", + ensRainbowPublicConfig: { + version: "0.34.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, isSubgraphCompatible: false, namespace: "mainnet", plugins: [ @@ -23,8 +31,6 @@ export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig({ ponder: "0.11.43", ensIndexer: "0.35.0", ensDb: "0.35.0", - ensRainbow: "0.34.0", - ensRainbowSchema: 3, ensNormalize: "1.11.1", }, }); diff --git a/apps/ensadmin/src/app/mock/config-info/data.json b/apps/ensadmin/src/app/mock/config-info/data.json index f8d9f90fb..fd9319ecb 100644 --- a/apps/ensadmin/src/app/mock/config-info/data.json +++ b/apps/ensadmin/src/app/mock/config-info/data.json @@ -12,6 +12,14 @@ }, "indexedChainIds": [1, 8453, 59144, 10, 42161, 534352, 567], "databaseSchemaName": "alphaSchema0.34.0", + "ensRainbowPublicConfig": { + "version": "0.34.0", + "labelSet": { + "labelSetId": "subgraph", + "highestLabelSetVersion": 0 + }, + "recordsCount": 100 + }, "isSubgraphCompatible": false, "namespace": "mainnet", "plugins": [ @@ -28,9 +36,7 @@ "ponder": "0.11.43", "ensDb": "0.35.0", "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1", - "ensRainbow": "0.34.0", - "ensRainbowSchema": 3 + "ensNormalize": "1.11.1" } } }, @@ -50,9 +56,7 @@ "ponder": "0.11.43", "ensDb": "0.35.0", "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1", - "ensRainbow": "0.34.0", - "ensRainbowSchema": 3 + "ensNormalize": "1.11.1" }, "indexedChainIds": [11155111, 84532, 59141, 11155420, 421614, 534351], "namespace": "sepolia", @@ -65,6 +69,14 @@ "registrars" ], "databaseSchemaName": "alphaSepoliaSchema0.34.0", + "ensRainbowPublicConfig": { + "version": "0.34.0", + "labelSet": { + "labelSetId": "subgraph", + "highestLabelSetVersion": 0 + }, + "recordsCount": 100 + }, "isSubgraphCompatible": false } }, @@ -84,14 +96,20 @@ "ponder": "0.11.43", "ensDb": "0.35.0", "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1", - "ensRainbow": "0.34.0", - "ensRainbowSchema": 3 + "ensNormalize": "1.11.1" }, "indexedChainIds": [1], "namespace": "mainnet", "plugins": ["subgraph"], "databaseSchemaName": "mainnetSchema0.34.0", + "ensRainbowPublicConfig": { + "version": "0.34.0", + "labelSet": { + "labelSetId": "subgraph", + "highestLabelSetVersion": 0 + }, + "recordsCount": 100 + }, "isSubgraphCompatible": true } }, @@ -111,14 +129,20 @@ "ponder": "0.11.43", "ensDb": "0.35.0", "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1", - "ensRainbow": "0.34.0", - "ensRainbowSchema": 3 + "ensNormalize": "1.11.1" }, "indexedChainIds": [11155111], "namespace": "sepolia", "plugins": ["subgraph"], "databaseSchemaName": "sepoliaSchema0.34.0", + "ensRainbowPublicConfig": { + "version": "0.34.0", + "labelSet": { + "labelSetId": "subgraph", + "highestLabelSetVersion": 0 + }, + "recordsCount": 100 + }, "isSubgraphCompatible": true } }, @@ -138,14 +162,20 @@ "ponder": "", "ensDb": "", "ensIndexer": "", - "ensNormalize": "", - "ensRainbow": "", - "ensRainbowSchema": -1 + "ensNormalize": "" }, "indexedChainIds": [11155111], "namespace": "sepolia", "plugins": ["subgraph"], "databaseSchemaName": "DeserializationSchema0.34.0", + "ensRainbowPublicConfig": { + "version": "", + "labelSet": { + "labelSetId": "", + "highestLabelSetVersion": -1 + }, + "recordsCount": -1 + }, "isSubgraphCompatible": true } } diff --git a/apps/ensadmin/src/app/mock/config-info/page.tsx b/apps/ensadmin/src/app/mock/config-info/page.tsx index a5700d460..be3d5e14c 100644 --- a/apps/ensadmin/src/app/mock/config-info/page.tsx +++ b/apps/ensadmin/src/app/mock/config-info/page.tsx @@ -13,7 +13,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import mockDataJson from "./data.json" with { type: "json" }; -const mockConfigData = mockDataJson as Record; +const mockConfigData = mockDataJson as unknown as Record; type LoadingVariant = "Loading" | "Loading Error"; type ConfigVariant = keyof typeof mockConfigData | LoadingVariant; diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index 29210bb11..f33e928db 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -507,7 +507,7 @@ function ENSNodeConfigCardContent({ {ensIndexerPublicConfig.plugins.map((plugin) => ( {plugin} @@ -561,7 +561,7 @@ function ENSNodeConfigCardContent({ icon={} version={

- v{ensIndexerPublicConfig.versionInfo.ensRainbow} + v{ensIndexerPublicConfig.ensRainbowPublicConfig.version}

} docsLink={new URL("https://ensnode.io/ensrainbow/")} @@ -571,8 +571,8 @@ function ENSNodeConfigCardContent({ label="Server LabelSet" value={

- {ensIndexerPublicConfig.labelSet.labelSetId}: - {ensIndexerPublicConfig.labelSet.labelSetVersion} + {ensIndexerPublicConfig.ensRainbowPublicConfig.labelSet.labelSetId}: + {ensIndexerPublicConfig.ensRainbowPublicConfig.labelSet.highestLabelSetVersion}

} additionalInfo={ @@ -586,6 +586,25 @@ function ENSNodeConfigCardContent({

} /> + + + {ensIndexerPublicConfig.ensRainbowPublicConfig.recordsCount.toLocaleString()} +

+ } + additionalInfo={ +

+ The total number of Rainbow Records.{" "} + + Learn more. + +

+ } + /> diff --git a/apps/ensapi/src/config/config.schema.test.ts b/apps/ensapi/src/config/config.schema.test.ts index f0905c285..407effcb2 100644 --- a/apps/ensapi/src/config/config.schema.test.ts +++ b/apps/ensapi/src/config/config.schema.test.ts @@ -32,6 +32,11 @@ const BASE_ENV = { const ENSINDEXER_PUBLIC_CONFIG = { namespace: "mainnet", databaseSchemaName: "ensapi", + ensRainbowPublicConfig: { + version: packageJson.version, + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, indexedChainIds: new Set([1]), isSubgraphCompatible: false, labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, @@ -39,8 +44,6 @@ const ENSINDEXER_PUBLIC_CONFIG = { versionInfo: { ensDb: packageJson.version, ensIndexer: packageJson.version, - ensRainbow: packageJson.version, - ensRainbowSchema: 1, ensNormalize: "1.1.1", nodejs: "1.1.1", ponder: "1.1.1", diff --git a/apps/ensapi/src/config/validations.ts b/apps/ensapi/src/config/validations.ts index 2b06e1e3e..6705ef792 100644 --- a/apps/ensapi/src/config/validations.ts +++ b/apps/ensapi/src/config/validations.ts @@ -34,12 +34,12 @@ export function invariant_ensIndexerPublicConfigVersionInfo( } // Invariant: ENSApi & ENSRainbow must match version numbers - if (ensIndexerPublicConfig.versionInfo.ensRainbow !== packageJson.version) { + if (ensIndexerPublicConfig.ensRainbowPublicConfig.version !== packageJson.version) { ctx.issues.push({ code: "custom", - path: ["ensIndexerPublicConfig.versionInfo.ensRainbow"], - input: ensIndexerPublicConfig.versionInfo.ensRainbow, - message: `Version Mismatch: ENSRainbow@${ensIndexerPublicConfig.versionInfo.ensRainbow} !== ENSApi@${packageJson.version}`, + path: ["ensIndexerPublicConfig.ensRainbowPublicConfig.version"], + input: ensIndexerPublicConfig.ensRainbowPublicConfig.version, + message: `Version Mismatch: ENSRainbow@${ensIndexerPublicConfig.ensRainbowPublicConfig.version} !== ENSApi@${packageJson.version}`, }); } } diff --git a/apps/ensindexer/src/config/public.ts b/apps/ensindexer/src/config/public.ts index 4e533f2e9..b80fa9f07 100644 --- a/apps/ensindexer/src/config/public.ts +++ b/apps/ensindexer/src/config/public.ts @@ -1,22 +1,29 @@ -import type { ENSIndexerPublicConfig } from "@ensnode/ensnode-sdk"; +import type { EnsIndexerPublicConfig } from "@ensnode/ensnode-sdk"; +import { getENSRainbowApiClient } from "@/lib/ensraibow-api-client"; import { getENSIndexerVersionInfo } from "@/lib/version-info"; -import type { ENSIndexerConfig } from "./types"; +import type { EnsIndexerConfig } from "./types"; + +const ensRainbowApiClient = getENSRainbowApiClient(); /** - * Build a public version of {@link ENSIndexerConfig}. + * Build a public version of {@link EnsIndexerConfig}. * - * Note: some values required to build an {@link ENSIndexerPublicConfig} object + * Note: some values required to build an {@link EnsIndexerPublicConfig} object * have to fetched over the network. */ export async function buildENSIndexerPublicConfig( - config: ENSIndexerConfig, -): Promise { - const versionInfo = await getENSIndexerVersionInfo(); + config: EnsIndexerConfig, +): Promise { + const [versionInfo, ensRainbowPublicConfig] = await Promise.all([ + getENSIndexerVersionInfo(), + ensRainbowApiClient.config(), + ]); return { databaseSchemaName: config.databaseSchemaName, + ensRainbowPublicConfig, labelSet: config.labelSet, indexedChainIds: config.indexedChainIds, isSubgraphCompatible: config.isSubgraphCompatible, diff --git a/apps/ensindexer/src/lib/ensdb-client/ensdb-client.mock.ts b/apps/ensindexer/src/lib/ensdb-client/ensdb-client.mock.ts index 4b7054aa6..7387cc19f 100644 --- a/apps/ensindexer/src/lib/ensdb-client/ensdb-client.mock.ts +++ b/apps/ensindexer/src/lib/ensdb-client/ensdb-client.mock.ts @@ -25,6 +25,14 @@ export const databaseSchemaName = "public"; export const publicConfig = { databaseSchemaName, + ensRainbowPublicConfig: { + version: "0.32.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -39,8 +47,6 @@ export const publicConfig = { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, }, } satisfies EnsIndexerPublicConfig; diff --git a/apps/ensindexer/src/lib/version-info.ts b/apps/ensindexer/src/lib/version-info.ts index aa1efb8d7..55bd8b65f 100644 --- a/apps/ensindexer/src/lib/version-info.ts +++ b/apps/ensindexer/src/lib/version-info.ts @@ -9,8 +9,6 @@ import { prettifyError } from "zod/v4"; import type { ENSIndexerVersionInfo, SerializedENSIndexerVersionInfo } from "@ensnode/ensnode-sdk"; import { makeENSIndexerVersionInfoSchema } from "@ensnode/ensnode-sdk/internal"; -import { getENSRainbowApiClient } from "@/lib/ensraibow-api-client"; - /** * Get NPM package version. * @@ -109,12 +107,6 @@ function getPackageVersionFromPnpmStore(pnpmDir: string, packageName: string): s * Get complete {@link ENSIndexerVersionInfo} for ENSIndexer app. */ export async function getENSIndexerVersionInfo(): Promise { - const ensRainbowApiClient = getENSRainbowApiClient(); - const { versionInfo: ensRainbowVersionInfo } = await ensRainbowApiClient.version(); - - // ENSRainbow version (fetched dynamically from the connected ENSRainbow service instance) - const ensRainbowSchema = ensRainbowVersionInfo.dbSchemaVersion; - // ENSIndexer version const ensIndexerVersion = packageJson.version; @@ -125,13 +117,11 @@ export async function getENSIndexerVersionInfo(): Promise // parse unvalidated version info const schema = makeENSIndexerVersionInfoSchema(); const parsed = schema.safeParse({ - ensRainbow: ensRainbowVersionInfo.version, nodejs: process.versions.node, ponder: getPackageVersion("ponder"), ensDb: ensDbVersion, ensIndexer: ensIndexerVersion, ensNormalize: getPackageVersion("@adraffy/ens-normalize"), - ensRainbowSchema, } satisfies SerializedENSIndexerVersionInfo); if (parsed.error) { diff --git a/docs/ensnode.io/src/content/docs/docs/usage/api.mdx b/docs/ensnode.io/src/content/docs/docs/usage/api.mdx index 52196100c..4eba56e49 100644 --- a/docs/ensnode.io/src/content/docs/docs/usage/api.mdx +++ b/docs/ensnode.io/src/content/docs/docs/usage/api.mdx @@ -86,7 +86,7 @@ The response includes several important configuration categories: functionality - **Plugins**: Activated plugins - **ENSIndexer Version Information**: Node.js version, Ponder framework version, - ENSRainbow version and schema details, ENSDb version, ENSIndexer version, [ENS Normalize version](https://www.npmjs.com/package/@adraffy/ens-normalize) + ENSRainbow public config, ENSDb version, ENSIndexer version, [ENS Normalize version](https://www.npmjs.com/package/@adraffy/ens-normalize) ### Example response @@ -118,12 +118,15 @@ The response includes several important configuration categories: ], "versionInfo": { "nodejs": "22.18.0", - "ponder": "0.11.43", - "ensDb": "0.35.0", - "ensIndexer": "0.35.0", + "ponder": "0.16.1", + "ensDb": "1.5.1", + "ensIndexer": "1.5.1", "ensNormalize": "1.11.1", - "ensRainbow": "0.34.0", - "ensRainbowSchema": 3 + "ensRainbowPublicConfig": { + "version": "1.5.1", + "labelSet": { "labelSetId": "subgraph", "highestLabelSetVersion": 0 }, + "recordsCount": 133856894 + } } } ``` diff --git a/packages/ensnode-sdk/src/ensapi/client.test.ts b/packages/ensnode-sdk/src/ensapi/client.test.ts index 1c3c6dedc..f53ea9ce0 100644 --- a/packages/ensnode-sdk/src/ensapi/client.test.ts +++ b/packages/ensnode-sdk/src/ensapi/client.test.ts @@ -64,6 +64,11 @@ const EXAMPLE_CONFIG_RESPONSE = { reason: "no-api-key", }, ensIndexerPublicConfig: { + ensRainbowPublicConfig: { + version: "0.31.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -86,8 +91,6 @@ const EXAMPLE_CONFIG_RESPONSE = { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.31.0", - ensRainbowSchema: 2, }, }, } satisfies SerializedEnsApiPublicConfig; diff --git a/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts index fb424aa60..ae15afb0c 100644 --- a/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts @@ -5,6 +5,7 @@ import { ENSNamespaceIds } from "@ensnode/datasources"; import { PluginName } from "../../ensindexer/config/types"; import { deserializeEnsApiPublicConfig } from "./deserialize"; import { serializeEnsApiPublicConfig } from "./serialize"; +import type { SerializedEnsApiPublicConfig } from "./serialized-types"; import type { EnsApiPublicConfig } from "./types"; const MOCK_ENSAPI_PUBLIC_CONFIG = { @@ -16,6 +17,11 @@ const MOCK_ENSAPI_PUBLIC_CONFIG = { ensIndexerPublicConfig: { namespace: ENSNamespaceIds.Mainnet, databaseSchemaName: "ensapi", + ensRainbowPublicConfig: { + version: "0.36.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, indexedChainIds: new Set([1]), isSubgraphCompatible: false, labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, @@ -23,8 +29,6 @@ const MOCK_ENSAPI_PUBLIC_CONFIG = { versionInfo: { ensDb: "0.36.0", ensIndexer: "0.36.0", - ensRainbow: "0.36.0", - ensRainbowSchema: 1, ensNormalize: "1.1.1", nodejs: "20.0.0", ponder: "0.5.0", @@ -48,6 +52,11 @@ describe("ENSApi Config Serialization/Deserialization", () => { ensIndexerPublicConfig: { namespace: ENSNamespaceIds.Mainnet, databaseSchemaName: "ensapi", + ensRainbowPublicConfig: { + version: "0.36.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, indexedChainIds: [1], isSubgraphCompatible: false, labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, @@ -55,14 +64,12 @@ describe("ENSApi Config Serialization/Deserialization", () => { versionInfo: { ensDb: "0.36.0", ensIndexer: "0.36.0", - ensRainbow: "0.36.0", - ensRainbowSchema: 1, ensNormalize: "1.1.1", nodejs: "20.0.0", ponder: "0.5.0", }, }, - }); + } satisfies SerializedEnsApiPublicConfig); }); }); diff --git a/packages/ensnode-sdk/src/ensindexer/client.mock.ts b/packages/ensnode-sdk/src/ensindexer/client.mock.ts index 5676b7723..84d0ceaa2 100644 --- a/packages/ensnode-sdk/src/ensindexer/client.mock.ts +++ b/packages/ensnode-sdk/src/ensindexer/client.mock.ts @@ -14,6 +14,14 @@ export const configResponseMock = { }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352], databaseSchemaName: "alphaSchema0.31.0", + ensRainbowPublicConfig: { + version: "0.31.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, isSubgraphCompatible: false, namespace: "mainnet", plugins: [ @@ -30,8 +38,6 @@ export const configResponseMock = { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.31.0", - ensRainbowSchema: 2, }, } satisfies SerializedEnsIndexerConfigResponse; diff --git a/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts b/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts index 337af0909..2258e2b8d 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts @@ -2,7 +2,7 @@ import type { EnsIndexerPublicConfig } from "./types"; export type EnsIndexerPublicConfigCompatibilityCheck = Omit< EnsIndexerPublicConfig, - "databaseSchemaName" | "versionInfo" + "databaseSchemaName" | "ensRainbowPublicConfig" | "versionInfo" >; /** diff --git a/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts index 298dbfbd0..79b1c82c0 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts @@ -11,6 +11,11 @@ describe("ENSIndexer: Config", () => { // arrange const config = { databaseSchemaName: "public", + ensRainbowPublicConfig: { + version: "0.32.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -25,8 +30,6 @@ describe("ENSIndexer: Config", () => { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, }, } satisfies EnsIndexerPublicConfig; @@ -51,6 +54,11 @@ describe("ENSIndexer: Config", () => { describe("deserialization", () => { const correctSerializedConfig = { databaseSchemaName: "public", + ensRainbowPublicConfig: { + version: "0.32.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -65,8 +73,6 @@ describe("ENSIndexer: Config", () => { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, }, } satisfies SerializedEnsIndexerPublicConfig; diff --git a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts index 01ab16613..5661d211c 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts @@ -4,7 +4,10 @@ import type { LabelSetId, LabelSetVersion, } from "../../ensrainbow"; -import { makeLabelSetIdSchema, makeLabelSetVersionSchema } from "./zod-schemas"; +import { + makeLabelSetIdSchema, + makeLabelSetVersionSchema, +} from "../../ensrainbow/zod-schemas/config"; /** * Builds a valid LabelSetId from a string. diff --git a/packages/ensnode-sdk/src/ensindexer/config/serialize.ts b/packages/ensnode-sdk/src/ensindexer/config/serialize.ts index a4e27c5e6..a24838803 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/serialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/serialize.ts @@ -19,20 +19,22 @@ export function serializeEnsIndexerPublicConfig( config: EnsIndexerPublicConfig, ): SerializedEnsIndexerPublicConfig { const { - labelSet, - indexedChainIds, databaseSchemaName, + ensRainbowPublicConfig, + indexedChainIds, isSubgraphCompatible, + labelSet, namespace, plugins, versionInfo, } = config; return { - labelSet, - indexedChainIds: serializeIndexedChainIds(indexedChainIds), databaseSchemaName, + ensRainbowPublicConfig, + indexedChainIds: serializeIndexedChainIds(indexedChainIds), isSubgraphCompatible, + labelSet, namespace, plugins, versionInfo, diff --git a/packages/ensnode-sdk/src/ensindexer/config/types.ts b/packages/ensnode-sdk/src/ensindexer/config/types.ts index f3067937d..afc70c50d 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/types.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/types.ts @@ -1,6 +1,6 @@ import type { ENSNamespaceId } from "@ensnode/datasources"; -import type { EnsRainbowClientLabelSet } from "../../ensrainbow"; +import type { EnsRainbowClientLabelSet, EnsRainbowPublicConfig } from "../../ensrainbow"; import type { ChainId } from "../../shared/types"; /** @@ -50,18 +50,6 @@ export interface EnsIndexerVersionInfo { **/ ensIndexer: string; - /** - * ENSRainbow service version - * - * @see https://ghcr.io/namehash/ensnode/ensindexer - **/ - ensRainbow: string; - - /** - * ENSRainbow schema version - **/ - ensRainbowSchema: number; - /** * ENS Normalize package version * @@ -108,6 +96,13 @@ export interface EnsIndexerPublicConfig { */ databaseSchemaName: string; + /** + * ENSRainbow public config + * + * Represents the public config of the connected ENSRainbow instance. + */ + ensRainbowPublicConfig: EnsRainbowPublicConfig; + /** * A set of strings referring to the names of plugins that are active. * diff --git a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts index 60bc78860..182251613 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts @@ -110,8 +110,6 @@ describe("ENSIndexer: Config", () => { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, } satisfies EnsIndexerVersionInfo), ).toStrictEqual({ nodejs: "v22.22.22", @@ -119,8 +117,6 @@ describe("ENSIndexer: Config", () => { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, } satisfies EnsIndexerVersionInfo); expect( @@ -131,8 +127,6 @@ describe("ENSIndexer: Config", () => { ensDb: "", ensIndexer: "", ensNormalize: "", - ensRainbow: "", - ensRainbowSchema: -1, } satisfies EnsIndexerVersionInfo), ), ).toStrictEqual(`✖ Value must be a non-empty string. @@ -144,15 +138,84 @@ describe("ENSIndexer: Config", () => { ✖ Value must be a non-empty string. → at ensIndexer ✖ Value must be a non-empty string. - → at ensNormalize -✖ Value must be a non-empty string. - → at ensRainbow -✖ Value must be a positive integer (>0). - → at ensRainbowSchema`); + → at ensNormalize`); + }); + + it("validates ensDb and ensIndexer versions match", () => { + expect( + formatParseError( + makeEnsIndexerVersionInfoSchema().safeParse({ + nodejs: "v22.22.22", + ponder: "0.11.25", + ensDb: "0.32.0", + ensIndexer: "0.33.0", // Different from ensDb + ensNormalize: "1.11.1", + } satisfies EnsIndexerVersionInfo), + ), + ).toContain("`ensDb` version must be same as `ensIndexer` version"); + }); + + it("validates ENSRainbow label set and version compatibility", () => { + const baseConfig = { + ensRainbowPublicConfig: { + version: "0.32.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, + indexedChainIds: [1], // Use array for serialized config + isSubgraphCompatible: false, // Set to false to bypass isSubgraphCompatible invariant + namespace: "mainnet" as const, + plugins: [PluginName.Subgraph, PluginName.Registrars], // Multiple plugins allowed when not subgraph compatible + databaseSchemaName: "test_schema", + versionInfo: { + nodejs: "v22.22.22", + ponder: "0.11.25", + ensDb: "0.32.0", + ensIndexer: "0.32.0", + ensNormalize: "1.11.1", + }, + }; + + // Test mismatched label set IDs + expect( + formatParseError( + makeEnsIndexerPublicConfigSchema().safeParse( + buildUnvalidatedEnsIndexerPublicConfig({ + ...baseConfig, + labelSet: { labelSetId: "custom-labels", labelSetVersion: 0 }, + }), + ), + ), + ).toContain( + 'Server label set ID "subgraph" does not match client\'s requested label set ID "custom-labels"', + ); + + // Test server version too low + expect( + formatParseError( + makeEnsIndexerPublicConfigSchema().safeParse( + buildUnvalidatedEnsIndexerPublicConfig({ + ...baseConfig, + labelSet: { labelSetId: "subgraph", labelSetVersion: 5 }, + }), + ), + ), + ).toContain("Server highest label set version 0 is less than client's requested version 5"); }); it("can parse full ENSIndexerPublicConfig with label set", () => { const validConfig = { + ensRainbowPublicConfig: { + version: "0.32.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -168,8 +231,6 @@ describe("ENSIndexer: Config", () => { ensDb: "0.32.0", ensIndexer: "0.32.0", ensNormalize: "1.11.1", - ensRainbow: "0.32.0", - ensRainbowSchema: 2, }, } satisfies SerializedEnsIndexerPublicConfig; diff --git a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts index 5f221d558..c491b8844 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts @@ -8,15 +8,17 @@ */ import { z } from "zod/v4"; -import { uniq } from "../../shared/collections"; +import type { EnsRainbowClientLabelSet, EnsRainbowServerLabelSet } from "../../ensrainbow/types"; import { - makeChainIdSchema, - makeENSNamespaceIdSchema, - makeNonNegativeIntegerSchema, - makePositiveIntegerSchema, -} from "../../shared/zod-schemas"; + makeEnsRainbowPublicConfigSchema, + makeLabelSetIdSchema, + makeLabelSetVersionSchema, +} from "../../ensrainbow/zod-schemas/config"; +import { uniq } from "../../shared/collections"; +import { makeChainIdSchema, makeENSNamespaceIdSchema } from "../../shared/zod-schemas"; import type { ZodCheckFnInput } from "../../shared/zod-types"; import { isSubgraphCompatible } from "./is-subgraph-compatible"; +import { validateSupportedLabelSetAndVersion } from "./labelset-utils"; import type { EnsIndexerPublicConfig } from "./types"; import { PluginName } from "./types"; import { invariant_ensDbVersionIsSameAsEnsIndexerVersion } from "./validations"; @@ -69,38 +71,6 @@ export const makeDatabaseSchemaNameSchema = (valueLabel: string = "Database sche error: `${valueLabel} is required and must be a non-empty string.`, }); -/** - * Makes a schema for parsing a label set ID. - * - * The label set ID is guaranteed to be a string between 1-50 characters - * containing only lowercase letters (a-z) and hyphens (-). - * - * @param valueLabel - The label to use in error messages (e.g., "Label set ID", "LABEL_SET_ID") - */ -export const makeLabelSetIdSchema = (valueLabel: string) => { - return z - .string({ error: `${valueLabel} must be a string` }) - .min(1, { error: `${valueLabel} must be 1-50 characters long` }) - .max(50, { error: `${valueLabel} must be 1-50 characters long` }) - .regex(/^[a-z-]+$/, { - error: `${valueLabel} can only contain lowercase letters (a-z) and hyphens (-)`, - }); -}; - -/** - * Makes a schema for parsing a label set version. - * - * The label set version is guaranteed to be a non-negative integer. - * - * @param valueLabel - The label to use in error messages (e.g., "Label set version", "LABEL_SET_VERSION") - - */ -export const makeLabelSetVersionSchema = (valueLabel: string) => { - return z.coerce - .number({ error: `${valueLabel} must be an integer.` }) - .pipe(makeNonNegativeIntegerSchema(valueLabel)); -}; - /** * Makes a schema for parsing a label set where both label set ID and label set version are required. * @@ -127,15 +97,13 @@ const makeNonEmptyStringSchema = (valueLabel: string = "Value") => export const makeEnsIndexerVersionInfoSchema = (valueLabel: string = "Value") => z - .strictObject( + .object( { nodejs: makeNonEmptyStringSchema(), ponder: makeNonEmptyStringSchema(), ensDb: makeNonEmptyStringSchema(), ensIndexer: makeNonEmptyStringSchema(), ensNormalize: makeNonEmptyStringSchema(), - ensRainbow: makeNonEmptyStringSchema(), - ensRainbowSchema: makePositiveIntegerSchema(), }, { error: `${valueLabel} must be a valid ENSIndexerVersionInfo object.`, @@ -165,6 +133,26 @@ export function invariant_isSubgraphCompatibleRequirements( } } +export function invariant_ensRainbowSupportedLabelSetAndVersion( + ctx: ZodCheckFnInput>, +) { + const clientLabelSet = ctx.value.labelSet satisfies EnsRainbowClientLabelSet; + const serverLabelSet = ctx.value.ensRainbowPublicConfig + .labelSet satisfies EnsRainbowServerLabelSet; + + try { + validateSupportedLabelSetAndVersion(serverLabelSet, clientLabelSet); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: `The ENSRainbow label set and version specified in the config are not supported by the ENSRainbow version specified in ensRainbowPublicConfig. Cause: ${errorMessage}`, + }); + } +} + /** * ENSIndexer Public Config Schema * @@ -174,14 +162,17 @@ export function invariant_isSubgraphCompatibleRequirements( export const makeEnsIndexerPublicConfigSchema = (valueLabel: string = "ENSIndexerPublicConfig") => z .object({ - labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), + databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`), + ensRainbowPublicConfig: makeEnsRainbowPublicConfigSchema( + `${valueLabel}.ensRainbowPublicConfig`, + ), indexedChainIds: makeIndexedChainIdsSchema(`${valueLabel}.indexedChainIds`), isSubgraphCompatible: z.boolean({ error: `${valueLabel}.isSubgraphCompatible must be a boolean value.`, }), + labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`), plugins: makePluginsListSchema(`${valueLabel}.plugins`), - databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`), versionInfo: makeEnsIndexerVersionInfoSchema(`${valueLabel}.versionInfo`), }) /** @@ -189,7 +180,8 @@ export const makeEnsIndexerPublicConfigSchema = (valueLabel: string = "ENSIndexe * * All required data validations must be performed below. */ - .check(invariant_isSubgraphCompatibleRequirements); + .check(invariant_isSubgraphCompatibleRequirements) + .check(invariant_ensRainbowSupportedLabelSetAndVersion); /** * ENSIndexer Public Config Schema @@ -202,13 +194,16 @@ export const makeSerializedEnsIndexerPublicConfigSchema = ( valueLabel: string = "Serialized ENSIndexerPublicConfig", ) => z.object({ - labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), + databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`), + ensRainbowPublicConfig: makeEnsRainbowPublicConfigSchema( + `${valueLabel}.ensRainbowPublicConfig`, + ), indexedChainIds: makeSerializedIndexedChainIdsSchema(`${valueLabel}.indexedChainIds`), isSubgraphCompatible: z.boolean({ error: `${valueLabel}.isSubgraphCompatible must be a boolean value.`, }), + labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`), plugins: makePluginsListSchema(`${valueLabel}.plugins`), - databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`), versionInfo: makeEnsIndexerVersionInfoSchema(`${valueLabel}.versionInfo`), }); diff --git a/packages/ensnode-sdk/src/ensrainbow/config.ts b/packages/ensnode-sdk/src/ensrainbow/config.ts new file mode 100644 index 000000000..d5f141c59 --- /dev/null +++ b/packages/ensnode-sdk/src/ensrainbow/config.ts @@ -0,0 +1,26 @@ +import type { EnsRainbowServerLabelSet } from "./types"; + +/** + * Complete public configuration object for ENSRainbow. + * + * Contains all public configuration information about the ENSRainbow service instance, + * including version, label set information, and record counts. + */ +export interface EnsRainbowPublicConfig { + /** + * ENSRainbow service version + * + * @see https://ghcr.io/namehash/ensnode/ensrainbow + */ + version: string; + + /** + * The label set reference managed by the ENSRainbow server. + */ + labelSet: EnsRainbowServerLabelSet; + + /** + * The total count of records managed by the ENSRainbow service. + */ + recordsCount: number; +} diff --git a/packages/ensnode-sdk/src/ensrainbow/index.ts b/packages/ensnode-sdk/src/ensrainbow/index.ts index eea524d65..b4fb84ab2 100644 --- a/packages/ensnode-sdk/src/ensrainbow/index.ts +++ b/packages/ensnode-sdk/src/ensrainbow/index.ts @@ -1 +1,2 @@ +export * from "./config"; export * from "./types"; diff --git a/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts new file mode 100644 index 000000000..d0022f310 --- /dev/null +++ b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts @@ -0,0 +1,49 @@ +import { z } from "zod/v4"; + +import { makeNonNegativeIntegerSchema } from "../../shared/zod-schemas"; + +/** + * Makes a schema for parsing a label set ID. + * + * The label set ID is guaranteed to be a string between 1-50 characters + * containing only lowercase letters (a-z) and hyphens (-). + * + * @param valueLabel - The label to use in error messages (e.g., "Label set ID", "LABEL_SET_ID") + */ +export const makeLabelSetIdSchema = (valueLabel: string = "Label set ID") => { + return z + .string({ error: `${valueLabel} must be a string` }) + .min(1, { error: `${valueLabel} must be 1-50 characters long` }) + .max(50, { error: `${valueLabel} must be 1-50 characters long` }) + .regex(/^[a-z-]+$/, { + error: `${valueLabel} can only contain lowercase letters (a-z) and hyphens (-)`, + }); +}; + +/** + * Makes a schema for parsing a label set version. + * + * The label set version is guaranteed to be a non-negative integer. + * + * @param valueLabel - The label to use in error messages (e.g., "Label set version", "LABEL_SET_VERSION") + */ +export const makeLabelSetVersionSchema = (valueLabel: string = "Label set version") => { + return z.coerce + .number({ error: `${valueLabel} must be an integer.` }) + .pipe(makeNonNegativeIntegerSchema(valueLabel)); +}; + +/** + * Makes a schema for parsing the EnsRainbowPublicConfig object. + */ +export const makeEnsRainbowPublicConfigSchema = (valueLabel: string = "EnsRainbowPublicConfig") => + z.object({ + version: z.string().nonempty({ error: `${valueLabel}.version must be a non-empty string.` }), + labelSet: z.object({ + labelSetId: makeLabelSetIdSchema(`${valueLabel}.labelSet.labelSetId`), + highestLabelSetVersion: makeLabelSetVersionSchema( + `${valueLabel}.labelSet.highestLabelSetVersion`, + ), + }), + recordsCount: makeNonNegativeIntegerSchema(`${valueLabel}.recordsCount`), + }); diff --git a/packages/ensrainbow-sdk/src/client.ts b/packages/ensrainbow-sdk/src/client.ts index 3a81a5dc5..5f8fe0d87 100644 --- a/packages/ensrainbow-sdk/src/client.ts +++ b/packages/ensrainbow-sdk/src/client.ts @@ -3,6 +3,7 @@ import { type Cache, type EncodedLabelHash, type EnsRainbowClientLabelSet, + type EnsRainbowPublicConfig, type EnsRainbowServerLabelSet, type Label, type LabelHash, @@ -168,24 +169,7 @@ export namespace EnsRainbow { * Contains all public configuration information about the ENSRainbow service instance, * including version, label set information, and record counts. */ - export interface ENSRainbowPublicConfig { - /** - * ENSRainbow service version - * - * @see https://ghcr.io/namehash/ensnode/ensrainbow - */ - version: string; - - /** - * The label set reference managed by the ENSRainbow server. - */ - labelSet: EnsRainbowServerLabelSet; - - /** - * The total count of records managed by the ENSRainbow service. - */ - recordsCount: number; - } + export type ENSRainbowPublicConfig = EnsRainbowPublicConfig; } export interface EnsRainbowApiClientOptions {