6 Commits

Author SHA1 Message Date
tecnovert
69f607d28f Merge pull request #408 from tecnovert/backports
Backports
2025-12-25 18:55:12 +00:00
tecnovert
e1bca8e384 backports 2025-12-25 13:18:48 +02:00
tecnovert
6fd324ec9f refactor: split watched classes into new file 2025-12-25 13:13:46 +02:00
tecnovert
b6af5ee93d fix delay checking for expired bids and offers 2025-12-25 13:10:38 +02:00
tecnovert
289c66e2e5 Merge pull request #407 from tecnovert/coincurve
Update coincurve to basicswap_v0.3
2025-12-02 07:35:07 +00:00
tecnovert
bcec90feca build(deps): use rebased coincurve version 2025-11-26 11:02:36 +02:00
11 changed files with 260 additions and 270 deletions

View File

@@ -72,6 +72,7 @@ from .db_util import remove_expired_data
from .http_server import HttpThread from .http_server import HttpThread
from .rpc import escape_rpcauth from .rpc import escape_rpcauth
from .rpc_xmr import make_xmr_rpc2_func from .rpc_xmr import make_xmr_rpc2_func
from .types import WatchedTransaction, WatchedScript, WatchedOutput
from .ui.app import UIApp from .ui.app import UIApp
from .ui.util import getCoinName from .ui.util import getCoinName
from .util import ( from .util import (
@@ -287,37 +288,6 @@ def threadPollChainState(swap_client, coin_type):
swap_client.chainstate_delay_event.wait(random.randrange(*poll_delay_range)) swap_client.chainstate_delay_event.wait(random.randrange(*poll_delay_range))
class WatchedOutput: # Watch for spends
__slots__ = ("bid_id", "txid_hex", "vout", "tx_type", "swap_type")
def __init__(self, bid_id: bytes, txid_hex: str, vout, tx_type, swap_type):
self.bid_id = bid_id
self.txid_hex = txid_hex
self.vout = vout
self.tx_type = tx_type
self.swap_type = swap_type
class WatchedScript: # Watch for txns containing outputs
__slots__ = ("bid_id", "script", "tx_type", "swap_type")
def __init__(self, bid_id: bytes, script: bytes, tx_type, swap_type):
self.bid_id = bid_id
self.script = script
self.tx_type = tx_type
self.swap_type = swap_type
class WatchedTransaction:
# TODO
# Watch for presence in mempool (getrawtransaction)
def __init__(self, bid_id: bytes, txid_hex: str, tx_type, swap_type):
self.bid_id = bid_id
self.txid_hex = txid_hex
self.tx_type = tx_type
self.swap_type = swap_type
class BasicSwap(BaseApp, BSXNetwork, UIApp): class BasicSwap(BaseApp, BSXNetwork, UIApp):
ws_server = None ws_server = None
protocolInterfaces = { protocolInterfaces = {
@@ -530,7 +500,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.db_version = self.getIntKV("db_version", cursor, CURRENT_DB_VERSION) self.db_version = self.getIntKV("db_version", cursor, CURRENT_DB_VERSION)
self.db_data_version = self.getIntKV("db_data_version", cursor, 0) self.db_data_version = self.getIntKV("db_data_version", cursor, 0)
self._contract_count = self.getIntKV("contract_count", cursor, 0) self._contract_count = self.getIntKV("contract_count", cursor, 0)
self.commitDB()
finally: finally:
self.closeDB(cursor) self.closeDB(cursor)
@@ -613,6 +582,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def finalise(self): def finalise(self):
self.log.info("Finalising") self.log.info("Finalising")
if self.ws_server:
try:
self.log.info("Stopping websocket server.")
self.ws_server.shutdown_gracefully()
except Exception as e: # noqa: F841
traceback.print_exc()
self._price_fetch_running = False self._price_fetch_running = False
if self._price_fetch_thread and self._price_fetch_thread.is_alive(): if self._price_fetch_thread and self._price_fetch_thread.is_alive():
self._price_fetch_thread.join(timeout=5) self._price_fetch_thread.join(timeout=5)
@@ -745,6 +721,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
"rpcauth": rpcauth, "rpcauth": rpcauth,
"blocks_confirmed": chain_client_settings.get("blocks_confirmed", 6), "blocks_confirmed": chain_client_settings.get("blocks_confirmed", 6),
"conf_target": chain_client_settings.get("conf_target", 2), "conf_target": chain_client_settings.get("conf_target", 2),
"watched_transactions": [],
"watched_outputs": [], "watched_outputs": [],
"watched_scripts": [], "watched_scripts": [],
"last_height_checked": last_height_checked, "last_height_checked": last_height_checked,
@@ -4453,17 +4430,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33]
elif ci_to.curve_type() == Curves.secp256k1: elif ci_to.curve_type() == Curves.secp256k1:
for i in range(10):
xmr_swap.kbsf_dleag = ci_to.signRecoverable( xmr_swap.kbsf_dleag = ci_to.signRecoverable(
kbsf, "proof kbsf owned for swap" kbsf, "proof kbsf owned for swap"
) )
pk_recovered = ci_to.verifySigAndRecover( pk_recovered = ci_to.verifySigAndRecover(
xmr_swap.kbsf_dleag, "proof kbsf owned for swap" xmr_swap.kbsf_dleag, "proof kbsf owned for swap"
) )
if pk_recovered == xmr_swap.pkbsf: ensure(pk_recovered == xmr_swap.pkbsf, "kbsf recovered pubkey mismatch")
break
self.log.debug("kbsl recovered pubkey mismatch, retrying.")
assert pk_recovered == xmr_swap.pkbsf
xmr_swap.pkasf = xmr_swap.pkbsf xmr_swap.pkasf = xmr_swap.pkbsf
else: else:
raise ValueError("Unknown curve") raise ValueError("Unknown curve")
@@ -4766,17 +4739,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl) xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl)
msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:dleag_split_size_init] msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:dleag_split_size_init]
elif ci_to.curve_type() == Curves.secp256k1: elif ci_to.curve_type() == Curves.secp256k1:
for i in range(10):
xmr_swap.kbsl_dleag = ci_to.signRecoverable( xmr_swap.kbsl_dleag = ci_to.signRecoverable(
kbsl, "proof kbsl owned for swap" kbsl, "proof kbsl owned for swap"
) )
pk_recovered = ci_to.verifySigAndRecover( pk_recovered = ci_to.verifySigAndRecover(
xmr_swap.kbsl_dleag, "proof kbsl owned for swap" xmr_swap.kbsl_dleag, "proof kbsl owned for swap"
) )
if pk_recovered == xmr_swap.pkbsl: ensure(pk_recovered == xmr_swap.pkbsl, "kbsl recovered pubkey mismatch")
break
self.log.debug("kbsl recovered pubkey mismatch, retrying.")
assert pk_recovered == xmr_swap.pkbsl
msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag
else: else:
raise ValueError("Unknown curve") raise ValueError("Unknown curve")
@@ -5586,7 +5555,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
block_header = ci.getBlockHeaderFromHeight(tx_height) block_header = ci.getBlockHeaderFromHeight(tx_height)
block_time = block_header["time"] block_time = block_header["time"]
cc = self.coin_clients[coin_type] cc = self.coin_clients[coin_type]
if len(cc["watched_outputs"]) == 0 and len(cc["watched_scripts"]) == 0: if (
len(cc["watched_outputs"]) == 0
and len(cc["watched_transactions"]) == 0
and len(cc["watched_scripts"]) == 0
):
cc["last_height_checked"] = tx_height cc["last_height_checked"] = tx_height
cc["block_check_min_time"] = block_time cc["block_check_min_time"] = block_time
self.setIntKV("block_check_min_time_" + coin_name, block_time, cursor) self.setIntKV("block_check_min_time_" + coin_name, block_time, cursor)
@@ -6732,6 +6705,39 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception: except Exception:
return None return None
def addWatchedTransaction(
self, coin_type, bid_id, txid_hex, tx_type, swap_type=None
):
self.log.debug(
f"Adding watched transaction {Coins(coin_type).name} bid {self.log.id(bid_id)} tx {self.log.id(txid_hex)} type {tx_type}"
)
watched = self.coin_clients[coin_type]["watched_transactions"]
for wo in watched:
if wo.bid_id == bid_id and wo.txid_hex == txid_hex:
self.log.debug("Transaction already being watched.")
return
watched.append(
WatchedTransaction(bid_id, coin_type, txid_hex, tx_type, swap_type)
)
def removeWatchedTransaction(self, coin_type, bid_id: bytes, txid_hex: str) -> None:
# Remove all for bid if txid is None
self.log.debug(
f"Removing watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(txid_hex)}"
)
watched = self.coin_clients[coin_type]["watched_transactions"]
old_len = len(watched)
for i in range(old_len - 1, -1, -1):
wo = watched[i]
if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex):
del watched[i]
self.log.debug(
f"Removed watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(wo.txid_hex)}"
)
def addWatchedOutput( def addWatchedOutput(
self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None
): ):
@@ -6740,7 +6746,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
watched = self.coin_clients[coin_type]["watched_outputs"] watched = self.coin_clients[coin_type]["watched_outputs"]
for wo in watched: for wo in watched:
if wo.bid_id == bid_id and wo.txid_hex == txid_hex and wo.vout == vout: if wo.bid_id == bid_id and wo.txid_hex == txid_hex and wo.vout == vout:
self.log.debug("Output already being watched.") self.log.debug("Output already being watched.")
@@ -6751,13 +6756,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def removeWatchedOutput(self, coin_type, bid_id: bytes, txid_hex: str) -> None: def removeWatchedOutput(self, coin_type, bid_id: bytes, txid_hex: str) -> None:
# Remove all for bid if txid is None # Remove all for bid if txid is None
self.log.debug( self.log.debug(
f"removeWatchedOutput {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(txid_hex)}" f"Removing watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(txid_hex)}"
) )
old_len = len(self.coin_clients[coin_type]["watched_outputs"]) watched = self.coin_clients[coin_type]["watched_outputs"]
old_len = len(watched)
for i in range(old_len - 1, -1, -1): for i in range(old_len - 1, -1, -1):
wo = self.coin_clients[coin_type]["watched_outputs"][i] wo = watched[i]
if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex): if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex):
del self.coin_clients[coin_type]["watched_outputs"][i] del watched[i]
self.log.debug( self.log.debug(
f"Removed watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(wo.txid_hex)}" f"Removed watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(wo.txid_hex)}"
) )
@@ -6770,7 +6776,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
watched = self.coin_clients[coin_type]["watched_scripts"] watched = self.coin_clients[coin_type]["watched_scripts"]
for ws in watched: for ws in watched:
if ws.bid_id == bid_id and ws.tx_type == tx_type and ws.script == script: if ws.bid_id == bid_id and ws.tx_type == tx_type and ws.script == script:
self.log.debug("Script already being watched.") self.log.debug("Script already being watched.")
@@ -6783,21 +6788,22 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) -> None: ) -> None:
# Remove all for bid if script and type_ind is None # Remove all for bid if script and type_ind is None
self.log.debug( self.log.debug(
"removeWatchedScript {} {}{}".format( "Removing watched script {} {}{}".format(
Coins(coin_type).name, Coins(coin_type).name,
{self.log.id(bid_id)}, {self.log.id(bid_id)},
(" type " + str(tx_type)) if tx_type is not None else "", (" type " + str(tx_type)) if tx_type is not None else "",
) )
) )
old_len = len(self.coin_clients[coin_type]["watched_scripts"]) watched = self.coin_clients[coin_type]["watched_scripts"]
old_len = len(watched)
for i in range(old_len - 1, -1, -1): for i in range(old_len - 1, -1, -1):
ws = self.coin_clients[coin_type]["watched_scripts"][i] ws = watched[i]
if ( if (
ws.bid_id == bid_id ws.bid_id == bid_id
and (script is None or ws.script == script) and (script is None or ws.script == script)
and (tx_type is None or ws.tx_type == tx_type) and (tx_type is None or ws.tx_type == tx_type)
): ):
del self.coin_clients[coin_type]["watched_scripts"][i] del watched[i]
self.log.debug( self.log.debug(
f"Removed watched script {Coins(coin_type).name} {self.log.id(bid_id)}" f"Removed watched script {Coins(coin_type).name} {self.log.id(bid_id)}"
) )
@@ -7175,6 +7181,17 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
finally: finally:
self.closeDB(cursor) self.closeDB(cursor)
def processFoundTransaction(
self,
watched_txn: WatchedTransaction,
block_hash_hex: str,
block_height: int,
chain_blocks: int,
):
self.log.warning(
f"Unknown swap_type for found transaction: {self.logIDT(bytes.fromhex(watched_txn.txid_hex))}."
)
def processSpentOutput( def processSpentOutput(
self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn
) -> None: ) -> None:
@@ -7437,6 +7454,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
continue continue
for tx in block["tx"]: for tx in block["tx"]:
for t in c["watched_transactions"]:
if t.block_hash is not None:
continue
if tx["txid"] == t.txid_hex:
self.processFoundTransaction(
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"]:
@@ -8746,20 +8770,18 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
# Extract pubkeys from MSG1F DLEAG # Extract pubkeys from MSG1F DLEAG
xmr_swap.pkasl = xmr_swap.kbsl_dleag[0:33] xmr_swap.pkasl = xmr_swap.kbsl_dleag[0:33]
if not ci_from.verifyPubkey(xmr_swap.pkasl):
raise ValueError("Invalid coin a pubkey.")
xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33 : 33 + 32] xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33 : 33 + 32]
if not ci_to.verifyPubkey(xmr_swap.pkbsl):
raise ValueError("Invalid coin b pubkey.")
elif ci_to.curve_type() == Curves.secp256k1: elif ci_to.curve_type() == Curves.secp256k1:
xmr_swap.pkasl = ci_to.verifySigAndRecover( xmr_swap.pkasl = ci_to.verifySigAndRecover(
xmr_swap.kbsl_dleag, "proof kbsl owned for swap" xmr_swap.kbsl_dleag, "proof kbsl owned for swap"
) )
if not ci_from.verifyPubkey(xmr_swap.pkasl):
raise ValueError("Invalid coin a pubkey.")
xmr_swap.pkbsl = xmr_swap.pkasl xmr_swap.pkbsl = xmr_swap.pkasl
else: else:
raise ValueError("Unknown curve") raise ValueError("Unknown curve")
if not ci_from.verifyPubkey(xmr_swap.pkasl):
raise ValueError("Invalid coin a pubkey.")
if not ci_to.verifyPubkey(xmr_swap.pkbsl):
raise ValueError("Invalid coin b pubkey.")
# vkbv and vkbvl are verified in processXmrBidAccept # vkbv and vkbvl are verified in processXmrBidAccept
xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf) xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf)
@@ -10827,7 +10849,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
>= self.check_expiring_bids_offers_seconds >= self.check_expiring_bids_offers_seconds
): ):
check_records = True check_records = True
self._last_checked_expiring_bids = now self._last_checked_expiring_bids_offers = now
if ( if (
len(bids_to_expire) == 0 len(bids_to_expire) == 0

View File

@@ -608,13 +608,6 @@ def runClient(
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
traceback.print_exc() traceback.print_exc()
if swap_client.ws_server:
try:
swap_client.log.info("Stopping websocket server.")
swap_client.ws_server.shutdown_gracefully()
except Exception as e: # noqa: F841
traceback.print_exc()
swap_client.finalise() swap_client.finalise()
closed_pids = [] closed_pids = []

View File

@@ -792,8 +792,8 @@ class NetworkPortal(Table):
created_at = Column("integer") created_at = Column("integer")
def extract_schema() -> dict: def extract_schema(input_globals=None) -> dict:
g = globals().copy() g = globals() if input_globals is None else input_globals
tables = {} tables = {}
for name, obj in g.items(): for name, obj in g.items():
if not inspect.isclass(obj): if not inspect.isclass(obj):

View File

@@ -152,38 +152,8 @@ def upgradeDatabaseData(self, data_version):
self.closeDB(cursor, commit=False) self.closeDB(cursor, commit=False)
def upgradeDatabase(self, db_version): def upgradeDatabaseFromSchema(self, cursor, expect_schema):
if self._force_db_upgrade is False and db_version >= CURRENT_DB_VERSION:
return
self.log.info(
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}."
)
# db_version, tablename, oldcolumnname, newcolumnname
rename_columns = [
(13, "actions", "event_id", "action_id"),
(13, "actions", "event_type", "action_type"),
(13, "actions", "event_data", "action_data"),
(
14,
"xmr_swaps",
"coin_a_lock_refund_spend_tx_msg_id",
"coin_a_lock_spend_tx_msg_id",
),
]
expect_schema = extract_schema()
have_tables = {} have_tables = {}
try:
cursor = self.openDB()
for rename_column in rename_columns:
dbv, table_name, colname_from, colname_to = rename_column
if db_version < dbv:
cursor.execute(
f"ALTER TABLE {table_name} RENAME COLUMN {colname_from} TO {colname_to}"
)
query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
tables = cursor.execute(query).fetchall() tables = cursor.execute(query).fetchall()
@@ -254,16 +224,13 @@ def upgradeDatabase(self, db_version):
for index in indices: for index in indices:
index_name = index["index_name"] index_name = index["index_name"]
if not any( if not any(
have_idx.get("index_name") == index_name have_idx.get("index_name") == index_name for have_idx in have_indices
for have_idx in have_indices
): ):
self.log.info(f"Adding index {index_name} to table {table_name}.") self.log.info(f"Adding index {index_name} to table {table_name}.")
column_1 = index["column_1"] column_1 = index["column_1"]
column_2 = index.get("column_2", None) column_2 = index.get("column_2", None)
column_3 = index.get("column_3", None) column_3 = index.get("column_3", None)
query: str = ( query: str = f"CREATE INDEX {index_name} ON {table_name} ({column_1}"
f"CREATE INDEX {index_name} ON {table_name} ({column_1}"
)
if column_2: if column_2:
query += f", {column_2}" query += f", {column_2}"
if column_3: if column_3:
@@ -271,6 +238,38 @@ def upgradeDatabase(self, db_version):
query += ")" query += ")"
cursor.execute(query) cursor.execute(query)
def upgradeDatabase(self, db_version: int):
if self._force_db_upgrade is False and db_version >= CURRENT_DB_VERSION:
return
self.log.info(
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}."
)
# db_version, tablename, oldcolumnname, newcolumnname
rename_columns = [
(13, "actions", "event_id", "action_id"),
(13, "actions", "event_type", "action_type"),
(13, "actions", "event_data", "action_data"),
(
14,
"xmr_swaps",
"coin_a_lock_refund_spend_tx_msg_id",
"coin_a_lock_spend_tx_msg_id",
),
]
expect_schema = extract_schema()
try:
cursor = self.openDB()
for rename_column in rename_columns:
dbv, table_name, colname_from, colname_to = rename_column
if db_version < dbv:
cursor.execute(
f"ALTER TABLE {table_name} RENAME COLUMN {colname_from} TO {colname_to}"
)
upgradeDatabaseFromSchema(self, cursor, expect_schema)
if CURRENT_DB_VERSION != db_version: if CURRENT_DB_VERSION != db_version:
self.db_version = CURRENT_DB_VERSION self.db_version = CURRENT_DB_VERSION
self.setIntKV("db_version", CURRENT_DB_VERSION, cursor) self.setIntKV("db_version", CURRENT_DB_VERSION, cursor)

View File

@@ -235,6 +235,14 @@ class BTCInterface(Secp256k1Interface):
def txoType(): def txoType():
return CTxOut return CTxOut
@staticmethod
def outpointType():
return COutPoint
@staticmethod
def txiType():
return CTxIn
@staticmethod @staticmethod
def getExpectedSequence(lockType: int, lockVal: int) -> int: def getExpectedSequence(lockType: int, lockVal: int) -> int:
ensure(lockVal >= 1, "Bad lockVal") ensure(lockVal >= 1, "Bad lockVal")
@@ -1203,7 +1211,7 @@ class BTCInterface(Secp256k1Interface):
ensure(C == Kaf, "Bad script pubkey") ensure(C == Kaf, "Bad script pubkey")
fee_paid = swap_value - locked_coin fee_paid = swap_value - locked_coin
assert fee_paid > 0 ensure(fee_paid > 0, "negative fee_paid")
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@@ -1267,7 +1275,7 @@ class BTCInterface(Secp256k1Interface):
tx_value = tx.vout[0].nValue tx_value = tx.vout[0].nValue
fee_paid = prevout_value - tx_value fee_paid = prevout_value - tx_value
assert fee_paid > 0 ensure(fee_paid > 0, "negative fee_paid")
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(
prevout_script prevout_script
@@ -2575,12 +2583,7 @@ class BTCInterface(Secp256k1Interface):
self._log.id(bytes.fromhex(tx["txid"])) self._log.id(bytes.fromhex(tx["txid"]))
) )
) )
self.rpc( self.publishTx(tx_signed)
"sendrawtransaction",
[
tx_signed,
],
)
return tx["txid"] return tx["txid"]

