bark: Avoid exit deadlock by accidentally double-spending input
Claude's words:
What the flake is
The exit of a round VTXO broadcasts a multi-tx exit chain, each tx fee-bumped by its own CPFP child that draws a fee input from barkd's on-chain wallet. In these tests the wallet has a single funded UTXO:
- tx1's CPFP spends the UTXO and confirms at height 121.
- barkd's on-chain wallet hasn't synced that spend yet, so when it builds tx2's CPFP it reuses the same (now-spent) UTXO.
- tx2's CPFP (14592f01) can never enter the mempool — its input is already spent (bad-txns-inputs-missingorspent).
- The recovery path just rebroadcasts that same doomed child every tick, forever → the exit never reaches Claimable → 120s timeout.
I confirmed this from the daemon log (barkd1/debug.log): CPFP broadcast conflict … another CPFP may already be in mempool at height 121, then fell out of mempool, rebroadcasting + inputs are missing or already spent ~78 times until the wall. No round-validation error appears, and the refresh round completed normally — so vul-55 is not involved.
The fix
In the AwaitingConfirmation → child NotFound → rebroadcast branch, if the rebroadcast fails specifically with MissingOrSpentInputs, the stored CPFP is permanently dead, so instead of erroring (and retrying it forever) I reset the exit tx to AwaitingCpfpBroadcast. The daemon's progress_exits_with_cpfp loop then builds a fresh CPFP from currently-spendable UTXOs (by then, tx1's CPFP change has confirmed), which broadcasts cleanly and confirms. It self-heals in a block or two.
This mirrors the two recovery patterns already right above it (None => AwaitingCpfpBroadcast for "child disappeared"). It's narrowly scoped:
- Only triggers when a stored CPFP is genuinely gone (NotFound) and rebroadcast fails with inputs-already-spent.
- I match the exact MissingOrSpentInputs variant, not the broader is_mempool_conflict() — so AlreadyKnown (a legitimate third-party CPFP already in the mempool) is untouched and still waits, no regression there.
- Healthy exits (rebroadcast succeeds) are unchanged.