Prevent swap-out prepayment loss on opening tx failure - #452
Prevent swap-out prepayment loss on opening tx failure#452YusukeShimizu wants to merge 8 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.
The original Liquid failure in issue ElementsProject#324 involved an encrypted maker wallet that remained visible to balance checks but could not sign the opening transaction. Existing integration coverage only exercised Bitcoin coin-selection failures. Add a CLN/CLN Liquid scenario that encrypts and locks the maker wallet, then verifies that the swap cancels before the taker pays the fee invoice. Include the scenario in the misc_2 CI shard.
|
I just tested this on LND v0.21.2-beta (latest version) and it seems to be working as expected. I used a setup where a node had two 0.02 BTC utxo's. One confirmed and the other unconfirmed. On the master version of peerswap I tried to initiate a 0.04 swapout with this test node. This failed but I still paid the prepayment: I then switched to the pr branch of peerswap on both nodes and tried the same thing: No prepayment was sent this time and the swap request failed cleanly. I then tried another scenario when I manually locked the confirmed utxo with So to summarize, I tested to two most common scenarios and in both cases the result was as expected. Nice 👍 |
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.