View File

@@ -191,17 +191,11 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33]
elif ci_to.curve_type() == Curves.secp256k1: elif ci_to.curve_type() == Curves.secp256k1:
for i in range(10): xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, "proof kbsf owned for swap")
xmr_swap.kbsf_dleag = ci_to.signRecoverable(
kbsf, "proof kbsf owned for swap"
)
pk_recovered: bytes = ci_to.verifySigAndRecover( pk_recovered: bytes = ci_to.verifySigAndRecover(
xmr_swap.kbsf_dleag, "proof kbsf owned for swap" xmr_swap.kbsf_dleag, "proof kbsf owned for swap"
) )
if pk_recovered == xmr_swap.pkbsf: ensure(pk_recovered == xmr_swap.pkbsf, "kbsf recovered pubkey mismatch")
break
# self.log.debug('kbsl recovered pubkey mismatch, retrying.')
assert pk_recovered == xmr_swap.pkbsf
xmr_swap.pkasf = xmr_swap.pkbsf xmr_swap.pkasf = xmr_swap.pkbsf
else: else:
raise ValueError("Unknown curve") raise ValueError("Unknown curve")

51
basicswap/types.py Normal file
View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
class WatchedOutput: # Watch for spends
__slots__ = ("bid_id", "txid_hex", "vout", "tx_type", "swap_type")
def __init__(self, bid_id: bytes, txid_hex: str, vout, tx_type, swap_type):
self.bid_id = bid_id
self.txid_hex = txid_hex
self.vout = vout
self.tx_type = tx_type
self.swap_type = swap_type
class WatchedScript: # Watch for txns containing outputs
__slots__ = ("bid_id", "script", "tx_type", "swap_type")
def __init__(self, bid_id: bytes, script: bytes, tx_type, swap_type):
self.bid_id = bid_id
self.script = script
self.tx_type = tx_type
self.swap_type = swap_type
class WatchedTransaction:
__slots__ = (
"bid_id",
"coin_type",
"txid_hex",
"tx_type",
"swap_type",
"block_hash",
"depth",
)
# TODO
# Watch for presence in mempool (getrawtransaction)
def __init__(
self, bid_id: bytes, coin_type: int, txid_hex: str, tx_type, swap_type
):
self.bid_id = bid_id
self.coin_type = coin_type
self.txid_hex = txid_hex
self.tx_type = tx_type
self.swap_type = swap_type
self.block_hash = None
self.depth = -1

