Skip to content

Commit

Permalink
feat(guardian-prover-health-check-ui): better pseudonym handling (#17177
Browse files Browse the repository at this point in the history
)

Co-authored-by: Karim <[email protected]>
  • Loading branch information
KorbinianK and kimo-ice authored May 27, 2024
1 parent d05ccf6 commit bc19b67
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 173 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/guardian-prover-health-check-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ jobs:
uses: ./.github/workflows/guardian-prover-health-check-ui--ci.yml

# Deployment name follow the pattern: deploy_<appname(guardian-prover-health-check-ui)>_<network(devnet|hekla|mainnet)>_<environment(preview|production)>
deploy_guardians-ui_mainnet_preview:
if: ${{ github.ref_name != 'main' }}
needs: build-and-test
uses: ./.github/workflows/repo--vercel-deploy.yml
with:
environment: "preview"
flags: ""
secrets:
vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID_GUARDIAN_UI_MAINNET }}
vercel_org_id: ${{ secrets.VERCEL_ORG_ID }}
vercel_token: ${{ secrets.VERCEL_TOKEN }}

deploy_guardians-ui_hekla_preview:
if: ${{ github.ref_name != 'main' }}
needs: build-and-test
Expand Down Expand Up @@ -48,3 +60,15 @@ jobs:
vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID_GUARDIAN_UI_HEKLA }}
vercel_org_id: ${{ secrets.VERCEL_ORG_ID }}
vercel_token: ${{ secrets.VERCEL_TOKEN }}

deploy_guardians-ui_mainnet_production:
if: ${{ startsWith(github.ref, 'refs/tags/guardians-ui-v') }}
needs: build-and-test
uses: ./.github/workflows/repo--vercel-deploy.yml
with:
environment: "production"
flags: "--prod"
secrets:
vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID_GUARDIAN_UI_MAINNET }}
vercel_org_id: ${{ secrets.VERCEL_ORG_ID }}
vercel_token: ${{ secrets.VERCEL_TOKEN }}
1 change: 1 addition & 0 deletions packages/guardian-prover-health-check-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"dependencies": {
"@wagmi/core": "^2.8.0",
"axios": "^1.6.7",
"debug": "^4.3.4",
"svelte-i18n": "^4.0.0",
"viem": "^2.9.29"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { signedBlocks, totalGuardianProvers, guardianProvers } from '$stores';
import { Spinner } from '$components/Spinner';
import { t } from 'svelte-i18n';
import type { SignedBlock } from '$lib/types';
const pageSize = 10;
let currentPage = 0;
Expand All @@ -14,6 +15,12 @@
};
$: blocksToDisplay = $signedBlocks.slice(currentPage * pageSize, (currentPage + 1) * pageSize);
function isSignedBlock(
prover: SignedBlock | { address: string; blockHash: string; signature: string }
): prover is SignedBlock {
return (prover as SignedBlock).guardianProverAddress !== undefined;
}
</script>

