diff --git a/src/api/routes/status.ts b/src/api/routes/status.ts index e91e6e97..f9103cb1 100644 --- a/src/api/routes/status.ts +++ b/src/api/routes/status.ts @@ -24,6 +24,8 @@ export const StatusRoutes: FastifyPluginCallback< }, async (request, reply) => { const result = await fastify.db.sqlTransaction(async sql => { + const block_height = await fastify.db.getChainTipBlockHeight(); + const smartContracts: Record = {}; const contractCounts = await fastify.db.getSmartContractCounts(); for (const row of contractCounts) { @@ -45,6 +47,9 @@ export const StatusRoutes: FastifyPluginCallback< return { server_version: `token-metadata-api ${SERVER_VERSION.tag} (${SERVER_VERSION.branch}:${SERVER_VERSION.commit})`, status: 'ready', + chain_tip: { + block_height, + }, tokens: tokenCounts.length ? tokens : undefined, token_contracts: contractCounts.length ? smartContracts : undefined, job_queue: jobCounts.length ? queue : undefined, diff --git a/src/api/schemas.ts b/src/api/schemas.ts index 31c1a5cf..f83e4569 100644 --- a/src/api/schemas.ts +++ b/src/api/schemas.ts @@ -310,6 +310,9 @@ export const ApiStatusResponse = Type.Object( { server_version: Type.String({ examples: ['token-metadata-api v0.0.1 (master:a1b2c3)'] }), status: Type.String({ examples: ['ready'] }), + chain_tip: Type.Object({ + block_height: Type.Integer({ examples: [163541] }), + }), tokens: Type.Optional( Type.Object( { diff --git a/src/pg/chainhook/chainhook-pg-store.ts b/src/pg/chainhook/chainhook-pg-store.ts index 418ff65f..9b7a1de1 100644 --- a/src/pg/chainhook/chainhook-pg-store.ts +++ b/src/pg/chainhook/chainhook-pg-store.ts @@ -113,6 +113,10 @@ export class ChainhookPgStore extends BasePgStoreModule { ); } + async updateChainTipBlockHeight(blockHeight: number): Promise { + await this.sql`UPDATE chain_tip SET block_height = GREATEST(${blockHeight}, block_height)`; + } + private async getLastIngestedBlockHeight(): Promise { const result = await this.sql<{ block_height: number }[]>`SELECT block_height FROM chain_tip`; return result[0].block_height; @@ -383,10 +387,6 @@ export class ChainhookPgStore extends BasePgStoreModule { throw new ContractNotFoundError(); } - private async updateChainTipBlockHeight(blockHeight: number): Promise { - await this.sql`UPDATE chain_tip SET block_height = GREATEST(${blockHeight}, block_height)`; - } - private async enqueueDynamicTokensDueForRefresh(): Promise { const interval = ENV.METADATA_DYNAMIC_TOKEN_REFRESH_INTERVAL.toString(); await this.sql` diff --git a/src/token-processor/token-processor-metrics.ts b/src/token-processor/token-processor-metrics.ts index de70e1e8..d85d0dc6 100644 --- a/src/token-processor/token-processor-metrics.ts +++ b/src/token-processor/token-processor-metrics.ts @@ -2,6 +2,7 @@ import * as prom from 'prom-client'; import { PgStore } from '../pg/pg-store'; export class TokenProcessorMetrics { + readonly token_metadata_block_height: prom.Gauge; /** Job count divided by status */ readonly token_metadata_job_count: prom.Gauge; /** Smart contract count divided by SIP number */ @@ -14,6 +15,14 @@ export class TokenProcessorMetrics { } private constructor(db: PgStore) { + this.token_metadata_block_height = new prom.Gauge({ + name: `token_metadata_block_height`, + help: 'The most recent Bitcoin block height ingested by the API', + async collect() { + const height = await db.getChainTipBlockHeight(); + this.set(height); + }, + }); this.token_metadata_job_count = new prom.Gauge({ name: `token_metadata_job_count`, help: 'Job count divided by status', diff --git a/tests/api/status.test.ts b/tests/api/status.test.ts index 434f41d1..9f2b24de 100644 --- a/tests/api/status.test.ts +++ b/tests/api/status.test.ts @@ -30,6 +30,9 @@ describe('Status routes', () => { expect(json).toStrictEqual({ server_version: 'token-metadata-api v0.0.1 (test:123456)', status: 'ready', + chain_tip: { + block_height: 1, + }, }); const noVersionResponse = await fastify.inject({ method: 'GET', url: '/metadata/' }); expect(response.statusCode).toEqual(noVersionResponse.statusCode); @@ -43,12 +46,16 @@ describe('Status routes', () => { DbSipNumber.sip009, 1n ); + await db.chainhook.updateChainTipBlockHeight(100); const response = await fastify.inject({ method: 'GET', url: '/metadata/v1/' }); const json = response.json(); expect(json).toStrictEqual({ server_version: 'token-metadata-api v0.0.1 (test:123456)', status: 'ready', + chain_tip: { + block_height: 100, + }, job_queue: { pending: 2, },