View File

@@ -4,4 +4,4 @@ Jinja2==3.1.6
pycryptodome==3.23.0 pycryptodome==3.23.0
PySocks==1.7.1 PySocks==1.7.1
websocket-client==1.9.0 websocket-client==1.9.0
coincurve@https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.2.zip coincurve@https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.3.zip

View File

@@ -4,81 +4,8 @@
# #
# pip-compile --generate-hashes --output-file=requirements.txt requirements.in # pip-compile --generate-hashes --output-file=requirements.txt requirements.in
# #
asn1crypto==1.5.1 \ coincurve @ https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.3.zip \
--hash=sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c \ --hash=sha256:41e40a335994112938984097b089142acceb24324d76a7e35d8b9d14c2cfa8b5
--hash=sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67
# via coincurve
cffi==1.17.1 \
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
--hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \
--hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \
--hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \
--hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
--hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \
--hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \
--hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \
--hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
--hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \
--hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \
--hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \
--hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \
--hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
--hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \
--hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \
--hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \
--hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
--hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \
--hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \
--hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \
--hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \
--hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \
--hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \
--hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \
--hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \
--hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \
--hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \
--hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
--hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \
--hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \
--hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \
--hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \
--hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
--hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
--hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
--hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
--hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \
--hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \
--hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \
--hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \
--hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \
--hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \
--hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \
--hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \
--hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
--hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \
--hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \
--hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \
--hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \
--hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \
--hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \
--hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
--hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \
--hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \
--hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \
--hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \
--hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \
--hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \
--hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \
--hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \
--hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \
--hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
--hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \
--hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \
--hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
# via coincurve
coincurve @ https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.2.zip \
--hash=sha256:c309deef22c929c9ab5b3adf7adbda940bffcea6c6ec7c66202d6c3d4e3ceb79
# via -r requirements.in # via -r requirements.in
jinja2==3.1.6 \ jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
@@ -147,10 +74,6 @@ markupsafe==3.0.2 \
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \
--hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50
# via jinja2 # via jinja2
pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
pycryptodome==3.23.0 \ pycryptodome==3.23.0 \
--hash=sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4 \ --hash=sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4 \
--hash=sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c \ --hash=sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c \

