@@ -3,6 +3,12 @@ import { requireAuth, requireAdmin } from "../auth/middleware.js";
33import { z } from "zod" ;
44import { db , schema } from "../db/index.js" ;
55import { sql , eq } from "drizzle-orm" ;
6+ import { provider } from "../starknet/client.js" ;
7+ import {
8+ incStarknetMetric ,
9+ labeledStarknetMetric ,
10+ setStarknetGauge ,
11+ } from "../starknet/client-metrics.js" ;
612
713export const backfillEventsRouter = Router ( ) ;
814
@@ -36,6 +42,7 @@ export const RESULTS_PREVIEW_SIZE = 10;
3642 * matches what is actually durable in `agreement_events`.
3743 */
3844export const BACKFILL_CHECKPOINT_BATCH_SIZE = 100 ;
45+ export const BACKFILL_LAG_METRIC = "backfill_lag_blocks" ;
3946
4047// ---------------------------------------------------------------------------
4148// Resume token freshness bounds (Issue #263)
@@ -61,6 +68,27 @@ export const CLOCK_SKEW_TOLERANCE_MS = 60 * 1000; // 60 seconds
6168
6269export type BackfillJobName = "employee-events" | "milestone-events" ;
6370
71+ /** Refresh lag observability without allowing an RPC outage to stop a backfill. */
72+ async function updateBackfillLag (
73+ jobName : BackfillJobName ,
74+ lastBlockNumber : number | null | undefined ,
75+ contractAddress : string | null | undefined ,
76+ ) : Promise < void > {
77+ try {
78+ const chainHead = await provider . getBlockNumber ( ) ;
79+ const lag = Math . max ( 0 , chainHead - ( lastBlockNumber ?? 0 ) ) ;
80+ setStarknetGauge (
81+ labeledStarknetMetric ( BACKFILL_LAG_METRIC , {
82+ job : jobName ,
83+ contract : contractAddress ?? "unknown" ,
84+ } ) ,
85+ lag ,
86+ ) ;
87+ } catch {
88+ incStarknetMetric ( "backfill_lag_rpc_errors_total" ) ;
89+ }
90+ }
91+
6492export const EMPLOYEE_BACKFILL_JOB : BackfillJobName = "employee-events" ;
6593export const MILESTONE_BACKFILL_JOB : BackfillJobName = "milestone-events" ;
6694export const BACKFILL_JOB_NAMES : readonly BackfillJobName [ ] = [
@@ -261,6 +289,8 @@ export async function upsertBackfillProgress(
261289 fields : {
262290 status ?: string ;
263291 lastCursor ?: Date | null ;
292+ lastBlockNumber ?: number | null ;
293+ lastContractAddress ?: string | null ;
264294 totalScanned ?: number ;
265295 totalCreated ?: number ;
266296 lastError ?: string | null ;
@@ -309,6 +339,8 @@ export async function getBackfillProgress(
309339) : Promise < {
310340 status : string ;
311341 lastCursor : Date | null ;
342+ lastBlockNumber : number | null ;
343+ lastContractAddress : string | null ;
312344 totalScanned : number ;
313345 totalCreated : number ;
314346 lastError : string | null ;
@@ -324,6 +356,8 @@ export async function getBackfillProgress(
324356 return {
325357 status : row . status ,
326358 lastCursor : row . lastCursor ,
359+ lastBlockNumber : row . lastBlockNumber ,
360+ lastContractAddress : row . lastContractAddress ,
327361 totalScanned : row . totalScanned ,
328362 totalCreated : row . totalCreated ,
329363 lastError : row . lastError ,
@@ -384,6 +418,7 @@ export async function performBackfill(
384418 persistedTotalScanned = progress . totalScanned ;
385419 persistedTotalCreated = progress . totalCreated ;
386420 }
421+ await updateBackfillLag ( jobName , progress ?. lastBlockNumber , progress ?. lastContractAddress ) ;
387422 }
388423
389424 const conditions = sql `1=1` ;
@@ -483,12 +518,20 @@ export async function performBackfill(
483518 totalScanned : batchTotalScanned + batch . length ,
484519 totalCreated : batchCreatedCount + batchCreated ,
485520 lastCursor : batchCursor ,
521+ lastBlockNumber : Number ( lastRow . block_number ) ,
522+ lastContractAddress : String ( lastRow . contract_address ) ,
486523 } ) ;
487524
488525 batchCreatedCount += batchCreated ;
489526 batchTotalScanned += batch . length ;
490527 } ) ;
491528
529+ await updateBackfillLag (
530+ jobName ,
531+ Number ( batch [ batch . length - 1 ] . block_number ) ,
532+ String ( batch [ batch . length - 1 ] . contract_address ) ,
533+ ) ;
534+
492535 const insertedIds = new Set ( insertedRows . map ( ( r ) => String ( r . id ) ) ) ;
493536
494537 for ( const row of batch ) {
0 commit comments