diff --git a/.changeset/all-kids-smash.md b/.changeset/all-kids-smash.md new file mode 100644 index 0000000000..98827bd74d --- /dev/null +++ b/.changeset/all-kids-smash.md @@ -0,0 +1,15 @@ +--- +"@ensnode/ensrainbow-sdk": minor +"@ensnode/ensnode-sdk": minor +--- + +**Breaking**: Updated core ENSNode data models. + +- `EnsIndexerPublicConfig` + - Renamed `labelSet` field to `clientLabelSet`. +- `EnsRainbowApiClientOptions` + - Renamed `labelSet` field to `clientLabelSet`. +- `EnsRainbowPublicConfig` + - Replaced `version: string` field with `versionInfo: EnsRainbowVersionInfo`. + - Renamed `labelSet` field to `serverLabelSet`. + - Removed `recordsCount` field from `EnsRainbowPublicConfig`. diff --git a/.changeset/calm-ravens-feel.md b/.changeset/calm-ravens-feel.md new file mode 100644 index 0000000000..643262d81b --- /dev/null +++ b/.changeset/calm-ravens-feel.md @@ -0,0 +1,5 @@ +--- +"ensadmin": minor +--- + +Removed _Records Count_ info from the ENSRainbow card UI on the _Connection_ page. diff --git a/.changeset/clear-rabbits-punch.md b/.changeset/clear-rabbits-punch.md new file mode 100644 index 0000000000..19b91950c7 --- /dev/null +++ b/.changeset/clear-rabbits-punch.md @@ -0,0 +1,5 @@ +--- +"ensadmin": minor +--- + +Renamed `ENSNodeConfig*` components to follow the `EnsNodeStackInfo*` pattern. diff --git a/.changeset/fifty-games-smash.md b/.changeset/fifty-games-smash.md new file mode 100644 index 0000000000..472f0e246b --- /dev/null +++ b/.changeset/fifty-games-smash.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-sdk": minor +--- + +Introduced a set of "stack info" data models: `EnsIndexerStackInfo`, `EnsNodeStackInfo`. diff --git a/apps/ensadmin/src/app/@breadcrumbs/mock/config-info/page.tsx b/apps/ensadmin/src/app/@breadcrumbs/mock/stack-info/page.tsx similarity index 100% rename from apps/ensadmin/src/app/@breadcrumbs/mock/config-info/page.tsx rename to apps/ensadmin/src/app/@breadcrumbs/mock/stack-info/page.tsx diff --git a/apps/ensadmin/src/app/mock/config-info/data.json b/apps/ensadmin/src/app/mock/config-info/data.json deleted file mode 100644 index c339d9615a..0000000000 --- a/apps/ensadmin/src/app/mock/config-info/data.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "Alpha Mainnet": { - "versionInfo": { - "ensApi": "0.35.0", - "ensNormalize": "1.11.1" - }, - "theGraphFallback": { - "canFallback": false, - "reason": "no-api-key" - }, - "ensIndexerPublicConfig": { - "labelSet": { - "labelSetId": "subgraph", - "labelSetVersion": 0 - }, - "indexedChainIds": [1, 8453, 59144, 10, 42161, 534352, 567], - "ensIndexerSchemaName": "alphaSchema0.34.0", - "ensRainbowPublicConfig": { - "version": "0.34.0", - "labelSet": { - "labelSetId": "subgraph", - "highestLabelSetVersion": 0 - }, - "recordsCount": 100 - }, - "isSubgraphCompatible": false, - "namespace": "mainnet", - "plugins": [ - "subgraph", - "basenames", - "lineanames", - "threedns", - "protocol-acceleration", - "registrars", - "tokenscope" - ], - "versionInfo": { - "nodejs": "22.18.0", - "ponder": "0.11.43", - "ensDb": "0.35.0", - "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1" - } - } - }, - "Alpha Sepolia": { - "versionInfo": { - "ensApi": "0.35.0", - "ensNormalize": "1.11.1" - }, - "theGraphFallback": { - "canFallback": true, - "url": "" - }, - "ensIndexerPublicConfig": { - "labelSet": { - "labelSetId": "subgraph", - "labelSetVersion": 0 - }, - "versionInfo": { - "nodejs": "22.18.0", - "ponder": "0.11.43", - "ensDb": "0.35.0", - "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1" - }, - "indexedChainIds": [11155111, 84532, 59141, 11155420, 421614, 534351], - "namespace": "sepolia", - "plugins": [ - "subgraph", - "basenames", - "lineanames", - "threedns", - "protocol-acceleration", - "registrars" - ], - "ensIndexerSchemaName": "alphaSepoliaSchema0.34.0", - "ensRainbowPublicConfig": { - "version": "0.34.0", - "labelSet": { - "labelSetId": "subgraph", - "highestLabelSetVersion": 0 - }, - "recordsCount": 100 - }, - "isSubgraphCompatible": false - } - }, - "Subgraph Mainnet": { - "versionInfo": { - "ensApi": "0.35.0", - "ensNormalize": "1.11.1" - }, - "theGraphFallback": { - "canFallback": false, - "reason": "no-api-key" - }, - "ensIndexerPublicConfig": { - "labelSet": { - "labelSetId": "subgraph", - "labelSetVersion": 0 - }, - "versionInfo": { - "nodejs": "22.18.0", - "ponder": "0.11.43", - "ensDb": "0.35.0", - "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1" - }, - "indexedChainIds": [1], - "namespace": "mainnet", - "plugins": ["subgraph"], - "ensIndexerSchemaName": "mainnetSchema0.34.0", - "ensRainbowPublicConfig": { - "version": "0.34.0", - "labelSet": { - "labelSetId": "subgraph", - "highestLabelSetVersion": 0 - }, - "recordsCount": 100 - }, - "isSubgraphCompatible": true - } - }, - "Subgraph Sepolia": { - "versionInfo": { - "ensApi": "0.35.0", - "ensNormalize": "1.11.1" - }, - "theGraphFallback": { - "canFallback": false, - "reason": "no-api-key" - }, - "ensIndexerPublicConfig": { - "labelSet": { - "labelSetId": "subgraph", - "labelSetVersion": 0 - }, - "versionInfo": { - "nodejs": "22.18.0", - "ponder": "0.11.43", - "ensDb": "0.35.0", - "ensIndexer": "0.35.0", - "ensNormalize": "1.11.1" - }, - "indexedChainIds": [11155111], - "namespace": "sepolia", - "plugins": ["subgraph"], - "ensIndexerSchemaName": "sepoliaSchema0.34.0", - "ensRainbowPublicConfig": { - "version": "0.34.0", - "labelSet": { - "labelSetId": "subgraph", - "highestLabelSetVersion": 0 - }, - "recordsCount": 100 - }, - "isSubgraphCompatible": true - } - }, - "Serialization Error": { - "versionInfo": { - "ensApi": "0.35.0", - "ensNormalize": "1.11.1" - }, - "theGraphFallback": { - "canFallback": false, - "reason": "no-api-key" - }, - "ensIndexerPublicConfig": { - "labelSet": { - "labelSetId": "", - "labelSetVersion": 0 - }, - "versionInfo": { - "nodejs": "", - "ponder": "", - "ensDb": "", - "ensIndexer": "", - "ensNormalize": "" - }, - "indexedChainIds": [11155111], - "namespace": "sepolia", - "plugins": ["subgraph"], - "ensIndexerSchemaName": "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 deleted file mode 100644 index bc6edc3527..0000000000 --- a/apps/ensadmin/src/app/mock/config-info/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -"use client"; - -import { useMemo, useState } from "react"; - -import { - buildEnsNodeStackInfo, - deserializeENSApiPublicConfig, - type EnsDbPublicConfig, - SerializedENSApiPublicConfig, -} from "@ensnode/ensnode-sdk"; - -import { - ENSNodeConfigInfoView, - ENSNodeConfigInfoViewProps, -} from "@/components/connection/cards/ensnode-info"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; - -import mockDataJson from "./data.json" with { type: "json" }; - -const mockConfigData = mockDataJson as unknown as Record; - -type LoadingVariant = "Loading" | "Loading Error"; -type ConfigVariant = keyof typeof mockConfigData | LoadingVariant; - -const DEFAULT_VARIANT = "Alpha Mainnet"; -export default function MockConfigPage() { - const [selectedConfig, setSelectedConfig] = useState(DEFAULT_VARIANT); - const props: ENSNodeConfigInfoViewProps = useMemo(() => { - switch (selectedConfig) { - case "Loading": - return { isLoading: true }; - - case "Loading Error": - return { - error: { - title: "ENSNodeConfigInfo Error", - description: "Failed to fetch ENSIndexer Config.", - }, - }; - - default: - try { - const ensApiPublicConfig = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]); - const ensDbPublicConfig = { - versionInfo: { - postgresql: "18.1", - }, - } satisfies EnsDbPublicConfig; - return { - ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig), - } satisfies ENSNodeConfigInfoViewProps; - } catch (error) { - const errorMessage = - error instanceof Error - ? error.message - : "Unknown ENSIndexerPublicConfig deserialization error"; - return { - error: { - title: "Deserialization Error", - description: errorMessage, - }, - }; - } - } - }, [selectedConfig]); - - return ( -
- - - Mock: ENSNodeConfigInfo - Select a mock ENSNodeConfigInfo variant - - - -
- {[...Object.keys(mockConfigData), "Loading", "Loading Error"].map((variant) => ( - - ))} -
-
-
- - -
- ); -} diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index b23533fbc5..13de3f219d 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -16,7 +16,7 @@ import { type SerializedEnsDbPublicConfig, type SerializedEnsIndexerPublicConfig, type SerializedEnsNodeStackInfo, - SerializedEnsRainbowPublicConfig, + type SerializedEnsRainbowPublicConfig, type SerializedOmnichainIndexingStatusSnapshotBackfill, type SerializedOmnichainIndexingStatusSnapshotCompleted, type SerializedOmnichainIndexingStatusSnapshotFollowing, @@ -24,19 +24,20 @@ import { } from "@ensnode/ensnode-sdk"; const serializedEnsIndexerPublicConfig = { - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352, 567], - ensIndexerSchemaName: "alphaSchema0.34.0", + ensIndexerSchemaName: "alphaSchema1.9.0", ensRainbowPublicConfig: { - version: "0.34.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, - recordsCount: 100, + versionInfo: { + ensRainbow: "1.9.0", + }, }, isSubgraphCompatible: false, namespace: "mainnet", @@ -51,8 +52,8 @@ const serializedEnsIndexerPublicConfig = { ], versionInfo: { ponder: "0.11.43", - ensIndexer: "0.35.0", - ensDb: "0.35.0", + ensIndexer: "1.9.0", + ensDb: "1.9.0", ensNormalize: "1.11.1", }, } satisfies SerializedEnsIndexerPublicConfig; @@ -64,7 +65,7 @@ export const serializedEnsApiPublicConfig = { url: "https://api.thegraph.com/subgraphs/name/ensdomains/ens", }, versionInfo: { - ensApi: "0.35.0", + ensApi: "1.9.0", ensNormalize: "1.11.1", }, } satisfies SerializedEnsApiPublicConfig; diff --git a/apps/ensadmin/src/app/mock/page.tsx b/apps/ensadmin/src/app/mock/page.tsx index ebbfc8937b..27eae62019 100644 --- a/apps/ensadmin/src/app/mock/page.tsx +++ b/apps/ensadmin/src/app/mock/page.tsx @@ -19,8 +19,8 @@ export default function MockList() {
+ ), + )} +
+
+ + + + + ); +} diff --git a/apps/ensadmin/src/app/mock/stack-info/stack-info.mock.ts b/apps/ensadmin/src/app/mock/stack-info/stack-info.mock.ts new file mode 100644 index 0000000000..4a6d1b11c4 --- /dev/null +++ b/apps/ensadmin/src/app/mock/stack-info/stack-info.mock.ts @@ -0,0 +1,301 @@ +import type { + SerializedEnsApiPublicConfig, + SerializedEnsIndexerPublicConfig, + SerializedEnsNodeStackInfo, + TheGraphFallback, +} from "@ensnode/ensnode-sdk"; + +// ============================================================================ +// Shared Constants +// ============================================================================ + +const COMMON_ENSDB_CONFIG = { + versionInfo: { + postgresql: "18.1", + }, +} as const; + +const COMMON_ENSINDEXER_VERSION_INFO = { + ponder: "0.11.43", + ensDb: "1.9.0", + ensIndexer: "1.9.0", + ensNormalize: "1.11.1", +} as const; + +const COMMON_ENSAPI_VERSION_INFO = { + ensApi: "1.9.0", + ensNormalize: "1.11.1", +} as const; + +const COMMON_CLIENT_LABEL_SET = { + labelSetId: "subgraph", + labelSetVersion: 0, +} as const; + +const COMMON_ENSRAINBOW_CONFIG = { + serverLabelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + versionInfo: { + ensRainbow: "1.9.0", + }, +} as const; + +const THE_GRAPH_FALLBACK_DISABLED: TheGraphFallback = { + canFallback: false, + reason: "no-api-key", +} as const; + +// ============================================================================ +// Variant-Specific Configurations +// ============================================================================ + +const ALPHA_PLUGINS = [ + "subgraph", + "basenames", + "lineanames", + "threedns", + "protocol-acceleration", + "registrars", + "tokenscope", +] as const satisfies string[]; + +const ALPHA_SEPOLIA_PLUGINS = [ + "subgraph", + "basenames", + "lineanames", + "threedns", + "protocol-acceleration", + "registrars", +] as const satisfies string[]; + +const SUBGRAPH_PLUGINS = ["subgraph"] as const satisfies string[]; + +const ALPHA_MAINNET_CHAINS: SerializedEnsIndexerPublicConfig["indexedChainIds"] = [ + 1, 8453, 59144, 10, 42161, 534352, 567, +]; +const ALPHA_SEPOLIA_CHAINS: SerializedEnsIndexerPublicConfig["indexedChainIds"] = [ + 11155111, 84532, 59141, 11155420, 421614, 534351, +]; +const SUBGRAPH_MAINNET_CHAINS: SerializedEnsIndexerPublicConfig["indexedChainIds"] = [1]; +const SUBGRAPH_SEPOLIA_CHAINS: SerializedEnsIndexerPublicConfig["indexedChainIds"] = [11155111]; + +// ============================================================================ +// Helper Functions for Creating Variants +// ============================================================================ + +function createEnsDbConfig() { + return { ...COMMON_ENSDB_CONFIG }; +} + +function createEnsRainbowConfig() { + return { ...COMMON_ENSRAINBOW_CONFIG }; +} + +function createEnsIndexerConfig( + namespace: SerializedEnsIndexerPublicConfig["namespace"], + indexedChainIds: SerializedEnsIndexerPublicConfig["indexedChainIds"], + plugins: SerializedEnsIndexerPublicConfig["plugins"], + ensIndexerSchemaName: string, + isSubgraphCompatible: boolean, +): SerializedEnsIndexerPublicConfig { + return { + clientLabelSet: { ...COMMON_CLIENT_LABEL_SET }, + indexedChainIds, + ensIndexerSchemaName, + isSubgraphCompatible, + namespace, + plugins, + versionInfo: { ...COMMON_ENSINDEXER_VERSION_INFO }, + ensRainbowPublicConfig: createEnsRainbowConfig(), + }; +} + +function createEnsApiConfig( + namespace: SerializedEnsIndexerPublicConfig["namespace"], + indexedChainIds: SerializedEnsIndexerPublicConfig["indexedChainIds"], + plugins: SerializedEnsIndexerPublicConfig["plugins"], + ensIndexerSchemaName: string, + isSubgraphCompatible: boolean, + theGraphFallback: TheGraphFallback, +): SerializedEnsApiPublicConfig { + return { + versionInfo: { ...COMMON_ENSAPI_VERSION_INFO }, + theGraphFallback, + ensIndexerPublicConfig: { + ...createEnsIndexerConfig( + namespace, + indexedChainIds, + plugins, + ensIndexerSchemaName, + isSubgraphCompatible, + ), + }, + }; +} +function createAlphaEnsIndexerConfig( + namespace: "mainnet" | "sepolia", + isMainnet: boolean, +): SerializedEnsIndexerPublicConfig { + return createEnsIndexerConfig( + namespace, + isMainnet ? ALPHA_MAINNET_CHAINS : ALPHA_SEPOLIA_CHAINS, + [...(isMainnet ? ALPHA_PLUGINS : ALPHA_SEPOLIA_PLUGINS)], + isMainnet ? "alphaSchema1.9.0" : "alphaSepoliaSchema1.9.0", + false, + ); +} + +function createAlphaEnsApiConfig( + namespace: "mainnet" | "sepolia", + isMainnet: boolean, + theGraphFallback: TheGraphFallback, +): SerializedEnsApiPublicConfig { + return createEnsApiConfig( + namespace, + isMainnet ? ALPHA_MAINNET_CHAINS : ALPHA_SEPOLIA_CHAINS, + [...(isMainnet ? ALPHA_PLUGINS : ALPHA_SEPOLIA_PLUGINS)], + isMainnet ? "alphaSchema1.9.0" : "alphaSepoliaSchema1.9.0", + false, + theGraphFallback, + ); +} + +function createSubgraphEnsIndexerConfig( + namespace: "mainnet" | "sepolia", + isMainnet: boolean, +): SerializedEnsIndexerPublicConfig { + return createEnsIndexerConfig( + namespace, + isMainnet ? SUBGRAPH_MAINNET_CHAINS : SUBGRAPH_SEPOLIA_CHAINS, + [...SUBGRAPH_PLUGINS], + isMainnet ? "mainnetSchema1.9.0" : "sepoliaSchema1.9.0", + true, + ); +} + +function createSubgraphEnsApiConfig( + namespace: "mainnet" | "sepolia", + isMainnet: boolean, +): SerializedEnsApiPublicConfig { + return createEnsApiConfig( + namespace, + isMainnet ? SUBGRAPH_MAINNET_CHAINS : SUBGRAPH_SEPOLIA_CHAINS, + [...SUBGRAPH_PLUGINS], + isMainnet ? "mainnetSchema1.9.0" : "sepoliaSchema1.9.0", + true, + { ...THE_GRAPH_FALLBACK_DISABLED }, + ); +} + +// ============================================================================ +// Error Variant (Deserialization Error) +// ============================================================================ + +function createDeserializationErrorVariant(): SerializedEnsNodeStackInfo { + return { + ensApi: { + versionInfo: { ...COMMON_ENSAPI_VERSION_INFO }, + theGraphFallback: { ...THE_GRAPH_FALLBACK_DISABLED }, + ensIndexerPublicConfig: { + clientLabelSet: { + labelSetId: "", + labelSetVersion: 0, + }, + versionInfo: { + ponder: "", + ensDb: "", + ensIndexer: "", + ensNormalize: "", + }, + indexedChainIds: [11155111], + namespace: "sepolia", + plugins: ["subgraph"], + ensIndexerSchemaName: "DeserializationSchema1.9.0", + isSubgraphCompatible: true, + ensRainbowPublicConfig: { + serverLabelSet: { + labelSetId: "", + highestLabelSetVersion: -1, + }, + versionInfo: { + ensRainbow: "", + }, + }, + }, + }, + ensDb: createEnsDbConfig(), + ensIndexer: { + clientLabelSet: { + labelSetId: "", + labelSetVersion: 0, + }, + versionInfo: { + ponder: "", + ensDb: "", + ensIndexer: "", + ensNormalize: "", + }, + indexedChainIds: [11155111], + namespace: "sepolia", + plugins: ["subgraph"], + ensIndexerSchemaName: "DeserializationSchema1.9.0", + isSubgraphCompatible: true, + ensRainbowPublicConfig: { + versionInfo: { + ensRainbow: "", + }, + serverLabelSet: { + labelSetId: "", + highestLabelSetVersion: -1, + }, + }, + }, + ensRainbow: { + versionInfo: { + ensRainbow: "", + }, + serverLabelSet: { + labelSetId: "", + highestLabelSetVersion: -1, + }, + }, + }; +} + +// ============================================================================ +// Record of Mock Variants +// ============================================================================ + +/** + * Record of mock SerializedEnsNodeStackInfo objects keyed by variant name. + * These can be deserialized to simulate the full deserialization process. + */ +export const mockSerializedEnsNodeStackInfo = { + "Alpha Mainnet": { + ensApi: createAlphaEnsApiConfig("mainnet", true, { ...THE_GRAPH_FALLBACK_DISABLED }), + ensDb: createEnsDbConfig(), + ensIndexer: createAlphaEnsIndexerConfig("mainnet", true), + ensRainbow: createEnsRainbowConfig(), + }, + "Alpha Sepolia": { + ensApi: createAlphaEnsApiConfig("sepolia", false, { canFallback: true, url: "" }), + ensDb: createEnsDbConfig(), + ensIndexer: createAlphaEnsIndexerConfig("sepolia", false), + ensRainbow: createEnsRainbowConfig(), + }, + "Subgraph Mainnet": { + ensApi: createSubgraphEnsApiConfig("mainnet", true), + ensDb: createEnsDbConfig(), + ensIndexer: createSubgraphEnsIndexerConfig("mainnet", true), + ensRainbow: createEnsRainbowConfig(), + }, + "Subgraph Sepolia": { + ensApi: createSubgraphEnsApiConfig("sepolia", false), + ensDb: createEnsDbConfig(), + ensIndexer: createSubgraphEnsIndexerConfig("sepolia", false), + ensRainbow: createEnsRainbowConfig(), + }, + "Deserialization Error": createDeserializationErrorVariant(), +} as const satisfies Record; diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-stack-info.tsx similarity index 89% rename from apps/ensadmin/src/components/connection/cards/ensnode-info.tsx rename to apps/ensadmin/src/components/connection/cards/ensnode-stack-info.tsx index 47baa1022f..d0b9f4bf2b 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-stack-info.tsx @@ -93,27 +93,29 @@ function ENSNodeCardLoadingSkeleton() { } /** - * Props for ENSNodeConfigCardDisplay - display component that accepts props for testing/mocking + * Props for EnsNodeStackInfoCardDisplay - display component that accepts props for testing/mocking */ -export interface ENSNodeConfigCardDisplayProps { +export interface EnsNodeStackInfoCardDisplayProps { ensNodeStackInfo: EnsNodeStackInfo; } /** * Display component that receives props - used for reusable/mockable presentation */ -export function ENSNodeConfigCardDisplay({ ensNodeStackInfo }: ENSNodeConfigCardDisplayProps) { +export function EnsNodeStackInfoCardDisplay({ + ensNodeStackInfo, +}: EnsNodeStackInfoCardDisplayProps) { return ( - + ); } /** - * Props for ENSNodeConfigInfoView - internal component that accepts props for testing/mocking + * Props for DisplayEnsNodeStackInfo - internal component that accepts props for testing/mocking */ -export interface ENSNodeConfigInfoViewProps { +export interface DisplayEnsNodeStackInfoProps { ensNodeStackInfo?: EnsNodeStackInfo; error?: ErrorInfoProps; isLoading?: boolean; @@ -122,11 +124,11 @@ export interface ENSNodeConfigInfoViewProps { /** * Internal view component that accepts props - used by both the main component and mock pages */ -export function ENSNodeConfigInfoView({ +export function DisplayEnsNodeStackInfo({ ensNodeStackInfo, error, isLoading = false, -}: ENSNodeConfigInfoViewProps) { +}: DisplayEnsNodeStackInfoProps) { if (error) { return ; } @@ -140,18 +142,18 @@ export function ENSNodeConfigInfoView({ ); } - return ; + return ; } /** - * ENSNodeConfigInfo component - fetches and displays ENSNode configuration data + * LoadAndDisplayEnsNodeStackInfo component - fetches and displays ENSNode configuration data */ -export function ENSNodeConfigInfo() { +export function LoadAndDisplayEnsNodeStackInfo() { const ensNodeStackInfo = useEnsNodeStackInfo(); if (ensNodeStackInfo.isError) { return ( - ; + return ; } - return ; + return ; } -function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsNodeStackInfo }) { +function EnsNodeStackInfoCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsNodeStackInfo }) { const cardItemValueStyles = "text-sm leading-6 font-normal text-black"; const { @@ -568,16 +570,16 @@ function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsN value={
  • - {ensIndexerPublicConfig.labelSet.labelSetId}: - {ensIndexerPublicConfig.labelSet.labelSetVersion} + {ensIndexerPublicConfig.clientLabelSet.labelSetId}: + {ensIndexerPublicConfig.clientLabelSet.labelSetVersion}
} additionalInfo={

- The "fully pinned" labelset id and version used for deterministic healing of unknown - labels across time. The label set version may be equal to or less than the highest - label set version offered by the connected ENSRainbow server.{" "} + The "fully pinned" label set id and version used for deterministic healing of + unknown labels across time. The label set version may be equal to or less than the + highest label set version offered by the connected ENSRainbow server.{" "} @@ -597,7 +599,7 @@ function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsN icon={} version={

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

} docsLink={new URL("https://ensnode.io/ensrainbow")} @@ -607,13 +609,13 @@ function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsN label="Server LabelSet" value={

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

} additionalInfo={

- The labelset id and highest labelset version offered by the ENSRainbow server.{" "} + The label set id and highest label set version offered by the ENSRainbow server.{" "} @@ -622,25 +624,6 @@ function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsN

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

- } - additionalInfo={ -

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

- } - /> diff --git a/apps/ensadmin/src/components/connection/index.tsx b/apps/ensadmin/src/components/connection/index.tsx index 0d58bde069..af79e327e6 100644 --- a/apps/ensadmin/src/components/connection/index.tsx +++ b/apps/ensadmin/src/components/connection/index.tsx @@ -4,7 +4,7 @@ import { InfoCardConnector } from "@/components/connection/shared/info-card"; import { ConnectionInfo } from "./cards/connection-info"; import { ENSAdminInfo } from "./cards/ensadmin-info"; -import { ENSNodeConfigInfo } from "./cards/ensnode-info"; +import { LoadAndDisplayEnsNodeStackInfo } from "./cards/ensnode-stack-info"; export default function DisplayConnectionDetails() { return ( @@ -18,7 +18,7 @@ export default function DisplayConnectionDetails() { - + ); diff --git a/apps/ensadmin/src/components/require-ensadmin-feature.tsx b/apps/ensadmin/src/components/require-ensadmin-feature.tsx index 30929794b8..f638805c77 100644 --- a/apps/ensadmin/src/components/require-ensadmin-feature.tsx +++ b/apps/ensadmin/src/components/require-ensadmin-feature.tsx @@ -82,7 +82,7 @@ export function RequireENSAdminFeatureView({ ) : ( )} diff --git a/apps/ensapi/src/cache/stack-info.cache.ts b/apps/ensapi/src/cache/stack-info.cache.ts index 7bc864bf6f..3a94a38b5c 100644 --- a/apps/ensapi/src/cache/stack-info.cache.ts +++ b/apps/ensapi/src/cache/stack-info.cache.ts @@ -30,8 +30,15 @@ async function loadEnsNodeStackInfo( const ensApiPublicConfig = buildEnsApiPublicConfig(config); const ensDbPublicConfig = await ensDbClient.buildEnsDbPublicConfig(); + const ensIndexerPublicConfig = ensApiPublicConfig.ensIndexerPublicConfig; + const ensRainbowPublicConfig = ensIndexerPublicConfig.ensRainbowPublicConfig; - return buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig); + return buildEnsNodeStackInfo( + ensApiPublicConfig, + ensDbPublicConfig, + ensIndexerPublicConfig, + ensRainbowPublicConfig, + ); } // lazyProxy defers construction until first use so that this module can be diff --git a/apps/ensapi/src/config/config.schema.test.ts b/apps/ensapi/src/config/config.schema.test.ts index f5b10fb14d..4f1e9493c1 100644 --- a/apps/ensapi/src/config/config.schema.test.ts +++ b/apps/ensapi/src/config/config.schema.test.ts @@ -42,13 +42,14 @@ const ENSINDEXER_PUBLIC_CONFIG = { namespace: "mainnet", ensIndexerSchemaName: "ensindexer_0", ensRainbowPublicConfig: { - version: packageJson.version, - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: packageJson.version, + }, }, indexedChainIds: new Set([1]), isSubgraphCompatible: false, - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, plugins: [PluginName.Subgraph], versionInfo: { ensDb: packageJson.version, @@ -196,19 +197,7 @@ describe("buildEnsApiPublicConfig", () => { const result = buildEnsApiPublicConfig(mockConfig); // Verify that all ENSIndexer public config fields are preserved - expect(result.ensIndexerPublicConfig.namespace).toBe(ENSINDEXER_PUBLIC_CONFIG.namespace); - expect(result.ensIndexerPublicConfig.plugins).toEqual(ENSINDEXER_PUBLIC_CONFIG.plugins); - expect(result.ensIndexerPublicConfig.versionInfo).toEqual(ENSINDEXER_PUBLIC_CONFIG.versionInfo); - expect(result.ensIndexerPublicConfig.indexedChainIds).toEqual( - ENSINDEXER_PUBLIC_CONFIG.indexedChainIds, - ); - expect(result.ensIndexerPublicConfig.isSubgraphCompatible).toBe( - ENSINDEXER_PUBLIC_CONFIG.isSubgraphCompatible, - ); - expect(result.ensIndexerPublicConfig.labelSet).toEqual(ENSINDEXER_PUBLIC_CONFIG.labelSet); - expect(result.ensIndexerPublicConfig.ensIndexerSchemaName).toBe( - ENSINDEXER_PUBLIC_CONFIG.ensIndexerSchemaName, - ); + expect(result.ensIndexerPublicConfig).toStrictEqual(ENSINDEXER_PUBLIC_CONFIG); }); it("includes the theGraphFallback and redacts api key", () => { diff --git a/apps/ensapi/src/config/validations.ts b/apps/ensapi/src/config/validations.ts index 7bbdd05852..b091e21085 100644 --- a/apps/ensapi/src/config/validations.ts +++ b/apps/ensapi/src/config/validations.ts @@ -36,12 +36,14 @@ export function invariant_ensIndexerPublicConfigVersionInfo( } // Invariant: ENSApi & ENSRainbow must match version numbers - if (ensIndexerPublicConfig.ensRainbowPublicConfig.version !== packageJson.version) { + if ( + ensIndexerPublicConfig.ensRainbowPublicConfig.versionInfo.ensRainbow !== packageJson.version + ) { ctx.issues.push({ code: "custom", - path: ["ensIndexerPublicConfig.ensRainbowPublicConfig.version"], - input: ensIndexerPublicConfig.ensRainbowPublicConfig.version, - message: `Version Mismatch: ENSRainbow@${ensIndexerPublicConfig.ensRainbowPublicConfig.version} !== ENSApi@${packageJson.version}`, + path: ["ensIndexerPublicConfig.ensRainbowPublicConfig.versionInfo.ensRainbow"], + input: ensIndexerPublicConfig.ensRainbowPublicConfig.versionInfo.ensRainbow, + message: `Version Mismatch: ENSRainbow@${ensIndexerPublicConfig.ensRainbowPublicConfig.versionInfo.ensRainbow} !== ENSApi@${packageJson.version}`, }); } diff --git a/apps/ensindexer/src/config/config.schema.ts b/apps/ensindexer/src/config/config.schema.ts index 5e4239cde9..a419ad1b34 100644 --- a/apps/ensindexer/src/config/config.schema.ts +++ b/apps/ensindexer/src/config/config.schema.ts @@ -94,7 +94,7 @@ const ENSIndexerConfigSchema = z isSubgraphCompatible: IsSubgraphCompatibleSchema, globalBlockrange: BlockrangeSchema, ensRainbowUrl: EnsRainbowUrlSchema, - labelSet: LabelSetSchema, + clientLabelSet: LabelSetSchema, // include the ENSDbConfig params in the ENSIndexerConfigSchema ensDbUrl: z.string(), @@ -177,7 +177,7 @@ export function buildConfigFromEnvironment(_env: ENSIndexerEnvironment): EnsInde endBlock: env.END_BLOCK, }, ensRainbowUrl: env.ENSRAINBOW_URL, - labelSet: { + clientLabelSet: { labelSetId: env.LABEL_SET_ID, labelSetVersion: env.LABEL_SET_VERSION, }, diff --git a/apps/ensindexer/src/config/config.test.ts b/apps/ensindexer/src/config/config.test.ts index 40a21dfbb9..3c6208b3f1 100644 --- a/apps/ensindexer/src/config/config.test.ts +++ b/apps/ensindexer/src/config/config.test.ts @@ -100,7 +100,7 @@ describe("config (with base env)", () => { vi.stubEnv("LABEL_SET_ID", "subgraph"); const newConfig = await getConfig(); - expect(newConfig.labelSet.labelSetId).toBe("subgraph"); + expect(newConfig.clientLabelSet.labelSetId).toBe("subgraph"); expect(newConfig).not.toBe(initialConfig); }); }); @@ -566,12 +566,12 @@ describe("config (with base env)", () => { }); }); - describe(".labelSet", () => { - it("returns the labelSet configuration if both LABEL_SET_ID and LABEL_SET_VERSION are valid", async () => { + describe(".clientLabelSet", () => { + it("returns the clientLabelSet configuration if both LABEL_SET_ID and LABEL_SET_VERSION are valid", async () => { vi.stubEnv("LABEL_SET_ID", "subgraph"); vi.stubEnv("LABEL_SET_VERSION", "5"); const config = await getConfig(); - expect(config.labelSet).toEqual({ + expect(config.clientLabelSet).toEqual({ labelSetId: "subgraph", labelSetVersion: 5, }); @@ -587,7 +587,7 @@ describe("config (with base env)", () => { vi.stubEnv("LABEL_SET_VERSION", undefined); await expect(getConfig()).resolves.toMatchObject({ - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, }); }); }); @@ -602,7 +602,7 @@ describe("config (with base env)", () => { vi.stubEnv("LABEL_SET_VERSION", undefined); await expect(getConfig()).resolves.toMatchObject({ - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, }); }); }); @@ -635,7 +635,7 @@ describe("config (with base env)", () => { it("accepts valid LABEL_SET_ID with hyphens", async () => { vi.stubEnv("LABEL_SET_ID", "ens-test-env"); const config = await getConfig(); - expect(config.labelSet.labelSetId).toBe("ens-test-env"); + expect(config.clientLabelSet.labelSetId).toBe("ens-test-env"); }); it("throws an error when LABEL_SET_VERSION is negative", async () => { @@ -656,7 +656,7 @@ describe("config (with base env)", () => { it("accepts zero as a valid LABEL_SET_VERSION", async () => { vi.stubEnv("LABEL_SET_VERSION", "0"); const config = await getConfig(); - expect(config.labelSet.labelSetVersion).toBe(0); + expect(config.clientLabelSet.labelSetVersion).toBe(0); }); }); }); @@ -848,7 +848,7 @@ describe("config (minimal base env)", () => { stubEnv({ SUBGRAPH_COMPAT: "true" }); }); - it("ens-test-env namespace/labelset is subgraph-compatible", async () => { + it("ens-test-env namespace/clientLabelSet is subgraph-compatible", async () => { stubEnv({ NAMESPACE: "ens-test-env", LABEL_SET_ID: "ens-test-env", @@ -857,7 +857,7 @@ describe("config (minimal base env)", () => { }); await expect(getConfig()).resolves.toMatchObject({ namespace: ENSNamespaceIds.EnsTestEnv, - labelSet: { + clientLabelSet: { labelSetId: "ens-test-env", labelSetVersion: 0, }, diff --git a/apps/ensindexer/src/config/environment-defaults.test.ts b/apps/ensindexer/src/config/environment-defaults.test.ts index 47a02de67b..0f888277bc 100644 --- a/apps/ensindexer/src/config/environment-defaults.test.ts +++ b/apps/ensindexer/src/config/environment-defaults.test.ts @@ -55,14 +55,14 @@ describe("environment-defaults", () => { // test runtime behavior for specific cases. // partial config provided by user - const PROVIDED: any = { labelSet: { labelSetVersion: "1" } }; + const PROVIDED: any = { clientLabelSet: { labelSetVersion: "1" } }; // full default set - const DEFAULTS: any = { labelSet: { labelSetId: "subgraph", labelSetVersion: "0" } }; + const DEFAULTS: any = { clientLabelSet: { labelSetId: "subgraph", labelSetVersion: "0" } }; // applyDefaults correctly provides the nested value without clobbering user-provided nested value expect(applyDefaults(PROVIDED, DEFAULTS)).toStrictEqual({ - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: "1", }, diff --git a/apps/ensindexer/src/config/serialize.ts b/apps/ensindexer/src/config/serialize.ts index 509668379d..563970289e 100644 --- a/apps/ensindexer/src/config/serialize.ts +++ b/apps/ensindexer/src/config/serialize.ts @@ -44,7 +44,7 @@ export function serializeRedactedENSIndexerConfig( ensIndexerSchemaName: redactedConfig.ensIndexerSchemaName, ensDbUrl: redactedConfig.ensDbUrl, ensRainbowUrl: serializeUrl(redactedConfig.ensRainbowUrl), - labelSet: redactedConfig.labelSet, + clientLabelSet: redactedConfig.clientLabelSet, globalBlockrange: redactedConfig.globalBlockrange, indexedChainIds: serializeIndexedChainIds(redactedConfig.indexedChainIds), isSubgraphCompatible: redactedConfig.isSubgraphCompatible, diff --git a/apps/ensindexer/src/config/serialized-types.ts b/apps/ensindexer/src/config/serialized-types.ts index 1d67489100..167b4d572e 100644 --- a/apps/ensindexer/src/config/serialized-types.ts +++ b/apps/ensindexer/src/config/serialized-types.ts @@ -35,7 +35,7 @@ export interface SerializedENSIndexerConfig /** * The "fully pinned" label set reference that ENSIndexer will request ENSRainbow use for deterministic label healing across time. This label set reference is "fully pinned" as it requires both the labelSetId and labelSetVersion fields to be defined. */ - labelSet: Required; + clientLabelSet: Required; /** * Serialized representation of {@link ENSIndexerConfig.indexedChainIds}. diff --git a/apps/ensindexer/src/config/types.ts b/apps/ensindexer/src/config/types.ts index 924e062cae..c41b68b951 100644 --- a/apps/ensindexer/src/config/types.ts +++ b/apps/ensindexer/src/config/types.ts @@ -33,7 +33,7 @@ export interface EnsIndexerConfig { /** * The "fully pinned" label set reference that ENSIndexer will request ENSRainbow use for deterministic label healing across time. This label set reference is "fully pinned" as it requires both the labelSetId and labelSetVersion fields to be defined. */ - labelSet: Required; + clientLabelSet: Required; /** * The name of the ENSIndexer Schema in ENSDb where ENSIndexer will create @@ -150,7 +150,7 @@ export interface EnsIndexerConfig { * * If {@link isSubgraphCompatible} is true, the following invariants are true for the ENSIndexerConfig: * 1. only the 'subgraph' plugin is enabled, and - * 2. the labelSet must be { labelSetId: 'subgraph', labelSetVersion: 0 } + * 2. the {@link clientLabelSet} must be { labelSetId: 'subgraph', labelSetVersion: 0 } * * If {@link isSubgraphCompatible} is false, ENSIndexer will additionally: * diff --git a/apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.mock.ts b/apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.mock.ts index c4d5d48422..50da45a6ff 100644 --- a/apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.mock.ts +++ b/apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.mock.ts @@ -20,9 +20,10 @@ import type { PublicConfigBuilder } from "@/lib/public-config-builder"; // Test fixture for EnsRainbowPublicConfig export const mockEnsRainbowPublicConfig: EnsRainbowPublicConfig = { - version: "1.0.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 1000, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "1.0.0", + }, }; // Test fixture for EnsIndexerVersionInfo @@ -36,7 +37,7 @@ export const mockVersionInfo: EnsIndexerVersionInfo = { // Test fixture for EnsIndexerPublicConfig export const mockPublicConfig: EnsIndexerPublicConfig = { ensIndexerSchemaName: "ensindexer_0", - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, ensRainbowPublicConfig: mockEnsRainbowPublicConfig, indexedChainIds: new Set([1, 8453]), isSubgraphCompatible: true, diff --git a/apps/ensindexer/src/lib/ensrainbow/singleton.ts b/apps/ensindexer/src/lib/ensrainbow/singleton.ts index c6785560c0..331d62e6a9 100644 --- a/apps/ensindexer/src/lib/ensrainbow/singleton.ts +++ b/apps/ensindexer/src/lib/ensrainbow/singleton.ts @@ -7,7 +7,7 @@ import { EnsRainbowApiClient } from "@ensnode/ensrainbow-sdk"; import { logger } from "@/lib/logger"; -const { ensRainbowUrl, labelSet } = config; +const { ensRainbowUrl, clientLabelSet } = config; if (ensRainbowUrl.href === EnsRainbowApiClient.defaultOptions().endpointUrl.href) { logger.warn({ @@ -21,7 +21,7 @@ if (ensRainbowUrl.href === EnsRainbowApiClient.defaultOptions().endpointUrl.href */ export const ensRainbowClient = new EnsRainbowApiClient({ endpointUrl: ensRainbowUrl, - labelSet, + clientLabelSet, }); /** diff --git a/apps/ensindexer/src/lib/public-config-builder/public-config-builder.test.ts b/apps/ensindexer/src/lib/public-config-builder/public-config-builder.test.ts index 56b396976c..c52966478f 100644 --- a/apps/ensindexer/src/lib/public-config-builder/public-config-builder.test.ts +++ b/apps/ensindexer/src/lib/public-config-builder/public-config-builder.test.ts @@ -15,7 +15,7 @@ import { PublicConfigBuilder } from "./public-config-builder"; vi.mock("@/config", () => ({ default: { ensIndexerSchemaName: "ensindexer_0", - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, indexedChainIds: new Set([1, 8453]), isSubgraphCompatible: true, namespace: ENSNamespaceIds.Mainnet, @@ -51,9 +51,10 @@ import { getEnsIndexerVersion, getPackageVersion } from "@/lib/version-info"; // Test fixtures const mockEnsRainbowConfig: EnsRainbowPublicConfig = { - version: "1.0.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 1000, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "1.0.0", + }, }; const mockVersionInfo: EnsIndexerVersionInfo = { @@ -67,7 +68,7 @@ const mockVersionInfo: EnsIndexerVersionInfo = { function createMockPublicConfig(overrides: Partial = {}) { return { ensIndexerSchemaName: "ensindexer_0", - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, ensRainbowPublicConfig: mockEnsRainbowConfig, indexedChainIds: new Set([1, 8453]), isSubgraphCompatible: true, @@ -122,7 +123,7 @@ describe("PublicConfigBuilder", () => { expect(validateEnsIndexerPublicConfig).toHaveBeenCalledWith({ ensIndexerSchemaName: config.ensIndexerSchemaName, ensRainbowPublicConfig: mockEnsRainbowConfig, - labelSet: config.labelSet, + clientLabelSet: config.clientLabelSet, indexedChainIds: config.indexedChainIds, isSubgraphCompatible: config.isSubgraphCompatible, namespace: config.namespace, @@ -203,13 +204,14 @@ describe("PublicConfigBuilder", () => { // Arrange const customConfig = createMockPublicConfig({ isSubgraphCompatible: false, - labelSet: { labelSetId: "custom", labelSetVersion: 1 }, + clientLabelSet: { labelSetId: "custom", labelSetVersion: 1 }, }); const customEnsRainbowConfig: EnsRainbowPublicConfig = { - version: "1.0.0", - labelSet: { labelSetId: "custom", highestLabelSetVersion: 1 }, - recordsCount: 2000, + serverLabelSet: { labelSetId: "custom", highestLabelSetVersion: 1 }, + versionInfo: { + ensRainbow: "1.0.0", + }, }; const ensRainbowClientMock = { diff --git a/apps/ensindexer/src/lib/public-config-builder/public-config-builder.ts b/apps/ensindexer/src/lib/public-config-builder/public-config-builder.ts index a34a8465f0..7229af0367 100644 --- a/apps/ensindexer/src/lib/public-config-builder/public-config-builder.ts +++ b/apps/ensindexer/src/lib/public-config-builder/public-config-builder.ts @@ -53,7 +53,7 @@ export class PublicConfigBuilder { this.immutablePublicConfig = validateEnsIndexerPublicConfig({ ensIndexerSchemaName: config.ensIndexerSchemaName, ensRainbowPublicConfig, - labelSet: config.labelSet, + clientLabelSet: config.clientLabelSet, indexedChainIds: config.indexedChainIds, isSubgraphCompatible: config.isSubgraphCompatible, namespace: config.namespace, diff --git a/apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts b/apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts index 311f3a2aef..17c9e95316 100644 --- a/apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts +++ b/apps/ensindexer/src/ponder/indexing-behavior-injection-contract.ts @@ -47,12 +47,12 @@ interface IndexingBehaviorDependencies { isSubgraphCompatible: boolean; /** - * Label Set + * Label Set for ENSIndexer client requests to ENSRainbow * - * When `labelSet` changes, the label "healing" results may change during indexing, + * When `clientLabelSet` changes, the label "healing" results may change during indexing, * which influences the indexing behavior. */ - labelSet: EnsIndexerConfig["labelSet"]; + clientLabelSet: EnsIndexerConfig["clientLabelSet"]; /** * ENSDb Schema Checksum @@ -112,7 +112,7 @@ const indexingBehaviorDependencies = { // injected here to ensure that, if they are configured differently, ponder generates a unique // build id to differentiate between runs with otherwise-identical configs (see above). isSubgraphCompatible: config.isSubgraphCompatible, - labelSet: config.labelSet, + clientLabelSet: config.clientLabelSet, ensDbSchemaChecksum: ENSDB_SCHEMA_CHECKSUM, } satisfies IndexingBehaviorDependencies; diff --git a/apps/ensrainbow/src/commands/server-command.test.ts b/apps/ensrainbow/src/commands/server-command.test.ts index 50f9bc26ca..82b93931e0 100644 --- a/apps/ensrainbow/src/commands/server-command.test.ts +++ b/apps/ensrainbow/src/commands/server-command.test.ts @@ -35,7 +35,7 @@ describe("Server Command Tests", () => { const ensRainbowServer = await ENSRainbowServer.init(db); const dbConfig = await buildDbConfig(ensRainbowServer); const publicConfig = buildEnsRainbowPublicConfig(dbConfig); - app = createApi(ensRainbowServer, publicConfig); + app = createApi(ensRainbowServer, publicConfig, dbConfig); // Start the server on a different port than what ENSRainbow defaults to server = serve({ @@ -129,7 +129,7 @@ describe("Server Command Tests", () => { }); describe("GET /v1/labels/count", () => { - it("should return count snapshot from startup (same as /v1/config)", async () => { + it("should return count snapshot from startup (from dbConfig.recordsCount)", async () => { // Count is fixed at server start; changing the DB does not affect the response await db.setPrecalculatedRainbowRecordCount(42); @@ -144,17 +144,6 @@ describe("Server Command Tests", () => { expect(data).toEqual(expectedData); expect(() => new Date(data.timestamp as string)).not.toThrow(); }); - - it("should match recordsCount in /v1/config", async () => { - const [countRes, configRes] = await Promise.all([ - fetch(`http://localhost:${nonDefaultPort}/v1/labels/count`), - fetch(`http://localhost:${nonDefaultPort}/v1/config`), - ]); - const countData = (await countRes.json()) as EnsRainbow.CountSuccess; - const configData = (await configRes.json()) as EnsRainbow.ENSRainbowPublicConfig; - expect(countData.status).toBe(StatusCode.Success); - expect(countData.count).toBe(configData.recordsCount); - }); }); describe("GET /v1/config", () => { @@ -165,12 +154,10 @@ describe("Server Command Tests", () => { expect(response.status).toBe(200); const data = (await response.json()) as EnsRainbow.ENSRainbowPublicConfig; - expect(typeof data.version).toBe("string"); - expect(data.version.length).toBeGreaterThan(0); - expect(data.labelSet.labelSetId).toBe("test-label-set-id"); - expect(data.labelSet.highestLabelSetVersion).toBe(0); - // Config is built on startup with count = 0, so it returns the startup value - expect(data.recordsCount).toBe(0); + expect(typeof data.versionInfo.ensRainbow).toBe("string"); + expect(data.versionInfo.ensRainbow.length).toBeGreaterThan(0); + expect(data.serverLabelSet.labelSetId).toBe("test-label-set-id"); + expect(data.serverLabelSet.highestLabelSetVersion).toBe(0); }); it("should return same config even if database count changes", async () => { @@ -182,12 +169,10 @@ describe("Server Command Tests", () => { expect(response.status).toBe(200); const data = (await response.json()) as EnsRainbow.ENSRainbowPublicConfig; - expect(typeof data.version).toBe("string"); - expect(data.version.length).toBeGreaterThan(0); - expect(data.labelSet.labelSetId).toBe("test-label-set-id"); - expect(data.labelSet.highestLabelSetVersion).toBe(0); - // Config is built on startup with count = 0, so changing the DB doesn't affect it - expect(data.recordsCount).toBe(0); + expect(typeof data.versionInfo.ensRainbow).toBe("string"); + expect(data.versionInfo.ensRainbow.length).toBeGreaterThan(0); + expect(data.serverLabelSet.labelSetId).toBe("test-label-set-id"); + expect(data.serverLabelSet.highestLabelSetVersion).toBe(0); }); }); diff --git a/apps/ensrainbow/src/commands/server-command.ts b/apps/ensrainbow/src/commands/server-command.ts index 1288c9b469..f5c940044a 100644 --- a/apps/ensrainbow/src/commands/server-command.ts +++ b/apps/ensrainbow/src/commands/server-command.ts @@ -30,7 +30,7 @@ export async function serverCommand(options: ServerCommandOptions): Promise { describe("buildEnsRainbowPublicConfig", () => { const dbConfig: DbConfig = { - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, @@ -396,9 +398,10 @@ describe("buildEnsRainbowPublicConfig", () => { const result = buildEnsRainbowPublicConfig(dbConfig); expect(result).toStrictEqual({ - version: packageJson.version, - labelSet: dbConfig.labelSet, - recordsCount: dbConfig.recordsCount, - }); + serverLabelSet: dbConfig.serverLabelSet, + versionInfo: { + ensRainbow: packageJson.version, + }, + } satisfies EnsRainbowPublicConfig); }); }); diff --git a/apps/ensrainbow/src/config/public.ts b/apps/ensrainbow/src/config/public.ts index 906a361e64..d6b75ee090 100644 --- a/apps/ensrainbow/src/config/public.ts +++ b/apps/ensrainbow/src/config/public.ts @@ -1,13 +1,17 @@ import packageJson from "@/../package.json" with { type: "json" }; +import type { EnsRainbowVersionInfo } from "@ensnode/ensnode-sdk"; import type { EnsRainbow } from "@ensnode/ensrainbow-sdk"; import type { DbConfig } from "./types"; export function buildEnsRainbowPublicConfig(dbConfig: DbConfig): EnsRainbow.ENSRainbowPublicConfig { + const versionInfo = { + ensRainbow: packageJson.version, + } satisfies EnsRainbowVersionInfo; + return { - version: packageJson.version, - labelSet: dbConfig.labelSet, - recordsCount: dbConfig.recordsCount, + serverLabelSet: dbConfig.serverLabelSet, + versionInfo, }; } diff --git a/apps/ensrainbow/src/config/types.ts b/apps/ensrainbow/src/config/types.ts index eb5d822542..1301dc77c5 100644 --- a/apps/ensrainbow/src/config/types.ts +++ b/apps/ensrainbow/src/config/types.ts @@ -38,6 +38,6 @@ export interface ServeCommandConfig { * Metadata read from an opened ENSRainbow database. */ export interface DbConfig { - labelSet: EnsRainbowServerLabelSet; + serverLabelSet: EnsRainbowServerLabelSet; recordsCount: number; } diff --git a/apps/ensrainbow/src/lib/api.ts b/apps/ensrainbow/src/lib/api.ts index dd754a3ce0..ca590d0554 100644 --- a/apps/ensrainbow/src/lib/api.ts +++ b/apps/ensrainbow/src/lib/api.ts @@ -12,6 +12,7 @@ import { } from "@ensnode/ensnode-sdk"; import { type EnsRainbow, ErrorCode, StatusCode } from "@ensnode/ensrainbow-sdk"; +import type { DbConfig } from "@/config/types"; import type { ENSRainbowServer } from "@/lib/server"; import { getErrorMessage } from "@/utils/error-utils"; import { logger } from "@/utils/logger"; @@ -22,6 +23,7 @@ import { logger } from "@/utils/logger"; export function createApi( server: ENSRainbowServer, publicConfig: EnsRainbow.ENSRainbowPublicConfig, + dbConfig: DbConfig, ): Hono { const api = new Hono(); @@ -89,7 +91,7 @@ export function createApi( api.get("/v1/labels/count", (c: HonoContext) => { const countResponse: EnsRainbow.CountSuccess = { status: StatusCode.Success, - count: publicConfig.recordsCount, + count: dbConfig.recordsCount, timestamp: new Date().toISOString(), }; return c.json(countResponse); diff --git a/apps/ensrainbow/src/lib/database.ts b/apps/ensrainbow/src/lib/database.ts index 3452a869fb..8d4b9ea0f7 100644 --- a/apps/ensrainbow/src/lib/database.ts +++ b/apps/ensrainbow/src/lib/database.ts @@ -577,11 +577,11 @@ export class ENSRainbowDB { } // 3. Check Label Set ID and Highest Label Set Version Existence and Validity - let labelSet: EnsRainbowServerLabelSet; + let serverLabelSet: EnsRainbowServerLabelSet; try { - labelSet = await this.getLabelSet(); + serverLabelSet = await this.getLabelSet(); logger.info( - `Label set verified - ID: ${labelSet.labelSetId}, highest version: ${labelSet.highestLabelSetVersion}`, + `Label set verified - ID: ${serverLabelSet.labelSetId}, highest version: ${serverLabelSet.highestLabelSetVersion}`, ); } catch (error) { const errorMsg = generatePurgeErrorMessage(`Error checking label set: ${error}`); @@ -650,9 +650,9 @@ export class ENSRainbowDB { // Only proceed with further checks if decoding was successful // Label set version comparison - if (versionedRainbowRecord.labelSetVersion > labelSet.highestLabelSetVersion) { + if (versionedRainbowRecord.labelSetVersion > serverLabelSet.highestLabelSetVersion) { logger.error( - `Label set version mismatch for label "${value}": record set ${versionedRainbowRecord.labelSetVersion} > highest set ${labelSet.highestLabelSetVersion}`, + `Label set version mismatch for label "${value}": record set ${versionedRainbowRecord.labelSetVersion} > highest set ${serverLabelSet.highestLabelSetVersion}`, ); labelSetVersionMismatches++; } diff --git a/apps/ensrainbow/src/lib/server.ts b/apps/ensrainbow/src/lib/server.ts index b3da287c5a..6892869e75 100644 --- a/apps/ensrainbow/src/lib/server.ts +++ b/apps/ensrainbow/src/lib/server.ts @@ -29,7 +29,7 @@ export async function buildDbConfig(server: ENSRainbowServer): Promise } return { - labelSet: server.serverLabelSet, + serverLabelSet: server.serverLabelSet, recordsCount: countResult.count, }; } diff --git a/docs/ensnode.io/ensapi-openapi.json b/docs/ensnode.io/ensapi-openapi.json index 9676b3c7a6..487ad34b48 100644 --- a/docs/ensnode.io/ensapi-openapi.json +++ b/docs/ensnode.io/ensapi-openapi.json @@ -782,6 +782,127 @@ "stackInfo": { "type": "object", "properties": { + "ensDb": { + "type": "object", + "properties": { + "versionInfo": { + "type": "object", + "properties": { + "postgresql": { + "type": "string", + "minLength": 1, + "description": "Version of the PostgreSQL server hosting the ENSDb instance." + } + }, + "required": ["postgresql"] + } + }, + "required": ["versionInfo"] + }, + "ensIndexer": { + "type": "object", + "properties": { + "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, + "ensRainbowPublicConfig": { + "type": "object", + "properties": { + "serverLabelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "highestLabelSetVersion": { "type": "integer", "minimum": 0 } + }, + "required": ["labelSetId", "highestLabelSetVersion"] + }, + "versionInfo": { + "type": "object", + "properties": { + "ensRainbow": { "type": "string", "minLength": 1 } + }, + "required": ["ensRainbow"] + } + }, + "required": ["serverLabelSet", "versionInfo"] + }, + "indexedChainIds": { + "type": "array", + "items": { "type": "integer", "exclusiveMinimum": 0 }, + "minItems": 1 + }, + "isSubgraphCompatible": { "type": "boolean" }, + "clientLabelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "labelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "labelSetVersion"] + }, + "namespace": { + "type": "string", + "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] + }, + "plugins": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "versionInfo": { + "type": "object", + "properties": { + "ponder": { "type": "string", "minLength": 1 }, + "ensDb": { "type": "string", "minLength": 1 }, + "ensIndexer": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 } + }, + "required": ["ponder", "ensDb", "ensIndexer", "ensNormalize"] + } + }, + "required": [ + "ensIndexerSchemaName", + "ensRainbowPublicConfig", + "indexedChainIds", + "isSubgraphCompatible", + "clientLabelSet", + "namespace", + "plugins", + "versionInfo" + ] + }, + "ensRainbow": { + "type": "object", + "properties": { + "serverLabelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "highestLabelSetVersion": { "type": "integer", "minimum": 0 } + }, + "required": ["labelSetId", "highestLabelSetVersion"] + }, + "versionInfo": { + "type": "object", + "properties": { "ensRainbow": { "type": "string", "minLength": 1 } }, + "required": ["ensRainbow"] + } + }, + "required": ["serverLabelSet", "versionInfo"] + }, "ensApi": { "type": "object", "properties": { @@ -792,8 +913,7 @@ "ensRainbowPublicConfig": { "type": "object", "properties": { - "version": { "type": "string", "minLength": 1 }, - "labelSet": { + "serverLabelSet": { "type": "object", "properties": { "labelSetId": { @@ -809,9 +929,15 @@ }, "required": ["labelSetId", "highestLabelSetVersion"] }, - "recordsCount": { "type": "integer", "minimum": 0 } + "versionInfo": { + "type": "object", + "properties": { + "ensRainbow": { "type": "string", "minLength": 1 } + }, + "required": ["ensRainbow"] + } }, - "required": ["version", "labelSet", "recordsCount"] + "required": ["serverLabelSet", "versionInfo"] }, "indexedChainIds": { "type": "array", @@ -819,7 +945,7 @@ "minItems": 1 }, "isSubgraphCompatible": { "type": "boolean" }, - "labelSet": { + "clientLabelSet": { "type": "object", "properties": { "labelSetId": { @@ -857,7 +983,7 @@ "ensRainbowPublicConfig", "indexedChainIds", "isSubgraphCompatible", - "labelSet", + "clientLabelSet", "namespace", "plugins", "versionInfo" @@ -902,124 +1028,9 @@ } }, "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] - }, - "ensDb": { - "type": "object", - "properties": { - "versionInfo": { - "type": "object", - "properties": { - "postgresql": { - "type": "string", - "minLength": 1, - "description": "Version of the PostgreSQL server hosting the ENSDb instance." - } - }, - "required": ["postgresql"], - "description": "Serialized Indexing Status Response OK.ensDb.versionInfo" - } - }, - "required": ["versionInfo"], - "description": "Serialized Indexing Status Response OK.ensDb" - }, - "ensIndexer": { - "type": "object", - "properties": { - "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, - "ensRainbowPublicConfig": { - "type": "object", - "properties": { - "version": { "type": "string", "minLength": 1 }, - "labelSet": { - "type": "object", - "properties": { - "labelSetId": { - "type": "string", - "minLength": 1, - "maxLength": 50, - "pattern": "^[a-z-]+$" - }, - "highestLabelSetVersion": { "type": "integer", "minimum": 0 } - }, - "required": ["labelSetId", "highestLabelSetVersion"] - }, - "recordsCount": { "type": "integer", "minimum": 0 } - }, - "required": ["version", "labelSet", "recordsCount"] - }, - "indexedChainIds": { - "type": "array", - "items": { "type": "integer", "exclusiveMinimum": 0 }, - "minItems": 1 - }, - "isSubgraphCompatible": { "type": "boolean" }, - "labelSet": { - "type": "object", - "properties": { - "labelSetId": { - "type": "string", - "minLength": 1, - "maxLength": 50, - "pattern": "^[a-z-]+$" - }, - "labelSetVersion": { "type": ["number", "null"] } - }, - "required": ["labelSetId", "labelSetVersion"] - }, - "namespace": { - "type": "string", - "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] - }, - "plugins": { - "type": "array", - "items": { "type": "string" }, - "minItems": 1 - }, - "versionInfo": { - "type": "object", - "properties": { - "ponder": { "type": "string", "minLength": 1 }, - "ensDb": { "type": "string", "minLength": 1 }, - "ensIndexer": { "type": "string", "minLength": 1 }, - "ensNormalize": { "type": "string", "minLength": 1 } - }, - "required": ["ponder", "ensDb", "ensIndexer", "ensNormalize"] - } - }, - "required": [ - "ensIndexerSchemaName", - "ensRainbowPublicConfig", - "indexedChainIds", - "isSubgraphCompatible", - "labelSet", - "namespace", - "plugins", - "versionInfo" - ] - }, - "ensRainbow": { - "type": "object", - "properties": { - "version": { "type": "string", "minLength": 1 }, - "labelSet": { - "type": "object", - "properties": { - "labelSetId": { - "type": "string", - "minLength": 1, - "maxLength": 50, - "pattern": "^[a-z-]+$" - }, - "highestLabelSetVersion": { "type": "integer", "minimum": 0 } - }, - "required": ["labelSetId", "highestLabelSetVersion"] - }, - "recordsCount": { "type": "integer", "minimum": 0 } - }, - "required": ["version", "labelSet", "recordsCount"] } }, - "required": ["ensApi", "ensDb", "ensIndexer"] + "required": ["ensDb", "ensIndexer", "ensRainbow", "ensApi"] } }, "required": ["responseCode", "realtimeProjection", "stackInfo"] diff --git a/docs/ensnode.io/src/content/docs/ensrainbow/concepts/typescript-interfaces.mdx b/docs/ensnode.io/src/content/docs/ensrainbow/concepts/typescript-interfaces.mdx index e998b50476..420982184e 100644 --- a/docs/ensnode.io/src/content/docs/ensrainbow/concepts/typescript-interfaces.mdx +++ b/docs/ensnode.io/src/content/docs/ensrainbow/concepts/typescript-interfaces.mdx @@ -31,7 +31,7 @@ interface EnsRainbowServerLabelSet { ### `EnsRainbowClientLabelSet` -Provided when constructing `new EnsRainbowApiClient({ labelSet: … })` to pin client expectations: +Provided when constructing `new EnsRainbowApiClient({ clientLabelSet: … })` to pin client expectations: ```ts interface EnsRainbowClientLabelSet { @@ -42,11 +42,11 @@ interface EnsRainbowClientLabelSet { #### Usage Guidelines -1. **Neither field** → accept any labelset and use the latest version (default behaviour). -2. **Only `labelSetId`** → insist on a specific labelset and use the latest version. -3. **Both fields** → insist on a specific labelset and version, locking the client to an exact snapshot. +1. **Neither field** → accept any client label set and use the latest version (default behaviour). +2. **Only `labelSetId`** → insist on a specific client label set and use the latest version. +3. **Both fields** → insist on a specific client label set and version, locking the client to an exact snapshot. -This handshake that occurs when **both fields** are set ensures both client and server interpret every heal operation against the **same logical labelset snapshot**, enabling deterministic and reproducible healing results across time. +This handshake that occurs when **both fields** are set ensures both client and server interpret every heal operation against the **same logical label set snapshot**, enabling deterministic and reproducible healing results across time. ## Example Usage @@ -57,19 +57,19 @@ import { EnsRainbowApiClient } from '@ensnode/ensrainbow-sdk'; // Default: use latest version of any labelset const client1 = new EnsRainbowApiClient({ - baseUrl: 'https://api.ensrainbow.io' + endpointUrl: 'https://api.ensrainbow.io' }); // Pin to subgraph labelset, use latest version const client2 = new EnsRainbowApiClient({ - baseUrl: 'https://api.ensrainbow.io', - labelSet: { labelSetId: 'subgraph' } + endpointUrl: 'https://api.ensrainbow.io', + clientLabelSet: { labelSetId: 'subgraph' } }); // Pin to exact labelset snapshot for deterministic results across time const client3 = new EnsRainbowApiClient({ - baseUrl: 'https://api.ensrainbow.io', - labelSet: { + endpointUrl: 'https://api.ensrainbow.io', + clientLabelSet: { labelSetId: 'subgraph', labelSetVersion: 0 } diff --git a/docs/ensnode.io/src/content/docs/ensrainbow/usage/client-sdk.mdx b/docs/ensnode.io/src/content/docs/ensrainbow/usage/client-sdk.mdx index 6e0b4623a0..2729f4a8f0 100644 --- a/docs/ensnode.io/src/content/docs/ensrainbow/usage/client-sdk.mdx +++ b/docs/ensnode.io/src/content/docs/ensrainbow/usage/client-sdk.mdx @@ -29,8 +29,8 @@ pnpm add @ensnode/ensrainbow-sdk import { EnsRainbowApiClient } from '@ensnode/ensrainbow-sdk'; const client = new EnsRainbowApiClient({ - baseUrl: 'https://api.ensrainbow.io', - labelSet: { labelSetId: 'subgraph', labelSetVersion: 0 } + endpointUrl: 'https://api.ensrainbow.io', + clientLabelSet: { labelSetId: 'subgraph', labelSetVersion: 0 } }); const res = await client.healLabel('0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc'); diff --git a/packages/ensdb-sdk/src/client/ensdb-client.mock.ts b/packages/ensdb-sdk/src/client/ensdb-client.mock.ts index 926d2ca4f3..d9444acd56 100644 --- a/packages/ensdb-sdk/src/client/ensdb-client.mock.ts +++ b/packages/ensdb-sdk/src/client/ensdb-client.mock.ts @@ -26,14 +26,15 @@ export const ensIndexerSchemaName = "ensindexer_0"; export const publicConfig = { ensIndexerSchemaName, ensRainbowPublicConfig: { - version: "0.32.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, - recordsCount: 100, + versionInfo: { + ensRainbow: "0.32.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, diff --git a/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts index 7f4bf6afa1..548294d009 100644 --- a/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts @@ -21,13 +21,14 @@ const MOCK_ENSAPI_PUBLIC_CONFIG = { namespace: ENSNamespaceIds.Mainnet, ensIndexerSchemaName: "ensindexer_0", ensRainbowPublicConfig: { - version: "0.36.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "0.36.0", + }, }, indexedChainIds: new Set([1]), isSubgraphCompatible: false, - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, plugins: [PluginName.Subgraph], versionInfo: { ensDb: "0.36.0", @@ -58,13 +59,14 @@ describe("ENSApi Config Serialization/Deserialization", () => { namespace: ENSNamespaceIds.Mainnet, ensIndexerSchemaName: "ensindexer_0", ensRainbowPublicConfig: { - version: "0.36.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "0.36.0", + }, }, indexedChainIds: [1], isSubgraphCompatible: false, - labelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0 }, plugins: [PluginName.Subgraph], versionInfo: { ensDb: "0.36.0", diff --git a/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts index 1d9db9ed3b..3de6ede62c 100644 --- a/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts +++ b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts @@ -3,22 +3,18 @@ import { z } from "zod/v4"; const makeEnsDbVersionInfoSchema = (valueLabel?: string) => { const label = valueLabel ?? "EnsDbVersionInfo"; - return z - .object({ - postgresql: z - .string() - .nonempty(`${label}.postgresql must be a non-empty string`) - .describe("Version of the PostgreSQL server hosting the ENSDb instance."), - }) - .describe(label); + return z.object({ + postgresql: z + .string() + .nonempty(`${label}.postgresql must be a non-empty string`) + .describe("Version of the PostgreSQL server hosting the ENSDb instance."), + }); }; export const makeEnsDbPublicConfigSchema = (valueLabel?: string) => { const label = valueLabel ?? "EnsDbPublicConfig"; - return z - .object({ - versionInfo: makeEnsDbVersionInfoSchema(`${label}.versionInfo`), - }) - .describe(label); + return z.object({ + versionInfo: makeEnsDbVersionInfoSchema(`${label}.versionInfo`), + }); }; diff --git a/packages/ensnode-sdk/src/ensindexer/client.mock.ts b/packages/ensnode-sdk/src/ensindexer/client.mock.ts index 3bfd76b3cd..fcae370c64 100644 --- a/packages/ensnode-sdk/src/ensindexer/client.mock.ts +++ b/packages/ensnode-sdk/src/ensindexer/client.mock.ts @@ -8,19 +8,20 @@ import type { SerializedEnsIndexerIndexingStatusResponse } from "./api/indexing- import { PluginName } from "./config/types"; export const configResponseMock = { - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352], ensIndexerSchemaName: "alphaSchema0.31.0", ensRainbowPublicConfig: { - version: "0.31.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, - recordsCount: 100, + versionInfo: { + ensRainbow: "0.31.0", + }, }, isSubgraphCompatible: false, namespace: "mainnet", diff --git a/packages/ensnode-sdk/src/ensindexer/config/compatibility.test.ts b/packages/ensnode-sdk/src/ensindexer/config/compatibility.test.ts index c7da25c5b9..341a426e5f 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/compatibility.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/compatibility.test.ts @@ -12,7 +12,7 @@ describe("EnsIndexerConfig compatibility", () => { describe("validateEnsIndexerPublicConfigCompatibility()", () => { const config = { indexedChainIds: new Set([1, 10, 8453]), - labelSet: { + clientLabelSet: { labelSetId: "test-label-set", labelSetVersion: 1, }, @@ -67,35 +67,35 @@ describe("EnsIndexerConfig compatibility", () => { ); }); - it("throws error when 'configA.labelSet.labelSetId' is not same as 'configB.labelSet.labelSetId'", () => { + it("throws error when 'configA.clientLabelSet.labelSetId' is not same as 'configB.clientLabelSet.labelSetId'", () => { const configA = structuredClone(config); const configB = { ...structuredClone(config), - labelSet: { - ...structuredClone(config.labelSet), + clientLabelSet: { + ...structuredClone(config.clientLabelSet), labelSetId: "different-label-set", }, } satisfies EnsIndexerPublicConfigCompatibilityCheck; expect(() => validateEnsIndexerPublicConfigCompatibility(configA, configB)).toThrowError( - /'labelSet.labelSetId' must be compatible. Stored Config 'labelSet.labelSetId': 'test-label-set'. Current Config 'labelSet.labelSetId': 'different-label-set'/i, + /'clientLabelSet.labelSetId' must be compatible. Stored Config 'clientLabelSet.labelSetId': 'test-label-set'. Current Config 'clientLabelSet.labelSetId': 'different-label-set'/i, ); }); - it("throws error when 'configA.labelSet.labelSetVersion' is not same as 'configB.labelSet.labelSetVersion'", () => { + it("throws error when 'configA.clientLabelSet.labelSetVersion' is not same as 'configB.clientLabelSet.labelSetVersion'", () => { const configA = structuredClone(config); const configB = { ...structuredClone(config), - labelSet: { - ...structuredClone(config.labelSet), - labelSetVersion: config.labelSet.labelSetVersion + 1, + clientLabelSet: { + ...structuredClone(config.clientLabelSet), + labelSetVersion: config.clientLabelSet.labelSetVersion + 1, }, } satisfies EnsIndexerPublicConfigCompatibilityCheck; expect(() => validateEnsIndexerPublicConfigCompatibility(configA, configB)).toThrowError( - /'labelSet.labelSetVersion' must be compatible. Stored Config 'labelSet.labelSetVersion': '1'. Current Config 'labelSet.labelSetVersion': '2'/i, + /'clientLabelSet.labelSetVersion' must be compatible. Stored Config 'clientLabelSet.labelSetVersion': '1'. Current Config 'clientLabelSet.labelSetVersion': '2'/i, ); }); diff --git a/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts b/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts index b38f3769c1..dfd6ecaa38 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/compatibility.ts @@ -44,22 +44,22 @@ export function validateEnsIndexerPublicConfigCompatibility( ); } - if (configA.labelSet.labelSetId !== configB.labelSet.labelSetId) { + if (configA.clientLabelSet.labelSetId !== configB.clientLabelSet.labelSetId) { throw new Error( [ - `'labelSet.labelSetId' must be compatible.`, - `Stored Config 'labelSet.labelSetId': '${configA.labelSet.labelSetId}'.`, - `Current Config 'labelSet.labelSetId': '${configB.labelSet.labelSetId}'.`, + `'clientLabelSet.labelSetId' must be compatible.`, + `Stored Config 'clientLabelSet.labelSetId': '${configA.clientLabelSet.labelSetId}'.`, + `Current Config 'clientLabelSet.labelSetId': '${configB.clientLabelSet.labelSetId}'.`, ].join(" "), ); } - if (configA.labelSet.labelSetVersion !== configB.labelSet.labelSetVersion) { + if (configA.clientLabelSet.labelSetVersion !== configB.clientLabelSet.labelSetVersion) { throw new Error( [ - `'labelSet.labelSetVersion' must be compatible.`, - `Stored Config 'labelSet.labelSetVersion': '${configA.labelSet.labelSetVersion}'.`, - `Current Config 'labelSet.labelSetVersion': '${configB.labelSet.labelSetVersion}'.`, + `'clientLabelSet.labelSetVersion' must be compatible.`, + `Stored Config 'clientLabelSet.labelSetVersion': '${configA.clientLabelSet.labelSetVersion}'.`, + `Current Config 'clientLabelSet.labelSetVersion': '${configB.clientLabelSet.labelSetVersion}'.`, ].join(" "), ); } diff --git a/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts index 4dd5c05493..9d8fdda275 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/conversions.test.ts @@ -12,11 +12,12 @@ describe("ENSIndexer: Config", () => { const config = { ensIndexerSchemaName: "ensindexer_0", ensRainbowPublicConfig: { - version: "0.32.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "0.32.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, @@ -54,11 +55,12 @@ describe("ENSIndexer: Config", () => { const correctSerializedConfig = { ensIndexerSchemaName: "ensindexer_0", ensRainbowPublicConfig: { - version: "0.32.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "0.32.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, diff --git a/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.test.ts b/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.test.ts index 6320299249..44a1623b8b 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.test.ts @@ -6,7 +6,7 @@ import { isSubgraphCompatible } from "./is-subgraph-compatible"; import { PluginName } from "./types"; describe("isSubgraphCompatible", () => { - const subgraphCompatibleLabelSet = { + const subgraphCompatibleClientLabelSet = { labelSetId: "subgraph" as const, labelSetVersion: 0, }; @@ -16,7 +16,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.Mainnet, plugins: [PluginName.Subgraph], - labelSet: subgraphCompatibleLabelSet, + clientLabelSet: subgraphCompatibleClientLabelSet, }), ).toBe(true); }); @@ -26,7 +26,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.Mainnet, plugins: [], - labelSet: subgraphCompatibleLabelSet, + clientLabelSet: subgraphCompatibleClientLabelSet, }), ).toBe(false); @@ -34,7 +34,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.Mainnet, plugins: [PluginName.Subgraph, PluginName.Lineanames], - labelSet: subgraphCompatibleLabelSet, + clientLabelSet: subgraphCompatibleClientLabelSet, }), ).toBe(false); }); @@ -44,7 +44,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.Mainnet, plugins: [PluginName.Subgraph], - labelSet: { + clientLabelSet: { labelSetId: "other-label-set", labelSetVersion: 0, }, @@ -57,7 +57,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.Mainnet, plugins: [PluginName.Subgraph], - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 1, }, @@ -70,7 +70,7 @@ describe("isSubgraphCompatible", () => { isSubgraphCompatible({ namespace: ENSNamespaceIds.EnsTestEnv, plugins: [PluginName.Subgraph], - labelSet: { + clientLabelSet: { labelSetId: "ens-test-env", labelSetVersion: 0, }, diff --git a/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.ts b/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.ts index b1587c7dc3..ab859f5485 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/is-subgraph-compatible.ts @@ -9,7 +9,7 @@ import { type EnsIndexerPublicConfig, PluginName } from "./types"; * @see https://ensnode.io/docs/concepts/what-is-the-ens-subgraph */ export function isSubgraphCompatible( - config: Pick, + config: Pick, ): boolean { // 1. only the subgraph plugin is active const onlySubgraphPluginActivated = @@ -17,10 +17,11 @@ export function isSubgraphCompatible( // 2. label set id must be "subgraph" and version must be 0 const isSubgraphLabelSet = - config.labelSet.labelSetId === "subgraph" && config.labelSet.labelSetVersion === 0; + config.clientLabelSet.labelSetId === "subgraph" && config.clientLabelSet.labelSetVersion === 0; const isEnsTestEnvLabelSet = - config.labelSet.labelSetId === "ens-test-env" && config.labelSet.labelSetVersion === 0; + config.clientLabelSet.labelSetId === "ens-test-env" && + config.clientLabelSet.labelSetVersion === 0; // config should be considered subgraph-compatible if in ens-test-env namespace with ens-test-env labelset const labelSetIsSubgraphCompatible = diff --git a/packages/ensnode-sdk/src/ensindexer/config/serialize.ts b/packages/ensnode-sdk/src/ensindexer/config/serialize.ts index da32387442..7f7a3fc6aa 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/serialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/serialize.ts @@ -24,7 +24,7 @@ export function serializeEnsIndexerPublicConfig( ensRainbowPublicConfig, indexedChainIds, isSubgraphCompatible, - labelSet, + clientLabelSet, namespace, plugins, versionInfo, @@ -35,7 +35,7 @@ export function serializeEnsIndexerPublicConfig( ensRainbowPublicConfig, indexedChainIds: serializeIndexedChainIds(indexedChainIds), isSubgraphCompatible, - labelSet, + clientLabelSet, namespace, plugins, versionInfo, diff --git a/packages/ensnode-sdk/src/ensindexer/config/types.ts b/packages/ensnode-sdk/src/ensindexer/config/types.ts index 0476e7d377..e6779a98fd 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/types.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/types.ts @@ -78,7 +78,7 @@ export interface EnsIndexerPublicConfig { /** * The "fully pinned" label set reference that ENSIndexer will request ENSRainbow use for deterministic label healing across time. This label set reference is "fully pinned" as it requires both the labelSetId and labelSetVersion fields to be defined. */ - labelSet: Required; + clientLabelSet: Required; /** * The name of the ENSIndexer Schema in the ENSDb instance, @@ -127,7 +127,7 @@ export interface EnsIndexerPublicConfig { * * If {@link isSubgraphCompatible} is true, the following invariants are true for the ENSIndexerConfig: * 1. only the 'subgraph' plugin is enabled, and - * 2. the labelSet must be { labelSetId: 'subgraph', labelSetVersion: 0 } + * 2. the {@link clientLabelSet} must be { labelSetId: 'subgraph', labelSetVersion: 0 } * * If {@link isSubgraphCompatible} is false, ENSIndexer will additionally: * 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 fcc9adab74..6af6a116fe 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts @@ -141,12 +141,13 @@ describe("ENSIndexer: Config", () => { it("validates ENSRainbow label set and version compatibility", () => { const baseConfig = { ensRainbowPublicConfig: { - version: "0.32.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, - recordsCount: 100, + versionInfo: { + ensRainbow: "0.32.0", + }, }, indexedChainIds: [1], // Use array for serialized config isSubgraphCompatible: false, // Set to false to bypass isSubgraphCompatible invariant @@ -167,7 +168,7 @@ describe("ENSIndexer: Config", () => { makeEnsIndexerPublicConfigSchema().safeParse( buildUnvalidatedEnsIndexerPublicConfig({ ...baseConfig, - labelSet: { labelSetId: "custom-labels", labelSetVersion: 0 }, + clientLabelSet: { labelSetId: "custom-labels", labelSetVersion: 0 }, }), ), ), @@ -181,7 +182,7 @@ describe("ENSIndexer: Config", () => { makeEnsIndexerPublicConfigSchema().safeParse( buildUnvalidatedEnsIndexerPublicConfig({ ...baseConfig, - labelSet: { labelSetId: "subgraph", labelSetVersion: 5 }, + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 5 }, }), ), ), @@ -191,14 +192,15 @@ describe("ENSIndexer: Config", () => { it("can parse full ENSIndexerPublicConfig with label set", () => { const validConfig = { ensRainbowPublicConfig: { - version: "0.32.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0, }, - recordsCount: 100, + versionInfo: { + ensRainbow: "0.32.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, @@ -226,11 +228,11 @@ describe("ENSIndexer: Config", () => { makeEnsIndexerPublicConfigSchema().safeParse( buildUnvalidatedEnsIndexerPublicConfig({ ...validConfig, - labelSet: { ...validConfig.labelSet, labelSetId: "" }, + clientLabelSet: { ...validConfig.clientLabelSet, labelSetId: "" }, }), ), ), - ).toContain("labelSet.labelSetId must be 1-50 characters long"); + ).toContain("clientLabelSet.labelSetId must be 1-50 characters long"); // Test invalid labelSetVersion expect( @@ -238,14 +240,14 @@ describe("ENSIndexer: Config", () => { makeEnsIndexerPublicConfigSchema().safeParse( buildUnvalidatedEnsIndexerPublicConfig({ ...validConfig, - labelSet: { - ...validConfig.labelSet, + clientLabelSet: { + ...validConfig.clientLabelSet, labelSetVersion: "not-a-number" as unknown as number, }, }), ), ), - ).toContain("labelSet.labelSetVersion must be a non-negative integer"); + ).toContain("clientLabelSet.labelSetVersion must be a non-negative integer"); }); }); diff --git a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts index 68f32f9987..a4d4d47606 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts @@ -8,7 +8,6 @@ */ import { z } from "zod/v4"; -import type { EnsRainbowClientLabelSet, EnsRainbowServerLabelSet } from "../../ensrainbow/types"; import { makeEnsRainbowPublicConfigSchema, makeLabelSetIdSchema, @@ -118,7 +117,10 @@ export const makeENSIndexerVersionInfoSchema = makeEnsIndexerVersionInfoSchema; // Invariant: If config.isSubgraphCompatible, the config must pass isSubgraphCompatible(config) export function invariant_isSubgraphCompatibleRequirements( ctx: ZodCheckFnInput< - Pick + Pick< + EnsIndexerPublicConfig, + "namespace" | "plugins" | "isSubgraphCompatible" | "clientLabelSet" + > >, ) { const { value: config } = ctx; @@ -127,17 +129,16 @@ export function invariant_isSubgraphCompatibleRequirements( ctx.issues.push({ code: "custom", input: config, - message: `'isSubgraphCompatible' requires only the '${PluginName.Subgraph}' plugin to be active and labelSet must be {labelSetId: "subgraph", labelSetVersion: 0}`, + message: `'isSubgraphCompatible' requires only the '${PluginName.Subgraph}' plugin to be active and 'clientLabelSet' must be {labelSetId: "subgraph", labelSetVersion: 0}`, }); } } export function invariant_ensRainbowSupportedLabelSetAndVersion( - ctx: ZodCheckFnInput>, + ctx: ZodCheckFnInput>, ) { - const clientLabelSet = ctx.value.labelSet satisfies EnsRainbowClientLabelSet; - const serverLabelSet = ctx.value.ensRainbowPublicConfig - .labelSet satisfies EnsRainbowServerLabelSet; + const { clientLabelSet } = ctx.value; + const { serverLabelSet } = ctx.value.ensRainbowPublicConfig; try { validateSupportedLabelSetAndVersion(serverLabelSet, clientLabelSet); @@ -169,7 +170,7 @@ export const makeEnsIndexerPublicConfigSchema = (valueLabel: string = "ENSIndexe isSubgraphCompatible: z.boolean({ error: `${valueLabel}.isSubgraphCompatible must be a boolean value.`, }), - labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), + clientLabelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.clientLabelSet`), namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`), plugins: makePluginsListSchema(`${valueLabel}.plugins`), versionInfo: makeEnsIndexerVersionInfoSchema(`${valueLabel}.versionInfo`), @@ -201,7 +202,7 @@ export const makeSerializedEnsIndexerPublicConfigSchema = ( isSubgraphCompatible: z.boolean({ error: `${valueLabel}.isSubgraphCompatible must be a boolean value.`, }), - labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`), + clientLabelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.clientLabelSet`), namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`), plugins: makePluginsListSchema(`${valueLabel}.plugins`), versionInfo: makeEnsIndexerVersionInfoSchema(`${valueLabel}.versionInfo`), diff --git a/packages/ensnode-sdk/src/ensnode/client.test.ts b/packages/ensnode-sdk/src/ensnode/client.test.ts index d23e7a9ce0..c10a4ec4d0 100644 --- a/packages/ensnode-sdk/src/ensnode/client.test.ts +++ b/packages/ensnode-sdk/src/ensnode/client.test.ts @@ -71,16 +71,17 @@ const EXAMPLE_ENSAPI_CONFIG_RESPONSE = { }, ensIndexerPublicConfig: { ensRainbowPublicConfig: { - version: "0.31.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "1.9.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352], - ensIndexerSchemaName: "alphaSchema0.31.0", + ensIndexerSchemaName: "alphaSchema1.9.0", isSubgraphCompatible: false, namespace: "mainnet", plugins: [ @@ -93,8 +94,8 @@ const EXAMPLE_ENSAPI_CONFIG_RESPONSE = { ], versionInfo: { ponder: "0.11.43", - ensDb: "0.32.0", - ensIndexer: "0.32.0", + ensDb: "1.9.0", + ensIndexer: "1.9.0", ensNormalize: "1.11.1", }, }, @@ -108,16 +109,17 @@ const EXAMPLE_ENSDB_PUBLIC_RESPONSE = { const EXAMPLE_ENSINDEXER_PUBLIC_CONFIG = { ensRainbowPublicConfig: { - version: "0.31.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "1.9.0", + }, }, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: 0, }, indexedChainIds: [1, 8453, 59144, 10, 42161, 534352], - ensIndexerSchemaName: "alphaSchema0.31.0", + ensIndexerSchemaName: "alphaSchema1.9.0", isSubgraphCompatible: false, namespace: "mainnet", plugins: [ @@ -130,16 +132,17 @@ const EXAMPLE_ENSINDEXER_PUBLIC_CONFIG = { ], versionInfo: { ponder: "0.11.43", - ensDb: "0.32.0", - ensIndexer: "0.32.0", + ensDb: "1.9.0", + ensIndexer: "1.9.0", ensNormalize: "1.11.1", }, } satisfies SerializedEnsIndexerPublicConfig; const EXAMPLE_ENSRAINBOW_PUBLIC_CONFIG = { - version: "0.31.0", - labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, - recordsCount: 100, + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + versionInfo: { + ensRainbow: "1.9.0", + }, } satisfies SerializedEnsRainbowPublicConfig; const serializedStackInfo = { diff --git a/packages/ensnode-sdk/src/ensrainbow/config.ts b/packages/ensnode-sdk/src/ensrainbow/config.ts index d5f141c59d..e271a0dd82 100644 --- a/packages/ensnode-sdk/src/ensrainbow/config.ts +++ b/packages/ensnode-sdk/src/ensrainbow/config.ts @@ -1,26 +1,30 @@ 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. + * Version info about ENSRainbow and its dependencies. */ -export interface EnsRainbowPublicConfig { +export interface EnsRainbowVersionInfo { /** * ENSRainbow service version * * @see https://ghcr.io/namehash/ensnode/ensrainbow - */ - version: string; + **/ + ensRainbow: string; +} +/** + * Complete public configuration object for ENSRainbow. + * + * Contains all public configuration information about the ENSRainbow service instance. + */ +export interface EnsRainbowPublicConfig { /** * The label set reference managed by the ENSRainbow server. */ - labelSet: EnsRainbowServerLabelSet; + serverLabelSet: EnsRainbowServerLabelSet; /** - * The total count of records managed by the ENSRainbow service. + * ENSRainbow version info */ - recordsCount: number; + versionInfo: EnsRainbowVersionInfo; } diff --git a/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts index b8aff5a513..c49a2e26b2 100644 --- a/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts +++ b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts @@ -45,12 +45,16 @@ export const makeLabelSetVersionStringSchema = (valueLabel: string = "Label set */ 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`), + serverLabelSet: z.object({ + labelSetId: makeLabelSetIdSchema(`${valueLabel}.serverLabelSet.labelSetId`), highestLabelSetVersion: makeLabelSetVersionSchema( - `${valueLabel}.labelSet.highestLabelSetVersion`, + `${valueLabel}.serverLabelSet.highestLabelSetVersion`, ), }), - recordsCount: makeNonNegativeIntegerSchema(`${valueLabel}.recordsCount`), + + versionInfo: z.object({ + ensRainbow: z + .string() + .nonempty({ error: `${valueLabel}.versionInfo.ensRainbow must be a non-empty string.` }), + }), }); diff --git a/packages/ensnode-sdk/src/stack-info/deserialize/ensindexer-stack-info.ts b/packages/ensnode-sdk/src/stack-info/deserialize/ensindexer-stack-info.ts new file mode 100644 index 0000000000..aa1d146f73 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/deserialize/ensindexer-stack-info.ts @@ -0,0 +1,50 @@ +import { prettifyError } from "zod/v4"; + +import { buildUnvalidatedEnsIndexerPublicConfig } from "../../ensindexer/config/deserialize"; +import type { Unvalidated } from "../../shared/types"; +import type { EnsIndexerStackInfo } from "../ensindexer-stack-info"; +import type { SerializedEnsIndexerStackInfo } from "../serialize/ensindexer-stack-info"; +import { + makeEnsIndexerStackInfoSchema, + makeSerializedEnsIndexerStackInfoSchema, +} from "../zod-schemas/ensindexer-stack-info"; + +/** + * Builds an unvalidated {@link EnsIndexerStackInfo} object to be + * validated with {@link makeEnsIndexerStackInfoSchema}. + * + * @param serializedStackInfo - The serialized stack info to build from. + * @return An unvalidated {@link EnsIndexerStackInfo} object. + */ +export function buildUnvalidatedEnsIndexerStackInfo( + serializedStackInfo: SerializedEnsIndexerStackInfo, +): Unvalidated { + // `ensDb` and `ensRainbow` are already in a deserialized form, so we can include them directly + const { ensDb, ensRainbow } = serializedStackInfo; + const ensIndexer = buildUnvalidatedEnsIndexerPublicConfig(serializedStackInfo.ensIndexer); + + return { + ensDb, + ensIndexer, + ensRainbow, + }; +} + +/** + * Deserialize value into {@link EnsIndexerStackInfo} object. + */ +export function deserializeEnsIndexerStackInfo( + maybeStackInfo: Unvalidated, + valueLabel?: string, +): EnsIndexerStackInfo { + const parsed = makeSerializedEnsIndexerStackInfoSchema(valueLabel) + .transform(buildUnvalidatedEnsIndexerStackInfo) + .pipe(makeEnsIndexerStackInfoSchema(valueLabel)) + .safeParse(maybeStackInfo); + + if (parsed.error) { + throw new Error(`Cannot deserialize EnsIndexerStackInfo:\n${prettifyError(parsed.error)}\n`); + } + + return parsed.data; +} diff --git a/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts index 40ee912e5a..d0c3d541d9 100644 --- a/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts @@ -1,7 +1,6 @@ import { prettifyError } from "zod/v4"; -import { buildUnvalidatedEnsApiPublicConfig } from "../../ensapi/config/deserialize"; -import { buildUnvalidatedEnsIndexerPublicConfig } from "../../ensindexer/config/deserialize"; +import { buildUnvalidatedEnsApiPublicConfig } from "../../ensapi"; import type { Unvalidated } from "../../shared/types"; import type { EnsNodeStackInfo } from "../ensnode-stack-info"; import type { SerializedEnsNodeStackInfo } from "../serialize/ensnode-stack-info"; @@ -9,6 +8,7 @@ import { makeEnsNodeStackInfoSchema, makeSerializedEnsNodeStackInfoSchema, } from "../zod-schemas/ensnode-stack-info"; +import { buildUnvalidatedEnsIndexerStackInfo } from "./ensindexer-stack-info"; /** * Builds an unvalidated {@link EnsNodeStackInfo} object to be @@ -20,16 +20,9 @@ import { export function buildUnvalidatedEnsNodeStackInfo( serializedStackInfo: SerializedEnsNodeStackInfo, ): Unvalidated { - // Stack info for ENSApi and ENSIndexer requires deserialization, - // so we handle them separately here before returning - // the final stack info object. Stack info for ENSDb and ENSRainbow can be - // passed through directly since they don't require deserialization. - const { ensApi, ensIndexer, ...rest } = serializedStackInfo; - return { - ...rest, - ensApi: buildUnvalidatedEnsApiPublicConfig(ensApi), - ensIndexer: buildUnvalidatedEnsIndexerPublicConfig(ensIndexer), + ...buildUnvalidatedEnsIndexerStackInfo(serializedStackInfo), + ensApi: buildUnvalidatedEnsApiPublicConfig(serializedStackInfo.ensApi), }; } diff --git a/packages/ensnode-sdk/src/stack-info/ensindexer-stack-info.ts b/packages/ensnode-sdk/src/stack-info/ensindexer-stack-info.ts new file mode 100644 index 0000000000..482dfad3eb --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/ensindexer-stack-info.ts @@ -0,0 +1,40 @@ +import type { EnsDbPublicConfig } from "../ensdb/config"; +import type { EnsIndexerPublicConfig } from "../ensindexer/config"; +import type { EnsRainbowPublicConfig } from "../ensrainbow/config"; +import { validateEnsIndexerStackInfo } from "./validate/ensindexer-stack-info"; + +/** + * Information about the stack of services associated with an ENSIndexer instance. + */ +export interface EnsIndexerStackInfo { + /** + * ENSDb Public Config + */ + ensDb: EnsDbPublicConfig; + + /** + * ENSIndexer Public Config + */ + ensIndexer: EnsIndexerPublicConfig; + + /** + * ENSRainbow Public Config + */ + ensRainbow: EnsRainbowPublicConfig; +} + +/** + * Build a complete {@link EnsIndexerStackInfo} object from + * the given public configs of ENSDb, ENSIndexer, and ENSRainbow. + */ +export function buildEnsIndexerStackInfo( + ensDbPublicConfig: EnsDbPublicConfig, + ensIndexerPublicConfig: EnsIndexerPublicConfig, + ensRainbowPublicConfig: EnsRainbowPublicConfig, +): EnsIndexerStackInfo { + return validateEnsIndexerStackInfo({ + ensDb: ensDbPublicConfig, + ensIndexer: ensIndexerPublicConfig, + ensRainbow: ensRainbowPublicConfig, + }); +} diff --git a/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts index 94f39d1955..3d16287c5d 100644 --- a/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts @@ -1,48 +1,32 @@ -import type { EnsApiPublicConfig } from "../ensapi/config/types"; +import type { EnsApiPublicConfig } from "../ensapi/config"; import type { EnsDbPublicConfig } from "../ensdb/config"; -import type { EnsIndexerPublicConfig } from "../ensindexer/config/types"; -import type { EnsRainbowPublicConfig } from "../ensrainbow/config"; +import type { EnsIndexerPublicConfig } from "../ensindexer/config"; +import type { EnsRainbowPublicConfig } from "../ensrainbow"; +import { buildEnsIndexerStackInfo, type EnsIndexerStackInfo } from "./ensindexer-stack-info"; +import { validateEnsNodeStackInfo } from "./validate/ensnode-stack-info"; /** * Information about the stack of services inside an ENSNode instance. */ -export interface EnsNodeStackInfo { +export interface EnsNodeStackInfo extends EnsIndexerStackInfo { /** * ENSApi Public Config */ ensApi: EnsApiPublicConfig; - - /** - * ENSDb Public Config - */ - ensDb: EnsDbPublicConfig; - - /** - * ENSIndexer Public Config - */ - ensIndexer: EnsIndexerPublicConfig; - - /** - * ENSRainbow Public Config - * - * If undefined, represents that ENSRainbow is currently undergoing - * a cold start and may take up to an hour to become ready. - */ - ensRainbow?: EnsRainbowPublicConfig; } /** * Build a complete {@link EnsNodeStackInfo} object from - * the given public configs of ENSApi and ENSDb. + * the given public configs of ENSApi, ENSDb, ENSIndexer, and ENSRainbow. */ export function buildEnsNodeStackInfo( ensApiPublicConfig: EnsApiPublicConfig, ensDbPublicConfig: EnsDbPublicConfig, + ensIndexerPublicConfig: EnsIndexerPublicConfig, + ensRainbowPublicConfig: EnsRainbowPublicConfig, ): EnsNodeStackInfo { - return { + return validateEnsNodeStackInfo({ + ...buildEnsIndexerStackInfo(ensDbPublicConfig, ensIndexerPublicConfig, ensRainbowPublicConfig), ensApi: ensApiPublicConfig, - ensDb: ensDbPublicConfig, - ensIndexer: ensApiPublicConfig.ensIndexerPublicConfig, - ensRainbow: ensApiPublicConfig.ensIndexerPublicConfig.ensRainbowPublicConfig, - }; + }); } diff --git a/packages/ensnode-sdk/src/stack-info/index.ts b/packages/ensnode-sdk/src/stack-info/index.ts index f3bf014196..f12f414254 100644 --- a/packages/ensnode-sdk/src/stack-info/index.ts +++ b/packages/ensnode-sdk/src/stack-info/index.ts @@ -1,3 +1,8 @@ +export * from "./deserialize/ensindexer-stack-info"; export * from "./deserialize/ensnode-stack-info"; +export * from "./ensindexer-stack-info"; export * from "./ensnode-stack-info"; +export * from "./serialize/ensindexer-stack-info"; export * from "./serialize/ensnode-stack-info"; +export * from "./validate/ensindexer-stack-info"; +export * from "./validate/ensnode-stack-info"; diff --git a/packages/ensnode-sdk/src/stack-info/serialize/ensindexer-stack-info.ts b/packages/ensnode-sdk/src/stack-info/serialize/ensindexer-stack-info.ts new file mode 100644 index 0000000000..47e39ac2da --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/serialize/ensindexer-stack-info.ts @@ -0,0 +1,35 @@ +import type { SerializedEnsDbPublicConfig } from "../../ensdb/serialize/config"; +import { serializeEnsIndexerPublicConfig } from "../../ensindexer/config/serialize"; +import type { SerializedEnsIndexerPublicConfig } from "../../ensindexer/config/serialized-types"; +import type { SerializedEnsRainbowPublicConfig } from "../../ensrainbow/serialize/config"; +import type { EnsIndexerStackInfo } from "../ensindexer-stack-info"; + +/** + * Serialized representation of {@link EnsIndexerStackInfo}. + */ +export interface SerializedEnsIndexerStackInfo { + ensDb: SerializedEnsDbPublicConfig; + ensIndexer: SerializedEnsIndexerPublicConfig; + ensRainbow: SerializedEnsRainbowPublicConfig; +} + +/** + * Serialize a {@link EnsIndexerStackInfo} object. + */ +export function serializeEnsIndexerStackInfo( + stackInfo: EnsIndexerStackInfo, +): SerializedEnsIndexerStackInfo { + // `ensDb` and `ensRainbow` are already in a serialized form, so we can include them directly + const { + ensDb: serializedEnsDbPublicConfig, + ensRainbow: serializedEnsRainbowPublicConfig, + ensIndexer, + } = stackInfo; + const serializedEnsIndexerPublicConfig = serializeEnsIndexerPublicConfig(ensIndexer); + + return { + ensDb: serializedEnsDbPublicConfig, + ensIndexer: serializedEnsIndexerPublicConfig, + ensRainbow: serializedEnsRainbowPublicConfig, + }; +} diff --git a/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts index 1c1f2f2638..3852fef5de 100644 --- a/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts @@ -1,19 +1,16 @@ import { serializeEnsApiPublicConfig } from "../../ensapi/config/serialize"; import type { SerializedEnsApiPublicConfig } from "../../ensapi/config/serialized-types"; -import type { SerializedEnsDbPublicConfig } from "../../ensdb/serialize/config"; -import { serializeEnsIndexerPublicConfig } from "../../ensindexer/config/serialize"; -import type { SerializedEnsIndexerPublicConfig } from "../../ensindexer/config/serialized-types"; -import type { SerializedEnsRainbowPublicConfig } from "../../ensrainbow/serialize/config"; import type { EnsNodeStackInfo } from "../ensnode-stack-info"; +import { + type SerializedEnsIndexerStackInfo, + serializeEnsIndexerStackInfo, +} from "./ensindexer-stack-info"; /** * Serialized representation of {@link EnsNodeStackInfo}. */ -export interface SerializedEnsNodeStackInfo { +export interface SerializedEnsNodeStackInfo extends SerializedEnsIndexerStackInfo { ensApi: SerializedEnsApiPublicConfig; - ensDb: SerializedEnsDbPublicConfig; - ensIndexer: SerializedEnsIndexerPublicConfig; - ensRainbow?: SerializedEnsRainbowPublicConfig; } /** @@ -21,9 +18,7 @@ export interface SerializedEnsNodeStackInfo { */ export function serializeEnsNodeStackInfo(stackInfo: EnsNodeStackInfo): SerializedEnsNodeStackInfo { return { + ...serializeEnsIndexerStackInfo(stackInfo), ensApi: serializeEnsApiPublicConfig(stackInfo.ensApi), - ensDb: stackInfo.ensDb, - ensIndexer: serializeEnsIndexerPublicConfig(stackInfo.ensIndexer), - ensRainbow: stackInfo.ensRainbow, }; } diff --git a/packages/ensnode-sdk/src/stack-info/validate/ensindexer-stack-info.ts b/packages/ensnode-sdk/src/stack-info/validate/ensindexer-stack-info.ts new file mode 100644 index 0000000000..46396745ac --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/validate/ensindexer-stack-info.ts @@ -0,0 +1,21 @@ +import { prettifyError } from "zod/v4"; + +import type { Unvalidated } from "../../shared/types"; +import type { EnsIndexerStackInfo } from "../ensindexer-stack-info"; +import { makeEnsIndexerStackInfoSchema } from "../zod-schemas/ensindexer-stack-info"; + +/** + * Validate a maybe {@link EnsIndexerStackInfo} object. + */ +export function validateEnsIndexerStackInfo( + maybeStackInfo: Unvalidated, + valueLabel?: string, +): EnsIndexerStackInfo { + const parsed = makeEnsIndexerStackInfoSchema(valueLabel).safeParse(maybeStackInfo); + + if (parsed.error) { + throw new Error(`Cannot validate EnsIndexerStackInfo:\n${prettifyError(parsed.error)}\n`); + } + + return parsed.data; +} diff --git a/packages/ensnode-sdk/src/stack-info/validate/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/validate/ensnode-stack-info.ts new file mode 100644 index 0000000000..1c24211267 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/validate/ensnode-stack-info.ts @@ -0,0 +1,21 @@ +import { prettifyError } from "zod/v4"; + +import type { Unvalidated } from "../../shared/types"; +import type { EnsNodeStackInfo } from "../ensnode-stack-info"; +import { makeEnsNodeStackInfoSchema } from "../zod-schemas/ensnode-stack-info"; + +/** + * Validate a maybe {@link EnsNodeStackInfo} object. + */ +export function validateEnsNodeStackInfo( + maybeStackInfo: Unvalidated, + valueLabel?: string, +): EnsNodeStackInfo { + const parsed = makeEnsNodeStackInfoSchema(valueLabel).safeParse(maybeStackInfo); + + if (parsed.error) { + throw new Error(`Cannot validate EnsNodeStackInfo:\n${prettifyError(parsed.error)}\n`); + } + + return parsed.data; +} diff --git a/packages/ensnode-sdk/src/stack-info/zod-schemas/ensindexer-stack-info.ts b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensindexer-stack-info.ts new file mode 100644 index 0000000000..185981932b --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensindexer-stack-info.ts @@ -0,0 +1,56 @@ +import { z } from "zod/v4"; + +import { makeEnsDbPublicConfigSchema } from "../../ensdb/zod-schemas/config"; +import { + makeEnsIndexerPublicConfigSchema, + makeSerializedEnsIndexerPublicConfigSchema, +} from "../../ensindexer/config/zod-schemas"; +import { makeEnsRainbowPublicConfigSchema } from "../../ensrainbow/zod-schemas/config"; +import type { ZodCheckFnInput } from "../../shared/zod-types"; +import type { EnsIndexerStackInfo } from "../ensindexer-stack-info"; + +export function makeSerializedEnsIndexerStackInfoSchema(valueLabel?: string) { + const label = valueLabel ?? "ENSIndexerStackInfo"; + + return z.object({ + ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), + ensIndexer: makeSerializedEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), + ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`), + }); +} + +export function invariant_ensRainbowCompatibilityWithEnsIndexer( + ctx: ZodCheckFnInput, +) { + const { ensIndexer, ensRainbow } = ctx.value; + const { clientLabelSet } = ensIndexer; + const { serverLabelSet } = ensRainbow; + + if (clientLabelSet.labelSetId !== serverLabelSet.labelSetId) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: `ENSRainbow's label set (id: ${serverLabelSet.labelSetId}) must be the same as ENSIndexer's label set (id: ${clientLabelSet.labelSetId}).`, + }); + } + + if (clientLabelSet.labelSetVersion > serverLabelSet.highestLabelSetVersion) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: `ENSRainbow's server label set version (highest: ${serverLabelSet.highestLabelSetVersion}) must be greater than or equal to ENSIndexer's client label set version (current: ${clientLabelSet.labelSetVersion}).`, + }); + } +} + +export function makeEnsIndexerStackInfoSchema(valueLabel?: string) { + const label = valueLabel ?? "ENSIndexerStackInfo"; + + return z + .object({ + ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), + ensIndexer: makeEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), + ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`), + }) + .check(invariant_ensRainbowCompatibilityWithEnsIndexer); +} diff --git a/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts index 094a17d078..f8a1bece01 100644 --- a/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts @@ -1,34 +1,76 @@ -import { z } from "zod/v4"; - import { makeEnsApiPublicConfigSchema, makeSerializedEnsApiPublicConfigSchema, } from "../../ensapi/config/zod-schemas"; -import { makeEnsDbPublicConfigSchema } from "../../ensdb/zod-schemas/config"; +import type { ZodCheckFnInput } from "../../shared/zod-types"; +import type { EnsNodeStackInfo } from "../ensnode-stack-info"; import { - makeEnsIndexerPublicConfigSchema, - makeSerializedEnsIndexerPublicConfigSchema, -} from "../../ensindexer/config/zod-schemas"; -import { makeEnsRainbowPublicConfigSchema } from "../../ensrainbow/zod-schemas/config"; + invariant_ensRainbowCompatibilityWithEnsIndexer, + makeEnsIndexerStackInfoSchema, + makeSerializedEnsIndexerStackInfoSchema, +} from "./ensindexer-stack-info"; + +function invariant_ensApiCompatibilityWithEnsIndexerAndEnsRainbow( + ctx: ZodCheckFnInput, +) { + const { ensApi, ensIndexer, ensRainbow } = ctx.value; + + // Invariant: ENSApi & ENSDB must match version numbers + if (ensIndexer.versionInfo.ensDb !== ensApi.versionInfo.ensApi) { + ctx.issues.push({ + code: "custom", + path: ["ensIndexer", "versionInfo", "ensDb"], + input: ensIndexer.versionInfo.ensDb, + message: `Version Mismatch: ENSDB@${ensIndexer.versionInfo.ensDb} !== ENSApi@${ensApi.versionInfo.ensApi}`, + }); + } + + // Invariant: ENSApi & ENSIndexer must match version numbers + if (ensIndexer.versionInfo.ensIndexer !== ensApi.versionInfo.ensApi) { + ctx.issues.push({ + code: "custom", + path: ["ensIndexer", "versionInfo", "ensIndexer"], + input: ensIndexer.versionInfo.ensIndexer, + message: `Version Mismatch: ENSIndexer@${ensIndexer.versionInfo.ensIndexer} !== ENSApi@${ensApi.versionInfo.ensApi}`, + }); + } + + // Invariant: ENSApi & ENSRainbow must match version numbers + if (ensRainbow.versionInfo.ensRainbow !== ensApi.versionInfo.ensApi) { + ctx.issues.push({ + code: "custom", + path: ["ensRainbow", "versionInfo", "ensRainbow"], + input: ensRainbow.versionInfo.ensRainbow, + message: `Version Mismatch: ENSRainbow@${ensRainbow.versionInfo.ensRainbow} !== ENSApi@${ensApi.versionInfo.ensApi}`, + }); + } + + // Invariant: `@adraffy/ens-normalize` package version must match between ENSApi & ENSIndexer + if (ensIndexer.versionInfo.ensNormalize !== ensApi.versionInfo.ensNormalize) { + ctx.issues.push({ + code: "custom", + path: ["ensIndexer", "versionInfo", "ensNormalize"], + input: ensIndexer.versionInfo.ensNormalize, + message: `Dependency Version Mismatch: '@adraffy/ens-normalize' version must be the same between ENSIndexer and ENSApi. Found ENSApi@${ensApi.versionInfo.ensNormalize} and ENSIndexer@${ensIndexer.versionInfo.ensNormalize}`, + }); + } +} export function makeSerializedEnsNodeStackInfoSchema(valueLabel?: string) { const label = valueLabel ?? "ENSNodeStackInfo"; - return z.object({ + return makeSerializedEnsIndexerStackInfoSchema(label).extend({ ensApi: makeSerializedEnsApiPublicConfigSchema(`${label}.ensApi`), - ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), - ensIndexer: makeSerializedEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), - ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`).optional(), }); } export function makeEnsNodeStackInfoSchema(valueLabel?: string) { const label = valueLabel ?? "ENSNodeStackInfo"; - return z.object({ - ensApi: makeEnsApiPublicConfigSchema(`${label}.ensApi`), - ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), - ensIndexer: makeEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), - ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`).optional(), - }); + return makeEnsIndexerStackInfoSchema(label) + .extend({ + ensApi: makeEnsApiPublicConfigSchema(`${label}.ensApi`), + }) + .check(invariant_ensApiCompatibilityWithEnsIndexerAndEnsRainbow) + .check(invariant_ensRainbowCompatibilityWithEnsIndexer); } diff --git a/packages/ensrainbow-sdk/src/client.test.ts b/packages/ensrainbow-sdk/src/client.test.ts index 16b57ca5e2..c2312ae911 100644 --- a/packages/ensrainbow-sdk/src/client.test.ts +++ b/packages/ensrainbow-sdk/src/client.test.ts @@ -28,7 +28,7 @@ describe("EnsRainbowApiClient", () => { expect(client.getOptions()).toEqual({ endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL), cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY, - labelSet: { + clientLabelSet: { labelSetId: undefined, labelSetVersion: undefined, }, @@ -45,7 +45,7 @@ describe("EnsRainbowApiClient", () => { expect(client.getOptions()).toEqual({ endpointUrl: customEndpointUrl, cacheCapacity: 0, - labelSet: { + clientLabelSet: { labelSetId: undefined, labelSetVersion: undefined, }, @@ -57,7 +57,7 @@ describe("EnsRainbowApiClient", () => { client = new EnsRainbowApiClient({ endpointUrl: customEndpointUrl, cacheCapacity: 0, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: undefined, }, @@ -66,7 +66,7 @@ describe("EnsRainbowApiClient", () => { expect(client.getOptions()).toEqual({ endpointUrl: customEndpointUrl, cacheCapacity: 0, - labelSet: { + clientLabelSet: { labelSetId: "subgraph", labelSetVersion: undefined, }, @@ -97,7 +97,7 @@ describe("EnsRainbowApiClient", () => { new EnsRainbowApiClient({ endpointUrl: customEndpointUrl, cacheCapacity: 0, - labelSet: { + clientLabelSet: { labelSetId: undefined, labelSetVersion: 0, }, @@ -274,12 +274,13 @@ describe("EnsRainbowApiClient", () => { describe("config", () => { it("should request /v1/config and return public config on success", async () => { const configData: EnsRainbow.ENSRainbowPublicConfig = { - version: "2.0.0", - labelSet: { + serverLabelSet: { labelSetId: "subgraph", highestLabelSetVersion: 5, }, - recordsCount: 133856894, + versionInfo: { + ensRainbow: "2.0.0", + }, }; mockFetch.mockResolvedValueOnce({ diff --git a/packages/ensrainbow-sdk/src/client.ts b/packages/ensrainbow-sdk/src/client.ts index f6511d5d0f..3144a35b6c 100644 --- a/packages/ensrainbow-sdk/src/client.ts +++ b/packages/ensrainbow-sdk/src/client.ts @@ -145,7 +145,7 @@ export interface EnsRainbowApiClientOptions { endpointUrl: URL; /** - * Optional label set preferences that the ENSRainbow server at endpointUrl is expected to + * Optional client label set preferences that the ENSRainbow server at endpointUrl is expected to * support. If provided, enables deterministic heal results across time, such that only * labels from label sets with versions less than or equal to this value will be returned. * Therefore, even if the ENSRainbow server later ingests label sets with greater versions @@ -159,7 +159,7 @@ export interface EnsRainbowApiClientOptions { * will be returned. * When `labelSetVersion` is defined, `labelSetId` must also be defined. */ - labelSet?: EnsRainbowClientLabelSet; + clientLabelSet?: EnsRainbowClientLabelSet; } /** @@ -178,7 +178,7 @@ export interface EnsRainbowApiClientOptions { export class EnsRainbowApiClient implements EnsRainbow.ApiClient { private readonly options: EnsRainbowApiClientOptions; private readonly cache: Cache; - private readonly labelSetSearchParams: URLSearchParams; + private readonly clientLabelSetSearchParams: URLSearchParams; public static readonly DEFAULT_CACHE_CAPACITY = 1000; @@ -191,23 +191,23 @@ export class EnsRainbowApiClient implements EnsRainbow.ApiClient { return { endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL), cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY, - labelSet: buildEnsRainbowClientLabelSet(), + clientLabelSet: buildEnsRainbowClientLabelSet(), }; } constructor(options: Partial = {}) { - const { labelSet: optionsLabelSet, ...rest } = options; + const { clientLabelSet: optionsClientLabelSet, ...rest } = options; const defaultOptions = EnsRainbowApiClient.defaultOptions(); const copiedLabelSet = buildEnsRainbowClientLabelSet( - optionsLabelSet?.labelSetId, - optionsLabelSet?.labelSetVersion, + optionsClientLabelSet?.labelSetId, + optionsClientLabelSet?.labelSetVersion, ); this.options = { ...defaultOptions, ...rest, - labelSet: copiedLabelSet, + clientLabelSet: copiedLabelSet, }; this.cache = new LruCache( @@ -215,14 +215,17 @@ export class EnsRainbowApiClient implements EnsRainbow.ApiClient { ); // Pre-compute query parameters for label set options - this.labelSetSearchParams = new URLSearchParams(); - if (this.options.labelSet?.labelSetId !== undefined) { - this.labelSetSearchParams.append("label_set_id", this.options.labelSet.labelSetId); + this.clientLabelSetSearchParams = new URLSearchParams(); + if (this.options.clientLabelSet?.labelSetId !== undefined) { + this.clientLabelSetSearchParams.append( + "label_set_id", + this.options.clientLabelSet.labelSetId, + ); } - if (this.options.labelSet?.labelSetVersion !== undefined) { - this.labelSetSearchParams.append( + if (this.options.clientLabelSet?.labelSetVersion !== undefined) { + this.clientLabelSetSearchParams.append( "label_set_version", - this.options.labelSet.labelSetVersion.toString(), + this.options.clientLabelSet.labelSetVersion.toString(), ); } } @@ -291,7 +294,7 @@ export class EnsRainbowApiClient implements EnsRainbow.ApiClient { const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl); // Apply pre-computed label set query parameters - this.labelSetSearchParams.forEach((value, key) => { + this.clientLabelSetSearchParams.forEach((value, key) => { url.searchParams.append(key, value); }); @@ -375,7 +378,7 @@ export class EnsRainbowApiClient implements EnsRainbow.ApiClient { const deepCopy = { cacheCapacity: this.options.cacheCapacity, endpointUrl: new URL(this.options.endpointUrl.href), - labelSet: this.options.labelSet ? { ...this.options.labelSet } : undefined, + clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : undefined, } satisfies EnsRainbowApiClientOptions; return Object.freeze(deepCopy);