refactor: backports

This commit is contained in:
tecnovert
2026-04-04 21:42:39 +02:00
parent 3c76454e68
commit 360d356cbf
11 changed files with 103 additions and 47 deletions

View File

@@ -3964,6 +3964,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception as e: except Exception as e:
raise ValueError(f"Invalid message networks: {e}") raise ValueError(f"Invalid message networks: {e}")
def isValidSwapDest(self, ci, dest: bytes):
ensure(isinstance(dest, bytes), "Swap destination must be bytes")
if ci.coin_type() in (Coins.PART_BLIND,):
return ci.isValidPubkey(dest)
# TODO: allow p2wsh
return ci.isValidAddressHash(dest)
def ensureWalletCanSend( def ensureWalletCanSend(
self, ci, swap_type, ensure_balance: int, estimated_fee: int, for_offer=True self, ci, swap_type, ensure_balance: int, estimated_fee: int, for_offer=True
) -> None: ) -> None:
@@ -5365,12 +5372,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.loadBidTxns(bid, cursor) self.loadBidTxns(bid, cursor)
return bid, xmr_swap return bid, xmr_swap
def getXmrBid(self, bid_id: bytes): def getXmrBid(self, bid_id: bytes, cursor=None):
try: try:
cursor = self.openDB() use_cursor = self.openDB(cursor)
return self.getXmrBidFromSession(cursor, bid_id) return self.getXmrBidFromSession(use_cursor, bid_id)
finally: finally:
self.closeDB(cursor, commit=False) if cursor is None:
self.closeDB(use_cursor, commit=False)
def getXmrOfferFromSession(self, cursor, offer_id: bytes): def getXmrOfferFromSession(self, cursor, offer_id: bytes):
offer = self.queryOne(Offer, cursor, {"offer_id": offer_id}) offer = self.queryOne(Offer, cursor, {"offer_id": offer_id})
@@ -5875,7 +5883,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
if bid.bid_id and bid_msg_id != bid.bid_id: if bid.bid_id and bid_msg_id != bid.bid_id:
self.log.warning( self.log.warning(
f"sendBidMessage: Mismatched bid ids: {bid.bid_id.hex()}, {bid_msg_id.hex()}." f"sendBidMessage: Mismatched bid ids: {self.log.id(bid.bid_id)}, {self.log.id(bid_msg_id)}."
) )
return bid_msg_id return bid_msg_id
@@ -7771,7 +7779,12 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
len(xmr_swap.al_lock_refund_tx_sig) > 0 len(xmr_swap.al_lock_refund_tx_sig) > 0
and len(xmr_swap.af_lock_refund_tx_sig) > 0 and len(xmr_swap.af_lock_refund_tx_sig) > 0
): ):
try: try:
if bid.xmr_b_lock_tx is None and self.haveDebugInd(
bid.bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_PREREFUND
):
raise TemporaryError("Debug: Waiting for Coin B Lock Tx")
txid = ci_from.publishTx(xmr_swap.a_lock_refund_tx) txid = ci_from.publishTx(xmr_swap.a_lock_refund_tx)
# BCH txids change # BCH txids change
@@ -7821,6 +7834,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.saveBidInSession(bid_id, bid, cursor, xmr_swap) self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB() self.commitDB()
return rv return rv
else:
self.log.warning(
f"Trying to publish coin a lock refund tx: {ex}"
)
state = BidStates(bid.state) state = BidStates(bid.state)
if state == BidStates.SWAP_COMPLETED: if state == BidStates.SWAP_COMPLETED:
@@ -8067,6 +8084,15 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.warning( self.log.warning(
f"Not releasing ads script coin lock tx for bid {self.log.id(bid_id)}: Chain A lock refund tx already exists." f"Not releasing ads script coin lock tx for bid {self.log.id(bid_id)}: Chain A lock refund tx already exists."
) )
elif (
bid.debug_ind == DebugTypes.DONT_RELEASE_COIN_A_LOCK
):
self.logBidEvent(
bid_id,
EventLogTypes.DEBUG_TWEAK_APPLIED,
f"ind {DebugTypes.DONT_RELEASE_COIN_A_LOCK}",
None,
)
else: else:
delay = self.get_delay_event_seconds() delay = self.get_delay_event_seconds()
self.log.info( self.log.info(
@@ -8513,9 +8539,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
watched = self.coin_clients[coin_type]["watched_transactions"] watched = self.coin_clients[coin_type]["watched_transactions"]
for wt in watched:
for wo in watched: if wt.bid_id == bid_id and wt.txid_hex == txid_hex:
if wo.bid_id == bid_id and wo.txid_hex == txid_hex:
self.log.debug("Transaction already being watched.") self.log.debug("Transaction already being watched.")
return return
@@ -8953,7 +8978,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
else: else:
self.log.info( self.log.info(
f"Coin a lock refund spent by unknown tx, bid {self.log.id(bid_id)}." f"Coin a lock refund spent by unknown tx, bid {self.log.id(bid_id)}, txid {self.logIDT(spending_txid)}."
) )
mercy_keyshare = None mercy_keyshare = None
@@ -8980,6 +9005,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED) bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED)
else: else:
delay = self.get_delay_event_seconds() delay = self.get_delay_event_seconds()
self.log.info("Found mercy output.")
self.log.info( self.log.info(
f"Redeeming coin b lock tx for bid {self.log.id(bid_id)} in {delay} seconds." f"Redeeming coin b lock tx for bid {self.log.id(bid_id)} in {delay} seconds."
) )
@@ -9298,6 +9324,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.processFoundTransaction( self.processFoundTransaction(
t, block_hash, block["height"], chain_blocks t, block_hash, block["height"], chain_blocks
) )
for s in c["watched_scripts"]: for s in c["watched_scripts"]:
for i, txo in enumerate(tx["vout"]): for i, txo in enumerate(tx["vout"]):
if "scriptPubKey" in txo and "hex" in txo["scriptPubKey"]: if "scriptPubKey" in txo and "hex" in txo["scriptPubKey"]:
@@ -12174,7 +12201,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES( xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(
kbsl, xmr_swap.af_lock_refund_spend_tx_esig kbsl, xmr_swap.af_lock_refund_spend_tx_esig
) )
prevout_amount = ci_from.getLockRefundTxSwapOutputValue(bid, xmr_swap) prevout_amount: int = ci_from.getLockRefundTxSwapOutputValue(
bid, xmr_swap
)
al_lock_refund_spend_tx_sig = ci_from.signTx( al_lock_refund_spend_tx_sig = ci_from.signTx(
kal, kal,
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_spend_tx,
@@ -12601,8 +12630,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
ci_from.verifyPubkey(msg_data.pkaf), "Invalid chain A follower public key" ci_from.verifyPubkey(msg_data.pkaf), "Invalid chain A follower public key"
) )
ensure( ensure(
ci_from.isValidAddressHash(msg_data.dest_af) self.isValidSwapDest(ci_from, msg_data.dest_af),
or ci_from.isValidPubkey(msg_data.dest_af),
"Invalid destination address", "Invalid destination address",
) )
if ci_to.curve_type() == Curves.ed25519: if ci_to.curve_type() == Curves.ed25519:

