Mobile: instant transaction history — RAM row cache, lock-free row taps, nav fixes#9
Open
Dirtybird99 wants to merge 6 commits into
Open
Mobile: instant transaction history — RAM row cache, lock-free row taps, nav fixes#9Dirtybird99 wants to merge 6 commits into
Dirtybird99 wants to merge 6 commits into
Conversation
…-warm The wallet's Show_Transfers takes the account's exclusive lock, which Save_Wallet holds while marshalling and encrypting the entire history every sync cycle — so the history fetch intermittently stalls for seconds on mobile. Stop making the user wait on it: cache the formatted rows (per wallet address, per view) in RAM, paint them synchronously on revisit, and swap in a fresh full fetch underneath only when something actually changed. Warm the Normal view a few seconds after login so even the first visit paints instantly. Cache is dropped on wallet close/switch (privacy) and an address mismatch can never serve another wallet's rows. Also lands the previously uncommitted history groundwork this builds on: the extracted row builders/streamed reveal (history_format.go), the async load() in layoutHistory, gate tests, and sandbox/ ignore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A row tap ran two full wallet scans under the exclusive account lock: one feeding a label that was never added to any container (dead since the detail overlay replaced the inline panel), and one inside layoutHistoryDetail to re-find an entry the list already had. Delete the dead scan and pass the entry into the detail view via a TXID-indexed copy of the rows on screen; the by-TXID fallback scan remains only for taps that land before the first fetch completes. Tap-to-detail no longer touches the wallet mutex, so it can no longer stall behind a wallet save. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The back handler built layoutDashboard()/layoutMain() into session.LastDomain, immediately overwrote it with the current window content, then built the same layout again for SetContent. The first build was pure discarded work — and each dashboard build starts its own listeners, so it also double-registered those. Drop the discarded assignment (BUG_NOTES DEROFDN#13). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every nav site swaps SetContent(layoutTransition()) and then the real screen synchronously inside the same event handler; Fyne paints only after the handler returns, so the transition frame is never shown. On the History round trip and the mobile back handler that build was pure overhead (GIF re-decode + animation start per navigation) — and the back handler never removed its overlay, leaving the GIF animating unseen afterwards. Remove it from these paths only; layoutTransition itself stays (shared with showLoadingOverlay). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
If the user switches views while a fetch is still stalled behind a wallet save, the older goroutine could finish later and repaint the previous view's rows and count under the new view's heading (a latent flaw in the original async load too). A per-screen generation counter, bumped on every load and checked inside each fyne.Do, makes superseded fetches drop their UI updates; their result is still cached, since the cache is keyed per view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review of the branch surfaced three defects: - Crash: load()'s goroutine re-dereferenced the global engram.Disk after fetch() returned. Signing out while a fetch sat behind the wallet's save lock nils the global, and the goroutine then panicked — an unrecovered panic in a bare goroutine kills the app. The wallet is now snapshotted once on the UI thread and threaded through fetch, so the goroutine never reads the global (same pattern as warmHistoryCache). - Privacy: the unconditional histCache.put could repopulate the cache seconds after closeWallet() cleared it. The put is now guarded by a wallet-identity re-check. - Parity: multi-payload transactions repeat a TXID; the byTXID map kept the last entry while the old tap-time scan returned the first match. The map now keeps the first, matching the old detail view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Transaction history on mobile took seconds to appear (and sometimes stalled outright), despite earlier optimization of the row-formatting path. This PR makes History paint instantly on every revisit (and, after a short warm-up, on first visit too), makes tapping a row open the detail view without ever touching the wallet lock, and removes wasted layout work on the mobile back path. No behavior of the underlying wallet sync is changed; derohe is untouched.
Root cause
Profiling the formatting layer was a dead end — by the time of this PR it built 20k rows in <1ms. Tracing into derohe (
walletapi) showed:account.EntriesNative[scid], kept sorted).Show_Transfersis a cheap O(n) post-filter — milliseconds even on a phone.Show_Transfers/Get_Payments_TXIDtake the wallet's exclusive RWMutex (wallet.go:267), andSave_Walletholds the same lock while itjson.Marshals and AES-encrypts the entire account — including the full history — every sync cycle (wallet_memory.go:268-305). On a mobile CPU with a long history, any history fetch randomly blocks for seconds behind that save, and it gets worse as history grows.The lock lives in a dependency, so the fix is to stop making the user wait on it.
What changed (commit by commit)
history_cache.go: formatted rows cached per wallet address + view (Normal/Coinbase/Messages). Revisits paint synchronously from cache withResults: N (updating), while the same full fetch as before runs underneath and swaps in only if anything changed (slices.Equalskip → no flicker, scroll preserved). The Normal view is pre-warmed ~3s after login so the first visit is instant too. Cache is cleared on wallet close (privacy) and an address mismatch can never serve another wallet's rows. Cold path (no cache) is byte-for-byte the previous behavior. Unit tests incl. a-raceconcurrency test. (Also folds in the previously uncommitted streaming groundwork this builds on: extracted row builders + streamed reveal + gate tests.)layoutHistoryDetail(txid, *rpc.Entry)now receives the entry from a TXID-indexed copy of the rows on screen (nil → old scan as fallback, only reachable before the first fetch lands). Tap→detail no longer touches the wallet mutex.layoutDashboard()/layoutMain()builds, which also double-registered their listeners).layoutTransitionwhere it never paints. BothSetContentcalls run synchronously in one event handler, so the transition GIF was decoded + animated and never shown (and the back path left it animating unseen). Removed on the History round trip and back handler only;layoutTransitionitself is untouched.engram.Diskafter the fetch; signing out mid-fetch nils it → panic in a bare goroutine kills the app. The wallet is now snapshotted once and threaded through (same pattern aswarmHistoryCache). (2) privacy: the cache can no longer be repopulated aftercloseWallet()cleared it (wallet-identity re-check beforeput). (3) parity: multi-payload transactions repeat a TXID; the index now keeps the first entry, matching the old scan's selection.Verification
go build .,go vet .,go test .green after every commit;go test -race -run HistoryCache .green (go1.26, fyne v2.6.2).On-device checklist (what reviewers should try)
Results: N (updating)→Results: N.Known limitations / accepted trade-offs
InsertReplacewritesEntriesNativewithout the lock readers take) is unchanged — this PR neither fixes nor worsens it.🤖 Generated with Claude Code