Timeout bids stuck as accepted.

This commit is contained in:
tecnovert 2023-03-09 00:53:54 +02:00
parent 724e7f0ffc
commit 97506850c4
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
5 changed files with 85 additions and 15 deletions

View file

@ -207,3 +207,7 @@ class BaseApp:
def getTime(self) -> int:
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

View file

@ -923,7 +923,7 @@ class BasicSwap(BaseApp):
identity_stats.num_sent_bids_successful = zeroIfNone(identity_stats.num_sent_bids_successful) + 1
else:
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:
identity_stats.num_sent_bids_failed = zeroIfNone(identity_stats.num_sent_bids_failed) + 1
else:
@ -1081,7 +1081,7 @@ class BasicSwap(BaseApp):
pass # No prevouts are locked
# 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
self.updateIdentityBidState(use_session, peer_address, bid)
@ -2714,25 +2714,29 @@ class BasicSwap(BaseApp):
finally:
self.mxDB.release()
def abandonBid(self, bid_id):
self.log.info('Abandoning Bid %s', bid_id.hex())
self.mxDB.acquire()
def deactivateBidForReason(self, bid_id, new_state, session_in=None) -> None:
try:
session = scoped_session(self.session_factory)
session = self.openSession(session_in)
bid = session.query(Bid).filter_by(bid_id=bid_id).first()
ensure(bid, 'Bid not found')
offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first()
ensure(offer, 'Offer not found')
# Mark bid as abandoned, no further processing will be done
bid.setState(BidStates.BID_ABANDONED)
bid.setState(new_state)
self.deactivateBid(session, offer, bid)
session.add(bid)
session.commit()
finally:
session.close()
session.remove()
self.mxDB.release()
if session_in is None:
self.closeSession(session)
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:
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)
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)))
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)
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)
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)
# 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)
txid_hex = ci_from.publishTx(lock_tx_signed)
@ -5212,6 +5246,10 @@ class BasicSwap(BaseApp):
ci_to = self.ci(coin_to)
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_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:
self.expireMessages()
self.checkAcceptedBids()
self._last_checked_expired = now
if now - self._last_checked_actions >= self.check_actions_seconds:

View file

@ -74,7 +74,7 @@ class Offer(Base):
addr_to = sa.Column(sa.String)
created_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)
to_feerate = sa.Column(sa.BigInteger)
@ -107,7 +107,7 @@ class Bid(Base):
active_ind = 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)
contract_count = sa.Column(sa.Integer)
created_at = sa.Column(sa.BigInteger)

View file

@ -24,6 +24,7 @@
- Added restrict_unknown_seed_wallets option.
- Set to false to disable unknown seed warnings.
- 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

View file

@ -570,7 +570,7 @@ class BasicSwapTest(BaseTest):
wait_for_unspent(test_delay_event, ci, swap_value)
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)
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['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):
__test__ = True