Corrupted wallet UTXO set after v1.10.x upgrade: crash cascade → interrupted resyncs → phantom balances → deposit tx never broadcasts (recovered via custom-input sweep)

Environment

  • Bisq version: upgraded to v1.10.3 (the post-hack hardening release); later showed v1.10.4 during troubleshooting

  • OS: Windows (32 GB RAM)

  • Network: Bitcoin mainnet over Tor

  • Data directory age: ~8 months (created Nov 2024), ~3 GB total, btc_mainnet ~2.77 GB — normal size

  • Wallet: encrypted (AES-256-CBC / Scrypt)

Summary

After upgrading to the v1.10.x hardening line, my wallet ended up with a corrupted UTXO set: the displayed available balance became overstated, and any new transaction the wallet tried to build (a trade deposit tx, and later trades) failed to broadcast — it never appeared in the mempool on public explorers, even after hours and multiple restarts. The corruption appears to have been caused/worsened by SPV resyncs that were repeatedly interrupted by Java heap space (OutOfMemory) crashes. Notably, the overstated balance grew larger after each additional crashed resync (from ~0.185 BTC to ~0.6 BTC), which strongly suggests the resync process was leaving the UTXO set in a progressively more inconsistent state rather than repairing it.

I was ultimately able to recover all real funds by using Funds → Send with “custom inputs,” selecting only the addresses I had independently verified as unspent on-chain, and sweeping them to an external wallet — that transaction broadcast and confirmed normally. This points fairly clearly at the wallet’s internal UTXO tracking being out of sync with the actual chain, rather than any on-chain problem.

Timeline / what happened

  1. Upgraded to v1.10.3 (released after the exploit).

  2. Started getting “deposit transaction (2-of-2 multisig) is absent” errors on trades; trades were failing with the “move to failed trades” prompt.

  3. Attempted an SPV resync to fix it — the resync crashed midway with Java heap space (OutOfMemory). Restarted the app; the resync eventually “completed” after a restart, but the problem persisted. Repeated this more than once.

  4. Trade 430935 (BTC seller) got stuck at “Aguardar confirmação da blockchain.” Its deposit txid showed “Transação não encontrada / Aguardando que apareça no mempool” — both inside Bisq and on mempool.space. The tx had apparently been signed locally but never broadcast to the network.

  5. Noticed the available balance was overstated: after a day where I had sold ~0.07–0.08 BTC in completed trades, the balance had not decreased accordingly (BTC_AVAILABLE still ~0.185 BTC).

  6. Hit repeated Java heap space crashes, plus a JavaFX NPE: Cannot invoke "javafx.scene.Scene.isDepthBuffer()" because the return value of "javafx.scene.Node.getScene()" is null, when navigating (e.g. toward DAO Network Monitor).

  7. Got a “Your Bisq DAO state needs to be resynced” prompt. The peer-based DAO resync got stuck for ~50 minutes on a single block (e.g. 958,877 of 958,892) and never finished. “Rebuild DAO state from resources” did complete successfully and was much faster.

  8. After yet another crash-riddled resync, the overstated balance grew to ~0.6 BTC — clearly divergent from reality.

Diagnostic evidence

Ctrl+J (Show Wallet Data) balances block — the overstated figure was present at the wallet-engine level, not just the UI summary:

Balances:
  0.18536948 BTC AVAILABLE
  0.18536948 BTC AVAILABLE_SPENDABLE
Transactions:
  2 pending
  7 unspent
  2088 spent

  • The 2 pending were the stuck deposit + fee txs for trade 430935 (they reference inputs the network rejects, so they sit unconfirmed forever).

  • The 7 unspent outputs did not match on-chain reality — some outputs the wallet believed were unspent had already been spent by earlier (completed) trade payouts.

Deposit txid for the stuck trade (430935): 3d174c64bd30f5edd193f8682ecb60ab5eadaf4fdd3cdce028a5dcaac64d7315 — not found in mempool on public explorers.

Interpretation: the wallet was trying to build new transactions using phantom/already-spent UTXOs as inputs. Any honest node rejects such a tx (it references an output that no longer exists), so it never propagates — which is exactly the “signed but never appears in mempool” symptom, and also explains the inflated balance (spent outputs still counted as spendable).

What fixed it (recovery)

  • Went to Funds → Send funds, switched input selection to “Usar inputs personalizados” (custom inputs).

  • Independently verified each address’s balance on a block explorer, and selected only the addresses that were genuinely unspent on-chain.

  • Set the destination to an external (hardware) wallet address and broadcast.

  • The sweep transaction broadcast and confirmed normally. All real funds recovered.

This confirms the funds were always safe on-chain; the problem was purely Bisq’s local UTXO accounting.

Open questions / requests

  1. Is the v1.10.x hardening (stricter deposit/payout/DAO validation) interacting badly with interrupted SPV resyncs to corrupt the local UTXO set? The fact that the overstated balance grew with each crashed resync seems like an important clue.

  2. Could Bisq guard against Java heap space crashes during resync (and/or make resync resumable/atomic) so a mid-resync OOM doesn’t leave the wallet in a worse state than before?

  3. For the stuck open trade: since no deposit was ever locked on-chain, mediation/arbitration is (correctly) unavailable, and the in-app dialog says no trade funds/deposit were locked (only possibly some miner/trade fees lost). Is the fee-reimbursement process (Failed_Trades_-_Reimbursement_of_Trade_Fees_and_Miner_Fees) the correct path here?

  4. Is anyone else seeing “deposit transaction absent” + inflated balance + deposit tx failing to broadcast specifically after upgrading into the v1.10.x releases?

Notes for others hitting this

  • Don’t stack SPV resyncs, and don’t force-kill Bisq mid-resync — that appears to be what corrupts the UTXO set.

  • If your balance looks wrong or a tx won’t broadcast, verify individual addresses/outputs on a block explorer before trusting Bisq’s displayed balance.

  • The custom-inputs sweep (selecting only explorer-verified unspent addresses) is a clean way to recover funds to an external wallet without relying on the corrupted UTXO set. If even that fails to broadcast, the next fallback is restoring the seed (+ wallet date) into a fresh Bisq data directory or into Electrum, which rebuilds the UTXO set from the chain.

  • Your coins live on-chain, not in Bisq’s cache — a corrupted local wallet is recoverable as long as you have your seed words + wallet date.