diff --git a/README.md b/README.md index 30951e9..8781bdb 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ pnpm db:apply:cron-runs:testnet | `testnet` | [Validators API Testnet](https://validators-api-test.workers.dev) | Manual `wrangler deploy --env testnet` | | `testnet-preview` | [Validators API Testnet Preview](https://validators-api-test.workers.dev) | Manual deployment | -Each environment has its own D1 database, KV cache, and R2 blob. Sync runs hourly via Cloudflare cron triggers (see `server/tasks/sync/`). +Each environment has its own D1 database, KV cache, and R2 blob. Sync runs every 12 hours via Cloudflare cron triggers (see `server/tasks/sync/`). ### Deployment Migration diff --git a/app/app.vue b/app/app.vue index ee7fcbc..0abff0a 100644 --- a/app/app.vue +++ b/app/app.vue @@ -124,7 +124,7 @@ const currentEnvItem = { branch: gitBranch, network: nimiqNetwork, link: environ
- Note: Data synchronization is handled automatically by scheduled tasks that run hourly. Please wait for the next sync cycle or contact an administrator if the issue persists. + Note: Data synchronization is handled automatically by scheduled tasks that run every 12 hours. A score lag of up to 1 epoch can be expected between sync cycles.
diff --git a/nuxt.config.ts b/nuxt.config.ts index 5febaf2..7f99f74 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -161,5 +161,5 @@ export default defineNuxtConfig({ }, }, - compatibilityDate: '2025-03-21', + compatibilityDate: '2026-02-26', }) diff --git a/package.json b/package.json index 43bca48..75f5853 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dev:testnet:prod": "nr dev:packages && nuxt dev --remote=production --dotenv .env.testnet", "dev:local": "nr dev:packages && nuxt dev --dotenv .env.local", "dev:packages": "nr -C packages -r dev", - "build": "pnpm validators:bundle:generate && nr -r build && nuxt build", + "build": "pnpm validators:bundle:generate && nr -r build && NODE_OPTIONS=--max-old-space-size=4096 nuxt build", "generate": "nuxt generate", "preview": "npx wrangler --cwd .output dev", "postinstall": "nuxt prepare", diff --git a/server/api/[version]/status.get.ts b/server/api/[version]/status.get.ts index c60e49c..c55c154 100644 --- a/server/api/[version]/status.get.ts +++ b/server/api/[version]/status.get.ts @@ -43,14 +43,24 @@ export default defineCachedEventHandler(async () => { if (!headBlockOk) throw createError(errorHeadBlockNumber || 'No head block number') + const allowedScoreLagEpochs = 1 + const latestScoreEpoch = await getLatestScoreEpoch() + const scoreLagEpochs = getScoreLagEpochs({ + toEpoch: range.toEpoch, + latestScoreEpoch, + }) + const missingEpochs = await findMissingEpochs(range) - const missingScore = await isMissingScore(range) + const missingScore = await isScoreMissingWithLag(range, allowedScoreLagEpochs, latestScoreEpoch) return { range, validators: validatorsEpoch, missingEpochs, missingScore, + latestScoreEpoch, + scoreLagEpochs, + allowedScoreLagEpochs, blockchain: { network, headBlockNumber }, } }) diff --git a/server/utils/score-freshness.test.ts b/server/utils/score-freshness.test.ts new file mode 100644 index 0000000..ee5e71b --- /dev/null +++ b/server/utils/score-freshness.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest' +import { getScoreLagEpochs, isScoreLagMissing } from './score-freshness' + +describe('score freshness', () => { + it('considers score synced when latest score epoch equals current toEpoch', () => { + expect(getScoreLagEpochs({ toEpoch: 100, latestScoreEpoch: 100 })).toBe(0) + expect(isScoreLagMissing({ toEpoch: 100, latestScoreEpoch: 100, allowedLagEpochs: 1 })).toBe(false) + }) + + it('considers score synced when lag is exactly one epoch', () => { + expect(getScoreLagEpochs({ toEpoch: 100, latestScoreEpoch: 99 })).toBe(1) + expect(isScoreLagMissing({ toEpoch: 100, latestScoreEpoch: 99, allowedLagEpochs: 1 })).toBe(false) + }) + + it('considers score missing when lag is two epochs', () => { + expect(getScoreLagEpochs({ toEpoch: 100, latestScoreEpoch: 98 })).toBe(2) + expect(isScoreLagMissing({ toEpoch: 100, latestScoreEpoch: 98, allowedLagEpochs: 1 })).toBe(true) + }) + + it('considers score missing when no score exists', () => { + expect(getScoreLagEpochs({ toEpoch: 100, latestScoreEpoch: null })).toBeNull() + expect(isScoreLagMissing({ toEpoch: 100, latestScoreEpoch: null, allowedLagEpochs: 1 })).toBe(true) + }) +}) diff --git a/server/utils/score-freshness.ts b/server/utils/score-freshness.ts new file mode 100644 index 0000000..c7c5cdb --- /dev/null +++ b/server/utils/score-freshness.ts @@ -0,0 +1,19 @@ +export interface ScoreFreshnessParams { + toEpoch: number + latestScoreEpoch: number | null + allowedLagEpochs?: number +} + +export function getScoreLagEpochs({ toEpoch, latestScoreEpoch }: Pick