Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- fixed: Added retry delay to fix dropped transaction thrashing.

## 3.8.6 (2025-10-13)

- fixed: (QTUM) Explorer URLs
Expand Down
48 changes: 45 additions & 3 deletions src/common/utxobased/engine/UtxoEngineProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ export function makeUtxoEngineProcessor(
transactionUpdateCache: {}
}

// Track pending timeouts so they can be cleared on stop
const pendingTimeouts: Set<NodeJS.Timeout> = new Set()

// Clear all pending timeouts
const clearPendingTimeouts = (): void => {
for (const timeoutId of pendingTimeouts) {
clearTimeout(timeoutId)
}
pendingTimeouts.clear()
}

const clearTaskCache = (): void => {
for (const key of Object.keys(taskCache.addressForTransactionsCache)) {
removeItem(taskCache.addressForTransactionsCache, key)
Expand Down Expand Up @@ -212,6 +223,7 @@ export function makeUtxoEngineProcessor(
dataLayer,
emitter,
taskCache,
pendingTimeouts,
updateProgressRatio,
updateSeenTxCheckpoint,
io,
Expand Down Expand Up @@ -358,6 +370,7 @@ export function makeUtxoEngineProcessor(
async stop(): Promise<void> {
serverStates.stop()
clearTaskCache()
clearPendingTimeouts()
running = false
},

Expand Down Expand Up @@ -628,6 +641,7 @@ interface CommonParams {
dataLayer: DataLayer
emitter: EngineEmitter
taskCache: TaskCache
pendingTimeouts: Set<NodeJS.Timeout>
updateProgressRatio: () => void
updateSeenTxCheckpoint: () => void
io: EdgeIo
Expand Down Expand Up @@ -1154,9 +1168,18 @@ async function* processCheckTransactionConfirmation(
} catch (err) {
console.error(err)
common.log('error while processing transaction update:', err)
common.taskCache.transactionUpdateCache[txId] = {
processing: false
}
// Delay putting the transaction back into the cache to avoid thrashing
// on dropped transactions. 10 seconds is a reasonable compromise between
// thrashing and not waiting too long.
addPendingTimeout(
common,
() => {
common.taskCache.transactionUpdateCache[txId] = {
processing: false
}
},
10000
)
return false
}
}
Expand Down Expand Up @@ -1681,3 +1704,22 @@ const addToDataLayerUtxoCache = (
dataLayerUtxoCache[scriptPubkey] = dataLayerUtxos
dataLayerUtxos.full = dataLayerUtxos.utxos.length >= requiredCount
}

/**
* This adds a pending timeout to the common.pendingTimeouts set.
* It will call the callback after the timeout.
*
* This utility is a safe way to add pending timeouts for the processor because
* it will clear the timeout when the processor stops.
*/
const addPendingTimeout = (
common: CommonParams,
callback: () => void,
timeout: number
): void => {
const timeoutId = setTimeout(() => {
common.pendingTimeouts.delete(timeoutId)
callback()
}, timeout)
common.pendingTimeouts.add(timeoutId)
}
Loading