diff --git a/packages/guardian-prover-health-check-ui/src/components/Overview/Filter.svelte b/packages/guardian-prover-health-check-ui/src/components/Overview/Filter.svelte index c9bb0e1475a..b1484661713 100644 --- a/packages/guardian-prover-health-check-ui/src/components/Overview/Filter.svelte +++ b/packages/guardian-prover-health-check-ui/src/components/Overview/Filter.svelte @@ -8,6 +8,8 @@ import DesktopOrLarger from '$components/DesktopOrLarger/DesktopOrLarger.svelte'; import { classNames } from '$lib/util/classNames'; + import { loading as loadingStore } from '$stores'; + export let refreshData: () => void; export let filteredGuardianProvers: Guardian[] = []; export let loading: boolean = false; @@ -31,6 +33,8 @@ $: selectedStatus = null; + $: spin = loading || $loadingStore; + $: if (selectedStatus !== null) { filtered = true; filterByStatus(selectedStatus); @@ -47,7 +51,7 @@
diff --git a/packages/guardian-prover-health-check-ui/src/components/Overview/StatusFilterDropdown.svelte b/packages/guardian-prover-health-check-ui/src/components/Overview/StatusFilterDropdown.svelte index d4e57674b36..6de251cb971 100644 --- a/packages/guardian-prover-health-check-ui/src/components/Overview/StatusFilterDropdown.svelte +++ b/packages/guardian-prover-health-check-ui/src/components/Overview/StatusFilterDropdown.svelte @@ -16,6 +16,7 @@ const options = [ { value: null, label: $t('filter.guardian_status.all') }, { value: GuardianProverStatus.DEAD, label: $t('filter.guardian_status.dead') }, + { value: GuardianProverStatus.UNHEALTHY, label: $t('filter.guardian_status.unhealthy') }, { value: GuardianProverStatus.ALIVE, label: $t('filter.guardian_status.alive') } ]; diff --git a/packages/guardian-prover-health-check-ui/src/i18n/en.json b/packages/guardian-prover-health-check-ui/src/i18n/en.json index e0240cb9e4e..5e0402e1270 100644 --- a/packages/guardian-prover-health-check-ui/src/i18n/en.json +++ b/packages/guardian-prover-health-check-ui/src/i18n/en.json @@ -57,6 +57,6 @@ "configuration_error": "Amount of provers does not match configuration!", "critical": "CRITICAL: only {online} of required {required} provers online", "degraded": "WARNING: only {online} of {total} provers online (min: {required})", - "operational": " {online} of {total} proves online. (min: {required})" + "operational": " {online} of {total} provers online. (min: {required})" } -} +} \ No newline at end of file diff --git a/packages/guardian-prover-health-check-ui/src/lib/dataFetcher.ts b/packages/guardian-prover-health-check-ui/src/lib/dataFetcher.ts index d3016e46484..09cb0dfad14 100644 --- a/packages/guardian-prover-health-check-ui/src/lib/dataFetcher.ts +++ b/packages/guardian-prover-health-check-ui/src/lib/dataFetcher.ts @@ -1,5 +1,5 @@ import { fetchGuardianProversFromContract } from './guardianProver/fetchGuardianProversFromContract'; -import { GuardianProverStatus, type SignedBlocks } from './types'; +import { GuardianProverStatus, type Guardian, type SignedBlocks } from './types'; import { fetchSignedBlocksFromApi } from './api/signedBlocksApiCalls'; import { getGuardianProverIdsPerBlockNumber } from './blocks/getGuardianProverIdsPerBlockNumber'; import { sortSignedBlocksDescending } from './blocks/sortSignedBlocks'; @@ -16,53 +16,71 @@ import { loading, totalGuardianProvers } from '$stores'; -import { get } from 'svelte/store'; +import { get, writable } from 'svelte/store'; const BLOCKS_TO_CHECK = 20; const THRESHOLD = BLOCKS_TO_CHECK / 2; const HEALTHCHECK_TIMEOUT_IN_SECONDS = 60; +const tempGuardianStore = writable([]); -export function startFetching() { - if (get(loading) === true) return; - // Fetch all data immediately - refreshData(); +let guardiansIntervalId; +let blocksAndLivelinessIntervalId; + +export async function startFetching() { + if (get(loading)) return; + + await refreshData(); - // Set up an interval to fetch guardians every 30 seconds - const guardiansInterval = setInterval(() => { + guardiansIntervalId = setInterval(() => { fetchGuardians(); - }, 30000); + }, 10000); - // Set up an interval to fetch signed block and liveliness stats every 12 seconds - const blocksAndLivelinessInterval = setInterval(() => { + blocksAndLivelinessIntervalId = setInterval(() => { fetchSignedBlockStats(); determineLiveliness(); }, 12000); - // Return a function to clear all intervals - return () => { - clearInterval(guardiansInterval); - clearInterval(blocksAndLivelinessInterval); - }; +} + +export function stopFetching() { + if (guardiansIntervalId) { + clearInterval(guardiansIntervalId); + guardiansIntervalId = null; + } + if (blocksAndLivelinessIntervalId) { + clearInterval(blocksAndLivelinessIntervalId); + blocksAndLivelinessIntervalId = null; + } } export async function refreshData() { if (get(loading) === true) return; loading.set(true); - await fetchSignedBlockStats(); - await fetchGuardians(); - await determineLiveliness(); + + if (get(guardianProvers)?.length === 0) { + await fetchGuardians(); + const block = fetchSignedBlockStats(); + const liveness = determineLiveliness(); + await Promise.all([block, liveness]); + } else { + const guardian = fetchGuardians(); + const block = fetchSignedBlockStats(); + const liveness = determineLiveliness(); + await Promise.all([block, guardian, liveness]); + } + loading.set(false); } async function fetchGuardians() { - const rawData = await fetchGuardianProversFromContract(); - const required = await fetchGuardianProverRequirementsFromContract(); - + const [rawData, required] = await Promise.all([ + fetchGuardianProversFromContract(), + fetchGuardianProverRequirementsFromContract() + ]); minGuardianRequirement.set(required); totalGuardianProvers.set(rawData.length); - const guardians = []; - for (const guardian of rawData) { + const guardianFetchPromises = rawData.map(async (guardian) => { const balance = await publicClient.getBalance({ address: guardian.address as Address }); @@ -70,27 +88,25 @@ async function fetchGuardians() { const balanceAsEther = formatEther(balance); guardian.balance = balanceAsEther; - const status = await fetchLatestGuardianProverHealtCheckFromApi( - import.meta.env.VITE_GUARDIAN_PROVER_API_URL, - guardian.id - ); - guardian.latestHealthCheck = status; + const [status, uptime] = await Promise.all([ + fetchLatestGuardianProverHealtCheckFromApi( + import.meta.env.VITE_GUARDIAN_PROVER_API_URL, + guardian.id + ), + fetchUptimeFromApi(import.meta.env.VITE_GUARDIAN_PROVER_API_URL, guardian.id) + ]); - const uptime = await fetchUptimeFromApi( - import.meta.env.VITE_GUARDIAN_PROVER_API_URL, - guardian.id - ); - guardian.uptime = uptime; - if (uptime > 100) guardian.uptime = 100; + guardian.latestHealthCheck = status; + guardian.uptime = Math.min(uptime, 100); + // guardian.alive = status.alive ? GuardianProverStatus.ALIVE : GuardianProverStatus.DEAD; - guardian.alive = status.alive ? GuardianProverStatus.ALIVE : GuardianProverStatus.DEAD; + return guardian; + }); - guardians.push(guardian); - } + const data = await Promise.all(guardianFetchPromises); + tempGuardianStore.set(data); lastGuardianFetchTimestamp.set(Date.now()); - - guardianProvers.set(guardians); } async function fetchSignedBlockStats() { @@ -105,19 +121,19 @@ async function fetchSignedBlockStats() { } async function determineLiveliness(): Promise { + const tempData = get(tempGuardianStore); const now = new Date(); + if (!tempData) return; - const guardians = get(guardianProvers); - if (!guardians) return; - for (const guardian of guardians) { + const guardians = tempData.map((guardian) => { const latestCheck = guardian.latestHealthCheck; const createdAt = new Date(latestCheck.createdAt); const secondsSinceLastCheck = (now.getTime() - createdAt.getTime()) / 1000; if (secondsSinceLastCheck > HEALTHCHECK_TIMEOUT_IN_SECONDS) { - guardian.alive = GuardianProverStatus.DEAD; - break; + return { ...guardian, alive: GuardianProverStatus.DEAD }; } + let countSignedBlocks = 0; const recentSignedBlocks = get(signedBlocks).slice(0, BLOCKS_TO_CHECK); @@ -127,8 +143,14 @@ async function determineLiveliness(): Promise { } } - // Update status based on whether the guardian signed at least half of the configured blocks to check - guardian.alive = + const status = countSignedBlocks >= THRESHOLD ? GuardianProverStatus.ALIVE : GuardianProverStatus.UNHEALTHY; - } + + return { + ...guardian, + alive: status + }; + }); + + guardianProvers.set(guardians); } diff --git a/packages/guardian-prover-health-check-ui/src/routes/+layout.svelte b/packages/guardian-prover-health-check-ui/src/routes/+layout.svelte index 5c1b91f7092..b1f216194f5 100644 --- a/packages/guardian-prover-health-check-ui/src/routes/+layout.svelte +++ b/packages/guardian-prover-health-check-ui/src/routes/+layout.svelte @@ -4,12 +4,12 @@ import Header from '$components/Header/Header.svelte'; import { Page } from '$components/Page'; import { onMount, onDestroy } from 'svelte'; - import { startFetching } from '$lib/dataFetcher'; - - let stopFetching: () => void; + import { startFetching, stopFetching } from '$lib/dataFetcher'; + import { loading } from '$stores/stores'; onMount(() => { - stopFetching = startFetching(); + loading.set(false); + startFetching(); }); onDestroy(() => { diff --git a/packages/guardian-prover-health-check-ui/src/stores/stores.ts b/packages/guardian-prover-health-check-ui/src/stores/stores.ts index 23b10a16ce1..27ac68eb42b 100644 --- a/packages/guardian-prover-health-check-ui/src/stores/stores.ts +++ b/packages/guardian-prover-health-check-ui/src/stores/stores.ts @@ -5,7 +5,7 @@ import { type GuardianProverIdsMap, PageTabs } from '$lib/types'; -import { type Writable, writable } from 'svelte/store'; +import { type Writable, writable, derived } from 'svelte/store'; // Healtchecks export const apiResponse: Writable = writable([]); @@ -24,3 +24,31 @@ export const signerPerBlock: Writable = writable({}); export const loading: Writable = writable(false); export const selectedTab = writable(PageTabs.GUARDIAN_PROVER); export const selectedGuardianProver = writable(); + +interface StatusCounts { + dead: number; + alive: number; + unhealthy: number; +} + +export const guardianStatusCounts = derived(guardianProvers, ($guardianProvers): StatusCounts => { + if (!$guardianProvers) return { dead: 0, alive: 0, unhealthy: 0 }; + + return $guardianProvers.reduce( + (acc, guardian) => { + switch (guardian.alive) { + case 0: // DEAD + acc.dead += 1; + break; + case 1: // ALIVE + acc.alive += 1; + break; + case 2: // UNHEALTHY + acc.unhealthy += 1; + break; + } + return acc; + }, + { dead: 0, alive: 0, unhealthy: 0 } + ); +});