diff --git a/electrum/commands.py b/electrum/commands.py index 77838f22e..2d55c4bb0 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1318,22 +1318,22 @@ class Commands: await sm.get_pairs() lightning_amount_sat = satoshis(lightning_amount) onchain_amount_sat = sm.get_recv_amount(lightning_amount_sat, is_reverse=True) - success = None + funding_txid = None elif lightning_amount == 'dryrun': await sm.get_pairs() onchain_amount_sat = satoshis(onchain_amount) lightning_amount_sat = sm.get_send_amount(onchain_amount_sat, is_reverse=True) - success = None + funding_txid = None else: lightning_amount_sat = satoshis(lightning_amount) claim_fee = sm.get_claim_fee() onchain_amount_sat = satoshis(onchain_amount) + claim_fee - success = await wallet.lnworker.swap_manager.reverse_swap( + funding_txid = await wallet.lnworker.swap_manager.reverse_swap( lightning_amount_sat=lightning_amount_sat, expected_onchain_amount_sat=onchain_amount_sat, ) return { - 'success': success, + 'funding_txid': funding_txid, 'lightning_amount': format_satoshis(lightning_amount_sat), 'onchain_amount': format_satoshis(onchain_amount_sat), } diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 33acb0d00..f928bd6e2 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -2704,3 +2704,8 @@ class LNWallet(LNWorker): self._channel_backups[bfh(channel_id)] = cb util.trigger_callback('channels_updated', self.wallet) self.lnwatcher.add_channel(cb.funding_outpoint.to_str(), cb.get_funding_address()) + + def fail_trampoline_forwarding(self, payment_key): + """ use this to fail htlcs received for hold invoices""" + e = OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'') + self.trampoline_forwarding_failures[payment_key] = e diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 92ed68396..0ccfafb14 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -967,6 +967,7 @@ class SimpleConfig(Logger): SWAPSERVER_URL_MAINNET = ConfigVar('swapserver_url_mainnet', default='https://swaps.electrum.org/api', type_=str) SWAPSERVER_URL_TESTNET = ConfigVar('swapserver_url_testnet', default='https://swaps.electrum.org/testnet', type_=str) SWAPSERVER_URL_REGTEST = ConfigVar('swapserver_url_regtest', default='http://localhost:5455/api', type_=str) + TEST_SWAPSERVER_REFUND = ConfigVar('test_swapserver_refund', default=False, type_=bool) # connect to remote WT WATCHTOWER_CLIENT_ENABLED = ConfigVar('use_watchtower', default=False, type_=bool) WATCHTOWER_CLIENT_URL = ConfigVar('watchtower_url', default=None, type_=str) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 47ef82b54..122b2e07f 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -232,11 +232,18 @@ class SwapManager(Logger): # tx = self.lnwatcher.adb.get_transaction(txin.spent_txid) preimage = tx.inputs()[0].witness_elements()[1] - assert swap.payment_hash == sha256(preimage) - swap.preimage = preimage - self.logger.info(f'found preimage: {preimage.hex()}') - self.lnworker.preimages[swap.payment_hash.hex()] = preimage.hex() - # note: we must check the payment secret before we broadcast the funding tx + if sha256(preimage) == swap.payment_hash: + swap.preimage = preimage + self.logger.info(f'found preimage: {preimage.hex()}') + self.lnworker.preimages[swap.payment_hash.hex()] = preimage.hex() + # note: we must check the payment secret before we broadcast the funding tx + else: + # refund tx + if spent_height > 0: + self.logger.info(f'found confirmed refund') + payment_secret = self.lnworker.get_payment_secret(swap.payment_hash) + payment_key = swap.payment_hash + payment_secret + self.lnworker.fail_trampoline_forwarding(payment_key) if spent_height > 0: if current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY: @@ -257,6 +264,8 @@ class SwapManager(Logger): if swap.is_reverse and swap.preimage is None: self.logger.info('preimage not available yet') continue + if swap.is_reverse and self.network.config.TEST_SWAPSERVER_REFUND: + continue try: tx = self._create_and_sign_claim_tx(txin=txin, swap=swap, config=self.wallet.config) except BelowDustLimit: @@ -586,13 +595,12 @@ class SwapManager(Logger): asyncio.ensure_future(self.lnworker.pay_invoice(fee_invoice, attempts=10)) # we return if we detect funding async def wait_for_funding(swap): - while swap.spending_txid is None: + while swap.funding_txid is None: await asyncio.sleep(1) # initiate main payment tasks = [asyncio.create_task(self.lnworker.pay_invoice(invoice, attempts=10, channels=channels)), asyncio.create_task(wait_for_funding(swap))] await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - success = swap.spending_txid is not None - return success + return swap.funding_txid def _add_or_reindex_swap(self, swap: SwapData) -> None: if swap.payment_hash.hex() not in self.swaps: diff --git a/electrum/tests/regtest.py b/electrum/tests/regtest.py index 090425c60..263b79663 100644 --- a/electrum/tests/regtest.py +++ b/electrum/tests/regtest.py @@ -47,8 +47,11 @@ class TestLightningAB(TestLightning): def test_collaborative_close(self): self.run_shell(['collaborative_close']) - def test_submarine_swap(self): - self.run_shell(['reverse_swap']) + def test_swapserver_success(self): + self.run_shell(['swapserver_success']) + + def test_swapserver_refund(self): + self.run_shell(['swapserver_refund']) def test_backup(self): self.run_shell(['backup']) diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh index 288127816..363d7c585 100755 --- a/electrum/tests/regtest/regtest.sh +++ b/electrum/tests/regtest/regtest.sh @@ -15,6 +15,19 @@ function new_blocks() $bitcoin_cli generatetoaddress $1 $($bitcoin_cli getnewaddress) > /dev/null } +function wait_until_htlcs_settled() +{ + msg="wait until $1's local_unsettled_sent is zero" + cmd="./run_electrum --regtest -D /tmp/$1" + while unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent') && [ $unsettled != "0" ]; do + sleep 1 + msg="$msg." + printf "$msg\r" + done + printf "\n" +} + + function wait_for_balance() { msg="wait until $1's balance reaches $2" @@ -171,7 +184,7 @@ if [[ $1 == "collaborative_close" ]]; then fi -if [[ $1 == "reverse_swap" ]]; then +if [[ $1 == "swapserver_success" ]]; then wait_for_balance alice 1 echo "alice opens channel" bob_node=$($bob nodeid) @@ -180,12 +193,34 @@ if [[ $1 == "reverse_swap" ]]; then wait_until_channel_open alice echo "alice initiates swap" dryrun=$($alice reverse_swap 0.02 dryrun) - echo $dryrun | jq onchain_amount=$(echo $dryrun| jq -r ".onchain_amount") - $alice reverse_swap 0.02 $onchain_amount + swap=$($alice reverse_swap 0.02 $onchain_amount) + echo $swap | jq + funding_txid=$(echo $swap| jq -r ".funding_txid") new_blocks 1 - sleep 1 + wait_until_spent $funding_txid 0 + wait_until_htlcs_settled alice +fi + + +if [[ $1 == "swapserver_refund" ]]; then + $alice setconfig test_swapserver_refund true + wait_for_balance alice 1 + echo "alice opens channel" + bob_node=$($bob nodeid) + channel=$($alice open_channel $bob_node 0.15) + new_blocks 3 + wait_until_channel_open alice + echo "alice initiates swap" + dryrun=$($alice reverse_swap 0.02 dryrun) + onchain_amount=$(echo $dryrun| jq -r ".onchain_amount") + swap=$($alice reverse_swap 0.02 $onchain_amount) + echo $swap | jq + funding_txid=$(echo $swap| jq -r ".funding_txid") + new_blocks 140 + wait_until_spent $funding_txid 0 new_blocks 1 + wait_until_htlcs_settled alice fi