Skip to content

Commit 4498397

Browse files
committed
Add processor-status GraphQL resolver
Exposes per-processor block heights for mainnet, base, sonic, and hyperliquid stateSchemas so external monitors can detect a stalled processor by comparing each height to the live chain head.
1 parent 6ef5ca5 commit 4498397

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

src/server-extension/resolvers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ import 'reflect-metadata'
22
import 'tsconfig-paths/register'
33

44
export { TestRenderResolver } from './test-render'
5+
export { ProcessorStatusResolver } from './processor-status'
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Field, Int, ObjectType, Query, Resolver } from 'type-graphql'
2+
3+
import { getSquidDbPool } from '../../test-render/db'
4+
5+
// Schemas match the `stateSchema` values in src/main.ts, src/main-base.ts,
6+
// src/main-sonic.ts, src/main-hyperliquid.ts. Chain IDs match the `chainId`
7+
// passed to `run()` in those files.
8+
const PROCESSORS: Array<{ schema: string; chainId: number }> = [
9+
{ schema: 'mainnet', chainId: 1 },
10+
{ schema: 'base', chainId: 8453 },
11+
{ schema: 'sonic', chainId: 146 },
12+
{ schema: 'hyperliquid', chainId: 999 },
13+
]
14+
15+
@ObjectType()
16+
class ProcessorStatus {
17+
@Field()
18+
schema!: string
19+
20+
@Field(() => Int)
21+
chainId!: number
22+
23+
// Latest indexed head (max of finalized status.height and hot_block.height).
24+
// -1 indicates no blocks have been processed yet.
25+
@Field(() => Int)
26+
height!: number
27+
28+
// Finalized head from `${schema}.status.height`.
29+
@Field(() => Int)
30+
finalizedHeight!: number
31+
32+
// True if both the status table and any expected hot_block rows are present.
33+
@Field()
34+
ready!: boolean
35+
36+
@Field({ nullable: true })
37+
error?: string
38+
}
39+
40+
@Resolver()
41+
export class ProcessorStatusResolver {
42+
@Query(() => [ProcessorStatus])
43+
async processorStatus(): Promise<ProcessorStatus[]> {
44+
const pool = getSquidDbPool()
45+
return Promise.all(
46+
PROCESSORS.map(async ({ schema, chainId }) => {
47+
try {
48+
const statusResult = await pool.query<{ height: number }>(
49+
`SELECT height FROM "${schema}"."status" WHERE id = 0`,
50+
)
51+
if (statusResult.rowCount === 0) {
52+
return {
53+
schema,
54+
chainId,
55+
height: -1,
56+
finalizedHeight: -1,
57+
ready: false,
58+
error: 'status row missing',
59+
}
60+
}
61+
const finalizedHeight = Number(statusResult.rows[0].height)
62+
const hotResult = await pool.query<{ height: number | null }>(
63+
`SELECT MAX(height) AS height FROM "${schema}"."hot_block"`,
64+
)
65+
const hotHeight =
66+
hotResult.rows[0]?.height != null
67+
? Number(hotResult.rows[0].height)
68+
: -1
69+
const height = Math.max(finalizedHeight, hotHeight)
70+
return {
71+
schema,
72+
chainId,
73+
height,
74+
finalizedHeight,
75+
ready: true,
76+
}
77+
} catch (err) {
78+
return {
79+
schema,
80+
chainId,
81+
height: -1,
82+
finalizedHeight: -1,
83+
ready: false,
84+
error: err instanceof Error ? err.message : 'unknown error',
85+
}
86+
}
87+
}),
88+
)
89+
}
90+
}

0 commit comments

Comments
 (0)