<h1 class="text-left">{$t('headings.blocks')}</h1>
Expand All @@ -25,18 +32,22 @@
<div class="col-span-3">{$t('blocks.signed')}</div>
</div>
{#each blocksToDisplay as { blockNumber, blocks }, index (blockNumber)}
{@const singedByProvers = blocks.sort((a, b) => a.guardianProverID - b.guardianProverID)}
{@const missingProverIDs = Array.from(
{ length: $totalGuardianProvers },
(_, i) => i + 1
).filter((id) => !singedByProvers.find((p) => p.guardianProverID === id))}
{@const missingProvers = missingProverIDs.map((id) => ({
guardianProverID: id,
blockHash: 'N/A',
signature: 'N/A'
}))}
{@const allProvers = [...singedByProvers, ...missingProvers]}
{@const displayProvers = allProvers.sort((a, b) => a.guardianProverID - b.guardianProverID)}
{@const signedByProvers = blocks.sort((a, b) =>
a.guardianProverAddress > b.guardianProverAddress ? 1 : -1
)}
{@const missingProverAddresses = $guardianProvers
.filter((g) => !signedByProvers.find((p) => p.guardianProverAddress === g.address))
.map((g) => ({
address: g.address,
blockHash: 'N/A',
signature: 'N/A'
}))}
{@const allProvers = [...signedByProvers, ...missingProverAddresses]}
{@const displayProvers = allProvers.sort((a, b) => {
const aAddress = isSignedBlock(a) ? a.guardianProverAddress : a.address;
const bAddress = isSignedBlock(b) ? b.guardianProverAddress : b.address;
return aAddress > bAddress ? 1 : -1;
})}
<div class="collapse collapse-arrow bg-base-200 rounded-lg shadow-md">
<input type="checkbox" id={`block-${index}`} class="peer" />
<label for={`block-${index}`} class="collapse-title font-medium items-center">
Expand All @@ -52,7 +63,7 @@
<div class="collapse-content bg-white">
{#each displayProvers as p}
{@const guardianProver = $guardianProvers?.find(
(g) => Number(g.id) === Number(p.guardianProverID)
(g) => g.address === (isSignedBlock(p) ? p.guardianProverAddress : p.address)
)}
<div class="grid grid-cols-4 items-center border-b py-[24px]">
<div class="f-col">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { HealthCheck } from '$lib/types';
import { onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import HealthCheckRow from './HealthCheckRow.svelte';
import { t } from 'svelte-i18n';
import Paginator from '$components/Paginator/Paginator.svelte';
Expand All @@ -21,27 +21,26 @@
export let selectedGuardianProver = null;
let healthCheckInterval;
const startFetching = async () => {
await fetchHealthChecks();
const healthCheckInterval = setInterval(() => {
healthCheckInterval = setInterval(() => {
fetchHealthChecks();
}, 1200);
return () => {
clearInterval(healthCheckInterval);
};
}, 12000);
};
const fetchHealthChecks = async () => {
const data = await fetchGuardianProverHealthChecksFromApi(
import.meta.env.VITE_GUARDIAN_PROVER_API_URL,
nextHealthCheckPage,
pageSize,
selectedGuardianProver?.id
selectedGuardianProver?.address
);
healthChecks = data.items;
totalItems = data.total;
filteredHealthChecks = healthChecks;
};
onMount(async () => {
Expand All @@ -51,12 +50,16 @@
await startFetching();
});
onDestroy(() => {
clearInterval(healthCheckInterval);
});
const handlePageChange = async (selectedPage: number) => {
const data = await fetchGuardianProverHealthChecksFromApi(
import.meta.env.VITE_GUARDIAN_PROVER_API_URL,
selectedPage,
pageSize,
selectedGuardianProver?.id
selectedGuardianProver?.address
);
healthChecks = data.items;
};
Expand Down
149 changes: 96 additions & 53 deletions packages/guardian-prover-health-check-ui/src/lib/dataFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import {
import { get } from 'svelte/store';
import { getPseudonym } from './guardianProver/addressToPseudonym';
import { loadGuardians } from './guardianProver/loadConfiguredGuardians';
import { loadGuardiansFromContract } from './guardianProver/loadGuardiansFromContract';
import { getLogger } from './util/logger';

const log = getLogger('dataFetcher');

const BLOCKS_TO_CHECK = 20;
const THRESHOLD = BLOCKS_TO_CHECK / 2;
Expand Down Expand Up @@ -63,7 +67,9 @@ export async function refreshData() {
if (get(loading) === true) return;
loading.set(true);

if (!get(guardianProvers)) {
log('refreshData start');

if (!get(guardianProvers) || get(guardianProvers).length === 0) {
// Initial data fetch
await initializeGuardians();
await fetchGuardians();
Expand All @@ -80,66 +86,97 @@ export async function refreshData() {
}

loading.set(false);
log('refreshData end');
}

async function initializeGuardians() {
const guardiansMap = await loadGuardians();
const rawGuardians: Guardian[] = Object.entries(guardiansMap).map(([address, name], index) => ({
name: name,
address: address as Address,
id: index + 1, // add +1 as guardian contract numbers starts at 1
latestHealthCheck: null,
alive: GuardianProverStatus.UNKNOWN,
balance: null,
lastRestart: null,
uptime: null,
nodeInfo: null
}));

guardianProvers.set(rawGuardians);
async function initializeGuardians(): Promise<void> {
log('initializeGuardians start');
const startTime = Date.now();

try {
log('Loading contract guardians start');
const contractGuardians = await loadGuardiansFromContract();
log('Loading contract guardians end', Date.now() - startTime);

log('Loading pseudonym mapping start');
const guardianPseudonymMapping = await loadGuardians();
log('Loading pseudonym mapping end', Date.now() - startTime);

const rawGuardians: Guardian[] = contractGuardians.map((guardian) => {
const name = guardianPseudonymMapping[guardian.address] || guardian.address;
return {
...guardian,
name,
balance: null,
lastRestart: null,
uptime: null,
versionInfo: null,
blockInfo: null,
latestHealthCheck: guardian.latestHealthCheck, // Preserve initial state
alive: guardian.alive // Preserve initial state
};
});

// Batch update guardianProvers store
guardianProvers.update(() => rawGuardians);
} catch (error) {
log('Error initializing guardians:', error);
throw error;
}

log('initializeGuardians end', Date.now() - startTime);
}

async function fetchGuardians() {
const existingGuardians = get(guardianProvers);

const [required] = await Promise.all([fetchGuardianProverRequirementsFromContract()]);

minGuardianRequirement.set(required);
totalGuardianProvers.set(existingGuardians?.length);

const guardianFetchPromises = existingGuardians.map(async (newGuardian) => {
const guardian = existingGuardians.find((g) => g.address === newGuardian.address) || {
...newGuardian,
alive: GuardianProverStatus.UNKNOWN
};

guardian.name = await getPseudonym(guardian.address);

const [status, uptime, balance] = await Promise.all([
fetchLatestGuardianProverHealthCheckFromApi(
import.meta.env.VITE_GUARDIAN_PROVER_API_URL,
guardian.address
),
fetchUptimeFromApi(import.meta.env.VITE_GUARDIAN_PROVER_API_URL, guardian.address),
publicClient.getBalance({ address: guardian.address as Address })
]);

guardian.balance = formatEther(balance);
console.log('balance', guardian.name, guardian.balance);

guardian.latestHealthCheck = status;
guardian.uptime = Math.min(uptime, 100);

return guardian;
});
log('fetchGuardians start');
const existingGuardians = get(guardianProvers) || [];

try {
const [required] = await Promise.all([fetchGuardianProverRequirementsFromContract()]);

minGuardianRequirement.set(required);
totalGuardianProvers.set(existingGuardians.length);

const guardianFetchPromises = existingGuardians.map(async (newGuardian) => {
const guardian = existingGuardians.find((g) => g.address === newGuardian.address) || {
...newGuardian,
alive: GuardianProverStatus.UNKNOWN
};

guardian.name = await getPseudonym(guardian.address);

const [status, uptime, balance] = await Promise.all([
fetchLatestGuardianProverHealthCheckFromApi(
import.meta.env.VITE_GUARDIAN_PROVER_API_URL,
guardian.address
),
fetchUptimeFromApi(import.meta.env.VITE_GUARDIAN_PROVER_API_URL, guardian.address),
publicClient.getBalance({ address: guardian.address as Address })
]);

guardian.balance = formatEther(balance);
log('balance', guardian.name, guardian.balance);

guardian.latestHealthCheck = status;
guardian.uptime = Math.min(uptime, 100);

return guardian;
});

const updatedGuardians = await Promise.all(guardianFetchPromises);
guardianProvers.set(updatedGuardians);
lastGuardianFetchTimestamp.set(Date.now());
log('updatedGuardians', updatedGuardians);
} catch (error) {
log('Error fetching guardians:', error);
throw error;
}

const updatedGuardians = await Promise.all(guardianFetchPromises);
guardianProvers.set(updatedGuardians);
lastGuardianFetchTimestamp.set(Date.now());
console.log('updatedGuardians', updatedGuardians);
log('fetchGuardians end');
}

async function fetchSignedBlockStats() {
log('fetchSignedBlockStats start');
const blocks: SignedBlocks = await fetchSignedBlocksFromApi(
import.meta.env.VITE_GUARDIAN_PROVER_API_URL
);
Expand All @@ -148,14 +185,16 @@ async function fetchSignedBlockStats() {

const signer = await getGuardianProverIdsPerBlockNumber(blocks);
signerPerBlock.set(signer);
log('fetchSignedBlockStats end');
}

async function determineLiveliness(): Promise<void> {
log('determineLiveliness start');
const now = new Date();
guardianProvers.update((guardians) =>
guardians.map((guardian) => {
const latestCheck = guardian.latestHealthCheck;
const createdAt = new Date(latestCheck.createdAt);
const createdAt = new Date(latestCheck?.createdAt || 0);
const secondsSinceLastCheck = (now.getTime() - createdAt.getTime()) / 1000;
let aliveStatus = guardian.alive;

Expand All @@ -178,8 +217,11 @@ async function determineLiveliness(): Promise<void> {
return { ...guardian, alive: aliveStatus };
})
);
log('determineLiveliness end');
}

async function fetchStats(): Promise<void> {
log('fetchStats start');
const guardians = get(guardianProvers);

const updatedGuardiansPromises = guardians.map(async (guardian) => {
Expand Down Expand Up @@ -220,4 +262,5 @@ async function fetchStats(): Promise<void> {

const updatedGuardians = await Promise.all(updatedGuardiansPromises);
guardianProvers.set(updatedGuardians);
log('fetchStats end');
}
Loading

0 comments on commit bc19b67

Please sign in to comment.