View File

@@ -240,8 +240,11 @@ class DebugTypes(IntEnum):
OFFER_LOCK_2_VALUE_INC = auto() OFFER_LOCK_2_VALUE_INC = auto()
BID_STOP_AFTER_COIN_B_LOCK = auto() BID_STOP_AFTER_COIN_B_LOCK = auto()
BID_DONT_SPEND_COIN_B_LOCK = auto() BID_DONT_SPEND_COIN_B_LOCK = auto()
WAIT_FOR_COIN_B_LOCK_BEFORE_PREREFUND = auto()
WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto() WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto()
BID_DONT_SPEND_COIN_A_LOCK = auto() BID_DONT_SPEND_COIN_A_LOCK = auto()
DONT_SEND_COIN_B_LOCK = auto()
DONT_RELEASE_COIN_A_LOCK = auto()
class NotificationTypes(IntEnum): class NotificationTypes(IntEnum):

View File

@@ -566,7 +566,7 @@ def getCoinIdFromTicker(ticker: str) -> str:
raise ValueError(f"Unknown coin {ticker}") raise ValueError(f"Unknown coin {ticker}")
def getCoinIdFromName(name: str) -> str: def getCoinIdFromName(name: str) -> Coins:
try: try:
return name_map[name.lower()] return name_map[name.lower()]
except Exception: except Exception:

