From cfa767f7b95ba62934b80a59898548ed28235aaa Mon Sep 17 00:00:00 2001 From: Imran Munir Date: Fri, 7 Aug 2026 12:02:41 +0100 Subject: [PATCH] fix(wallet-toolbox): pass trx to findProvenTxs in getProvenOrRawTx getProvenOrRawTx(txid, trx) receives a database transaction and binds it for its second query (proven_tx_reqs) but drops it on the first, so the proven_txs lookup runs on this.knex instead of the caller's transaction. findProvenTxs -> findProvenTxsQuery -> setupQuery uses this.toDb(args.trx), so with args.trx absent the query asks the pool for a second connection while the open transaction still holds the first. knex forces {min:1,max:1} on the sqlite dialect, so that connection can never be granted: the transaction will not release until the query returns, and the query cannot run until the transaction releases. After acquireConnectionTimeout it fails with "KnexTimeoutError: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?". The caller is the proof-completion path: findTransactions(.., trx) -> validateRawTransaction(t, trx) -> getRawTxOfKnownValidTransaction(.., trx) -> getProvenOrRawTx(txid, trx) so TaskCheckForProofs dies before processing any request, leaving the proven_tx_reqs backlog at zero attempts indefinitely. Downstream this makes BEEF payment headers grow without bound, because ancestry stops at the first parent holding a merkle proof and those proofs never arrive. Reproduced independently of any app: a standalone script on a copy of a real wallet database deadlocks with 343 outstanding requests and also with 5, so this is not backlog volume but a path that never completes. Verified in a live BSV Desktop wallet (2.4.4, better-sqlite3) that had been stuck for the whole session: proven_tx_reqs completed 18 -> 119 outstanding (unmined) 139 -> 38 proven_txs 19 -> 120 monitor KnexTimeoutErrors 27 -> 27 (none new) Payment header fell 33,058 -> 25,494 -> 13,666 bytes, and a wallet that Cloudflare had been refusing at its 32KB request-header cap began paying again on the first attempt. Unchanged in 2.4.4 and 2.6.1, and shared by every consumer on SQLite. --- packages/wallet/wallet-toolbox/src/storage/StorageKnex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet/wallet-toolbox/src/storage/StorageKnex.ts b/packages/wallet/wallet-toolbox/src/storage/StorageKnex.ts index e5b71d4f9..eeba9f2d7 100644 --- a/packages/wallet/wallet-toolbox/src/storage/StorageKnex.ts +++ b/packages/wallet/wallet-toolbox/src/storage/StorageKnex.ts @@ -88,7 +88,7 @@ export class StorageKnex extends StorageProvider implements WalletStorageProvide inputBEEF: undefined } - r.proven = verifyOneOrNone(await this.findProvenTxs({ partial: { txid } })) + r.proven = verifyOneOrNone(await this.findProvenTxs({ partial: { txid }, trx })) if (r.proven == null) { const reqRawTx = verifyOneOrNone( await k('proven_tx_reqs')