Skip to content

Reserve funding tx inputs at build time#35

Merged
amackillop merged 1 commit into
lsp-0.7.0from
austin_reserve-utxos-on-channel-funding
Jun 8, 2026
Merged

Reserve funding tx inputs at build time#35
amackillop merged 1 commit into
lsp-0.7.0from
austin_reserve-utxos-on-channel-funding

Conversation

@amackillop

Copy link
Copy Markdown

create_funding_transaction built and signed the funding tx but never applied it back to the BDK wallet graph, so the selected UTXOs stayed selectable until the next chain or mempool sync. Two opens between syncs would build funding transactions that spend the same inputs. The network rejects the loser, but its 0-conf channel is already live, leaving an unbacked phantom channel.

Apply the extracted tx with apply_unconfirmed_txs before releasing the wallet lock so its inputs are marked spent immediately, with no window for a second build to reselect them. A forced wallet sync was the obvious alternative but cannot close the hole: broadcast is async so the tx is not in any mempool when we would sync, and the rejected loser never enters a mempool at all, so a sync could never reserve its inputs.

Holding the inputs at build time means a failed open now has to release them again, otherwise the UTXOs are locked out of all future coin selection. The FundingGenerationReady handler rolls back via cancel_tx on any error before logging.

The regression test gives node A a single UTXO, opens one 0-conf channel, and checks that the spendable balance drops without a sync and that a second open is rejected with InsufficientFunds. Before the fix that second open silently produced a phantom channel.

create_funding_transaction built and signed the funding tx but never
applied it back to the BDK wallet graph, so the selected UTXOs stayed
selectable until the next chain or mempool sync. Two opens between
syncs would build funding transactions that spend the same inputs. The
network rejects the loser, but its 0-conf channel is already live,
leaving an unbacked phantom channel.

Apply the extracted tx with apply_unconfirmed_txs before releasing the
wallet lock so its inputs are marked spent immediately, with no window
for a second build to reselect them. A forced wallet sync was the
obvious alternative but cannot close the hole: broadcast is async so
the tx is not in any mempool when we would sync, and the rejected loser
never enters a mempool at all, so a sync could never reserve its
inputs.

Holding the inputs at build time means a failed open now has to release
them again, otherwise the UTXOs are locked out of all future coin
selection. The FundingGenerationReady handler rolls back via cancel_tx
on any error before logging.

The regression test gives node A a single UTXO, opens one 0-conf
channel, and checks that the spendable balance drops without a sync and
that a second open is rejected with InsufficientFunds. Before the fix
that second open silently produced a phantom channel.
@amackillop amackillop requested a review from martinsaposnic June 8, 2026 13:21
@amackillop amackillop merged commit c4c384d into lsp-0.7.0 Jun 8, 2026
4 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant