diff --git a/src/api/atoms.ts b/src/api/atoms.ts index 79440f22..a6460a37 100644 --- a/src/api/atoms.ts +++ b/src/api/atoms.ts @@ -29,6 +29,7 @@ import type { GossipPeersSize, GossipPeersRowsUpdate, GossipPeersCellUpdate, + ServerTimeNanos, } from "./types"; import { rafAtom } from "../atomUtils"; @@ -61,6 +62,7 @@ export const optimisticallyConfirmedSlotAtom = atom< >(undefined); export const completedSlotAtom = atom(undefined); +export const serverTimeNanosAtom = atom(undefined); export const estimatedSlotAtom = atom(undefined); diff --git a/src/api/entities.ts b/src/api/entities.ts index 3f23ec08..ce4e160e 100644 --- a/src/api/entities.ts +++ b/src/api/entities.ts @@ -121,6 +121,8 @@ export const catchUpHistorySchema = z.object({ turbine: z.number().array(), }); +export const serverTimeNanosSchema = z.coerce.number(); + export const estimatedSlotSchema = z.number(); export const resetSlotSchema = z.number().nullable(); export const storageSlotSchema = z.number().nullable(); @@ -565,6 +567,10 @@ export const summarySchema = z.discriminatedUnion("key", [ key: z.literal("catch_up_history"), value: catchUpHistorySchema, }), + summaryTopicSchema.extend({ + key: z.literal("server_time_nanos"), + value: serverTimeNanosSchema, + }), ]); export const epochNewSchema = z.object({ diff --git a/src/api/types.ts b/src/api/types.ts index 3aacf659..2f9c0b79 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -57,6 +57,7 @@ import type { gossipStorageStatsSchema, gossipMessageStatsSchema, schedulerCountsSchema, + serverTimeNanosSchema, } from "./entities"; export type Client = z.infer; @@ -94,6 +95,8 @@ export type OptimisticallyConfirmedSlot = z.infer< export type CompletedSlot = z.infer; export type CatchUpHistory = z.infer; +export type ServerTimeNanos = z.infer; + // export type SlotCompleted = z.infer; export type EstimatedSlot = z.infer; diff --git a/src/api/useSetAtomWsData.ts b/src/api/useSetAtomWsData.ts index 5e4303a7..2ee403bb 100644 --- a/src/api/useSetAtomWsData.ts +++ b/src/api/useSetAtomWsData.ts @@ -27,6 +27,7 @@ import { gossipPeersSizeAtom, gossipPeersRowsUpdateAtom, gossipPeersCellUpdateAtom, + serverTimeNanosAtom, } from "./atoms"; import { blockEngineSchema, @@ -196,6 +197,7 @@ export function useSetAtomWsData() { const setBlockEngine = useSetAtom(blockEngineAtom); const setCompletedSlot = useSetAtom(completedSlotAtom); + const setServerTimeNanos = useSetAtom(serverTimeNanosAtom); const addSkippedClusterSlots = useSetAtom(addSkippedClusterSlotsAtom); const deleteSkippedClusterSlot = useSetAtom(deleteSkippedClusterSlotAtom); @@ -383,6 +385,10 @@ export function useSetAtomWsData() { addRepairSlots(value.repair); break; } + case "server_time_nanos": { + setServerTimeNanos(value); + break; + } case "root_slot": case "optimistically_confirmed_slot": case "estimated_slot": diff --git a/src/atoms.ts b/src/atoms.ts index 2fcc3201..8c4ab929 100644 --- a/src/atoms.ts +++ b/src/atoms.ts @@ -1,10 +1,11 @@ import { atom } from "jotai"; -import { slotsPerLeader } from "./consts"; +import { nsPerMs, slotsPerLeader } from "./consts"; import { atomWithImmer } from "jotai-immer"; import { bootProgressAtom, estimatedSlotDurationAtom, identityKeyAtom, + serverTimeNanosAtom, skippedSlotsAtom, startupProgressAtom, } from "./api/atoms"; @@ -695,3 +696,9 @@ export const [ }), ]; })(); + +export const serverTimeMsAtom = atom((get) => { + const serverTimeNanos = get(serverTimeNanosAtom); + if (serverTimeNanos == null) return undefined; + return Math.round(serverTimeNanos / nsPerMs); +}); diff --git a/src/features/Overview/ShredsProgression/atoms.ts b/src/features/Overview/ShredsProgression/atoms.ts index 162bfa5a..1f9a91d1 100644 --- a/src/features/Overview/ShredsProgression/atoms.ts +++ b/src/features/Overview/ShredsProgression/atoms.ts @@ -5,6 +5,7 @@ import { delayMs, xRangeMs } from "./const"; import { nsPerMs, slotsPerLeader } from "../../../consts"; import { getSlotGroupLeader } from "../../../utils"; import { startupFinalTurbineHeadAtom } from "../../StartupProgress/atoms"; +import { serverTimeMsAtom } from "../../../atoms"; type ShredEventTsDeltaMs = number | undefined; /** @@ -169,11 +170,10 @@ export function createLiveShredsAtoms() { set(_liveShredsAtom, (prev) => { const slotRange = get(_slotRangeAtom); + const now = get(serverTimeMsAtom) ?? Date.now(); if (!prev || !slotRange) return prev; - const now = Date.now(); - if (isStartup) { // During startup, we only show event dots, not spans. Delete slots without events in chart view for ( diff --git a/src/features/Overview/ShredsProgression/shredsProgressionPlugin.ts b/src/features/Overview/ShredsProgression/shredsProgressionPlugin.ts index b25c5d88..a0cb349e 100644 --- a/src/features/Overview/ShredsProgression/shredsProgressionPlugin.ts +++ b/src/features/Overview/ShredsProgression/shredsProgressionPlugin.ts @@ -18,7 +18,7 @@ import { shredReplayStartedColor, shredSkippedColor, } from "../../../colors"; -import { skippedClusterSlotsAtom } from "../../../atoms"; +import { serverTimeMsAtom, skippedClusterSlotsAtom } from "../../../atoms"; import { clamp } from "lodash"; import { ShredEvent } from "../../../api/entities"; import { getSlotGroupLabelId, getSlotLabelId } from "./utils"; @@ -55,10 +55,16 @@ export function shredsProgressionPlugin( const minCompletedSlot = store.get(atoms.minCompletedSlot); const skippedSlotsCluster = store.get(skippedClusterSlotsAtom); const rangeAfterStartup = store.get(atoms.rangeAfterStartup); + const serverTimeMs = store.get(serverTimeMsAtom); const maxX = u.scales[shredsXScaleKey].max; - if (!liveShreds || !slotRange || maxX == null) { + if ( + !liveShreds || + !slotRange || + maxX == null || + serverTimeMs == null + ) { return; } @@ -74,7 +80,7 @@ export function shredsProgressionPlugin( } // Offset to convert shred event delta to chart x value - const delayedNow = Date.now() - delayMs; + const delayedNow = serverTimeMs - delayMs; const tsXValueOffset = delayedNow - liveShreds.referenceTs;