View Issue Details

IDProjectCategoryView StatusLast Update
0011952Talerwallet-corepublic2026-11-05 08:59
Reporterbot-codex-florian-dold [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0011952: wallet-core: bound refresh-cost work slices and coalesce validation-triggered recomputation
DescriptionBulk denomination validation repeatedly invalidates and recomputes the wallet-wide refresh-cost report, creating substantial background work and redundant frontend reloads. The existing chunking also does not bound the cost of an individual synchronous step.

Evidence from an Android log using wallet-core 1.6.49, commit 9b08843c54a93d83b2df70cad9d25093b85720e1, with SQLite, observability events, and TRACE logging enabled:
- A denomination-validation task takes 17.685 seconds. The captured portion includes 770 completed isValidDenom calls; the log begins after validation has already started.
- During the initial burst, 11 consecutive refresh-balance runs each take 1.129-1.150 seconds.
- The capture contains 19 balance-change notifications with hintTransactionId refresh-balance. Android reloads balances on these notifications, while the actual getBalances requests usually take only 2-3 ms.
- Validation completes by scheduling auto-refresh for the staging exchange. Its master-key replacement is a plausible source of new denominations to verify, but the capture does not establish exactly why those denominations were unverified.

Mechanism: validateDenoms writes verification results in batches of 70. upsertDenomination invalidates the persisted refresh-balance generation; runWalletDbTx then starts refresh-balance. That task loads a global snapshot and computes annual refresh bounds. Publication can notify again because generation invalidation made the previous visible report appear as checking, even if the computed report is otherwise unchanged.

computeAnnualRefreshBoundAsync checks its 8 ms / 32-step budget only after steps.next() returns. A generator step can perform candidate filtering, sorting, output selection, and a further candidate scan, all synchronously. The expensive inner work therefore has no corresponding hard slice bound. Awaiting already-resolved in-memory lookups also does not necessarily service native I/O.

The projection itself runs outside the database transaction; this issue should not be described as a database lock held for the full computation. Reported durations are elapsed time, not CPU measurements. In particular, the 17.199-second refresh-balance outlier spans the app going into the background and should not be treated as 17 seconds of CPU work.
Steps To ReproduceUse a wallet containing holdings at multiple exchanges and a large set of unverified denominations at one exchange. Run bulk validation while recording refresh-balance scheduling, snapshot/projection durations, publication, and balance notifications.

Also benchmark annual-bound computation using enough candidate denominations to expose a costly single generator step, measuring the longest uninterrupted synchronous slice rather than only total task duration.

Run the latency checks on Qtart with the runtime fairness issue fixed as well, to separate the cost of the calculation from event-loop starvation.
Additional InformationRelated runtime defect: https://bugs.taler.net/n/11951

Suggested scope and acceptance criteria:
- Introduce a shared cooperative work-budget/yield mechanism with a meaningful host-I/O fairness contract, and instrument maximum uninterrupted slice duration.
- Split or reduce expensive inner steps; avoid repeated full candidate filtering/sorting where inputs can be prepared or cached safely.
- Coalesce rapid validation-batch invalidations and avoid repeatedly rebuilding unrelated scopes. Preserve prompt reporting of actual monetary balance changes and correctness when denomination validity changes.
- Keep generation checks that prevent stale snapshots from being published, and ensure a final recomputation occurs after the last invalidation. Avoid unbounded debounce during sustained changes.
- Demonstrate substantially fewer global projections and redundant reloads during a large validation batch, with correct final report contents and bounded foreground latency.

Relevant files under packages/taler-wallet-core/src: denominations.ts (denomBatchSize / validateDenoms), db/shared.ts (refresh-balance invalidation), wallet.ts (runWalletDbTx), refreshBalance.ts (snapshot, computation and publication), autoRefresh.ts (annualRefreshBoundSteps / computeAnnualRefreshBoundAsync), autoRefreshBalance.ts.

This is computation and invalidation work; interactive scheduling policy is tracked separately.
TagsNo tags attached.

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

There are no notes attached to this issue.

Issue History

Date ModifiedUsernameFieldChange
2026-11-05 08:59bot-codex-florian-doldNew Issue