diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 0d8c275..bf1b555 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -3545,10 +3545,6 @@ class BasicSwap(BaseApp): q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.linked_id == bid_id, Action.action_type == int(action_type))) return q.count() - def countQueuedAcceptActions(self, session, bid_id): - q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.linked_id == bid_id, sa.or_(Action.action_type == int(ActionTypes.ACCEPT_XMR_BID), Action.action_type == int(ActionTypes.ACCEPT_BID)))) - return q.count() - def checkQueuedActions(self): self.mxDB.acquire() now = int(time.time()) @@ -3798,25 +3794,19 @@ class BasicSwap(BaseApp): def getCompletedAndActiveBidsValue(self, offer, session): bids = [] total_value = 0 - q = session.execute('SELECT bid_id, amount, state FROM bids WHERE active_ind = 1 AND offer_id = x\'{}\''.format(offer.offer_id.hex())) + q = session.execute( + '''SELECT bid_id, amount, state FROM bids + JOIN bidstates ON bidstates.state_id = bids.state AND (bidstates.state_id = {1} OR bidstates.in_progress > 0) + WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' + UNION + SELECT bid_id, amount, state FROM bids + JOIN actions ON actions.linked_id = bids.bid_id AND actions.active_ind = 1 AND (actions.action_type = {2} OR actions.action_type = {3}) + WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' + '''.format(offer.offer_id.hex(), BidStates.SWAP_COMPLETED, ActionTypes.ACCEPT_XMR_BID, ActionTypes.ACCEPT_BID)) for row in q: bid_id, amount, state = row - if state == BidStates.SWAP_COMPLETED: - bids.append((bid_id, amount, state, 1)) - total_value += amount - continue - if state == BidStates.BID_ACCEPTED: - bids.append((bid_id, amount, state, 2)) - total_value += amount - continue - if bid_id in self.swaps_in_progress: - bids.append((bid_id, amount, state, 3)) - total_value += amount - continue - if self.countQueuedAcceptActions(session, bid_id) > 0: - bids.append((bid_id, amount, state, 4)) - total_value += amount - continue + bids.append((bid_id, amount, state)) + total_value += amount return bids, total_value def shouldAutoAcceptBid(self, offer, bid, session=None): @@ -3855,7 +3845,7 @@ class BasicSwap(BaseApp): num_not_completed = 0 for active_bid in active_bids: - if active_bid[3] != 1: + if active_bid[2] != BidStates.SWAP_COMPLETED: num_not_completed += 1 max_concurrent_bids = opts.get('max_concurrent_bids', 1) if num_not_completed >= max_concurrent_bids: @@ -5062,14 +5052,8 @@ class BasicSwap(BaseApp): session = scoped_session(self.session_factory) try: activate_bid = False - if offer.swap_type == SwapTypes.SELLER_FIRST: - if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED: - activate_bid = True - else: - self.log.debug('TODO - determine in-progress for manualBidUpdate') - if offer.swap_type == SwapTypes.XMR_SWAP: - if bid.state and isActiveBidState(bid.state): - activate_bid = True + if bid.state and isActiveBidState(bid.state): + activate_bid = True if activate_bid: self.activateBid(session, bid) diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index d3bf068..7a980c3 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -64,38 +64,38 @@ class SwapTypes(IntEnum): class OfferStates(IntEnum): - OFFER_SENT = auto() - OFFER_RECEIVED = auto() - OFFER_ABANDONED = auto() + OFFER_SENT = 1 + OFFER_RECEIVED = 2 + OFFER_ABANDONED = 3 class BidStates(IntEnum): - BID_SENT = auto() - BID_RECEIVING = auto() # Partially received - BID_RECEIVED = auto() - BID_RECEIVING_ACC = auto() # Partially received accept message - BID_ACCEPTED = auto() # BidAcceptMessage received/sent - SWAP_INITIATED = auto() # Initiate txn validated - SWAP_PARTICIPATING = auto() # Participate txn validated - SWAP_COMPLETED = auto() # All swap txns spent - XMR_SWAP_SCRIPT_COIN_LOCKED = auto() - XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = auto() - XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto() - XMR_SWAP_LOCK_RELEASED = auto() - XMR_SWAP_SCRIPT_TX_REDEEMED = auto() - XMR_SWAP_SCRIPT_TX_PREREFUND = auto() # script txo moved into pre-refund tx - XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto() - XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto() - XMR_SWAP_FAILED_REFUNDED = auto() - XMR_SWAP_FAILED_SWIPED = auto() - XMR_SWAP_FAILED = auto() - SWAP_DELAYING = auto() - SWAP_TIMEDOUT = auto() - BID_ABANDONED = auto() # Bid will no longer be processed - BID_ERROR = auto() # An error occurred - BID_STALLED_FOR_TEST = auto() - BID_REJECTED = auto() - BID_STATE_UNKNOWN = auto() + BID_SENT = 1 + BID_RECEIVING = 2 # Partially received + BID_RECEIVED = 3 + BID_RECEIVING_ACC = 4 # Partially received accept message + BID_ACCEPTED = 5 # BidAcceptMessage received/sent + SWAP_INITIATED = 6 # Initiate txn validated + SWAP_PARTICIPATING = 7 # Participate txn validated + SWAP_COMPLETED = 8 # All swap txns spent + XMR_SWAP_SCRIPT_COIN_LOCKED = 9 + XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = 10 + XMR_SWAP_NOSCRIPT_COIN_LOCKED = 11 + XMR_SWAP_LOCK_RELEASED = 12 + XMR_SWAP_SCRIPT_TX_REDEEMED = 13 + XMR_SWAP_SCRIPT_TX_PREREFUND = 14 # script txo moved into pre-refund tx + XMR_SWAP_NOSCRIPT_TX_REDEEMED = 15 + XMR_SWAP_NOSCRIPT_TX_RECOVERED = 16 + XMR_SWAP_FAILED_REFUNDED = 17 + XMR_SWAP_FAILED_SWIPED = 18 + XMR_SWAP_FAILED = 19 + SWAP_DELAYING = 20 + SWAP_TIMEDOUT = 21 + BID_ABANDONED = 22 # Bid will no longer be processed + BID_ERROR = 23 # An error occurred + BID_STALLED_FOR_TEST = 24 + BID_REJECTED = 25 + BID_STATE_UNKNOWN = 26 class TxStates(IntEnum): @@ -385,6 +385,10 @@ def getLastBidState(packed_states): def isActiveBidState(state): + if state >= BidStates.BID_ACCEPTED and state < BidStates.SWAP_COMPLETED: + return True + if state == BidStates.SWAP_DELAYING: + return True if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: return True if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: diff --git a/basicswap/db.py b/basicswap/db.py index 8f73638..75b036c 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -460,3 +460,16 @@ class History(Base): changed_data = sa.Column(sa.LargeBinary) created_at = sa.Column(sa.BigInteger) + + +class BidState(Base): + __tablename__ = 'bidstates' + + record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) + state_id = sa.Column(sa.Integer) + label = sa.Column(sa.String) + in_progress = sa.Column(sa.Integer) + + note = sa.Column(sa.String) + created_at = sa.Column(sa.BigInteger) diff --git a/basicswap/db_upgrades.py b/basicswap/db_upgrades.py index fb2c1f8..7d3883b 100644 --- a/basicswap/db_upgrades.py +++ b/basicswap/db_upgrades.py @@ -5,14 +5,21 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import json +import time from sqlalchemy.orm import scoped_session from .db import ( + BidState, Concepts, AutomationStrategy, CURRENT_DB_VERSION, CURRENT_DB_DATA_VERSION) +from .basicswap_util import ( + BidStates, + strBidState, + isActiveBidState) + def upgradeDatabaseData(self, data_version): if data_version >= CURRENT_DB_DATA_VERSION: @@ -23,6 +30,8 @@ def upgradeDatabaseData(self, data_version): try: session = scoped_session(self.session_factory) + now = int(time.time()) + if data_version < 1: session.add(AutomationStrategy( active_ind=1, @@ -30,7 +39,8 @@ def upgradeDatabaseData(self, data_version): type_ind=Concepts.OFFER, data=json.dumps({'exact_rate_only': True, 'max_concurrent_bids': 5}).encode('utf-8'), - only_known_identities=False)) + only_known_identities=False, + created_at=now)) session.add(AutomationStrategy( active_ind=1, label='Accept Known', @@ -38,7 +48,16 @@ def upgradeDatabaseData(self, data_version): data=json.dumps({'exact_rate_only': True, 'max_concurrent_bids': 5}).encode('utf-8'), only_known_identities=True, - note='Accept bids from identities with previously successful swaps only')) + note='Accept bids from identities with previously successful swaps only', + created_at=now)) + + for state in BidStates: + session.add(BidState( + active_ind=1, + state_id=int(state), + in_progress=isActiveBidState(state), + label=strBidState(state), + created_at=now)) self.db_data_version = CURRENT_DB_DATA_VERSION self.setIntKVInSession('db_data_version', self.db_data_version, session) @@ -166,6 +185,17 @@ def upgradeDatabase(self, db_version): created_at BIGINT, PRIMARY KEY (record_id))''') + session.execute(''' + CREATE TABLE bidstates ( + record_id INTEGER NOT NULL, + state_id INTEGER, + label VARCHAR, + in_progress INTEGER, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))''') + session.execute('ALTER TABLE wallets ADD COLUMN active_ind INTEGER') session.execute('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER') session.execute('ALTER TABLE eventqueue RENAME TO actions') diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 11b5aa9..097852e 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -5,7 +5,7 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import time +import json import logging import basicswap.contrib.ed25519_fast as edf @@ -217,7 +217,7 @@ class XMRInterface(CoinInterface): def encodeSharedAddress(self, Kbv, Kbs): return xmr_util.encode_address(Kbv, Kbs) - def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): + def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10): with self._mx_wallet: self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) @@ -230,14 +230,17 @@ class XMRInterface(CoinInterface): self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) tx_hash = bytes.fromhex(rv['tx_hash']) - # Debug - for i in range(10): - params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } - rv = self.rpc_wallet_cb('get_transfers', params) - self._log.info('[rm] get_transfers {}'.format(dumpj(rv))) - if 'pending' not in rv: - break - time.sleep(1) + 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) + self._log.debug('get_transfers {}'.format(dumpj(rv))) + if 'pending' not in rv: + break + if i >= delay_for: + break + self._sc.delay_event.wait(1.0) return tx_hash @@ -333,7 +336,6 @@ class XMRInterface(CoinInterface): return True # TODO: Is it necessary to check the address? - ''' rv = self.rpc_wallet_cb('get_balance') print('get_balance', rv) @@ -346,7 +348,9 @@ class XMRInterface(CoinInterface): if i >= num_tries: raise ValueError('Balance not confirming on node') - time.sleep(1) + self._sc.delay_event.wait(1.0) + if self._sc.delay_event.is_set(): + raise ValueError('Stopped') return False @@ -431,7 +435,7 @@ class XMRInterface(CoinInterface): params['priority'] = self._fee_priority rv = self.rpc_wallet_cb('sweep_all', params) - print('sweep_all', rv) + self._log.debug('sweep_all {}'.format(json.dumps(rv))) return bytes.fromhex(rv['tx_hash_list'][0]) diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 19f1edb..d51d3c1 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -731,7 +731,6 @@ class Test(BaseTest): 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) bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)