Skip to content

Donation section reloads from scratch on every return to Home: 19 sequential explorer requests behind a 60s in-memory TTL #341

Description

@2ndtlmining

Reported from the built site: leaving Home for /nodes and coming back makes the donation section sit on its loading state for several seconds before any number appears. Nothing is broken — it is a cold rescan every time.

What actually happens

fetch_donation_totals() runs from Home's componentDidMount, and Home is a class component, so donationsSettled resets to false on every remount. The scan behind it is not cheap:

t3YcVbiQ…u9Zjrr (current, since 12 Sep) pagesTotal: 0 — 1 request
t1ebxupk…qwLmUG (OLD_ADDRESS_FLUX) pagesTotal: 18 — 18 requests
Per cold scan 19 requests, issued sequentially

scanDonationAddress walks the pages in a for loop with await inside, deliberately — it says so: "Sequentially rather than all at once, to stay under the explorer's limits." That is the right call, and it is also exactly why the wait is visible.

The only thing standing between a remount and those 19 requests is DONATION_SCAN_TTL_MS = 60 * 1000 (client/src/api/globalStats.js:284). _donationScanCache is a module-level variable — in-memory only. So:

  • back on Home within 60s → instant
  • back after 60s → full 19-request rescan
  • any page reload → full rescan regardless

The 60s figure came from #314, where the problem being solved was three callers each running their own scan on one page load. Sharing fixed that. Surviving a remount was never in scope.

The explorer is tighter than the TTL assumes

While measuring this I hit HTTP 429 twice from roughly four manual requests spread over two minutes. Per explorer.js, a 429 reaches the browser as a CORS error, because a rate-limited response omits its CORS headers. So the current design is not only slow on return visits, it is spending its request budget re-fetching bytes it already had — and the failure mode when it runs out does not look like rate limiting.

Proposal

Three parts, smallest first:

1. Raise the in-memory TTL. 60s → 5 minutes, matching RAW_APP_SPECS_CACHE_TTL in api/specs.js. One line; covers the ordinary Home → Nodes → Home round trip outright.

2. Persist a trimmed scan to localStorage. Key donationScan_v1, same shape as homeAppSpecsRaw_v1 (api/specs.js) and donorStatus_v2 (donor/donorStatus.js) already use. Read it synchronously on first call so Home renders last-known values immediately, then revalidate in the background and swap when the fresh scan lands.

Store a trimmed tx, not the raw one — _trimSpecForCache in api/specs.js is the existing precedent. The three callers between them read only txid, time, blockheight, vin[].addr, and vout[].value / vout[].scriptPubKey.addresses. Dropping script hex should take ~180 txs from a few hundred KB to tens of KB. Measure this during implementation rather than trusting the estimate — if it does not comfortably fit, fall back to persisting only the derived totals and let fetch_wallet_donation_summary stay network-bound.

3. Say when the number is from. fluxinfo.js already carries a 'stale' status marker for its persisted aggregate; the donation panel should do the same rather than presenting a cached total as live.

One correctness trap

buildDonationRows(txs, { nowMs }) applies a rolling WINDOW_SEC cutoff. Caching the derived rows would freeze that window and quietly show donations that have since aged out. Cache the trimmed transactions and rebuild rows at read time with a fresh nowMs. The all-time total from aggregateDonations has no such window and is safe either way.

Also: scanBothDonationAddresses deliberately refuses to cache a scan where every address returned null, so a single 429 cannot become a minute of confident zeros. Persisting must keep that guard — a bad scan written to localStorage would become a confident wrong zero that survives reloads, which is strictly worse than the bug this fixes.

Acceptance

  • Home → Nodes → Home renders donation figures with no loading state, from cache, before any network call resolves
  • A hard reload renders last-known values immediately, then updates
  • Cached values are visibly marked as such until the refresh lands
  • A failed scan never overwrites good cached values
  • Rows still respect the rolling window after a cache read
  • totalDonations.test.js stays green — note it already uses jest.isolateModules per test because of the existing module-level cache; persistence will need the same treatment plus a localStorage stub

Refs #314 (which introduced the shared scan and the 60s TTL), #258, #315.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions