+37
-9
src/App.svelte
+37
-9
src/App.svelte
···
31
31
mempool?: {
32
32
count: number;
33
33
eta_next_bundle_seconds: number;
34
+
last_time: Date;
34
35
};
35
36
latency?: number;
36
37
}
···
45
46
status?: StatusResponse | StatusResponseError;
46
47
modern?: boolean;
47
48
_head?: boolean;
49
+
_conflict?: boolean;
48
50
}
49
51
50
52
type LastKnownBundle = {
···
53
55
mempool: number | null;
54
56
mempoolPercent: number;
55
57
mempoolBundle: number;
58
+
lastTime?: Date;
56
59
time?: string;
57
60
etaNext?: Date | null;
58
61
totalSize?: number | null;
···
69
72
70
73
let isUpdating = $state(false)
71
74
let canRefresh = $state(true)
72
-
let isConflict = $state(false)
75
+
let consensus = $state({})
76
+
let isConflict = $state(consensus)
77
+
let instancesInConflict = $state<string[]>([])
73
78
let lastUpdated = $state(new Date())
74
79
let autoRefreshEnabled = $state(true)
75
80
let instances = $state<Instance[]>(instancesData.sort(() => Math.random() - 0.5))
···
124
129
125
130
function recalculateHead() {
126
131
isConflict = false
127
-
const headHashes: string[] = []
132
+
instancesInConflict = []
133
+
const headHashes: any = {}
128
134
for (const instance of instances) {
129
135
if (instance.status && 'error' in instance.status) {
130
136
continue
131
137
}
132
138
instance._head = instance.status?.bundles?.last_bundle === lastKnownBundle.number
133
139
if (instance._head && instance.status?.bundles?.head_hash) {
134
-
headHashes.push(instance.status.bundles.head_hash)
140
+
if (!headHashes[instance.status.bundles.head_hash]) {
141
+
headHashes[instance.status.bundles.head_hash] = []
142
+
}
143
+
headHashes[instance.status.bundles.head_hash].push(instance.url)
135
144
}
136
145
}
137
-
isConflict = [...new Set(headHashes)].length > 1
146
+
// second pass
147
+
const sorted: any = Object.fromEntries(
148
+
Object.entries(headHashes).sort(([, a]: any, [, b]: any) => b.length - a.length)
149
+
)
150
+
for (const instance of instances) {
151
+
if (Object.keys(sorted).length > 1 && Object.keys(sorted)[1] && sorted[Object.keys(sorted)[1]].includes(instance.url)) {
152
+
instance._conflict = true
153
+
instancesInConflict.push(instance.url)
154
+
}
155
+
}
156
+
//const uniq = [...new Set(headHashes)]
157
+
isConflict = instancesInConflict.length > Math.ceil(instances.length/2)
138
158
}
139
159
140
160
async function doCheck() {
···
163
183
if (status?.mempool?.count && (!lastKnownBundle.mempool || status.mempool.count > lastKnownBundle.mempool || status.bundles.last_bundle > lastKnownBundle.mempoolBundle)) {
164
184
lastKnownBundle.mempoolBundle = status.bundles.last_bundle
165
185
lastKnownBundle.mempool = status.mempool.count
186
+
lastKnownBundle.lastTime = status.mempool.last_time
166
187
lastKnownBundle.mempoolPercent = Math.round((lastKnownBundle.mempool/100)*100)/100
167
188
lastKnownBundle.etaNext = status.mempool.eta_next_bundle_seconds ? addSeconds(new Date(), status.mempool.eta_next_bundle_seconds) : null
168
189
lastKnownBundle.totalSize = status.bundles.total_size
···
249
270
<div>
250
271
<span class="opacity-50">{#if lastKnownBundle?.time} {formatDistanceToNow(lastKnownBundle.time, { addSuffix: true })}{/if}</span>
251
272
</div>
273
+
<div class="mt-1">
274
+
{#if instancesInConflict.length > 0}
275
+
⚠️ Fork alert on {instancesInConflict.length} instances!
276
+
{:else if !isConflict}
277
+
✅ Everything fine!
278
+
{/if}
279
+
</div>
252
280
</div>
253
281
</div>
254
282
<div>
···
311
339
{#each orderBy(instances, ...instanceOrderBy) as instance}
312
340
<tr>
313
341
<td><a href={instance.url} target="_blank" class="font-semibold">{instance.url.replace("https://", "")}</a></td>
314
-
<td>{#if instance._head && instance.status?.ok}{#if isConflict}⚠️{:else}✅{/if}{:else if instance.status && instance.status?.ok}🔄{:else if instance.status?.error}❌{:else}⌛{/if}</td>
342
+
<td>{#if instance._head && instance.status?.ok}{#if instance._conflict}⚠️{:else}✅{/if}{:else if instance.status && instance.status?.ok}🔄{:else if instance.status?.error}❌{:else}⌛{/if}</td>
315
343
{#if instance.status?.error}
316
344
<td colspan="5" class="opacity-50 text-xs">Error: {instance.status?.error}</td>
317
345
{:else}
318
-
<td>{#if instance.status?.bundles?.last_bundle}{instance.status?.bundles?.last_bundle}{/if}</td>
319
-
<td>{#if instance.status?.mempool && instance._head}{formatNumber(instance.status?.mempool.count)}{:else if instance.status?.error}<span class="opacity-25 text-xs">error</span>{:else if instance.status}<span class="opacity-25 text-xs">syncing</span>{/if}</td>
320
-
<td class="text-xs opacity-50">{#if instance.status?.mempool && instance._head}{instance.status?.mempool.last_op_age_seconds || 0}s{/if}</td>
321
-
<td><span class="font-mono text-xs {instance._head ? (isConflict ? 'text-error-600' : 'text-success-600') : 'opacity-50'}">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
346
+
<td>{#if instance.status?.bundles?.last_bundle}<span class="{instance._conflict ? 'text-error-600' : ''}">{instance.status?.bundles?.last_bundle}</span>{/if}</td>
347
+
<td>{#if instance.status?.mempool && instance._head}<span class="{instance._conflict ? 'text-error-600' : ''}">{formatNumber(instance.status?.mempool.count)}</span>{:else if instance.status?.error}<span class="opacity-25 text-xs">error</span>{:else if instance.status}<span class="opacity-25 text-xs">syncing</span>{/if}</td>
348
+
<td>{#if instance.status?.mempool && instance._head}<span class="text-xs opacity-50 {instance._conflict ? 'text-error-600' : ''}">{instance.status?.mempool.last_op_age_seconds || 0}s</span>{/if}</td>
349
+
<td><span class="font-mono text-xs {instance._head ? (instance._conflict ? 'text-error-600' : 'text-success-600') : 'opacity-50'}">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
322
350
<td><span class="font-mono text-xs {instance.status ? (instance.status?.bundles?.root_hash === ROOT ? 'text-success-600' : 'text-error-600') : ''}">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td>
323
351
{/if}
324
352