View File

@@ -1229,7 +1229,6 @@ class DBMethods:
values = {} values = {}
constraint_values = {} constraint_values = {}
set_columns = [] set_columns = []
for mc in inspect.getmembers(obj.__class__): for mc in inspect.getmembers(obj.__class__):
mc_name, mc_obj = mc mc_name, mc_obj = mc
if not hasattr(mc_obj, "__sqlite3_column__"): if not hasattr(mc_obj, "__sqlite3_column__"):

View File

@@ -7,6 +7,7 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import base64 import base64
import copy
import hashlib import hashlib
import json import json
import logging import logging
@@ -530,6 +531,16 @@ class BTCInterface(Secp256k1Interface):
) )
return self.rpc("getblockheader", [block_hash]) return self.rpc("getblockheader", [block_hash])
def getPrevBlockInChain(self, block_header_in):
block_header = copy.deeocopy(block_header_in)
while True:
previousblockhash = block_header.get("previousblockhash", None)
if previousblockhash is None:
raise RuntimeError("previousblockhash not in block_header")
if block_header["confirmations"] > 0:
return block_header
block_header = self.rpc("getblockheader", [previousblockhash])
def getBlockHeaderAt(self, time_target: int, block_after=False): def getBlockHeaderAt(self, time_target: int, block_after=False):
blockchaininfo = self.rpc("getblockchaininfo") blockchaininfo = self.rpc("getblockchaininfo")
last_block_header = self.rpc( last_block_header = self.rpc(
@@ -1310,7 +1321,7 @@ class BTCInterface(Secp256k1Interface):
vkbv=None, vkbv=None,
kbsf=None, kbsf=None,
): ):
# lock refund swipe tx # Lock refund swipe tx
# Sends the coinA locked coin to the follower # Sends the coinA locked coin to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes) tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
@@ -1584,7 +1595,7 @@ class BTCInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return txid, locked_coin, locked_n return txid, locked_coin, locked_n
@@ -1647,7 +1658,7 @@ class BTCInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return True return True
@@ -1706,7 +1717,7 @@ class BTCInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return True return True

View File

@@ -1403,7 +1403,7 @@ class DCRInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return True return True
@@ -1472,7 +1472,7 @@ class DCRInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return txid, locked_coin, locked_n return txid, locked_coin, locked_n
@@ -1533,7 +1533,7 @@ class DCRInterface(Secp256k1Interface):
) )
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return True return True

View File

@@ -658,7 +658,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure( ensure(
self.compareFeeRates(fee_rate_paid, feerate), self.compareFeeRates(fee_rate_paid, feerate),
"Bad fee rate, expected: {}".format(feerate), f"Bad fee rate, expected: {feerate}",
) )
return ( return (
@@ -734,7 +734,7 @@ class PARTInterfaceBlind(PARTInterface):
fee_rate_paid = fee_paid * 1000 // vsize fee_rate_paid = fee_paid * 1000 // vsize
ensure( ensure(
self.compareFeeRates(fee_rate_paid, feerate), self.compareFeeRates(fee_rate_paid, feerate),
"Bad fee rate, expected: {}".format(feerate), f"Bad fee rate, expected: {feerate}",
) )
return True return True
@@ -958,7 +958,7 @@ class PARTInterfaceBlind(PARTInterface):
fee_rate_paid = fee_paid * 1000 // vsize fee_rate_paid = fee_paid * 1000 // vsize
self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid) self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid)
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError("Bad fee rate, expected: {}".format(feerate)) raise ValueError(f"Bad fee rate, expected: {feerate}")
return True return True

