Prevent swap-out prepayment loss on opening tx failure#452
Draft
YusukeShimizu wants to merge 7 commits into
Draft
Prevent swap-out prepayment loss on opening tx failure#452YusukeShimizu wants to merge 7 commits into
YusukeShimizu wants to merge 7 commits into
Conversation
A peerswap maker only checks its flat onchain balance before sending the fee invoice for a swap out. A locked wallet or unspendable coins only surface when the opening transaction is funded, after the taker has already paid the prepayment (issue ElementsProject#324). Add PrecheckTransaction to the liquid wallet interface. The Elements implementation funds, blinds and signs a throwaway transaction without broadcasting it; fundrawtransaction does not lock utxos, so nothing needs to be released. The LWK implementation builds and signs the PSET and skips the broadcast. The tx template construction and the fund-and-finalize steps are extracted into helpers shared with the broadcast path.
The swap layer needs a wallet-level dry run of the opening transaction so a maker can detect wallet problems before the taker pays the fee invoice (issue ElementsProject#324). Add PrecheckOpeningTransaction to LiquidOnChain, which derives the confidential opening address and delegates to the liquid wallet's PrecheckTransaction. The address derivation is extracted into a helper shared with CreateOpeningTransaction.
A maker running lnd only notices funding problems such as insufficient spendable coins or a failing signer when the opening transaction is created, after the taker has already paid the fee invoice (issue ElementsProject#324). Add PrecheckOpeningTransaction, which funds and finalizes the same psbt as the real opening transaction but never publishes it. Utxos leased by FundPsbt are released again afterwards; a failed release is only logged since the lease expires on its own.
A maker running cln only notices funding problems such as insufficient spendable coins when the opening transaction is prepared, after the taker has already paid the fee invoice (issue ElementsProject#324). Add PrecheckOpeningTransaction, which runs txprepare with the same opening address and amount as the real opening transaction and then releases the input reservation with txdiscard. A failed discard is only logged since the reservation expires on its own.
A swap-out maker sends the fee invoice after checking only its flat onchain balance. When its wallet is locked or coin selection fails, the opening transaction fails after the taker paid the prepayment, and the taker loses that payment (issue ElementsProject#324). Require PrecheckOpeningTransaction from the onchain wallet and run it in CreateSwapOutFromRequestAction before the fee invoice is created. The dry run funds and signs a throwaway opening transaction with a placeholder payment hash, since the claim preimage does not exist yet; on failure the swap is canceled before the taker pays anything.
When a swap-out peer cancels after the fee invoice was paid but before the opening transaction is broadcast, the sender loses the prepayment. Repeated swap attempts against such a peer, e.g. from an automated job, lose the prepayment on every attempt (issue ElementsProject#324). Wrap the swap-out sender's canceled state with an action that adds the peer to the suspicious peer list iff the prepayment was paid, no opening transaction was received, and the cancel came from the peer. Outgoing swaps to suspicious peers are already refused, so at most one prepayment is lost per peer; removesuspiciouspeer undoes the entry.
The opening transaction precheck on the maker side only had unit coverage with a stubbed wallet. A regression in the real wallet integration (coin selection, input release) would go unnoticed by the existing e2e suites, which always run with spendable maker funds. Add cln-cln and lnd-lnd swap-out tests that drain the maker's coins into an unconfirmed change output, so the flat balance check passes while funding the opening transaction fails. They assert that both sides cancel with the precheck reason and that the taker never paid the fee invoice. The tests are wired into the misc_2 CI shard.
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.
Fixes #324
In a swap out, the taker pays the fee invoice (prepayment) before the maker constructs the opening transaction. If the maker's wallet cannot fund or sign the transaction at that point (locked wallet,
insufficient funds to fund PSBT, unspendable coins), the swap is canceled after the prepayment was already paid, and the taker loses it.Maker side: dry-run precheck before sending the fee invoice
When a swap-out request is received, the maker now funds and signs a throwaway opening transaction — without broadcasting it — before creating the fee invoice. If this fails, the swap is canceled before the taker pays anything. Implemented for all wallet backends:
FundPsbt+FinalizePsbt; utxo leases are released afterwardstxprepare+txdiscardfundrawtransaction+ blind + sign (utxos are not locked)testmempoolaccept(the second proposal in the issue) was left out: LND and CLN expose no equivalent API, and the fund+sign dry run already catches the failure classes reported in the issue.Taker side: flag peers after a prepayment loss
If a peer cancels after the fee invoice was paid but before the opening transaction was broadcast — the exact loss scenario of the issue — the peer is added to the suspicious peer list. Since outgoing swaps to suspicious peers are already refused, an automated setup loses at most one prepayment per misbehaving peer instead of one per attempt.
removesuspiciouspeerundoes the entry.