Show error when auto-accepting a bid fails.

2024-05-20_merge
tecnovert 11 months ago
parent bcfd63037a
commit f5d4b8dc0d
No known key found for this signature in database
GPG Key ID: 8ED6D8750C4E3F93
  1. 35
      basicswap/basicswap.py
  2. 6
      basicswap/http_server.py
  3. 16
      basicswap/interface/btc.py
  4. 13
      basicswap/interface/part.py
  5. 5
      basicswap/js_server.py
  6. 63
      tests/basicswap/test_btc_xmr.py
  7. 3
      tests/basicswap/test_run.py
  8. 4
      tests/basicswap/test_xmr.py

@ -2432,7 +2432,6 @@ class BasicSwap(BaseApp):
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from)
if reverse_bid: if reverse_bid:
return self.acceptADSReverseBid(bid_id) return self.acceptADSReverseBid(bid_id)
return self.acceptXmrBid(bid_id) return self.acceptXmrBid(bid_id)
if bid.contract_count is None: if bid.contract_count is None:
@ -4301,23 +4300,28 @@ class BasicSwap(BaseApp):
self.closeSession(session) self.closeSession(session)
def countQueuedActions(self, session, bid_id: bytes, action_type) -> int: def countQueuedActions(self, session, bid_id: bytes, action_type) -> int:
q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.linked_id == bid_id, Action.action_type == int(action_type))) q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.linked_id == bid_id))
if action_type is not None:
q.filter(Action.action_type == int(action_type))
return q.count() return q.count()
def checkQueuedActions(self) -> None: def checkQueuedActions(self) -> None:
self.mxDB.acquire() self.mxDB.acquire()
now: int = self.getTime() now: int = self.getTime()
session = None session = None
reload_in_progress = False reload_in_progress: bool = False
try: try:
session = scoped_session(self.session_factory) session = scoped_session(self.session_factory)
q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.trigger_at <= now)) q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.trigger_at <= now))
for row in q: for row in q:
accepting_bid: bool = False
try: try:
if row.action_type == ActionTypes.ACCEPT_BID: if row.action_type == ActionTypes.ACCEPT_BID:
accepting_bid = True
self.acceptBid(row.linked_id) self.acceptBid(row.linked_id)
elif row.action_type == ActionTypes.ACCEPT_XMR_BID: elif row.action_type == ActionTypes.ACCEPT_XMR_BID:
accepting_bid = True
self.acceptXmrBid(row.linked_id) self.acceptXmrBid(row.linked_id)
elif row.action_type == ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A: elif row.action_type == ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A:
self.sendXmrBidTxnSigsFtoL(row.linked_id, session) self.sendXmrBidTxnSigsFtoL(row.linked_id, session)
@ -4338,11 +4342,34 @@ class BasicSwap(BaseApp):
elif row.action_type == ActionTypes.REDEEM_ITX: elif row.action_type == ActionTypes.REDEEM_ITX:
atomic_swap_1.redeemITx(self, row.linked_id, session) atomic_swap_1.redeemITx(self, row.linked_id, session)
elif row.action_type == ActionTypes.ACCEPT_AS_REV_BID: elif row.action_type == ActionTypes.ACCEPT_AS_REV_BID:
accepting_bid = True
self.acceptADSReverseBid(row.linked_id) self.acceptADSReverseBid(row.linked_id)
else: else:
self.log.warning('Unknown event type: %d', row.event_type) self.log.warning('Unknown event type: %d', row.event_type)
except Exception as ex: except Exception as ex:
self.logException(f'checkQueuedActions failed: {ex}') err_msg = f'checkQueuedActions failed: {ex}'
self.logException(err_msg)
bid_id = row.linked_id
# Failing to accept a bid should not set an error state as the bid has not begun yet
if accepting_bid:
self.logEvent(Concepts.BID,
bid_id,
EventLogTypes.ERROR,
err_msg,
session)
# If delaying with no (further) queued actions reset state
if self.countQueuedActions(session, bid_id, None) < 2:
bid = self.getBid(bid_id, session)
if bid and bid.state == BidStates.SWAP_DELAYING:
bid.setState(BidStates.BID_RECEIVED)
self.saveBidInSession(bid_id, bid, session)
else:
bid = self.getBid(bid_id, session)
if bid:
bid.setState(BidStates.BID_ERROR, err_msg)
self.saveBidInSession(bid_id, bid, session)
if self.debug: if self.debug:
session.execute('UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now', {'now': now}) session.execute('UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now', {'now': now})

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2023 tecnovert # Copyright (c) 2019-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -327,8 +327,12 @@ class HttpHandler(BaseHTTPRequestHandler):
template = env.get_template('rpc.html') template = env.get_template('rpc.html')
coins = listAvailableCoins(swap_client, with_variants=False) coins = listAvailableCoins(swap_client, with_variants=False)
with_xmr: bool = any(c[0] == Coins.XMR for c in coins)
coins = [c for c in coins if c[0] != Coins.XMR] coins = [c for c in coins if c[0] != Coins.XMR]
if any(c[0] == Coins.LTC for c in coins):
coins.append((-5, 'Litecoin MWEB Wallet')) coins.append((-5, 'Litecoin MWEB Wallet'))
if with_xmr:
coins.append((-2, 'Monero')) coins.append((-2, 'Monero'))
coins.append((-3, 'Monero JSON')) coins.append((-3, 'Monero JSON'))
coins.append((-4, 'Monero Wallet')) coins.append((-4, 'Monero Wallet'))

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert # Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -1375,6 +1375,20 @@ class BTCInterface(CoinInterface):
for u in unspent: for u in unspent:
if u['spendable'] is not True: if u['spendable'] is not True:
continue continue
if 'address' not in u:
continue
if 'desc' in u:
desc = u['desc']
if self.using_segwit:
if self.use_p2shp2wsh():
if not desc.startswith('sh(wpkh'):
continue
else:
if not desc.startswith('wpkh'):
continue
else:
if not desc.startswith('pkh'):
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
return unspent_addr return unspent_addr

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert # Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -141,6 +141,17 @@ class PARTInterface(BTCInterface):
tx_vsize += 204 if redeem else 187 tx_vsize += 204 if redeem else 187
return tx_vsize return tx_vsize
def getUnspentsByAddr(self):
unspent_addr = dict()
unspent = self.rpc_wallet('listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
if 'address' not in u:
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
return unspent_addr
class PARTInterfaceBlind(PARTInterface): class PARTInterfaceBlind(PARTInterface):
@staticmethod @staticmethod

@ -84,11 +84,14 @@ def js_coins(self, url_split, post_string, is_json) -> bytes:
for coin in Coins: for coin in Coins:
cc = swap_client.coin_clients[coin] cc = swap_client.coin_clients[coin]
coin_chainparams = chainparams[cc['coin']] coin_chainparams = chainparams[cc['coin']]
coin_active: bool = False if cc['connection_type'] == 'none' else True
if coin == Coins.LTC_MWEB:
coin_active = False
entry = { entry = {
'id': int(coin), 'id': int(coin),
'ticker': coin_chainparams['ticker'], 'ticker': coin_chainparams['ticker'],
'name': getCoinName(coin), 'name': getCoinName(coin),
'active': False if cc['connection_type'] == 'none' else True, 'active': coin_active,
'decimal_places': coin_chainparams['decimal_places'], 'decimal_places': coin_chainparams['decimal_places'],
} }
if coin == Coins.PART_ANON: if coin == Coins.PART_ANON:

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2021-2023 tecnovert # Copyright (c) 2021-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -9,6 +9,9 @@ import random
import logging import logging
import unittest import unittest
from basicswap.db import (
Concepts,
)
from basicswap.basicswap import ( from basicswap.basicswap import (
Coins, Coins,
SwapTypes, SwapTypes,
@ -17,6 +20,7 @@ from basicswap.basicswap import (
) )
from basicswap.basicswap_util import ( from basicswap.basicswap_util import (
TxLockTypes, TxLockTypes,
EventLogTypes,
) )
from basicswap.util import ( from basicswap.util import (
make_int, make_int,
@ -28,6 +32,7 @@ from tests.basicswap.util import (
) )
from tests.basicswap.common import ( from tests.basicswap.common import (
wait_for_bid, wait_for_bid,
wait_for_event,
wait_for_offer, wait_for_offer,
wait_for_balance, wait_for_balance,
wait_for_unspent, wait_for_unspent,
@ -59,6 +64,7 @@ class TestFunctions(BaseTest):
node_a_id = 0 node_a_id = 0
node_b_id = 1 node_b_id = 1
node_c_id = 2
def callnoderpc(self, method, params=[], wallet=None, node_id=0): def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port) return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
@ -166,9 +172,6 @@ class TestFunctions(BaseTest):
post_json = {'with_extra_info': True} post_json = {'with_extra_info': True}
offer0 = read_json_api(1800 + id_offerer, f'offers/{offer_id.hex()}', post_json)[0] offer0 = read_json_api(1800 + id_offerer, f'offers/{offer_id.hex()}', post_json)[0]
offer1 = read_json_api(1800 + id_offerer, f'offers/{offer_id.hex()}', post_json)[0] offer1 = read_json_api(1800 + id_offerer, f'offers/{offer_id.hex()}', post_json)[0]
from basicswap.util import dumpj
logging.info('offer0 {} '.format(dumpj(offer0)))
logging.info('offer1 {} '.format(dumpj(offer1)))
assert ('lock_time_1' in offer0) assert ('lock_time_1' in offer0)
assert ('lock_time_1' in offer1) assert ('lock_time_1' in offer1)
@ -193,9 +196,7 @@ class TestFunctions(BaseTest):
found: bool = False found: bool = False
bids0 = read_json_api(1800 + id_offerer, 'bids') bids0 = read_json_api(1800 + id_offerer, 'bids')
logging.info('bids0 {} '.format(bids0))
for bid in bids0: for bid in bids0:
logging.info('bid {} '.format(bid))
if bid['bid_id'] != bid_id.hex(): if bid['bid_id'] != bid_id.hex():
continue continue
assert (bid['amount_from'] == bid1['amt_from']) assert (bid['amount_from'] == bid1['amt_from'])
@ -239,8 +240,6 @@ class TestFunctions(BaseTest):
post_json = {'show_extra': True} post_json = {'show_extra': True}
bid0 = read_json_api(1800 + id_offerer, f'bids/{bid_id.hex()}', post_json) bid0 = read_json_api(1800 + id_offerer, f'bids/{bid_id.hex()}', post_json)
bid1 = read_json_api(1800 + id_bidder, f'bids/{bid_id.hex()}', post_json) bid1 = read_json_api(1800 + id_bidder, f'bids/{bid_id.hex()}', post_json)
logging.info('bid0 {} '.format(dumpj(bid0)))
logging.info('bid1 {} '.format(dumpj(bid1)))
chain_a_lock_txid = None chain_a_lock_txid = None
chain_b_lock_txid = None chain_b_lock_txid = None
@ -417,20 +416,19 @@ class TestFunctions(BaseTest):
def do_test_05_self_bid(self, coin_from, coin_to): def do_test_05_self_bid(self, coin_from, coin_to):
logging.info('---------- Test {} to {} same client'.format(coin_from.name, coin_to.name)) logging.info('---------- Test {} to {} same client'.format(coin_from.name, coin_to.name))
id_offerer: int = self.node_a_id id_both: int = self.node_b_id
id_bidder: int = self.node_b_id
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci_from = swap_clients[id_offerer].ci(coin_from) ci_from = swap_clients[id_both].ci(coin_from)
ci_to = swap_clients[id_offerer].ci(coin_to) ci_to = swap_clients[id_both].ci(coin_to)
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[id_bidder].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True) offer_id = swap_clients[id_both].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap) bid_id = swap_clients[id_both].postXmrBid(offer_id, amt_swap)
wait_for_bid(test_delay_event, swap_clients[id_bidder], bid_id, BidStates.SWAP_COMPLETED, wait_for=(self.extra_wait_time + 180)) wait_for_bid(test_delay_event, swap_clients[id_both], bid_id, BidStates.SWAP_COMPLETED, wait_for=(self.extra_wait_time + 180))
class BasicSwapTest(TestFunctions): class BasicSwapTest(TestFunctions):
@ -689,7 +687,7 @@ class BasicSwapTest(TestFunctions):
utxo = unspents[0] utxo = unspents[0]
txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']]) txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']])
if 'address' in txout: if 'address' in txout['scriptPubKey']:
assert (addr_1 == txout['scriptPubKey']['address']) assert (addr_1 == txout['scriptPubKey']['address'])
else: else:
assert (addr_1 in txout['scriptPubKey']['addresses']) assert (addr_1 in txout['scriptPubKey']['addresses'])
@ -1000,6 +998,8 @@ class BasicSwapTest(TestFunctions):
self.do_test_05_self_bid(self.test_coin_from, Coins.PART) self.do_test_05_self_bid(self.test_coin_from, Coins.PART)
def test_05_self_bid_from_part(self): def test_05_self_bid_from_part(self):
if not self.has_segwit:
return
self.do_test_05_self_bid(Coins.PART, self.test_coin_from) self.do_test_05_self_bid(Coins.PART, self.test_coin_from)
def test_05_self_bid_rev(self): def test_05_self_bid_rev(self):
@ -1094,6 +1094,37 @@ class BasicSwapTest(TestFunctions):
swap_clients[0].check_expired_seconds = old_check_expired_seconds swap_clients[0].check_expired_seconds = old_check_expired_seconds
swap_clients[0].setMockTimeOffset(0) swap_clients[0].setMockTimeOffset(0)
def test_08_insufficient_funds(self):
tla_from = self.test_coin_from.name
logging.info('---------- Test {} Insufficient Funds'.format(tla_from))
swap_clients = self.swap_clients
coin_from = self.test_coin_from
coin_to = Coins.XMR
self.prepare_balance(self.test_coin_from, 10.0, 1802, 1800)
id_offerer: int = self.node_c_id
id_bidder: int = self.node_b_id
swap_clients = self.swap_clients
ci_from = swap_clients[id_offerer].ci(coin_from)
ci_to = swap_clients[id_bidder].ci(coin_to)
js_0 = read_json_api(1800 + id_offerer, 'wallets')
node0_from_before: float = self.getBalance(js_0, coin_from)
amt_swap: int = ci_from.make_int(node0_from_before, r=1)
rate_swap: int = ci_to.make_int(2.0, r=1)
offer_id = swap_clients[id_offerer].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
event = wait_for_event(test_delay_event, swap_clients[id_offerer], Concepts.BID, bid_id, event_type=EventLogTypes.ERROR, wait_for=60)
assert ('Insufficient funds' in event.event_msg)
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED, wait_for=20)
class TestBTC(BasicSwapTest): class TestBTC(BasicSwapTest):
__test__ = True __test__ = True

