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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-jobs-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Added indexing status based functions for checking Omnigraph API and Subgraph API availability.
5 changes: 5 additions & 0 deletions .changeset/four-cats-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensadmin": minor
---

Added indexing status based guard for Omnigraph API and Subgraph API views.
5 changes: 5 additions & 0 deletions .changeset/loose-pets-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensapi": minor
---

Added indexing status based guard for Omnigraph API and Subgraph API routes.
30 changes: 28 additions & 2 deletions apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { useMemo } from "react";

import {
hasOmnigraphApiConfigSupport,
hasOmnigraphApiIndexingStatusSupport,
hasRegistrarActionsConfigSupport,
hasRegistrarActionsIndexingStatusSupport,
hasSubgraphApiConfigSupport,
hasSubgraphApiIndexingStatusSupport,
PrerequisiteResult,
} from "@ensnode/ensnode-sdk";

Expand Down Expand Up @@ -86,15 +88,39 @@ export function useENSAdminFeatures(): ENSAdminFeatures {
if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS;

const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo;
return prerequisiteResultToFeatureStatus(hasSubgraphApiConfigSupport(ensIndexerPublicConfig));
const configSupportResult = hasSubgraphApiConfigSupport(ensIndexerPublicConfig);
if (!configSupportResult.supported)
return prerequisiteResultToFeatureStatus(configSupportResult);

const { realtimeProjection } = indexingStatusQuery.data;
const { omnichainSnapshot } = realtimeProjection.snapshot;

const indexingStatusSupportResult = hasSubgraphApiIndexingStatusSupport(
omnichainSnapshot.omnichainStatus,
);
if (!indexingStatusSupportResult.supported)
return { type: "not-ready", reason: indexingStatusSupportResult.reason };
return { type: "supported" };
}, [indexingStatusQuery]);

const omnigraph: FeatureStatus = useMemo(() => {
if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS;
if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS;

const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo;
return prerequisiteResultToFeatureStatus(hasOmnigraphApiConfigSupport(ensIndexerPublicConfig));
const configSupportResult = hasOmnigraphApiConfigSupport(ensIndexerPublicConfig);
if (!configSupportResult.supported)
return prerequisiteResultToFeatureStatus(configSupportResult);

const { realtimeProjection } = indexingStatusQuery.data;
const { omnichainSnapshot } = realtimeProjection.snapshot;

const indexingStatusSupportResult = hasOmnigraphApiIndexingStatusSupport(
omnichainSnapshot.omnichainStatus,
);
if (!indexingStatusSupportResult.supported)
return { type: "not-ready", reason: indexingStatusSupportResult.reason };
return { type: "supported" };
}, [indexingStatusQuery]);

