From 07cc828c0804326050922fd0b2b03607b2ffa1e9 Mon Sep 17 00:00:00 2001 From: sainathr19 Date: Wed, 19 Aug 2026 16:10:37 +0530 Subject: [PATCH] fix: don't let the batch event scan block the status display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refresh() awaited fetchStatus and fetchBatches together via Promise.all. fetchBatches scans from_block=0 to "latest" on its first call — against a contract deployed near the chain's current tip, that's a huge paginated range with zero matching events, taking many RPC round-trips to exhaust. While it was in flight, setStatus never ran, so the UI stayed blank even though fetchStatus itself resolves instantly. The 10s poll interval also kept starting a fresh overlapping scan on top of the still-running one. Decouple the batch scan from status/plan, and guard it so a slow scan can't be restarted by the next poll tick before it finishes. --- ui/src/App.jsx | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/ui/src/App.jsx b/ui/src/App.jsx index 5741284..4f5c9fd 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -81,9 +81,16 @@ export default function App() { // two contracts' feeds, and its cursor would skip blocks on the new one. const feedGenerationRef = useRef(0); + // A from-block-0 event scan against a contract deployed near the chain's + // current tip can take many paginated RPC round-trips to reach "latest". + // It must never gate the status/plan display, and a slow scan must not be + // restarted from under itself by the next poll tick. + const batchScanInFlightRef = useRef(false); + useEffect(() => { feedGenerationRef.current += 1; nextFromBlockRef.current = 0; + batchScanInFlightRef.current = false; setBatches([]); }, [provider, address]); @@ -91,23 +98,39 @@ export default function App() { if (!address) return; const generation = feedGenerationRef.current; try { - const [nextStatus, { newBatches, nextFromBlock }] = await Promise.all([ + const [nextStatus, nextPlan] = await Promise.all([ fetchStatus(provider, address), - fetchBatches(provider, address, nextFromBlockRef.current), + commitment ? fetchPlan(provider, address, commitment) : Promise.resolve(null), ]); - const nextPlan = commitment ? await fetchPlan(provider, address, commitment) : null; if (generation !== feedGenerationRef.current) return; - nextFromBlockRef.current = nextFromBlock; setStatus(nextStatus); - if (newBatches.length > 0) { - setBatches((prev) => [...newBatches.reverse(), ...prev]); - } setPlan(nextPlan); setError(""); } catch (refreshError) { if (generation !== feedGenerationRef.current) return; setError(String(refreshError.message ?? refreshError)); } + + if (batchScanInFlightRef.current) return; + batchScanInFlightRef.current = true; + try { + const { newBatches, nextFromBlock } = await fetchBatches( + provider, + address, + nextFromBlockRef.current, + ); + if (generation !== feedGenerationRef.current) return; + nextFromBlockRef.current = nextFromBlock; + if (newBatches.length > 0) { + setBatches((prev) => [...newBatches.reverse(), ...prev]); + } + } catch (batchError) { + if (generation === feedGenerationRef.current) { + console.error("fetchBatches failed:", batchError); + } + } finally { + batchScanInFlightRef.current = false; + } }, [provider, address, commitment]); useEffect(() => {