View File

@@ -94,6 +94,7 @@ class Test(unittest.TestCase):
time_val = 48 * 60 * 60 time_val = 48 * 60 * 60
encoded = ci.getExpectedSequence(TxLockTypes.SEQUENCE_LOCK_TIME, time_val) encoded = ci.getExpectedSequence(TxLockTypes.SEQUENCE_LOCK_TIME, time_val)
decoded = ci.decodeSequence(encoded) decoded = ci.decodeSequence(encoded)
assert encoded == 4194642
assert decoded >= time_val assert decoded >= time_val
assert decoded <= time_val + 512 assert decoded <= time_val + 512

View File

@@ -319,6 +319,7 @@ class Test(BaseTest):
test_coin_from = Coins.PART test_coin_from = Coins.PART
# p2wpkh # p2wpkh
logging.info("---------- Test {} segwit".format(test_coin_from.name)) logging.info("---------- Test {} segwit".format(test_coin_from.name))
ci = self.swap_clients[0].ci(test_coin_from) ci = self.swap_clients[0].ci(test_coin_from)
addr_native = ci.rpc_wallet("getnewaddress", ["p2pkh segwit test"]) addr_native = ci.rpc_wallet("getnewaddress", ["p2pkh segwit test"])
@@ -329,9 +330,11 @@ class Test(BaseTest):
], ],
) )
assert addr_info["iswitness"] is False # address is p2pkh, not p2wpkh assert addr_info["iswitness"] is False # address is p2pkh, not p2wpkh
addr_segwit = ci.rpc_wallet( addr_segwit = ci.rpc_wallet(
"getnewaddress", ["p2wpkh segwit test", True, False, False, "bech32"] "getnewaddress", ["p2wpkh segwit test", True, False, False, "bech32"]
) )
addr_info = ci.rpc_wallet( addr_info = ci.rpc_wallet(
"getaddressinfo", "getaddressinfo",
[ [
@@ -351,6 +354,7 @@ class Test(BaseTest):
], ],
], ],
) )
assert len(txid) == 64 assert len(txid) == 64
tx_wallet = ci.rpc_wallet( tx_wallet = ci.rpc_wallet(
"gettransaction", "gettransaction",