Timeout bids stuck as accepted.
This commit is contained in:
		
							parent
							
								
									724e7f0ffc
								
							
						
					
					
						commit
						97506850c4
					
				@ -207,3 +207,7 @@ class BaseApp:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def getTime(self) -> int:
 | 
					    def getTime(self) -> int:
 | 
				
			||||||
        return int(time.time()) + self.mock_time_offset
 | 
					        return int(time.time()) + self.mock_time_offset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setMockTimeOffset(self, new_offset: int) -> None:
 | 
				
			||||||
 | 
					        self.log.warning(f'Setting mocktime to {new_offset}')
 | 
				
			||||||
 | 
					        self.mock_time_offset = new_offset
 | 
				
			||||||
 | 
				
			|||||||
@ -923,7 +923,7 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
                identity_stats.num_sent_bids_successful = zeroIfNone(identity_stats.num_sent_bids_successful) + 1
 | 
					                identity_stats.num_sent_bids_successful = zeroIfNone(identity_stats.num_sent_bids_successful) + 1
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                identity_stats.num_recv_bids_successful = zeroIfNone(identity_stats.num_recv_bids_successful) + 1
 | 
					                identity_stats.num_recv_bids_successful = zeroIfNone(identity_stats.num_recv_bids_successful) + 1
 | 
				
			||||||
        elif bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED):
 | 
					        elif bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED, BidStates.SWAP_TIMEDOUT):
 | 
				
			||||||
            if bid.was_sent:
 | 
					            if bid.was_sent:
 | 
				
			||||||
                identity_stats.num_sent_bids_failed = zeroIfNone(identity_stats.num_sent_bids_failed) + 1
 | 
					                identity_stats.num_sent_bids_failed = zeroIfNone(identity_stats.num_sent_bids_failed) + 1
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
@ -1081,7 +1081,7 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
                pass  # No prevouts are locked
 | 
					                pass  # No prevouts are locked
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update identity stats
 | 
					            # Update identity stats
 | 
				
			||||||
            if bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED, BidStates.SWAP_COMPLETED):
 | 
					            if bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED, BidStates.SWAP_COMPLETED, BidStates.SWAP_TIMEDOUT):
 | 
				
			||||||
                peer_address = offer.addr_from if bid.was_sent else bid.bid_addr
 | 
					                peer_address = offer.addr_from if bid.was_sent else bid.bid_addr
 | 
				
			||||||
                self.updateIdentityBidState(use_session, peer_address, bid)
 | 
					                self.updateIdentityBidState(use_session, peer_address, bid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -2714,25 +2714,29 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            self.mxDB.release()
 | 
					            self.mxDB.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def abandonBid(self, bid_id):
 | 
					    def deactivateBidForReason(self, bid_id, new_state, session_in=None) -> None:
 | 
				
			||||||
        self.log.info('Abandoning Bid %s', bid_id.hex())
 | 
					 | 
				
			||||||
        self.mxDB.acquire()
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            session = scoped_session(self.session_factory)
 | 
					            session = self.openSession(session_in)
 | 
				
			||||||
            bid = session.query(Bid).filter_by(bid_id=bid_id).first()
 | 
					            bid = session.query(Bid).filter_by(bid_id=bid_id).first()
 | 
				
			||||||
            ensure(bid, 'Bid not found')
 | 
					            ensure(bid, 'Bid not found')
 | 
				
			||||||
            offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first()
 | 
					            offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first()
 | 
				
			||||||
            ensure(offer, 'Offer not found')
 | 
					            ensure(offer, 'Offer not found')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Mark bid as abandoned, no further processing will be done
 | 
					            bid.setState(new_state)
 | 
				
			||||||
            bid.setState(BidStates.BID_ABANDONED)
 | 
					 | 
				
			||||||
            self.deactivateBid(session, offer, bid)
 | 
					            self.deactivateBid(session, offer, bid)
 | 
				
			||||||
            session.add(bid)
 | 
					            session.add(bid)
 | 
				
			||||||
            session.commit()
 | 
					            session.commit()
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            session.close()
 | 
					            if session_in is None:
 | 
				
			||||||
            session.remove()
 | 
					                self.closeSession(session)
 | 
				
			||||||
            self.mxDB.release()
 | 
					
 | 
				
			||||||
 | 
					    def abandonBid(self, bid_id: bytes) -> None:
 | 
				
			||||||
 | 
					        self.log.info('Abandoning Bid %s', bid_id.hex())
 | 
				
			||||||
 | 
					        self.deactivateBidForReason(bid_id, BidStates.BID_ABANDONED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def timeoutBid(self, bid_id: bytes, session_in=None) -> None:
 | 
				
			||||||
 | 
					        self.log.info('Bid %s timed-out', bid_id.hex())
 | 
				
			||||||
 | 
					        self.deactivateBidForReason(bid_id, BidStates.SWAP_TIMEDOUT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setBidError(self, bid_id, bid, error_str, save_bid=True, xmr_swap=None) -> None:
 | 
					    def setBidError(self, bid_id, bid, error_str, save_bid=True, xmr_swap=None) -> None:
 | 
				
			||||||
        self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
 | 
					        self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
 | 
				
			||||||
@ -3959,7 +3963,29 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
                ci_part.close_rpc(rpc_conn)
 | 
					                ci_part.close_rpc(rpc_conn)
 | 
				
			||||||
            self.mxDB.release()
 | 
					            self.mxDB.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def countQueuedActions(self, session, bid_id, action_type):
 | 
					    def checkAcceptedBids(self) -> None:
 | 
				
			||||||
 | 
					        # Check for bids stuck as accepted (not yet in-progress)
 | 
				
			||||||
 | 
					        if self._is_locked is True:
 | 
				
			||||||
 | 
					            self.log.debug('Not checking accepted bids while system locked')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        now: int = self.getTime()
 | 
				
			||||||
 | 
					        session = self.openSession()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        grace_period: int = 60 * 60
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            query_str = 'SELECT bid_id FROM bids ' + \
 | 
				
			||||||
 | 
					                        'WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now '
 | 
				
			||||||
 | 
					            q = session.execute(query_str, {'accepted_state': int(BidStates.BID_ACCEPTED), 'now': now, 'grace_period': grace_period})
 | 
				
			||||||
 | 
					            for row in q:
 | 
				
			||||||
 | 
					                bid_id = row[0]
 | 
				
			||||||
 | 
					                self.log.info('Timing out bid {}.'.format(bid_id.hex()))
 | 
				
			||||||
 | 
					                self.timeoutBid(bid_id, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.closeSession(session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def countQueuedActions(self, session, bid_id, 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, Action.action_type == int(action_type)))
 | 
				
			||||||
        return q.count()
 | 
					        return q.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -4734,6 +4760,10 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
            v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount)
 | 
					            v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount)
 | 
				
			||||||
            ensure(v, 'Invalid coin A lock refund tx leader sig')
 | 
					            ensure(v, 'Invalid coin A lock refund tx leader sig')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            allowed_states = [BidStates.BID_SENT, BidStates.BID_RECEIVED]
 | 
				
			||||||
 | 
					            if bid.was_sent and offer.was_sent:
 | 
				
			||||||
 | 
					                allowed_states.append(BidStates.BID_ACCEPTED)  # TODO: Split BID_ACCEPTED into recieved and sent
 | 
				
			||||||
 | 
					            ensure(bid.state in allowed_states, 'Invalid state for bid {}'.format(bid.state))
 | 
				
			||||||
            bid.setState(BidStates.BID_RECEIVING_ACC)
 | 
					            bid.setState(BidStates.BID_RECEIVING_ACC)
 | 
				
			||||||
            self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap)
 | 
					            self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -4859,6 +4889,10 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
        self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
 | 
					        self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # publishalocktx
 | 
					        # publishalocktx
 | 
				
			||||||
 | 
					        if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.state:
 | 
				
			||||||
 | 
					            if bid.xmr_a_lock_tx.state >= TxStates.TX_SENT:
 | 
				
			||||||
 | 
					                raise ValueError('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
 | 
					        lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
 | 
				
			||||||
        txid_hex = ci_from.publishTx(lock_tx_signed)
 | 
					        txid_hex = ci_from.publishTx(lock_tx_signed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5212,6 +5246,10 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
        ci_to = self.ci(coin_to)
 | 
					        ci_to = self.ci(coin_to)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
 | 
					            allowed_states = [BidStates.BID_ACCEPTED, ]
 | 
				
			||||||
 | 
					            if bid.was_sent and offer.was_sent:
 | 
				
			||||||
 | 
					                allowed_states.append(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS)
 | 
				
			||||||
 | 
					            ensure(bid.state in allowed_states, 'Invalid state for bid {}'.format(bid.state))
 | 
				
			||||||
            xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig
 | 
					            xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig
 | 
				
			||||||
            xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig
 | 
					            xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5480,6 +5518,7 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if now - self._last_checked_expired >= self.check_expired_seconds:
 | 
					            if now - self._last_checked_expired >= self.check_expired_seconds:
 | 
				
			||||||
                self.expireMessages()
 | 
					                self.expireMessages()
 | 
				
			||||||
 | 
					                self.checkAcceptedBids()
 | 
				
			||||||
                self._last_checked_expired = now
 | 
					                self._last_checked_expired = now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if now - self._last_checked_actions >= self.check_actions_seconds:
 | 
					            if now - self._last_checked_actions >= self.check_actions_seconds:
 | 
				
			||||||
 | 
				
			|||||||
@ -74,7 +74,7 @@ class Offer(Base):
 | 
				
			|||||||
    addr_to = sa.Column(sa.String)
 | 
					    addr_to = sa.Column(sa.String)
 | 
				
			||||||
    created_at = sa.Column(sa.BigInteger)
 | 
					    created_at = sa.Column(sa.BigInteger)
 | 
				
			||||||
    expire_at = sa.Column(sa.BigInteger)
 | 
					    expire_at = sa.Column(sa.BigInteger)
 | 
				
			||||||
    was_sent = sa.Column(sa.Boolean)
 | 
					    was_sent = sa.Column(sa.Boolean)  # Sent by node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from_feerate = sa.Column(sa.BigInteger)
 | 
					    from_feerate = sa.Column(sa.BigInteger)
 | 
				
			||||||
    to_feerate = sa.Column(sa.BigInteger)
 | 
					    to_feerate = sa.Column(sa.BigInteger)
 | 
				
			||||||
@ -107,7 +107,7 @@ class Bid(Base):
 | 
				
			|||||||
    active_ind = sa.Column(sa.Integer)
 | 
					    active_ind = sa.Column(sa.Integer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protocol_version = sa.Column(sa.Integer)
 | 
					    protocol_version = sa.Column(sa.Integer)
 | 
				
			||||||
    was_sent = sa.Column(sa.Boolean)
 | 
					    was_sent = sa.Column(sa.Boolean)  # Sent by node
 | 
				
			||||||
    was_received = sa.Column(sa.Boolean)
 | 
					    was_received = sa.Column(sa.Boolean)
 | 
				
			||||||
    contract_count = sa.Column(sa.Integer)
 | 
					    contract_count = sa.Column(sa.Integer)
 | 
				
			||||||
    created_at = sa.Column(sa.BigInteger)
 | 
					    created_at = sa.Column(sa.BigInteger)
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@
 | 
				
			|||||||
- Added restrict_unknown_seed_wallets option.
 | 
					- Added restrict_unknown_seed_wallets option.
 | 
				
			||||||
  - Set to false to disable unknown seed warnings.
 | 
					  - Set to false to disable unknown seed warnings.
 | 
				
			||||||
- ui: Can edit offer automation strategy.
 | 
					- ui: Can edit offer automation strategy.
 | 
				
			||||||
 | 
					- Accepted bids will timeout if the peer does not respond within an hour after the bid expires.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
0.0.54
 | 
					0.0.54
 | 
				
			||||||
 | 
				
			|||||||
@ -570,7 +570,7 @@ class BasicSwapTest(BaseTest):
 | 
				
			|||||||
        wait_for_unspent(test_delay_event, ci, swap_value)
 | 
					        wait_for_unspent(test_delay_event, ci, swap_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        extra_options = {'prefunded_itx': itx}
 | 
					        extra_options = {'prefunded_itx': itx}
 | 
				
			||||||
        rate_swap = ci_to.make_int(random.uniform(0.2, 20.0))
 | 
					        rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
 | 
				
			||||||
        offer_id = swap_clients[2].postOffer(self.test_coin_from, Coins.XMR, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP, extra_options=extra_options)
 | 
					        offer_id = swap_clients[2].postOffer(self.test_coin_from, Coins.XMR, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP, extra_options=extra_options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        wait_for_offer(test_delay_event, swap_clients[1], offer_id)
 | 
					        wait_for_offer(test_delay_event, swap_clients[1], offer_id)
 | 
				
			||||||
@ -594,6 +594,32 @@ class BasicSwapTest(BaseTest):
 | 
				
			|||||||
            assert (txin['txid'] == txin_after['txid'])
 | 
					            assert (txin['txid'] == txin_after['txid'])
 | 
				
			||||||
            assert (txin['vout'] == txin_after['vout'])
 | 
					            assert (txin['vout'] == txin_after['vout'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_07_expire_stuck_accepted(self):
 | 
				
			||||||
 | 
					        coin_from, coin_to = (self.test_coin_from, Coins.XMR)
 | 
				
			||||||
 | 
					        logging.info('---------- Test {} to {} expires bid stuck on accepted'.format(coin_from.name, coin_to.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					        ci_to = swap_clients[0].ci(coin_to)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
 | 
				
			||||||
 | 
					        rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].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[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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            swap_clients[0].setMockTimeOffset(7200)
 | 
				
			||||||
 | 
					            old_check_expired_seconds = swap_clients[0].check_expired_seconds
 | 
				
			||||||
 | 
					            swap_clients[0].check_expired_seconds = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_TIMEDOUT, wait_for=180)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            swap_clients[0].check_expired_seconds = old_check_expired_seconds
 | 
				
			||||||
 | 
					            swap_clients[0].setMockTimeOffset(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestBTC(BasicSwapTest):
 | 
					class TestBTC(BasicSwapTest):
 | 
				
			||||||
    __test__ = True
 | 
					    __test__ = True
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user