@ -112,7 +112,6 @@ class Test(BaseTest):
def test_001_js_coins(self): def test_001_js_coins(self):
js_coins = read_json_api(1800, 'coins') js_coins = read_json_api(1800, 'coins')
for c in Coins: for c in Coins:
coin = next((x for x in js_coins if x['id'] == int(c)), None) coin = next((x for x in js_coins if x['id'] == int(c)), None)
if c in (Coins.PART, Coins.BTC, Coins.LTC, Coins.PART_ANON, Coins.PART_BLIND): if c in (Coins.PART, Coins.BTC, Coins.LTC, Coins.PART_ANON, Coins.PART_BLIND):
@ -130,7 +129,7 @@ class Test(BaseTest):
assert ('coingecko' in rv) assert ('coingecko' in rv)
rv = read_json_api(1800, 'rateslist?from=PART&to=BTC') rv = read_json_api(1800, 'rateslist?from=PART&to=BTC')
assert len(rv) == 2 assert len(rv) == 1
def test_003_api(self): def test_003_api(self):
logging.info('---------- Test API') logging.info('---------- Test API')

@ -204,9 +204,9 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_c
'check_events_seconds': 1, 'check_events_seconds': 1,
'check_xmr_swaps_seconds': 1, 'check_xmr_swaps_seconds': 1,
'min_delay_event': 1, 'min_delay_event': 1,
'max_delay_event': 5, 'max_delay_event': 4,
'min_delay_event_short': 1, 'min_delay_event_short': 1,
'max_delay_event_short': 5, 'max_delay_event_short': 3,
'min_delay_retry': 2, 'min_delay_retry': 2,
'max_delay_retry': 10, 'max_delay_retry': 10,
'debug_ui': True, 'debug_ui': True,

Loading…
Cancel
Save