Repository navigation
fix(wallet): reserve a funding transaction's inputs until it is broadcast or discarded - #2
Merged
Conversation
…cast or discarded BDK keeps a coin selectable until a transaction spending it is in the wallet's graph. LDK broadcasts a channel's funding only after the peer's `funding_signed`, and the wallet only learns of it at the next sync, so a second open inside that window builds its funding from the same coins. That funding is a double-spend the network rejects, and its channel then waits for confirmations forever (upstream lightningdevkit#41). Seen on mainnet 2026-10-03: autopilot opened two channels 228 ms apart on a node with one coin. Tx 1bef01e0 confirmed; tx ce08a17f was never accepted. The second channel sat at 0/6 confirmations until it was force-closed by hand. - `create_funding_transaction` now applies the signed funding to the wallet as unconfirmed, so coin selection treats its inputs as spent. The swap funding path shares it. - `release_funding_transaction` evicts it again, and the event handler calls it when LDK discards the funding: on `DiscardFunding`, or when `funding_transaction_generated` refuses it. - A funding that was never broadcast is also released by the BROADCAST tail on rejection, and by any sync that finds it in neither the mempool nor the chain. Upstream fixes this in lightningdevkit#1037 with bdk_wallet 3's UTXO locks. This fork is on bdk_wallet 2, so it uses the wallet graph instead. Drop this commit for lightningdevkit#1037 when the fork moves to bdk_wallet 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two channel opens in quick succession double-spend the wallet's own coins, and the second channel then shows "pending" forever (upstream lightningdevkit/ldk-node#41, open since 2023).
funding_signed.FundingGenerationReadyinside that window builds its funding from the same coins.Seen on mainnet 2026-10-03 (node-3): autopilot opened two 12 080 sat channels 228 ms apart on a node holding one 28 000 sat coin.
1bef01e0…confirmed at block 969743.ce08a17f…was never accepted: mempool.space, blockstream and the node's own lookup all say "not found".Fix
Wallet::create_funding_transactionapplies the signed funding to the wallet as unconfirmed (apply_unconfirmed_txs), so coin selection treats its inputs as spent. The next open spends the change, or fails withInsufficientFunds, instead of producing a phantom channel. The swap funding path (create_swap_funding_tx) goes through the same function.Wallet::release_funding_transaction(txid)evicts it (apply_evicted_txs). The event handler calls it onEvent::DiscardFunding { funding_info: FundingInfo::Tx }, and whenfunding_transaction_generatedreturns an error.expected_spk_txidseviction).Upstream solves this in lightningdevkit/ldk-node#1037 with bdk_wallet 3 UTXO locks. This fork is on bdk_wallet 2, so it uses the wallet graph instead. Drop this commit in favour of lightningdevkit#1037 when the fork moves to bdk_wallet 3.
Tests
New tests in
wallet::chain_listen_tests, against a realWalletwith one confirmed coin:back_to_back_fundings_never_spend_the_same_coina_funding_too_big_for_what_is_left_fails_instead_of_double_spendingreleasing_a_discarded_funding_hands_its_coin_backreleasing_a_transaction_the_wallet_never_built_is_a_no_opResults:
cargo test --lib --features swaps,cycles,cbf: 285 passed, 0 failed (built against node-app-ldk-node'sCargo.lock).Release
Fast-forward
chain-dependent-tier(head of lamtuanvu#8) to this commit, the same way #1 landed95266ba. Then bumpnode-app-ldk-node's pin from95266batode0ce4c. Do not merge, rebase or force-push.🤖 Generated with Claude Code