From 4a375f58d05f0451e14062a595a188f8b0d7d19b Mon Sep 17 00:00:00 2001
From: liangping <18786721@qq.com>
Date: Wed, 27 Mar 2024 15:42:53 +0800
Subject: [PATCH] optimized block loading

---
 src/modules/[chain]/uptime/index.vue | 230 +++++++++++----------------
 1 file changed, 96 insertions(+), 134 deletions(-)

diff --git a/src/modules/[chain]/uptime/index.vue b/src/modules/[chain]/uptime/index.vue
index 1dcd0fc0..30c05326 100644
--- a/src/modules/[chain]/uptime/index.vue
+++ b/src/modules/[chain]/uptime/index.vue
@@ -5,10 +5,10 @@ import {
   useStakingStore,
   useBaseStore,
   useBlockchain,
-useFormatter,
+  useFormatter,
 } from '@/stores';
 import UptimeBar from '@/components/UptimeBar.vue';
-import type { SlashingParam, SigningInfo } from '@/types';
+import type { SlashingParam, SigningInfo, Block } from '@/types';
 import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
 
 const props = defineProps(['chain']);
@@ -53,24 +53,24 @@ const validatorSet = computed(() => {
   });
 });
 
-const blocks = ref({} as Record<string, BlockColor[]>);
+const blockColors = ref({} as Record<string, BlockColor[]>);
 