const restApi: FeatureStatus = useMemo(() => {
Expand Down
30 changes: 24 additions & 6 deletions apps/ensapi/src/handlers/api/omnigraph/omnigraph-api.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import config from "@/config";

import { hasOmnigraphApiConfigSupport } from "@ensnode/ensnode-sdk";
import {
hasOmnigraphApiConfigSupport,
hasOmnigraphApiIndexingStatusSupport,
} from "@ensnode/ensnode-sdk";

import { createApp } from "@/lib/hono-factory";
import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware";

const app = createApp();
const app = createApp({ middlewares: [indexingStatusMiddleware] });

// 503 if prerequisites not met
app.use(async (c, next) => {
const prerequisite = hasOmnigraphApiConfigSupport(config.ensIndexerPublicConfig);
if (!prerequisite.supported) {
return c.text(`Service Unavailable: ${prerequisite.reason}`, 503);
const configPrerequisite = hasOmnigraphApiConfigSupport(config.ensIndexerPublicConfig);
// 503 if Omnigraph API is not available due to config prerequisites not met
if (!configPrerequisite.supported) {
return c.text(`Service Unavailable: ${configPrerequisite.reason}`, 503);
}

// 503 if indexing status snapshot is not available yet
if (c.var.indexingStatus instanceof Error) {
return c.text(`Service Unavailable: Indexing Status Snapshot is not available yet`, 503);
Comment thread
tk-o marked this conversation as resolved.
Comment thread
tk-o marked this conversation as resolved.
}

// 503 if omnigraph API not available due to indexing status prerequisites not met
const indexingStatusPrerequisite = hasOmnigraphApiIndexingStatusSupport(
c.var.indexingStatus.snapshot.omnichainSnapshot.omnichainStatus,
);

if (!indexingStatusPrerequisite.supported) {
return c.text(`Service Unavailable: ${indexingStatusPrerequisite.reason}`, 503);
}
Comment thread
tk-o marked this conversation as resolved.

await next();
Expand Down
32 changes: 23 additions & 9 deletions apps/ensapi/src/handlers/subgraph/subgraph-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { createDocumentationMiddleware } from "ponder-enrich-gql-docs-middleware
// Once the lazy proxy implemented for `ensIndexerSchema` export is improved
// to support Drizzle ORM in `ponder-subgraph` package.
import * as ensIndexerSchema from "@ensnode/ensdb-sdk/ensindexer-abstract";
import { hasSubgraphApiConfigSupport } from "@ensnode/ensnode-sdk";
import {
hasSubgraphApiConfigSupport,
hasSubgraphApiIndexingStatusSupport,
} from "@ensnode/ensnode-sdk";
import { subgraphGraphQLMiddleware } from "@ensnode/ponder-subgraph";

import { createApp } from "@/lib/hono-factory";
Expand All @@ -26,21 +29,32 @@ const MAX_REALTIME_DISTANCE_TO_RESOLVE: Duration = 10 * 60; // 10 minutes in sec
// generate a subgraph-specific subset of the schema
const subgraphSchema = filterSchemaByPrefix("subgraph_", ensIndexerSchema);

const app = createApp();
const app = createApp({ middlewares: [indexingStatusMiddleware] });

// 503 if subgraph plugin not available
app.use(async (c, next) => {
const prerequisite = hasSubgraphApiConfigSupport(config.ensIndexerPublicConfig);
if (!prerequisite.supported) {
return c.text(`Service Unavailable: ${prerequisite.reason}`, 503);
const configPrerequisite = hasSubgraphApiConfigSupport(config.ensIndexerPublicConfig);
// 503 if Subgraph API is not available due to config prerequisites not met
if (!configPrerequisite.supported) {
return c.text(`Service Unavailable: ${configPrerequisite.reason}`, 503);
}

// 503 if indexing status snapshot is not available yet
if (c.var.indexingStatus instanceof Error) {
return c.text(`Service Unavailable: Indexing Status Snapshot is not available yet`, 503);
Comment thread
tk-o marked this conversation as resolved.
}
Comment thread
tk-o marked this conversation as resolved.

// 503 if Subgraph API is not available due to indexing status prerequisites not met
const indexingStatusPrerequisite = hasSubgraphApiIndexingStatusSupport(
c.var.indexingStatus.snapshot.omnichainSnapshot.omnichainStatus,
);

if (!indexingStatusPrerequisite.supported) {
return c.text(`Service Unavailable: ${indexingStatusPrerequisite.reason}`, 503);
Comment thread
tk-o marked this conversation as resolved.
}

await next();
});

// inject c.var.indexingStatus
app.use(indexingStatusMiddleware);

// inject c.var.isRealtime derived from MAX_REALTIME_DISTANCE_TO_RESOLVE
app.use(makeIsRealtimeMiddleware("subgraph-api", MAX_REALTIME_DISTANCE_TO_RESOLVE));

Expand Down
24 changes: 24 additions & 0 deletions packages/ensnode-sdk/src/ensnode/api/prerequisites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type OmnichainIndexingStatusId, OmnichainIndexingStatusIds } from "../../indexing-status";
import type { PrerequisiteResult } from "../../shared/prerequisites";

/**
* Check if provided OmnichainIndexingStatusId indicates that the backfill is complete.
*
* This is a prerequisite for all APIs that rely on indexed data. We need to ensure that
* the backfill is complete to guarantee that the necessary data is completely indexed
* and available for queries.
Comment thread
tk-o marked this conversation as resolved.
*/
export function hasBackfillCompleted(
indexingStatus: OmnichainIndexingStatusId,
): PrerequisiteResult {
const supported =
indexingStatus === OmnichainIndexingStatusIds.Completed ||
indexingStatus === OmnichainIndexingStatusIds.Following;

if (supported) return { supported };

return {
supported: false,
reason: `The connected ENSNode's Indexing Status must be "${OmnichainIndexingStatusIds.Completed}" or "${OmnichainIndexingStatusIds.Following}". Currently, it is "${indexingStatus}".`,
};
}
13 changes: 12 additions & 1 deletion packages/ensnode-sdk/src/omnigraph-api/prerequisites.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { type EnsIndexerPublicConfig, PluginName } from "../ensindexer/config/types";
import { hasBackfillCompleted } from "../ensnode/api/prerequisites";
import type { OmnichainIndexingStatusId } from "../indexing-status";
import type { PrerequisiteResult } from "../shared/prerequisites";

/**
* Check if provided EnsIndexerPublicConfig supports the ENSNode Omnigraph API.
* Check if provided EnsIndexerPublicConfig supports the Omnigraph API.
*/
export function hasOmnigraphApiConfigSupport(config: EnsIndexerPublicConfig): PrerequisiteResult {
const supported = config.plugins.includes(PluginName.ENSv2);
Expand All @@ -13,3 +15,12 @@ export function hasOmnigraphApiConfigSupport(config: EnsIndexerPublicConfig): Pr
reason: `The connected ENSNode's Config must have the '${PluginName.ENSv2}' plugin enabled.`,
};
}

/**
* Check if provided OmnichainIndexingStatusId supports the Omnigraph API.
*/
export function hasOmnigraphApiIndexingStatusSupport(
indexingStatus: OmnichainIndexingStatusId,
): PrerequisiteResult {
return hasBackfillCompleted(indexingStatus);
}
11 changes: 11 additions & 0 deletions packages/ensnode-sdk/src/subgraph-api/prerequisites.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type EnsIndexerPublicConfig, PluginName } from "../ensindexer/config/types";
import { hasBackfillCompleted } from "../ensnode/api/prerequisites";
import type { OmnichainIndexingStatusId } from "../indexing-status";
import type { PrerequisiteResult } from "../shared/prerequisites";

/**
Expand All @@ -13,3 +15,12 @@ export function hasSubgraphApiConfigSupport(config: EnsIndexerPublicConfig): Pre
reason: `The connected ENSNode's Config must have the '${PluginName.Subgraph}' plugin enabled.`,
};
}

/**
* Check if provided OmnichainIndexingStatusId supports the Subgraph API.
*/
export function hasSubgraphApiIndexingStatusSupport(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo DRY up this impl and have the reason message just say

`The connected ENSNode's Omnichain Indexing Status must be "${OmnichainIndexingStatusIds.Completed}" or "${OmnichainIndexingStatusIds.Following}". Current omnichain indexing status is "${indexingStatus}".`

indexingStatus: OmnichainIndexingStatusId,
): PrerequisiteResult {
return hasBackfillCompleted(indexingStatus);
}
Comment thread
tk-o marked this conversation as resolved.
Comment thread
tk-o marked this conversation as resolved.
Loading