-
Notifications
You must be signed in to change notification settings - Fork 15
Make ENSDb Writer Worker to use builders for ENSIndexer Public Config and Indexing Status objects #1715
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
Make ENSDb Writer Worker to use builders for ENSIndexer Public Config and Indexing Status objects #1715
Changes from all commits
2a44669
9a62c0e
64bbc0b
f95146b
7a73da2
ab000eb
a655c23
d775259
e28d4d2
66f17c5
4dc3b7c
8e53487
5fa7ba0
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 @@ | ||
| --- | ||
| "@ensnode/ensnode-sdk": minor | ||
| --- | ||
|
|
||
| Added `validateEnsIndexerPublicConfig` and `validateEnsIndexerVersionInfo` functions. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ensindexer": minor | ||
| --- | ||
|
|
||
| Refactored HTTP handlers to rely solely on ENSDb Client for data. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,7 @@ | ||||||||||||||||||||||||||||
| import config from "@/config"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { getUnixTime } from "date-fns"; | ||||||||||||||||||||||||||||
| import { Hono } from "hono"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||
| buildCrossChainIndexingStatusSnapshotOmnichain, | ||||||||||||||||||||||||||||
| createRealtimeIndexingStatusProjection, | ||||||||||||||||||||||||||||
| IndexingStatusResponseCodes, | ||||||||||||||||||||||||||||
| type IndexingStatusResponseError, | ||||||||||||||||||||||||||||
|
|
@@ -13,31 +10,37 @@ import { | |||||||||||||||||||||||||||
| serializeIndexingStatusResponse, | ||||||||||||||||||||||||||||
| } from "@ensnode/ensnode-sdk"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { buildENSIndexerPublicConfig } from "@/config/public"; | ||||||||||||||||||||||||||||
| import { indexingStatusBuilder } from "@/lib/indexing-status-builder/singleton"; | ||||||||||||||||||||||||||||
| import { ensDbClient } from "@/lib/ensdb-client/singleton"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const app = new Hono(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // include ENSIndexer Public Config endpoint | ||||||||||||||||||||||||||||
| app.get("/config", async (c) => { | ||||||||||||||||||||||||||||
| // prepare the public config object, including dependency info | ||||||||||||||||||||||||||||
| const publicConfig = await buildENSIndexerPublicConfig(config); | ||||||||||||||||||||||||||||
| const publicConfig = await ensDbClient.getEnsIndexerPublicConfig(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Invariant: the public config is guaranteed to be available in ENSDb after | ||||||||||||||||||||||||||||
| // application startup. | ||||||||||||||||||||||||||||
| if (typeof publicConfig === "undefined") { | ||||||||||||||||||||||||||||
| throw new Error("Unreachable: ENSIndexer Public Config is not available in ENSDb"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+25
Contributor
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.
✅ Suggested handling if (typeof publicConfig === "undefined") {
- throw new Error("Unreachable: ENSIndexer Public Config is not available in ENSDb");
+ return c.json(
+ { message: "ENSIndexer Public Config is not available yet." },
+ 503,
+ );
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
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. In practice, it won't be rechable. ENSIndexer might not need any HTTP API whatsoever.
Contributor
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.
I'll record this for future reviews. ✏️ Learnings added
🧠 Learnings used |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // respond with the serialized public config object | ||||||||||||||||||||||||||||
| return c.json(serializeENSIndexerPublicConfig(publicConfig)); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| app.get("/indexing-status", async (c) => { | ||||||||||||||||||||||||||||
| // get system timestamp for the current request | ||||||||||||||||||||||||||||
| const snapshotTime = getUnixTime(new Date()); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| const omnichainSnapshot = await indexingStatusBuilder.getOmnichainIndexingStatusSnapshot(); | ||||||||||||||||||||||||||||
| const crossChainSnapshot = await ensDbClient.getIndexingStatusSnapshot(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const crossChainSnapshot = buildCrossChainIndexingStatusSnapshotOmnichain( | ||||||||||||||||||||||||||||
| omnichainSnapshot, | ||||||||||||||||||||||||||||
| snapshotTime, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| // Invariant: the Indexing Status Snapshot is expected to be available in | ||||||||||||||||||||||||||||
| // ENSDb shortly after application startup. There is a possibility that | ||||||||||||||||||||||||||||
| // the snapshot is not yet available at the time of the request, | ||||||||||||||||||||||||||||
| // i.e. when ENSDb has not yet been populated with the first snapshot. | ||||||||||||||||||||||||||||
| // In this case, we treat the snapshot as unavailable and respond with | ||||||||||||||||||||||||||||
| // an error response. | ||||||||||||||||||||||||||||
| if (typeof crossChainSnapshot === "undefined") { | ||||||||||||||||||||||||||||
| throw new Error("ENSDb does not contain an Indexing Status Snapshot"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const projectedAt = getUnixTime(new Date()); | ||||||||||||||||||||||||||||
| const realtimeProjection = createRealtimeIndexingStatusProjection( | ||||||||||||||||||||||||||||
|
|
@@ -53,7 +56,7 @@ app.get("/indexing-status", async (c) => { | |||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||
| const errorMessage = error instanceof Error ? error.message : "Unknown error"; | ||||||||||||||||||||||||||||
| console.error(`Omnichain snapshot is currently not available: ${errorMessage}`); | ||||||||||||||||||||||||||||
| console.error(`Indexing Status Snapshot is currently not available: ${errorMessage}`); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return c.json( | ||||||||||||||||||||||||||||
| serializeIndexingStatusResponse({ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { vi } from "vitest"; | ||
|
|
||
| import { | ||
| type CrossChainIndexingStatusSnapshot, | ||
| CrossChainIndexingStrategyIds, | ||
| type EnsIndexerPublicConfig, | ||
| OmnichainIndexingStatusIds, | ||
| type OmnichainIndexingStatusSnapshot, | ||
| } from "@ensnode/ensnode-sdk"; | ||
|
|
||
| import type { EnsDbClient } from "@/lib/ensdb-client/ensdb-client"; | ||
| import * as ensDbClientMock from "@/lib/ensdb-client/ensdb-client.mock"; | ||
| import type { IndexingStatusBuilder } from "@/lib/indexing-status-builder"; | ||
| import type { PublicConfigBuilder } from "@/lib/public-config-builder"; | ||
|
|
||
| // Helper to create mock objects with consistent typing | ||
| export function createMockEnsDbClient( | ||
| overrides: Partial<ReturnType<typeof baseEnsDbClient>> = {}, | ||
| ): EnsDbClient { | ||
| return { | ||
| ...baseEnsDbClient(), | ||
| ...overrides, | ||
| } as unknown as EnsDbClient; | ||
| } | ||
|
|
||
| export function baseEnsDbClient() { | ||
| return { | ||
| getEnsIndexerPublicConfig: vi.fn().mockResolvedValue(undefined), | ||
| upsertEnsDbVersion: vi.fn().mockResolvedValue(undefined), | ||
| upsertEnsIndexerPublicConfig: vi.fn().mockResolvedValue(undefined), | ||
| upsertIndexingStatusSnapshot: vi.fn().mockResolvedValue(undefined), | ||
| }; | ||
| } | ||
|
|
||
| export function createMockPublicConfigBuilder( | ||
| resolvedConfig: EnsIndexerPublicConfig = ensDbClientMock.publicConfig, | ||
| ): PublicConfigBuilder { | ||
| return { | ||
| getPublicConfig: vi.fn().mockResolvedValue(resolvedConfig), | ||
| } as unknown as PublicConfigBuilder; | ||
| } | ||
|
|
||
| export function createMockIndexingStatusBuilder( | ||
| resolvedSnapshot: OmnichainIndexingStatusSnapshot = createMockOmnichainSnapshot(), | ||
| ): IndexingStatusBuilder { | ||
| return { | ||
| getOmnichainIndexingStatusSnapshot: vi.fn().mockResolvedValue(resolvedSnapshot), | ||
| } as unknown as IndexingStatusBuilder; | ||
| } | ||
|
|
||
| export function createMockOmnichainSnapshot( | ||
| overrides: Partial<OmnichainIndexingStatusSnapshot> = {}, | ||
| ): OmnichainIndexingStatusSnapshot { | ||
| return { | ||
| omnichainStatus: OmnichainIndexingStatusIds.Following, | ||
| omnichainIndexingCursor: 100, | ||
| chains: new Map(), | ||
| ...overrides, | ||
| }; | ||
| } | ||
|
|
||
| export function createMockCrossChainSnapshot( | ||
| overrides: Partial<CrossChainIndexingStatusSnapshot> = {}, | ||
| ): CrossChainIndexingStatusSnapshot { | ||
| return { | ||
| strategy: CrossChainIndexingStrategyIds.Omnichain, | ||
| slowestChainIndexingCursor: 100, | ||
| snapshotTime: 200, | ||
| omnichainSnapshot: createMockOmnichainSnapshot(), | ||
| ...overrides, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
"Unreachable"branch is reachable:startEnsDbWriterWorker()is kicked off without awaiting.run(), so/configcan be called before the worker has upserted the public config to ENSDb. Instead of throwing (which becomes a 500), return a controlled 503/500 response indicating config isn’t ready yet, or await worker initialization before serving routes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice, it won't be rechable. ENSIndexer might not need any HTTP API whatsoever.