Skip to content

Commit 1190392

Browse files
committed
fix(explorer): prevent block skipping in live view
The live block view could skip blocks when multiple blocks arrived faster than they could be fetched. The issue was that useBlock hook only fetches the latest blockNumber value - if blocks N and N+1 arrive while N is still being fetched, the hook switches to fetching N+1 and block N is never added. Fixed by maintaining a queue of pending block numbers and processing them sequentially. Each block that arrives is added to the queue and fetched in order, ensuring no blocks are skipped.
1 parent ba3ee0f commit 1190392

File tree

5 files changed

+46
-26
lines changed

5 files changed

+46
-26
lines changed

apps/explorer/src/lib/og-params.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ export function buildAddressOgUrl(
162162
}
163163
if (params.accountType) {
164164
search.set('accountType', params.accountType)
165-
if (params.accountType === 'contract' && params.methods && params.methods.length > 0) {
165+
if (
166+
params.accountType === 'contract' &&
167+
params.methods &&
168+
params.methods.length > 0
169+
) {
166170
search.set(
167171
'methods',
168172
params.methods

apps/explorer/src/lib/og.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,10 @@ export async function buildAddressOgData(address: string): Promise<{
984984
}
985985
if (addressData.accountType) {
986986
params.set('accountType', addressData.accountType)
987-
if (addressData.accountType === 'contract' && addressData.methods.length > 0) {
987+
if (
988+
addressData.accountType === 'contract' &&
989+
addressData.methods.length > 0
990+
) {
988991
const truncatedMethods = addressData.methods.map((m) =>
989992
truncateOgText(m, 14),
990993
)

apps/explorer/src/routes/_layout/address/$address.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
useTransactionDataFromBatch,
4646
} from '#comps/TxTransactionRow'
4747
import { cx } from '#cva.config'
48+
import { type AccountType, getAccountType } from '#lib/account'
4849
import {
4950
type ContractSource,
5051
contractSourceQueryOptions,
@@ -58,7 +59,6 @@ import {
5859
getContractInfo,
5960
} from '#lib/domain/contracts'
6061
import { parseKnownEvents } from '#lib/domain/known-events'
61-
import { type AccountType, getAccountType } from '#lib/account'
6262
import * as Tip20 from '#lib/domain/tip20'
6363
import { DateFormatter, HexFormatter, PriceFormatter } from '#lib/formatting'
6464
import { useIsMounted, useMediaQuery } from '#lib/hooks'

apps/explorer/src/routes/_layout/blocks.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'
22
import { createFileRoute, Link } from '@tanstack/react-router'
33
import * as React from 'react'
44
import type { Block } from 'viem'
5-
import { useBlock, useWatchBlockNumber } from 'wagmi'
5+
import { useWatchBlockNumber } from 'wagmi'
66
import { getBlock } from 'wagmi/actions'
77
import * as z from 'zod/mini'
88
import { Midcut } from '#comps/Midcut'
@@ -48,11 +48,41 @@ function RouteComponent() {
4848
const [liveBlocks, setLiveBlocks] = React.useState<Block[]>(() =>
4949
queryData.blocks.slice(0, BLOCKS_PER_PAGE),
5050
)
51+
// Queue of block numbers waiting to be fetched
52+
const pendingBlocksRef = React.useRef<bigint[]>([])
53+
const isFetchingRef = React.useRef(false)
5154
const { timeFormat, cycleTimeFormat, formatLabel } = useTimeFormat()
5255

5356
// Use loader data for initial render, then live updates
5457
const currentLatest = latestBlockNumber ?? queryData.latestBlockNumber
5558

59+
// Process pending blocks queue sequentially
60+
const processPendingBlocks = React.useCallback(async () => {
61+
if (isFetchingRef.current || pendingBlocksRef.current.length === 0) return
62+
63+
isFetchingRef.current = true
64+
65+
while (pendingBlocksRef.current.length > 0) {
66+
const blockNumber = pendingBlocksRef.current.shift()
67+
if (blockNumber === undefined) continue
68+
try {
69+
const block = await getBlock(config, { blockNumber })
70+
if (block) {
71+
setLiveBlocks((prev) => {
72+
// Don't add if already exists
73+
if (prev.some((b) => b.number === block.number)) return prev
74+
// Prepend new block and keep only BLOCKS_PER_PAGE
75+
return [block, ...prev].slice(0, BLOCKS_PER_PAGE)
76+
})
77+
}
78+
} catch {
79+
// Block fetch failed, skip it
80+
}
81+
}
82+
83+
isFetchingRef.current = false
84+
}, [])
85+
5686
// Watch for new blocks (only on page 1 when live)
5787
useWatchBlockNumber({
5888
pollingInterval: 500, // Fast polling for snappy updates
@@ -68,32 +98,15 @@ function RouteComponent() {
6898
setTimeout(() => {
6999
recentlyAddedBlocks.delete(blockNumber.toString())
70100
}, 400)
101+
102+
// Add to pending queue and start processing
103+
pendingBlocksRef.current.push(blockNumber)
104+
processPendingBlocks()
71105
}
72106
}
73107
},
74108
})
75109

76-
// Fetch the latest block when block number changes (for live updates on page 1)
77-
const { data: latestBlock } = useBlock({
78-
blockNumber: latestBlockNumber,
79-
query: {
80-
enabled: live && page === 1 && latestBlockNumber !== undefined,
81-
staleTime: Number.POSITIVE_INFINITY, // Block data never changes
82-
},
83-
})
84-
85-
// Add new blocks as they arrive
86-
React.useEffect(() => {
87-
if (!live || page !== 1 || !latestBlock) return
88-
89-
setLiveBlocks((prev) => {
90-
// Don't add if already exists
91-
if (prev.some((b) => b.number === latestBlock.number)) return prev
92-
// Prepend new block and keep only BLOCKS_PER_PAGE
93-
return [latestBlock, ...prev].slice(0, BLOCKS_PER_PAGE)
94-
})
95-
}, [latestBlock, live, page])
96-
97110
// Re-initialize when navigating back to page 1 with live mode
98111
React.useEffect(() => {
99112
if (page === 1 && live && queryData.blocks) {

apps/explorer/src/routes/_layout/tx/$hash.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import { cx } from '#cva.config.ts'
3131
import { autoloadAbiQueryOptions, lookupSignatureQueryOptions } from '#lib/abi'
3232
import { apostrophe } from '#lib/chars'
3333
import type { KnownEvent } from '#lib/domain/known-events'
34+
import type { FeeBreakdownItem } from '#lib/domain/receipt'
3435
import { isTip20Address } from '#lib/domain/tip20'
3536
import { PriceFormatter } from '#lib/formatting'
36-
import type { FeeBreakdownItem } from '#lib/domain/receipt'
3737
import { useCopy, useMediaQuery } from '#lib/hooks'
3838
import { buildOgImageUrl, buildTxDescription } from '#lib/og'
3939
import { LIMIT, type TxData, txQueryOptions } from '#lib/queries'

0 commit comments

Comments
 (0)