From 66de1311f8870771d8da549f22022aa95a994074 Mon Sep 17 00:00:00 2001 From: Udit Veerwani <25996904+uditdc@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:48:11 +0530 Subject: [PATCH] Include a sync diff check (#339) --- packages/api/src/constants/syncKeys.ts | 55 +++++++++++++++++ .../routes/auxiliary/auxiliaryController.ts | 60 +++++++++++++++++++ .../src/routes/auxiliary/auxiliaryRoutes.ts | 16 +++-- packages/api/src/routes/index.ts | 4 +- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 packages/api/src/constants/syncKeys.ts diff --git a/packages/api/src/constants/syncKeys.ts b/packages/api/src/constants/syncKeys.ts new file mode 100644 index 00000000..deee70df --- /dev/null +++ b/packages/api/src/constants/syncKeys.ts @@ -0,0 +1,55 @@ +import { getNetwork } from '../viem/viemClient' + +interface SyncConfig { + key: string + syncThreshold: number +} + +// Block time constants (in seconds) +const MAINNET_BLOCK_TIME = 12 +const TESTNET_BLOCK_TIME = 2 + +const THIRTY_MINS_BLOCKS = + (30 * 60) / (getNetwork().testnet ? TESTNET_BLOCK_TIME : MAINNET_BLOCK_TIME) +const ONE_DAY_BLOCKS = + (24 * 60 * 60) / (getNetwork().testnet ? TESTNET_BLOCK_TIME : MAINNET_BLOCK_TIME) +const ONE_DAY_MS = 24 * 60 * 60 * 1000 + +export const syncConfigs: SyncConfig[] = [ + // Block-based syncs + { key: 'lastSyncedBlock_logs_pods', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_avs', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_avsOperators', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_operators', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_operatorShares', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_stakers', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_strategyWhitelist', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_podShares', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_distributionRootSubmitted', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_deposit', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_completedWithdrawals', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_queuedWithdrawals', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_logs_avsRewardsSubmission', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_pods', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_avs', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_operators', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_avsOperators', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_operatorShares', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_strategies', syncThreshold: ONE_DAY_BLOCKS }, + { key: 'lastSyncedBlock_stakers', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_deposit', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_avsStrategyRewards', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_completedWithdrawals', syncThreshold: THIRTY_MINS_BLOCKS }, + { key: 'lastSyncedBlock_queuedWithdrawals', syncThreshold: THIRTY_MINS_BLOCKS }, + + // Time-based syncs + { key: 'lastSyncedTimestamp_metrics_restaking', syncThreshold: ONE_DAY_MS }, + { key: 'lastSyncedTime_metrics_deposit', syncThreshold: ONE_DAY_MS }, + { key: 'lastSyncedTime_metrics_stakerRewards', syncThreshold: ONE_DAY_MS }, + { key: 'lastSyncedTimestamp_metrics_tvl', syncThreshold: ONE_DAY_MS }, + { key: 'lastSyncedTimestamp_metrics_eigenPods', syncThreshold: ONE_DAY_MS }, + { key: 'lastSyncedTime_metrics_withdrawal', syncThreshold: ONE_DAY_MS } +] + +// Helper function to get just the keys if needed +export const includedBlockSyncKeys = syncConfigs.map((config) => config.key) diff --git a/packages/api/src/routes/auxiliary/auxiliaryController.ts b/packages/api/src/routes/auxiliary/auxiliaryController.ts index f95b3887..c2830600 100644 --- a/packages/api/src/routes/auxiliary/auxiliaryController.ts +++ b/packages/api/src/routes/auxiliary/auxiliaryController.ts @@ -2,6 +2,9 @@ import { Request, Response } from 'express' import { handleAndReturnErrorResponse } from '../../schema/errors' import { fetchTokenPrices } from '../../utils/tokenPrices' import { getPrismaClient } from '../../utils/prismaClient' +import { getViemClient } from '../../viem/viemClient' +import { includedBlockSyncKeys } from '../../constants/syncKeys' +import { syncConfigs } from '../../constants/syncKeys' /** * Route to fetch cached prices @@ -41,6 +44,63 @@ export async function getLastSyncBlocks(req: Request, res: Response) { } } +/** + * Route to fetch and display the difference between the current block + * + * @param req + * @param res + */ +export async function getSyncDiff(req: Request, res: Response) { + try { + const prismaClient = getPrismaClient() + const syncKeys = await prismaClient.settings.findMany({ + where: { key: { in: includedBlockSyncKeys } } + }) + + let isOutOfSync = false + const currentBlock = await getViemClient().getBlockNumber() + const currentTime = Date.now() + + const timeKeys = syncKeys.filter((s) => s.key.startsWith('lastSyncedTime')) + const timeKeyDifferences = timeKeys.map((t) => { + const config = syncConfigs.find((c) => c.key === t.key) + const threshold = config?.syncThreshold || 0 + const difference = currentTime - Number(t.value) + return { + key: t.key, + difference, + outOfSync: difference > threshold + } + }) + + const blockKeys = syncKeys.filter((s) => s.key.startsWith('lastSyncedBlock')) + const blockKeyDifferences = blockKeys.map((b) => { + const config = syncConfigs.find((c) => c.key === b.key) + const threshold = config?.syncThreshold || 0 + const difference = Number(currentBlock) - Number(b.value) + return { + key: b.key, + difference, + outOfSync: difference > threshold + } + }) + + if ( + timeKeyDifferences.some((t) => t.outOfSync) || + blockKeyDifferences.some((b) => b.outOfSync) + ) { + isOutOfSync = true + } + + res.status(isOutOfSync ? 409 : 200).send({ + isOutOfSync, + syncKeys: [...timeKeyDifferences, ...blockKeyDifferences] + }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + /** * Route to fetch and display all strategies and tokens * diff --git a/packages/api/src/routes/auxiliary/auxiliaryRoutes.ts b/packages/api/src/routes/auxiliary/auxiliaryRoutes.ts index 05a82fcf..f45ff02f 100644 --- a/packages/api/src/routes/auxiliary/auxiliaryRoutes.ts +++ b/packages/api/src/routes/auxiliary/auxiliaryRoutes.ts @@ -1,10 +1,18 @@ import express from 'express' -import { getCachedPrices, getLastSyncBlocks, getStrategies } from './auxiliaryController' +import { + getCachedPrices, + getLastSyncBlocks, + getStrategies, + getSyncDiff +} from './auxiliaryController' + +import routeCache from 'route-cache' const router = express.Router() -router.get('/prices', getCachedPrices) -router.get('/sync-status', getLastSyncBlocks) -router.get('/strategies', getStrategies) +router.get('/prices', routeCache.cacheSeconds(120), getCachedPrices) +router.get('/strategies', routeCache.cacheSeconds(120), getStrategies) +router.get('/sync-status', routeCache.cacheSeconds(120), getLastSyncBlocks) +router.get('/sync-diff', routeCache.cacheSeconds(120), getSyncDiff) export default router diff --git a/packages/api/src/routes/index.ts b/packages/api/src/routes/index.ts index 98d90f1d..99ef51b1 100644 --- a/packages/api/src/routes/index.ts +++ b/packages/api/src/routes/index.ts @@ -26,6 +26,9 @@ apiRouter.get('/version', (_, res) => res.send({ version: process.env.API_VERSION || 'development' }) ) +// Auxiliary routes +apiRouter.use('/aux', auxiliaryRoutes) + // Remaining routes const routes = { '/avs': avsRoutes, @@ -35,7 +38,6 @@ const routes = { '/metrics': metricRoutes, '/withdrawals': withdrawalRoutes, '/deposits': depositRoutes, - '/aux': auxiliaryRoutes, '/rewards': rewardRoutes, '/events': eventRoutes, '/auth': authRoutes