-
Notifications
You must be signed in to change notification settings - Fork 16
Fix Ponder Build ID uniqueness #1913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b132ade
282dc83
192945f
e381726
deb0b7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ensindexer": minor | ||
| --- | ||
|
|
||
| Updated Ponder Config object to include values that indexing behavior depends on. This is to ensure that the Ponder Build ID changes when any value in indexing behavior dependencies changes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@ensnode/ensdb-sdk": minor | ||
| --- | ||
|
|
||
| Exported `ENSDB_SCHEMA_CHECKSUM` const which changes when ENSDb Schema definition changes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| import config from "@/config"; | ||
|
|
||
| import type { ContractConfig } from "ponder"; | ||
|
|
||
| import { getENSRootChainId } from "@ensnode/datasources"; | ||
| import { ENSDB_SCHEMA_CHECKSUM } from "@ensnode/ensdb-sdk"; | ||
|
|
||
| import type { EnsIndexerConfig } from "@/config/types"; | ||
|
|
||
| /** | ||
| * Indexing Behavior Dependencies | ||
| * | ||
| * Defines all values that influence the indexing behavior of the ENSIndexer | ||
| * instance. | ||
| */ | ||
| interface IndexingBehaviorDependencies { | ||
| /** | ||
| * ENS Namespace | ||
| * | ||
| * When `namespace` changes, the datasources used for indexing may change, | ||
| * which influences the indexing behavior. | ||
| */ | ||
| namespace: string; | ||
|
|
||
| /** | ||
| * ENSIndexer Plugins | ||
| * | ||
| * When `plugins` change, the indexed chains and contracts may change, | ||
| * which influences the indexing behavior. | ||
| */ | ||
| plugins: EnsIndexerConfig["plugins"]; | ||
|
|
||
| /** | ||
| * Global Blockrange | ||
| * | ||
| * When `globalBlockrange` changes, the blockrange of indexed chains may change, | ||
| * which influences the indexing behavior. | ||
| */ | ||
| globalBlockrange: EnsIndexerConfig["globalBlockrange"]; | ||
|
|
||
| /** | ||
| * Subgraph Compatibility | ||
| * | ||
| * When `isSubgraphCompatible` changes, the indexing logic may change, | ||
| * which influences the indexing behavior. | ||
| */ | ||
| isSubgraphCompatible: boolean; | ||
|
|
||
| /** | ||
| * Label Set | ||
| * | ||
| * When `labelSet` changes, the label "healing" results may change during indexing, | ||
| * which influences the indexing behavior. | ||
| */ | ||
| labelSet: EnsIndexerConfig["labelSet"]; | ||
|
|
||
| /** | ||
| * ENSDb Schema Checksum | ||
| * | ||
| * When `ensDbSchemaChecksum` changes, the ENSDb schema definition may have | ||
| * changed, which influences the indexing behavior. | ||
| */ | ||
| ensDbSchemaChecksum: string; | ||
| } | ||
|
|
||
| /** | ||
| * A special "indexing behavior injection" contract config | ||
| * | ||
| * This config does not reference any real onchain contract to be indexed. | ||
| * Instead, it serves as a placeholder to collect all values that influence | ||
| * the indexing behavior of the ENSIndexer instance. | ||
| * | ||
| * This contract config is designed to be injected into the `contracts` field | ||
| * of the Ponder Config object. | ||
| */ | ||
| interface IndexingBehaviorInjectionContractConfig extends ContractConfig { | ||
| indexingBehaviorDependencies: IndexingBehaviorDependencies; | ||
| } | ||
|
|
||
| /** | ||
| * Build a contract config placeholder with the necessary fields to be included in | ||
| * the `contracts` field of the Ponder Config. | ||
| */ | ||
| function buildContractConfigPlaceholder(): ContractConfig { | ||
| return { | ||
| // The placeholder contract does not reference any real chain, | ||
| // but we need to provide a valid chain id to satisfy the ContractConfig type. | ||
| // The ENS Root Chain ID is a reasonable choice since it's guaranteed to be | ||
| // a valid indexed chain ID for any ENSIndexer instance. | ||
| chain: `${getENSRootChainId(config.namespace)}`, | ||
| // The placeholder contract does not have any real ABI, | ||
| // but we need to provide an empty array to satisfy the ContractConfig type. | ||
| abi: [], | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Indexing Behavior Dependencies | ||
| */ | ||
| const indexingBehaviorDependencies = { | ||
| // while technically not necessary, since these config properties are reflected in the | ||
| // generated ponderConfig, we include them here for clarity | ||
| namespace: config.namespace, | ||
| // Sort plugins to ensure canonical checksum regardless of config order. | ||
| // The actual indexing behavior does not depend on plugin order since: | ||
| // 1. All plugin checks use Array.includes() which is order-independent | ||
| // 2. Plugin execution order is determined by `ALL_PLUGINS`, not config.plugins | ||
| // Sorting ensures consistent Build IDs for semantically identical config. | ||
| plugins: [...config.plugins].sort(), | ||
| globalBlockrange: config.globalBlockrange, | ||
| // these config properties don't explicitly affect the generated ponderConfig and need to be | ||
| // 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, | ||
| ensDbSchemaChecksum: ENSDB_SCHEMA_CHECKSUM, | ||
| } satisfies IndexingBehaviorDependencies; | ||
|
tk-o marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * A special "indexing behavior injection" contract config | ||
| * | ||
| * This config is designed to be injected into the `contracts` field of | ||
| * the Ponder Config object in order to make Ponder create | ||
| * a unique build ID for any changes to {@link indexingBehaviorDependencies}. | ||
| */ | ||
| export const IndexingBehaviorInjectionContract = { | ||
| ...buildContractConfigPlaceholder(), | ||
| indexingBehaviorDependencies, | ||
| } satisfies IndexingBehaviorInjectionContractConfig; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,7 @@ | ||||||||||||||
| import * as abstractEnsIndexerSchema from "../ensindexer-abstract"; | ||||||||||||||
| import * as ensNodeSchema from "../ensnode"; | ||||||||||||||
| import { getDrizzleSchemaChecksum } from "../lib/drizzle"; | ||||||||||||||
|
|
||||||||||||||
|
tk-o marked this conversation as resolved.
|
||||||||||||||
| /** | ||||||||||||||
| * ENSDb Config | ||||||||||||||
| */ | ||||||||||||||
|
|
@@ -19,3 +23,19 @@ export interface EnsDbConfig { | |||||||||||||
| */ | ||||||||||||||
| ensIndexerSchemaName: string; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * ENSDb Schema Checksum | ||||||||||||||
| * | ||||||||||||||
| * Checksum representing the ENSDb Schema definition, which is a combination of | ||||||||||||||
| * - the ENSIndexer Schema definition, and | ||||||||||||||
| * - the ENSNode Schema definition. | ||||||||||||||
| * | ||||||||||||||
| * This checksum can be used to verify compatibility between | ||||||||||||||
| * the ENSDb Schema definition expected by any client app connecting to ENSDb | ||||||||||||||
| * instance and the actual ENSDb Schema definition present in ENSDb SDK. | ||||||||||||||
|
tk-o marked this conversation as resolved.
|
||||||||||||||
| */ | ||||||||||||||
| export const ENSDB_SCHEMA_CHECKSUM = getDrizzleSchemaChecksum({ | ||||||||||||||
| ...abstractEnsIndexerSchema, | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it necessary to include ensindexerschema in this checksum? i thought it was still included in the ponder-generated build id, but if that's not the case, then good catch
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shrugs ENSDb Schema definition includes both, ENSIndexer Schema definition and ENSNode Schema definition. Changing any of those schema definitions may result in changed indexing behavior. While generating the Ponder Build ID, Ponder reads the literal contents of the ensnode/apps/ensindexer/ponder/ponder.schema.ts Lines 1 to 6 in c78a960
As you can see, the Here's how Ponder calculated the content hash value for |
||||||||||||||
| ...ensNodeSchema, | ||||||||||||||
| }); | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { createChecksum } from "./checksum"; | ||
|
|
||
| describe("createChecksum", () => { | ||
| it("returns a 10-character hex string", () => { | ||
| const checksum = createChecksum("test data"); | ||
|
|
||
| expect(checksum).toHaveLength(10); | ||
| expect(checksum).toMatch(/^[a-f0-9]{10}$/); | ||
| }); | ||
|
|
||
| it("returns consistent results for the same input", () => { | ||
| const input = "consistent input"; | ||
| const checksum1 = createChecksum(input); | ||
| const checksum2 = createChecksum(input); | ||
|
|
||
| expect(checksum1).toBe(checksum2); | ||
| }); | ||
|
|
||
| it("returns different results for different inputs", () => { | ||
| const checksum1 = createChecksum("input one"); | ||
| const checksum2 = createChecksum("input two"); | ||
|
|
||
| expect(checksum1).not.toBe(checksum2); | ||
| }); | ||
|
|
||
| it("handles empty string input", () => { | ||
| const checksum = createChecksum(""); | ||
|
|
||
| expect(checksum).toHaveLength(10); | ||
| expect(checksum).toMatch(/^[a-f0-9]{10}$/); | ||
| }); | ||
|
|
||
| it("handles Buffer input", () => { | ||
| const buffer = Buffer.from("buffer data"); | ||
| const checksum = createChecksum(buffer); | ||
|
|
||
| expect(checksum).toHaveLength(10); | ||
| expect(checksum).toMatch(/^[a-f0-9]{10}$/); | ||
| }); | ||
|
|
||
| it("handles Uint8Array input", () => { | ||
| const uint8Array = new Uint8Array([1, 2, 3, 4, 5]); | ||
| const checksum = createChecksum(uint8Array); | ||
|
|
||
| expect(checksum).toHaveLength(10); | ||
| expect(checksum).toMatch(/^[a-f0-9]{10}$/); | ||
| }); | ||
|
|
||
| it("produces expected checksum for known input", () => { | ||
| // SHA-256 of "hello" starts with "2cf24dba5f..." | ||
| const checksum = createChecksum("hello"); | ||
|
|
||
| expect(checksum).toBe("2cf24dba5f"); | ||
| }); | ||
| }); | ||
|
tk-o marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { type BinaryLike, createHash } from "node:crypto"; | ||
|
|
||
| /** | ||
| * Create a checksum for the given data | ||
| * | ||
| * @param data - The data to create a checksum for | ||
| * @returns A 10-character hash string representing the checksum of the data | ||
| * | ||
| */ | ||
|
tk-o marked this conversation as resolved.
|
||
| export function createChecksum(data: BinaryLike): string { | ||
| return createHash("sha256").update(data).digest("hex").slice(0, 10); | ||
|
tk-o marked this conversation as resolved.
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.