-const grid  = computed(() => {
+const grid = computed(() => {
 
-  const validators = keyword.value.length === 0 ? validatorSet.value : 
-  validatorSet.value.filter((v) => v.moniker.toLowerCase().includes(keyword.value.toLowerCase()));
+  const validators = keyword.value.length === 0 ? validatorSet.value :
+    validatorSet.value.filter((v) => v.moniker.toLowerCase().includes(keyword.value.toLowerCase()));
 
   const window = Number(slashingParam.value.signed_blocks_window || 0);
   return validators.map((v) => {
     const signing = signingInfo.value[v.hex];
     const uptime = signing && window > 0
-            ? (window - Number(signing.missed_blocks_counter)) / window
-            : undefined
+      ? (window - Number(signing.missed_blocks_counter)) / window
+      : undefined
     return {
       moniker: v.moniker,
       hex: v.hex,
       base64: v.base64,
-      blocks: padding(blocks.value[v.base64] || []) ,
+      blocks: padding(blockColors.value[v.base64] || []),
       uptime,
       missed_blocks_counter: signing?.missed_blocks_counter || 0,
       signing
@@ -78,77 +78,29 @@ const grid  = computed(() => {
   })
 });
 
+const preload = ref(false);
 baseStore.$subscribe((_, state) => {
   const newHeight = Number(state.latest?.block?.header?.height || 0)
   if (newHeight > latest.value) {
     latest.value = newHeight;
-    if(Number(state.latest.block.header.height) % 7 === 0 ) updateTotalSigningInfo();
-    validatorSet.value.forEach((v) => {
-      const sig = state.latest.block.last_commit?.signatures.find((s) => s.validator_address === v.base64)
-      const block = blocks.value[v.base64] || [];
-      if (sig){
-        block.push({
-          height: state.latest.block.header.height,
-          color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500'
-        });
-      } else {
-        block.push({
-          height: state.latest.block.header.height,
-          color: 'bg-red-500'
-        });
-      }
-      if (block.length > 50) block.shift();
-      blocks.value[v.base64] = block;
-    });
+    // initialize if it's the first time
+    if(!preload.value) {
+      preFill();
+      preload.value = true;
+    }
+    
+    if (Number(state.latest.block.header.height) % 7 === 0) updateTotalSigningInfo();
+    fillblock(state.latest);
   }
 });
 
 onMounted(() => {
   live.value = true;
 
-  baseStore.fetchLatest().then((l) => {
-    let b = l;
-    if (
-      baseStore.recents?.findIndex((x) => x.block_id.hash === l.block_id.hash) >
-      -1
-    ) {
-      b = baseStore.recents?.at(0) || l;
-    }
-    latest.value = Number(b.block.header.height);
-    const height = Number(b.block.header?.height || 0);
-    if (height > 50) {
-      // constructs sequence for loading blocks
-      let promise = Promise.resolve();
-      for (let i = height - 1; i > height - 50; i -= 1) {
-        promise = promise.then(
-          () =>
-            new Promise((resolve) => {
-              if (live.value) {
-                // continue only if the page is living
-                baseStore.fetchBlock(i).then((x) => {
-                  validatorSet.value.forEach((v) => {
-                    const sig = x.block.last_commit?.signatures.find((s) => s.validator_address === v.base64)
-                    const block = blocks.value[v.base64] || [];
-                    if (sig){
-                      block.unshift({
-                        height: x.block.header.height,
-                        color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500'
-                      });
-                    } else {
-                      block.unshift({
-                        height: x.block.header.height,
-                        color: 'bg-red-500'
-                      });
-                    }
-                    blocks.value[v.base64] = block;
-                  });
-                  resolve();
-                });
-              }
-            })
-        );
-      }
-    }
+
+  // fill the recent blocks
+  baseStore.recents?.forEach((b) => {
+    fillblock(b, 'start');
   });
 
   updateTotalSigningInfo();
@@ -158,6 +110,50 @@ onMounted(() => {
   });
 });
 
+function preFill() {
+
+  if(latest.value > 50 && baseStore.recents.length >= 49 ) return
+  // preload 50 blocks if recent blocks are not enough
+  let promise = Promise.resolve();
+  for (let i = latest.value - baseStore.recents.length; i > latest.value - 50 && i > 1; i -= 1) {
+    promise = promise.then(() =>
+      new Promise((resolve) => {
+        if (live.value) {
+          // continue only if the page is living
+          if (i > latest.value - 50)
+            baseStore.fetchBlock(i).then((x) => {
+              fillblock(x, 'start');
+              resolve();
+            });
+        }
+      })
+    );
+  }
+}
+function fillblock(b: Block, direction: string = 'end') {
+  validatorSet.value.forEach((v) => {
+    const sig = b.block.last_commit?.signatures.find((s) => s.validator_address === v.base64)
+    const block = blockColors.value[v.base64] || [];
+    let color = {
+      height: b.block.header.height,
+      color: 'bg-red-500'
+    }
+    if (sig) {
+      color = {
+        height: b.block.header.height,
+        color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500'
+      }
+    }
+    if (direction === 'end') {
+      block.push(color);
+    } else {
+      block.unshift(color);
+    }
+    if (block.length > 50) block.shift();
+    blockColors.value[v.base64] = block;
+  });
+}
+
 function updateTotalSigningInfo() {
   chainStore.rpc.getSlashingSigningInfos().then((x) => {
     x.info?.forEach((i) => {
@@ -184,36 +180,24 @@ function fetchAllKeyRotation() {
 <template>
   <div>
     <div class="tabs tabs-boxed bg-transparent mb-4">
-      <a
-        class="tab text-gray-400 capitalize"
-        :class="{ 'tab-active': tab === '3' }"
-        @click="changeTab('3')"
-        >{{ $t('uptime.overall') }}</a
-      >
-      <a
-        class="tab text-gray-400 capitalize"
-        :class="{ 'tab-active': tab === '2' }"
-        @click="changeTab('2')"
-        >{{ $t('module.blocks') }}</a
-      >
+      <a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{
+        $t('uptime.overall') }}</a>
+      <a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{
+        $t('module.blocks') }}</a>
       <RouterLink :to="`/${chain}/uptime/customize`">
         <a class="tab text-gray-400 capitalize">{{ $t('uptime.customize') }}</a>
       </RouterLink>
     </div>
     <div class="bg-base-100 px-5 pt-5">
       <div class="flex items-center gap-x-4">
-        <input
-          type="text"
-          v-model="keyword"
-          placeholder="Keywords to filter validators"
-          class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600"
-        />
-        <button v-if="chainStore.isConsumerChain" class="btn btn-sm btn-primary" @click="fetchAllKeyRotation">Load Rotated Keys</button>
+        <input type="text" v-model="keyword" placeholder="Keywords to filter validators"
+          class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
+        <button v-if="chainStore.isConsumerChain" class="btn btn-sm btn-primary" @click="fetchAllKeyRotation">Load
+          Rotated Keys</button>
       </div>
 
       <div v-if="chainStore.isConsumerChain && Object.keys(stakingStore.keyRotation).length === 0"
-        class="alert alert-warning my-4"
-      >
+        class="alert alert-warning my-4">
         Note: Please load rotated keys to see the correct uptime
       </div>
       <!-- grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4 -->
@@ -222,20 +206,13 @@ function fetchAllKeyRotation() {
           <div v-for="(unit, i) in grid" :key="i">
             <div class="flex justify-between py-0 w-[248px]">
               <label class="truncate text-sm">
-                <span class="ml-1 text-black dark:text-white"
-                  >{{ i + 1 }}.{{ unit.moniker }}</span
-                >
+                <span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ unit.moniker }}</span>
               </label>
-              <div
-                v-if="Number(unit?.missed_blocks_counter || 0) > 10"
-                class="badge badge-sm bg-transparent border-0 text-red-500 font-bold"
-              >
+              <div v-if="Number(unit?.missed_blocks_counter || 0) > 10"
+                class="badge badge-sm bg-transparent border-0 text-red-500 font-bold">
                 {{ unit?.missed_blocks_counter }}
               </div>
-              <div
-                v-else
-                class="badge badge-sm bg-transparent text-green-600 border-0 font-bold"
-              >
+              <div v-else class="badge badge-sm bg-transparent text-green-600 border-0 font-bold">
                 {{ unit?.missed_blocks_counter }}
               </div>
             </div>
@@ -244,9 +221,9 @@ function fetchAllKeyRotation() {
         </div>
         <div class="mt-5 text-xs flex justify-center gap-2">
           <span class=" font-bold">{{ $t('uptime.legend') }}: </span>
-          <span class="bg-green-500">&nbsp;</span> {{ $t('uptime.committed')}} 
-          <span class="bg-yellow-500">&nbsp;</span> {{ $t('uptime.precommitted')}} 
-          <span class="bg-red-500">&nbsp;</span> {{ $t('uptime.missed')}} 
+          <span class="bg-green-500">&nbsp;</span> {{ $t('uptime.committed') }}
+          <span class="bg-yellow-500">&nbsp;</span> {{ $t('uptime.precommitted') }}
+          <span class="bg-red-500">&nbsp;</span> {{ $t('uptime.missed') }}
         </div>
       </div>
 
@@ -269,40 +246,27 @@ function fetchAllKeyRotation() {
               </div>
             </td>
             <td class="text-right">
-              <span
-                :class="
-                  v.uptime && v.uptime > 0.95 ? 'text-green-500' : 'text-red-500'
-                "
-              >
-                <div
-                  class="tooltip"
-                  :data-tip="`${v.missed_blocks_counter} missing blocks`"
-                >
+              <span :class="v.uptime && v.uptime > 0.95 ? 'text-green-500' : 'text-red-500'
+        ">
+                <div class="tooltip" :data-tip="`${v.missed_blocks_counter} missing blocks`">
                   {{ format.percent(v.uptime) }}
                 </div>
               </span>
             </td>
             <td>
               <span v-if="v.signing && !v.signing.jailed_until.startsWith('1970')">
-                <div
-                  class="tooltip"
-                  :data-tip="format.toDay(v.signing.jailed_until, 'long')"
-                >
+                <div class="tooltip" :data-tip="format.toDay(v.signing.jailed_until, 'long')">
                   <span>{{ format.toDay(v.signing.jailed_until, 'from') }}</span>
                 </div>
               </span>
             </td>
             <td class="text-xs text-right">
-              <span
-                v-if="v.signing && v.signing.jailed_until.startsWith('1970')"
-                class="text-right"
-                >{{
-                  format.percent(
-                    Number(v.signing.index_offset) /
-                      (latest - Number(v.signing.start_height))
-                  )
-                }}</span
-              >
+              <span v-if="v.signing && v.signing.jailed_until.startsWith('1970')" class="text-right">{{
+        format.percent(
+          Number(v.signing.index_offset) /
+          (latest - Number(v.signing.start_height))
+        )
+      }}</span>
               {{ v.signing?.index_offset }}
             </td>
             <td class="text-right">{{ v.signing?.start_height }}</td>
@@ -312,12 +276,10 @@ function fetchAllKeyRotation() {
             <tr>
               <td colspan="2" class="text-right">
                 {{ $t('uptime.minimum_uptime') }}:
-                <span
-                  class="lowercase tooltip"
-                  :data-tip="`Window size: ${slashingParam.signed_blocks_window}`"
-                  ><span class="ml-2 btn btn-error btn-xs">{{
-                    format.percent(slashingParam.min_signed_per_window)
-                  }}</span>
+                <span class="lowercase tooltip" :data-tip="`Window size: ${slashingParam.signed_blocks_window}`"><span
+                    class="ml-2 btn btn-error btn-xs">{{
+        format.percent(slashingParam.min_signed_per_window)
+                    }}</span>
                 </span>
               </td>
               <td colspan="8"></td>