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 }
+ );
+});