diff --git a/packages/uni-info-watcher/docs/analysis/schema/health-schema.graphql b/packages/uni-info-watcher/docs/analysis/schema/health-schema.graphql new file mode 100644 index 00000000..6afce8b8 --- /dev/null +++ b/packages/uni-info-watcher/docs/analysis/schema/health-schema.graphql @@ -0,0 +1,66 @@ +scalar BigInt + +type Block { + hash: Bytes! + number: BigInt! +} + +scalar Bytes + +interface ChainIndexingStatus { + network: String! + chainHeadBlock: Block + earliestBlock: Block + latestBlock: Block + lastHealthyBlock: Block +} + +type EthereumIndexingStatus implements ChainIndexingStatus { + network: String! + chainHeadBlock: Block + earliestBlock: Block + latestBlock: Block + lastHealthyBlock: Block +} + +enum Health { + """Subgraph syncing normally""" + healthy + + """Subgraph syncing but with errors""" + unhealthy + + """Subgraph halted due to errors""" + failed +} + +type Query { + indexingStatusForCurrentVersion(subgraphName: String!): SubgraphIndexingStatus + indexingStatusForPendingVersion(subgraphName: String!): SubgraphIndexingStatus + indexingStatusesForSubgraphName(subgraphName: String!): [SubgraphIndexingStatus!]! + indexingStatuses(subgraphs: [String!]): [SubgraphIndexingStatus!]! + proofOfIndexing(subgraph: String!, blockNumber: Int!, blockHash: Bytes!, indexer: Bytes): Bytes +} + +type SubgraphError { + message: String! + block: Block + handler: String + deterministic: Boolean! +} + +type SubgraphIndexingStatus { + subgraph: String! + synced: Boolean! + health: Health! + + """If the subgraph has failed, this is the error caused it""" + fatalError: SubgraphError + + """Sorted from first to last, limited to first 1000""" + nonFatalErrors: [SubgraphError!]! + chains: [ChainIndexingStatus!]! + entityCount: BigInt! + node: String +} + diff --git a/packages/uni-info-watcher/package.json b/packages/uni-info-watcher/package.json index 5fcca024..5cf6eb9b 100644 --- a/packages/uni-info-watcher/package.json +++ b/packages/uni-info-watcher/package.json @@ -22,6 +22,7 @@ "lint": "eslint .", "build": "tsc", "generate:schema": "get-graphql-schema https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-alt > docs/analysis/schema/full-schema.graphql", + "generate:health-schema": "get-graphql-schema https://api.thegraph.com/index-node/graphql > docs/analysis/schema/health-schema.graphql", "lint:schema": "graphql-schema-linter", "smoke-test": "mocha src/smoke.test.ts", "test:gpev": "mocha src/get-prev-entity.test.ts", diff --git a/packages/uni-info-watcher/src/indexer.ts b/packages/uni-info-watcher/src/indexer.ts index e81b441f..96282b73 100644 --- a/packages/uni-info-watcher/src/indexer.ts +++ b/packages/uni-info-watcher/src/indexer.ts @@ -33,6 +33,8 @@ import { PositionSnapshot } from './entity/PositionSnapshot'; import { SyncStatus } from './entity/SyncStatus'; import { BlockProgress } from './entity/BlockProgress'; +const SYNC_DELTA = 5; + const log = debug('vulcanize:indexer'); export interface ValueResult { @@ -175,6 +177,29 @@ export class Indexer implements IndexerInterface { })); } + async getIndexingStatus (): Promise { + const syncStatus = await this.getSyncStatus(); + assert(syncStatus); + const synced = (syncStatus.chainHeadBlockNumber - syncStatus.latestIndexedBlockNumber) <= SYNC_DELTA; + + return { + synced, + health: 'healthy', + chains: [ + { + chainHeadBlock: { + number: syncStatus.chainHeadBlockNumber, + hash: syncStatus.chainHeadBlockHash + }, + latestBlock: { + number: syncStatus.latestIndexedBlockNumber, + hash: syncStatus.latestIndexedBlockHash + } + } + ] + }; + } + async markBlocksAsPruned (blocks: BlockProgress[]): Promise { return this._baseIndexer.markBlocksAsPruned(blocks); } diff --git a/packages/uni-info-watcher/src/resolvers.ts b/packages/uni-info-watcher/src/resolvers.ts index 16edd1a5..9a0b4981 100644 --- a/packages/uni-info-watcher/src/resolvers.ts +++ b/packages/uni-info-watcher/src/resolvers.ts @@ -35,6 +35,12 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch return { BigInt: new BigInt('bigInt'), + ChainIndexingStatus: { + __resolveType: () => { + return 'EthereumIndexingStatus'; + } + }, + Subscription: { onBlockProgressEvent: { subscribe: () => eventWatcher.getBlockProgressEventIterator() @@ -148,6 +154,12 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch log('blocks', first, orderBy, orderDirection, where); return indexer.getBlocks(where, { limit: first, orderBy, orderDirection }); + }, + + indexingStatusForCurrentVersion: async (_: any, { subgraphName }: { subgraphName: string }) => { + log('health', subgraphName); + + return indexer.getIndexingStatus(); } } }; diff --git a/packages/uni-info-watcher/src/schema.ts b/packages/uni-info-watcher/src/schema.ts index db561212..ea21654b 100644 --- a/packages/uni-info-watcher/src/schema.ts +++ b/packages/uni-info-watcher/src/schema.ts @@ -291,6 +291,33 @@ enum Block_orderBy { timestamp } +interface ChainIndexingStatus { + chainHeadBlock: Block + latestBlock: Block +} + +type EthereumIndexingStatus implements ChainIndexingStatus { + chainHeadBlock: Block + latestBlock: Block +} + +enum Health { + """Syncing normally""" + healthy + + """Syncing but with errors""" + unhealthy + + """Halted due to errors""" + failed +} + +type SubgraphIndexingStatus { + synced: Boolean! + health: Health! + chains: [ChainIndexingStatus!]! +} + type Query { bundle( id: ID! @@ -443,6 +470,10 @@ type Query { orderDirection: OrderDirection where: Block_filter ): [Block!]! + + indexingStatusForCurrentVersion( + subgraphName: String + ): SubgraphIndexingStatus } #