From 28d99c4c0ffcfde63e5da87d3efff89890301afc Mon Sep 17 00:00:00 2001 From: tecnovert <tecnovert@tecnovert.net> Date: Mon, 6 Jan 2025 20:15:37 +0200 Subject: [PATCH] Fix recoverNoScriptTxnWithKey regression, add to more tests. --- basicswap/basicswap.py | 11 +++++- basicswap/interface/base.py | 5 +++ basicswap/interface/btc.py | 3 +- basicswap/interface/dcr/dcr.py | 3 +- basicswap/interface/ltc.py | 6 +-- basicswap/interface/nav.py | 3 +- basicswap/interface/part.py | 10 ++++- basicswap/protocols/xmr_swap_1.py | 28 ++++++++------ tests/basicswap/test_btc_xmr.py | 54 +++++++++++++++++++-------- tests/basicswap/test_partblind_xmr.py | 33 +++++----------- 10 files changed, 97 insertions(+), 59 deletions(-) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index e48c508..650010e 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -10327,7 +10327,14 @@ class BasicSwap(BaseApp): elif coin == Coins.NAV: rv["immature"] = walletinfo["immature_balance"] elif coin == Coins.LTC: - rv["mweb_address"] = self.getCachedStealthAddressForCoin(Coins.LTC_MWEB) + try: + rv["mweb_address"] = self.getCachedStealthAddressForCoin( + Coins.LTC_MWEB + ) + except Exception as e: + self.log.warning( + f"getCachedStealthAddressForCoin for {ci.coin_name()} failed with: {e}" + ) rv["mweb_balance"] = walletinfo["mweb_balance"] rv["mweb_pending"] = ( walletinfo["mweb_unconfirmed"] + walletinfo["mweb_immature"] diff --git a/basicswap/interface/base.py b/basicswap/interface/base.py index ec821c2..be31631 100644 --- a/basicswap/interface/base.py +++ b/basicswap/interface/base.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 tecnovert +# Copyright (c) 2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -53,6 +54,10 @@ class CoinInterface: self._mx_wallet = threading.Lock() self._altruistic = True + def interface_type(self) -> int: + # coin_type() returns the base coin type, interface_type() returns the coin+balance type. + return self.coin_type() + def setDefaults(self): self._unknown_wallet_seed = True self._restore_height = None diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 7f37245..4b8bc8b 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -1397,6 +1397,7 @@ class BTCInterface(Secp256k1Interface): cb_swap_value: int, b_fee: int, restore_height: int, + spend_actual_balance: bool = False, lock_tx_vout=None, ) -> bytes: self._log.info( diff --git a/basicswap/interface/dcr/dcr.py b/basicswap/interface/dcr/dcr.py index 6a8d436..614a73d 100644 --- a/basicswap/interface/dcr/dcr.py +++ b/basicswap/interface/dcr/dcr.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -1726,6 +1726,7 @@ class DCRInterface(Secp256k1Interface): cb_swap_value: int, b_fee: int, restore_height: int, + spend_actual_balance: bool = False, lock_tx_vout=None, ) -> bytes: self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex()) diff --git a/basicswap/interface/ltc.py b/basicswap/interface/ltc.py index a6c3ba2..33693f9 100644 --- a/basicswap/interface/ltc.py +++ b/basicswap/interface/ltc.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2023 tecnovert +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -52,7 +53,6 @@ class LTCInterface(BTCInterface): def getWalletInfo(self): rv = super(LTCInterface, self).getWalletInfo() - mweb_info = self.rpc_wallet_mweb("getwalletinfo") rv["mweb_balance"] = mweb_info["balance"] rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"] @@ -88,8 +88,8 @@ class LTCInterface(BTCInterface): class LTCInterfaceMWEB(LTCInterface): - @staticmethod - def coin_type(): + + def interface_type(self) -> int: return Coins.LTC_MWEB def __init__(self, coin_settings, network, swap_client=None): diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py index 4f131e5..ba8f190 100644 --- a/basicswap/interface/nav.py +++ b/basicswap/interface/nav.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2023 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -666,6 +666,7 @@ class NAVInterface(BTCInterface): cb_swap_value: int, b_fee: int, restore_height: int, + spend_actual_balance: bool = False, lock_tx_vout=None, ) -> bytes: self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex()) diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py index 894a371..df5c79f 100644 --- a/basicswap/interface/part.py +++ b/basicswap/interface/part.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -187,6 +187,10 @@ class PARTInterface(BTCInterface): class PARTInterfaceBlind(PARTInterface): + + def interface_type(self) -> int: + return Coins.PART_BLIND + @staticmethod def balance_type(): return BalanceTypes.BLIND @@ -1174,6 +1178,10 @@ class PARTInterfaceBlind(PARTInterface): class PARTInterfaceAnon(PARTInterface): + + def interface_type(self) -> int: + return Coins.PART_ANON + @staticmethod def balance_type(): return BalanceTypes.ANON diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index 9620346..e2a7276 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -15,9 +15,10 @@ from basicswap.chainparams import ( Coins, ) from basicswap.basicswap_util import ( + EventLogTypes, KeyTypes, SwapTypes, - EventLogTypes, + TxTypes, ) from . import ProtocolInterface from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG @@ -55,7 +56,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None): ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) # The no-script coin is always the follower - reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from) + reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) ci_follower = ci_from if reverse_bid else ci_to @@ -89,16 +90,20 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None): summed_pkbs = ci_follower.getPubkey(vkbs) if summed_pkbs != xmr_swap.pkbs: err_msg: str = "Summed key does not match expected wallet spend pubkey" - have_pk = summed_pkbs.hex() - expect_pk = xmr_swap.pkbs.hex() - self.log.error(f"{err_msg}. Got: {have_pk}, Expect: {expect_pk}") + self.log.error( + f"{err_msg}. Got: {summed_pkbs.hex()}, Expect: {xmr_swap.pkbs.hex()}" + ) raise ValueError(err_msg) - if ci_follower.coin_type() in (Coins.XMR, Coins.WOW): + coin_to: int = ci_follower.interface_type() + base_coin_to: int = ci_follower.coin_type() + if coin_to in (Coins.XMR, Coins.WOW): address_to = self.getCachedMainWalletAddress(ci_follower, use_cursor) + elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): + address_to = self.getCachedStealthAddressForCoin(base_coin_to, use_cursor) else: - address_to = self.getCachedStealthAddressForCoin( - ci_follower.coin_type(), use_cursor + address_to = self.getReceiveAddressFromPool( + base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor ) amount = bid.amount_to lock_tx_vout = bid.getLockTXBVout() @@ -145,10 +150,11 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer): was_sent: bool = bid.was_received if reverse_bid else bid.was_sent key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL + return ci_follower.encodeKey( swap_client.getPathKey( - ci_leader.coin_type(), - ci_follower.coin_type(), + ci_leader.interface_type(), + ci_follower.interface_type(), bid.created_at, xmr_swap.contract_count, key_type, diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index 69a7c36..02407a8 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -170,7 +170,11 @@ class TestFunctions(BaseTest): bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from) wait_for_bid( - test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED + test_delay_event, + swap_clients[id_offerer], + bid_id, + BidStates.BID_RECEIVED, + wait_for=(self.extra_wait_time + 40), ) bid0 = read_json_api(1800 + id_offerer, f"bids/{bid_id.hex()}") @@ -392,7 +396,7 @@ class TestFunctions(BaseTest): ) swap_clients[id_follower].ci( - coin_from if reverse_bid else coin_to + coin_to if reverse_bid else coin_from )._altruistic = with_mercy amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) @@ -415,17 +419,10 @@ class TestFunctions(BaseTest): test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED ) - debug_type = ( - DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2 - if with_mercy - else DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND - ) - swap_clients[id_leader].setBidDebugInd(bid_id, debug_type) - debug_type = ( - DebugTypes.BID_DONT_SPEND_COIN_B_LOCK - if with_mercy - else DebugTypes.BID_STOP_AFTER_COIN_A_LOCK + swap_clients[id_leader].setBidDebugInd( + bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2 ) + debug_type = DebugTypes.BID_DONT_SPEND_COIN_B_LOCK swap_clients[id_follower].setBidDebugInd(bid_id, debug_type) swap_clients[id_leader].setBidDebugInd( @@ -442,7 +439,7 @@ class TestFunctions(BaseTest): expect_state = ( (BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED, BidStates.SWAP_COMPLETED) if with_mercy - else BidStates.BID_STALLED_FOR_TEST + else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED) ) wait_for_bid( test_delay_event, @@ -473,6 +470,19 @@ class TestFunctions(BaseTest): wait_for_none_active(test_delay_event, 1800 + id_offerer) wait_for_none_active(test_delay_event, 1800 + id_bidder) + if with_mercy is False: + # Test manually redeeming the no-script lock tx + offerer_key = read_json_api( + 1800 + id_offerer, + "bids/{}".format(bid_id.hex()), + {"chainbkeysplit": True}, + )["splitkey"] + data = {"spendchainblocktx": True, "remote_key": offerer_key} + redeemed_txid = read_json_api( + 1800 + id_bidder, "bids/{}".format(bid_id.hex()), data + )["txid"] + assert len(redeemed_txid) == 64 + def do_test_04_follower_recover_b_lock_tx( self, coin_from, coin_to, lock_value: int = 32 ): @@ -1601,7 +1611,13 @@ class BasicSwapTest(TestFunctions): offer = swap_clients[1].getOffer(offer_id) bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) - wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED) + wait_for_bid( + test_delay_event, + swap_clients[2], + bid_id, + BidStates.BID_RECEIVED, + wait_for=(self.extra_wait_time + 40), + ) swap_clients[2].acceptBid(bid_id) wait_for_bid( @@ -1662,7 +1678,13 @@ class BasicSwapTest(TestFunctions): wait_for_offer(test_delay_event, swap_clients[1], offer_id) bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap) swap_clients[1].abandonBid(bid_id) - wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_ACCEPTED) + wait_for_bid( + test_delay_event, + swap_clients[0], + bid_id, + BidStates.BID_ACCEPTED, + wait_for=(self.extra_wait_time + 40), + ) try: swap_clients[0].setMockTimeOffset(7200) diff --git a/tests/basicswap/test_partblind_xmr.py b/tests/basicswap/test_partblind_xmr.py index 087fd49..52a63ce 100644 --- a/tests/basicswap/test_partblind_xmr.py +++ b/tests/basicswap/test_partblind_xmr.py @@ -2,16 +2,13 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021-2023 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import json import random import logging import unittest -from urllib import parse -from urllib.request import urlopen from basicswap.basicswap import ( Coins, @@ -27,7 +24,6 @@ from basicswap.util import ( format_amount, ) from tests.basicswap.util import ( - post_json_req, read_json_api, ) from tests.basicswap.common import ( @@ -61,9 +57,7 @@ class Test(BaseTest): "subfee": False, "type_to": "blind", } - json_rv = json.loads( - post_json_req("http://127.0.0.1:1800/json/wallets/part/withdraw", post_json) - ) + json_rv = read_json_api(1800, "wallets/part/withdraw", post_json) assert len(json_rv["txid"]) == 64 logging.info("Waiting for blind balance") @@ -388,7 +382,7 @@ class Test(BaseTest): swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK) swap_clients[0].setBidDebugInd( - bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND + bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2 ) swap_clients[0].acceptXmrBid(bid_id) @@ -397,7 +391,7 @@ class Test(BaseTest): test_delay_event, swap_clients[0], bid_id, - BidStates.BID_STALLED_FOR_TEST, + (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED), wait_for=180, ) wait_for_bid( @@ -422,21 +416,14 @@ class Test(BaseTest): wait_for_none_active(test_delay_event, 1800) wait_for_none_active(test_delay_event, 1801) - data = parse.urlencode({"chainbkeysplit": True}).encode() - offerer_key = json.loads( - urlopen( - "http://127.0.0.1:1800/json/bids/{}".format(bid_id.hex()), data=data - ).read() + offerer_key = read_json_api( + 1800, "bids/{}".format(bid_id.hex()), {"chainbkeysplit": True} )["splitkey"] - data = parse.urlencode( - {"spendchainblocktx": True, "remote_key": offerer_key} - ).encode() - redeemed_txid = json.loads( - urlopen( - "http://127.0.0.1:1801/json/bids/{}".format(bid_id.hex()), data=data - ).read() - )["txid"] + data = {"spendchainblocktx": True, "remote_key": offerer_key} + redeemed_txid = read_json_api(1801, "bids/{}".format(bid_id.hex()), data)[ + "txid" + ] assert len(redeemed_txid) == 64 def do_test_04_follower_recover_b_lock_tx(self, coin_from, coin_to):