From 757f8f27627048bd10703e9529effc36d715b379 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 19 Nov 2024 21:45:19 +0200 Subject: [PATCH] Replace sqlalchemy with manbearpigSQL --- basicswap/base.py | 6 +- basicswap/basicswap.py | 2121 ++++++++++++-------------- basicswap/db.py | 1027 +++++++++---- basicswap/db_upgrades.py | 715 ++++----- basicswap/db_util.py | 121 +- basicswap/interface/bch.py | 9 +- basicswap/interface/btc.py | 24 +- basicswap/protocols/atomic_swap_1.py | 8 +- basicswap/protocols/xmr_swap_1.py | 22 +- basicswap/ui/page_offers.py | 6 +- doc/release-notes.md | 1 + guix.scm | 12 - requirements.in | 1 - requirements.txt | 254 +-- tests/basicswap/test_btc_xmr.py | 10 + 15 files changed, 2182 insertions(+), 2155 deletions(-) diff --git a/basicswap/base.py b/basicswap/base.py index 6a6ef7f..be05b91 100644 --- a/basicswap/base.py +++ b/basicswap/base.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -18,6 +19,9 @@ import subprocess from sockshandler import SocksiPyHandler +from .db import ( + DBMethods, +) from .rpc import ( callrpc, ) @@ -34,7 +38,7 @@ def getaddrinfo_tor(*args): return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))] -class BaseApp: +class BaseApp(DBMethods): def __init__(self, fp, data_dir, settings, chain, log_name="BasicSwap"): self.log_name = log_name self.fp = fp diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 3e29940..602337a 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -5,31 +5,27 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import os -import sys -import zmq -import copy -import json -import time import base64 -import random -import shutil -import string -import struct -import secrets -import datetime as dt -import threading -import traceback -import sqlalchemy as sa import collections import concurrent.futures +import copy +import datetime as dt +import json +import os +import random +import secrets +import shutil +import sqlite3 +import string +import struct +import sys +import threading +import time +import traceback +import zmq from typing import Optional -from sqlalchemy.sql import text -from sqlalchemy.orm import sessionmaker, scoped_session -from sqlalchemy.orm.session import close_all_sessions - from .interface.base import Curves from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind @@ -88,30 +84,29 @@ from .messages_npb import ( XmrSplitMessage, ) from .db import ( - CURRENT_DB_VERSION, - Concepts, - Base, - DBKVInt, - DBKVString, - Offer, - Bid, - SwapTx, - PrefundedTx, - PooledAddress, - SentOffer, - SmsgAddress, Action, - EventLog, - XmrOffer, - XmrSwap, - XmrSplitData, - Wallets, - Notification, - KnownIdentity, AutomationLink, AutomationStrategy, + Bid, + Concepts, + create_db, + CURRENT_DB_VERSION, + EventLog, + firstOrNone, + KnownIdentity, MessageLink, + Notification, + Offer, pack_state, + PooledAddress, + PrefundedTx, + SentOffer, + SmsgAddress, + SwapTx, + Wallets, + XmrOffer, + XmrSplitData, + XmrSwap, ) from .db_upgrades import upgradeDatabase, upgradeDatabaseData from .base import BaseApp @@ -125,30 +120,30 @@ import basicswap.network as bsn import basicswap.protocols.atomic_swap_1 as atomic_swap_1 import basicswap.protocols.xmr_swap_1 as xmr_swap_1 from .basicswap_util import ( - KeyTypes, - TxLockTypes, - AddressTypes, - MessageTypes, - SwapTypes, - OfferStates, - BidStates, - TxStates, - TxTypes, ActionTypes, - EventLogTypes, - XmrSplitMsgTypes, + AddressTypes, + AutomationOverrideOptions, + BidStates, DebugTypes, - strBidState, describeEventEntry, + EventLogTypes, + getLastBidState, + getOfferProofOfFundsHash, getVoutByAddress, getVoutByScriptPubKey, - getOfferProofOfFundsHash, - getLastBidState, - isActiveBidState, - NotificationTypes as NT, - AutomationOverrideOptions, - VisibilityOverrideOptions, inactive_states, + isActiveBidState, + KeyTypes, + MessageTypes, + NotificationTypes as NT, + OfferStates, + strBidState, + SwapTypes, + TxLockTypes, + TxStates, + TxTypes, + VisibilityOverrideOptions, + XmrSplitMsgTypes, ) from basicswap.db_util import ( remove_expired_data, @@ -421,50 +416,18 @@ class BasicSwap(BaseApp): ) db_exists: bool = os.path.exists(self.sqlite_file) - # HACK: create_all hangs when using tox, unless create_engine is called with echo=True if not db_exists: - if os.getenv("FOR_TOX"): - self.engine = sa.create_engine( - "sqlite:///" + self.sqlite_file, echo=True - ) - else: - self.engine = sa.create_engine("sqlite:///" + self.sqlite_file) - close_all_sessions() - Base.metadata.create_all(self.engine) - self.engine.dispose() - - self.engine = sa.create_engine( - "sqlite:///" + self.sqlite_file, echo=self.db_echo - ) - self.session_factory = sessionmaker(bind=self.engine, expire_on_commit=False) - - session = scoped_session(self.session_factory) - try: - self.db_version = ( - session.query(DBKVInt).filter_by(key="db_version").first().value - ) - except Exception: self.log.info("First run") - self.db_version = CURRENT_DB_VERSION - session.add(DBKVInt(key="db_version", value=self.db_version)) - session.commit() - try: - self.db_data_version = ( - session.query(DBKVInt).filter_by(key="db_data_version").first().value - ) - except Exception: - self.db_data_version = 0 - try: - self._contract_count = ( - session.query(DBKVInt).filter_by(key="contract_count").first().value - ) - except Exception: - self._contract_count = 0 - session.add(DBKVInt(key="contract_count", value=self._contract_count)) - session.commit() + create_db(self.sqlite_file, self.log) - session.close() - session.remove() + cursor = self.openDB() + try: + self.db_version = self.getIntKV("db_version", cursor, CURRENT_DB_VERSION) + self.db_data_version = self.getIntKV("db_data_version", cursor, 0) + self._contract_count = self.getIntKV("contract_count", cursor, 0) + self.commitDB() + finally: + self.closeDB(cursor) if self._zmq_queue_enabled: self.zmqContext = zmq.Context() @@ -537,30 +500,13 @@ class BasicSwap(BaseApp): self.zmqContext.destroy() self.swaps_in_progress.clear() - close_all_sessions() - self.engine.dispose() - def openSession(self, session=None): - if session: - return session - - self.mxDB.acquire() - return scoped_session(self.session_factory) - - def closeSession(self, session, commit=True): - if commit: - session.commit() - - session.close() - session.remove() - self.mxDB.release() - - def handleSessionErrors(self, e, session, tag): + def handleSessionErrors(self, e, cursor, tag): if self.debug: self.log.error(traceback.format_exc()) self.log.error(f"Error: {tag} - {e}") - session.rollback() + self.rollbackDB() def setCoinConnectParams(self, coin): # Set anything that does not require the daemon to be running @@ -592,27 +538,19 @@ class BasicSwap(BaseApp): ) try: - session = self.openSession() + cursor = self.openDB() + coin_name: str = chainparams[coin]["name"] + last_height_checked = self.getIntKV( + "last_height_checked_" + coin_name, cursor, 0 + ) try: - last_height_checked = ( - session.query(DBKVInt) - .filter_by(key="last_height_checked_" + chainparams[coin]["name"]) - .first() - .value + block_check_min_time = self.getIntKV( + "block_check_min_time_" + coin_name, cursor ) - except Exception: - last_height_checked = 0 - try: - block_check_min_time = ( - session.query(DBKVInt) - .filter_by(key="block_check_min_time_" + chainparams[coin]["name"]) - .first() - .value - ) - except Exception: + except Exception as e: # noqa: F841 block_check_min_time = 0xFFFFFFFFFFFFFFFF finally: - self.closeSession(session) + self.closeDB(cursor) coin_chainparams = chainparams[coin] default_segwit = coin_chainparams.get("has_segwit", False) @@ -1002,7 +940,8 @@ class BasicSwap(BaseApp): "Starting BasicSwap %s, database v%d\n\n", __version__, self.db_version ) self.log.info(f"Python version: {platform.python_version()}") - self.log.info("SQLAlchemy version: %s", sa.__version__) + self.log.info(f"SQLite version: {sqlite3.sqlite_version}") + self.log.info("Timezone offset: %d (%s)", time.timezone, time.tzname[0]) upgradeDatabase(self, self.db_version) @@ -1368,28 +1307,30 @@ class BasicSwap(BaseApp): if coin_type == Coins.DCR: legacy_root_hash = ci.getSeedHash(root_key, 20) try: - session = self.openSession() + cursor = self.openDB() key_str = "main_wallet_seedid_" + db_key_coin_name - self.setStringKV(key_str, root_hash.hex(), session) + self.setStringKV(key_str, root_hash.hex(), cursor) if coin_type == Coins.DCR: # TODO: How to force getmasterpubkey to always return the new slip44 (42) key key_str = "main_wallet_seedid_alt_" + db_key_coin_name - self.setStringKV(key_str, legacy_root_hash.hex(), session) + self.setStringKV(key_str, legacy_root_hash.hex(), cursor) # Clear any saved addresses - self.clearStringKV("receive_addr_" + db_key_coin_name, session) - self.clearStringKV("stealth_addr_" + db_key_coin_name, session) + self.clearStringKV("receive_addr_" + db_key_coin_name, cursor) + self.clearStringKV("stealth_addr_" + db_key_coin_name, cursor) coin_id = int(coin_type) info_type = 1 # wallet query_str = f"DELETE FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type}" - session.execute(text(query_str)) + cursor.execute(query_str) finally: - self.closeSession(session) + self.closeDB(cursor) - def updateIdentityBidState(self, session, address: str, bid) -> None: - identity_stats = session.query(KnownIdentity).filter_by(address=address).first() + def updateIdentityBidState(self, cursor, address: str, bid) -> None: + identity_stats = firstOrNone( + self.query(KnownIdentity, cursor, {"address": address}) + ) if not identity_stats: identity_stats = KnownIdentity( active_ind=1, address=address, created_at=self.getTime() @@ -1421,90 +1362,45 @@ class BasicSwap(BaseApp): ) identity_stats.updated_at = self.getTime() - session.add(identity_stats) - - def setIntKV(self, str_key: str, int_val: int, session=None) -> None: - try: - use_session = self.openSession(session) - kv = use_session.query(DBKVInt).filter_by(key=str_key).first() - if not kv: - kv = DBKVInt(key=str_key, value=int_val) - else: - kv.value = int_val - use_session.add(kv) - finally: - if session is None: - self.closeSession(use_session) - - def setStringKV(self, str_key: str, str_val: str, session=None) -> None: - try: - use_session = self.openSession(session) - kv = use_session.query(DBKVString).filter_by(key=str_key).first() - if not kv: - kv = DBKVString(key=str_key, value=str_val) - else: - kv.value = str_val - use_session.add(kv) - finally: - if session is None: - self.closeSession(use_session) - - def getStringKV(self, str_key: str, session=None) -> Optional[str]: - try: - use_session = self.openSession(session) - v = use_session.query(DBKVString).filter_by(key=str_key).first() - if not v: - return None - return v.value - finally: - if session is None: - self.closeSession(use_session, commit=False) - - def clearStringKV(self, str_key: str, session=None) -> None: - try: - use_session = self.openSession(session) - use_session.execute( - text("DELETE FROM kv_string WHERE key = :key"), {"key": str_key} - ) - finally: - if session is None: - self.closeSession(use_session) + self.add(identity_stats, cursor, upsert=True) def getPreFundedTx( - self, linked_type: int, linked_id: bytes, tx_type: int, session=None + self, linked_type: int, linked_id: bytes, tx_type: int, cursor=None ) -> Optional[bytes]: try: - use_session = self.openSession(session) - tx = ( - use_session.query(PrefundedTx) - .filter_by( - linked_type=linked_type, - linked_id=linked_id, - tx_type=tx_type, - used_by=None, + use_cursor = self.openDB(cursor) + tx = firstOrNone( + self.query( + PrefundedTx, + use_cursor, + { + "linked_type": linked_type, + "linked_id": linked_id, + "tx_type": tx_type, + "used_by": None, + }, ) - .first() ) - if not tx: + if tx is None: return None tx.used_by = linked_id - use_session.add(tx) + self.add(tx, use_cursor, upsert=True) return tx.tx_data finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) - def activateBid(self, session, bid) -> None: + def activateBid(self, cursor, bid) -> None: if bid.bid_id in self.swaps_in_progress: self.log.debug("Bid %s is already in progress", bid.bid_id.hex()) self.log.debug("Loading active bid %s", bid.bid_id.hex()) - offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() + offer = self.getOffer(bid.offer_id, cursor=cursor) if not offer: raise ValueError("Offer not found") - self.loadBidTxns(bid, session) + self.loadBidTxns(bid, cursor) coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) @@ -1514,8 +1410,8 @@ class BasicSwap(BaseApp): ci_to = self.ci(offer.coin_from if reverse_bid else offer.coin_to) if offer.swap_type == SwapTypes.XMR_SWAP: - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - self.watchXmrSwap(bid, offer, xmr_swap, session) + xmr_swap = firstOrNone(self.query(XmrSwap, cursor, {"bid_id": bid.bid_id})) + self.watchXmrSwap(bid, offer, xmr_swap, cursor) if ( ci_to.watch_blocks_for_scripts() and bid.xmr_a_lock_tx @@ -1530,7 +1426,7 @@ class BasicSwap(BaseApp): ) dest_script = ci_to.getPkDest(xmr_swap.pkbs) self.setLastHeightCheckedStart( - ci_to.coin_type(), chain_b_block_header["height"], session + ci_to.coin_type(), chain_b_block_header["height"], cursor ) self.addWatchedScript( ci_to.coin_type(), @@ -1571,7 +1467,7 @@ class BasicSwap(BaseApp): chain_a_block_header["time"] ) self.setLastHeightCheckedStart( - coin_to, chain_b_block_header["height"], session + coin_to, chain_b_block_header["height"], cursor ) self.addWatchedScript( coin_to, @@ -1583,23 +1479,23 @@ class BasicSwap(BaseApp): if self.coin_clients[coin_from]["last_height_checked"] < 1: if bid.initiate_tx and bid.initiate_tx.chain_height: self.setLastHeightCheckedStart( - coin_from, bid.initiate_tx.chain_height, session + coin_from, bid.initiate_tx.chain_height, cursor ) if self.coin_clients[coin_to]["last_height_checked"] < 1: if bid.participate_tx and bid.participate_tx.chain_height: self.setLastHeightCheckedStart( - coin_to, bid.participate_tx.chain_height, session + coin_to, bid.participate_tx.chain_height, cursor ) # TODO process addresspool if bid has previously been abandoned - def deactivateBid(self, session, offer, bid) -> None: + def deactivateBid(self, cursor, offer, bid) -> None: # Remove from in progress self.log.debug("Removing bid from in-progress: %s", bid.bid_id.hex()) self.swaps_in_progress.pop(bid.bid_id, None) bid.in_progress = 0 - if session is None: + if cursor is None: self.saveBid(bid.bid_id, bid) # Remove any watched outputs @@ -1623,30 +1519,26 @@ class BasicSwap(BaseApp): self.returnAddressToPool(bid.bid_id, TxTypes.PTX_REFUND) try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) # Remove any delayed events - query: str = "DELETE FROM actions WHERE linked_id = x'{}' ".format( - bid.bid_id.hex() - ) + query: str = "DELETE FROM actions WHERE linked_id = :bid_id " if self.debug: - query = ( - "UPDATE actions SET active_ind = 2 WHERE linked_id = x'{}' ".format( - bid.bid_id.hex() - ) - ) - use_session.execute(text(query)) + query = "UPDATE actions SET active_ind = 2 WHERE linked_id = :bid_id " + use_cursor.execute(query, {"bid_id": bid.bid_id}) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) # Unlock locked inputs (TODO) if offer.swap_type == SwapTypes.XMR_SWAP: ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) - xmr_swap = ( - use_session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - ) - if xmr_swap: + rows = use_cursor.execute( + "SELECT a_lock_tx FROM xmr_swaps WHERE bid_id = :bid_id", + {"bid_id": bid.bid_id}, + ).fetchall() + if len(rows) > 0: + xmr_swap_a_lock_tx = rows[0][0] try: - ci_from.unlockInputs(xmr_swap.a_lock_tx) + ci_from.unlockInputs(xmr_swap_a_lock_tx) except Exception as e: self.log.debug("unlockInputs failed {}".format(str(e))) pass # Invalid parameter, unknown transaction @@ -1664,46 +1556,42 @@ class BasicSwap(BaseApp): ): was_sent: bool = bid.was_received if reverse_bid else bid.was_sent peer_address = offer.addr_from if was_sent else bid.bid_addr - self.updateIdentityBidState(use_session, peer_address, bid) + self.updateIdentityBidState(use_cursor, peer_address, bid) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def loadFromDB(self) -> None: if self.isSystemUnlocked() is False: self.log.info("Not loading from db. System is locked.") return self.log.info("Loading data from db") - self.mxDB.acquire() self.swaps_in_progress.clear() try: - session = scoped_session(self.session_factory) - for bid in session.query(Bid): + cursor = self.openDB() + for bid in self.query(Bid, cursor): if bid.in_progress == 1 or ( bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED ): try: - self.activateBid(session, bid) + self.activateBid(cursor, bid) except Exception as ex: self.logException(f"Failed to activate bid! Error: {ex}") try: bid.setState(BidStates.BID_ERROR, "Failed to activate") - offer = ( - session.query(Offer) - .filter_by(offer_id=bid.offer_id) - .first() + + offer = firstOrNone( + self.query(Offer, cursor, {"offer_id": bid.offer_id}) ) - self.deactivateBid(session, offer, bid) + self.deactivateBid(cursor, offer, bid) except Exception as ex: self.logException(f"Further error deactivating: {ex}") - self.buildNotificationsCache(session) + self.buildNotificationsCache(cursor) finally: - session.close() - session.remove() - self.mxDB.release() + self.closeDB(cursor) def getActiveBidMsgValidTime(self) -> int: return self.SMSG_SECONDS_IN_HOUR * 48 @@ -1766,7 +1654,7 @@ class BasicSwap(BaseApp): ) ) - def notify(self, event_type, event_data, session=None) -> None: + def notify(self, event_type, event_data, cursor=None) -> None: show_event = event_type not in self._disabled_notification_types if event_type == NT.OFFER_RECEIVED: self.log.debug("Received new offer %s", event_data["offer_id"]) @@ -1793,20 +1681,19 @@ class BasicSwap(BaseApp): try: now: int = self.getTime() - use_session = self.openSession(session) - use_session.add( + use_cursor = self.openDB(cursor) + self.add( Notification( active_ind=1, created_at=now, event_type=int(event_type), event_data=bytes(json.dumps(event_data), "UTF-8"), - ) + ), + use_cursor, ) - use_session.execute( - text( - f"DELETE FROM notifications WHERE record_id NOT IN (SELECT record_id FROM notifications WHERE active_ind=1 ORDER BY created_at ASC LIMIT {self._keep_notifications})" - ) + use_cursor.execute( + f"DELETE FROM notifications WHERE record_id NOT IN (SELECT record_id FROM notifications WHERE active_ind=1 ORDER BY created_at ASC LIMIT {self._keep_notifications})" ) if show_event: @@ -1816,15 +1703,13 @@ class BasicSwap(BaseApp): self._notifications_cache.pop(next(iter(self._notifications_cache))) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) - def buildNotificationsCache(self, session): + def buildNotificationsCache(self, cursor): self._notifications_cache.clear() - q = session.execute( - text( - f"SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT {self._show_notifications}" - ) + q = cursor.execute( + f"SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT {self._show_notifications}" ) for entry in q: self._notifications_cache[entry[0]] = ( @@ -1847,24 +1732,20 @@ class BasicSwap(BaseApp): try: now: int = self.getTime() - session = self.openSession() - q = session.execute( - text("SELECT COUNT(*) FROM knownidentities WHERE address = :address"), + cursor = self.openDB() + q = cursor.execute( + "SELECT COUNT(*) FROM knownidentities WHERE address = :address", {"address": address}, - ).first() + ).fetchone() if q[0] < 1: - session.execute( - text( - "INSERT INTO knownidentities (active_ind, address, created_at) VALUES (1, :address, :now)" - ), + cursor.execute( + "INSERT INTO knownidentities (active_ind, address, created_at) VALUES (1, :address, :now)", {"address": address, "now": now}, ) if "label" in data: - session.execute( - text( - "UPDATE knownidentities SET label = :label WHERE address = :address" - ), + cursor.execute( + "UPDATE knownidentities SET label = :label WHERE address = :address", {"address": address, "label": data["label"]}, ) @@ -1887,10 +1768,8 @@ class BasicSwap(BaseApp): else: raise ValueError("Unknown automation_override type") - session.execute( - text( - "UPDATE knownidentities SET automation_override = :new_value WHERE address = :address" - ), + cursor.execute( + "UPDATE knownidentities SET automation_override = :new_value WHERE address = :address", {"address": address, "new_value": new_value}, ) @@ -1913,27 +1792,23 @@ class BasicSwap(BaseApp): else: raise ValueError("Unknown visibility_override type") - session.execute( - text( - "UPDATE knownidentities SET visibility_override = :new_value WHERE address = :address" - ), + cursor.execute( + "UPDATE knownidentities SET visibility_override = :new_value WHERE address = :address", {"address": address, "new_value": new_value}, ) if "note" in data: - session.execute( - text( - "UPDATE knownidentities SET note = :note WHERE address = :address" - ), + cursor.execute( + "UPDATE knownidentities SET note = :note WHERE address = :address", {"address": address, "note": data["note"]}, ) finally: - self.closeSession(session) + self.closeDB(cursor) def listIdentities(self, filters={}): try: - session = self.openSession() + cursor = self.openDB() query_str = ( "SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, " @@ -1958,7 +1833,7 @@ class BasicSwap(BaseApp): if offset is not None: query_str += f" OFFSET {offset}" - q = session.execute(text(query_str)) + q = cursor.execute(query_str) rv = [] for row in q: identity = { @@ -1977,14 +1852,14 @@ class BasicSwap(BaseApp): rv.append(identity) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def vacuumDB(self): try: - session = self.openSession() - return session.execute(text("VACUUM")) + cursor = self.openDB() + return cursor.execute("VACUUM") finally: - self.closeSession(session) + self.closeDB(cursor) def validateOfferAmounts( self, coin_from, coin_to, amount: int, amount_to: int, min_bid_amount: int @@ -2129,10 +2004,10 @@ class BasicSwap(BaseApp): reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) try: - session = self.openSession() + cursor = self.openDB() self.checkCoinsReady(coin_from_t, coin_to_t) offer_addr = self.prepareSMSGAddress( - addr_send_from, AddressTypes.OFFER, session + addr_send_from, AddressTypes.OFFER, cursor ) offer_created_at = self.getTime() @@ -2264,7 +2139,7 @@ class BasicSwap(BaseApp): if swap_type == SwapTypes.XMR_SWAP: xmr_offer.offer_id = offer_id - session.add(xmr_offer) + self.add(xmr_offer, cursor) automation_id = extra_options.get("automation_id", -1) if automation_id == -1 and auto_accept_bids: @@ -2280,7 +2155,7 @@ class BasicSwap(BaseApp): repeat_limit=1, repeat_count=0, ) - session.add(auto_link) + self.add(auto_link, cursor) if "prefunded_itx" in extra_options: prefunded_tx = PrefundedTx( @@ -2291,21 +2166,21 @@ class BasicSwap(BaseApp): tx_type=TxTypes.ITX_PRE_FUNDED, tx_data=extra_options["prefunded_itx"], ) - session.add(prefunded_tx) + self.add(prefunded_tx, cursor) - session.add(offer) - session.add(SentOffer(offer_id=offer_id)) + self.add(offer, cursor) + self.add(SentOffer(offer_id=offer_id), cursor) finally: - self.closeSession(session) + self.closeDB(cursor) self.log.info("Sent OFFER %s", offer_id.hex()) return offer_id def revokeOffer(self, offer_id, security_token=None) -> None: self.log.info("Revoking offer %s", offer_id.hex()) - session = self.openSession() + cursor = self.openDB() try: - offer = session.query(Offer).filter_by(offer_id=offer_id).first() + offer = firstOrNone(self.query(Offer, cursor, {"offer_id": offer_id})) if ( offer.security_token is not None @@ -2333,32 +2208,45 @@ class BasicSwap(BaseApp): ) self.log.debug("Revoked offer %s in msg %s", offer_id.hex(), msg_id.hex()) finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def archiveOffer(self, offer_id) -> None: self.log.info("Archiving offer %s", offer_id.hex()) - session = self.openSession() + cursor = self.openDB() try: - offer = session.query(Offer).filter_by(offer_id=offer_id).first() + offer = firstOrNone(self.query(Offer, cursor, {"offer_id": offer_id})) if offer.active_ind != 1: raise ValueError("Offer is not active") offer.active_ind = 3 + self.updateDB( + Offer, + cursor, + [ + "offer_id", + ], + ) finally: - self.closeSession(session) + self.closeDB(cursor) def editOffer(self, offer_id, data) -> None: self.log.info("Editing offer %s", offer_id.hex()) - session = self.openSession() + cursor = self.openDB() try: - offer = session.query(Offer).filter_by(offer_id=offer_id).first() + offer = firstOrNone(self.query(Offer, cursor, {"offer_id": offer_id})) + ensure(offer, f"Offer not found: {offer_id.hex()}.") if "automation_strat_id" in data: new_automation_strat_id = data["automation_strat_id"] - link = ( - session.query(AutomationLink) - .filter_by(linked_type=Concepts.OFFER, linked_id=offer.offer_id) - .first() + link = firstOrNone( + self.query( + Offer, + cursor, + { + "linked_type": int(Concepts.OFFER), + "linked_id": offer.offer_id, + }, + ) ) if not link: if new_automation_strat_id > 0: @@ -2369,16 +2257,15 @@ class BasicSwap(BaseApp): strategy_id=new_automation_strat_id, created_at=self.getTime(), ) - session.add(link) else: if new_automation_strat_id < 1: link.active_ind = 0 else: link.strategy_id = new_automation_strat_id link.active_ind = 1 - session.add(link) + self.add(link, cursor, upsert=True) finally: - self.closeSession(session) + self.closeDB(cursor) def grindForEd25519Key(self, coin_type, evkey, key_path_base) -> bytes: ci = self.ci(coin_type) @@ -2526,25 +2413,21 @@ class BasicSwap(BaseApp): ) ) - def getReceiveAddressFromPool( - self, coin_type, bid_id: bytes, tx_type, session=None - ): + def getReceiveAddressFromPool(self, coin_type, bid_id: bytes, tx_type, cursor=None): self.log.debug( "Get address from pool bid_id {}, type {}, coin {}".format( bid_id.hex(), tx_type, coin_type ) ) try: - use_session = self.openSession(session) - record = ( - use_session.query(PooledAddress) - .filter( - sa.and_( - PooledAddress.coin_type == int(coin_type), - PooledAddress.bid_id == None, # noqa: E711 - ) + use_cursor = self.openDB(cursor) + + record = firstOrNone( + self.query( + PooledAddress, + use_cursor, + {"coin_type": int(coin_type), "bid_id": None}, ) - .first() ) if not record: address = self.getReceiveAddressForCoin(coin_type) @@ -2556,11 +2439,11 @@ class BasicSwap(BaseApp): self.ci(coin_type).isAddressMine(addr), "Pool address not owned by wallet!", ) - use_session.add(record) - use_session.commit() + self.add(record, use_cursor, upsert=True) + self.commitDB() finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) return addr def returnAddressToPool(self, bid_id: bytes, tx_type): @@ -2568,25 +2451,26 @@ class BasicSwap(BaseApp): "Return address to pool bid_id {}, type {}".format(bid_id.hex(), tx_type) ) try: - session = self.openSession() + cursor = self.openDB() try: - record = ( - session.query(PooledAddress) - .filter( - sa.and_( - PooledAddress.bid_id == bid_id, - PooledAddress.tx_type == tx_type, - ) + record = firstOrNone( + self.query( + PooledAddress, + cursor, + {"tx_type": int(tx_type), "bid_id": bid_id}, ) - .one() ) self.log.debug("Returning address to pool addr {}".format(record.addr)) - record.bid_id = None - session.commit() + + # unset PooledAddress.bid_id + query = "UPDATE addresspool SET bid_id = NULL WHERE bid_id = :bid_id AND tx_type = :tx_type" + cursor.execute(query, {"bid_id": bid_id, "tx_type": tx_type}) + + self.commitDB() except Exception as e: # noqa: F841 pass finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def getReceiveAddressForCoin(self, coin_type): new_addr = self.ci(coin_type).getNewAddress( @@ -2656,21 +2540,21 @@ class BasicSwap(BaseApp): self.log.debug("In txn: {}".format(txid)) return txid - def cacheNewAddressForCoin(self, coin_type, session=None): + def cacheNewAddressForCoin(self, coin_type, cursor=None): self.log.debug("cacheNewAddressForCoin %s", Coins(coin_type).name) key_str = "receive_addr_" + self.ci(coin_type).coin_name().lower() addr = self.getReceiveAddressForCoin(coin_type) - self.setStringKV(key_str, addr, session) + self.setStringKV(key_str, addr, cursor) return addr - def getCachedMainWalletAddress(self, ci, session=None): + def getCachedMainWalletAddress(self, ci, cursor=None): db_key = "main_wallet_addr_" + ci.coin_name().lower() - cached_addr = self.getStringKV(db_key, session) + cached_addr = self.getStringKV(db_key, cursor) if cached_addr is not None: return cached_addr self.log.warning(f"Setting {db_key}") main_address = ci.getMainWalletAddress() - self.setStringKV(db_key, main_address, session) + self.setStringKV(db_key, main_address, cursor) return main_address def checkWalletSeed(self, c) -> bool: @@ -2749,24 +2633,21 @@ class BasicSwap(BaseApp): else: raise ValueError("Wallet seed doesn't match expected.") - def getCachedAddressForCoin(self, coin_type, session=None): + def getCachedAddressForCoin(self, coin_type, cursor=None): self.log.debug("getCachedAddressForCoin %s", Coins(coin_type).name) # TODO: auto refresh after used ci = self.ci(coin_type) key_str = "receive_addr_" + ci.coin_name().lower() - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) try: - try: - addr = ( - use_session.query(DBKVString).filter_by(key=key_str).first().value - ) - except Exception: + addr = self.getStringKV(key_str, use_cursor) + if addr is None: addr = self.getReceiveAddressForCoin(coin_type) - use_session.add(DBKVString(key=key_str, value=addr)) + self.setStringKV(key_str, addr, use_cursor) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) return addr def cacheNewStealthAddressForCoin(self, coin_type): @@ -2780,59 +2661,56 @@ class BasicSwap(BaseApp): self.setStringKV(key_str, addr) return addr - def getCachedStealthAddressForCoin(self, coin_type, session=None): + def getCachedStealthAddressForCoin(self, coin_type, cursor=None): self.log.debug("getCachedStealthAddressForCoin %s", Coins(coin_type).name) if coin_type == Coins.LTC_MWEB: coin_type = Coins.LTC ci = self.ci(coin_type) key_str = "stealth_addr_" + ci.coin_name().lower() - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) try: - try: - addr = ( - use_session.query(DBKVString).filter_by(key=key_str).first().value - ) - except Exception: + addr = self.getStringKV(key_str, use_cursor) + if addr is None: addr = ci.getNewStealthAddress() self.log.info("Generated new stealth address for %s", coin_type) - use_session.add(DBKVString(key=key_str, value=addr)) + self.setStringKV(key_str, addr, use_cursor) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) return addr - def getCachedWalletRestoreHeight(self, ci, session=None): + def getCachedWalletRestoreHeight(self, ci, cursor=None): self.log.debug("getCachedWalletRestoreHeight %s", ci.coin_name()) key_str = "restore_height_" + ci.coin_name().lower() - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) try: try: - wrh = use_session.query(DBKVInt).filter_by(key=key_str).first().value + wrh = self.getIntKV(key_str, use_cursor) except Exception: wrh = ci.getWalletRestoreHeight() self.log.info( "Found restore height for %s, block %d", ci.coin_name(), wrh ) - use_session.add(DBKVInt(key=key_str, value=wrh)) + self.setIntKV(key_str, wrh, use_cursor) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) return wrh - def getWalletRestoreHeight(self, ci, session=None): + def getWalletRestoreHeight(self, ci, cursor=None): wrh = ci._restore_height if wrh is not None: return wrh - found_height = self.getCachedWalletRestoreHeight(ci, session=session) + found_height = self.getCachedWalletRestoreHeight(ci, cursor=cursor) ci.setWalletRestoreHeight(found_height) return found_height - def getNewContractId(self, session): + def getNewContractId(self, cursor): self._contract_count += 1 - session.execute( - text('UPDATE kv_int SET value = :value WHERE KEY="contract_count"'), + cursor.execute( + 'UPDATE kv_int SET value = :value WHERE KEY="contract_count"', {"value": self._contract_count}, ) return self._contract_count @@ -2849,23 +2727,23 @@ class BasicSwap(BaseApp): return ci.getProofOfFunds(amount_for, extra_commit_bytes) def saveBidInSession( - self, bid_id: bytes, bid, session, xmr_swap=None, save_in_progress=None + self, bid_id: bytes, bid, cursor, xmr_swap=None, save_in_progress=None ) -> None: - session.add(bid) + self.add(bid, cursor, upsert=True) if bid.initiate_tx: - session.add(bid.initiate_tx) + self.add(bid.initiate_tx, cursor, upsert=True) if bid.participate_tx: - session.add(bid.participate_tx) + self.add(bid.participate_tx, cursor, upsert=True) if bid.xmr_a_lock_tx: - session.add(bid.xmr_a_lock_tx) + self.add(bid.xmr_a_lock_tx, cursor, upsert=True) if bid.xmr_a_lock_spend_tx: - session.add(bid.xmr_a_lock_spend_tx) + self.add(bid.xmr_a_lock_spend_tx, cursor, upsert=True) if bid.xmr_b_lock_tx: - session.add(bid.xmr_b_lock_tx) + self.add(bid.xmr_b_lock_tx, cursor, upsert=True) for tx_type, tx in bid.txns.items(): - session.add(tx) + self.add(tx, cursor, upsert=True) if xmr_swap is not None: - session.add(xmr_swap) + self.add(xmr_swap, cursor, upsert=True) if save_in_progress is not None: if not isinstance(save_in_progress, Offer): @@ -2873,21 +2751,14 @@ class BasicSwap(BaseApp): self.swaps_in_progress[bid_id] = (bid, save_in_progress) # (bid, offer) def saveBid(self, bid_id: bytes, bid, xmr_swap=None) -> None: - session = self.openSession() + cursor = self.openDB() try: - self.saveBidInSession(bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) finally: - self.closeSession(session) - - def saveToDB(self, db_record) -> None: - session = self.openSession() - try: - session.add(db_record) - finally: - self.closeSession(session) + self.closeDB(cursor) def createActionInSession( - self, delay: int, action_type: int, linked_id: bytes, session + self, delay: int, action_type: int, linked_id: bytes, cursor ) -> None: self.log.debug("createAction %d %s", action_type, linked_id.hex()) now: int = self.getTime() @@ -2898,7 +2769,7 @@ class BasicSwap(BaseApp): action_type=action_type, linked_id=linked_id, ) - session.add(action) + self.add(action, cursor) for debug_case in self._debug_cases: bid_id, debug_ind = debug_case if bid_id == linked_id and debug_ind == DebugTypes.DUPLICATE_ACTIONS: @@ -2909,16 +2780,14 @@ class BasicSwap(BaseApp): action_type=action_type, linked_id=linked_id, ) - session.add(action) + self.add(action, cursor) def createAction(self, delay: int, action_type: int, linked_id: bytes) -> None: - # self.log.debug('createAction %d %s', action_type, linked_id.hex()) - - session = self.openSession() + cursor = self.openDB() try: - self.createActionInSession(delay, action_type, linked_id, session) + self.createActionInSession(delay, action_type, linked_id, cursor) finally: - self.closeSession(session) + self.closeDB(cursor) def logEvent( self, @@ -2926,7 +2795,7 @@ class BasicSwap(BaseApp): linked_id: bytes, event_type: int, event_msg: str, - session, + cursor, ) -> None: entry = EventLog( active_ind=1, @@ -2937,44 +2806,38 @@ class BasicSwap(BaseApp): event_msg=event_msg, ) - if session is not None: - session.add(entry) - return - session = self.openSession() + use_cursor = self.openDB(cursor) try: - session.add(entry) + self.add(entry, use_cursor) finally: - self.closeSession(session) + if cursor is None: + self.closeDB(use_cursor) def logBidEvent( - self, bid_id: bytes, event_type: int, event_msg: str, session + self, bid_id: bytes, event_type: int, event_msg: str, cursor ) -> None: self.log.debug("logBidEvent %s %s", bid_id.hex(), event_type) - self.logEvent(Concepts.BID, bid_id, event_type, event_msg, session) + self.logEvent(Concepts.BID, bid_id, event_type, event_msg, cursor) - def countBidEvents(self, bid, event_type, session): - q = session.execute( - text( - "SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x'{}' AND event_type = {}".format( - int(Concepts.BID), bid.bid_id.hex(), int(event_type) - ) + def countBidEvents(self, bid, event_type, cursor): + q = cursor.execute( + "SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x'{}' AND event_type = {}".format( + int(Concepts.BID), bid.bid_id.hex(), int(event_type) ) - ).first() + ).fetchone() return q[0] def getEvents(self, linked_type: int, linked_id: bytes): events = [] - session = self.openSession() + cursor = self.openDB() try: - for entry in session.query(EventLog).filter( - sa.and_( - EventLog.linked_type == linked_type, EventLog.linked_id == linked_id - ) + for entry in self.query( + EventLog, cursor, {"linked_type": linked_type, "linked_id": linked_id} ): events.append(entry) return events finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def addMessageLink( self, @@ -2983,7 +2846,7 @@ class BasicSwap(BaseApp): msg_type: int, msg_id: bytes, msg_sequence: int = 0, - session=None, + cursor=None, ) -> None: entry = MessageLink( active_ind=1, @@ -2995,14 +2858,12 @@ class BasicSwap(BaseApp): msg_id=msg_id, ) - if session is not None: - session.add(entry) - return - session = self.openSession() + use_cursor = self.openDB(cursor) try: - session.add(entry) + self.add(entry, use_cursor) finally: - self.closeSession(session) + if cursor is None: + self.closeDB(use_cursor) def getLinkedMessageId( self, @@ -3010,25 +2871,23 @@ class BasicSwap(BaseApp): linked_id: int, msg_type: int, msg_sequence: int = 0, - session=None, + cursor=None, ) -> bytes: try: - use_session = self.openSession(session) - q = use_session.execute( - text( - "SELECT msg_id FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence" - ), + use_cursor = self.openDB(cursor) + q = use_cursor.execute( + "SELECT msg_id FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence", { "linked_type": linked_type, "linked_id": linked_id, "msg_type": msg_type, "msg_sequence": msg_sequence, }, - ).first() + ).fetchone() return q[0] finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def countMessageLinks( self, @@ -3036,25 +2895,23 @@ class BasicSwap(BaseApp): linked_id: int, msg_type: int, msg_sequence: int = 0, - session=None, + cursor=None, ) -> int: try: - use_session = self.openSession(session) - q = use_session.execute( - text( - "SELECT COUNT(*) FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence" - ), + use_cursor = self.openDB(cursor) + q = use_cursor.execute( + "SELECT COUNT(*) FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence", { "linked_type": linked_type, "linked_id": linked_id, "msg_type": msg_type, "msg_sequence": msg_sequence, }, - ).first() + ).fetchone() return q[0] finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def setBidAmounts( self, amount: int, offer, extra_options, ci_from @@ -3151,7 +3008,7 @@ class BasicSwap(BaseApp): self.validateBidAmount(offer, amount, bid_rate) try: - session = self.openSession() + cursor = self.openDB() self.checkCoinsReady(coin_from, coin_to) msg_buf = BidMessage() @@ -3172,7 +3029,7 @@ class BasicSwap(BaseApp): if len(proof_utxos) > 0: msg_buf.proof_utxos = ci_to.encodeProofUtxos(proof_utxos) - contract_count = self.getNewContractId(session) + contract_count = self.getNewContractId(cursor) contract_pubkey = self.getContractPubkey( dt.datetime.fromtimestamp(now).date(), contract_count ) @@ -3187,9 +3044,7 @@ class BasicSwap(BaseApp): bid_bytes = msg_buf.to_bytes() payload_hex = str.format("{:02x}", MessageTypes.BID) + bid_bytes.hex() - bid_addr = self.prepareSMSGAddress( - addr_send_from, AddressTypes.BID, session - ) + bid_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.BID, cursor) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) bid_id = self.sendSmsg(bid_addr, offer.addr_from, payload_hex, msg_valid) @@ -3217,20 +3072,20 @@ class BasicSwap(BaseApp): if len(msg_buf.pkhash_buyer_to) > 0: bid.pkhash_buyer_to = msg_buf.pkhash_buyer_to - self.saveBidInSession(bid_id, bid, session) + self.saveBidInSession(bid_id, bid, cursor) self.log.info("Sent BID %s", bid_id.hex()) return bid_id finally: - self.closeSession(session) + self.closeDB(cursor) - def getOffer(self, offer_id: bytes, sent: bool = False, session=None): + def getOffer(self, offer_id: bytes, cursor=None): try: - use_session = self.openSession(session) - return use_session.query(Offer).filter_by(offer_id=offer_id).first() + use_cursor = self.openDB(cursor) + return firstOrNone(self.query(Offer, use_cursor, {"offer_id": offer_id})) finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def setTxBlockInfoFromHeight(self, ci, tx, height: int) -> None: try: @@ -3241,9 +3096,9 @@ class BasicSwap(BaseApp): except Exception as e: self.log.warning(f"setTxBlockInfoFromHeight failed {e}") - def loadBidTxns(self, bid, session) -> None: + def loadBidTxns(self, bid, cursor) -> None: bid.txns = {} - for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)): + for stx in self.query(SwapTx, cursor, {"bid_id": bid.bid_id}): if stx.tx_type == TxTypes.ITX: bid.initiate_tx = stx elif stx.tx_type == TxTypes.PTX: @@ -3257,104 +3112,111 @@ class BasicSwap(BaseApp): else: bid.txns[stx.tx_type] = stx - def getXmrBidFromSession(self, session, bid_id: bytes, sent: bool = False): - bid = session.query(Bid).filter_by(bid_id=bid_id).first() + def getXmrBidFromSession(self, cursor, bid_id: bytes): + bid = firstOrNone(self.query(Bid, cursor, {"bid_id": bid_id})) xmr_swap = None if bid: - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid_id).first() - self.loadBidTxns(bid, session) + xmr_swap = firstOrNone(self.query(XmrSwap, cursor, {"bid_id": bid_id})) + self.loadBidTxns(bid, cursor) return bid, xmr_swap - def getXmrBid(self, bid_id: bytes, sent: bool = False): + def getXmrBid(self, bid_id: bytes): try: - session = self.openSession() - return self.getXmrBidFromSession(session, bid_id, sent) + cursor = self.openDB() + return self.getXmrBidFromSession(cursor, bid_id) finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) - def getXmrOfferFromSession(self, session, offer_id: bytes, sent: bool = False): - offer = session.query(Offer).filter_by(offer_id=offer_id).first() + def getXmrOfferFromSession(self, cursor, offer_id: bytes): + offer = firstOrNone(self.query(Offer, cursor, {"offer_id": offer_id})) xmr_offer = None if offer: - xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer_id).first() + xmr_offer = firstOrNone( + self.query(XmrOffer, cursor, {"offer_id": offer_id}) + ) return offer, xmr_offer - def getXmrOffer(self, offer_id: bytes, sent: bool = False, session=None): + def getXmrOffer(self, offer_id: bytes, cursor=None): try: - use_session = self.openSession(session) - return self.getXmrOfferFromSession(use_session, offer_id, sent) + use_cursor = self.openDB(cursor) + return self.getXmrOfferFromSession(use_cursor, offer_id) finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) - def getBid(self, bid_id: bytes, session=None): + def getBid(self, bid_id: bytes, cursor=None, with_txns=True): try: - use_session = self.openSession(session) - bid = use_session.query(Bid).filter_by(bid_id=bid_id).first() - if bid: - self.loadBidTxns(bid, use_session) + use_cursor = self.openDB(cursor) + bid = firstOrNone(self.query(Bid, use_cursor, {"bid_id": bid_id})) + if bid and with_txns: + self.loadBidTxns(bid, use_cursor) return bid finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) - def getBidAndOffer(self, bid_id: bytes, session=None): + def getBidAndOffer(self, bid_id: bytes, cursor=None, with_txns=True): try: - use_session = self.openSession(session) - bid = use_session.query(Bid).filter_by(bid_id=bid_id).first() + use_cursor = self.openDB(cursor) + bid = firstOrNone(self.query(Bid, use_cursor, {"bid_id": bid_id})) offer = None if bid: - offer = ( - use_session.query(Offer).filter_by(offer_id=bid.offer_id).first() + offer = firstOrNone( + self.query(Offer, use_cursor, {"offer_id": bid.offer_id}) ) - self.loadBidTxns(bid, use_session) + if with_txns: + self.loadBidTxns(bid, use_cursor) return bid, offer finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def getXmrBidAndOffer(self, bid_id: bytes, list_events=True): try: - session = self.openSession() + cursor = self.openDB() xmr_swap = None offer = None xmr_offer = None events = [] - bid = session.query(Bid).filter_by(bid_id=bid_id).first() + bid = firstOrNone(self.query(Bid, cursor, {"bid_id": bid_id})) if bid: - offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() + offer = firstOrNone( + self.query(Offer, cursor, {"offer_id": bid.offer_id}) + ) if offer and offer.swap_type == SwapTypes.XMR_SWAP: - xmr_swap = ( - session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + xmr_swap = firstOrNone( + self.query(XmrSwap, cursor, {"bid_id": bid.bid_id}) ) - xmr_offer = ( - session.query(XmrOffer).filter_by(offer_id=bid.offer_id).first() + xmr_offer = firstOrNone( + self.query(XmrOffer, cursor, {"offer_id": bid.offer_id}) ) - self.loadBidTxns(bid, session) + self.loadBidTxns(bid, cursor) if list_events: - events = self.list_bid_events(bid.bid_id, session) + events = self.list_bid_events(bid.bid_id, cursor) return bid, xmr_swap, offer, xmr_offer, events finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def getIdentity(self, address: str): try: - session = self.openSession() - identity = session.query(KnownIdentity).filter_by(address=address).first() + cursor = self.openDB() + identity = firstOrNone( + self.query(KnownIdentity, cursor, {"address": address}) + ) return identity finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) - def list_bid_events(self, bid_id: bytes, session): + def list_bid_events(self, bid_id: bytes, cursor): query_str = ( "SELECT created_at, event_type, event_msg FROM eventlog " + "WHERE active_ind = 1 AND linked_type = {} AND linked_id = x'{}' ".format( Concepts.BID, bid_id.hex() ) ) - q = session.execute(text(query_str)) + q = cursor.execute(query_str) events = [] for row in q: events.append({"at": row[0], "desc": describeEventEntry(row[1], row[2])}) @@ -3363,7 +3225,7 @@ class BasicSwap(BaseApp): "SELECT created_at, trigger_at FROM actions " + "WHERE active_ind = 1 AND linked_id = x'{}' ".format(bid_id.hex()) ) - q = session.execute(text(query_str)) + q = cursor.execute(query_str) for row in q: events.append( { @@ -3376,13 +3238,13 @@ class BasicSwap(BaseApp): return events - def acceptBid(self, bid_id: bytes, session=None) -> None: + def acceptBid(self, bid_id: bytes, cursor=None) -> None: self.log.info("Accepting bid %s", bid_id.hex()) try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) - bid, offer = self.getBidAndOffer(bid_id, use_session) + bid, offer = self.getBidAndOffer(bid_id, use_cursor) ensure(bid, "Bid not found") ensure(offer, "Offer not found") @@ -3403,15 +3265,15 @@ class BasicSwap(BaseApp): offer.coin_from, offer.coin_to ) if reverse_bid: - return self.acceptADSReverseBid(bid_id, use_session) - return self.acceptXmrBid(bid_id, use_session) + return self.acceptADSReverseBid(bid_id, use_cursor) + return self.acceptXmrBid(bid_id, use_cursor) ensure( bid.protocol_version >= MINPROTO_VERSION_SECRET_HASH, "Incompatible bid protocol version", ) if bid.contract_count is None: - bid.contract_count = self.getNewContractId(use_session) + bid.contract_count = self.getNewContractId(use_cursor) coin_from = Coins(offer.coin_from) ci_from = self.ci(coin_from) @@ -3475,7 +3337,7 @@ class BasicSwap(BaseApp): Concepts.OFFER, offer.offer_id, TxTypes.ITX_PRE_FUNDED, - session=use_session, + cursor=use_cursor, ) txn, lock_tx_vout = self.createInitiateTxn( coin_from, bid_id, bid, script, prefunded_tx @@ -3483,7 +3345,7 @@ class BasicSwap(BaseApp): # Store the signed refund txn in case wallet is locked when refund is possible refund_txn = self.createRefundTxn( - coin_from, txn, offer, bid, script, session=use_session + coin_from, txn, offer, bid, script, cursor=use_cursor ) bid.initiate_txn_refund = bytes.fromhex(refund_txn) @@ -3508,7 +3370,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.ITX_PUBLISHED, "", - use_session, + use_cursor, ) # Check non-bip68 final @@ -3545,18 +3407,18 @@ class BasicSwap(BaseApp): bid_id, MessageTypes.BID_ACCEPT, accept_msg_id, - session=use_session, + cursor=use_cursor, ) self.log.info("Sent BID_ACCEPT %s", accept_msg_id.hex()) bid.setState(BidStates.BID_ACCEPTED) - self.saveBidInSession(bid_id, bid, use_session) + self.saveBidInSession(bid_id, bid, use_cursor) self.swaps_in_progress[bid_id] = (bid, offer) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def sendXmrSplitMessages( self, @@ -3590,8 +3452,8 @@ class BasicSwap(BaseApp): self.log.debug("postXmrBid %s", offer_id.hex()) try: - session = self.openSession() - offer, xmr_offer = self.getXmrOffer(offer_id, session=session) + cursor = self.openDB() + offer, xmr_offer = self.getXmrOffer(offer_id, cursor=cursor) ensure(offer, "Offer not found: {}.".format(offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) @@ -3651,10 +3513,10 @@ class BasicSwap(BaseApp): ) xmr_swap = XmrSwap() - xmr_swap.contract_count = self.getNewContractId(session) + xmr_swap.contract_count = self.getNewContractId(cursor) bid_addr = self.prepareSMSGAddress( - addr_send_from, AddressTypes.BID, session + addr_send_from, AddressTypes.BID, cursor ) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) @@ -3680,8 +3542,8 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_REQUEST_SENT) - self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(xmr_swap.bid_id, bid, cursor, xmr_swap) + self.commitDB() self.log.info("Sent ADS_BID_LF %s", xmr_swap.bid_id.hex()) return xmr_swap.bid_id @@ -3694,7 +3556,7 @@ class BasicSwap(BaseApp): msg_buf.amount_to = amount_to address_out = self.getReceiveAddressFromPool( - coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK, session=session + coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK, cursor=cursor ) if coin_from in (Coins.PART_BLIND,): addrinfo = ci_from.rpc("getaddressinfo", [address_out]) @@ -3703,7 +3565,7 @@ class BasicSwap(BaseApp): msg_buf.dest_af = ci_from.decodeAddress(address_out) xmr_swap = XmrSwap() - xmr_swap.contract_count = self.getNewContractId(session) + xmr_swap.contract_count = self.getNewContractId(cursor) xmr_swap.dest_af = msg_buf.dest_af for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False @@ -3768,9 +3630,7 @@ class BasicSwap(BaseApp): str.format("{:02x}", MessageTypes.XMR_BID_FL) + bid_bytes.hex() ) - bid_addr = self.prepareSMSGAddress( - addr_send_from, AddressTypes.BID, session - ) + bid_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.BID, cursor) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) xmr_swap.bid_id = self.sendSmsg( @@ -3807,7 +3667,7 @@ class BasicSwap(BaseApp): bid.chain_a_height_start = ci_from.getChainHeight() bid.chain_b_height_start = ci_to.getChainHeight() - wallet_restore_height = self.getWalletRestoreHeight(ci_to, session) + wallet_restore_height = self.getWalletRestoreHeight(ci_to, cursor) if bid.chain_b_height_start < wallet_restore_height: bid.chain_b_height_start = wallet_restore_height self.log.warning( @@ -3818,7 +3678,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_SENT) - self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap) + self.saveBidInSession(xmr_swap.bid_id, bid, cursor, xmr_swap) for k, msg_id in bid_msg_ids.items(): self.addMessageLink( Concepts.BID, @@ -3826,22 +3686,22 @@ class BasicSwap(BaseApp): MessageTypes.BID, msg_id, msg_sequence=k, - session=session, + cursor=cursor, ) self.log.info("Sent XMR_BID_FL %s", xmr_swap.bid_id.hex()) return xmr_swap.bid_id finally: - self.closeSession(session) + self.closeDB(cursor) - def acceptXmrBid(self, bid_id: bytes, session=None) -> None: + def acceptXmrBid(self, bid_id: bytes, cursor=None) -> None: # MSG1F and MSG2F L -> F self.log.info("Accepting adaptor-sig bid %s", bid_id.hex()) now: int = self.getTime() try: - use_session = self.openSession(session) - bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) + use_cursor = self.openDB(cursor) + bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) ensure(bid.expire_at > now, "Bid expired") @@ -3855,7 +3715,7 @@ class BasicSwap(BaseApp): "Wrong bid state: {}".format(str(BidStates(last_bid_state))), ) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, session=use_session) + offer, xmr_offer = self.getXmrOffer(bid.offer_id, cursor=use_cursor) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure( xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) @@ -3873,7 +3733,7 @@ class BasicSwap(BaseApp): ) if xmr_swap.contract_count is None: - xmr_swap.contract_count = self.getNewContractId(use_session) + xmr_swap.contract_count = self.getNewContractId(use_cursor) for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False kbvl = self.getPathKey( @@ -3919,7 +3779,7 @@ class BasicSwap(BaseApp): lockExtraArgs = dict() if self.isBchXmrSwap(offer): pkh_refund_to = ci_from.decodeAddress( - self.getCachedAddressForCoin(coin_from, use_session) + self.getCachedAddressForCoin(coin_from, use_cursor) ) pkh_dest = xmr_swap.dest_af # refund script @@ -3950,7 +3810,7 @@ class BasicSwap(BaseApp): Concepts.OFFER, bid.offer_id, TxTypes.ITX_PRE_FUNDED, - session=use_session, + cursor=use_cursor, ) if prefunded_tx: xmr_swap.a_lock_tx = pi.promoteMockTx( @@ -3999,7 +3859,7 @@ class BasicSwap(BaseApp): ) ensure(v, "Invalid coin A lock refund tx leader sig") pkh_refund_to = ci_from.decodeAddress( - self.getCachedAddressForCoin(coin_from, use_session) + self.getCachedAddressForCoin(coin_from, use_cursor) ) xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx( xmr_swap.a_lock_refund_tx, @@ -4119,7 +3979,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_ACCEPTED) # ADS - self.saveBidInSession(bid_id, bid, use_session, xmr_swap=xmr_swap) + self.saveBidInSession(bid_id, bid, use_cursor, xmr_swap=xmr_swap) for k, msg_id in bid_msg_ids.items(): self.addMessageLink( Concepts.BID, @@ -4127,23 +3987,23 @@ class BasicSwap(BaseApp): MessageTypes.BID_ACCEPT, msg_id, msg_sequence=k, - session=use_session, + cursor=use_cursor, ) # Add to swaps_in_progress only when waiting on txns self.log.info("Sent XMR_BID_ACCEPT_LF %s", bid_id.hex()) return bid_id finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) - def acceptADSReverseBid(self, bid_id: bytes, session=None) -> None: + def acceptADSReverseBid(self, bid_id: bytes, cursor=None) -> None: self.log.info("Accepting reverse adaptor-sig bid %s", bid_id.hex()) now: int = self.getTime() try: - use_session = self.openSession(session) - bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) + use_cursor = self.openDB(cursor) + bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) ensure(bid.expire_at > now, "Bid expired") @@ -4157,7 +4017,7 @@ class BasicSwap(BaseApp): "Wrong bid state: {}".format(str(BidStates(last_bid_state))), ) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, session=use_session) + offer, xmr_offer = self.getXmrOffer(bid.offer_id, cursor=use_cursor) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure( xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) @@ -4171,7 +4031,7 @@ class BasicSwap(BaseApp): ci_to = self.ci(coin_to) if xmr_swap.contract_count is None: - xmr_swap.contract_count = self.getNewContractId(use_session) + xmr_swap.contract_count = self.getNewContractId(use_cursor) for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False kbvf = self.getPathKey( @@ -4200,7 +4060,7 @@ class BasicSwap(BaseApp): ) address_out = self.getReceiveAddressFromPool( - coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK, session=use_session + coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK, cursor=use_cursor ) if coin_from == Coins.PART_BLIND: addrinfo = ci_from.rpc("getaddressinfo", [address_out]) @@ -4259,29 +4119,34 @@ class BasicSwap(BaseApp): MessageTypes.ADS_BID_ACCEPT_FL, msg_id, msg_sequence=k, - session=use_session, + cursor=use_cursor, ) self.log.info("Sent ADS_BID_ACCEPT_FL %s", bid_msg_ids[0].hex()) - self.saveBidInSession(bid_id, bid, use_session, xmr_swap=xmr_swap) + self.saveBidInSession(bid_id, bid, use_cursor, xmr_swap=xmr_swap) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) - def deactivateBidForReason(self, bid_id: bytes, new_state, session_in=None) -> None: + def deactivateBidForReason(self, bid_id: bytes, new_state, cursor=None) -> None: try: - session = self.openSession(session_in) - bid = session.query(Bid).filter_by(bid_id=bid_id).first() + use_cursor = self.openDB(cursor) + bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False) ensure(bid, "Bid not found") - offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() ensure(offer, "Offer not found") bid.setState(new_state) - self.deactivateBid(session, offer, bid) - session.add(bid) - session.commit() + self.deactivateBid(use_cursor, offer, bid) + self.updateDB( + bid, + use_cursor, + [ + "bid_id", + ], + ) + self.commitDB() finally: - if session_in is None: - self.closeSession(session) + if cursor is None: + self.closeDB(use_cursor) def abandonBid(self, bid_id: bytes) -> None: if not self.debug: @@ -4291,11 +4156,9 @@ class BasicSwap(BaseApp): self.log.info("Abandoning Bid %s", bid_id.hex()) self.deactivateBidForReason(bid_id, BidStates.BID_ABANDONED) - def timeoutBid(self, bid_id: bytes, session_in=None) -> None: + def timeoutBid(self, bid_id: bytes, cursor=None) -> None: self.log.info("Bid %s timed-out", bid_id.hex()) - self.deactivateBidForReason( - bid_id, BidStates.SWAP_TIMEDOUT, session_in=session_in - ) + self.deactivateBidForReason(bid_id, BidStates.SWAP_TIMEDOUT, cursor=cursor) def setBidError( self, bid_id: bytes, bid, error_str: str, save_bid: bool = True, xmr_swap=None @@ -4476,7 +4339,7 @@ class BasicSwap(BaseApp): for_txn_type="participate", addr_redeem_out=None, fee_rate=None, - session=None, + cursor=None, ): self.log.debug("createRedeemTxn for coin %s", Coins(coin_type).name) ci = self.ci(coin_type) @@ -4542,7 +4405,7 @@ class BasicSwap(BaseApp): if for_txn_type == "participate" else TxTypes.ITX_REDEEM ), - session, + cursor, ) assert addr_redeem_out is not None @@ -4643,7 +4506,7 @@ class BasicSwap(BaseApp): txn_script: bytearray, addr_refund_out=None, tx_type=TxTypes.ITX_REFUND, - session=None, + cursor=None, ): self.log.debug("createRefundTxn for coin %s", Coins(coin_type).name) if self.coin_clients[coin_type]["connection_type"] != "rpc": @@ -4697,7 +4560,7 @@ class BasicSwap(BaseApp): if addr_refund_out is None: addr_refund_out = self.getReceiveAddressFromPool( - coin_type, bid.bid_id, tx_type, session + coin_type, bid.bid_id, tx_type, cursor ) ensure(addr_refund_out is not None, "addr_refund_out is null") self.log.debug("addr_refund_out %s", addr_refund_out) @@ -4867,7 +4730,7 @@ class BasicSwap(BaseApp): # Bid saved in checkBidState - def setLastHeightCheckedStart(self, coin_type, tx_height: int, session=None) -> int: + def setLastHeightCheckedStart(self, coin_type, tx_height: int, cursor=None) -> int: ci = self.ci(coin_type) coin_name = ci.coin_name() if tx_height < 1: @@ -4879,12 +4742,12 @@ class BasicSwap(BaseApp): if len(cc["watched_outputs"]) == 0 and len(cc["watched_scripts"]) == 0: cc["last_height_checked"] = tx_height cc["block_check_min_time"] = block_time - self.setIntKV("block_check_min_time_" + coin_name, block_time, session) + self.setIntKV("block_check_min_time_" + coin_name, block_time, cursor) self.log.debug("Start checking %s chain at height %d", coin_name, tx_height) elif cc["last_height_checked"] > tx_height: cc["last_height_checked"] = tx_height cc["block_check_min_time"] = block_time - self.setIntKV("block_check_min_time_" + coin_name, block_time, session) + self.setIntKV("block_check_min_time_" + coin_name, block_time, cursor) self.log.debug( "Rewind %s chain last height checked to %d", coin_name, tx_height ) @@ -5057,7 +4920,7 @@ class BasicSwap(BaseApp): return sum_unspent return None - def findTxB(self, ci_to, xmr_swap, bid, session, bid_sender: bool) -> bool: + def findTxB(self, ci_to, xmr_swap, bid, cursor, bid_sender: bool) -> bool: bid_changed = False found_tx = None @@ -5086,19 +4949,19 @@ class BasicSwap(BaseApp): ) if isinstance(found_tx, int) and found_tx == -1: - if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1: + if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, cursor) < 1: self.logBidEvent( bid.bid_id, EventLogTypes.LOCK_TX_B_INVALID, "Detected invalid lock tx B", - session, + cursor, ) bid_changed = True elif found_tx is not None: if found_tx["height"] != 0 and ( bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height ): - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, "", session) + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, "", cursor) found_txid = bytes.fromhex(found_tx["txid"]) if ( @@ -5136,17 +4999,18 @@ class BasicSwap(BaseApp): was_sent: bool = bid.was_received if reverse_bid else bid.was_sent was_received: bool = bid.was_sent if reverse_bid else bid.was_received - session = None + cursor = None try: - session = self.openSession() - xmr_offer = ( - session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() + cursor = self.openDB() + + xmr_offer = firstOrNone( + self.query(XmrOffer, cursor, {"offer_id": offer.offer_id}) ) ensure( xmr_offer, "Adaptor-sig offer not found: {}.".format(offer.offer_id.hex()), ) - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + xmr_swap = firstOrNone(self.query(XmrSwap, cursor, {"bid_id": bid.bid_id})) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: @@ -5160,14 +5024,14 @@ class BasicSwap(BaseApp): ) bid.setState(BidStates.BID_STALLED_FOR_TEST) rv = True - self.saveBidInSession(bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) self.logBidEvent( bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) - session.commit() + self.commitDB() return rv if TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND not in bid.txns: @@ -5193,7 +5057,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED, "", - session, + cursor, ) self.log.info( @@ -5206,8 +5070,8 @@ class BasicSwap(BaseApp): tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND, txid=bytes.fromhex(txid_str), ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() except Exception as ex: self.log.debug( "Trying to publish coin a lock refund spend tx: %s", @@ -5219,8 +5083,8 @@ class BasicSwap(BaseApp): self.createCoinALockRefundSwipeTx( ci_from, bid, offer, xmr_swap, xmr_offer ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns: try: @@ -5229,7 +5093,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, "", - session, + cursor, ) self.log.info( "Submitted coin a lock refund swipe tx for bid {}".format( @@ -5278,7 +5142,7 @@ class BasicSwap(BaseApp): bid_id, EventLogTypes.BCH_MERCY_TX_PUBLISHED, "", - session, + cursor, ) else: self.log.info( @@ -5287,8 +5151,8 @@ class BasicSwap(BaseApp): ) ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() except Exception as ex: self.log.debug( "Trying to publish coin a lock refund swipe tx: %s", @@ -5305,8 +5169,8 @@ class BasicSwap(BaseApp): ) rv = True # Remove from swaps_in_progress bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() return rv else: # not XMR_SWAP_A_LOCK_REFUND in bid.txns if ( @@ -5316,7 +5180,7 @@ class BasicSwap(BaseApp): try: txid = ci_from.publishTx(xmr_swap.a_lock_refund_tx) - # bch txids change + # BCH txids change if self.isBchXmrSwap(offer): self.log.debug( "Recomputing refund spend transaction and txid after submitting lock tx spend." @@ -5340,15 +5204,15 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED, "", - session, + cursor, ) bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, txid=bytes.fromhex(txid), ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() return rv except Exception as ex: if ci_from.isTxExistsError(str(ex)): @@ -5364,8 +5228,8 @@ class BasicSwap(BaseApp): tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, txid=txid, ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() return rv state = BidStates(bid.state) @@ -5379,7 +5243,7 @@ class BasicSwap(BaseApp): if was_sent and bid.xmr_b_lock_tx: if ( self.countQueuedActions( - session, bid_id, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B + cursor, bid_id, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B ) < 1 ): @@ -5393,9 +5257,9 @@ class BasicSwap(BaseApp): delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, - session, + cursor, ) - session.commit() + self.commitDB() elif state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: if bid.xmr_a_lock_tx is None or bid.xmr_a_lock_tx.txid is None: return rv @@ -5445,7 +5309,7 @@ class BasicSwap(BaseApp): # Update watcher self.removeWatchedOutput(ci_from.coin_type(), bid.bid_id, None) self.removeWatchedOutput(ci_to.coin_type(), bid.bid_id, None) - self.watchXmrSwap(bid, offer, xmr_swap, session) + self.watchXmrSwap(bid, offer, xmr_swap, cursor) bid_changed = True if ( @@ -5459,7 +5323,7 @@ class BasicSwap(BaseApp): and lock_tx_chain_info["height"] != 0 ): self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, "", session + bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, "", cursor ) self.setTxBlockInfoFromHeight( ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info["height"] @@ -5476,7 +5340,7 @@ class BasicSwap(BaseApp): if lock_tx_chain_info["depth"] >= ci_from.blocks_confirmed: self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, "", session + bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, "", cursor ) bid.xmr_a_lock_tx.setState(TxStates.TX_CONFIRMED) @@ -5491,7 +5355,7 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session + delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, cursor ) # bid.setState(BidStates.SWAP_DELAYING) elif ci_to.watch_blocks_for_scripts(): @@ -5507,7 +5371,7 @@ class BasicSwap(BaseApp): ) dest_script = ci_to.getPkDest(xmr_swap.pkbs) self.setLastHeightCheckedStart( - ci_to.coin_type(), chain_b_block_header["height"], session + ci_to.coin_type(), chain_b_block_header["height"], cursor ) self.addWatchedScript( ci_to.coin_type(), @@ -5517,14 +5381,14 @@ class BasicSwap(BaseApp): ) if bid_changed: - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() elif state in ( BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND, ): - bid_changed = self.findTxB(ci_to, xmr_swap, bid, session, was_sent) + bid_changed = self.findTxB(ci_to, xmr_swap, bid, cursor, was_sent) if ( bid.xmr_b_lock_tx @@ -5544,14 +5408,14 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) elif ( chain_height - bid.xmr_b_lock_tx.chain_height >= ci_to.blocks_confirmed ): self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_B_CONFIRMED, "", session + bid.bid_id, EventLogTypes.LOCK_TX_B_CONFIRMED, "", cursor ) bid.xmr_b_lock_tx.setState(TxStates.TX_CONFIRMED) bid.setState(BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED) @@ -5574,12 +5438,12 @@ class BasicSwap(BaseApp): delay, ActionTypes.SEND_XMR_LOCK_RELEASE, bid_id, - session, + cursor, ) if bid_changed: - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() elif state == BidStates.XMR_SWAP_LOCK_RELEASED: # Wait for script spend tx to confirm # TODO: Use explorer to get tx / block hash for getrawtransaction @@ -5593,7 +5457,7 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_spend_tx_id.hex(), ) self.process_XMR_SWAP_A_LOCK_tx_spend( - bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex, session + bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex, cursor ) except Exception as e: self.log.debug( @@ -5603,7 +5467,7 @@ class BasicSwap(BaseApp): if ( was_received and self.countQueuedActions( - session, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B + cursor, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B ) < 1 ): @@ -5618,7 +5482,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) else: bid.setState(BidStates.SWAP_DELAYING) @@ -5632,10 +5496,10 @@ class BasicSwap(BaseApp): delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, - session, + cursor, ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: txid_hex = bid.xmr_b_lock_tx.spend_txid.hex() @@ -5644,8 +5508,8 @@ class BasicSwap(BaseApp): self.log.info("Found coin b lock spend tx bid %s", bid_id.hex()) rv = True # Remove from swaps_in_progress bid.setState(BidStates.SWAP_COMPLETED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() elif state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] @@ -5669,13 +5533,13 @@ class BasicSwap(BaseApp): ci_from, refund_tx, lock_refund_tx_chain_info["height"] ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - session.commit() + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) + self.commitDB() except Exception as ex: raise ex finally: - self.closeSession(session) + self.closeDB(cursor) return rv @@ -6193,14 +6057,14 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid) def process_XMR_SWAP_A_LOCK_tx_spend( - self, bid_id: bytes, spend_txid_hex, spend_txn_hex, session=None + self, bid_id: bytes, spend_txid_hex, spend_txn_hex, cursor=None ) -> None: self.log.debug( "Detected spend of Adaptor-sig swap coin a lock tx for bid %s", bid_id.hex() ) try: - use_session = self.openSession(session) - bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) + use_cursor = self.openDB(cursor) + bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) @@ -6208,9 +6072,7 @@ class BasicSwap(BaseApp): self.log.debug("Bid stalled %s", bid_id.hex()) return - offer, xmr_offer = self.getXmrOfferFromSession( - use_session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure( xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) @@ -6292,7 +6154,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND) self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, "", use_session + bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, "", use_cursor ) else: self.setBidError( @@ -6303,13 +6165,13 @@ class BasicSwap(BaseApp): ) self.saveBidInSession( - bid_id, bid, use_session, xmr_swap, save_in_progress=offer + bid_id, bid, use_cursor, xmr_swap, save_in_progress=offer ) except Exception as ex: self.logException(f"process_XMR_SWAP_A_LOCK_tx_spend {ex}") finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def process_XMR_SWAP_A_LOCK_REFUND_tx_spend( self, bid_id: bytes, spend_txid_hex: str, spend_txn @@ -6319,14 +6181,12 @@ class BasicSwap(BaseApp): bid_id.hex(), ) try: - session = self.openSession() - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + cursor = self.openDB() + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure( xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) @@ -6371,7 +6231,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN, "", - session, + cursor, ) if bid.xmr_a_lock_tx: @@ -6398,7 +6258,7 @@ class BasicSwap(BaseApp): delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, - session, + cursor, ) else: # Other side refunded before swap lock tx was sent @@ -6450,7 +6310,7 @@ class BasicSwap(BaseApp): delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, - session, + cursor, ) else: bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED) @@ -6466,13 +6326,11 @@ class BasicSwap(BaseApp): mercy_keyshare ) - self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer - ) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) except Exception as ex: self.logException(f"process_XMR_SWAP_A_LOCK_REFUND_tx_spend {ex}") finally: - self.closeSession(session) + self.closeDB(cursor) def processSpentOutput( self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn @@ -6585,7 +6443,7 @@ class BasicSwap(BaseApp): self.log.info("Found mercy tx for bid: {}".format(bid_id.hex())) self.logBidEvent( - bid_id, EventLogTypes.BCH_MERCY_TX_FOUND, txid.hex(), session=None + bid_id, EventLogTypes.BCH_MERCY_TX_FOUND, txid.hex(), cursor=None ) if bid_id not in self.swaps_in_progress: @@ -6615,33 +6473,28 @@ class BasicSwap(BaseApp): self.removeWatchedScript(coin_type, bid_id, watched_script.script) - def checkNewBlock(self, coin_type, c): - pass - - def haveCheckedPrevBlock(self, ci, c, block, session=None) -> bool: + def haveCheckedPrevBlock(self, ci, c, block, cursor=None) -> bool: previousblockhash = bytes.fromhex(block["previousblockhash"]) try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) - q = use_session.execute( - text( - "SELECT COUNT(*) FROM checkedblocks WHERE block_hash = :block_hash" - ), + q = use_cursor.execute( + "SELECT COUNT(*) FROM checkedblocks WHERE block_hash = :block_hash", {"block_hash": previousblockhash}, - ).first() + ).fetchone() if q[0] > 0: return True finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) return False - def updateCheckedBlock(self, ci, cc, block, session=None) -> None: + def updateCheckedBlock(self, ci, cc, block, cursor=None) -> None: now: int = self.getTime() try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) block_height = int(block["height"]) if cc["last_height_checked"] != block_height: @@ -6649,13 +6502,13 @@ class BasicSwap(BaseApp): self.setIntKV( "last_height_checked_" + ci.coin_name().lower(), block_height, - session=use_session, + cursor=use_cursor, ) query = """INSERT INTO checkedblocks (created_at, coin_type, block_height, block_hash, block_time) VALUES (:now, :coin_type, :block_height, :block_hash, :block_time)""" - use_session.execute( - text(query), + use_cursor.execute( + query, { "now": now, "coin_type": int(ci.coin_type()), @@ -6666,8 +6519,8 @@ class BasicSwap(BaseApp): ) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def checkForSpends(self, coin_type, c): # assert (self.mxDB.locked()) @@ -6868,7 +6721,7 @@ class BasicSwap(BaseApp): return now: int = self.getTime() - session = self.openSession() + cursor = self.openDB() grace_period: int = 60 * 60 try: @@ -6876,8 +6729,8 @@ class BasicSwap(BaseApp): "SELECT bid_id FROM bids " + "WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now " ) - q = session.execute( - text(query_str), + q = cursor.execute( + query_str, { "accepted_state": int(BidStates.BID_ACCEPTED), "now": now, @@ -6887,74 +6740,77 @@ class BasicSwap(BaseApp): for row in q: bid_id = row[0] self.log.info("Timing out bid {}.".format(bid_id.hex())) - self.timeoutBid(bid_id, session) + self.timeoutBid(bid_id, cursor) finally: - self.closeSession(session) + self.closeDB(cursor) - def countQueuedActions(self, session, bid_id: bytes, action_type) -> int: - q = session.query(Action).filter( - sa.and_(Action.active_ind == 1, Action.linked_id == bid_id) - ) + def countQueuedActions(self, cursor, bid_id: bytes, action_type) -> int: + query = "SELECT COUNT(*) FROM actions WHERE active_ind = 1 AND linked_id = :linked_id " if action_type is not None: - q.filter(Action.action_type == int(action_type)) - return q.count() + query += "AND action_type = :action_type" + + q = cursor.execute( + query, {"linked_id": bid_id, "action_type": action_type} + ).fetchone() + return q[0] def checkQueuedActions(self) -> None: now: int = self.getTime() reload_in_progress: bool = False try: - session = self.openSession() + cursor = self.openDB() + + query = "SELECT action_type, linked_id FROM actions WHERE active_ind = 1 AND trigger_at <= :now" + q = cursor.execute(query, {"now": now}) - q = session.query(Action).filter( - sa.and_(Action.active_ind == 1, Action.trigger_at <= now) - ) for row in q: + action_type, linked_id = row accepting_bid: bool = False try: - if row.action_type == ActionTypes.ACCEPT_BID: + if action_type == ActionTypes.ACCEPT_BID: accepting_bid = True - self.acceptBid(row.linked_id, session) - elif row.action_type == ActionTypes.ACCEPT_XMR_BID: + self.acceptBid(linked_id, cursor) + elif action_type == ActionTypes.ACCEPT_XMR_BID: accepting_bid = True - self.acceptXmrBid(row.linked_id, session) - elif row.action_type == ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A: - self.sendXmrBidTxnSigsFtoL(row.linked_id, session) - elif row.action_type == ActionTypes.SEND_XMR_SWAP_LOCK_TX_A: - self.sendXmrBidCoinALockTx(row.linked_id, session) - elif row.action_type == ActionTypes.SEND_XMR_SWAP_LOCK_TX_B: - self.sendXmrBidCoinBLockTx(row.linked_id, session) - elif row.action_type == ActionTypes.SEND_XMR_LOCK_RELEASE: - self.sendXmrBidLockRelease(row.linked_id, session) - elif row.action_type == ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_A: - self.redeemXmrBidCoinALockTx(row.linked_id, session) - elif row.action_type == ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B: - self.redeemXmrBidCoinBLockTx(row.linked_id, session) - elif row.action_type == ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B: - self.recoverXmrBidCoinBLockTx(row.linked_id, session) - elif row.action_type == ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG: - self.sendXmrBidCoinALockSpendTxMsg(row.linked_id, session) - elif row.action_type == ActionTypes.REDEEM_ITX: - atomic_swap_1.redeemITx(self, row.linked_id, session) - elif row.action_type == ActionTypes.ACCEPT_AS_REV_BID: + self.acceptXmrBid(linked_id, cursor) + elif action_type == ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A: + self.sendXmrBidTxnSigsFtoL(linked_id, cursor) + elif action_type == ActionTypes.SEND_XMR_SWAP_LOCK_TX_A: + self.sendXmrBidCoinALockTx(linked_id, cursor) + elif action_type == ActionTypes.SEND_XMR_SWAP_LOCK_TX_B: + self.sendXmrBidCoinBLockTx(linked_id, cursor) + elif action_type == ActionTypes.SEND_XMR_LOCK_RELEASE: + self.sendXmrBidLockRelease(linked_id, cursor) + elif action_type == ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_A: + self.redeemXmrBidCoinALockTx(linked_id, cursor) + elif action_type == ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B: + self.redeemXmrBidCoinBLockTx(linked_id, cursor) + elif action_type == ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B: + self.recoverXmrBidCoinBLockTx(linked_id, cursor) + elif action_type == ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG: + self.sendXmrBidCoinALockSpendTxMsg(linked_id, cursor) + elif action_type == ActionTypes.REDEEM_ITX: + atomic_swap_1.redeemITx(self, linked_id, cursor) + elif action_type == ActionTypes.ACCEPT_AS_REV_BID: accepting_bid = True - self.acceptADSReverseBid(row.linked_id, session) + self.acceptADSReverseBid(linked_id, cursor) else: - self.log.warning("Unknown event type: %d", row.event_type) + self.log.warning("Unknown event type: %d", action_type) except Exception as ex: err_msg = f"checkQueuedActions failed: {ex}" self.logException(err_msg) - bid_id = row.linked_id + bid_id = linked_id # Failing to accept a bid should not set an error state as the bid has not begun yet if accepting_bid: self.logEvent( - Concepts.BID, bid_id, EventLogTypes.ERROR, err_msg, session + Concepts.BID, bid_id, EventLogTypes.ERROR, err_msg, cursor ) # If delaying with no (further) queued actions reset state - if self.countQueuedActions(session, bid_id, None) < 2: - bid, offer = self.getBidAndOffer(bid_id, session) + if self.countQueuedActions(cursor, bid_id, None) < 2: + bid, offer = self.getBidAndOffer(bid_id, cursor) last_state = getLastBidState(bid.states) if ( bid @@ -6967,23 +6823,23 @@ class BasicSwap(BaseApp): else BidStates.BID_RECEIVED ) bid.setState(new_state) - self.saveBidInSession(bid_id, bid, session) + self.saveBidInSession(bid_id, bid, cursor) else: - bid = self.getBid(bid_id, session) + bid = self.getBid(bid_id, cursor) if bid: bid.setState(BidStates.BID_ERROR, err_msg) - self.saveBidInSession(bid_id, bid, session) + self.saveBidInSession(bid_id, bid, cursor) query: str = "DELETE FROM actions WHERE trigger_at <= :now" if self.debug: query = "UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now" - session.execute(text(query), {"now": now}) + cursor.execute(query, {"now": now}) except Exception as ex: - self.handleSessionErrors(ex, session, "checkQueuedActions") + self.handleSessionErrors(ex, cursor, "checkQueuedActions") reload_in_progress = True finally: - self.closeSession(session) + self.closeDB(cursor) if reload_in_progress: self.loadFromDB() @@ -6992,20 +6848,17 @@ class BasicSwap(BaseApp): now: int = self.getTime() ttl_xmr_split_messages = 60 * 60 try: - session = self.openSession() - q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING) + cursor = self.openDB() + q = self.query(Bid, cursor, {"state": int(BidStates.BID_RECEIVING)}) for bid in q: - q = session.execute( - text( - "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {}".format( - bid.bid_id.hex(), XmrSplitMsgTypes.BID - ) - ) - ).first() + q = cursor.execute( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = :bid_id AND msg_type = :msg_type", + {"bid_id": bid.bid_id, "msg_type": int(XmrSplitMsgTypes.BID)}, + ).fetchone() num_segments = q[0] if num_segments > 1: try: - self.receiveXmrBid(bid, session) + self.receiveXmrBid(bid, cursor) except Exception as ex: self.log.info( "Verify adaptor-sig bid {} failed: {}".format( @@ -7017,7 +6870,13 @@ class BasicSwap(BaseApp): bid.setState( BidStates.BID_ERROR, "Failed validation: " + str(ex) ) - session.add(bid) + self.updateDB( + bid, + cursor, + [ + "bid_id", + ], + ) self.updateBidInProgress(bid) continue if bid.created_at + ttl_xmr_split_messages < now: @@ -7025,21 +6884,27 @@ class BasicSwap(BaseApp): "Expiring partially received bid: {}".format(bid.bid_id.hex()) ) bid.setState(BidStates.BID_ERROR, "Timed out") - session.add(bid) - - q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING_ACC) - for bid in q: - q = session.execute( - text( - "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {}".format( - bid.bid_id.hex(), XmrSplitMsgTypes.BID_ACCEPT - ) + self.updateDB( + bid, + cursor, + [ + "bid_id", + ], ) - ).first() + + q = self.query(Bid, cursor, {"state": int(BidStates.BID_RECEIVING_ACC)}) + for bid in q: + q = cursor.execute( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = :bid_id AND msg_type = :msg_type", + { + "bid_id": bid.bid_id, + "msg_type": int(XmrSplitMsgTypes.BID_ACCEPT), + }, + ).fetchone() num_segments = q[0] if num_segments > 1: try: - self.receiveXmrBidAccept(bid, session) + self.receiveXmrBidAccept(bid, cursor) except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) @@ -7051,7 +6916,13 @@ class BasicSwap(BaseApp): bid.setState( BidStates.BID_ERROR, "Failed accept validation: " + str(ex) ) - session.add(bid) + self.updateDB( + bid, + cursor, + [ + "bid_id", + ], + ) self.updateBidInProgress(bid) continue if bid.created_at + ttl_xmr_split_messages < now: @@ -7061,16 +6932,21 @@ class BasicSwap(BaseApp): ) ) bid.setState(BidStates.BID_ERROR, "Timed out") - session.add(bid) + self.updateDB( + bid, + cursor, + [ + "bid_id", + ], + ) # Expire old records - q = session.query(XmrSplitData).filter( - XmrSplitData.created_at + ttl_xmr_split_messages < now + cursor.execute( + "DELETE FROM xmr_split_data WHERE created_at + :ttl < :now", + {"ttl": ttl_xmr_split_messages, "now": now}, ) - q.delete(synchronize_session=False) - finally: - self.closeSession(session) + self.closeDB(cursor) def processOffer(self, msg) -> None: offer_bytes = bytes.fromhex(msg["hex"][2:-2]) @@ -7170,19 +7046,20 @@ class BasicSwap(BaseApp): raise ValueError("Offer has been revoked {}.".format(offer_id.hex())) try: - session = self.openSession() + cursor = self.openDB() # Offers must be received on the public network_addr or manually created addresses if msg["to"] != self.network_addr: # Double check active_ind, shouldn't be possible to receive message if not active - query_str = 'SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = "{}" AND use_type = {} AND active_ind = 1'.format( - msg["to"], AddressTypes.RECV_OFFER - ) - rv = session.execute(text(query_str)).first() + query_str = "SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = :addr AND use_type = :use_type AND active_ind = 1" + rv = cursor.execute( + query_str, + {"addr": msg["to"], "use_type": int(AddressTypes.RECV_OFFER)}, + )[0] if rv[0] < 1: raise ValueError("Offer received on incorrect address") # Check for sent - existing_offer = self.getOffer(offer_id, session=session) + existing_offer = self.getOffer(offer_id, cursor=cursor) if existing_offer is None: bid_reversed: bool = ( offer_data.swap_type == SwapTypes.XMR_SWAP @@ -7214,7 +7091,7 @@ class BasicSwap(BaseApp): bid_reversed=bid_reversed, ) offer.setState(OfferStates.OFFER_RECEIVED) - session.add(offer) + self.add(offer, cursor) if offer.swap_type == SwapTypes.XMR_SWAP: xmr_offer = XmrOffer() @@ -7235,14 +7112,14 @@ class BasicSwap(BaseApp): xmr_offer.a_fee_rate = offer_data.fee_rate_from xmr_offer.b_fee_rate = offer_data.fee_rate_to - session.add(xmr_offer) + self.add(xmr_offer, cursor) - self.notify(NT.OFFER_RECEIVED, {"offer_id": offer_id.hex()}, session) + self.notify(NT.OFFER_RECEIVED, {"offer_id": offer_id.hex()}, cursor) else: existing_offer.setState(OfferStates.OFFER_RECEIVED) - session.add(existing_offer) + self.add(existing_offer, cursor, upsert=True) finally: - self.closeSession(session) + self.closeDB(cursor) def processOfferRevoke(self, msg) -> None: ensure(msg["to"] == self.network_addr, "Message received on wrong address") @@ -7253,16 +7130,14 @@ class BasicSwap(BaseApp): now: int = self.getTime() try: - session = self.openSession() + cursor = self.openDB() if len(msg_data.offer_msg_id) != 28: raise ValueError("Invalid msg_id length") if len(msg_data.signature) != 65: raise ValueError("Invalid signature length") - offer = ( - session.query(Offer).filter_by(offer_id=msg_data.offer_msg_id).first() - ) + offer = self.getOffer(msg_data.offer_msg_id, cursor=cursor) if offer is None: self.storeOfferRevoke(msg_data.offer_msg_id, msg_data.signature) @@ -7296,16 +7171,21 @@ class BasicSwap(BaseApp): offer.active_ind = 2 # TODO: Remove message, or wait for expire - session.add(offer) + self.updateDB( + offer, + cursor, + [ + "offer_id", + ], + ) finally: - self.closeSession(session) + self.closeDB(cursor) - def getCompletedAndActiveBidsValue(self, offer, session): + def getCompletedAndActiveBidsValue(self, offer, cursor): bids = [] total_value = 0 - q = session.execute( - text( - """SELECT bid_id, amount, state FROM bids + q = cursor.execute( + """SELECT bid_id, amount, state FROM bids JOIN bidstates ON bidstates.state_id = bids.state AND (bidstates.state_id = {1} OR bidstates.in_progress > 0) WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' UNION @@ -7313,11 +7193,10 @@ class BasicSwap(BaseApp): JOIN actions ON actions.linked_id = bids.bid_id AND actions.active_ind = 1 AND (actions.action_type = {2} OR actions.action_type = {3}) WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' """.format( - offer.offer_id.hex(), - BidStates.SWAP_COMPLETED, - ActionTypes.ACCEPT_XMR_BID, - ActionTypes.ACCEPT_BID, - ) + offer.offer_id.hex(), + BidStates.SWAP_COMPLETED, + ActionTypes.ACCEPT_XMR_BID, + ActionTypes.ACCEPT_BID, ) ) for row in q: @@ -7353,24 +7232,30 @@ class BasicSwap(BaseApp): raise AutomationConstraint("Bidder has too many failed swaps") return True - def shouldAutoAcceptBid(self, offer, bid, session=None, options={}) -> bool: + def shouldAutoAcceptBid(self, offer, bid, cursor=None, options={}) -> bool: try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) - link = ( - use_session.query(AutomationLink) - .filter_by( - active_ind=1, linked_type=Concepts.OFFER, linked_id=offer.offer_id + link = firstOrNone( + self.query( + AutomationLink, + use_cursor, + { + "active_ind": 1, + "linked_type": int(Concepts.OFFER), + "linked_id": offer.offer_id, + }, ) - .first() ) - if not link: + if link is None: return False - strategy = ( - use_session.query(AutomationStrategy) - .filter_by(active_ind=1, record_id=link.strategy_id) - .first() + strategy = firstOrNone( + self.query( + AutomationStrategy, + use_cursor, + {"active_ind": 1, "record_id": link.strategy_id}, + ) ) opts = json.loads(strategy.data.decode("utf-8")) @@ -7395,7 +7280,7 @@ class BasicSwap(BaseApp): raise AutomationConstraint("Need exact rate match") active_bids, total_bids_value = self.getCompletedAndActiveBidsValue( - offer, use_session + offer, use_cursor ) total_bids_value_multiplier = opts.get("total_bids_value_multiplier", 1.0) @@ -7421,8 +7306,8 @@ class BasicSwap(BaseApp): "Already have {} bids to complete".format(num_not_completed) ) - identity_stats = ( - use_session.query(KnownIdentity).filter_by(address=bid.bid_addr).first() + identity_stats = firstOrNone( + self.query(KnownIdentity, use_cursor, {"address": bid.bid_addr}) ) self.evaluateKnownIdentityForAutoAccept(strategy, identity_stats) @@ -7431,7 +7316,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.AUTOMATION_ACCEPTING_BID, "", - use_session, + use_cursor, ) return True @@ -7445,15 +7330,15 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.AUTOMATION_CONSTRAINT, str(e), - use_session, + use_cursor, ) return False except Exception as e: self.logException(f"shouldAutoAcceptBid {e}") return False finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def processBid(self, msg) -> None: self.log.debug("Processing bid msg %s", msg["msgid"]) @@ -7470,7 +7355,7 @@ class BasicSwap(BaseApp): ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id - offer = self.getOffer(offer_id, sent=True) + offer = self.getOffer(offer_id) ensure(offer and offer.was_sent, "Unknown offer") ensure(offer.state == OfferStates.OFFER_RECEIVED, "Bad offer state") @@ -7576,7 +7461,7 @@ class BasicSwap(BaseApp): self.log.debug("for bid %s", bid_accept_data.bid_msg_id.hex()) - bid_id = bid_accept_data.bid_msg_id + bid_id: bytes = bid_accept_data.bid_msg_id bid, offer = self.getBidAndOffer(bid_id) ensure(bid is not None and bid.was_sent is True, "Unknown bid_id") ensure(offer, "Offer not found " + bid.offer_id.hex()) @@ -7689,14 +7574,14 @@ class BasicSwap(BaseApp): self.swaps_in_progress[bid_id] = (bid, offer) self.notify(NT.BID_ACCEPTED, {"bid_id": bid_id.hex()}) - def receiveXmrBid(self, bid, session) -> None: + def receiveXmrBid(self, bid, cursor) -> None: self.log.debug("Receiving adaptor-sig bid %s", bid.bid_id.hex()) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=True) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + xmr_swap = firstOrNone(self.query(XmrSwap, cursor, {"bid_id": bid.bid_id})) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) @@ -7715,15 +7600,11 @@ class BasicSwap(BaseApp): if ci_to.curve_type() == Curves.ed25519: if len(xmr_swap.kbsf_dleag) < ci_to.lengthDLEAG(): - q = ( - session.query(XmrSplitData) - .filter( - sa.and_( - XmrSplitData.bid_id == bid.bid_id, - XmrSplitData.msg_type == XmrSplitMsgTypes.BID, - ) - ) - .order_by(XmrSplitData.msg_sequence.asc()) + q = self.query( + XmrSplitData, + cursor, + {"bid_id": bid.bid_id, "msg_type": int(XmrSplitMsgTypes.BID)}, + {"msg_sequence": "asc"}, ) for row in q: ensure( @@ -7771,12 +7652,12 @@ class BasicSwap(BaseApp): "bid_id": bid.bid_id.hex(), "offer_id": bid.offer_id.hex(), }, - session, + cursor, ) bid.setState(BidStates.BID_RECEIVED) - if reverse_bid or self.shouldAutoAcceptBid(offer, bid, session): + if reverse_bid or self.shouldAutoAcceptBid(offer, bid, cursor): delay = self.get_delay_event_seconds() self.log.info( "Auto accepting %sadaptor-sig bid %s in %d seconds", @@ -7785,20 +7666,20 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.ACCEPT_XMR_BID, bid.bid_id, session + delay, ActionTypes.ACCEPT_XMR_BID, bid.bid_id, cursor ) bid.setState(BidStates.SWAP_DELAYING) - self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid.bid_id, bid, cursor, xmr_swap) - def receiveXmrBidAccept(self, bid, session) -> None: + def receiveXmrBidAccept(self, bid, cursor) -> None: # Follower receiving MSG1F and MSG2F self.log.debug("Receiving adaptor-sig bid accept %s", bid.bid_id.hex()) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True, session=session) + offer, xmr_offer = self.getXmrOffer(bid.offer_id, cursor=cursor) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + xmr_swap = firstOrNone(self.query(XmrSwap, cursor, {"bid_id": bid.bid_id})) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) @@ -7809,15 +7690,14 @@ class BasicSwap(BaseApp): if ci_to.curve_type() == Curves.ed25519: if len(xmr_swap.kbsl_dleag) < ci_to.lengthDLEAG(): - q = ( - session.query(XmrSplitData) - .filter( - sa.and_( - XmrSplitData.bid_id == bid.bid_id, - XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT, - ) - ) - .order_by(XmrSplitData.msg_sequence.asc()) + q = self.query( + XmrSplitData, + cursor, + { + "bid_id": bid.bid_id, + "msg_type": int(XmrSplitMsgTypes.BID_ACCEPT), + }, + order_by={"msg_sequence": "asc"}, ) for row in q: ensure( @@ -7868,10 +7748,10 @@ class BasicSwap(BaseApp): raise ValueError("Duplicate script spend pubkey.") bid.setState(BidStates.BID_ACCEPTED) # ADS - self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid.bid_id, bid, cursor, xmr_swap) if reverse_bid is False: - self.notify(NT.BID_ACCEPTED, {"bid_id": bid.bid_id.hex()}, session) + self.notify(NT.BID_ACCEPTED, {"bid_id": bid.bid_id.hex()}, cursor) delay = self.get_delay_event_seconds() self.log.info( @@ -7880,7 +7760,7 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session + delay, ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, cursor ) def processXmrBid(self, msg) -> None: @@ -7899,7 +7779,7 @@ class BasicSwap(BaseApp): ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id - offer, xmr_offer = self.getXmrOffer(offer_id, sent=True) + offer, xmr_offer = self.getXmrOffer(offer_id) ensure(offer and offer.was_sent, "Offer not found: {}.".format(offer_id.hex())) ensure(offer.swap_type == SwapTypes.XMR_SWAP, "Bid/offer swap type mismatch") ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) @@ -7988,10 +7868,10 @@ class BasicSwap(BaseApp): if ci_to.curve_type() != Curves.ed25519: try: - session = self.openSession() - self.receiveXmrBid(bid, session) + cursor = self.openDB() + self.receiveXmrBid(bid, cursor) finally: - self.closeSession(session) + self.closeDB(cursor) def processXmrBidAccept(self, msg) -> None: # F receiving MSG1F and MSG2F @@ -8011,7 +7891,7 @@ class BasicSwap(BaseApp): "Adaptor-sig swap not found: {}.".format(msg_data.bid_msg_id.hex()), ) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True) + offer, xmr_offer = self.getXmrOffer(bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8168,22 +8048,22 @@ class BasicSwap(BaseApp): if ci_to.curve_type() != Curves.ed25519: try: - session = self.openSession() - self.receiveXmrBidAccept(bid, session) + cursor = self.openDB() + self.receiveXmrBidAccept(bid, cursor) finally: - self.closeSession(session) + self.closeDB(cursor) except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) self.setBidError(bid.bid_id, bid, str(ex), xmr_swap=xmr_swap) - def watchXmrSwap(self, bid, offer, xmr_swap, session=None) -> None: + def watchXmrSwap(self, bid, offer, xmr_swap, cursor=None) -> None: self.log.debug("Adaptor-sig swap in progress, bid %s", bid.bid_id.hex()) self.swaps_in_progress[bid.bid_id] = (bid, offer) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) - self.setLastHeightCheckedStart(coin_from, bid.chain_a_height_start, session) + self.setLastHeightCheckedStart(coin_from, bid.chain_a_height_start, cursor) if bid.xmr_a_lock_tx.txid: self.addWatchedOutput( @@ -8216,17 +8096,15 @@ class BasicSwap(BaseApp): coin_from, bid.bid_id, find_script, TxTypes.XMR_SWAP_A_LOCK ) - def sendXmrBidTxnSigsFtoL(self, bid_id, session) -> None: + def sendXmrBidTxnSigsFtoL(self, bid_id, cursor) -> None: # F -> L: Sending MSG3L self.log.debug("Signing adaptor-sig bid lock txns %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8253,7 +8131,6 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_refund_tx_script, prevout_amount, ) - prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) xmr_swap.af_lock_refund_tx_sig = ci_from.signTx( kaf, @@ -8287,7 +8164,7 @@ class BasicSwap(BaseApp): bid_id, MessageTypes.XMR_BID_TXN_SIGS_FL, coin_a_lock_tx_sigs_l_msg_id, - session=session, + cursor=cursor, ) self.log.info( "Sent XMR_BID_TXN_SIGS_FL %s for bid %s", @@ -8330,23 +8207,21 @@ class BasicSwap(BaseApp): bid.xmr_a_lock_tx.setState(TxStates.TX_NONE) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS) - self.watchXmrSwap(bid, offer, xmr_swap, session) - self.saveBidInSession(bid_id, bid, session, xmr_swap) + self.watchXmrSwap(bid, offer, xmr_swap, cursor) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) except Exception as e: # noqa: F841 if self.debug: self.log.error(traceback.format_exc()) - def sendXmrBidCoinALockTx(self, bid_id: bytes, session) -> None: + def sendXmrBidCoinALockTx(self, bid_id: bytes, cursor) -> None: # Offerer/Leader. Send coin A lock tx self.log.debug("Sending coin A lock tx for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8406,7 +8281,15 @@ class BasicSwap(BaseApp): if lock_tx_sent is False: lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx) + if not self.isBchXmrSwap(offer): + # Double check txid hashn't changed, can happen if the prevouts are not segwit + if ci_from.getTxid(lock_tx_signed) != xmr_swap.a_lock_tx_id: + self.log.debug(f"Before tx {xmr_swap.a_lock_tx.hex()}") + self.log.debug(f"After tx {lock_tx_signed.hex()}") + raise ValueError("Coin A lock tx txid changed after signing!") + txid_hex = ci_from.publishTx(lock_tx_signed) + if txid_hex != b2h(xmr_swap.a_lock_tx_id): self.log.info( "Recomputing refund transactions and txids after lock tx publish" @@ -8459,10 +8342,10 @@ class BasicSwap(BaseApp): vout=vout_pos, ) bid.xmr_a_lock_tx.setState(TxStates.TX_SENT) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, "", session) + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, "", cursor) bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) - self.watchXmrSwap(bid, offer, xmr_swap, session) + self.watchXmrSwap(bid, offer, xmr_swap, cursor) delay = self.get_short_delay_event_seconds() self.log.info( @@ -8471,22 +8354,20 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session + delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, cursor ) - self.saveBidInSession(bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap) - def sendXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: + def sendXmrBidCoinBLockTx(self, bid_id: bytes, cursor) -> None: # Follower sending coin B lock tx self.log.debug("Sending coin B lock tx for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8495,10 +8376,8 @@ class BasicSwap(BaseApp): b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate was_sent: bool = bid.was_received if reverse_bid else bid.was_sent - if self.findTxB(ci_to, xmr_swap, bid, session, was_sent) is True: - self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer - ) + if self.findTxB(ci_to, xmr_swap, bid, cursor, was_sent) is True: + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) return if bid.xmr_b_lock_tx: @@ -8520,11 +8399,9 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, - ) - self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer + cursor, ) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) return unlock_time = 0 @@ -8543,7 +8420,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) if bid.debug_ind == DebugTypes.SEND_LOCKED_XMR: unlock_time = 10000 @@ -8556,7 +8433,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) try: @@ -8578,7 +8455,7 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) raise TemporaryError("Fail for debug event") except Exception as ex: @@ -8588,7 +8465,7 @@ class BasicSwap(BaseApp): bid_id.hex(), str(ex) ) num_retries = self.countBidEvents( - bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session + bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, cursor ) if num_retries > 0: error_msg += ", retry no. {}".format(num_retries) @@ -8604,18 +8481,18 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session + delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, cursor ) else: self.setBidError( bid_id, bid, "publishBLockTx failed: " + str(ex), save_bid=False ) self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer + bid_id, bid, cursor, xmr_swap, save_in_progress=offer ) self.logBidEvent( - bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str(ex), session + bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str(ex), cursor ) return @@ -8632,7 +8509,7 @@ class BasicSwap(BaseApp): ) xmr_swap.b_lock_tx_id = b_lock_tx_id bid.xmr_b_lock_tx.setState(TxStates.TX_SENT) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, "", session) + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, "", cursor) if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_B_LOCK: self.log.debug( "Adaptor-sig bid %s: Stalling bid for testing: %d.", @@ -8644,21 +8521,19 @@ class BasicSwap(BaseApp): bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, "ind {}".format(bid.debug_ind), - session, + cursor, ) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) - def sendXmrBidLockRelease(self, bid_id: bytes, session) -> None: + def sendXmrBidLockRelease(self, bid_id: bytes, cursor) -> None: # Leader sending lock tx a release secret (MSG5F) self.log.debug("Sending bid secret for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8684,23 +8559,21 @@ class BasicSwap(BaseApp): bid_id, MessageTypes.XMR_BID_LOCK_RELEASE_LF, coin_a_lock_release_msg_id, - session=session, + cursor=cursor, ) bid.setState(BidStates.XMR_SWAP_LOCK_RELEASED) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) - def redeemXmrBidCoinALockTx(self, bid_id: bytes, session) -> None: + def redeemXmrBidCoinALockTx(self, bid_id: bytes, cursor) -> None: # Follower redeeming A lock tx self.log.debug("Redeeming coin A lock tx for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8802,7 +8675,7 @@ class BasicSwap(BaseApp): bid_id.hex(), ) self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED, "", session + bid.bid_id, EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED, "", cursor ) if bid.xmr_a_lock_spend_tx is None: bid.xmr_a_lock_spend_tx = SwapTx( @@ -8818,19 +8691,17 @@ class BasicSwap(BaseApp): bid_id.hex(), ) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) - def redeemXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: + def redeemXmrBidCoinBLockTx(self, bid_id: bytes, cursor) -> None: # Leader redeeming B lock tx self.log.debug("Redeeming coin B lock tx for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -8889,12 +8760,12 @@ class BasicSwap(BaseApp): vkbs = ci_to.sumKeys(kbsl, kbsf) if coin_to == (Coins.XMR, Coins.WOW): - address_to = self.getCachedMainWalletAddress(ci_to, session) + address_to = self.getCachedMainWalletAddress(ci_to, cursor) elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): - address_to = self.getCachedStealthAddressForCoin(coin_to, session) + address_to = self.getCachedStealthAddressForCoin(coin_to, cursor) else: address_to = self.getReceiveAddressFromPool( - coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, session + coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, cursor ) lock_tx_vout = bid.getLockTXBVout() @@ -8915,14 +8786,14 @@ class BasicSwap(BaseApp): bid_id.hex(), ) self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, "", session + bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, "", cursor ) except Exception as ex: error_msg = "spendBLockTx failed for bid {} with error {}".format( bid_id.hex(), str(ex) ) num_retries = self.countBidEvents( - bid, EventLogTypes.FAILED_TX_B_SPEND, session + bid, EventLogTypes.FAILED_TX_B_SPEND, cursor ) if num_retries > 0: error_msg += ", retry no. {}".format(num_retries) @@ -8938,18 +8809,18 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session + delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, cursor ) else: self.setBidError( bid_id, bid, "spendBLockTx failed: " + str(ex), save_bid=False ) self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer + bid_id, bid, cursor, xmr_swap, save_in_progress=offer ) self.logBidEvent( - bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str(ex), session + bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str(ex), cursor ) return @@ -8959,19 +8830,17 @@ class BasicSwap(BaseApp): bid.xmr_b_lock_tx.setState(TxStates.TX_REDEEMED) # TODO: Why does using bid.txns error here? - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) - def recoverXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: + def recoverXmrBidCoinBLockTx(self, bid_id: bytes, cursor) -> None: # Follower recovering B lock tx self.log.debug("Recovering coin B lock tx for adaptor-sig bid %s", bid_id.hex()) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9006,12 +8875,12 @@ class BasicSwap(BaseApp): try: if offer.coin_to in (Coins.XMR, Coins.WOW): - address_to = self.getCachedMainWalletAddress(ci_to, session) + address_to = self.getCachedMainWalletAddress(ci_to, cursor) elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): - address_to = self.getCachedStealthAddressForCoin(coin_to, session) + address_to = self.getCachedStealthAddressForCoin(coin_to, cursor) else: address_to = self.getReceiveAddressFromPool( - coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND, session + coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND, cursor ) lock_tx_vout = bid.getLockTXBVout() @@ -9032,7 +8901,7 @@ class BasicSwap(BaseApp): bid_id.hex(), ) self.logBidEvent( - bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, "", session + bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, "", cursor ) except Exception as ex: # TODO: Make min-conf 10? @@ -9040,7 +8909,7 @@ class BasicSwap(BaseApp): bid_id.hex(), str(ex) ) num_retries = self.countBidEvents( - bid, EventLogTypes.FAILED_TX_B_REFUND, session + bid, EventLogTypes.FAILED_TX_B_REFUND, cursor ) if num_retries > 0: error_msg += ", retry no. {}".format(num_retries) @@ -9057,7 +8926,7 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session + delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, cursor ) else: self.setBidError( @@ -9067,11 +8936,11 @@ class BasicSwap(BaseApp): save_bid=False, ) self.saveBidInSession( - bid_id, bid, session, xmr_swap, save_in_progress=offer + bid_id, bid, cursor, xmr_swap, save_in_progress=offer ) self.logBidEvent( - bid.bid_id, EventLogTypes.FAILED_TX_B_REFUND, str_error, session + bid.bid_id, EventLogTypes.FAILED_TX_B_REFUND, str_error, cursor ) return @@ -9080,21 +8949,19 @@ class BasicSwap(BaseApp): bid.setState(BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED) if bid.xmr_b_lock_tx: bid.xmr_b_lock_tx.setState(TxStates.TX_REFUNDED) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) - def sendXmrBidCoinALockSpendTxMsg(self, bid_id: bytes, session) -> None: + def sendXmrBidCoinALockSpendTxMsg(self, bid_id: bytes, cursor) -> None: # Send MSG4F L -> F self.log.debug( "Sending coin A lock spend tx msg for adaptor-sig bid %s", bid_id.hex() ) - bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) + bid, xmr_swap = self.getXmrBidFromSession(cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9120,7 +8987,7 @@ class BasicSwap(BaseApp): ) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer) def processXmrBidCoinALockSigs(self, msg) -> None: # Leader processing MSG3L @@ -9137,7 +9004,7 @@ class BasicSwap(BaseApp): ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + offer, xmr_offer = self.getXmrOffer(bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9227,7 +9094,8 @@ class BasicSwap(BaseApp): ensure(v, "Invalid signature for lock refund spend txn") xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from) else: - # bch signs the output pkh + # BCH signs the output pkh + tx = ci_from.loadTx(xmr_swap.a_lock_refund_spend_tx) out1 = tx.vout[0].scriptPubKey out1_sig = ci_from.decryptOtVES( @@ -9275,7 +9143,7 @@ class BasicSwap(BaseApp): ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + offer, xmr_offer = self.getXmrOffer(bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9343,15 +9211,13 @@ class BasicSwap(BaseApp): msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT ): - session = self.openSession() + cursor = self.openDB() try: - q = session.execute( - text( - "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {} AND msg_sequence = {}".format( - msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence - ) + q = cursor.execute( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {} AND msg_sequence = {}".format( + msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence ) - ).first() + ).fetchone() num_exists = q[0] if num_exists > 0: self.log.warning( @@ -9369,9 +9235,9 @@ class BasicSwap(BaseApp): dbr.msg_sequence = msg_data.sequence dbr.dleag = msg_data.dleag dbr.created_at = now - session.add(dbr) + self.add(dbr, cursor, upsert=True) finally: - self.closeSession(session) + self.closeDB(cursor) def processXmrLockReleaseMessage(self, msg) -> None: self.log.debug("Processing adaptor-sig swap lock release msg %s", msg["msgid"]) @@ -9392,7 +9258,7 @@ class BasicSwap(BaseApp): self.log.debug("Bid stalled %s", bid_id.hex()) return - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + offer, xmr_offer = self.getXmrOffer(bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9460,7 +9326,7 @@ class BasicSwap(BaseApp): ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id - offer, xmr_offer = self.getXmrOffer(offer_id, sent=True) + offer, xmr_offer = self.getXmrOffer(offer_id) ensure(offer and offer.was_sent, "Offer not found: {}.".format(offer_id.hex())) ensure(offer.swap_type == SwapTypes.XMR_SWAP, "Bid/offer swap type mismatch") ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) @@ -9535,7 +9401,7 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid, xmr_swap=xmr_swap) try: - session = self.openSession() + cursor = self.openDB() self.notify( NT.BID_RECEIVED, { @@ -9543,11 +9409,11 @@ class BasicSwap(BaseApp): "bid_id": bid.bid_id.hex(), "offer_id": bid.offer_id.hex(), }, - session, + cursor, ) options = {"reverse_bid": True, "bid_rate": bid_rate} - if self.shouldAutoAcceptBid(offer, bid, session, options=options): + if self.shouldAutoAcceptBid(offer, bid, cursor, options=options): delay = self.get_delay_event_seconds() self.log.info( "Auto accepting reverse adaptor-sig bid %s in %d seconds", @@ -9555,11 +9421,11 @@ class BasicSwap(BaseApp): delay, ) self.createActionInSession( - delay, ActionTypes.ACCEPT_AS_REV_BID, bid.bid_id, session + delay, ActionTypes.ACCEPT_AS_REV_BID, bid.bid_id, cursor ) bid.setState(BidStates.SWAP_DELAYING) finally: - self.closeSession(session) + self.closeDB(cursor) def processADSBidReversedAccept(self, msg) -> None: self.log.debug("Processing adaptor-sig reverse bid accept msg %s", msg["msgid"]) @@ -9573,7 +9439,7 @@ class BasicSwap(BaseApp): ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + offer, xmr_offer = self.getXmrOffer(bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -9623,12 +9489,12 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid, xmr_swap=xmr_swap) try: - session = self.openSession() - self.notify(NT.BID_ACCEPTED, {"bid_id": bid_id.hex()}, session) + cursor = self.openDB() + self.notify(NT.BID_ACCEPTED, {"bid_id": bid_id.hex()}, cursor) if ci_to.curve_type() != Curves.ed25519: - self.receiveXmrBid(bid, session) + self.receiveXmrBid(bid, cursor) finally: - self.closeSession(session) + self.closeDB(cursor) def processMsg(self, msg) -> None: try: @@ -9732,15 +9598,15 @@ class BasicSwap(BaseApp): bids_expired: int = 0 offers_expired: int = 0 try: - session = self.openSession() + cursor = self.openDB() 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 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 """ - q = session.execute( - text(query), + q = cursor.execute( + query, { "bid_received": int(BidStates.BID_RECEIVED), "offer_received": int(OfferStates.OFFER_RECEIVED), @@ -9764,10 +9630,8 @@ class BasicSwap(BaseApp): offers_to_expire.add(record_id) for bid_id in bids_to_expire: - query = text( - "SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent)" - ) - rows = session.execute( + query = "SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent)" + rows = cursor.execute( query, { "bid_id": bid_id, @@ -9781,15 +9645,15 @@ class BasicSwap(BaseApp): bytes() if rows[0][1] is None else rows[0][1] ) + pack_state(new_state, now) query = "UPDATE bids SET state = :new_state, states = :states WHERE bid_id = :bid_id" - session.execute( - text(query), + cursor.execute( + query, {"bid_id": bid_id, "new_state": new_state, "states": states}, ) bids_expired += 1 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)" - rows = session.execute( - text(query), + rows = cursor.execute( + query, { "offer_id": offer_id, "offer_received": int(OfferStates.OFFER_RECEIVED), @@ -9802,8 +9666,8 @@ class BasicSwap(BaseApp): bytes() if rows[0][1] is None else rows[0][1] ) + pack_state(new_state, now) query = "UPDATE offers SET state = :new_state, states = :states WHERE offer_id = :offer_id" - session.execute( - text(query), + cursor.execute( + query, { "offer_id": offer_id, "new_state": new_state, @@ -9812,7 +9676,7 @@ class BasicSwap(BaseApp): ) offers_expired += 1 finally: - self.closeSession(session) + self.closeDB(cursor) if bids_expired + offers_expired > 0: mb = "" if bids_expired == 1 else "s" @@ -9864,7 +9728,7 @@ class BasicSwap(BaseApp): bid_id, EventLogTypes.SYSTEM_WARNING, "No connection to daemon", - session=None, + cursor=None, ) else: self.log.error("checkBidState %s %s", bid_id.hex(), str(ex)) @@ -9908,8 +9772,8 @@ class BasicSwap(BaseApp): add_bid_action = -1 try: - session = self.openSession() - bid, offer = self.getBidAndOffer(bid_id, session) + cursor = self.openDB() + bid, offer = self.getBidAndOffer(bid_id, cursor) ensure(bid, "Bid not found {}".format(bid_id.hex())) ensure(offer, "Offer not found {}".format(bid.offer_id.hex())) @@ -9939,7 +9803,7 @@ class BasicSwap(BaseApp): if data.get("kbs_other", None) is not None: return xmr_swap_1.recoverNoScriptTxnWithKey( - self, bid_id, data["kbs_other"], session + self, bid_id, data["kbs_other"], cursor ) if has_changed: @@ -9949,19 +9813,19 @@ class BasicSwap(BaseApp): if add_bid_action > -1: delay = self.get_delay_event_seconds() - self.createActionInSession(delay, add_bid_action, bid_id, session) + self.createActionInSession(delay, add_bid_action, bid_id, cursor) if activate_bid: - self.activateBid(session, bid) + self.activateBid(cursor, bid) else: - self.deactivateBid(session, offer, bid) + self.deactivateBid(cursor, offer, bid) - self.saveBidInSession(bid_id, bid, session) - session.commit() + self.saveBidInSession(bid_id, bid, cursor) + self.commitDB() else: raise ValueError("No changes") finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def editGeneralSettings(self, data): self.log.info("Updating general settings") @@ -10277,18 +10141,21 @@ class BasicSwap(BaseApp): now, now ) - with self.engine.connect() as conn: - q = conn.execute(text(q_bids_str)).first() + try: + cursor = self.openDB() + q = cursor.execute(q_bids_str).fetchone() bids_sent = q[0] bids_sent_active = q[1] bids_received = q[2] bids_available = q[3] bids_recv_active = q[4] - q = conn.execute(text(q_offers_str)).first() + q = cursor.execute(q_offers_str).fetchone() num_offers = q[0] num_sent_offers = q[1] num_sent_active_offers = q[2] + finally: + self.closeDB(cursor, commit=False) rv = { "network": self.chain, @@ -10374,30 +10241,29 @@ class BasicSwap(BaseApp): return rv except Exception as e: - self.log.warning( - "getWalletInfo for %s failed with: %s", ci.coin_name(), str(e) - ) + self.log.warning(f"getWalletInfo for {ci.coin_name()} failed with: {e}") def addWalletInfoRecord(self, coin, info_type, wi) -> None: coin_id = int(coin) - session = self.openSession() + cursor = self.openDB() try: now: int = self.getTime() - session.add( + self.add( Wallets( coin_id=coin, balance_type=info_type, wallet_data=json.dumps(wi), created_at=now, - ) + ), + cursor, ) query_str = f"DELETE FROM wallets WHERE (coin_id = {coin_id} AND balance_type = {info_type}) AND record_id NOT IN (SELECT record_id FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type} ORDER BY created_at DESC LIMIT 3 )" - session.execute(text(query_str)) - session.commit() + cursor.execute(query_str) + self.commitDB() except Exception as e: self.log.error(f"addWalletInfoRecord {e}") finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def updateWalletInfo(self, coin) -> None: # Store wallet info to db so it's available after startup @@ -10459,7 +10325,7 @@ class BasicSwap(BaseApp): def getCachedWalletsInfo(self, opts=None): rv = {} try: - session = self.openSession() + cursor = self.openDB() where_str = "" if opts is not None and "coin_id" in opts: where_str = "WHERE coin_id = {}".format(opts["coin_id"]) @@ -10468,7 +10334,7 @@ class BasicSwap(BaseApp): inner_str ) - q = session.execute(text(query_str)) + q = cursor.execute(query_str) for row in q: coin_id = row[0] @@ -10484,15 +10350,12 @@ class BasicSwap(BaseApp): ) # Ensure the latest addresses are displayed - q = session.execute( - text( - 'SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format( - chainparams[coin_id]["name"] - ) + q = cursor.execute( + 'SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format( + chainparams[coin_id]["name"] ) ) for row in q: - if row[0].startswith("stealth"): if coin_id == Coins.LTC: wallet_data["mweb_address"] = row[1] @@ -10506,7 +10369,7 @@ class BasicSwap(BaseApp): else: rv[coin_id] = wallet_data finally: - self.closeSession(session) + self.closeDB(cursor) if opts is not None and "coin_id" in opts: return rv @@ -10523,112 +10386,83 @@ class BasicSwap(BaseApp): return rv def countAcceptedBids(self, offer_id: bytes = None) -> int: - session = self.openSession() + cursor = self.openDB() try: if offer_id: - q = session.execute( - text( - "SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x'{}'".format( - BidStates.BID_ACCEPTED, offer_id.hex() - ) + q = cursor.execute( + "SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x'{}'".format( + BidStates.BID_ACCEPTED, offer_id.hex() ) - ).first() + ).fetchone() else: - q = session.execute( - text( - "SELECT COUNT(*) FROM bids WHERE state >= {}".format( - BidStates.BID_ACCEPTED - ) + q = cursor.execute( + "SELECT COUNT(*) FROM bids WHERE state >= {}".format( + BidStates.BID_ACCEPTED ) - ).first() + ).fetchone() return q[0] finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) - def listOffers(self, sent: bool = False, filters={}, with_bid_info: bool = False): - session = self.openSession() + def listOffers(self, sent: bool = False, filters={}): + cursor = self.openDB() try: rv = [] now: int = self.getTime() - if with_bid_info: - subquery = ( - session.query(sa.func.sum(Bid.amount).label("completed_bid_amount")) - .filter( - sa.and_( - Bid.offer_id == Offer.offer_id, - Bid.state == BidStates.SWAP_COMPLETED, - ) - ) - .correlate(Offer) - .scalar_subquery() - ) - q = session.query(Offer, subquery) - else: - q = session.query(Offer) + query_suffix = "" if sent: - q = q.filter(Offer.was_sent == True) # noqa: E712 + query_suffix += " AND was_sent = 1" active_state = filters.get("active", "any") if active_state == "active": - q = q.filter(Offer.expire_at > now, Offer.active_ind == 1) + query_suffix += f" AND (expire_at > {now} AND active_ind = 1)" elif active_state == "expired": - q = q.filter(Offer.expire_at <= now) + query_suffix += f" AND expire_at <= {now}" elif active_state == "revoked": - q = q.filter(Offer.active_ind != 1) + query_suffix += " AND active_ind != 1" else: - q = q.filter(sa.and_(Offer.expire_at > now, Offer.active_ind == 1)) + query_suffix += f" AND (expire_at > {now} AND active_ind = 1)" filter_offer_id = filters.get("offer_id", None) if filter_offer_id is not None: - q = q.filter(Offer.offer_id == filter_offer_id) + query_suffix += f" AND offer_id = x'{filter_offer_id.hex()}'" filter_coin_from = filters.get("coin_from", None) if filter_coin_from and filter_coin_from > -1: - q = q.filter(Offer.coin_from == int(filter_coin_from)) + query_suffix += f" AND coin_from = {int(filter_coin_from)}" filter_coin_to = filters.get("coin_to", None) if filter_coin_to and filter_coin_to > -1: - q = q.filter(Offer.coin_to == int(filter_coin_to)) + query_suffix += f" AND coin_to = {int(filter_coin_to)}" filter_include_sent = filters.get("include_sent", None) if filter_include_sent is not None and filter_include_sent is not True: - q = q.filter(Offer.was_sent == False) # noqa: E712 + query_suffix += " AND was_sent = 0" order_dir = filters.get("sort_dir", "desc") order_by = filters.get("sort_by", "created_at") - - if order_by == "created_at": - q = q.order_by( - Offer.created_at.desc() - if order_dir == "desc" - else Offer.created_at.asc() - ) - elif order_by == "rate": - q = q.order_by( - Offer.rate.desc() if order_dir == "desc" else Offer.rate.asc() - ) + query_suffix += f" ORDER BY {order_by} {order_dir.upper()}" limit = filters.get("limit", None) if limit is not None: - q = q.limit(limit) + query_suffix += f" LIMIT {limit}" offset = filters.get("offset", None) if offset is not None: - q = q.offset(offset) + query_suffix += f" OFFSET {offset}" + + q = self.query(Offer, cursor, query_suffix=query_suffix) for row in q: - offer = row[0] if with_bid_info else row + offer = row # Show offers for enabled coins only try: _ = self.ci(offer.coin_from) _ = self.ci(offer.coin_to) except Exception as e: # noqa: F841 continue - if with_bid_info: - rv.append((offer, 0 if row[1] is None else row[1])) - else: - rv.append(offer) + rv.append(offer) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def activeBidsQueryStr( self, now: int, offer_table: str = "offers", bids_table: str = "bids" @@ -10647,7 +10481,7 @@ class BasicSwap(BaseApp): for_html: bool = False, filters={}, ): - session = self.openSession() + cursor = self.openDB() try: rv = [] now: int = self.getTime() @@ -10700,8 +10534,8 @@ class BasicSwap(BaseApp): if offset is not None: query_str += f" OFFSET {offset}" - q = session.execute( - text(query_str), + q = cursor.execute( + query_str, { "ads_swap": SwapTypes.XMR_SWAP, "itx_type": TxTypes.ITX, @@ -10730,7 +10564,7 @@ class BasicSwap(BaseApp): rv.append(result) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def listSwapsInProgress(self, for_html=False): self.mxDB.acquire() @@ -10769,7 +10603,7 @@ class BasicSwap(BaseApp): finally: self.mxDB.release() - def listAllSMSGAddresses(self, filters={}, session=None): + def listAllSMSGAddresses(self, filters={}, cursor=None): query_str = "SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses" query_str += " WHERE 1 = 1 " query_data = {} @@ -10798,9 +10632,9 @@ class BasicSwap(BaseApp): query_str += f" OFFSET {offset}" try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) rv = [] - q = use_session.execute(text(query_str), query_data) + q = use_cursor.execute(query_str, query_data) for row in q: rv.append( { @@ -10815,8 +10649,8 @@ class BasicSwap(BaseApp): ) return rv finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def listSMSGAddresses(self, use_type_str: str): if use_type_str == "offer_send_from": @@ -10829,24 +10663,22 @@ class BasicSwap(BaseApp): raise ValueError("Unknown address type") try: - session = self.openSession() + cursor = self.openDB() rv = [] - q = session.execute( - text( - "SELECT sa.addr, ki.label FROM smsgaddresses AS sa LEFT JOIN knownidentities AS ki ON sa.addr = ki.address WHERE sa.use_type = {} AND sa.active_ind = 1 ORDER BY sa.addr_id DESC".format( - use_type - ) + q = cursor.execute( + "SELECT sa.addr, ki.label FROM smsgaddresses AS sa LEFT JOIN knownidentities AS ki ON sa.addr = ki.address WHERE sa.use_type = {} AND sa.active_ind = 1 ORDER BY sa.addr_id DESC".format( + use_type ) ) for row in q: rv.append((row[0], row[1])) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def listAutomationStrategies(self, filters={}): try: - session = self.openSession() + cursor = self.openDB() rv = [] query_str = "SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats" @@ -10867,41 +10699,37 @@ class BasicSwap(BaseApp): if offset is not None: query_str += f" OFFSET {offset}" - q = session.execute(text(query_str)) + q = cursor.execute(query_str) for row in q: rv.append(row) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def getAutomationStrategy(self, strategy_id: int): try: - session = self.openSession() - return ( - session.query(AutomationStrategy) - .filter_by(record_id=strategy_id) - .first() + cursor = self.openDB() + return firstOrNone( + self.query(AutomationStrategy, cursor, {"record_id": strategy_id}) ) finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def updateAutomationStrategy(self, strategy_id: int, data, note: str) -> None: try: - session = self.openSession() - strategy = ( - session.query(AutomationStrategy) - .filter_by(record_id=strategy_id) - .first() + cursor = self.openDB() + strategy = firstOrNone( + self.query(AutomationStrategy, cursor, {"record_id": strategy_id}) ) strategy.data = json.dumps(data).encode("utf-8") strategy.note = note - session.add(strategy) + self.updateDB(strategy, cursor, ["record_id"]) finally: - self.closeSession(session) + self.closeDB(cursor) def getLinkedStrategy(self, linked_type: int, linked_id): try: - session = self.openSession() + cursor = self.openDB() query_str = ( "SELECT links.strategy_id, strats.label FROM automationlinks links" + " LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id" @@ -10909,20 +10737,20 @@ class BasicSwap(BaseApp): int(linked_type), linked_id.hex() ) ) - q = session.execute(text(query_str)).first() + q = cursor.execute(query_str).fetchone() return q finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def newSMSGAddress( - self, use_type=AddressTypes.RECV_OFFER, addressnote=None, session=None + self, use_type=AddressTypes.RECV_OFFER, addressnote=None, cursor=None ): now: int = self.getTime() try: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) - v = use_session.query(DBKVString).filter_by(key="smsg_chain_id").first() - if not v: + smsg_chain_id = self.getStringKV("smsg_chain_id", use_cursor) + if not smsg_chain_id: smsg_account = self.callrpc( "extkey", ["deriveAccount", "smsg keys", "78900"] ) @@ -10931,7 +10759,6 @@ class BasicSwap(BaseApp): extkey = self.callrpc("extkey") # Disable receiving on all chains - smsg_chain_id = None extkey = self.callrpc("extkey", ["account", smsg_account_id]) for c in extkey["chains"]: self.callrpc("extkey", ["options", c["id"], "receive_on", "false"]) @@ -10941,9 +10768,7 @@ class BasicSwap(BaseApp): if not smsg_chain_id: raise ValueError("External chain not found.") - use_session.add(DBKVString(key="smsg_chain_id", value=smsg_chain_id)) - else: - smsg_chain_id = v.value + self.setStringKV("smsg_chain_id", smsg_chain_id, use_cursor) smsg_chain = self.callrpc("extkey", ["key", smsg_chain_id]) num_derives = int(smsg_chain["num_derives"]) @@ -10972,14 +10797,14 @@ class BasicSwap(BaseApp): if addressnote is not None: addr_obj.note = addressnote - use_session.add(addr_obj) + self.add(addr_obj, use_cursor) return new_addr, addr_info["pubkey"] finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def addSMSGAddress(self, pubkey_hex: str, addressnote: str = None) -> None: - session = self.openSession() + cursor = self.openDB() try: now: int = self.getTime() ci = self.ci(Coins.PART) @@ -10987,7 +10812,7 @@ class BasicSwap(BaseApp): self.callrpc("smsgaddaddress", [add_addr, pubkey_hex]) self.callrpc("smsglocalkeys", ["anon", "-", add_addr]) - session.add( + self.add( SmsgAddress( addr=add_addr, use_type=AddressTypes.SEND_OFFER, @@ -10995,11 +10820,12 @@ class BasicSwap(BaseApp): created_at=now, note=addressnote, pubkey=pubkey_hex, - ) + ), + cursor, ) return add_addr finally: - self.closeSession(session) + self.closeDB(cursor) def editSMSGAddress( self, @@ -11007,9 +10833,9 @@ class BasicSwap(BaseApp): active_ind: int, addressnote: str = None, use_type=None, - session=None, + cursor=None, ) -> None: - use_session = self.openSession(session) + use_cursor = self.openDB(cursor) try: mode = "-" if active_ind == 0 else "+" rv = self.callrpc("smsglocalkeys", ["recv", mode, address]) @@ -11028,34 +10854,34 @@ class BasicSwap(BaseApp): query_str += ", note = :note" query_str += " WHERE addr = :addr" - rv = use_session.execute(text(query_str), values) + rv = use_cursor.execute(query_str, values) if rv.rowcount < 1: query_str: str = ( "INSERT INTO smsgaddresses (addr, active_ind, use_type) VALUES (:addr, :active_ind, :use_type)" ) - use_session.execute(text(query_str), values) + use_cursor.execute(query_str, values) finally: - if session is None: - self.closeSession(use_session) + if cursor is None: + self.closeDB(use_cursor) def disableAllSMSGAddresses(self): filters = { "exclude_inactive": True, } - session = self.openSession() + cursor = self.openDB() rv = {} num_disabled = 0 try: - active_addresses = self.listAllSMSGAddresses(filters, session=session) + active_addresses = self.listAllSMSGAddresses(filters, cursor=cursor) for active_address in active_addresses: if active_address["addr"] == self.network_addr: continue self.editSMSGAddress( - active_address["addr"], active_ind=0, session=session + active_address["addr"], active_ind=0, cursor=cursor ) num_disabled += 1 finally: - self.closeSession(session) + self.closeDB(cursor) rv["num_disabled"] = num_disabled @@ -11079,12 +10905,12 @@ class BasicSwap(BaseApp): rv["num_core_disabled"] = num_core_disabled return rv - def prepareSMSGAddress(self, addr_send_from, use_type, session): + def prepareSMSGAddress(self, addr_send_from, use_type, cursor): if addr_send_from is None: - return self.newSMSGAddress(use_type=use_type, session=session)[0] + return self.newSMSGAddress(use_type=use_type, cursor=cursor)[0] use_addr = addr_send_from self.editSMSGAddress( - use_addr, 1, use_type=use_type, session=session + use_addr, 1, use_type=use_type, cursor=cursor ) # Ensure receive is active return use_addr @@ -11195,15 +11021,15 @@ class BasicSwap(BaseApp): self.swaps_in_progress[bid.bid_id] = (bid, swap_in_progress[1]) def getAddressLabel(self, addresses): - session = self.openSession() + cursor = self.openDB() try: rv = [] for a in addresses: - v = session.query(KnownIdentity).filter_by(address=a).first() + v = firstOrNone(self.query(KnownIdentity, cursor, {"address": a})) rv.append("" if (not v or not v.label) else v.label) return rv finally: - self.closeSession(session, commit=False) + self.closeDB(cursor, commit=False) def add_connection(self, host, port, peer_pubkey): self.log.info("add_connection %s %d %s", host, port, peer_pubkey.hex()) @@ -11293,28 +11119,15 @@ class BasicSwap(BaseApp): return rv def setFilters(self, prefix, filters): - try: - session = self.openSession() - key_str = "saved_filters_" + prefix - value_str = json.dumps(filters) - self.setStringKV(key_str, value_str, session) - finally: - self.closeSession(session) + key_str = "saved_filters_" + prefix + value_str = json.dumps(filters) + self.setStringKV(key_str, value_str) def getFilters(self, prefix): - try: - session = self.openSession() - key_str = "saved_filters_" + prefix - value_str = self.getStringKV(key_str, session) - return None if not value_str else json.loads(value_str) - finally: - self.closeSession(session, commit=False) + key_str = "saved_filters_" + prefix + value_str = self.getStringKV(key_str) + return None if not value_str else json.loads(value_str) def clearFilters(self, prefix) -> None: - try: - session = self.openSession() - key_str = "saved_filters_" + prefix - query_str = "DELETE FROM kv_string WHERE key = :key_str" - session.execute(text(query_str), {"key_str": key_str}) - finally: - self.closeSession(session) + key_str = "saved_filters_" + prefix + self.clearStringKV(key_str) diff --git a/basicswap/db.py b/basicswap/db.py index 1c3ae32..4d5e785 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -1,19 +1,20 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. +import inspect +import sqlite3 import time -import sqlalchemy as sa from enum import IntEnum, auto -from sqlalchemy.orm import declarative_base +from typing import Optional CURRENT_DB_VERSION = 24 CURRENT_DB_DATA_VERSION = 4 -Base = declarative_base() class Concepts(IntEnum): @@ -33,136 +34,196 @@ def strConcepts(state): return "Unknown" +def firstOrNone(gen): + all_rows = list(gen) + return all_rows[0] if len(all_rows) > 0 else None + + def pack_state(new_state: int, now: int) -> bytes: return int(new_state).to_bytes(4, "little") + now.to_bytes(8, "little") -class DBKVInt(Base): +class Table: + __sqlite3_table__ = True + + def __init__(self, **kwargs): + for name, value in kwargs.items(): + if not hasattr(self, name): + raise ValueError(f"Unknown attribute {name}") + setattr(self, name, value) + # Init any unset columns to None + for mc in inspect.getmembers(self): + mc_name, mc_obj = mc + if hasattr(mc_obj, "__sqlite3_column__"): + setattr(self, mc_name, None) + + def isSet(self, field: str): + io = getattr(self, field) + + # Column is not set in instance + if hasattr(io, "__sqlite3_column__"): + return False + return True if io is not None else False + + +class Column: + __sqlite3_column__ = True + + def __init__( + self, column_type, primary_key=False, autoincrement=False, unique=False + ): + self.column_type = column_type + self.primary_key = primary_key + self.autoincrement = autoincrement + self.unique = unique + + +class PrimaryKeyConstraint: + __sqlite3_primary_key__ = True + + def __init__(self, column_1, column_2=None, column_3=None): + self.column_1 = column_1 + self.column_2 = column_2 + self.column_3 = column_3 + + +class UniqueConstraint: + __sqlite3_unique__ = True + + def __init__(self, column_1, column_2=None, column_3=None): + self.column_1 = column_1 + self.column_2 = column_2 + self.column_3 = column_3 + + +class Index: + __sqlite3_index__ = True + + def __init__(self, name, column_1, column_2=None, column_3=None): + self.name = name + self.column_1 = column_1 + self.column_2 = column_2 + self.column_3 = column_3 + + +class DBKVInt(Table): __tablename__ = "kv_int" - key = sa.Column(sa.String, primary_key=True) - value = sa.Column(sa.Integer) + key = Column("string", primary_key=True) + value = Column("integer") -class DBKVString(Base): +class DBKVString(Table): __tablename__ = "kv_string" - key = sa.Column(sa.String, primary_key=True) - value = sa.Column(sa.String) + key = Column("string", primary_key=True) + value = Column("string") -class Offer(Base): +class Offer(Table): __tablename__ = "offers" - offer_id = sa.Column(sa.LargeBinary, primary_key=True) - active_ind = sa.Column(sa.Integer) + offer_id = Column("blob", primary_key=True) + active_ind = Column("integer") - protocol_version = sa.Column(sa.Integer) - coin_from = sa.Column(sa.Integer) - coin_to = sa.Column(sa.Integer) - amount_from = sa.Column(sa.BigInteger) - amount_to = sa.Column(sa.BigInteger) - rate = sa.Column(sa.BigInteger) - min_bid_amount = sa.Column(sa.BigInteger) - time_valid = sa.Column(sa.BigInteger) - lock_type = sa.Column(sa.Integer) - lock_value = sa.Column(sa.Integer) - swap_type = sa.Column(sa.Integer) + protocol_version = Column("integer") + coin_from = Column("integer") + coin_to = Column("integer") + amount_from = Column("integer") + amount_to = Column("integer") + rate = Column("integer") + min_bid_amount = Column("integer") + time_valid = Column("integer") + lock_type = Column("integer") + lock_value = Column("integer") + swap_type = Column("integer") - proof_address = sa.Column(sa.String) - proof_signature = sa.Column(sa.LargeBinary) - proof_utxos = sa.Column(sa.LargeBinary) - pkhash_seller = sa.Column(sa.LargeBinary) - secret_hash = sa.Column(sa.LargeBinary) + proof_address = Column("string") + proof_signature = Column("blob") + proof_utxos = Column("blob") + pkhash_seller = Column("blob") + secret_hash = Column("blob") - addr_from = sa.Column(sa.String) - addr_to = sa.Column(sa.String) - created_at = sa.Column(sa.BigInteger) - expire_at = sa.Column(sa.BigInteger) - was_sent = sa.Column(sa.Boolean) # Sent by node + addr_from = Column("string") + addr_to = Column("string") + created_at = Column("integer") + expire_at = Column("integer") - from_feerate = sa.Column(sa.BigInteger) - to_feerate = sa.Column(sa.BigInteger) + from_feerate = Column("integer") + to_feerate = Column("integer") - amount_negotiable = sa.Column(sa.Boolean) - rate_negotiable = sa.Column(sa.Boolean) + amount_negotiable = Column("bool") + rate_negotiable = Column("bool") # Local fields - auto_accept_bids = sa.Column(sa.Boolean) - withdraw_to_addr = sa.Column( - sa.String + auto_accept_bids = Column("bool") + was_sent = Column("bool") # Sent by node + withdraw_to_addr = Column( + "string" ) # Address to spend lock tx to - address from wallet if empty TODO - security_token = sa.Column(sa.LargeBinary) - bid_reversed = sa.Column(sa.Boolean) + security_token = Column("blob") + bid_reversed = Column("bool") - state = sa.Column(sa.Integer) - states = sa.Column(sa.LargeBinary) # Packed states and times + state = Column("integer") + states = Column("blob") # Packed states and times def setState(self, new_state): now = int(time.time()) self.state = new_state - if self.states is None: + if self.isSet("states") is False: self.states = pack_state(new_state, now) else: self.states += pack_state(new_state, now) -class Bid(Base): +class Bid(Table): __tablename__ = "bids" - bid_id = sa.Column(sa.LargeBinary, primary_key=True) - offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id")) - active_ind = sa.Column(sa.Integer) + bid_id = Column("blob", primary_key=True) + offer_id = Column("blob") + active_ind = Column("integer") + protocol_version = Column("integer") + created_at = Column("integer") + expire_at = Column("integer") + bid_addr = Column("string") + proof_address = Column("string") + proof_utxos = Column("blob") + # Address to spend lock tx to - address from wallet if empty TODO + withdraw_to_addr = Column("string") - protocol_version = sa.Column(sa.Integer) - was_sent = sa.Column(sa.Boolean) # Sent by node - was_received = sa.Column(sa.Boolean) - contract_count = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - expire_at = sa.Column(sa.BigInteger) - bid_addr = sa.Column(sa.String) - proof_address = sa.Column(sa.String) - proof_utxos = sa.Column(sa.LargeBinary) - withdraw_to_addr = sa.Column( - sa.String - ) # Address to spend lock tx to - address from wallet if empty TODO + recovered_secret = Column("blob") + amount_to = Column("integer") # amount * offer.rate - recovered_secret = sa.Column(sa.LargeBinary) - amount_to = sa.Column(sa.BigInteger) # amount * offer.rate + pkhash_buyer = Column("blob") + pkhash_buyer_to = Column("blob") # Used for the ptx if coin pubkey hashes differ + amount = Column("integer") + rate = Column("integer") - pkhash_buyer = sa.Column(sa.LargeBinary) - pkhash_buyer_to = sa.Column( - sa.LargeBinary - ) # Used for the ptx if coin pubkey hashes differ - amount = sa.Column(sa.BigInteger) - rate = sa.Column(sa.BigInteger) + pkhash_seller = Column("blob") - pkhash_seller = sa.Column(sa.LargeBinary) + initiate_txn_redeem = Column("blob") + initiate_txn_refund = Column("blob") - initiate_txn_redeem = sa.Column(sa.LargeBinary) - initiate_txn_refund = sa.Column(sa.LargeBinary) + participate_txn_redeem = Column("blob") + participate_txn_refund = Column("blob") - participate_txn_redeem = sa.Column(sa.LargeBinary) - participate_txn_refund = sa.Column(sa.LargeBinary) + in_progress = Column("integer") + state = Column("integer") + state_time = Column("integer") # Timestamp of last state change + states = Column("blob") # Packed states and times - in_progress = sa.Column(sa.Integer) - state = sa.Column(sa.Integer) - state_time = sa.Column(sa.BigInteger) # Timestamp of last state change - states = sa.Column(sa.LargeBinary) # Packed states and times + state_note = Column("string") + was_sent = Column("bool") # Sent by node + was_received = Column("bool") + contract_count = Column("integer") + debug_ind = Column("integer") + security_token = Column("blob") - state_note = sa.Column(sa.String) + chain_a_height_start = Column("integer") # Height of script chain before the swap + # Height of scriptless chain before the swap + chain_b_height_start = Column("integer") - debug_ind = sa.Column(sa.Integer) - security_token = sa.Column(sa.LargeBinary) - - chain_a_height_start = sa.Column( - sa.Integer - ) # Height of script chain before the swap - chain_b_height_start = sa.Column( - sa.Integer - ) # Height of scriptless chain before the swap - - reject_code = sa.Column(sa.Integer) + reject_code = Column("integer") initiate_tx = None participate_tx = None @@ -173,21 +234,21 @@ class Bid(Base): txns = {} def getITxState(self): - if self.initiate_tx is None: + if self.isSet("initiate_tx") is False: return None return self.initiate_tx.state def setITxState(self, new_state): - if self.initiate_tx is not None: + if self.isSet("initiate_tx"): self.initiate_tx.setState(new_state) def getPTxState(self): - if self.participate_tx is None: + if self.isSet("participate_tx") is False: return None return self.participate_tx.state def setPTxState(self, new_state): - if self.participate_tx is not None: + if self.isSet("participate_tx"): self.participate_tx.setState(new_state) def setState(self, new_state, state_note=None): @@ -195,367 +256,687 @@ class Bid(Base): self.state = new_state self.state_time = now - if state_note is not None: + if self.isSet("state_note"): self.state_note = state_note - if self.states is None: + if self.isSet("states") is False: self.states = pack_state(new_state, now) else: self.states += pack_state(new_state, now) def getLockTXBVout(self): - if self.xmr_b_lock_tx: + if self.isSet("xmr_b_lock_tx"): return self.xmr_b_lock_tx.vout return None -class SwapTx(Base): +class SwapTx(Table): __tablename__ = "transactions" - bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id")) - tx_type = sa.Column(sa.Integer) # TxTypes - __table_args__ = ( - sa.PrimaryKeyConstraint("bid_id", "tx_type"), - {}, - ) + bid_id = Column("blob") + tx_type = Column("integer") # TxTypes - txid = sa.Column(sa.LargeBinary) - vout = sa.Column(sa.Integer) - tx_data = sa.Column(sa.LargeBinary) + txid = Column("blob") + vout = Column("integer") + tx_data = Column("blob") - script = sa.Column(sa.LargeBinary) + script = Column("blob") - tx_fee = sa.Column(sa.BigInteger) - chain_height = sa.Column(sa.Integer) - conf = sa.Column(sa.Integer) + tx_fee = Column("integer") + chain_height = Column("integer") + conf = Column("integer") - spend_txid = sa.Column(sa.LargeBinary) - spend_n = sa.Column(sa.Integer) + spend_txid = Column("blob") + spend_n = Column("integer") - block_hash = sa.Column(sa.LargeBinary) - block_height = sa.Column(sa.Integer) - block_time = sa.Column(sa.BigInteger) + block_hash = Column("blob") + block_height = Column("integer") + block_time = Column("integer") - state = sa.Column(sa.Integer) - states = sa.Column(sa.LargeBinary) # Packed states and times + state = Column("integer") + states = Column("blob") # Packed states and times + + primary_key = PrimaryKeyConstraint("bid_id", "tx_type") def setState(self, new_state): if self.state == new_state: return self.state = new_state now: int = int(time.time()) - if self.states is None: + if self.isSet("states") is False: self.states = pack_state(new_state, now) else: self.states += pack_state(new_state, now) -class PrefundedTx(Base): +class PrefundedTx(Table): __tablename__ = "prefunded_transactions" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - linked_type = sa.Column(sa.Integer) - linked_id = sa.Column(sa.LargeBinary) - tx_type = sa.Column(sa.Integer) # TxTypes - tx_data = sa.Column(sa.LargeBinary) - used_by = sa.Column(sa.LargeBinary) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") + linked_type = Column("integer") + linked_id = Column("blob") + tx_type = Column("integer") # TxTypes + tx_data = Column("blob") + used_by = Column("blob") -class PooledAddress(Base): +class PooledAddress(Table): __tablename__ = "addresspool" - addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - coin_type = sa.Column(sa.Integer) - addr = sa.Column(sa.String) - bid_id = sa.Column(sa.LargeBinary) - tx_type = sa.Column(sa.Integer) + addr_id = Column("integer", primary_key=True, autoincrement=True) + coin_type = Column("integer") + addr = Column("string") + bid_id = Column("blob") + tx_type = Column("integer") -class SentOffer(Base): +class SentOffer(Table): __tablename__ = "sentoffers" - offer_id = sa.Column(sa.LargeBinary, primary_key=True) + offer_id = Column("blob", primary_key=True) -class SmsgAddress(Base): +class SmsgAddress(Table): __tablename__ = "smsgaddresses" - addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - addr = sa.Column(sa.String, unique=True) - pubkey = sa.Column(sa.String) - use_type = sa.Column(sa.Integer) - note = sa.Column(sa.String) + addr_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") + addr = Column("string", unique=True) + pubkey = Column("string") + use_type = Column("integer") + note = Column("string") -class Action(Base): +class Action(Table): __tablename__ = "actions" - action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - trigger_at = sa.Column(sa.BigInteger) - linked_id = sa.Column(sa.LargeBinary) - action_type = sa.Column(sa.Integer) - action_data = sa.Column(sa.LargeBinary) + action_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") + trigger_at = Column("integer") + linked_id = Column("blob") + action_type = Column("integer") + action_data = Column("blob") -class EventLog(Base): +class EventLog(Table): __tablename__ = "eventlog" - event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - linked_type = sa.Column(sa.Integer) - linked_id = sa.Column(sa.LargeBinary) - event_type = sa.Column(sa.Integer) - event_msg = sa.Column(sa.String) + event_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") + linked_type = Column("integer") + linked_id = Column("blob") + event_type = Column("integer") + event_msg = Column("string") - __table_args__ = (sa.Index("main_index", "linked_type", "linked_id"),) + index = Index("main_index", "linked_type", "linked_id") -class XmrOffer(Base): +class XmrOffer(Table): __tablename__ = "xmr_offers" # TODO: Merge to Offer - swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id")) + swap_id = Column("integer", primary_key=True, autoincrement=True) + offer_id = Column("blob") - a_fee_rate = sa.Column(sa.BigInteger) # Chain a fee rate - b_fee_rate = sa.Column(sa.BigInteger) # Chain b fee rate + a_fee_rate = Column("integer") # Chain a fee rate + b_fee_rate = Column("integer") # Chain b fee rate - lock_time_1 = sa.Column( - sa.Integer - ) # Delay before the chain a lock refund tx can be mined - lock_time_2 = sa.Column( - sa.Integer - ) # Delay before the follower can spend from the chain a lock refund tx + # Delay before the chain a lock refund tx can be mined + lock_time_1 = Column("integer") + # Delay before the follower can spend from the chain a lock refund tx + lock_time_2 = Column("integer") -class XmrSwap(Base): +class XmrSwap(Table): __tablename__ = "xmr_swaps" - swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id")) + swap_id = Column("integer", primary_key=True, autoincrement=True) + bid_id = Column("blob") - contract_count = sa.Column(sa.Integer) + contract_count = Column("integer") - dest_af = sa.Column( - sa.LargeBinary - ) # Destination for coin A amount to follower when swap completes successfully + # Destination for coin A amount to follower when swap completes successfully + dest_af = Column("blob") - pkal = sa.Column(sa.LargeBinary) - pkasl = sa.Column(sa.LargeBinary) + pkal = Column("blob") + pkasl = Column("blob") - pkaf = sa.Column(sa.LargeBinary) - pkasf = sa.Column(sa.LargeBinary) + pkaf = Column("blob") + pkasf = Column("blob") - vkbvl = sa.Column(sa.LargeBinary) - vkbsl = sa.Column(sa.LargeBinary) - pkbvl = sa.Column(sa.LargeBinary) - pkbsl = sa.Column(sa.LargeBinary) + vkbvl = Column("blob") + vkbsl = Column("blob") + pkbvl = Column("blob") + pkbsl = Column("blob") - vkbvf = sa.Column(sa.LargeBinary) - vkbsf = sa.Column(sa.LargeBinary) - pkbvf = sa.Column(sa.LargeBinary) - pkbsf = sa.Column(sa.LargeBinary) + vkbvf = Column("blob") + vkbsf = Column("blob") + pkbvf = Column("blob") + pkbsf = Column("blob") - kbsl_dleag = sa.Column(sa.LargeBinary) - kbsf_dleag = sa.Column(sa.LargeBinary) + kbsl_dleag = Column("blob") + kbsf_dleag = Column("blob") - vkbv = sa.Column(sa.LargeBinary) # chain b view private key - pkbv = sa.Column(sa.LargeBinary) # chain b view public key - pkbs = sa.Column(sa.LargeBinary) # chain b spend public key + vkbv = Column("blob") # chain b view private key + pkbv = Column("blob") # chain b view public key + pkbs = Column("blob") # chain b spend public key - a_lock_tx = sa.Column(sa.LargeBinary) - a_lock_tx_script = sa.Column(sa.LargeBinary) - a_lock_tx_id = sa.Column(sa.LargeBinary) - a_lock_tx_vout = sa.Column(sa.Integer) + a_lock_tx = Column("blob") + a_lock_tx_script = Column("blob") + a_lock_tx_id = Column("blob") + a_lock_tx_vout = Column("integer") - a_lock_refund_tx = sa.Column(sa.LargeBinary) - a_lock_refund_tx_script = sa.Column(sa.LargeBinary) - a_lock_refund_tx_id = sa.Column(sa.LargeBinary) - a_swap_refund_value = sa.Column(sa.BigInteger) - al_lock_refund_tx_sig = sa.Column(sa.LargeBinary) - af_lock_refund_tx_sig = sa.Column(sa.LargeBinary) + a_lock_refund_tx = Column("blob") + a_lock_refund_tx_script = Column("blob") + a_lock_refund_tx_id = Column("blob") + a_swap_refund_value = Column("integer") + al_lock_refund_tx_sig = Column("blob") + af_lock_refund_tx_sig = Column("blob") - a_lock_refund_spend_tx = sa.Column(sa.LargeBinary) - a_lock_refund_spend_tx_id = sa.Column(sa.LargeBinary) + a_lock_refund_spend_tx = Column("blob") + a_lock_refund_spend_tx_id = Column("blob") - af_lock_refund_spend_tx_esig = sa.Column(sa.LargeBinary) - af_lock_refund_spend_tx_sig = sa.Column(sa.LargeBinary) + af_lock_refund_spend_tx_esig = Column("blob") + af_lock_refund_spend_tx_sig = Column("blob") - a_lock_spend_tx = sa.Column(sa.LargeBinary) - a_lock_spend_tx_id = sa.Column(sa.LargeBinary) - al_lock_spend_tx_esig = sa.Column(sa.LargeBinary) - kal_sig = sa.Column(sa.LargeBinary) + a_lock_spend_tx = Column("blob") + a_lock_spend_tx_id = Column("blob") + al_lock_spend_tx_esig = Column("blob") + kal_sig = Column("blob") - a_lock_refund_swipe_tx = sa.Column( - sa.LargeBinary - ) # Follower spends script coin lock refund tx + # Follower spends script coin lock refund tx + a_lock_refund_swipe_tx = Column("blob") - b_lock_tx_id = sa.Column(sa.LargeBinary) + b_lock_tx_id = Column("blob") -class XmrSplitData(Base): +class XmrSplitData(Table): __tablename__ = "xmr_split_data" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - addr_from = sa.Column(sa.String) - addr_to = sa.Column(sa.String) - bid_id = sa.Column(sa.LargeBinary) - msg_type = sa.Column(sa.Integer) - msg_sequence = sa.Column(sa.Integer) - dleag = sa.Column(sa.LargeBinary) - created_at = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + addr_from = Column("string") + addr_to = Column("string") + bid_id = Column("blob") + msg_type = Column("integer") + msg_sequence = Column("integer") + dleag = Column("blob") + created_at = Column("integer") - __table_args__ = ( - sa.UniqueConstraint("bid_id", "msg_type", "msg_sequence", name="uc_1"), - ) + uc_1 = UniqueConstraint("bid_id", "msg_type", "msg_sequence") -class RevokedMessage(Base): +class RevokedMessage(Table): __tablename__ = "revoked_messages" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - msg_id = sa.Column(sa.LargeBinary) - created_at = sa.Column(sa.BigInteger) - expires_at = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + msg_id = Column("blob") + created_at = Column("integer") + expires_at = Column("integer") -class Wallets(Base): +class Wallets(Table): __tablename__ = "wallets" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - coin_id = sa.Column(sa.Integer) - wallet_name = sa.Column(sa.String) - wallet_data = sa.Column(sa.String) - balance_type = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + coin_id = Column("integer") + wallet_name = Column("string") + wallet_data = Column("string") + balance_type = Column("integer") + created_at = Column("integer") -class KnownIdentity(Base): +class KnownIdentity(Table): __tablename__ = "knownidentities" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - address = sa.Column(sa.String) - label = sa.Column(sa.String) - publickey = sa.Column(sa.LargeBinary) - num_sent_bids_successful = sa.Column(sa.Integer) - num_recv_bids_successful = sa.Column(sa.Integer) - num_sent_bids_rejected = sa.Column(sa.Integer) - num_recv_bids_rejected = sa.Column(sa.Integer) - num_sent_bids_failed = sa.Column(sa.Integer) - num_recv_bids_failed = sa.Column(sa.Integer) - automation_override = sa.Column(sa.Integer) # AutomationOverrideOptions - visibility_override = sa.Column(sa.Integer) # VisibilityOverrideOptions - data = sa.Column(sa.LargeBinary) - note = sa.Column(sa.String) - updated_at = sa.Column(sa.BigInteger) - created_at = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + address = Column("string") + label = Column("string") + publickey = Column("blob") + num_sent_bids_successful = Column("integer") + num_recv_bids_successful = Column("integer") + num_sent_bids_rejected = Column("integer") + num_recv_bids_rejected = Column("integer") + num_sent_bids_failed = Column("integer") + num_recv_bids_failed = Column("integer") + automation_override = Column("integer") # AutomationOverrideOptions + visibility_override = Column("integer") # VisibilityOverrideOptions + data = Column("blob") + note = Column("string") + updated_at = Column("integer") + created_at = Column("integer") -class AutomationStrategy(Base): +class AutomationStrategy(Table): __tablename__ = "automationstrategies" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") - label = sa.Column(sa.String) - type_ind = sa.Column(sa.Integer) - only_known_identities = sa.Column(sa.Integer) - num_concurrent = sa.Column(sa.Integer) - data = sa.Column(sa.LargeBinary) + label = Column("string") + type_ind = Column("integer") + only_known_identities = Column("integer") + num_concurrent = Column("integer") + data = Column("blob") - note = sa.Column(sa.String) - created_at = sa.Column(sa.BigInteger) + note = Column("string") + created_at = Column("integer") -class AutomationLink(Base): +class AutomationLink(Table): __tablename__ = "automationlinks" # Contains per order/bid options - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") - linked_type = sa.Column(sa.Integer) - linked_id = sa.Column(sa.LargeBinary) - strategy_id = sa.Column(sa.Integer) + linked_type = Column("integer") + linked_id = Column("blob") + strategy_id = Column("integer") - data = sa.Column(sa.LargeBinary) - repeat_limit = sa.Column(sa.Integer) - repeat_count = sa.Column(sa.Integer) + data = Column("blob") + repeat_limit = Column("integer") + repeat_count = Column("integer") - note = sa.Column(sa.String) - created_at = sa.Column(sa.BigInteger) + note = Column("string") + created_at = Column("integer") - __table_args__ = (sa.Index("linked_index", "linked_type", "linked_id"),) + index = Index("linked_index", "linked_type", "linked_id") -class History(Base): +class History(Table): __tablename__ = "history" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - concept_type = sa.Column(sa.Integer) - concept_id = sa.Column(sa.Integer) + record_id = Column("integer", primary_key=True, autoincrement=True) + concept_type = Column("integer") + concept_id = Column("integer") - changed_data = sa.Column(sa.LargeBinary) - created_at = sa.Column(sa.BigInteger) + changed_data = Column("blob") + created_at = Column("integer") -class BidState(Base): +class BidState(Table): __tablename__ = "bidstates" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - state_id = sa.Column(sa.Integer) - label = sa.Column(sa.String) - in_progress = sa.Column(sa.Integer) - in_error = sa.Column(sa.Integer) - swap_failed = sa.Column(sa.Integer) - swap_ended = sa.Column(sa.Integer) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + state_id = Column("integer") + label = Column("string") + in_progress = Column("integer") + in_error = Column("integer") + swap_failed = Column("integer") + swap_ended = Column("integer") - note = sa.Column(sa.String) - created_at = sa.Column(sa.BigInteger) + note = Column("string") + created_at = Column("integer") -class Notification(Base): +class Notification(Table): __tablename__ = "notifications" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) - event_type = sa.Column(sa.Integer) - event_data = sa.Column(sa.LargeBinary) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") + event_type = Column("integer") + event_data = Column("blob") -class MessageLink(Base): +class MessageLink(Table): __tablename__ = "message_links" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - active_ind = sa.Column(sa.Integer) - created_at = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + active_ind = Column("integer") + created_at = Column("integer") - linked_type = sa.Column(sa.Integer) - linked_id = sa.Column(sa.LargeBinary) + linked_type = Column("integer") + linked_id = Column("blob") # linked_row_id = sa.Column(sa.Integer) # TODO: Find a way to use table rowids - msg_type = sa.Column(sa.Integer) - msg_sequence = sa.Column(sa.Integer) - msg_id = sa.Column(sa.LargeBinary) + msg_type = Column("integer") + msg_sequence = Column("integer") + msg_id = Column("blob") -class CheckedBlock(Base): +class CheckedBlock(Table): __tablename__ = "checkedblocks" - record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - created_at = sa.Column(sa.BigInteger) - coin_type = sa.Column(sa.Integer) - block_height = sa.Column(sa.Integer) - block_hash = sa.Column(sa.LargeBinary) - block_time = sa.Column(sa.BigInteger) + record_id = Column("integer", primary_key=True, autoincrement=True) + created_at = Column("integer") + coin_type = Column("integer") + block_height = Column("integer") + block_hash = Column("blob") + block_time = Column("integer") + + +def create_db(db_path: str, log) -> None: + con = None + try: + con = sqlite3.connect(db_path) + c = con.cursor() + + g = globals().copy() + for name, obj in g.items(): + if not inspect.isclass(obj): + continue + if not hasattr(obj, "__sqlite3_table__"): + continue + if not hasattr(obj, "__tablename__"): + continue + + table_name: str = obj.__tablename__ + query: str = f"CREATE TABLE {table_name} (" + + primary_key = None + constraints = [] + indices = [] + num_columns: int = 0 + for m in inspect.getmembers(obj): + m_name, m_obj = m + + if hasattr(m_obj, "__sqlite3_primary_key__"): + primary_key = m_obj + continue + if hasattr(m_obj, "__sqlite3_unique__"): + constraints.append(m_obj) + continue + if hasattr(m_obj, "__sqlite3_index__"): + indices.append(m_obj) + continue + if hasattr(m_obj, "__sqlite3_column__"): + if num_columns > 0: + query += "," + + col_type: str = m_obj.column_type.upper() + if col_type == "BOOL": + col_type = "INTEGER" + query += f" {m_name} {col_type} " + + if m_obj.primary_key: + query += "PRIMARY KEY ASC " + if m_obj.unique: + query += "UNIQUE " + num_columns += 1 + + if primary_key is not None: + query += f", PRIMARY KEY ({primary_key.column_1}" + if primary_key.column_2: + query += f", {primary_key.column_2}" + if primary_key.column_3: + query += f", {primary_key.column_3}" + query += ") " + + for constraint in constraints: + query += f", UNIQUE ({constraint.column_1}" + if constraint.column_2: + query += f", {constraint.column_2}" + if constraint.column_3: + query += f", {constraint.column_3}" + query += ") " + + query += ")" + c.execute(query) + for i in indices: + query: str = f"CREATE INDEX {i.name} ON {table_name} ({i.column_1}" + if i.column_2 is not None: + query += f", {i.column_2}" + if i.column_3 is not None: + query += f", {i.column_3}" + query += ")" + c.execute(query) + + con.commit() + finally: + if con: + con.close() + + +class DBMethods: + def openDB(self, cursor=None): + if cursor: + # assert(self._thread_debug == threading.get_ident()) + assert self.mxDB.locked() + return cursor + + self.mxDB.acquire() + # self._thread_debug = threading.get_ident() + self._db_con = sqlite3.connect(self.sqlite_file) + return self._db_con.cursor() + + def commitDB(self): + assert self.mxDB.locked() + self._db_con.commit() + + def rollbackDB(self): + assert self.mxDB.locked() + self._db_con.rollback() + + def closeDB(self, cursor, commit=True): + assert self.mxDB.locked() + + if commit: + self._db_con.commit() + + self._db_con.close() + self.mxDB.release() + + def setIntKV(self, str_key: str, int_val: int, cursor=None) -> None: + try: + use_cursor = self.openDB(cursor) + use_cursor.execute( + """INSERT INTO kv_int (key, value) + VALUES (:key, :value) + ON CONFLICT(key) + DO UPDATE SET value=:value + WHERE key=:key;""", + { + "key": str_key, + "value": int(int_val), + }, + ) + finally: + if cursor is None: + self.closeDB(use_cursor) + + def getIntKV( + self, + str_key: str, + cursor=None, + default_val: int = None, + update_if_default: bool = True, + ) -> Optional[int]: + try: + use_cursor = self.openDB(cursor) + rows = use_cursor.execute( + "SELECT value FROM kv_int WHERE key = :key", {"key": str_key} + ).fetchall() + return rows[0][0] + except Exception as e: + if default_val is not None: + if update_if_default: + use_cursor.execute( + """INSERT INTO kv_int (key, value) + VALUES (:key, :value)""", + { + "key": str_key, + "value": int(default_val), + }, + ) + return default_val + else: + raise e + finally: + if cursor is None: + self.closeDB(use_cursor) + + def setStringKV(self, str_key: str, str_val: str, cursor=None) -> None: + try: + use_cursor = self.openDB(cursor) + use_cursor.execute( + """INSERT INTO kv_string (key, value) + VALUES (:key, :value) + ON CONFLICT(key) + DO UPDATE SET value=:value""", + { + "key": str_key, + "value": str_val, + }, + ) + finally: + if cursor is None: + self.closeDB(use_cursor) + + def getStringKV(self, str_key: str, cursor=None) -> Optional[str]: + try: + use_cursor = self.openDB(cursor) + rows = use_cursor.execute( + "SELECT value FROM kv_string WHERE key = :key", {"key": str_key} + ).fetchall() + if len(rows) < 1: + return None + return rows[0][0] + finally: + if cursor is None: + self.closeDB(use_cursor, commit=False) + + def clearStringKV(self, str_key: str, cursor=None) -> None: + try: + use_cursor = self.openDB(cursor) + use_cursor.execute( + "DELETE FROM kv_string WHERE key = :key", {"key": str_key} + ) + finally: + if cursor is None: + self.closeDB(use_cursor, commit=False) + + def add(self, obj, cursor, upsert: bool = False): + if cursor is None: + raise ValueError("Cursor is null") + if not hasattr(obj, "__tablename__"): + raise ValueError("Adding invalid object") + table_name: str = obj.__tablename__ + + values = {} + query: str = f"INSERT INTO {table_name} (" + + # See if the instance overwrote any class methods + for mc in inspect.getmembers(obj.__class__): + mc_name, mc_obj = mc + + if not hasattr(mc_obj, "__sqlite3_column__"): + continue + + m_obj = getattr(obj, mc_name) + + # Column is not set in instance + if hasattr(m_obj, "__sqlite3_column__"): + continue + + values[mc_name] = m_obj + + query_values: str = " VALUES (" + for i, key in enumerate(values): + if i > 0: + query += ", " + query_values += ", " + query += key + query_values += ":" + key + query += ") " + query_values + ")" + + if upsert: + query += " ON CONFLICT DO UPDATE SET " + for i, key in enumerate(values): + if i > 0: + query += ", " + query += f"{key}=:{key}" + + cursor.execute(query, values) + + def query( + self, table_class, cursor, constraints={}, order_by={}, query_suffix=None + ): + if cursor is None: + raise ValueError("Cursor is null") + if not hasattr(table_class, "__tablename__"): + raise ValueError("Querying invalid class") + table_name: str = table_class.__tablename__ + + query: str = "SELECT " + + columns = [] + + for mc in inspect.getmembers(table_class): + mc_name, mc_obj = mc + + if not hasattr(mc_obj, "__sqlite3_column__"): + continue + + if len(columns) > 0: + query += ", " + query += mc_name + columns.append((mc_name, mc_obj.column_type)) + + query += f" FROM {table_name} WHERE 1=1 " + + for ck, cv in constraints.items(): + query += f" AND {ck} = :{ck} " + + for order_col, order_dir in order_by.items(): + query += f" ORDER BY {order_col} {order_dir.upper()}" + + if query_suffix: + query += query_suffix + + rows = cursor.execute(query, constraints) + for row in rows: + obj = table_class() + for i, column_info in enumerate(columns): + colname, coltype = column_info + value = row[i] + if coltype == "bool": + if row[i] is not None: + value = False if row[i] == 0 else True + setattr(obj, colname, value) + yield obj + + def updateDB(self, obj, cursor, constraints=[]): + if cursor is None: + raise ValueError("Cursor is null") + if not hasattr(obj, "__tablename__"): + raise ValueError("Updating invalid obj") + table_name: str = obj.__tablename__ + + query: str = f"UPDATE {table_name} SET " + + values = {} + for mc in inspect.getmembers(obj.__class__): + mc_name, mc_obj = mc + + if not hasattr(mc_obj, "__sqlite3_column__"): + continue + + m_obj = getattr(obj, mc_name) + # Column is not set in instance + if hasattr(m_obj, "__sqlite3_column__"): + continue + + if mc_name in constraints: + values[mc_name] = m_obj + continue + + if len(values) > 0: + query += ", " + query += f"{mc_name} = :{mc_name}" + values[mc_name] = m_obj + + query += " WHERE 1=1 " + + for ck in constraints: + query += f" AND {ck} = :{ck} " + + cursor.execute(query, values) diff --git a/basicswap/db_upgrades.py b/basicswap/db_upgrades.py index c4f0bb4..3bfa724 100644 --- a/basicswap/db_upgrades.py +++ b/basicswap/db_upgrades.py @@ -1,29 +1,28 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import json import time -from sqlalchemy.sql import text -from sqlalchemy.orm import scoped_session from .db import ( + AutomationStrategy, BidState, Concepts, - AutomationStrategy, - CURRENT_DB_VERSION, CURRENT_DB_DATA_VERSION, + CURRENT_DB_VERSION, ) from .basicswap_util import ( BidStates, - strBidState, isActiveBidState, isErrorBidState, isFailingBidState, isFinalBidState, + strBidState, ) @@ -36,110 +35,110 @@ def upgradeDatabaseData(self, data_version): data_version, CURRENT_DB_DATA_VERSION, ) - with self.mxDB: - try: - session = scoped_session(self.session_factory) + cursor = self.openDB() + try: + now = int(time.time()) - now = int(time.time()) - - if data_version < 1: - session.add( - AutomationStrategy( - active_ind=1, - label="Accept All", - type_ind=Concepts.OFFER, - data=json.dumps( - {"exact_rate_only": True, "max_concurrent_bids": 5} - ).encode("utf-8"), - only_known_identities=False, - created_at=now, - ) - ) - session.add( - AutomationStrategy( - active_ind=1, - label="Accept Known", - type_ind=Concepts.OFFER, - data=json.dumps( - {"exact_rate_only": True, "max_concurrent_bids": 5} - ).encode("utf-8"), - only_known_identities=True, - note="Accept bids from identities with previously successful swaps only", - created_at=now, - ) - ) - - for state in BidStates: - session.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), - label=strBidState(state), - created_at=now, - ) - ) - - if data_version > 0 and data_version < 2: - for state in ( - BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, - BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX, - ): - session.add( - BidState( - active_ind=1, - state_id=int(state), - in_progress=isActiveBidState(state), - label=strBidState(state), - created_at=now, - ) - ) - if data_version > 0 and data_version < 3: - for state in BidStates: - in_error = isErrorBidState(state) - swap_failed = isFailingBidState(state) - swap_ended = isFinalBidState(state) - session.execute( - text( - "UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id", - { - "in_error": in_error, - "swap_failed": swap_failed, - "swap_ended": swap_ended, - "state_id": int(state), - }, - ) - ) - if data_version > 0 and data_version < 4: - for state in ( - BidStates.BID_REQUEST_SENT, - BidStates.BID_REQUEST_ACCEPTED, - ): - session.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), - label=strBidState(state), - created_at=now, - ) - ) - - self.db_data_version = CURRENT_DB_DATA_VERSION - self.setIntKV("db_data_version", self.db_data_version, session) - session.commit() - self.log.info( - "Upgraded database records to version {}".format(self.db_data_version) + if data_version < 1: + self.add( + AutomationStrategy( + active_ind=1, + label="Accept All", + type_ind=Concepts.OFFER, + data=json.dumps( + {"exact_rate_only": True, "max_concurrent_bids": 5} + ).encode("utf-8"), + only_known_identities=False, + created_at=now, + ), + cursor, ) - finally: - session.close() - session.remove() + self.add( + AutomationStrategy( + active_ind=1, + label="Accept Known", + type_ind=Concepts.OFFER, + data=json.dumps( + {"exact_rate_only": True, "max_concurrent_bids": 5} + ).encode("utf-8"), + only_known_identities=True, + note="Accept bids from identities with previously successful swaps only", + created_at=now, + ), + cursor, + ) + + for state in BidStates: + 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), + label=strBidState(state), + created_at=now, + ), + cursor, + ) + + if data_version > 0 and data_version < 2: + for state in ( + BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, + BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX, + ): + self.add( + BidState( + active_ind=1, + state_id=int(state), + in_progress=isActiveBidState(state), + label=strBidState(state), + created_at=now, + ), + cursor, + ) + if data_version > 0 and data_version < 3: + for state in BidStates: + in_error = isErrorBidState(state) + swap_failed = isFailingBidState(state) + swap_ended = isFinalBidState(state) + cursor.execute( + "UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id", + { + "in_error": in_error, + "swap_failed": swap_failed, + "swap_ended": swap_ended, + "state_id": int(state), + }, + ) + if data_version > 0 and data_version < 4: + for state in ( + BidStates.BID_REQUEST_SENT, + BidStates.BID_REQUEST_ACCEPTED, + ): + 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), + label=strBidState(state), + created_at=now, + ), + cursor, + ) + + self.db_data_version = CURRENT_DB_DATA_VERSION + self.setIntKV("db_data_version", self.db_data_version, cursor) + self.commitDB() + self.log.info( + "Upgraded database records to version {}".format(self.db_data_version) + ) + finally: + self.closeDB(cursor, commit=False) def upgradeDatabase(self, db_version): @@ -147,312 +146,276 @@ def upgradeDatabase(self, db_version): return self.log.info( - "Upgrading database from version %d to %d.", db_version, CURRENT_DB_VERSION + f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}." ) while True: - session = scoped_session(self.session_factory) + try: + cursor = self.openDB() - current_version = db_version - if current_version == 6: - session.execute(text("ALTER TABLE bids ADD COLUMN security_token BLOB")) - session.execute(text("ALTER TABLE offers ADD COLUMN security_token BLOB")) - db_version += 1 - elif current_version == 7: - session.execute(text("ALTER TABLE transactions ADD COLUMN block_hash BLOB")) - session.execute( - text("ALTER TABLE transactions ADD COLUMN block_height INTEGER") - ) - session.execute( - text("ALTER TABLE transactions ADD COLUMN block_time INTEGER") - ) - db_version += 1 - elif current_version == 8: - session.execute( - text( - """ - CREATE TABLE wallets ( - record_id INTEGER NOT NULL, - coin_id INTEGER, - wallet_name VARCHAR, - wallet_data VARCHAR, - balance_type INTEGER, - created_at BIGINT, - PRIMARY KEY (record_id))""" + current_version = db_version + if current_version == 6: + cursor.execute("ALTER TABLE bids ADD COLUMN security_token BLOB") + cursor.execute("ALTER TABLE offers ADD COLUMN security_token BLOB") + db_version += 1 + elif current_version == 7: + cursor.execute("ALTER TABLE transactions ADD COLUMN block_hash BLOB") + cursor.execute( + "ALTER TABLE transactions ADD COLUMN block_height INTEGER" ) - ) - db_version += 1 - elif current_version == 9: - session.execute(text("ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR")) - db_version += 1 - elif current_version == 10: - session.execute( - text("ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER") - ) - session.execute( - text("ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER") - ) - session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR")) - session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR")) - session.execute( - text("UPDATE smsgaddresses SET active_ind = 1, created_at = 1") - ) - - session.execute(text("ALTER TABLE offers ADD COLUMN addr_to VARCHAR")) - session.execute(text(f'UPDATE offers SET addr_to = "{self.network_addr}"')) - db_version += 1 - elif current_version == 11: - session.execute( - text("ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER") - ) - session.execute( - text("ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER") - ) - session.execute( - text("ALTER TABLE bids ADD COLUMN protocol_version INTEGER") - ) - session.execute( - text("ALTER TABLE offers ADD COLUMN protocol_version INTEGER") - ) - session.execute(text("ALTER TABLE transactions ADD COLUMN tx_data BLOB")) - db_version += 1 - elif current_version == 12: - session.execute( - text( + cursor.execute("ALTER TABLE transactions ADD COLUMN block_time INTEGER") + db_version += 1 + elif current_version == 8: + cursor.execute( """ - CREATE TABLE knownidentities ( - record_id INTEGER NOT NULL, - address VARCHAR, - label VARCHAR, - publickey BLOB, - num_sent_bids_successful INTEGER, - num_recv_bids_successful INTEGER, - num_sent_bids_rejected INTEGER, - num_recv_bids_rejected INTEGER, - num_sent_bids_failed INTEGER, - num_recv_bids_failed INTEGER, - note VARCHAR, - updated_at BIGINT, - created_at BIGINT, - PRIMARY KEY (record_id))""" + CREATE TABLE wallets ( + record_id INTEGER NOT NULL, + coin_id INTEGER, + wallet_name VARCHAR, + wallet_data VARCHAR, + balance_type INTEGER, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - session.execute(text("ALTER TABLE bids ADD COLUMN reject_code INTEGER")) - session.execute(text("ALTER TABLE bids ADD COLUMN rate INTEGER")) - session.execute( - text("ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER") - ) - session.execute( - text("ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER") - ) - db_version += 1 - elif current_version == 13: - db_version += 1 - session.execute( - text( + db_version += 1 + elif current_version == 9: + cursor.execute("ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR") + db_version += 1 + elif current_version == 10: + cursor.execute( + "ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER" + ) + cursor.execute( + "ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER" + ) + cursor.execute("ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR") + cursor.execute("ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR") + cursor.execute( + "UPDATE smsgaddresses SET active_ind = 1, created_at = 1" + ) + + cursor.execute("ALTER TABLE offers ADD COLUMN addr_to VARCHAR") + cursor.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"') + db_version += 1 + elif current_version == 11: + cursor.execute( + "ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER" + ) + cursor.execute( + "ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER" + ) + cursor.execute("ALTER TABLE bids ADD COLUMN protocol_version INTEGER") + cursor.execute("ALTER TABLE offers ADD COLUMN protocol_version INTEGER") + cursor.execute("ALTER TABLE transactions ADD COLUMN tx_data BLOB") + db_version += 1 + elif current_version == 12: + cursor.execute( """ - CREATE TABLE automationstrategies ( - record_id INTEGER NOT NULL, - active_ind INTEGER, - label VARCHAR, - type_ind INTEGER, - only_known_identities INTEGER, - num_concurrent INTEGER, - data BLOB, - - note VARCHAR, - created_at BIGINT, - PRIMARY KEY (record_id))""" + CREATE TABLE knownidentities ( + record_id INTEGER NOT NULL, + address VARCHAR, + label VARCHAR, + publickey BLOB, + num_sent_bids_successful INTEGER, + num_recv_bids_successful INTEGER, + num_sent_bids_rejected INTEGER, + num_recv_bids_rejected INTEGER, + num_sent_bids_failed INTEGER, + num_recv_bids_failed INTEGER, + note VARCHAR, + updated_at BIGINT, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - - session.execute( - text( + cursor.execute("ALTER TABLE bids ADD COLUMN reject_code INTEGER") + cursor.execute("ALTER TABLE bids ADD COLUMN rate INTEGER") + cursor.execute( + "ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER" + ) + cursor.execute("ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER") + db_version += 1 + elif current_version == 13: + db_version += 1 + cursor.execute( """ - CREATE TABLE automationlinks ( - record_id INTEGER NOT NULL, - active_ind INTEGER, + CREATE TABLE automationstrategies ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + label VARCHAR, + type_ind INTEGER, + only_known_identities INTEGER, + num_concurrent INTEGER, + data BLOB, - linked_type INTEGER, - linked_id BLOB, - strategy_id INTEGER, - - data BLOB, - repeat_limit INTEGER, - repeat_count INTEGER, - - note VARCHAR, - created_at BIGINT, - PRIMARY KEY (record_id))""" + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - session.execute( - text( + cursor.execute( """ - CREATE TABLE history ( - record_id INTEGER NOT NULL, - concept_type INTEGER, - concept_id INTEGER, - changed_data BLOB, + CREATE TABLE automationlinks ( + record_id INTEGER NOT NULL, + active_ind INTEGER, - note VARCHAR, - created_at BIGINT, - PRIMARY KEY (record_id))""" + linked_type INTEGER, + linked_id BLOB, + strategy_id INTEGER, + + data BLOB, + repeat_limit INTEGER, + repeat_count INTEGER, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - session.execute( - text( + cursor.execute( """ - CREATE TABLE bidstates ( - record_id INTEGER NOT NULL, - active_ind INTEGER, - state_id INTEGER, - label VARCHAR, - in_progress INTEGER, + CREATE TABLE history ( + record_id INTEGER NOT NULL, + concept_type INTEGER, + concept_id INTEGER, + changed_data BLOB, - note VARCHAR, - created_at BIGINT, - PRIMARY KEY (record_id))""" + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - session.execute(text("ALTER TABLE wallets ADD COLUMN active_ind INTEGER")) - session.execute( - text("ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER") - ) - session.execute(text("ALTER TABLE eventqueue RENAME TO actions")) - session.execute( - text("ALTER TABLE actions RENAME COLUMN event_id TO action_id") - ) - session.execute( - text("ALTER TABLE actions RENAME COLUMN event_type TO action_type") - ) - session.execute( - text("ALTER TABLE actions RENAME COLUMN event_data TO action_data") - ) - elif current_version == 14: - db_version += 1 - session.execute( - text("ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB") - ) - session.execute( - text( + cursor.execute( + """ + CREATE TABLE bidstates ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + state_id INTEGER, + label VARCHAR, + in_progress INTEGER, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))""" + ) + + cursor.execute("ALTER TABLE wallets ADD COLUMN active_ind INTEGER") + cursor.execute( + "ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER" + ) + cursor.execute("ALTER TABLE eventqueue RENAME TO actions") + cursor.execute( + "ALTER TABLE actions RENAME COLUMN event_id TO action_id" + ) + cursor.execute( + "ALTER TABLE actions RENAME COLUMN event_type TO action_type" + ) + cursor.execute( + "ALTER TABLE actions RENAME COLUMN event_data TO action_data" + ) + elif current_version == 14: + db_version += 1 + cursor.execute( + "ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB" + ) + cursor.execute( "ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id" ) - ) - elif current_version == 15: - db_version += 1 - session.execute( - text( + elif current_version == 15: + db_version += 1 + cursor.execute( """ - CREATE TABLE notifications ( - record_id INTEGER NOT NULL, - active_ind INTEGER, - event_type INTEGER, - event_data BLOB, - created_at BIGINT, - PRIMARY KEY (record_id))""" + CREATE TABLE notifications ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + event_type INTEGER, + event_data BLOB, + created_at BIGINT, + PRIMARY KEY (record_id))""" ) - ) - elif current_version == 16: - db_version += 1 - session.execute( - text( + elif current_version == 16: + db_version += 1 + cursor.execute( """ - CREATE TABLE prefunded_transactions ( - record_id INTEGER NOT NULL, - active_ind INTEGER, - created_at BIGINT, - linked_type INTEGER, - linked_id BLOB, - tx_type INTEGER, - tx_data BLOB, - used_by BLOB, - PRIMARY KEY (record_id))""" + CREATE TABLE prefunded_transactions ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + created_at BIGINT, + linked_type INTEGER, + linked_id BLOB, + tx_type INTEGER, + tx_data BLOB, + used_by BLOB, + PRIMARY KEY (record_id))""" ) - ) - elif current_version == 17: - db_version += 1 - session.execute( - text( + elif current_version == 17: + db_version += 1 + cursor.execute( "ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER" ) - ) - session.execute( - text( + cursor.execute( "ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER" ) - ) - session.execute(text("ALTER TABLE knownidentities ADD COLUMN data BLOB")) - session.execute(text("UPDATE knownidentities SET active_ind = 1")) - elif current_version == 18: - db_version += 1 - session.execute( - text("ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING") - ) - session.execute( - text("ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING") - ) - elif current_version == 19: - db_version += 1 - session.execute(text("ALTER TABLE bidstates ADD COLUMN in_error INTEGER")) - session.execute( - text("ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER") - ) - session.execute(text("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER")) - elif current_version == 20: - db_version += 1 - session.execute( - text( + cursor.execute("ALTER TABLE knownidentities ADD COLUMN data BLOB") + cursor.execute("UPDATE knownidentities SET active_ind = 1") + elif current_version == 18: + db_version += 1 + cursor.execute("ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING") + cursor.execute("ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING") + elif current_version == 19: + db_version += 1 + cursor.execute("ALTER TABLE bidstates ADD COLUMN in_error INTEGER") + cursor.execute("ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER") + cursor.execute("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER") + elif current_version == 20: + db_version += 1 + cursor.execute( """ - CREATE TABLE message_links ( - record_id INTEGER NOT NULL, - active_ind INTEGER, - created_at BIGINT, + CREATE TABLE message_links ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + created_at BIGINT, - linked_type INTEGER, - linked_id BLOB, + linked_type INTEGER, + linked_id BLOB, - msg_type INTEGER, - msg_sequence INTEGER, - msg_id BLOB, - PRIMARY KEY (record_id))""" + msg_type INTEGER, + msg_sequence INTEGER, + msg_id BLOB, + PRIMARY KEY (record_id))""" ) - ) - session.execute(text("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER")) - elif current_version == 21: - db_version += 1 - session.execute(text("ALTER TABLE offers ADD COLUMN proof_utxos BLOB")) - session.execute(text("ALTER TABLE bids ADD COLUMN proof_utxos BLOB")) - elif current_version == 22: - db_version += 1 - session.execute(text("ALTER TABLE offers ADD COLUMN amount_to INTEGER")) - elif current_version == 23: - db_version += 1 - session.execute( - text( + cursor.execute("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER") + elif current_version == 21: + db_version += 1 + cursor.execute("ALTER TABLE offers ADD COLUMN proof_utxos BLOB") + cursor.execute("ALTER TABLE bids ADD COLUMN proof_utxos BLOB") + elif current_version == 22: + db_version += 1 + cursor.execute("ALTER TABLE offers ADD COLUMN amount_to INTEGER") + elif current_version == 23: + db_version += 1 + cursor.execute( """ - CREATE TABLE checkedblocks ( - record_id INTEGER NOT NULL, - created_at BIGINT, - coin_type INTEGER, - block_height INTEGER, - block_hash BLOB, - block_time INTEGER, - PRIMARY KEY (record_id))""" + CREATE TABLE checkedblocks ( + record_id INTEGER NOT NULL, + created_at BIGINT, + coin_type INTEGER, + block_height INTEGER, + block_hash BLOB, + block_time INTEGER, + PRIMARY KEY (record_id))""" ) - ) - session.execute(text("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB")) - if current_version != db_version: - self.db_version = db_version - self.setIntKV("db_version", db_version, session) - session.commit() - session.close() - session.remove() - self.log.info("Upgraded database to version {}".format(self.db_version)) - continue + cursor.execute("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB") + if current_version != db_version: + self.db_version = db_version + self.setIntKV("db_version", db_version, cursor) + cursor = self.commitDB() + self.log.info("Upgraded database to version {}".format(self.db_version)) + continue + except Exception as e: + self.log.error("Upgrade failed {}".format(e)) + self.rollbackDB() + finally: + self.closeDB(cursor, commit=False) break if db_version != CURRENT_DB_VERSION: diff --git a/basicswap/db_util.py b/basicswap/db_util.py index 65a491f..f1ac68c 100644 --- a/basicswap/db_util.py +++ b/basicswap/db_util.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2023-2024 The BSX Developers +# Copyright (c) 2023-2024 The Basicswap Developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -from sqlalchemy.sql import text from .db import ( Concepts, ) @@ -13,7 +12,7 @@ from .db import ( def remove_expired_data(self, time_offset: int = 0): now: int = self.getTime() try: - session = self.openSession() + cursor = self.openDB() active_bids_insert = self.activeBidsQueryStr(now, "", "b2") query_str = f""" @@ -22,118 +21,94 @@ def remove_expired_data(self, time_offset: int = 0): """ num_offers = 0 num_bids = 0 - offer_rows = session.execute(text(query_str), {"expired_at": now - time_offset}) + offer_rows = cursor.execute(query_str, {"expired_at": now - time_offset}) for offer_row in offer_rows: num_offers += 1 - bid_rows = session.execute( - text("SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id"), + bid_rows = cursor.execute( + "SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id", {"offer_id": offer_row[0]}, ) for bid_row in bid_rows: num_bids += 1 - session.execute( - text( - "DELETE FROM transactions WHERE transactions.bid_id = :bid_id" - ), + cursor.execute( + "DELETE FROM transactions WHERE transactions.bid_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id" - ), + cursor.execute( + "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id", {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id" - ), + cursor.execute( + "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id", {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id" - ), + cursor.execute( + "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id", {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id" - ), + cursor.execute( + "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id", {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, ) - session.execute( - text("DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id"), + cursor.execute( + "DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text("DELETE FROM actions WHERE actions.linked_id = :bid_id"), + cursor.execute( + "DELETE FROM actions WHERE actions.linked_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text("DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id"), + cursor.execute( + "DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id" - ), + cursor.execute( + "DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text("DELETE FROM bids WHERE bids.bid_id = :bid_id"), + cursor.execute( + "DELETE FROM bids WHERE bids.bid_id = :bid_id", {"bid_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id" - ), + cursor.execute( + "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id", {"type_ind": int(Concepts.BID), "linked_id": bid_row[0]}, ) - session.execute( - text( - "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id" - ), + cursor.execute( + "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id", {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, ) - session.execute( - text( - "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id" - ), + cursor.execute( + "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id", {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, ) - session.execute( - text( - "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id" - ), + cursor.execute( + "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id", {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, ) - session.execute( - text( - "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id" - ), + cursor.execute( + "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id", {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, ) - session.execute( - text("DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id"), + cursor.execute( + "DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id", {"offer_id": offer_row[0]}, ) - session.execute( - text("DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id"), + cursor.execute( + "DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id", {"offer_id": offer_row[0]}, ) - session.execute( - text("DELETE FROM actions WHERE actions.linked_id = :offer_id"), + cursor.execute( + "DELETE FROM actions WHERE actions.linked_id = :offer_id", {"offer_id": offer_row[0]}, ) - session.execute( - text("DELETE FROM offers WHERE offers.offer_id = :offer_id"), + cursor.execute( + "DELETE FROM offers WHERE offers.offer_id = :offer_id", {"offer_id": offer_row[0]}, ) - session.execute( - text( - "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id" - ), + cursor.execute( + "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id", {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, ) @@ -147,10 +122,10 @@ def remove_expired_data(self, time_offset: int = 0): ) ) - session.execute( - text("DELETE FROM checkedblocks WHERE created_at <= :expired_at"), + cursor.execute( + "DELETE FROM checkedblocks WHERE created_at <= :expired_at", {"expired_at": now - time_offset}, ) finally: - self.closeSession(session) + self.closeDB(cursor) diff --git a/basicswap/interface/bch.py b/basicswap/interface/bch.py index 37af903..29e2e86 100644 --- a/basicswap/interface/bch.py +++ b/basicswap/interface/bch.py @@ -612,11 +612,11 @@ class BCHInterface(BTCInterface): prevout_script: bytes, prevout_value: int, ) -> bool: - # simple ecdsa signature verification + # Simple ecdsa signature verification return self.verifyDataSig(tx_bytes, sig, K) def verifyDataSig(self, data: bytes, sig: bytes, K: bytes) -> bool: - # simple ecdsa signature verification + # Simple ecdsa signature verification pubkey = PublicKey(K) return pubkey.verify(sig, sha256(data), hasher=None) @@ -637,7 +637,7 @@ class BCHInterface(BTCInterface): return signature, mining_fee, out_1, out_2, public_key, timelock def extractScriptLockScriptValues(self, script_bytes): - # see BCHInterface.genScriptLockTxScript for reference + # See BCHInterface.genScriptLockTxScript for reference o = 0 @@ -1071,6 +1071,9 @@ class BCHInterface(BTCInterface): ) return out_1 + def lockNonSegwitPrevouts(self) -> None: + pass + def createMercyTx( self, refund_swipe_tx_bytes: bytes, diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 2e7e263..3bd7b6c 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -1158,7 +1158,8 @@ class BTCInterface(Secp256k1Interface): def fundTx(self, tx: bytes, feerate) -> bytes: feerate_str = self.format_amount(feerate) - # TODO: unlock unspents if bid cancelled + # TODO: Unlock unspents if bid cancelled + # TODO: Manually select only segwit prevouts options = { "lockUnspents": True, "feeRate": feerate_str, @@ -1166,6 +1167,27 @@ class BTCInterface(Secp256k1Interface): rv = self.rpc_wallet("fundrawtransaction", [tx.hex(), options]) return bytes.fromhex(rv["hex"]) + def lockNonSegwitPrevouts(self) -> None: + # For tests + unspent = self.rpc_wallet("listunspent") + + to_lock = [] + for u in unspent: + if u.get("spendable", False) is False: + continue + if "desc" in u: + desc = u["desc"] + if self.use_p2shp2wsh(): + if not desc.startswith("sh(wpkh"): + to_lock.append({"txid": u["txid"], "vout": u["vout"]}) + else: + if not desc.startswith("wpkh"): + to_lock.append({"txid": u["txid"], "vout": u["vout"]}) + + if len(to_lock) > 0: + self._log.debug(f"Locking {len(to_lock)} non segwit prevouts") + self.rpc_wallet("lockunspent", [False, to_lock]) + def listInputs(self, tx_bytes: bytes): tx = self.loadTx(tx_bytes) diff --git a/basicswap/protocols/atomic_swap_1.py b/basicswap/protocols/atomic_swap_1.py index d825728..5d0d82b 100644 --- a/basicswap/protocols/atomic_swap_1.py +++ b/basicswap/protocols/atomic_swap_1.py @@ -119,12 +119,12 @@ def extractScriptSecretHash(script): return script[7:39] -def redeemITx(self, bid_id: bytes, session): - bid, offer = self.getBidAndOffer(bid_id, session) +def redeemITx(self, bid_id: bytes, cursor): + bid, offer = self.getBidAndOffer(bid_id, cursor) ci_from = self.ci(offer.coin_from) txn = self.createRedeemTxn( - ci_from.coin_type(), bid, for_txn_type="initiate", session=session + ci_from.coin_type(), bid, for_txn_type="initiate", cursor=cursor ) txid = ci_from.publishTx(bytes.fromhex(txn)) @@ -135,7 +135,7 @@ def redeemITx(self, bid_id: bytes, session): ci_from.coin_name(), bid_id.hex(), ) - self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", session) + self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", cursor) class AtomicSwapInterface(ProtocolInterface): diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index 9ddb270..9620346 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -42,17 +42,15 @@ def addLockRefundSigs(self, xmr_swap, ci): xmr_swap.a_lock_refund_tx = signed_tx -def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): +def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None): self.log.info(f"Manually recovering {bid_id.hex()}") # Manually recover txn if other key is known try: - use_session = self.openSession(session) - bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) + use_cursor = self.openDB(cursor) + bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id) ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession( - use_session, bid.offer_id, sent=False - ) + offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id) ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) @@ -97,10 +95,10 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): raise ValueError(err_msg) if ci_follower.coin_type() in (Coins.XMR, Coins.WOW): - address_to = self.getCachedMainWalletAddress(ci_follower, use_session) + address_to = self.getCachedMainWalletAddress(ci_follower, use_cursor) else: address_to = self.getCachedStealthAddressForCoin( - ci_follower.coin_type(), use_session + ci_follower.coin_type(), use_cursor ) amount = bid.amount_to lock_tx_vout = bid.getLockTXBVout() @@ -125,17 +123,17 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), - use_session, + use_cursor, ) - use_session.commit() + self.commitDB() return txid except Exception as e: self.log.error(traceback.format_exc()) raise (e) finally: - if session is None: - self.closeSession(use_session, commit=False) + if cursor is None: + self.closeDB(use_cursor, commit=False) def getChainBSplitKey(swap_client, bid, xmr_swap, offer): diff --git a/basicswap/ui/page_offers.py b/basicswap/ui/page_offers.py index 9119cfc..0489ba7 100644 --- a/basicswap/ui/page_offers.py +++ b/basicswap/ui/page_offers.py @@ -898,7 +898,7 @@ def page_offers(self, url_split, post_string, sent=False): sent = True else: sent = False - offers = swap_client.listOffers(sent, filters, with_bid_info=True) + offers = swap_client.listOffers(sent, filters) now: int = swap_client.getTime() formatted_offers = [] @@ -906,7 +906,7 @@ def page_offers(self, url_split, post_string, sent=False): tla_to = "" for row in offers: - o, completed_amount = row + o = row ci_from = swap_client.ci(Coins(o.coin_from)) ci_to = swap_client.ci(Coins(o.coin_to)) is_expired = o.expire_at <= now @@ -932,7 +932,7 @@ def page_offers(self, url_split, post_string, sent=False): "Public" if o.addr_to == swap_client.network_addr else o.addr_to, o.addr_from, o.was_sent, - ci_from.format_amount(completed_amount), + None, # placeholder, was completed_amount is_expired, o.active_ind, formatted_expired_at, diff --git a/doc/release-notes.md b/doc/release-notes.md index cc2cead..cf230c1 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -5,6 +5,7 @@ - BCH support. - Mercy outputs on swipe txns of BTC descended coins. - Disable by setting 'altruistic' to false in basicswap.json +- Removed sqlalchemy. diff --git a/guix.scm b/guix.scm index 53c4b8a..db30302 100644 --- a/guix.scm +++ b/guix.scm @@ -111,17 +111,6 @@ (home-page "https://github.com/basicswap/coincurve") (license license:bsd-3))) -(define python-sqlalchemy-1.4.39 - (package - (inherit python-sqlalchemy) - (version "1.4.39") - (source - (origin - (method url-fetch) - (uri (pypi-uri "SQLAlchemy" version)) - (sha256 - (base32 "09sx2lghywnm7qj1xm8xc3xrgj40bndfh2hbiaq4cfvm71h8k541")))))) - (define-public basicswap (package (name "basicswap") @@ -153,7 +142,6 @@ python-coincurve-basicswap python-pycryptodome python-pytest - python-sqlalchemy python-pyzmq python-gnupg python-jinja2 diff --git a/requirements.in b/requirements.in index bb5f020..484447b 100644 --- a/requirements.in +++ b/requirements.in @@ -1,5 +1,4 @@ pyzmq==26.2.0 -SQLAlchemy==2.0.35 python-gnupg==0.5.3 Jinja2==3.1.4 pycryptodome==3.21.0 diff --git a/requirements.txt b/requirements.txt index 89ff65e..ba7eb40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -80,147 +80,72 @@ cffi==1.17.1 \ coincurve @ https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.2.zip \ --hash=sha256:c309deef22c929c9ab5b3adf7adbda940bffcea6c6ec7c66202d6c3d4e3ceb79 # via -r requirements.in -greenlet==3.1.1 \ - --hash=sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e \ - --hash=sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7 \ - --hash=sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01 \ - --hash=sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1 \ - --hash=sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159 \ - --hash=sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563 \ - --hash=sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83 \ - --hash=sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9 \ - --hash=sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395 \ - --hash=sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa \ - --hash=sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942 \ - --hash=sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1 \ - --hash=sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441 \ - --hash=sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22 \ - --hash=sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9 \ - --hash=sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0 \ - --hash=sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba \ - --hash=sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3 \ - --hash=sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1 \ - --hash=sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6 \ - --hash=sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291 \ - --hash=sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39 \ - --hash=sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d \ - --hash=sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467 \ - --hash=sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475 \ - --hash=sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef \ - --hash=sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c \ - --hash=sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511 \ - --hash=sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c \ - --hash=sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822 \ - --hash=sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a \ - --hash=sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8 \ - --hash=sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d \ - --hash=sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01 \ - --hash=sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145 \ - --hash=sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80 \ - --hash=sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13 \ - --hash=sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e \ - --hash=sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b \ - --hash=sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1 \ - --hash=sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef \ - --hash=sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc \ - --hash=sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff \ - --hash=sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120 \ - --hash=sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437 \ - --hash=sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd \ - --hash=sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981 \ - --hash=sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36 \ - --hash=sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a \ - --hash=sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798 \ - --hash=sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7 \ - --hash=sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761 \ - --hash=sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0 \ - --hash=sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e \ - --hash=sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af \ - --hash=sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa \ - --hash=sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c \ - --hash=sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42 \ - --hash=sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e \ - --hash=sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81 \ - --hash=sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e \ - --hash=sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617 \ - --hash=sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc \ - --hash=sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de \ - --hash=sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111 \ - --hash=sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383 \ - --hash=sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70 \ - --hash=sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6 \ - --hash=sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4 \ - --hash=sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011 \ - --hash=sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803 \ - --hash=sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79 \ - --hash=sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f - # via sqlalchemy jinja2==3.1.4 \ --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via -r requirements.in -markupsafe==3.0.1 \ - --hash=sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396 \ - --hash=sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38 \ - --hash=sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a \ - --hash=sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8 \ - --hash=sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b \ - --hash=sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad \ - --hash=sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a \ - --hash=sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a \ - --hash=sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da \ - --hash=sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6 \ - --hash=sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8 \ - --hash=sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344 \ - --hash=sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a \ - --hash=sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8 \ - --hash=sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5 \ - --hash=sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7 \ - --hash=sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170 \ - --hash=sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132 \ - --hash=sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9 \ - --hash=sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd \ - --hash=sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9 \ - --hash=sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346 \ - --hash=sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc \ - --hash=sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589 \ - --hash=sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5 \ - --hash=sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915 \ - --hash=sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295 \ - --hash=sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453 \ - --hash=sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea \ - --hash=sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b \ - --hash=sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d \ - --hash=sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b \ - --hash=sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4 \ - --hash=sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b \ - --hash=sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7 \ - --hash=sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf \ - --hash=sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f \ - --hash=sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91 \ - --hash=sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd \ - --hash=sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50 \ - --hash=sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b \ - --hash=sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583 \ - --hash=sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a \ - --hash=sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984 \ - --hash=sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c \ - --hash=sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c \ - --hash=sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25 \ - --hash=sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa \ - --hash=sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4 \ - --hash=sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3 \ - --hash=sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97 \ - --hash=sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1 \ - --hash=sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd \ - --hash=sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772 \ - --hash=sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a \ - --hash=sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729 \ - --hash=sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca \ - --hash=sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6 \ - --hash=sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635 \ - --hash=sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b \ - --hash=sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 # via jinja2 pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ @@ -380,58 +305,3 @@ pyzmq==26.2.0 \ --hash=sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6 \ --hash=sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919 # via -r requirements.in -sqlalchemy==2.0.35 \ - --hash=sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9 \ - --hash=sha256:032d979ce77a6c2432653322ba4cbeabf5a6837f704d16fa38b5a05d8e21fa00 \ - --hash=sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee \ - --hash=sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6 \ - --hash=sha256:05c3f58cf91683102f2f0265c0db3bd3892e9eedabe059720492dbaa4f922da1 \ - --hash=sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72 \ - --hash=sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf \ - --hash=sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8 \ - --hash=sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b \ - --hash=sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc \ - --hash=sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c \ - --hash=sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1 \ - --hash=sha256:2e795c2f7d7249b75bb5f479b432a51b59041580d20599d4e112b5f2046437a3 \ - --hash=sha256:3655af10ebcc0f1e4e06c5900bb33e080d6a1fa4228f502121f28a3b1753cde5 \ - --hash=sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90 \ - --hash=sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec \ - --hash=sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71 \ - --hash=sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7 \ - --hash=sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b \ - --hash=sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468 \ - --hash=sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3 \ - --hash=sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e \ - --hash=sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139 \ - --hash=sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff \ - --hash=sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11 \ - --hash=sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01 \ - --hash=sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62 \ - --hash=sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d \ - --hash=sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a \ - --hash=sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db \ - --hash=sha256:9509c4123491d0e63fb5e16199e09f8e262066e58903e84615c301dde8fa2e87 \ - --hash=sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e \ - --hash=sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1 \ - --hash=sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9 \ - --hash=sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f \ - --hash=sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0 \ - --hash=sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44 \ - --hash=sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936 \ - --hash=sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8 \ - --hash=sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea \ - --hash=sha256:cc32b2990fc34380ec2f6195f33a76b6cdaa9eecf09f0c9404b74fc120aef36f \ - --hash=sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4 \ - --hash=sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0 \ - --hash=sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c \ - --hash=sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f \ - --hash=sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60 \ - --hash=sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2 \ - --hash=sha256:f021d334f2ca692523aaf7bbf7592ceff70c8594fad853416a81d66b35e3abf9 \ - --hash=sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33 - # via -r requirements.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via sqlalchemy diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index ecb1f7a..45efedf 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -1414,6 +1414,16 @@ class BasicSwapTest(TestFunctions): ) assert len(tx_wallet["blockhash"]) == 64 + def test_01_0_lock_bad_prevouts(self): + logging.info( + "---------- Test {} lock_bad_prevouts".format(self.test_coin_from.name) + ) + # Lock non segwit prevouts created in earlier tests + for i in range(2): + ci = self.swap_clients[i].ci(self.test_coin_from) + if hasattr(ci, "lockNonSegwitPrevouts"): + ci.lockNonSegwitPrevouts() + def test_01_a_full_swap(self): if not self.has_segwit: return