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
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import {
type ChainIndexingStatusSnapshotFollowing,
type ChainIndexingStatusSnapshotQueued,
} from "./chain-indexing-status-snapshot";
import { deserializeOmnichainIndexingStatusSnapshot } from "./deserialize";
import { serializeOmnichainIndexingStatusSnapshot } from "./serialize";
import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialized-types";
import { OmnichainIndexingStatusIds, type OmnichainIndexingStatusSnapshot } from "./types";
import { deserializeOmnichainIndexingStatusSnapshot } from "./deserialize/omnichain-indexing-status-snapshot";
import {
OmnichainIndexingStatusIds,
type OmnichainIndexingStatusSnapshot,
} from "./omnichain-indexing-status-snapshot";
import {
type SerializedOmnichainIndexingStatusSnapshot,
serializeOmnichainIndexingStatusSnapshot,
} from "./serialize/omnichain-indexing-status-snapshot";

describe("ENSIndexer: Indexing Status", () => {
describe("Omnichain Indexing Status Snapshot", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,14 @@ import { prettifyError } from "zod/v4";

import type {
SerializedCrossChainIndexingStatusSnapshot,
SerializedOmnichainIndexingStatusSnapshot,
SerializedRealtimeIndexingStatusProjection,
} from "./serialized-types";
import type {
CrossChainIndexingStatusSnapshot,
OmnichainIndexingStatusSnapshot,
RealtimeIndexingStatusProjection,
} from "./types";
import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types";
import {
makeCrossChainIndexingStatusSnapshotSchema,
makeOmnichainIndexingStatusSnapshotSchema,
makeRealtimeIndexingStatusProjectionSchema,
} from "./zod-schemas";

/**
* Deserialize an {@link OmnichainIndexingStatusSnapshot} object.
*/
export function deserializeOmnichainIndexingStatusSnapshot(
maybeSnapshot: SerializedOmnichainIndexingStatusSnapshot,
valueLabel?: string,
): OmnichainIndexingStatusSnapshot {
const schema = makeOmnichainIndexingStatusSnapshotSchema(valueLabel);
const parsed = schema.safeParse(maybeSnapshot);

if (parsed.error) {
throw new Error(
`Cannot deserialize into OmnichainIndexingStatusSnapshot:\n${prettifyError(parsed.error)}\n`,
);
}

return parsed.data;
}

/**
* Deserialize an {@link CrossChainIndexingStatusSnapshot} object.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { prettifyError } from "zod/v4";

import type { OmnichainIndexingStatusSnapshot } from "../omnichain-indexing-status-snapshot";
import type { SerializedOmnichainIndexingStatusSnapshot } from "../serialize/omnichain-indexing-status-snapshot";
import { makeOmnichainIndexingStatusSnapshotSchema } from "../zod-schema/omnichain-indexing-status-snapshot";

/**
* Deserialize an {@link OmnichainIndexingStatusSnapshot} object.
*/
export function deserializeOmnichainIndexingStatusSnapshot(
maybeSnapshot: SerializedOmnichainIndexingStatusSnapshot,
valueLabel?: string,
): OmnichainIndexingStatusSnapshot {
const schema = makeOmnichainIndexingStatusSnapshotSchema(valueLabel);
const parsed = schema.safeParse(maybeSnapshot);

if (parsed.error) {
throw new Error(
`Cannot deserialize into OmnichainIndexingStatusSnapshot:\n${prettifyError(parsed.error)}\n`,
);
}

return parsed.data;
}
159 changes: 3 additions & 156 deletions packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,159 +1,6 @@
import type { BlockRef, ChainId, UnixTimestamp } from "../../shared/types";
import {
ChainIndexingStatusIds,
type ChainIndexingStatusSnapshot,
type ChainIndexingStatusSnapshotCompleted,
type ChainIndexingStatusSnapshotQueued,
} from "./chain-indexing-status-snapshot";
import {
type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill,
type CrossChainIndexingStatusSnapshot,
type OmnichainIndexingStatusId,
OmnichainIndexingStatusIds,
} from "./types";

/**
* Get {@link OmnichainIndexingStatusId} based on indexed chains' statuses.
*
* This function decides what is the `OmnichainIndexingStatusId` is,
* based on provided chain indexing statuses.
*
* @throws an error if unable to determine overall indexing status
*/
export function getOmnichainIndexingStatus(
chains: ChainIndexingStatusSnapshot[],
): OmnichainIndexingStatusId {
if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(chains)) {
return OmnichainIndexingStatusIds.Following;
}

if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(chains)) {
return OmnichainIndexingStatusIds.Backfill;
}

if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(chains)) {
return OmnichainIndexingStatusIds.Unstarted;
}

if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(chains)) {
return OmnichainIndexingStatusIds.Completed;
}

// if none of the chain statuses matched, throw an error
throw new Error(`Unable to determine omnichain indexing status for provided chains.`);
}

