fork alert

Changed files
+37 -9
src
+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