Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/api/endpoints/sync-progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export const syncProgressHandler: RouteHandler<typeof syncProgressRoute> =
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,
};
}

Expand Down
28 changes: 26 additions & 2 deletions tests/api/sync-progress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ChainProgress>;
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<string, ChainProgress>;
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<string, ChainProgress>;
expect(body["gnosis"]!.isComplete).toBe(true);
});

Expand Down
Loading