/**
* Get Omnichain Indexing Cursor
*
* The cursor tracks the "highest" latest indexed block timestamp across
* all indexed chains. If all chains are queued, the cursor tracks the moment
* just before the earliest start block timestamp across those chains.
*
* @throws an error if no chains are provided
*/
export function getOmnichainIndexingCursor(chains: ChainIndexingStatusSnapshot[]): UnixTimestamp {
if (chains.length === 0) {
throw new Error(`Unable to determine omnichain indexing cursor when no chains were provided.`);
}

// for omnichain indexing status snapshot 'unstarted', the cursor tracks
// the moment just before the indexing would start from.
if (getOmnichainIndexingStatus(chains) === OmnichainIndexingStatusIds.Unstarted) {
const earliestStartBlockTimestamps = chains.map((chain) => chain.config.startBlock.timestamp);

return Math.min(...earliestStartBlockTimestamps) - 1;
}

// otherwise, the cursor tracks the "highest" latest indexed block timestamp
// across all indexed chains
const latestIndexedBlockTimestamps = chains
.filter((chain) => chain.chainStatus !== ChainIndexingStatusIds.Queued)
.map((chain) => chain.latestIndexedBlock.timestamp);

// Invariant: there's at least one element in `latestIndexedBlockTimestamps` array
// This is theoretically impossible based on the 2 checks above,
// but the invariant is explicitly added here as a formality.
if (latestIndexedBlockTimestamps.length < 1) {
throw new Error("latestIndexedBlockTimestamps array must include at least one element");
}

return Math.max(...latestIndexedBlockTimestamps);
}

/**
* Check if Chain Indexing Status Snapshots fit the 'unstarted' overall status
* snapshot requirements:
* - All chains are guaranteed to have a status of "queued".
*
* Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to
* {@link ChainIndexingStatusSnapshotQueued}.
*/
export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(
chains: ChainIndexingStatusSnapshot[],
): chains is ChainIndexingStatusSnapshotQueued[] {
return chains.every((chain) => chain.chainStatus === ChainIndexingStatusIds.Queued);
}

/**
* Check if Chain Indexing Status Snapshots fit the 'backfill' overall status
* snapshot requirements:
* - At least one chain is guaranteed to be in the "backfill" status.
* - Each chain is guaranteed to have a status of either "queued",
* "backfill" or "completed".
*
* Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to
* {@link ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill}.
*/
export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(
chains: ChainIndexingStatusSnapshot[],
): chains is ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill[] {
const atLeastOneChainInTargetStatus = chains.some(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill,
);
const otherChainsHaveValidStatuses = chains.every(
(chain) =>
chain.chainStatus === ChainIndexingStatusIds.Queued ||
chain.chainStatus === ChainIndexingStatusIds.Backfill ||
chain.chainStatus === ChainIndexingStatusIds.Completed,
);

return atLeastOneChainInTargetStatus && otherChainsHaveValidStatuses;
}

/**
* Checks if Chain Indexing Status Snapshots fit the 'completed' overall status
* snapshot requirements:
* - All chains are guaranteed to have a status of "completed".
*
* Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to
* {@link ChainIndexingStatusSnapshotCompleted}.
*/
export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(
chains: ChainIndexingStatusSnapshot[],
): chains is ChainIndexingStatusSnapshotCompleted[] {
const allChainsHaveValidStatuses = chains.every(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Completed,
);

return allChainsHaveValidStatuses;
}

/**
* Checks Chain Indexing Status Snapshots fit the 'following' overall status
* snapshot requirements:
* - At least one chain is guaranteed to be in the "following" status.
* - Any other chain can have any status.
*/
export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(
chains: ChainIndexingStatusSnapshot[],
): chains is ChainIndexingStatusSnapshot[] {
const allChainsHaveValidStatuses = chains.some(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Following,
);

return allChainsHaveValidStatuses;
}
import type { BlockRef, ChainId } from "../../shared/types";
import { ChainIndexingStatusIds } from "./chain-indexing-status-snapshot";
import type { CrossChainIndexingStatusSnapshot } from "./types";

/**
* Gets the latest indexed {@link BlockRef} for the given {@link ChainId}.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
export * from "./chain-indexing-status-snapshot";
export * from "./deserialize";
export * from "./deserialize/chain-indexing-status-snapshot";
export * from "./deserialize/omnichain-indexing-status-snapshot";
export * from "./helpers";
export * from "./omnichain-indexing-status-snapshot";
export * from "./projection";
export * from "./serialize";
export * from "./serialize/chain-indexing-status-snapshot";
export * from "./serialize/omnichain-indexing-status-snapshot";
export * from "./serialized-types";
export * from "./types";
export * from "./validate/chain-indexing-status-snapshot";
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import {
type ChainIndexingStatusSnapshotFollowing,
type ChainIndexingStatusSnapshotQueued,
} from "./chain-indexing-status-snapshot";
import { getOmnichainIndexingCursor, getOmnichainIndexingStatus } from "./helpers";
import { OmnichainIndexingStatusIds } from "./types";
import {
getOmnichainIndexingCursor,
getOmnichainIndexingStatus,
OmnichainIndexingStatusIds,
} from "./omnichain-indexing-status-snapshot";

describe("ENSIndexer: Indexing Snapshot helpers", () => {
describe("getOmnichainIndexingStatus", () => {
Expand Down
Loading
Loading