Reduce UI lag: dedup and throttle UTXO engine emissions#446
Reduce UI lag: dedup and throttle UTXO engine emissions#446paullinator wants to merge 4 commits intomasterfrom
Conversation
On every start(), the UTXO engine re-queried blockbook for every address and re-emitted every returned transaction even if it already existed on disk unchanged. Skipping the emit when existingTx exists and its blockHeight hasn't changed eliminated 199+ redundant onTransactions calls per wallet on restart. Also computes confirmations directly from blockHeight in toEdgeTransaction so UTXO txs report confirmed/unconfirmed correctly without waiting for onBlockHeightChanged.
start() unconditionally reset processedCount and processedPercent to zero, causing the sync ratio to walk 0%→100% on every login and emitting ADDRESSES_CHECKED for each increment. Guarding the reset prevents redundant progress walks within the same session. Also adds a high-water-mark guard to updateProgressRatio to prevent progress from going backwards when setLookAhead inflates the denominator.
EngineEmitter had zero deduplication — BLOCK_HEIGHT_CHANGED, WALLET_BALANCE_CHANGED, and ADDRESSES_CHECKED all fired unconditionally. Adding last-value tracking via typed emit methods (emitBlockHeightChanged, emitWalletBalanceChanged, emitAddressesChecked) suppresses duplicate emissions for block height, balance, and sync ratio. Also stops forwarding BLOCK_HEIGHT_CHANGED to core-js via onBlockHeightChanged; UTXO txs now set confirmations directly so the callback is no longer needed.
Even with per-engine dedup, multiple UTXO engines syncing simultaneously produced frequent onAddressesChecked callbacks. A global rate limiter (max 1 per 500ms, ratio=1 always passes) reduces aggregate sync-progress volume across all wallets.
| common.emitter.emit(EngineEvent.TRANSACTIONS, [ | ||
| { isNew, transaction: edgeTx } | ||
| ]) | ||
| } |
There was a problem hiding this comment.
Transaction emission skipped when ourAmount changes but blockHeight unchanged
Medium Severity
The new dedup guard only emits when existingTx is null or blockHeight changed, but saveTransaction can also change ourOuts, ourIns, and ourAmount when a new scriptPubkey is discovered for an already-known transaction. During initial sync, when setLookAhead discovers a new address that is an output of an existing confirmed transaction, the merged edgeTx (with updated nativeAmount) is saved to the DataLayer but never emitted to core-js. The transaction list then displays a stale nativeAmount indefinitely for that transaction.
| } | ||
|
|
||
| return super.emit(EngineEvent.ADDRESSES_CHECKED, progressRatio) | ||
| } |
There was a problem hiding this comment.
Throttled progress ratios pollute dedup cache prematurely
Low Severity
emitAddressesChecked sets lastAddressesCheckedRatio before the global 500ms throttle check. When the throttle suppresses an emission, the dedup cache records a value that was never actually delivered to listeners. This makes the "last-value tracking" semantically incorrect — it tracks the last input rather than the last emitted value. In the current single-call-site usage with monotonically increasing ratios, this has no observable effect, but it introduces a latent correctness issue if the method is ever reused elsewhere.
|
Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.
Or push these changes by commenting: Preview (22783161e3)diff --git a/src/common/plugin/EngineEmitter.ts b/src/common/plugin/EngineEmitter.ts
--- a/src/common/plugin/EngineEmitter.ts
+++ b/src/common/plugin/EngineEmitter.ts
@@ -121,7 +121,6 @@
emitAddressesChecked(progressRatio: number): boolean {
if (this.lastAddressesCheckedRatio === progressRatio) return false
- this.lastAddressesCheckedRatio = progressRatio
if (progressRatio !== 1) {
const now = Date.now()
@@ -129,6 +128,7 @@
acLastEmitTime = now
}
+ this.lastAddressesCheckedRatio = progressRatio
return super.emit(EngineEvent.ADDRESSES_CHECKED, progressRatio)
}
}
diff --git a/src/common/utxobased/engine/UtxoEngineProcessor.ts b/src/common/utxobased/engine/UtxoEngineProcessor.ts
--- a/src/common/utxobased/engine/UtxoEngineProcessor.ts
+++ b/src/common/utxobased/engine/UtxoEngineProcessor.ts
@@ -1270,8 +1270,12 @@
// The tx unconfirmed or confirmed after/at the last seenTxCheckpoint
(tx.blockHeight === 0 || tx.blockHeight > seenTxBlockHeight)
- // Only emit if tx is new or changed (blockHeight changed)
- if (existingTx == null || existingTx.blockHeight !== tx.blockHeight) {
+ // Only emit if tx is new or changed (blockHeight or ourAmount changed)
+ if (
+ existingTx == null ||
+ existingTx.blockHeight !== tx.blockHeight ||
+ existingTx.ourAmount !== processedTx.ourAmount
+ ) {
common.emitter.emit(EngineEvent.TRANSACTIONS, [
{ isNew, transaction: edgeTx }
]) |



CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Reduces RN JS thread starvation from UTXO engine callbacks. Four targeted fixes:
processAddressForTransactionsnow only emitsTRANSACTIONSevents when a transaction is genuinely new or itsblockHeighthas changed.processedPercentonstart(): TreatsprocessedPercentas an in-memory high-water-mark that persists across pause/unpause cycles. Prevents the reported sync ratio from rolling backwards whenstart()is called again orsetLookAheadinflates the denominator.EngineEmitter: AddsemitBlockHeightChanged,emitWalletBalanceChanged, andemitAddressesCheckedwith last-value tracking. Only emits when the value actually changes.emitAddressesChecked: Caps aggregate sync-progress bridge traffic to 2 emissions/sec across all UTXO engines (ratio=1 always passes immediately).Note
Medium Risk
Changes event emission semantics (frequency, monotonicity, and when transactions fire) which could affect UI sync/progress behavior and any consumers relying on prior callback patterns.
Overview
Reduces UI/JS-thread load from UTXO engines by deduping and throttling key
EngineEmitterevents.EngineEmittergains typedemitBlockHeightChanged,emitWalletBalanceChanged, andemitAddressesCheckedhelpers that suppress unchanged values, with a global 500ms throttle forADDRESSES_CHECKED(except final ratio1).UTXO sync progress reporting is adjusted to be a monotonic high-water mark (no backwards ratios) across
start()restarts and lookahead expansion, and transaction callbacks are reduced by emittingTRANSACTIONSonly when a tx is new or itsblockHeightchanges. Separately, UTXOtoEdgeTransactionnow derivesconfirmationsfromblockHeightwhen missing/unconfirmed, andBLOCK_HEIGHT_CHANGEDis no longer forwarded to core callbacks (kept internal for engine logic).Written by Cursor Bugbot for commit 6a886a2. This will update automatically on new commits. Configure here.