diff --git a/src/api/endpoints/sync-progress.ts b/src/api/endpoints/sync-progress.ts index 4caf985..7678fdf 100644 --- a/src/api/endpoints/sync-progress.ts +++ b/src/api/endpoints/sync-progress.ts @@ -74,7 +74,9 @@ export const syncProgressHandler: RouteHandler = processedBlocks: processed, historicalBlocksFetchedPct: pct, isRealtime: (isRealtime.get(chain) ?? 0) === 1, - isComplete: (isComplete.get(chain) ?? 0) === 1, + // ponder_sync_is_complete never reaches 1 for chains with live block handlers + // (C1–C5 run indefinitely). Derive locally: synced means realtime + all blocks processed. + isComplete: (isRealtime.get(chain) ?? 0) === 1 && pct >= 100, }; } diff --git a/tests/api/sync-progress.test.ts b/tests/api/sync-progress.test.ts index b39e401..97e0727 100644 --- a/tests/api/sync-progress.test.ts +++ b/tests/api/sync-progress.test.ts @@ -95,14 +95,38 @@ describe("GET /api/sync-progress", () => { expect(body["gnosis"]!.historicalBlocksFetchedPct).toBe(14.1); }); - it("sets isRealtime and isComplete from metrics flags", async () => { + it("sets isRealtime from ponder_sync_is_realtime metric", async () => { mockFetch(SAMPLE_METRICS); const app = buildApp(); const res = await app.request("http://localhost/api/sync-progress"); const body = (await res.json()) as Record; expect(body["mainnet"]!.isRealtime).toBe(false); - expect(body["mainnet"]!.isComplete).toBe(false); expect(body["gnosis"]!.isRealtime).toBe(true); + }); + + it("isComplete requires both isRealtime=true and pct>=100 (ignores ponder_sync_is_complete)", async () => { + // gnosis is realtime but only 14.1% processed — must NOT be complete + mockFetch(SAMPLE_METRICS); + const app = buildApp(); + const res = await app.request("http://localhost/api/sync-progress"); + const body = (await res.json()) as Record; + expect(body["gnosis"]!.isRealtime).toBe(true); + expect(body["gnosis"]!.isComplete).toBe(false); + }); + + it("isComplete is true when isRealtime=true and all blocks processed", async () => { + // Regression: ponder_sync_is_complete=0 must not block isComplete when realtime+100% + const fullSyncMetrics = ` +ponder_historical_total_blocks{chain="gnosis"} 1000 +ponder_historical_completed_blocks{chain="gnosis"} 600 +ponder_historical_cached_blocks{chain="gnosis"} 400 +ponder_sync_is_realtime{chain="gnosis"} 1 +ponder_sync_is_complete{chain="gnosis"} 0 +`.trim(); + mockFetch(fullSyncMetrics); + const app = buildApp(); + const res = await app.request("http://localhost/api/sync-progress"); + const body = (await res.json()) as Record; expect(body["gnosis"]!.isComplete).toBe(true); });