mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-22 11:39:34 +00:00
Improve auto-accept
This commit is contained in:
parent
26de907185
commit
08df0ceae0
9 changed files with 211 additions and 74 deletions
|
@ -35,6 +35,7 @@ from .rpc_xmr import make_xmr_rpc2_func
|
||||||
from .ui.util import getCoinName, known_chart_coins
|
from .ui.util import getCoinName, known_chart_coins
|
||||||
from .util import (
|
from .util import (
|
||||||
AutomationConstraint,
|
AutomationConstraint,
|
||||||
|
AutomationConstraintTemporary,
|
||||||
LockedCoinError,
|
LockedCoinError,
|
||||||
TemporaryError,
|
TemporaryError,
|
||||||
InactiveCoin,
|
InactiveCoin,
|
||||||
|
@ -125,25 +126,26 @@ from .basicswap_util import (
|
||||||
AutomationOverrideOptions,
|
AutomationOverrideOptions,
|
||||||
BidStates,
|
BidStates,
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
describeEventEntry,
|
|
||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
getLastBidState,
|
|
||||||
getOfferProofOfFundsHash,
|
|
||||||
getVoutByAddress,
|
|
||||||
getVoutByScriptPubKey,
|
|
||||||
inactive_states,
|
|
||||||
isActiveBidState,
|
|
||||||
KeyTypes,
|
KeyTypes,
|
||||||
MessageTypes,
|
MessageTypes,
|
||||||
NotificationTypes as NT,
|
NotificationTypes as NT,
|
||||||
OfferStates,
|
OfferStates,
|
||||||
strBidState,
|
|
||||||
SwapTypes,
|
SwapTypes,
|
||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
TxStates,
|
TxStates,
|
||||||
TxTypes,
|
TxTypes,
|
||||||
VisibilityOverrideOptions,
|
VisibilityOverrideOptions,
|
||||||
XmrSplitMsgTypes,
|
XmrSplitMsgTypes,
|
||||||
|
canAcceptBidState,
|
||||||
|
describeEventEntry,
|
||||||
|
getLastBidState,
|
||||||
|
getOfferProofOfFundsHash,
|
||||||
|
getVoutByAddress,
|
||||||
|
getVoutByScriptPubKey,
|
||||||
|
inactive_states,
|
||||||
|
isActiveBidState,
|
||||||
|
strBidState,
|
||||||
)
|
)
|
||||||
from basicswap.db_util import (
|
from basicswap.db_util import (
|
||||||
remove_expired_data,
|
remove_expired_data,
|
||||||
|
@ -307,8 +309,12 @@ class BasicSwap(BaseApp):
|
||||||
self.check_watched_seconds = self.get_int_setting(
|
self.check_watched_seconds = self.get_int_setting(
|
||||||
"check_watched_seconds", 60, 1, 10 * 60
|
"check_watched_seconds", 60, 1, 10 * 60
|
||||||
)
|
)
|
||||||
self.check_xmr_swaps_seconds = self.get_int_setting(
|
self.check_split_messages_seconds = self.get_int_setting(
|
||||||
"check_xmr_swaps_seconds", 20, 1, 10 * 60
|
"check_split_messages_seconds", 20, 1, 10 * 60
|
||||||
|
)
|
||||||
|
# Retry auto accept for bids at BID_AACCEPT_DELAY, also updates when bids complete
|
||||||
|
self.check_delayed_auto_accept_seconds = self.get_int_setting(
|
||||||
|
"check_delayed_auto_accept_seconds", 60, 1, 20 * 60
|
||||||
)
|
)
|
||||||
self.startup_tries = self.get_int_setting(
|
self.startup_tries = self.get_int_setting(
|
||||||
"startup_tries", 21, 1, 100
|
"startup_tries", 21, 1, 100
|
||||||
|
@ -321,7 +327,8 @@ class BasicSwap(BaseApp):
|
||||||
self._last_checked_progress = 0
|
self._last_checked_progress = 0
|
||||||
self._last_checked_smsg = 0
|
self._last_checked_smsg = 0
|
||||||
self._last_checked_watched = 0
|
self._last_checked_watched = 0
|
||||||
self._last_checked_xmr_swaps = 0
|
self._last_checked_split_messages = 0
|
||||||
|
self._last_checked_delayed_auto_accept = 0
|
||||||
self._possibly_revoked_offers = collections.deque(
|
self._possibly_revoked_offers = collections.deque(
|
||||||
[], maxlen=48
|
[], maxlen=48
|
||||||
) # TODO: improve
|
) # TODO: improve
|
||||||
|
@ -409,7 +416,6 @@ class BasicSwap(BaseApp):
|
||||||
bytes.fromhex(self.network_pubkey),
|
bytes.fromhex(self.network_pubkey),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db_echo: bool = self.settings.get("db_echo", False)
|
|
||||||
self.sqlite_file: str = os.path.join(
|
self.sqlite_file: str = os.path.join(
|
||||||
self.data_dir,
|
self.data_dir,
|
||||||
"db{}.sqlite".format("" if self.chain == "mainnet" else ("_" + self.chain)),
|
"db{}.sqlite".format("" if self.chain == "mainnet" else ("_" + self.chain)),
|
||||||
|
@ -3229,7 +3235,7 @@ class BasicSwap(BaseApp):
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
ensure(bid.expire_at > now, "Bid expired")
|
ensure(bid.expire_at > now, "Bid expired")
|
||||||
ensure(
|
ensure(
|
||||||
bid.state in (BidStates.BID_RECEIVED,),
|
canAcceptBidState(bid.state),
|
||||||
"Wrong bid state: {}".format(BidStates(bid.state).name),
|
"Wrong bid state: {}".format(BidStates(bid.state).name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3688,7 +3694,7 @@ class BasicSwap(BaseApp):
|
||||||
last_bid_state = getLastBidState(bid.states)
|
last_bid_state = getLastBidState(bid.states)
|
||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
last_bid_state == BidStates.BID_RECEIVED,
|
canAcceptBidState(last_bid_state),
|
||||||
"Wrong bid state: {}".format(str(BidStates(last_bid_state))),
|
"Wrong bid state: {}".format(str(BidStates(last_bid_state))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3990,7 +3996,7 @@ class BasicSwap(BaseApp):
|
||||||
last_bid_state = getLastBidState(bid.states)
|
last_bid_state = getLastBidState(bid.states)
|
||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
last_bid_state == BidStates.BID_RECEIVED,
|
canAcceptBidState(last_bid_state),
|
||||||
"Wrong bid state: {}".format(str(BidStates(last_bid_state))),
|
"Wrong bid state: {}".format(str(BidStates(last_bid_state))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6790,12 +6796,12 @@ class BasicSwap(BaseApp):
|
||||||
if (
|
if (
|
||||||
bid
|
bid
|
||||||
and bid.state == BidStates.SWAP_DELAYING
|
and bid.state == BidStates.SWAP_DELAYING
|
||||||
and last_state == BidStates.BID_RECEIVED
|
and canAcceptBidState(last_state)
|
||||||
):
|
):
|
||||||
new_state = (
|
new_state = (
|
||||||
BidStates.BID_ERROR
|
BidStates.BID_ERROR
|
||||||
if offer.bid_reversed
|
if offer.bid_reversed
|
||||||
else BidStates.BID_RECEIVED
|
else last_state
|
||||||
)
|
)
|
||||||
bid.setState(new_state)
|
bid.setState(new_state)
|
||||||
self.saveBidInSession(bid_id, bid, cursor)
|
self.saveBidInSession(bid_id, bid, cursor)
|
||||||
|
@ -6819,7 +6825,8 @@ class BasicSwap(BaseApp):
|
||||||
if reload_in_progress:
|
if reload_in_progress:
|
||||||
self.loadFromDB()
|
self.loadFromDB()
|
||||||
|
|
||||||
def checkXmrSwaps(self) -> None:
|
def checkSplitMessages(self) -> None:
|
||||||
|
# Combines split data messages
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
ttl_xmr_split_messages = 60 * 60
|
ttl_xmr_split_messages = 60 * 60
|
||||||
bid_cursor = None
|
bid_cursor = None
|
||||||
|
@ -6930,6 +6937,27 @@ class BasicSwap(BaseApp):
|
||||||
self.closeDBCursor(bid_cursor)
|
self.closeDBCursor(bid_cursor)
|
||||||
self.closeDB(cursor)
|
self.closeDB(cursor)
|
||||||
|
|
||||||
|
def checkDelayedAutoAccept(self) -> None:
|
||||||
|
bids_cursor = None
|
||||||
|
try:
|
||||||
|
cursor = self.openDB()
|
||||||
|
bids_cursor = self.getNewDBCursor()
|
||||||
|
for bid in self.query(
|
||||||
|
Bid, bids_cursor, {"state": int(BidStates.BID_AACCEPT_DELAY)}
|
||||||
|
):
|
||||||
|
offer = self.getOffer(bid.offer_id, cursor=cursor)
|
||||||
|
if self.shouldAutoAcceptBid(offer, bid, cursor=cursor):
|
||||||
|
delay = self.get_delay_event_seconds()
|
||||||
|
self.log.info(
|
||||||
|
"Auto accepting bid %s in %d seconds", bid.bid_id.hex(), delay
|
||||||
|
)
|
||||||
|
self.createActionInSession(
|
||||||
|
delay, ActionTypes.ACCEPT_BID, bid.bid_id, cursor
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.closeDBCursor(bids_cursor)
|
||||||
|
self.closeDB(cursor)
|
||||||
|
|
||||||
def processOffer(self, msg) -> None:
|
def processOffer(self, msg) -> None:
|
||||||
offer_bytes = bytes.fromhex(msg["hex"][2:-2])
|
offer_bytes = bytes.fromhex(msg["hex"][2:-2])
|
||||||
|
|
||||||
|
@ -7222,6 +7250,10 @@ class BasicSwap(BaseApp):
|
||||||
try:
|
try:
|
||||||
use_cursor = self.openDB(cursor)
|
use_cursor = self.openDB(cursor)
|
||||||
|
|
||||||
|
if self.countQueuedActions(use_cursor, bid.bid_id, ActionTypes.ACCEPT_BID):
|
||||||
|
# Bid is already queued to be accepted
|
||||||
|
return False
|
||||||
|
|
||||||
link = self.queryOne(
|
link = self.queryOne(
|
||||||
AutomationLink,
|
AutomationLink,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
|
@ -7250,6 +7282,12 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
self.log.debug("Evaluating against strategy {}".format(strategy.record_id))
|
self.log.debug("Evaluating against strategy {}".format(strategy.record_id))
|
||||||
|
|
||||||
|
now: int = self.getTime()
|
||||||
|
if bid.expire_at < now:
|
||||||
|
raise AutomationConstraint(
|
||||||
|
"Bid expired"
|
||||||
|
) # State will be set to expired in expireBidsAndOffers
|
||||||
|
|
||||||
if not offer.amount_negotiable:
|
if not offer.amount_negotiable:
|
||||||
if bid_amount != offer.amount_from:
|
if bid_amount != offer.amount_from:
|
||||||
raise AutomationConstraint("Need exact amount match")
|
raise AutomationConstraint("Need exact amount match")
|
||||||
|
@ -7287,7 +7325,7 @@ class BasicSwap(BaseApp):
|
||||||
f"active_bids {num_not_completed}, max_concurrent_bids {max_concurrent_bids}"
|
f"active_bids {num_not_completed}, max_concurrent_bids {max_concurrent_bids}"
|
||||||
)
|
)
|
||||||
if num_not_completed >= max_concurrent_bids:
|
if num_not_completed >= max_concurrent_bids:
|
||||||
raise AutomationConstraint(
|
raise AutomationConstraintTemporary(
|
||||||
"Already have {} bids to complete".format(num_not_completed)
|
"Already have {} bids to complete".format(num_not_completed)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -7296,6 +7334,15 @@ class BasicSwap(BaseApp):
|
||||||
)
|
)
|
||||||
self.evaluateKnownIdentityForAutoAccept(strategy, identity_stats)
|
self.evaluateKnownIdentityForAutoAccept(strategy, identity_stats)
|
||||||
|
|
||||||
|
# Ensure the coin from wallet has sufficient balance for multiple bids
|
||||||
|
bids_active_if_accepted: int = num_not_completed + 1
|
||||||
|
|
||||||
|
ci_from = self.ci(offer.coin_from)
|
||||||
|
try:
|
||||||
|
ci_from.ensureFunds(bids_active_if_accepted * bid_amount)
|
||||||
|
except Exception as e: # noqa: F841
|
||||||
|
raise AutomationConstraintTemporary("Balance too low")
|
||||||
|
|
||||||
self.logEvent(
|
self.logEvent(
|
||||||
Concepts.BID,
|
Concepts.BID,
|
||||||
bid.bid_id,
|
bid.bid_id,
|
||||||
|
@ -7305,7 +7352,7 @@ class BasicSwap(BaseApp):
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except AutomationConstraint as e:
|
except (AutomationConstraint, AutomationConstraintTemporary) as e:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Not auto accepting bid {}, {}".format(bid.bid_id.hex(), str(e))
|
"Not auto accepting bid {}, {}".format(bid.bid_id.hex(), str(e))
|
||||||
)
|
)
|
||||||
|
@ -7317,6 +7364,19 @@ class BasicSwap(BaseApp):
|
||||||
str(e),
|
str(e),
|
||||||
use_cursor,
|
use_cursor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(e, AutomationConstraintTemporary):
|
||||||
|
bid.setState(BidStates.BID_AACCEPT_DELAY)
|
||||||
|
else:
|
||||||
|
bid.setState(BidStates.BID_AACCEPT_FAIL)
|
||||||
|
self.updateDB(
|
||||||
|
bid,
|
||||||
|
use_cursor,
|
||||||
|
[
|
||||||
|
"bid_id",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logException(f"shouldAutoAcceptBid {e}")
|
self.logException(f"shouldAutoAcceptBid {e}")
|
||||||
|
@ -9589,18 +9649,20 @@ class BasicSwap(BaseApp):
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
|
|
||||||
if check_records:
|
if check_records:
|
||||||
query = """SELECT 1, bid_id, expire_at FROM bids WHERE active_ind = 1 AND state IN (:bid_received, :bid_sent) AND expire_at <= :check_time
|
query = """SELECT 1, bid_id, expire_at FROM bids WHERE active_ind = 1 AND state IN (:bid_received, :bid_sent, :bid_aad, :bid_aaf) AND expire_at <= :check_time
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 2, offer_id, expire_at FROM offers WHERE active_ind = 1 AND state IN (:offer_received, :offer_sent) AND expire_at <= :check_time
|
SELECT 2, offer_id, expire_at FROM offers WHERE active_ind = 1 AND state IN (:offer_received, :offer_sent) AND expire_at <= :check_time
|
||||||
"""
|
"""
|
||||||
q = cursor.execute(
|
q = cursor.execute(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
"bid_received": int(BidStates.BID_RECEIVED),
|
|
||||||
"offer_received": int(OfferStates.OFFER_RECEIVED),
|
"offer_received": int(OfferStates.OFFER_RECEIVED),
|
||||||
"bid_sent": int(BidStates.BID_SENT),
|
|
||||||
"offer_sent": int(OfferStates.OFFER_SENT),
|
"offer_sent": int(OfferStates.OFFER_SENT),
|
||||||
"check_time": now + self.check_expiring_bids_offers_seconds,
|
"check_time": now + self.check_expiring_bids_offers_seconds,
|
||||||
|
"bid_sent": int(BidStates.BID_SENT),
|
||||||
|
"bid_received": int(BidStates.BID_RECEIVED),
|
||||||
|
"bid_aad": int(BidStates.BID_AACCEPT_DELAY),
|
||||||
|
"bid_aaf": int(BidStates.BID_AACCEPT_FAIL),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
for entry in q:
|
for entry in q:
|
||||||
|
@ -9618,13 +9680,15 @@ class BasicSwap(BaseApp):
|
||||||
offers_to_expire.add(record_id)
|
offers_to_expire.add(record_id)
|
||||||
|
|
||||||
for bid_id in bids_to_expire:
|
for bid_id in bids_to_expire:
|
||||||
query = "SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent)"
|
query = "SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent, :bid_aad, :bid_aaf)"
|
||||||
rows = cursor.execute(
|
rows = cursor.execute(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
"bid_id": bid_id,
|
"bid_id": bid_id,
|
||||||
"bid_received": int(BidStates.BID_RECEIVED),
|
"bid_received": int(BidStates.BID_RECEIVED),
|
||||||
"bid_sent": int(BidStates.BID_SENT),
|
"bid_sent": int(BidStates.BID_SENT),
|
||||||
|
"bid_aad": int(BidStates.BID_AACCEPT_DELAY),
|
||||||
|
"bid_aaf": int(BidStates.BID_AACCEPT_FAIL),
|
||||||
},
|
},
|
||||||
).fetchall()
|
).fetchall()
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
|
@ -9699,8 +9763,8 @@ class BasicSwap(BaseApp):
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
self.expireBidsAndOffers(now)
|
self.expireBidsAndOffers(now)
|
||||||
|
|
||||||
|
to_remove = []
|
||||||
if now - self._last_checked_progress >= self.check_progress_seconds:
|
if now - self._last_checked_progress >= self.check_progress_seconds:
|
||||||
to_remove = []
|
|
||||||
for bid_id, v in self.swaps_in_progress.items():
|
for bid_id, v in self.swaps_in_progress.items():
|
||||||
try:
|
try:
|
||||||
if self.checkBidState(bid_id, v[0], v[1]) is True:
|
if self.checkBidState(bid_id, v[0], v[1]) is True:
|
||||||
|
@ -9748,9 +9812,20 @@ class BasicSwap(BaseApp):
|
||||||
self.checkQueuedActions()
|
self.checkQueuedActions()
|
||||||
self._last_checked_actions = now
|
self._last_checked_actions = now
|
||||||
|
|
||||||
if now - self._last_checked_xmr_swaps >= self.check_xmr_swaps_seconds:
|
if (
|
||||||
self.checkXmrSwaps()
|
now - self._last_checked_split_messages
|
||||||
self._last_checked_xmr_swaps = now
|
>= self.check_split_messages_seconds
|
||||||
|
):
|
||||||
|
self.checkSplitMessages()
|
||||||
|
self._last_checked_split_messages = now
|
||||||
|
|
||||||
|
if (
|
||||||
|
len(to_remove) > 0
|
||||||
|
or now - self._last_checked_delayed_auto_accept
|
||||||
|
>= self.check_delayed_auto_accept_seconds
|
||||||
|
):
|
||||||
|
self.checkDelayedAutoAccept()
|
||||||
|
self._last_checked_delayed_auto_accept = now
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.logException(f"update {ex}")
|
self.logException(f"update {ex}")
|
||||||
|
@ -10113,7 +10188,7 @@ class BasicSwap(BaseApp):
|
||||||
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
|
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
|
||||||
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
|
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
|
||||||
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
|
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
|
||||||
COUNT(CASE WHEN b.was_received AND b.state = :received_state AND b.expire_at > :now AND o.expire_at > :now THEN 1 ELSE NULL END) AS count_available,
|
COUNT(CASE WHEN b.was_received AND s.can_accept AND b.expire_at > :now AND o.expire_at > :now THEN 1 ELSE NULL END) AS count_available,
|
||||||
COUNT(CASE WHEN b.was_received AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_recv_active
|
COUNT(CASE WHEN b.was_received AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_recv_active
|
||||||
FROM bids b
|
FROM bids b
|
||||||
JOIN offers o ON b.offer_id = o.offer_id
|
JOIN offers o ON b.offer_id = o.offer_id
|
||||||
|
@ -10131,9 +10206,7 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
q = cursor.execute(
|
q = cursor.execute(q_bids_str, {"now": now}).fetchone()
|
||||||
q_bids_str, {"now": now, "received_state": int(BidStates.BID_RECEIVED)}
|
|
||||||
).fetchone()
|
|
||||||
bids_sent = q[0]
|
bids_sent = q[0]
|
||||||
bids_sent_active = q[1]
|
bids_sent_active = q[1]
|
||||||
bids_received = q[2]
|
bids_received = q[2]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2024 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
|
@ -106,6 +107,8 @@ class BidStates(IntEnum):
|
||||||
BID_REQUEST_SENT = 29
|
BID_REQUEST_SENT = 29
|
||||||
BID_REQUEST_ACCEPTED = 30
|
BID_REQUEST_ACCEPTED = 30
|
||||||
BID_EXPIRED = 31
|
BID_EXPIRED = 31
|
||||||
|
BID_AACCEPT_DELAY = 32
|
||||||
|
BID_AACCEPT_FAIL = 33
|
||||||
|
|
||||||
|
|
||||||
class TxStates(IntEnum):
|
class TxStates(IntEnum):
|
||||||
|
@ -330,6 +333,10 @@ def strBidState(state):
|
||||||
return "Unknown bid state"
|
return "Unknown bid state"
|
||||||
if state == BidStates.BID_EXPIRED:
|
if state == BidStates.BID_EXPIRED:
|
||||||
return "Expired"
|
return "Expired"
|
||||||
|
if state == BidStates.BID_AACCEPT_DELAY:
|
||||||
|
return "Auto accept delay"
|
||||||
|
if state == BidStates.BID_AACCEPT_FAIL:
|
||||||
|
return "Auto accept failed"
|
||||||
return "Unknown" + " " + str(state)
|
return "Unknown" + " " + str(state)
|
||||||
|
|
||||||
|
|
||||||
|
@ -539,6 +546,14 @@ inactive_states = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def canAcceptBidState(state):
|
||||||
|
return state in (
|
||||||
|
BidStates.BID_RECEIVED,
|
||||||
|
BidStates.BID_AACCEPT_DELAY,
|
||||||
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def isActiveBidState(state):
|
def isActiveBidState(state):
|
||||||
if state >= BidStates.BID_ACCEPTED and state < BidStates.SWAP_COMPLETED:
|
if state >= BidStates.BID_ACCEPTED and state < BidStates.SWAP_COMPLETED:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -13,8 +13,8 @@ from enum import IntEnum, auto
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
CURRENT_DB_VERSION = 24
|
CURRENT_DB_VERSION = 25
|
||||||
CURRENT_DB_DATA_VERSION = 4
|
CURRENT_DB_DATA_VERSION = 5
|
||||||
|
|
||||||
|
|
||||||
class Concepts(IntEnum):
|
class Concepts(IntEnum):
|
||||||
|
@ -601,6 +601,7 @@ class BidState(Table):
|
||||||
in_error = Column("integer")
|
in_error = Column("integer")
|
||||||
swap_failed = Column("integer")
|
swap_failed = Column("integer")
|
||||||
swap_ended = Column("integer")
|
swap_ended = Column("integer")
|
||||||
|
can_accept = Column("integer")
|
||||||
|
|
||||||
note = Column("string")
|
note = Column("string")
|
||||||
created_at = Column("integer")
|
created_at = Column("integer")
|
||||||
|
@ -751,7 +752,6 @@ class DBMethods:
|
||||||
|
|
||||||
def closeDBCursor(self, cursor):
|
def closeDBCursor(self, cursor):
|
||||||
assert self.mxDB.locked()
|
assert self.mxDB.locked()
|
||||||
|
|
||||||
if cursor:
|
if cursor:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from .db import (
|
||||||
|
|
||||||
from .basicswap_util import (
|
from .basicswap_util import (
|
||||||
BidStates,
|
BidStates,
|
||||||
|
canAcceptBidState,
|
||||||
isActiveBidState,
|
isActiveBidState,
|
||||||
isErrorBidState,
|
isErrorBidState,
|
||||||
isFailingBidState,
|
isFailingBidState,
|
||||||
|
@ -26,6 +27,23 @@ from .basicswap_util import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def addBidState(self, state, now, cursor):
|
||||||
|
self.add(
|
||||||
|
BidState(
|
||||||
|
active_ind=1,
|
||||||
|
state_id=int(state),
|
||||||
|
in_progress=isActiveBidState(state),
|
||||||
|
in_error=isErrorBidState(state),
|
||||||
|
swap_failed=isFailingBidState(state),
|
||||||
|
swap_ended=isFinalBidState(state),
|
||||||
|
can_accept=canAcceptBidState(state),
|
||||||
|
label=strBidState(state),
|
||||||
|
created_at=now,
|
||||||
|
),
|
||||||
|
cursor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def upgradeDatabaseData(self, data_version):
|
def upgradeDatabaseData(self, data_version):
|
||||||
if data_version >= CURRENT_DB_DATA_VERSION:
|
if data_version >= CURRENT_DB_DATA_VERSION:
|
||||||
return
|
return
|
||||||
|
@ -69,19 +87,7 @@ def upgradeDatabaseData(self, data_version):
|
||||||
)
|
)
|
||||||
|
|
||||||
for state in BidStates:
|
for state in BidStates:
|
||||||
self.add(
|
addBidState(self, state, now, cursor)
|
||||||
BidState(
|
|
||||||
active_ind=1,
|
|
||||||
state_id=int(state),
|
|
||||||
in_progress=isActiveBidState(state),
|
|
||||||
in_error=isErrorBidState(state),
|
|
||||||
swap_failed=isFailingBidState(state),
|
|
||||||
swap_ended=isFinalBidState(state),
|
|
||||||
label=strBidState(state),
|
|
||||||
created_at=now,
|
|
||||||
),
|
|
||||||
cursor,
|
|
||||||
)
|
|
||||||
|
|
||||||
if data_version > 0 and data_version < 2:
|
if data_version > 0 and data_version < 2:
|
||||||
for state in (
|
for state in (
|
||||||
|
@ -117,19 +123,15 @@ def upgradeDatabaseData(self, data_version):
|
||||||
BidStates.BID_REQUEST_SENT,
|
BidStates.BID_REQUEST_SENT,
|
||||||
BidStates.BID_REQUEST_ACCEPTED,
|
BidStates.BID_REQUEST_ACCEPTED,
|
||||||
):
|
):
|
||||||
self.add(
|
addBidState(self, state, now, cursor)
|
||||||
BidState(
|
|
||||||
active_ind=1,
|
if data_version > 0 and data_version < 5:
|
||||||
state_id=int(state),
|
for state in (
|
||||||
in_progress=isActiveBidState(state),
|
BidStates.BID_EXPIRED,
|
||||||
in_error=isErrorBidState(state),
|
BidStates.BID_AACCEPT_DELAY,
|
||||||
swap_failed=isFailingBidState(state),
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
swap_ended=isFinalBidState(state),
|
):
|
||||||
label=strBidState(state),
|
addBidState(self, state, now, cursor)
|
||||||
created_at=now,
|
|
||||||
),
|
|
||||||
cursor,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.db_data_version = CURRENT_DB_DATA_VERSION
|
self.db_data_version = CURRENT_DB_DATA_VERSION
|
||||||
self.setIntKV("db_data_version", self.db_data_version, cursor)
|
self.setIntKV("db_data_version", self.db_data_version, cursor)
|
||||||
|
@ -405,10 +407,13 @@ def upgradeDatabase(self, db_version):
|
||||||
PRIMARY KEY (record_id))"""
|
PRIMARY KEY (record_id))"""
|
||||||
)
|
)
|
||||||
cursor.execute("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB")
|
cursor.execute("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB")
|
||||||
|
elif current_version == 24:
|
||||||
|
db_version += 1
|
||||||
|
cursor.execute("ALTER TABLE bidstates ADD COLUMN can_accept INTEGER")
|
||||||
if current_version != db_version:
|
if current_version != db_version:
|
||||||
self.db_version = db_version
|
self.db_version = db_version
|
||||||
self.setIntKV("db_version", db_version, cursor)
|
self.setIntKV("db_version", db_version, cursor)
|
||||||
cursor = self.commitDB()
|
self.commitDB()
|
||||||
self.log.info("Upgraded database to version {}".format(self.db_version))
|
self.log.info("Upgraded database to version {}".format(self.db_version))
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -25,6 +25,7 @@ from basicswap.basicswap_util import (
|
||||||
BidStates,
|
BidStates,
|
||||||
SwapTypes,
|
SwapTypes,
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
|
canAcceptBidState,
|
||||||
strTxState,
|
strTxState,
|
||||||
strBidState,
|
strBidState,
|
||||||
)
|
)
|
||||||
|
@ -124,7 +125,7 @@ def page_bid(self, url_split, post_string):
|
||||||
|
|
||||||
if len(data["addr_from_label"]) > 0:
|
if len(data["addr_from_label"]) > 0:
|
||||||
data["addr_from_label"] = "(" + data["addr_from_label"] + ")"
|
data["addr_from_label"] = "(" + data["addr_from_label"] + ")"
|
||||||
data["can_accept_bid"] = True if bid.state == BidStates.BID_RECEIVED else False
|
data["can_accept_bid"] = True if canAcceptBidState(bid.state) else False
|
||||||
|
|
||||||
if swap_client.debug_ui:
|
if swap_client.debug_ui:
|
||||||
data["bid_actions"] = [
|
data["bid_actions"] = [
|
||||||
|
|
|
@ -20,6 +20,7 @@ from basicswap.basicswap_util import (
|
||||||
ActionTypes,
|
ActionTypes,
|
||||||
BidStates,
|
BidStates,
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
|
canAcceptBidState,
|
||||||
getLastBidState,
|
getLastBidState,
|
||||||
strBidState,
|
strBidState,
|
||||||
strTxState,
|
strTxState,
|
||||||
|
@ -258,7 +259,7 @@ def describeBid(
|
||||||
if bid.state == BidStates.BID_RECEIVING:
|
if bid.state == BidStates.BID_RECEIVING:
|
||||||
# Offerer receiving bid from bidder
|
# Offerer receiving bid from bidder
|
||||||
state_description = "Waiting for bid to be fully received"
|
state_description = "Waiting for bid to be fully received"
|
||||||
elif bid.state == BidStates.BID_RECEIVED:
|
elif canAcceptBidState(bid.state):
|
||||||
# Offerer received bid from bidder
|
# Offerer received bid from bidder
|
||||||
# TODO: Manual vs automatic
|
# TODO: Manual vs automatic
|
||||||
state_description = "Bid must be accepted"
|
state_description = "Bid must be accepted"
|
||||||
|
@ -270,7 +271,7 @@ def describeBid(
|
||||||
)
|
)
|
||||||
elif bid.state == BidStates.SWAP_DELAYING:
|
elif bid.state == BidStates.SWAP_DELAYING:
|
||||||
last_state = getLastBidState(bid.states)
|
last_state = getLastBidState(bid.states)
|
||||||
if last_state == BidStates.BID_RECEIVED:
|
if canAcceptBidState(last_state):
|
||||||
state_description = "Delaying before accepting bid"
|
state_description = "Delaying before accepting bid"
|
||||||
elif last_state == BidStates.BID_RECEIVING_ACC:
|
elif last_state == BidStates.BID_RECEIVING_ACC:
|
||||||
state_description = "Delaying before responding to accepted bid"
|
state_description = "Delaying before responding to accepted bid"
|
||||||
|
|
|
@ -26,6 +26,10 @@ class AutomationConstraint(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationConstraintTemporary(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InactiveCoin(Exception):
|
class InactiveCoin(Exception):
|
||||||
def __init__(self, coinid):
|
def __init__(self, coinid):
|
||||||
self.coinid = coinid
|
self.coinid = coinid
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
- Incoming expired offers no longer raise an error.
|
- Incoming expired offers no longer raise an error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0.13.2
|
0.13.2
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
@ -900,6 +900,7 @@ class Test(unittest.TestCase):
|
||||||
|
|
||||||
waitForServer(self.delay_event, UI_PORT + 0)
|
waitForServer(self.delay_event, UI_PORT + 0)
|
||||||
waitForServer(self.delay_event, UI_PORT + 1)
|
waitForServer(self.delay_event, UI_PORT + 1)
|
||||||
|
waitForServer(self.delay_event, UI_PORT + 2)
|
||||||
|
|
||||||
logging.info("Reset test")
|
logging.info("Reset test")
|
||||||
clear_offers(self.delay_event, 0)
|
clear_offers(self.delay_event, 0)
|
||||||
|
@ -926,6 +927,13 @@ class Test(unittest.TestCase):
|
||||||
expect_balance,
|
expect_balance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
inactive_states = (
|
||||||
|
"Received",
|
||||||
|
"Completed",
|
||||||
|
"Auto accept failed",
|
||||||
|
"Auto accept delay",
|
||||||
|
)
|
||||||
|
|
||||||
# Try post bids at the same time
|
# Try post bids at the same time
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
|
|
||||||
|
@ -933,8 +941,12 @@ class Test(unittest.TestCase):
|
||||||
post_json = {"offer_id": offer_id, "amount_from": amount}
|
post_json = {"offer_id": offer_id, "amount_from": amount}
|
||||||
read_json_api(UI_PORT + node_from, "bids/new", post_json)
|
read_json_api(UI_PORT + node_from, "bids/new", post_json)
|
||||||
|
|
||||||
def test_bid_pair(amount_1, amount_2, expect_inactive, delay_event):
|
def test_bid_pair(
|
||||||
logging.debug(f"test_bid_pair {amount_1} {amount_2}, {expect_inactive}")
|
amount_1, amount_2, max_active, delay_event, final_completed=None
|
||||||
|
):
|
||||||
|
logging.debug(
|
||||||
|
f"test_bid_pair {amount_1} {amount_2}, {max_active}, {final_completed}"
|
||||||
|
)
|
||||||
|
|
||||||
wait_for_balance(
|
wait_for_balance(
|
||||||
self.delay_event,
|
self.delay_event,
|
||||||
|
@ -966,6 +978,31 @@ class Test(unittest.TestCase):
|
||||||
pbid1.join()
|
pbid1.join()
|
||||||
pbid2.join()
|
pbid2.join()
|
||||||
|
|
||||||
|
if final_completed is not None:
|
||||||
|
# bids should complete
|
||||||
|
|
||||||
|
logging.info("Waiting for bids to settle")
|
||||||
|
for i in range(50):
|
||||||
|
|
||||||
|
delay_event.wait(5)
|
||||||
|
bids = wait_for_bids(self.delay_event, 0, 2, offer_id)
|
||||||
|
|
||||||
|
if any(bid["bid_state"] == "Receiving" for bid in bids):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info(f"[rm] bids {bids}")
|
||||||
|
num_active_state = 0
|
||||||
|
num_completed = 0
|
||||||
|
for bid in bids:
|
||||||
|
if bid["bid_state"] == "Completed":
|
||||||
|
num_completed += 1
|
||||||
|
if bid["bid_state"] not in inactive_states:
|
||||||
|
num_active_state += 1
|
||||||
|
assert num_active_state <= max_active
|
||||||
|
if num_completed == final_completed:
|
||||||
|
return
|
||||||
|
raise ValueError(f"Failed to complete {num_completed} bids {bids}")
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
logging.info("Waiting for bids to settle")
|
logging.info("Waiting for bids to settle")
|
||||||
|
|
||||||
|
@ -974,16 +1011,18 @@ class Test(unittest.TestCase):
|
||||||
|
|
||||||
if any(bid["bid_state"] == "Receiving" for bid in bids):
|
if any(bid["bid_state"] == "Receiving" for bid in bids):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
logging.info(f"[rm] bids {bids}")
|
||||||
break
|
break
|
||||||
|
|
||||||
num_received_state = 0
|
num_active_state = 0
|
||||||
for bid in bids:
|
for bid in bids:
|
||||||
if bid["bid_state"] == "Received":
|
if bid["bid_state"] not in inactive_states:
|
||||||
num_received_state += 1
|
num_active_state += 1
|
||||||
assert num_received_state == expect_inactive
|
assert num_active_state == max_active
|
||||||
|
|
||||||
# Bids with a combined value less than the offer value should both be accepted
|
# Bids with a combined value less than the offer value should both be accepted
|
||||||
test_bid_pair(1.1, 1.2, 0, self.delay_event)
|
test_bid_pair(1.1, 1.2, 2, self.delay_event)
|
||||||
|
|
||||||
# Only one bid of bids with a combined value greater than the offer value should be accepted
|
# Only one bid of bids with a combined value greater than the offer value should be accepted
|
||||||
test_bid_pair(1.1, 9.2, 1, self.delay_event)
|
test_bid_pair(1.1, 9.2, 1, self.delay_event)
|
||||||
|
@ -1006,7 +1045,7 @@ class Test(unittest.TestCase):
|
||||||
assert json_rv["note"] == "changed"
|
assert json_rv["note"] == "changed"
|
||||||
|
|
||||||
# Only one bid should be active
|
# Only one bid should be active
|
||||||
test_bid_pair(1.1, 1.2, 1, self.delay_event)
|
test_bid_pair(1.1, 1.2, 1, self.delay_event, 2)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
logging.debug("Reset max_concurrent_bids")
|
logging.debug("Reset max_concurrent_bids")
|
||||||
|
|
Loading…
Reference in a new issue