mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
Merge pull request #366 from tecnovert/expire_accepted
timeout bids before the script coin lock tx is mined.
This commit is contained in:
@@ -399,6 +399,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self._expire_db_records_after = self.get_int_setting(
|
self._expire_db_records_after = self.get_int_setting(
|
||||||
"expire_db_records_after", 7 * 86400, 0, 31 * 86400
|
"expire_db_records_after", 7 * 86400, 0, 31 * 86400
|
||||||
) # Seconds
|
) # Seconds
|
||||||
|
self._sc_lock_tx_timeout = self.get_int_setting(
|
||||||
|
"sc_lock_tx_timeout", 48 * 3600, 3600, 6 * 3600
|
||||||
|
) # Seconds
|
||||||
|
self._sc_lock_tx_mempool_timeout = self.get_int_setting(
|
||||||
|
"sc_lock_tx_mempool_timeout", 48 * 3600, 3600, 12 * 3600
|
||||||
|
) # Seconds
|
||||||
|
|
||||||
self._max_logfile_bytes = self.settings.get(
|
self._max_logfile_bytes = self.settings.get(
|
||||||
"max_logfile_size", 100
|
"max_logfile_size", 100
|
||||||
) # In MB. Set to 0 to disable truncation
|
) # In MB. Set to 0 to disable truncation
|
||||||
@@ -6029,6 +6036,20 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
and lock_tx_chain_info["height"] == 0
|
and lock_tx_chain_info["height"] == 0
|
||||||
):
|
):
|
||||||
bid.xmr_a_lock_tx.setState(TxStates.TX_IN_MEMPOOL)
|
bid.xmr_a_lock_tx.setState(TxStates.TX_IN_MEMPOOL)
|
||||||
|
self.logBidEvent(
|
||||||
|
bid.bid_id, EventLogTypes.LOCK_TX_A_IN_MEMPOOL, "", cursor
|
||||||
|
)
|
||||||
|
|
||||||
|
if "conflicts" in lock_tx_chain_info:
|
||||||
|
if (
|
||||||
|
self.countBidEvents(
|
||||||
|
bid, EventLogTypes.LOCK_TX_A_CONFLICTS, cursor
|
||||||
|
)
|
||||||
|
< 1
|
||||||
|
):
|
||||||
|
self.logBidEvent(
|
||||||
|
bid.bid_id, EventLogTypes.LOCK_TX_A_CONFLICTS, "", cursor
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not bid.xmr_a_lock_tx.chain_height
|
not bid.xmr_a_lock_tx.chain_height
|
||||||
@@ -7387,7 +7408,18 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
nonlocal num_messages, num_removed
|
nonlocal num_messages, num_removed
|
||||||
try:
|
try:
|
||||||
num_messages += 1
|
num_messages += 1
|
||||||
expire_at: int = msg["sent"] + msg["ttl"]
|
if "sent" not in msg:
|
||||||
|
# TODO: Always show time sent and ttl from core
|
||||||
|
options = {"encoding": "none", "export": True}
|
||||||
|
msg_data = ci_part.json_request(
|
||||||
|
rpc_conn, "smsg", [msg["msgid"], options]
|
||||||
|
)
|
||||||
|
msg_time: int = msg_data["sent"]
|
||||||
|
msg_ttl: int = msg_data["ttl"]
|
||||||
|
else:
|
||||||
|
msg_time: int = msg["sent"]
|
||||||
|
msg_ttl: int = msg["ttl"]
|
||||||
|
expire_at: int = msg_time + msg_ttl
|
||||||
if expire_at < now:
|
if expire_at < now:
|
||||||
options = {"encoding": "none", "delete": True}
|
options = {"encoding": "none", "delete": True}
|
||||||
ci_part.json_request(rpc_conn, "smsg", [msg["msgid"], options])
|
ci_part.json_request(rpc_conn, "smsg", [msg["msgid"], options])
|
||||||
@@ -7435,18 +7467,31 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
|
|
||||||
grace_period: int = 60 * 60
|
respond_grace_period: int = 60 * 60
|
||||||
|
# Time for transaction to be mined into the chain
|
||||||
|
# Only timeout waiting for the tx to be mined if not the sending the tx.
|
||||||
|
tx_grace_period: int = self._sc_lock_tx_timeout
|
||||||
|
tx_mempool_grace_period: int = self._sc_lock_tx_mempool_timeout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
query_str = (
|
query_str = (
|
||||||
"SELECT bid_id FROM bids "
|
"SELECT b.bid_id FROM bids AS b, bidstates AS s "
|
||||||
+ "WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now "
|
+ "WHERE b.active_ind = 1 AND s.state_id = b.state "
|
||||||
|
+ " AND ((b.state = :accepted_state AND b.expire_at + :respond_grace_period <= :now) "
|
||||||
|
+ " OR (s.can_timeout AND b.expire_at + (CASE WHEN EXISTS(SELECT event_id FROM eventlog WHERE linked_type = :event_linked_type AND linked_id = b.bid_id AND event_type = :tx_mempool_event_type) THEN :tx_mempool_grace_period ELSE :tx_grace_period END) <= :now)) "
|
||||||
|
+ " AND NOT EXISTS(SELECT event_id FROM eventlog WHERE linked_type = :event_linked_type AND linked_id = b.bid_id AND event_type = :tx_sent_event_type)"
|
||||||
)
|
)
|
||||||
q = cursor.execute(
|
q = cursor.execute(
|
||||||
query_str,
|
query_str,
|
||||||
{
|
{
|
||||||
"accepted_state": int(BidStates.BID_ACCEPTED),
|
"accepted_state": int(BidStates.BID_ACCEPTED),
|
||||||
"now": now,
|
"now": now,
|
||||||
"grace_period": grace_period,
|
"respond_grace_period": respond_grace_period,
|
||||||
|
"tx_grace_period": tx_grace_period,
|
||||||
|
"tx_mempool_grace_period": tx_mempool_grace_period,
|
||||||
|
"event_linked_type": int(Concepts.BID),
|
||||||
|
"tx_mempool_event_type": EventLogTypes.LOCK_TX_A_IN_MEMPOOL,
|
||||||
|
"tx_sent_event_type": EventLogTypes.LOCK_TX_A_PUBLISHED,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
for row in q:
|
for row in q:
|
||||||
@@ -10641,7 +10686,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
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, :bid_aad, :bid_aaf, :bid_req_sent) AND expire_at <= :check_time
|
query = """SELECT 1, b.bid_id, b.expire_at FROM bids AS b, bidstates AS s WHERE b.active_ind = 1 AND b.expire_at <= :check_time AND s.state_id = b.state AND s.can_expire
|
||||||
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
|
||||||
"""
|
"""
|
||||||
@@ -10651,11 +10696,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
"offer_received": int(OfferStates.OFFER_RECEIVED),
|
"offer_received": int(OfferStates.OFFER_RECEIVED),
|
||||||
"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),
|
|
||||||
"bid_req_sent": int(BidStates.BID_REQUEST_SENT),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
for entry in q:
|
for entry in q:
|
||||||
@@ -10673,22 +10713,17 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
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, :bid_aad, :bid_aaf, :bid_req_sent)"
|
query = "SELECT b.states FROM bids AS b, bidstates AS s WHERE b.bid_id = :bid_id AND b.active_ind = 1 AND s.state_id = b.state AND s.can_expire"
|
||||||
rows = cursor.execute(
|
rows = cursor.execute(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
"bid_id": bid_id,
|
"bid_id": bid_id,
|
||||||
"bid_received": int(BidStates.BID_RECEIVED),
|
|
||||||
"bid_sent": int(BidStates.BID_SENT),
|
|
||||||
"bid_aad": int(BidStates.BID_AACCEPT_DELAY),
|
|
||||||
"bid_aaf": int(BidStates.BID_AACCEPT_FAIL),
|
|
||||||
"bid_req_sent": int(BidStates.BID_REQUEST_SENT),
|
|
||||||
},
|
},
|
||||||
).fetchall()
|
).fetchall()
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
new_state: int = int(BidStates.BID_EXPIRED)
|
new_state: int = int(BidStates.BID_EXPIRED)
|
||||||
states = (
|
states = (
|
||||||
bytes() if rows[0][1] is None else rows[0][1]
|
bytes() if rows[0][0] is None else rows[0][0]
|
||||||
) + pack_state(new_state, now)
|
) + pack_state(new_state, now)
|
||||||
query = "UPDATE bids SET state = :new_state, states = :states WHERE bid_id = :bid_id"
|
query = "UPDATE bids SET state = :new_state, states = :states WHERE bid_id = :bid_id"
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
@@ -10697,7 +10732,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
bids_expired += 1
|
bids_expired += 1
|
||||||
for offer_id in offers_to_expire:
|
for offer_id in offers_to_expire:
|
||||||
query = "SELECT expire_at, states FROM offers WHERE offer_id = :offer_id AND active_ind = 1 AND state IN (:offer_received, :offer_sent)"
|
query = "SELECT states FROM offers WHERE offer_id = :offer_id AND active_ind = 1 AND state IN (:offer_received, :offer_sent)"
|
||||||
rows = cursor.execute(
|
rows = cursor.execute(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
@@ -10709,7 +10744,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
new_state: int = int(OfferStates.OFFER_EXPIRED)
|
new_state: int = int(OfferStates.OFFER_EXPIRED)
|
||||||
states = (
|
states = (
|
||||||
bytes() if rows[0][1] is None else rows[0][1]
|
bytes() if rows[0][0] is None else rows[0][0]
|
||||||
) + pack_state(new_state, now)
|
) + pack_state(new_state, now)
|
||||||
query = "UPDATE offers SET state = :new_state, states = :states WHERE offer_id = :offer_id"
|
query = "UPDATE offers SET state = :new_state, states = :states WHERE offer_id = :offer_id"
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
@@ -11704,6 +11739,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if offer_id is not None:
|
if offer_id is not None:
|
||||||
query_str += "AND bids.offer_id = :filter_offer_id "
|
query_str += "AND bids.offer_id = :filter_offer_id "
|
||||||
query_data["filter_offer_id"] = offer_id
|
query_data["filter_offer_id"] = offer_id
|
||||||
|
elif sent is None:
|
||||||
|
pass # Return both sent and received
|
||||||
elif sent:
|
elif sent:
|
||||||
query_str += "AND bids.was_sent = 1 "
|
query_str += "AND bids.was_sent = 1 "
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ class EventLogTypes(IntEnum):
|
|||||||
LOCK_TX_B_IN_MEMPOOL = auto()
|
LOCK_TX_B_IN_MEMPOOL = auto()
|
||||||
BCH_MERCY_TX_PUBLISHED = auto()
|
BCH_MERCY_TX_PUBLISHED = auto()
|
||||||
BCH_MERCY_TX_FOUND = auto()
|
BCH_MERCY_TX_FOUND = auto()
|
||||||
|
LOCK_TX_A_IN_MEMPOOL = auto()
|
||||||
|
LOCK_TX_A_CONFLICTS = auto()
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
@@ -436,6 +438,10 @@ def describeEventEntry(event_type, event_msg):
|
|||||||
return "Lock tx B published"
|
return "Lock tx B published"
|
||||||
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
||||||
return "Failed to publish lock tx B spend: " + event_msg
|
return "Failed to publish lock tx B spend: " + event_msg
|
||||||
|
if event_type == EventLogTypes.LOCK_TX_A_IN_MEMPOOL:
|
||||||
|
return "Lock tx A seen in mempool"
|
||||||
|
if event_type == EventLogTypes.LOCK_TX_A_CONFLICTS:
|
||||||
|
return "Lock tx A conflicting txn/s"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
||||||
return "Lock tx A seen in chain"
|
return "Lock tx A seen in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
||||||
@@ -605,6 +611,26 @@ def canAcceptBidState(state):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def canExpireBidState(state):
|
||||||
|
return state in (
|
||||||
|
BidStates.BID_SENT,
|
||||||
|
BidStates.BID_RECEIVING,
|
||||||
|
BidStates.BID_RECEIVED,
|
||||||
|
BidStates.BID_AACCEPT_DELAY,
|
||||||
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
|
BidStates.BID_REQUEST_SENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def canTimeoutBidState(state):
|
||||||
|
return state in (
|
||||||
|
BidStates.BID_ACCEPTED,
|
||||||
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
|
||||||
|
BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX,
|
||||||
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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 = 30
|
CURRENT_DB_VERSION = 31
|
||||||
CURRENT_DB_DATA_VERSION = 6
|
CURRENT_DB_DATA_VERSION = 7
|
||||||
|
|
||||||
|
|
||||||
class Concepts(IntEnum):
|
class Concepts(IntEnum):
|
||||||
@@ -619,6 +619,8 @@ class BidState(Table):
|
|||||||
swap_failed = Column("integer")
|
swap_failed = Column("integer")
|
||||||
swap_ended = Column("integer")
|
swap_ended = Column("integer")
|
||||||
can_accept = Column("integer")
|
can_accept = Column("integer")
|
||||||
|
can_expire = Column("integer")
|
||||||
|
can_timeout = Column("integer")
|
||||||
|
|
||||||
note = Column("string")
|
note = Column("string")
|
||||||
created_at = Column("integer")
|
created_at = Column("integer")
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ from .db import (
|
|||||||
from .basicswap_util import (
|
from .basicswap_util import (
|
||||||
BidStates,
|
BidStates,
|
||||||
canAcceptBidState,
|
canAcceptBidState,
|
||||||
|
canExpireBidState,
|
||||||
|
canTimeoutBidState,
|
||||||
isActiveBidState,
|
isActiveBidState,
|
||||||
isErrorBidState,
|
isErrorBidState,
|
||||||
isFailingBidState,
|
isFailingBidState,
|
||||||
@@ -39,6 +41,8 @@ def addBidState(self, state, now, cursor):
|
|||||||
swap_failed=isFailingBidState(state),
|
swap_failed=isFailingBidState(state),
|
||||||
swap_ended=isFinalBidState(state),
|
swap_ended=isFinalBidState(state),
|
||||||
can_accept=canAcceptBidState(state),
|
can_accept=canAcceptBidState(state),
|
||||||
|
can_expire=canExpireBidState(state),
|
||||||
|
can_timeout=canTimeoutBidState(state),
|
||||||
label=strBidState(state),
|
label=strBidState(state),
|
||||||
created_at=now,
|
created_at=now,
|
||||||
),
|
),
|
||||||
@@ -105,19 +109,23 @@ def upgradeDatabaseData(self, data_version):
|
|||||||
),
|
),
|
||||||
cursor,
|
cursor,
|
||||||
)
|
)
|
||||||
if data_version > 0 and data_version < 6:
|
if data_version > 0 and data_version < 7:
|
||||||
for state in BidStates:
|
for state in BidStates:
|
||||||
in_error = isErrorBidState(state)
|
in_error = isErrorBidState(state)
|
||||||
swap_failed = isFailingBidState(state)
|
swap_failed = isFailingBidState(state)
|
||||||
swap_ended = isFinalBidState(state)
|
swap_ended = isFinalBidState(state)
|
||||||
can_accept = canAcceptBidState(state)
|
can_accept = canAcceptBidState(state)
|
||||||
|
can_expire = canExpireBidState(state)
|
||||||
|
can_timeout = canTimeoutBidState(state)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"UPDATE bidstates SET can_accept = :can_accept, in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id",
|
"UPDATE bidstates SET can_accept = :can_accept, can_expire = :can_expire, can_timeout = :can_timeout, in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id",
|
||||||
{
|
{
|
||||||
"in_error": in_error,
|
"in_error": in_error,
|
||||||
"swap_failed": swap_failed,
|
"swap_failed": swap_failed,
|
||||||
"swap_ended": swap_ended,
|
"swap_ended": swap_ended,
|
||||||
"can_accept": can_accept,
|
"can_accept": can_accept,
|
||||||
|
"can_expire": can_expire,
|
||||||
|
"can_timeout": can_timeout,
|
||||||
"state_id": int(state),
|
"state_id": int(state),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
self._use_descriptors = coin_settings.get("use_descriptors", False)
|
self._use_descriptors = coin_settings.get("use_descriptors", False)
|
||||||
# Use hardened account indices to match existing wallet keys, only applies when use_descriptors is True
|
# Use hardened account indices to match existing wallet keys, only applies when use_descriptors is True
|
||||||
self._use_legacy_key_paths = coin_settings.get("use_legacy_key_paths", False)
|
self._use_legacy_key_paths = coin_settings.get("use_legacy_key_paths", False)
|
||||||
|
self._disable_lock_tx_rbf = False
|
||||||
|
|
||||||
def open_rpc(self, wallet=None):
|
def open_rpc(self, wallet=None):
|
||||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||||
@@ -775,8 +776,15 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
|
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None) -> bytes:
|
||||||
return self.fundTx(tx_bytes, feerate)
|
funded_tx = self.fundTx(tx_bytes, feerate)
|
||||||
|
|
||||||
|
if self._disable_lock_tx_rbf:
|
||||||
|
tx = self.loadTx(funded_tx)
|
||||||
|
for txi in tx.vin:
|
||||||
|
txi.nSequence = 0xFFFFFFFE
|
||||||
|
funded_tx = tx.serialize_with_witness()
|
||||||
|
return funded_tx
|
||||||
|
|
||||||
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
|
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
|
||||||
|
|
||||||
@@ -1784,6 +1792,10 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
"height": block_height,
|
"height": block_height,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "mempoolconflicts" in tx and len(tx["mempoolconflicts"]) > 0:
|
||||||
|
rv["conflicts"] = tx["mempoolconflicts"]
|
||||||
|
elif "walletconflicts" in tx and len(tx["walletconflicts"]) > 0:
|
||||||
|
rv["conflicts"] = tx["walletconflicts"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
|
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
|
||||||
|
|||||||
@@ -619,12 +619,9 @@ class TestFunctions(BaseTest):
|
|||||||
assert node1_to_before - node1_to_after < max_fee_to
|
assert node1_to_before - node1_to_after < max_fee_to
|
||||||
|
|
||||||
def do_test_05_self_bid(self, coin_from, coin_to):
|
def do_test_05_self_bid(self, coin_from, coin_to):
|
||||||
logging.info(
|
logging.info(f"---------- Test {coin_from.name} to {coin_to.name} Same Client")
|
||||||
"---------- Test {} to {} same client".format(coin_from.name, coin_to.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
id_both: int = self.node_b_id
|
id_both: int = self.node_b_id
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci_from = swap_clients[id_both].ci(coin_from)
|
ci_from = swap_clients[id_both].ci(coin_from)
|
||||||
ci_to = swap_clients[id_both].ci(coin_to)
|
ci_to = swap_clients[id_both].ci(coin_to)
|
||||||
@@ -653,10 +650,9 @@ class TestFunctions(BaseTest):
|
|||||||
|
|
||||||
def do_test_08_insufficient_funds(self, coin_from, coin_to):
|
def do_test_08_insufficient_funds(self, coin_from, coin_to):
|
||||||
logging.info(
|
logging.info(
|
||||||
"---------- Test {} to {} Insufficient Funds".format(
|
f"---------- Test {coin_from.name} to {coin_to.name} Insufficient Funds"
|
||||||
coin_from.name, coin_to.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||||
|
|
||||||
@@ -775,6 +771,144 @@ class TestFunctions(BaseTest):
|
|||||||
else:
|
else:
|
||||||
assert False, "Should fail"
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
def do_test_09_expire_accepted(self, coin_from, coin_to):
|
||||||
|
logging.info(
|
||||||
|
f"---------- Test {coin_from.name} to {coin_to.name} Expire Accepted"
|
||||||
|
)
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||||
|
|
||||||
|
id_offerer: int = self.node_a_id
|
||||||
|
id_bidder: int = self.node_b_id
|
||||||
|
|
||||||
|
# Leader sends the initial (chain a) lock tx.
|
||||||
|
# Follower sends the participate (chain b) lock tx.
|
||||||
|
id_leader: int = id_bidder if reverse_bid else id_offerer
|
||||||
|
id_follower: int = id_offerer if reverse_bid else id_bidder
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||||
|
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||||
|
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||||
|
|
||||||
|
self.prepare_balance(
|
||||||
|
coin_from, 100.0, 1800 + id_offerer, 1801 if reverse_bid else 1800
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
offer_id = swap_clients[id_offerer].postOffer(
|
||||||
|
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP
|
||||||
|
)
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||||
|
offer = swap_clients[id_bidder].listOffers(filters={"offer_id": offer_id})[0]
|
||||||
|
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
bid_id,
|
||||||
|
BidStates.BID_RECEIVED,
|
||||||
|
wait_for=(self.extra_wait_time + 40),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Stop BTC mining
|
||||||
|
old_btc_addr: str = self.__class__.btc_addr
|
||||||
|
self.__class__.btc_addr = None
|
||||||
|
old_check_expired_seconds = swap_clients[0].check_expired_seconds
|
||||||
|
|
||||||
|
swap_clients[id_offerer].acceptBid(bid_id)
|
||||||
|
|
||||||
|
wait_for_event(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_follower],
|
||||||
|
Concepts.BID,
|
||||||
|
bid_id,
|
||||||
|
event_type=EventLogTypes.LOCK_TX_A_IN_MEMPOOL,
|
||||||
|
wait_for=90,
|
||||||
|
)
|
||||||
|
|
||||||
|
post_json = {"show_extra": True}
|
||||||
|
bid = read_json_api(1800 + id_leader, f"bids/{bid_id.hex()}", post_json)
|
||||||
|
|
||||||
|
chain_a_lock_txid = None
|
||||||
|
for tx in bid["txns"]:
|
||||||
|
if tx["type"] == "Chain A Lock":
|
||||||
|
chain_a_lock_txid = tx["txid"]
|
||||||
|
assert chain_a_lock_txid
|
||||||
|
|
||||||
|
ci_btc_l = swap_clients[id_leader].ci(Coins.BTC)
|
||||||
|
rv = ci_btc_l.rpc_wallet(
|
||||||
|
"psbtbumpfee",
|
||||||
|
[
|
||||||
|
chain_a_lock_txid,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
rv = ci_btc_l.rpc_wallet(
|
||||||
|
"walletprocesspsbt",
|
||||||
|
[
|
||||||
|
rv["psbt"],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
rv = ci_btc_l.rpc_wallet(
|
||||||
|
"finalizepsbt",
|
||||||
|
[
|
||||||
|
rv["psbt"],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
new_tx_hex = rv["hex"]
|
||||||
|
rv = ci_btc_l.rpc_wallet(
|
||||||
|
"sendrawtransaction",
|
||||||
|
[
|
||||||
|
new_tx_hex,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert rv != chain_a_lock_txid
|
||||||
|
|
||||||
|
wait_for_event(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_follower],
|
||||||
|
Concepts.BID,
|
||||||
|
bid_id,
|
||||||
|
event_type=EventLogTypes.LOCK_TX_A_CONFLICTS,
|
||||||
|
wait_for=90,
|
||||||
|
)
|
||||||
|
# Mine the replacement tx
|
||||||
|
ci_btc_l.rpc_wallet("generatetoaddress", [1, old_btc_addr])
|
||||||
|
|
||||||
|
swap_clients[0].setMockTimeOffset(13 * 3600)
|
||||||
|
swap_clients[1].setMockTimeOffset(13 * 3600)
|
||||||
|
swap_clients[0].check_expired_seconds = 2
|
||||||
|
swap_clients[1].check_expired_seconds = 2
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_follower],
|
||||||
|
bid_id,
|
||||||
|
BidStates.SWAP_TIMEDOUT,
|
||||||
|
sent=None,
|
||||||
|
wait_for=(self.extra_wait_time + 40),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Leader (which funded the lock tx) should not timeout.
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_leader],
|
||||||
|
bid_id,
|
||||||
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
|
||||||
|
sent=None,
|
||||||
|
wait_for=(self.extra_wait_time + 40),
|
||||||
|
)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Restore BTC mining:
|
||||||
|
self.__class__.btc_addr = old_btc_addr
|
||||||
|
swap_clients[0].setMockTimeOffset(0)
|
||||||
|
swap_clients[1].setMockTimeOffset(0)
|
||||||
|
swap_clients[0].check_expired_seconds = old_check_expired_seconds
|
||||||
|
swap_clients[1].check_expired_seconds = old_check_expired_seconds
|
||||||
|
|
||||||
|
|
||||||
class BasicSwapTest(TestFunctions):
|
class BasicSwapTest(TestFunctions):
|
||||||
|
|
||||||
@@ -2155,6 +2289,12 @@ class BasicSwapTest(TestFunctions):
|
|||||||
def test_08_insufficient_funds_rev(self):
|
def test_08_insufficient_funds_rev(self):
|
||||||
self.do_test_08_insufficient_funds(Coins.XMR, self.test_coin_from)
|
self.do_test_08_insufficient_funds(Coins.XMR, self.test_coin_from)
|
||||||
|
|
||||||
|
def test_09_expire_accepted(self):
|
||||||
|
self.do_test_09_expire_accepted(self.test_coin_from, Coins.XMR)
|
||||||
|
|
||||||
|
def test_09_expire_accepted_rev(self):
|
||||||
|
self.do_test_09_expire_accepted(Coins.XMR, self.test_coin_from)
|
||||||
|
|
||||||
|
|
||||||
class TestBTC(BasicSwapTest):
|
class TestBTC(BasicSwapTest):
|
||||||
__test__ = True
|
__test__ = True
|
||||||
|
|||||||
Reference in New Issue
Block a user