diff --git a/packages/indexer-common/src/__tests__/grafting.test.ts b/packages/indexer-common/src/__tests__/grafting.test.ts index 052f5c048..a5c28669f 100644 --- a/packages/indexer-common/src/__tests__/grafting.test.ts +++ b/packages/indexer-common/src/__tests__/grafting.test.ts @@ -1,4 +1,4 @@ -import { resolveGrafting, GraftableSubgraph } from '../subgraphs' +import { resolveGrafting, GraftableSubgraph } from '../grafting' import { SubgraphDeploymentID } from '@graphprotocol/common-ts' // Create a mock for the fetchSubgraphManifest function diff --git a/packages/indexer-common/src/grafting.ts b/packages/indexer-common/src/grafting.ts new file mode 100644 index 000000000..ecdc0a948 --- /dev/null +++ b/packages/indexer-common/src/grafting.ts @@ -0,0 +1,90 @@ +import { SubgraphDeploymentID } from '@graphprotocol/common-ts' +import { GraphNodeInterface } from './graph-node' +import { BlockPointer, SubgraphManifest } from './types' +import { indexerError, IndexerErrorCode } from './errors' + +export interface GraftBase { + block: number + base: SubgraphDeploymentID +} + +export interface GraftableSubgraph { + deployment: SubgraphDeploymentID + graft: GraftBase | null // Root subgraph does not have a graft base +} + +type SubgraphManifestResolver = ( + subgraphID: SubgraphDeploymentID, +) => Promise + +interface IndexingStatus { + latestBlock: BlockPointer | null + health: string + synced: boolean +} + +interface SubgraphGraftStatus extends GraftableSubgraph { + indexingStatus: IndexingStatus | null +} + +// Resolves all graft dependencies for a given subgraph +export async function resolveGrafting( + subgraphManifestResolver: SubgraphManifestResolver, + targetDeployment: SubgraphDeploymentID, + maxIterations: number = 100, +): Promise { + const graftBases: GraftableSubgraph[] = [] + let iterationCount = 0 + let deployment = targetDeployment + while (iterationCount < maxIterations) { + const manifest = await subgraphManifestResolver(deployment) + let graft: GraftBase | null = null + if (manifest.features?.includes('grafting') && manifest.graft) { + // Found a graft base + const base = new SubgraphDeploymentID(manifest.graft.base) + graft = { block: manifest.graft.block, base } + graftBases.push({ deployment, graft }) + deployment = base + } else { + // Reached root subgraph, stop iterating + iterationCount = maxIterations + graftBases.push({ deployment, graft }) + } + iterationCount++ + } + // Check if we have found the graft root + if (graftBases.length > 0 && graftBases[graftBases.length - 1].graft !== null) { + throw new Error( + `Failed to find a graft root for target subgraph deployment (${targetDeployment.ipfsHash}) after ${iterationCount} iterations.`, + ) + } + return graftBases +} + +export async function getIndexingStatusOfGraftableSubgraph( + subgraph: GraftableSubgraph, + graphNode: GraphNodeInterface, +): Promise { + let response + try { + response = await graphNode.indexingStatus([subgraph.deployment]) + } catch (error) { + const message = `Failed to fetch indexing status when resolving subgraph grafts` + // TODO: log this error + throw indexerError(IndexerErrorCode.IE075, { message, error }) + } + let indexingStatus: IndexingStatus | null = null + if (response && response.length) { + const subgraphIndexingStatus = response[0] + let latestBlock: BlockPointer | null = null + if (subgraphIndexingStatus.chains && subgraphIndexingStatus.chains.length) { + latestBlock = subgraphIndexingStatus.chains[0].latestBlock + } + indexingStatus = { + health: subgraphIndexingStatus.health, + synced: subgraphIndexingStatus.synced, + latestBlock, + } + } + return { ...subgraph, indexingStatus } +} diff --git a/packages/indexer-common/src/subgraphs.ts b/packages/indexer-common/src/subgraphs.ts index 1998ad209..df842ee18 100644 --- a/packages/indexer-common/src/subgraphs.ts +++ b/packages/indexer-common/src/subgraphs.ts @@ -1,7 +1,7 @@ import { base58 } from 'ethers/lib/utils' import { BigNumber, utils } from 'ethers' import { Logger, SubgraphDeploymentID } from '@graphprotocol/common-ts' -import { SubgraphDeployment, SubgraphManifestSchema, BlockPointer } from './types' +import { SubgraphDeployment, SubgraphManifest, SubgraphManifestSchema } from './types' import { INDEXING_RULE_GLOBAL, IndexingDecisionBasis, @@ -13,8 +13,6 @@ import { QueryResult } from './network-subgraph' import { mergeSelectionSets, sleep } from './utils' import * as yaml from 'yaml' import { indexerError, IndexerErrorCode } from './errors' -import { GraphNodeInterface } from './graph-node' -import { z } from 'zod' export enum SubgraphIdentifierType { DEPLOYMENT = 'deployment', @@ -528,59 +526,6 @@ export class SubgraphFreshnessChecker { } } -export interface GraftBase { - block: number - base: SubgraphDeploymentID -} - -export interface GraftableSubgraph { - deployment: SubgraphDeploymentID - graft: GraftBase | null // Root subgraph does not have a graft base -} - -type SubgraphManifestResolver = ( - subgraphID: SubgraphDeploymentID, -) => Promise - -// Resolves all graft dependencies for a given subgraph -export async function resolveGrafting( - subgraphManifestResolver: SubgraphManifestResolver, - targetDeployment: SubgraphDeploymentID, - maxIterations: number = 100, -): Promise { - const graftBases: GraftableSubgraph[] = [] - - let iterationCount = 0 - let deployment = targetDeployment - while (iterationCount < maxIterations) { - const manifest = await subgraphManifestResolver(deployment) - let graft: GraftBase | null = null - if (manifest.features?.includes('grafting') && manifest.graft) { - // Found a graft base - const base = new SubgraphDeploymentID(manifest.graft.base) - graft = { block: manifest.graft.block, base } - graftBases.push({ deployment, graft }) - deployment = base - } else { - // Reached root subgraph, stop iterating - iterationCount = maxIterations - graftBases.push({ deployment, graft }) - } - iterationCount++ - } - - // Check if we have found the graft root - if (graftBases.length > 0 && graftBases[graftBases.length - 1].graft !== null) { - throw new Error( - `Failed to find a graft root for target subgraph deployment (${targetDeployment.ipfsHash}) after ${iterationCount} iterations.`, - ) - } - - return graftBases -} - -type SubgraphManifest = z.infer - // Simple fetch for subgraph manifest export async function fetchSubgraphManifest( ipfsEndpoint: URL, @@ -641,41 +586,3 @@ export async function fetchSubgraphManifest( throw indexerError(IndexerErrorCode.IE075, message) } } - -interface IndexingStatus { - latestBlock: BlockPointer | null - health: string - synced: boolean -} - -interface SubgraphGraftStatus extends GraftableSubgraph { - indexingStatus: IndexingStatus | null -} - -export async function getIndexingStatusOfGraftableSubgraph( - subgraph: GraftableSubgraph, - graphNode: GraphNodeInterface, -): Promise { - let response - try { - response = await graphNode.indexingStatus([subgraph.deployment]) - } catch (error) { - const message = `Failed to fetch indexing status when resolving subgraph grafts` - // TODO: log this error - throw indexerError(IndexerErrorCode.IE075, { message, error }) - } - let indexingStatus: IndexingStatus | null = null - if (response && response.length) { - const subgraphIndexingStatus = response[0] - let latestBlock: BlockPointer | null = null - if (subgraphIndexingStatus.chains && subgraphIndexingStatus.chains.length) { - latestBlock = subgraphIndexingStatus.chains[0].latestBlock - } - indexingStatus = { - health: subgraphIndexingStatus.health, - synced: subgraphIndexingStatus.synced, - latestBlock, - } - } - return { ...subgraph, indexingStatus } -} diff --git a/packages/indexer-common/src/types.ts b/packages/indexer-common/src/types.ts index f3e97fc9e..c0885a60d 100644 --- a/packages/indexer-common/src/types.ts +++ b/packages/indexer-common/src/types.ts @@ -118,3 +118,5 @@ export const SubgraphManifestSchema = z.object({ features: z.array(z.string()).optional(), graft: Graft.optional(), }) + +export type SubgraphManifest = z.infer