diff --git a/basicswap/__init__.py b/basicswap/__init__.py index ff4df57..8b91f10 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.11.53" +__version__ = "0.11.54" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index d7db78c..1385117 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -3065,6 +3065,33 @@ class BasicSwap(BaseApp): return sum_unspent return None + def findTxB(self, ci_to, xmr_swap, bid, session) -> bool: + bid_changed = False + # Have to use findTxB instead of relying on the first seen height to detect chain reorgs + found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, bid.chain_b_height_start, bid.was_sent) + + if isinstance(found_tx, int) and found_tx == -1: + if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1: + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_INVALID, 'Detected invalid lock tx B', session) + bid_changed = True + elif found_tx is not None: + if bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height: + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session) + if bid.xmr_b_lock_tx is None: + self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name())) + xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid']) + bid.xmr_b_lock_tx = SwapTx( + bid_id=bid.bid_id, + tx_type=TxTypes.XMR_SWAP_B_LOCK, + txid=xmr_swap.b_lock_tx_id, + chain_height=found_tx['height'], + ) + bid_changed = True + else: + bid.xmr_b_lock_tx.chain_height = found_tx['height'] + bid_changed = True + return bid_changed + def checkXmrBidState(self, bid_id, bid, offer): rv = False @@ -3176,6 +3203,13 @@ class BasicSwap(BaseApp): rv = True # Remove from swaps_in_progress elif state == BidStates.XMR_SWAP_FAILED_SWIPED: rv = True # Remove from swaps_in_progress + elif state == BidStates.XMR_SWAP_FAILED: + if bid.was_sent and bid.xmr_b_lock_tx: + if self.countQueuedActions(session, bid_id, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B) < 1: + delay = random.randrange(self.min_delay_event, self.max_delay_event) + self.log.info('Recovering xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) + self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) + session.commit() elif state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: if bid.xmr_a_lock_tx is None: return rv @@ -3217,33 +3251,7 @@ class BasicSwap(BaseApp): session.commit() elif state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: - if bid.was_sent and bid.xmr_b_lock_tx is None: - return rv - - bid_changed = False - # Have to use findTxB instead of relying on the first seen height to detect chain reorgs - found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, bid.chain_b_height_start, bid.was_sent) - - if isinstance(found_tx, int) and found_tx == -1: - if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_INVALID, 'Detected invalid lock tx B', session) - bid_changed = True - elif found_tx is not None: - if bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session) - if bid.xmr_b_lock_tx is None: - self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name())) - xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid']) - bid.xmr_b_lock_tx = SwapTx( - bid_id=bid_id, - tx_type=TxTypes.XMR_SWAP_B_LOCK, - txid=xmr_swap.b_lock_tx_id, - chain_height=found_tx['height'], - ) - bid_changed = True - else: - bid.xmr_b_lock_tx.chain_height = found_tx['height'] - bid_changed = True + bid_changed = self.findTxB(ci_to, xmr_swap, bid, session) if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.chain_height is not None and bid.xmr_b_lock_tx.chain_height > 0: chain_height = ci_to.getChainHeight() @@ -4715,6 +4723,14 @@ class BasicSwap(BaseApp): ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) + if self.findTxB(ci_to, xmr_swap, bid, session) is True: + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + return + + if bid.xmr_b_lock_tx: + self.log.warning('Coin B lock tx {} exists for xmr bid {}'.format(bid.xmr_b_lock_tx.b_lock_tx_id, bid_id.hex())) + return + if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_A_LOCK: self.log.debug('XMR bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) bid.setState(BidStates.BID_STALLED_FOR_TEST) @@ -4723,7 +4739,7 @@ class BasicSwap(BaseApp): return unlock_time = 0 - if bid.debug_ind == DebugTypes.CREATE_INVALID_COIN_B_LOCK: + if bid.debug_ind in (DebugTypes.CREATE_INVALID_COIN_B_LOCK, DebugTypes.B_LOCK_TX_MISSED_SEND): bid.amount_to -= int(bid.amount_to * 0.1) self.log.debug('XMR bid %s: Debug %d - Reducing lock b txn amount by 10%% to %s.', bid_id.hex(), bid.debug_ind, ci_to.format_amount(bid.amount_to)) self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) @@ -4731,8 +4747,13 @@ class BasicSwap(BaseApp): unlock_time = 10000 self.log.debug('XMR bid %s: Debug %d - Sending locked XMR.', bid_id.hex(), bid.debug_ind) self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + try: b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate, unlock_time=unlock_time) + if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND: + self.log.debug('XMR bid %s: Debug %d - Losing xmr lock tx %s.', bid_id.hex(), bid.debug_ind, b_lock_tx_id.hex()) + self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + raise TemporaryError('Fail for debug event') except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index 7cc1e94..8ce931f 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -189,6 +189,7 @@ class DebugTypes(IntEnum): DONT_SPEND_ITX = auto() SKIP_LOCK_TX_REFUND = auto() SEND_LOCKED_XMR = auto() + B_LOCK_TX_MISSED_SEND = auto() def strOfferState(state): @@ -322,8 +323,6 @@ def getLockName(lock_type): def describeEventEntry(event_type, event_msg): - if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH: - return 'Failed to publish lock tx B' if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH: return 'Failed to publish lock tx B' if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED: @@ -456,4 +455,6 @@ def isActiveBidState(state): return True if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: return True + if state == BidStates.XMR_SWAP_FAILED: + return True return False diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 6e293ff..19f0c8f 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -1186,7 +1186,7 @@ class BTCInterface(CoinInterface): return True if address_hash == pubkey_hash else False - def showLockTransfers(self, Kbv, Kbs): + def showLockTransfers(self, kbv, Kbs, restore_height): raise ValueError('Unimplemented') def getLockTxSwapOutputValue(self, bid, xmr_swap): diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py index 8312550..f064523 100644 --- a/basicswap/interface/dash.py +++ b/basicswap/interface/dash.py @@ -20,12 +20,18 @@ class DASHInterface(BTCInterface): def coin_type(): return Coins.DASH + def __init__(self, coin_settings, network, swap_client=None): + super().__init__(coin_settings, network, swap_client) + self._wallet_passphrase = '' + def seedToMnemonic(self, key): return Mnemonic('english').to_mnemonic(key) def initialiseWallet(self, key): words = self.seedToMnemonic(key) - self.rpc_callback('upgradetohd', [words, ]) + + mnemonic_passphrase = '' + self.rpc_callback('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase]) def decodeAddress(self, address): return decodeAddress(address)[1:] @@ -70,3 +76,12 @@ class DASHInterface(BTCInterface): return {'txid': txid_hex, 'amount': 0, 'height': block_height} return None + + def unlockWallet(self, password: str): + super().unlockWallet(password) + # Store password for initialiseWallet + self._wallet_passphrase = password + + def lockWallet(self): + super().lockWallet() + self._wallet_passphrase = '' diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 1a0da16..dc855ac 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -5,7 +5,6 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import os import json import logging @@ -100,8 +99,7 @@ class XMRInterface(CoinInterface): try: # Can't reopen the same wallet in windows, !is_keys_file_locked() - if os.name == 'nt': - self.rpc_wallet_cb('close_wallet') + self.rpc_wallet_cb('close_wallet') except Exception: pass self.rpc_wallet_cb('open_wallet', params) @@ -263,6 +261,7 @@ class XMRInterface(CoinInterface): def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes: with self._mx_wallet: self.openWallet(self._wallet_filename) + self.rpc_wallet_cb('refresh') shared_addr = xmr_util.encode_address(Kbv, Kbs) @@ -276,8 +275,8 @@ class XMRInterface(CoinInterface): if self._sc.debug: i = 0 while not self._sc.delay_event.is_set(): - params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } - rv = self.rpc_wallet_cb('get_transfers', params) + gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } + rv = self.rpc_wallet_cb('get_transfers', gt_params) self._log.debug('get_transfers {}'.format(dumpj(rv))) if 'pending' not in rv: break @@ -292,11 +291,6 @@ class XMRInterface(CoinInterface): Kbv = self.getPubkey(kbv) address_b58 = xmr_util.encode_address(Kbv, Kbs) - try: - self.rpc_wallet_cb('close_wallet') - except Exception as e: - self._log.warning('close_wallet failed %s', str(e)) - kbv_le = kbv[::-1] params = { 'restore_height': restore_height, @@ -328,10 +322,14 @@ class XMRInterface(CoinInterface): rv = None if 'transfers' in transfers: for transfer in transfers['transfers']: + # unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE if not transfer['unlocked']: - self._log.warning('Coin b lock txn is locked: {}'.format(transfer['tx_hash'])) - rv = -1 - continue + full_tx = self.rpc_wallet_cb('get_transfer_by_txid', {'txid': transfer['tx_hash']}) + unlock_time = full_tx['transfer']['unlock_time'] + if unlock_time != 0: + self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time)) + rv = -1 + continue if transfer['amount'] == cb_swap_value: return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} else: @@ -367,11 +365,6 @@ class XMRInterface(CoinInterface): Kbs = self.getPubkey(kbs) address_b58 = xmr_util.encode_address(Kbv, Kbs) - try: - self.rpc_wallet_cb('close_wallet') - except Exception as e: - self._log.warning('close_wallet failed %s', str(e)) - wallet_filename = address_b58 + '_spend' params = { @@ -446,16 +439,29 @@ class XMRInterface(CoinInterface): rv = self.rpc_wallet_cb('transfer', params) return rv['tx_hash'] - def showLockTransfers(self, Kbv, Kbs): + def showLockTransfers(self, kbv, Kbs, restore_height): with self._mx_wallet: try: + Kbv = self.getPubkey(kbv) address_b58 = xmr_util.encode_address(Kbv, Kbs) wallet_file = address_b58 + '_spend' try: self.openWallet(wallet_file) except Exception: wallet_file = address_b58 - self.openWallet(wallet_file) + try: + self.openWallet(wallet_file) + except Exception: + self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.') + kbv_le = kbv[::-1] + params = { + 'restore_height': restore_height, + 'filename': address_b58, + 'address': address_b58, + 'viewkey': b2h(kbv_le), + } + self.createWallet(params) + self.openWallet(address_b58) self.rpc_wallet_cb('refresh') diff --git a/basicswap/js_server.py b/basicswap/js_server.py index fbff5dc..05fe6a5 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -168,11 +168,11 @@ def js_offers(self, url_split, post_string, is_json, sent=False): filters['limit'] = int(get_data_entry(post_data, 'limit')) assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' - offers = self.server.swap_client.listOffers(sent, filters) + offers = swap_client.listOffers(sent, filters) rv = [] for o in offers: - ci_from = self.server.swap_client.ci(o.coin_from) - ci_to = self.server.swap_client.ci(o.coin_to) + ci_from = swap_client.ci(o.coin_from) + ci_to = swap_client.ci(o.coin_to) rv.append({ 'addr_from': o.addr_from, 'addr_to': o.addr_to, @@ -494,6 +494,10 @@ def js_lock(self, url_split, post_string, is_json): return bytes(json.dumps({'success': True}), 'UTF-8') +def js_404(self, url_split, post_string, is_json): + return bytes(json.dumps({'Error': 'path unknown'}), 'UTF-8') + + def js_help(self, url_split, post_string, is_json): # TODO: Add details and examples commands = [] @@ -528,5 +532,5 @@ pages = { def js_url_to_function(url_split): if len(url_split) > 2: - return pages.get(url_split[2], js_index) + return pages.get(url_split[2], js_404) return js_index diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index ffeaf59..b1a2e29 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -91,6 +91,24 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer): return ci_to.encodeKey(swap_client.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, key_type, True if offer.coin_to == Coins.XMR else False)) +def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): + ci_from = swap_client.ci(offer.coin_from) + ci_to = swap_client.ci(offer.coin_to) + + if bid.was_sent: + if xmr_swap.a_lock_refund_spend_tx: + af_lock_refund_spend_tx_sig = ci_from.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx) + kbsl = ci_from.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl) + return ci_to.encodeKey(kbsl) + else: + if xmr_swap.a_lock_spend_tx: + al_lock_spend_tx_sig = ci_from.extractLeaderSig(xmr_swap.a_lock_spend_tx) + kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf) + return ci_to.encodeKey(kbsf) + + return None + + class XmrSwapInterface(ProtocolInterface): swap_type = SwapTypes.XMR_SWAP diff --git a/basicswap/templates/bid.html b/basicswap/templates/bid.html index eda9f67..743a8a2 100644 --- a/basicswap/templates/bid.html +++ b/basicswap/templates/bid.html @@ -83,7 +83,7 @@ Swap
- {{ data.amt_to }} {{ data.ticker_to }} + {{ data.amt_to }} {{ data.ticker_to }} {{ data.amt_from }} {{ data.ticker_from }} @@ -95,7 +95,7 @@ Swap
- {{ data.amt_from }} {{ data.ticker_from }} + {{ data.amt_from }} {{ data.ticker_from }} {{ data.amt_to }} {{ data.ticker_to }} @@ -264,7 +264,7 @@
{{ s[0] | formatts }}
- + {% endfor %}
@@ -306,7 +306,7 @@
-
+
{% if edit_bid %} @@ -319,11 +319,11 @@ - + {% if data.debug_ui == true %} @@ -331,7 +331,7 @@
- +
- + - {% endif %} + {% endif %} + {% if data.xmr_b_half_privatekey_remote %} + + + + + {% endif %}
Debug Option:Swap
- {{ data.amt_to }} {{ data.ticker_to }} + {{ data.amt_to }} {{ data.ticker_to }} {{ data.amt_from }} {{ data.ticker_from }} @@ -305,10 +305,16 @@ {% endif %} {% if data.xmr_b_half_privatekey %}
Key Half:Key Half (WARNING key data!): {{ data.xmr_b_half_privatekey }}
Remote Key Half:{{ data.xmr_b_half_privatekey_remote }}
@@ -475,7 +481,7 @@ {% if data.debug_ui == true %} Debug Option - +
@@ -503,7 +509,7 @@
+ Submit Edit
{% else %} {% if data.show_bidder_seq_diagram %} @@ -547,7 +553,7 @@ {% endif %}
+ Edit bid
{% endif %} {% if data.was_received == 'True' %} @@ -572,4 +578,4 @@
{% include 'footer.html' %} - \ No newline at end of file + diff --git a/basicswap/ui/page_offers.py b/basicswap/ui/page_offers.py index bbfc40c..4b42c60 100644 --- a/basicswap/ui/page_offers.py +++ b/basicswap/ui/page_offers.py @@ -213,7 +213,16 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}): def postNewOfferFromParsed(swap_client, parsed_data): swap_type = SwapTypes.SELLER_FIRST - if parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON): + + if swap_type in parsed_data: + str_swap_type = parsed_data['swap_type'].lower() + if str_swap_type == 'seller_first': + swap_type = SwapTypes.SELLER_FIRST + elif str_swap_type == 'xmr_swap': + swap_type = SwapTypes.XMR_SWAP + else: + raise ValueError('Unknown swap type') + elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON): swap_type = SwapTypes.XMR_SWAP if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']: diff --git a/basicswap/ui/util.py b/basicswap/ui/util.py index a4131c2..9a11bb5 100644 --- a/basicswap/ui/util.py +++ b/basicswap/ui/util.py @@ -28,7 +28,7 @@ from basicswap.basicswap_util import ( getLastBidState, ) -from basicswap.protocols.xmr_swap_1 import getChainBSplitKey +from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSplitKey PAGE_LIMIT = 50 invalid_coins_from = (Coins.XMR, Coins.PART_ANON) @@ -302,12 +302,16 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b if swap_client.debug_ui: try: data['xmr_b_half_privatekey'] = getChainBSplitKey(swap_client, bid, xmr_swap, offer) + + remote_split_key = getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer) + if remote_split_key: + data['xmr_b_half_privatekey_remote'] = remote_split_key except Exception as e: swap_client.log.error(traceback.format_exc()) if show_lock_transfers: if xmr_swap.pkbs: - data['lock_transfers'] = json.dumps(ci_to.showLockTransfers(xmr_swap.pkbv, xmr_swap.pkbs), indent=4) + data['lock_transfers'] = json.dumps(ci_to.showLockTransfers(xmr_swap.vkbv, xmr_swap.pkbs, bid.chain_b_height_start), indent=4) else: data['lock_transfers'] = 'Shared address not yet known.' else: diff --git a/doc/release-notes.md b/doc/release-notes.md index b48903f..c8daaa6 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,6 +3,24 @@ ============== +0.0.54 +============== + +- If the XMR daemon is busy the wallet can fail a transfer, later sending the tx unknown to bsx. + - Check for existing transfers before trying to send the chain b lock tx. + - Check for transfers in XMR_SWAP_SCRIPT_COIN_LOCKED state when bid is sent. + - Continually try refund noscript lock tx in XMR_SWAP_FAILED state. +- showLockTransfers will attempt to create a wallet if none exists. +- tests: + - Add B_LOCK_TX_MISSED_SEND debug event and test. +- Store the Dash wallet password in memory for use in upgradetohd +- Remove false positive warning. Check for unlock_time transfer is not unlocked. +- ui: + - Add 'Remote Key Half' to Show More Info section (with debug_ui on) +- api: + - An unknown path will return an error instead of the default/index data. + + 0.0.32 ============== diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index d4af781..b18206f 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -1318,6 +1318,26 @@ class Test(BaseTest): assert (txin['txid'] == itx_after['vin'][i]['txid']) assert (txin['vout'] == itx_after['vin'][i]['vout']) + def test_15_missed_xmr_send(self): + logging.info('---------- Test PART to XMR B lock tx is lost') + swap_clients = self.swap_clients + + amt_swap = make_int(random.uniform(0.1, 10.0), scale=8, r=1) + rate_swap = make_int(random.uniform(2.0, 20.0), scale=12, r=1) + offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, + lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=28) + + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offer = swap_clients[1].getOffer(offer_id) + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.B_LOCK_TX_MISSED_SEND) + swap_clients[0].acceptXmrBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800, sent=True) + def test_98_withdraw_all(self): logging.info('---------- Test XMR withdrawal all') try: