Skip to content

Commit d63b975

Browse files
committed
dashboard: split out standby guardians
1 parent c3bbec8 commit d63b975

File tree

4 files changed

+120
-32
lines changed

4 files changed

+120
-32
lines changed

common/src/consts.ts

+7
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,13 @@ export const GUARDIAN_SET_4 = [
311311
},
312312
];
313313

314+
export const STANDBY_GUARDIANS = [
315+
{
316+
pubkey: '0x68c16a92903c4c74ffddc730582ba53d967d3dac',
317+
name: 'Google Cloud',
318+
},
319+
];
320+
314321
export type GuardianSetInfo = {
315322
timestamp: string;
316323
contract: string;

dashboard/src/components/Alerts.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import { useCallback, useMemo, useState } from 'react';
1818
import { Environment, useCurrentEnvironment } from '../contexts/NetworkContext';
1919
import { ChainIdToHeartbeats } from '../hooks/useChainHeartbeats';
2020
import { Heartbeat } from '../utils/getLastHeartbeats';
21-
import { GUARDIAN_SET_4, chainIdToName } from '@wormhole-foundation/wormhole-monitor-common';
21+
import {
22+
GUARDIAN_SET_4,
23+
STANDBY_GUARDIANS,
24+
chainIdToName,
25+
} from '@wormhole-foundation/wormhole-monitor-common';
2226

2327
export const BEHIND_DIFF = 1000;
2428
export const CHAIN_LESS_THAN_MAX_WARNING_THRESHOLD = 2;
@@ -56,15 +60,23 @@ function chainDownAlerts(
5660
chainIdsToHeartbeats: ChainIdToHeartbeats,
5761
environment: Environment
5862
): AlertEntry[] {
63+
// don't count standby guardians in chain down alerts
64+
const filteredHeartbeats = heartbeats.filter(
65+
(hb) => !STANDBY_GUARDIANS.find((g) => g.pubkey.toLowerCase() === hb.guardianAddr.toLowerCase())
66+
);
5967
const downChains: { [chainId: string]: string[] } = {};
6068
Object.entries(chainIdsToHeartbeats)
6169
// Aurora is known to be disabled, no need to alert on it
6270
.filter(([chainId]) => chainId !== chainToChainId('Aurora').toString())
6371
.forEach(([chainId, chainHeartbeats]) => {
72+
// don't count standby guardians in chain down alerts
73+
const filteredChainHeartbeats = chainHeartbeats.filter(
74+
(hb) => !STANDBY_GUARDIANS.find((g) => g.pubkey.toLowerCase() === hb.guardian.toLowerCase())
75+
);
6476
// Search for known guardians without heartbeats
65-
const missingGuardians = heartbeats.filter(
77+
const missingGuardians = filteredHeartbeats.filter(
6678
(guardianHeartbeat) =>
67-
chainHeartbeats.findIndex(
79+
filteredChainHeartbeats.findIndex(
6880
(chainHeartbeat) => chainHeartbeat.guardian === guardianHeartbeat.guardianAddr
6981
) === -1
7082
);
@@ -78,7 +90,7 @@ function chainDownAlerts(
7890
// Could be disconnected or erroring post initial checks
7991
// Track highest height to check for lagging guardians
8092
let highest = BigInt(0);
81-
chainHeartbeats.forEach((chainHeartbeat) => {
93+
filteredChainHeartbeats.forEach((chainHeartbeat) => {
8294
const height = BigInt(chainHeartbeat.network.height);
8395
if (height > highest) {
8496
highest = height;
@@ -91,7 +103,7 @@ function chainDownAlerts(
91103
}
92104
});
93105
// Search for guardians which are lagging significantly behind
94-
chainHeartbeats.forEach((chainHeartbeat) => {
106+
filteredChainHeartbeats.forEach((chainHeartbeat) => {
95107
if (chainHeartbeat.network.height !== '0') {
96108
const height = BigInt(chainHeartbeat.network.height);
97109
const diff = highest - height;

dashboard/src/components/Chains.tsx

+29-9
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ import {
2323
useMediaQuery,
2424
} from '@mui/material';
2525
import {
26-
SortingState,
2726
createColumnHelper,
2827
getCoreRowModel,
2928
getSortedRowModel,
29+
SortingState,
3030
useReactTable,
3131
} from '@tanstack/react-table';
32+
import { chainToChainId } from '@wormhole-foundation/sdk-base';
33+
import { chainIdToName, STANDBY_GUARDIANS } from '@wormhole-foundation/wormhole-monitor-common';
3234
import { useCallback, useMemo, useState } from 'react';
3335
import { Environment, useCurrentEnvironment } from '../contexts/NetworkContext';
3436
import { useSettingsContext } from '../contexts/SettingsContext';
3537
import { ChainIdToHeartbeats, HeartbeatInfo } from '../hooks/useChainHeartbeats';
38+
import { CHAIN_ICON_MAP } from '../utils/consts';
3639
import {
3740
BEHIND_DIFF,
3841
CHAIN_LESS_THAN_MAX_WARNING_THRESHOLD,
@@ -42,9 +45,6 @@ import {
4245
} from './Alerts';
4346
import CollapsibleSection from './CollapsibleSection';
4447
import Table from './Table';
45-
import { CHAIN_ICON_MAP } from '../utils/consts';
46-
import { chainToChainId } from '@wormhole-foundation/sdk-base';
47-
import { chainIdToName } from '@wormhole-foundation/wormhole-monitor-common';
4848

4949
const columnHelper = createColumnHelper<HeartbeatInfo>();
5050

@@ -111,6 +111,19 @@ function Chain({
111111
conditionalRowStyle?: ((a: HeartbeatInfo) => SxProps<Theme> | undefined) | undefined;
112112
environment: Environment;
113113
}) {
114+
const [guardianHeartbeats, standbyHeartbeats] = useMemo(
115+
() =>
116+
heartbeats.reduce(
117+
([guardian, standby], hb) => {
118+
if (STANDBY_GUARDIANS.find((g) => g.pubkey.toLowerCase() === hb.guardian.toLowerCase())) {
119+
return [guardian, [...standby, hb]];
120+
}
121+
return [[...guardian, hb], standby];
122+
},
123+
[[] as HeartbeatInfo[], [] as HeartbeatInfo[]]
124+
),
125+
[heartbeats]
126+
);
114127
const smUp = useMediaQuery((theme: any) => theme.breakpoints.up('sm'));
115128
const {
116129
settings: { showChainName },
@@ -132,7 +145,7 @@ function Chain({
132145
{chainIdToName(Number(chainId))} ({chainId})
133146
</Typography>
134147
<Typography>
135-
{healthyCount} / {heartbeats.length}
148+
{healthyCount} / {guardianHeartbeats.length}
136149
</Typography>
137150
</Box>
138151
}
@@ -150,7 +163,7 @@ function Chain({
150163
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
151164
<CircularProgress
152165
variant="determinate"
153-
value={healthyCount === 0 ? 100 : (healthyCount / heartbeats.length) * 100}
166+
value={healthyCount === 0 ? 100 : (healthyCount / guardianHeartbeats.length) * 100}
154167
color={
155168
healthyCount < getQuorumCount(environment)
156169
? 'error'
@@ -206,7 +219,11 @@ function Chain({
206219
{chainIdToName(Number(chainId))} ({chainId})
207220
</DialogTitle>
208221
<DialogContent>
209-
<ChainDetails heartbeats={heartbeats} conditionalRowStyle={conditionalRowStyle} />
222+
<ChainDetails heartbeats={guardianHeartbeats} conditionalRowStyle={conditionalRowStyle} />
223+
<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
224+
Standby Guardians
225+
</Typography>
226+
<ChainDetails heartbeats={standbyHeartbeats} conditionalRowStyle={conditionalRowStyle} />
210227
</DialogContent>
211228
</Dialog>
212229
</>
@@ -238,15 +255,18 @@ function Chains({ chainIdsToHeartbeats }: { chainIdsToHeartbeats: ChainIdToHeart
238255
let numErrors = 0;
239256
const helpers = Object.entries(chainIdsToHeartbeats).reduce((obj, [chainId, heartbeats]) => {
240257
let highest = BigInt(0);
241-
heartbeats.forEach((heartbeat) => {
258+
const filteredHeartbeats = heartbeats.filter(
259+
(hb) => !STANDBY_GUARDIANS.find((g) => g.pubkey.toLowerCase() === hb.guardian.toLowerCase())
260+
);
261+
filteredHeartbeats.forEach((heartbeat) => {
242262
const height = BigInt(heartbeat.network.height);
243263
if (height > highest) {
244264
highest = height;
245265
}
246266
});
247267
const conditionalRowStyle = (heartbeat: HeartbeatInfo) =>
248268
isHeartbeatUnhealthy(heartbeat, highest) ? { backgroundColor: 'rgba(100,0,0,.2)' } : {};
249-
const healthyCount = heartbeats.reduce(
269+
const healthyCount = filteredHeartbeats.reduce(
250270
(count, heartbeat) => count + (isHeartbeatUnhealthy(heartbeat, highest) ? 0 : 1),
251271
0
252272
);

dashboard/src/components/Guardians.tsx

+67-18
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,20 @@ import {
3333
Typography,
3434
} from '@mui/material';
3535
import {
36-
SortingState,
3736
createColumnHelper,
3837
getCoreRowModel,
3938
getSortedRowModel,
39+
SortingState,
4040
useReactTable,
4141
} from '@tanstack/react-table';
42+
import { chainIdToName, STANDBY_GUARDIANS } from '@wormhole-foundation/wormhole-monitor-common';
4243
import { useCallback, useMemo, useState } from 'react';
4344
import TimeAgo from 'react-timeago';
4445
import { ChainIdToHeartbeats } from '../hooks/useChainHeartbeats';
4546
import { Heartbeat } from '../utils/getLastHeartbeats';
4647
import { isHeartbeatUnhealthy } from './Chains';
4748
import CollapsibleSection from './CollapsibleSection';
4849
import Table from './Table';
49-
import { chainIdToName } from '@wormhole-foundation/wormhole-monitor-common';
5050

5151
const columnHelper = createColumnHelper<Heartbeat>();
5252

@@ -298,6 +298,21 @@ function Guardians({
298298
chainIdsToHeartbeats: ChainIdToHeartbeats;
299299
latestRelease: string | null;
300300
}) {
301+
const [guardianHeartbeats, standbyHeartbeats] = useMemo(
302+
() =>
303+
heartbeats.reduce(
304+
([guardian, standby], hb) => {
305+
if (
306+
STANDBY_GUARDIANS.find((g) => g.pubkey.toLowerCase() === hb.guardianAddr.toLowerCase())
307+
) {
308+
return [guardian, [...standby, hb]];
309+
}
310+
return [[...guardian, hb], standby];
311+
},
312+
[[] as Heartbeat[], [] as Heartbeat[]]
313+
),
314+
[heartbeats]
315+
);
301316
const highestByChain = useMemo(
302317
() =>
303318
Object.entries(chainIdsToHeartbeats).reduce((obj, [chainId, heartbeats]) => {
@@ -316,7 +331,18 @@ function Guardians({
316331
const [sorting, setSorting] = useState<SortingState>([]);
317332
const table = useReactTable({
318333
columns,
319-
data: heartbeats,
334+
data: guardianHeartbeats,
335+
state: {
336+
sorting,
337+
},
338+
getRowId: (heartbeat) => `${heartbeat.guardianAddr}-${heartbeat.nodeName}`,
339+
getCoreRowModel: getCoreRowModel(),
340+
getSortedRowModel: getSortedRowModel(),
341+
onSortingChange: setSorting,
342+
});
343+
const standbyTable = useReactTable({
344+
columns,
345+
data: standbyHeartbeats,
320346
state: {
321347
sorting,
322348
},
@@ -348,7 +374,7 @@ function Guardians({
348374
let numWarnings = 0;
349375
let numErrors = 0;
350376
const chainCount = Object.keys(highestByChain).length;
351-
for (const heartbeat of heartbeats) {
377+
for (const heartbeat of guardianHeartbeats) {
352378
const healthyCount = heartbeat.networks.reduce(
353379
(count, network) =>
354380
isHeartbeatUnhealthy(
@@ -373,7 +399,7 @@ function Guardians({
373399
numWarnings,
374400
numErrors,
375401
};
376-
}, [heartbeats, highestByChain]);
402+
}, [guardianHeartbeats, highestByChain]);
377403
return (
378404
<CollapsibleSection
379405
header={
@@ -464,20 +490,43 @@ function Guardians({
464490
}
465491
>
466492
{display === 'cards' ? (
467-
<Box display="flex" flexWrap="wrap" alignItems="center" justifyContent={'center'}>
468-
{heartbeats.map((hb) => (
469-
<GuardianCard
470-
key={`${hb.guardianAddr}-${hb.nodeName}`}
471-
heartbeat={hb}
472-
highestByChain={highestByChain}
473-
latestRelease={latestRelease}
474-
/>
475-
))}
476-
</Box>
493+
<>
494+
<Box display="flex" flexWrap="wrap" alignItems="center" justifyContent={'center'}>
495+
{guardianHeartbeats.map((hb) => (
496+
<GuardianCard
497+
key={`${hb.guardianAddr}-${hb.nodeName}`}
498+
heartbeat={hb}
499+
highestByChain={highestByChain}
500+
latestRelease={latestRelease}
501+
/>
502+
))}
503+
</Box>
504+
<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
505+
Standby Guardians
506+
</Typography>
507+
<Box display="flex" flexWrap="wrap" alignItems="center" justifyContent={'center'}>
508+
{standbyHeartbeats.map((hb) => (
509+
<GuardianCard
510+
key={`${hb.guardianAddr}-${hb.nodeName}`}
511+
heartbeat={hb}
512+
highestByChain={highestByChain}
513+
latestRelease={latestRelease}
514+
/>
515+
))}
516+
</Box>
517+
</>
477518
) : (
478-
<Card>
479-
<Table<Heartbeat> table={table} showRowCount />
480-
</Card>
519+
<>
520+
<Card>
521+
<Table<Heartbeat> table={table} showRowCount />
522+
</Card>
523+
<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
524+
Standby Guardians
525+
</Typography>
526+
<Card>
527+
<Table<Heartbeat> table={standbyTable} showRowCount />
528+
</Card>
529+
</>
481530
)}
482531
</CollapsibleSection>
483532
);

0 commit comments

Comments
 (0)