View File

@@ -45,3 +45,21 @@ class BSXLogger(logging.Logger):
def info_s(self, msg, *args, **kwargs): def info_s(self, msg, *args, **kwargs):
if self.safe_logs is False: if self.safe_logs is False:
self.info(msg, *args, **kwargs) self.info(msg, *args, **kwargs)
class BSXLogAdapter(logging.LoggerAdapter):
def __init__(self, logger, prefix):
super().__init__(logger, {})
self.prefix = prefix
def process(self, msg, kwargs):
return f"{self.prefix} {msg}", kwargs
def addr(self, addr: str) -> str:
return self.logger.addr(addr)
def id(self, concept_id: bytes, prefix: str = "") -> str:
return self.logger.id(concept_id, prefix)
def info_s(self, msg, *args, **kwargs):
return self.logger.info_s(msg, *args, **kwargs)

View File

@@ -290,7 +290,7 @@ def wait_for_event(
def wait_for_offer(delay_event, swap_client, offer_id, wait_for=20): def wait_for_offer(delay_event, swap_client, offer_id, wait_for=20):
logging.info("wait_for_offer %s", offer_id.hex()) logging.info(f"wait_for_offer {offer_id.hex()}")
for i in range(wait_for): for i in range(wait_for):
if delay_event.is_set(): if delay_event.is_set():
raise ValueError("Test stopped.") raise ValueError("Test stopped.")

View File

@@ -229,22 +229,24 @@ class Test(unittest.TestCase):
== "0dde9df8660d3e0f28fe00d648b70e0323e9c192fe9b94f1cf7138515e877725" == "0dde9df8660d3e0f28fe00d648b70e0323e9c192fe9b94f1cf7138515e877725"
) )
sum_secp256k1 = ci_btc.sumPubkeys( pk_secp256k1 = ci_btc.sumPubkeys(
ci_btc.getPubkey(keys[0]), ci_btc.getPubkey(keys[1]) ci_btc.getPubkey(keys[0]), ci_btc.getPubkey(keys[1])
) )
assert ( assert (
sum_secp256k1.hex() pk_secp256k1.hex()
== "028c30392e35620af0787b363a03cf9a695336759664436e1f609481c869541a5c" == "028c30392e35620af0787b363a03cf9a695336759664436e1f609481c869541a5c"
) )
sum_ed25519 = ci_xmr.sumPubkeys( pk_ed25519 = ci_xmr.sumPubkeys(
ci_xmr.getPubkey(keys[0]), ci_xmr.getPubkey(keys[1]) ci_xmr.getPubkey(keys[0]), ci_xmr.getPubkey(keys[1])
) )
assert ( assert (
sum_ed25519.hex() pk_ed25519.hex()
== "4b2dd2dc9acc9be7efed4fdbfb96f0002aeb9e4c8638c5b24562a7158b283626" == "4b2dd2dc9acc9be7efed4fdbfb96f0002aeb9e4c8638c5b24562a7158b283626"
) )
assert pk_secp256k1 == ci_btc.getPubkey(sum_secp256k1)
def test_ecdsa_otves(self): def test_ecdsa_otves(self):
ci = self.ci_btc() ci = self.ci_btc()
vk_sign = ci.getNewRandomKey() vk_sign = ci.getNewRandomKey()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 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.
@@ -263,9 +263,7 @@ def waitForXMRNode(rpc_offset, max_tries=7):
except Exception as ex: except Exception as ex:
if i < max_tries: if i < max_tries:
logging.warning( logging.warning(
"Can't connect to XMR RPC: %s. Retrying in %d second/s.", f"Can't connect to XMR RPC: {ex}. Retrying in {i + 1} second/s."
str(ex),
(i + 1),
) )
time.sleep(i + 1) time.sleep(i + 1)
raise ValueError("waitForXMRNode failed") raise ValueError("waitForXMRNode failed")
@@ -281,9 +279,7 @@ def waitForXMRWallet(rpc_offset, auth, max_tries=7):
except Exception as ex: except Exception as ex:
if i < max_tries: if i < max_tries:
logging.warning( logging.warning(
"Can't connect to XMR wallet RPC: %s. Retrying in %d second/s.", f"Can't connect to XMR wallet RPC: {ex}. Retrying in {i + 1} second/s."
str(ex),
(i + 1),
) )
time.sleep(i + 1) time.sleep(i + 1)
raise ValueError("waitForXMRWallet failed") raise ValueError("waitForXMRWallet failed")
@@ -304,7 +300,7 @@ def run_coins_loop(cls):
cls.coins_loop() cls.coins_loop()
except Exception as e: except Exception as e:
logging.warning("run_coins_loop " + str(e)) logging.warning("run_coins_loop " + str(e))
test_delay_event.wait(1.0) test_delay_event.wait(cls.coins_loop_delay)
def run_loop(cls): def run_loop(cls):
@@ -336,6 +332,7 @@ class BaseTest(unittest.TestCase):
xmr_addr = None xmr_addr = None
btc_addr = None btc_addr = None
ltc_addr = None ltc_addr = None
coins_loop_delay = 1.0
@classmethod @classmethod
def getRandomPubkey(cls): def getRandomPubkey(cls):
@@ -345,7 +342,7 @@ class BaseTest(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
if signal_event.is_set(): if signal_event.is_set():
raise ValueError("Test has been cancelled.") raise ValueError("Test has been cancelled")
test_delay_event.clear() test_delay_event.clear()
random.seed(time.time()) random.seed(time.time())
@@ -400,7 +397,7 @@ class BaseTest(unittest.TestCase):
cls.prepareTestDir() cls.prepareTestDir()
try: try:
logging.info("Preparing coin nodes.") logging.info("Preparing coin nodes")
part_wallet_bin = "particl-wallet" + (".exe" if os.name == "nt" else "") part_wallet_bin = "particl-wallet" + (".exe" if os.name == "nt" else "")
for i in range(NUM_NODES): for i in range(NUM_NODES):
if not cls.restore_instance: if not cls.restore_instance:
@@ -411,7 +408,7 @@ class BaseTest(unittest.TestCase):
part_wallet_bin, part_wallet_bin,
) )
): ):
logging.warning(f"{part_wallet_bin} not found.") logging.warning(f"{part_wallet_bin} not found")
else: else:
try: try:
callrpc_cli( callrpc_cli(
@@ -623,10 +620,8 @@ class BaseTest(unittest.TestCase):
) )
for i in range(NUM_XMR_NODES): for i in range(NUM_XMR_NODES):
cls.xmr_wallet_auth.append( cls.xmr_wallet_auth.append((f"test{i}", f"test_pass{i}"))
("test{0}".format(i), "test_pass{0}".format(i)) logging.info(f"Creating XMR wallet {i}")
)
logging.info("Creating XMR wallet %i", i)
waitForXMRWallet(i, cls.xmr_wallet_auth[i]) waitForXMRWallet(i, cls.xmr_wallet_auth[i])
@@ -732,7 +727,7 @@ class BaseTest(unittest.TestCase):
wallet="wallet.dat", wallet="wallet.dat",
) )
num_blocks = 400 # Mine enough to activate segwit num_blocks = 400 # Mine enough to activate segwit
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr) logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
callnoderpc( callnoderpc(
0, 0,
"generatetoaddress", "generatetoaddress",
@@ -763,7 +758,7 @@ class BaseTest(unittest.TestCase):
.ci(Coins.BTC) .ci(Coins.BTC)
.pubkey_to_segwit_address(void_block_rewards_pubkey) .pubkey_to_segwit_address(void_block_rewards_pubkey)
) )
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr) logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
callnoderpc( callnoderpc(
0, 0,
"generatetoaddress", "generatetoaddress",
@@ -801,7 +796,7 @@ class BaseTest(unittest.TestCase):
wallet="wallet.dat", wallet="wallet.dat",
) )
logging.info( logging.info(
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr f"Mining {num_blocks} Litecoin blocks to {cls.ltc_addr}"
) )
callnoderpc( callnoderpc(
0, 0,