Let SQLite handle all query parameters.
Some checks failed
lint / build (3.12) (push) Has been cancelled

This commit is contained in:
tecnovert 2024-11-20 22:26:35 +02:00
parent fa8764342e
commit bd571702cb
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
5 changed files with 223 additions and 154 deletions

View file

@ -93,6 +93,7 @@ from .db import (
CURRENT_DB_VERSION, CURRENT_DB_VERSION,
EventLog, EventLog,
firstOrNone, firstOrNone,
getOrderByStr,
KnownIdentity, KnownIdentity,
MessageLink, MessageLink,
Notification, Notification,
@ -936,13 +937,10 @@ class BasicSwap(BaseApp):
def start(self): def start(self):
import platform import platform
self.log.info( self.log.info(f"Starting BasicSwap {__version__}, database v{self.db_version}\n\n")
"Starting BasicSwap %s, database v%d\n\n", __version__, self.db_version
)
self.log.info(f"Python version: {platform.python_version()}") self.log.info(f"Python version: {platform.python_version()}")
self.log.info(f"SQLite version: {sqlite3.sqlite_version}") self.log.info(f"SQLite version: {sqlite3.sqlite_version}")
self.log.info(f"Timezone offset: {time.timezone} ({time.tzname[0]})")
self.log.info("Timezone offset: %d (%s)", time.timezone, time.tzname[0])
upgradeDatabase(self, self.db_version) upgradeDatabase(self, self.db_version)
upgradeDatabaseData(self, self.db_data_version) upgradeDatabaseData(self, self.db_data_version)
@ -1322,8 +1320,8 @@ class BasicSwap(BaseApp):
coin_id = int(coin_type) coin_id = int(coin_type)
info_type = 1 # wallet info_type = 1 # wallet
query_str = f"DELETE FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type}" query_str = "DELETE FROM wallets WHERE coin_id = ? AND balance_type = ?"
cursor.execute(query_str) cursor.execute(query_str, (coin_id, info_type))
finally: finally:
self.closeDB(cursor) self.closeDB(cursor)
@ -1693,7 +1691,8 @@ class BasicSwap(BaseApp):
) )
use_cursor.execute( 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})" "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: if show_event:
@ -1709,7 +1708,8 @@ class BasicSwap(BaseApp):
def buildNotificationsCache(self, cursor): def buildNotificationsCache(self, cursor):
self._notifications_cache.clear() self._notifications_cache.clear()
q = cursor.execute( 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}" "SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT ?",
(self._keep_notifications,),
) )
for entry in q: for entry in q:
self._notifications_cache[entry[0]] = ( self._notifications_cache[entry[0]] = (
@ -1810,30 +1810,32 @@ class BasicSwap(BaseApp):
try: try:
cursor = self.openDB() cursor = self.openDB()
query_str = ( query_str: str = (
"SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, " "SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, "
+ " num_sent_bids_rejected, num_recv_bids_rejected, num_sent_bids_failed, num_recv_bids_failed, " + " num_sent_bids_rejected, num_recv_bids_rejected, num_sent_bids_failed, num_recv_bids_failed, "
+ " automation_override, visibility_override, note " + " automation_override, visibility_override, note "
+ " FROM knownidentities " + " FROM knownidentities "
+ " WHERE active_ind = 1 " + " WHERE active_ind = 1 "
) )
query_data: dict = {}
address = filters.get("address", None) address = filters.get("address", None)
if address is not None: if address is not None:
query_str += f' AND address = "{address}" ' query_str += " AND address = :address "
query_data["address"] = address
sort_dir = filters.get("sort_dir", "DESC").upper() query_str += getOrderByStr(filters)
sort_by = filters.get("sort_by", "created_at")
query_str += f" ORDER BY {sort_by} {sort_dir}"
limit = filters.get("limit", None) limit = filters.get("limit", None)
if limit is not None: if limit is not None:
query_str += f" LIMIT {limit}" query_str += " LIMIT :limit"
query_data["limit"] = limit
offset = filters.get("offset", None) offset = filters.get("offset", None)
if offset is not None: if offset is not None:
query_str += f" OFFSET {offset}" query_str += " OFFSET :offset"
query_data["offset"] = offset
q = cursor.execute(query_str) q = cursor.execute(query_str, query_data)
rv = [] rv = []
for row in q: for row in q:
identity = { identity = {
@ -2821,9 +2823,12 @@ class BasicSwap(BaseApp):
def countBidEvents(self, bid, event_type, cursor): def countBidEvents(self, bid, event_type, cursor):
q = cursor.execute( q = cursor.execute(
"SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x'{}' AND event_type = {}".format( "SELECT COUNT(*) FROM eventlog WHERE linked_type = :linked_type AND linked_id = :linked_id AND event_type = :event_type",
int(Concepts.BID), bid.bid_id.hex(), int(event_type) {
) "linked_type": int(Concepts.BID),
"linked_id": bid.bid_id,
"event_type": int(event_type),
},
).fetchone() ).fetchone()
return q[0] return q[0]
@ -3212,20 +3217,20 @@ class BasicSwap(BaseApp):
def list_bid_events(self, bid_id: bytes, cursor): def list_bid_events(self, bid_id: bytes, cursor):
query_str = ( query_str = (
"SELECT created_at, event_type, event_msg FROM eventlog " "SELECT created_at, event_type, event_msg FROM eventlog "
+ "WHERE active_ind = 1 AND linked_type = {} AND linked_id = x'{}' ".format( + "WHERE active_ind = 1 AND linked_type = :linked_type AND linked_id = :linked_id "
Concepts.BID, bid_id.hex()
) )
q = cursor.execute(
query_str, {"linked_type": int(Concepts.BID), "linked_id": bid_id}
) )
q = cursor.execute(query_str)
events = [] events = []
for row in q: for row in q:
events.append({"at": row[0], "desc": describeEventEntry(row[1], row[2])}) events.append({"at": row[0], "desc": describeEventEntry(row[1], row[2])})
query_str = ( query_str = (
"SELECT created_at, trigger_at FROM actions " "SELECT created_at, trigger_at FROM actions "
+ "WHERE active_ind = 1 AND linked_id = x'{}' ".format(bid_id.hex()) + "WHERE active_ind = 1 AND linked_id = :linked_id "
) )
q = cursor.execute(query_str) q = cursor.execute(query_str, {"linked_id": bid_id})
for row in q: for row in q:
events.append( events.append(
{ {
@ -7183,21 +7188,23 @@ class BasicSwap(BaseApp):
def getCompletedAndActiveBidsValue(self, offer, cursor): def getCompletedAndActiveBidsValue(self, offer, cursor):
bids = [] bids = []
total_value = 0 total_value: int = 0
q = cursor.execute( q = cursor.execute(
"""SELECT bid_id, amount, state FROM bids """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) JOIN bidstates ON bidstates.state_id = bids.state AND (bidstates.state_id = :state_id OR bidstates.in_progress > 0)
WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' WHERE bids.active_ind = 1 AND bids.offer_id = :offer_id
UNION UNION
SELECT bid_id, amount, state FROM bids SELECT bid_id, amount, state FROM bids
JOIN actions ON actions.linked_id = bids.bid_id AND actions.active_ind = 1 AND (actions.action_type = {2} OR actions.action_type = {3}) JOIN actions ON actions.linked_id = bids.bid_id AND actions.active_ind = 1 AND (actions.action_type = :action_type_acc_bid OR actions.action_type = :action_type_acc_adp_bid)
WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' WHERE bids.active_ind = 1 AND bids.offer_id = :offer_id
""".format( """,
offer.offer_id.hex(), {
BidStates.SWAP_COMPLETED, "state_id": int(BidStates.SWAP_COMPLETED),
ActionTypes.ACCEPT_XMR_BID, "offer_id": offer.offer_id,
ActionTypes.ACCEPT_BID, "action_type_acc_bid": int(ActionTypes.ACCEPT_BID),
) "action_type_acc_adp_bid": int(ActionTypes.ACCEPT_XMR_BID),
},
) )
for row in q: for row in q:
bid_id, amount, state = row bid_id, amount, state = row
@ -9214,9 +9221,12 @@ class BasicSwap(BaseApp):
cursor = self.openDB() cursor = self.openDB()
try: try:
q = cursor.execute( q = cursor.execute(
"SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {} AND msg_sequence = {}".format( "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = :bid_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence",
msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence {
) "bid_id": msg_data.msg_id,
"msg_type": msg_data.msg_type,
"msg_sequence": msg_data.sequence,
},
).fetchone() ).fetchone()
num_exists = q[0] num_exists = q[0]
if num_exists > 0: if num_exists > 0:
@ -10120,37 +10130,39 @@ class BasicSwap(BaseApp):
num_watched_outputs += len(v["watched_outputs"]) num_watched_outputs += len(v["watched_outputs"])
now: int = self.getTime() now: int = self.getTime()
q_bids_str = """SELECT q_bids_str: str = (
"""SELECT
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > {} AND o.expire_at > {})) THEN 1 ELSE NULL END) AS count_sent_active, COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received, COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
COUNT(CASE WHEN b.was_received AND b.state = {} AND b.expire_at > {} AND o.expire_at > {} THEN 1 ELSE NULL END) AS count_available, COUNT(CASE WHEN b.was_received AND b.state = :received_state AND b.expire_at > :now AND o.expire_at > :now THEN 1 ELSE NULL END) AS count_available,
COUNT(CASE WHEN b.was_received AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > {} AND o.expire_at > {})) THEN 1 ELSE NULL END) AS count_recv_active COUNT(CASE WHEN b.was_received AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_recv_active
FROM bids b FROM bids b
JOIN offers o ON b.offer_id = o.offer_id JOIN offers o ON b.offer_id = o.offer_id
JOIN bidstates s ON b.state = s.state_id JOIN bidstates s ON b.state = s.state_id
WHERE b.active_ind = 1""".format( WHERE b.active_ind = 1"""
now, now, BidStates.BID_RECEIVED, now, now, now, now
) )
q_offers_str = """SELECT q_offers_str: str = (
COUNT(CASE WHEN expire_at > {} THEN 1 ELSE NULL END) AS count_active, """SELECT
COUNT(CASE WHEN expire_at > :now THEN 1 ELSE NULL END) AS count_active,
COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent,
COUNT(CASE WHEN was_sent AND expire_at > {} THEN 1 ELSE NULL END) AS count_sent_active COUNT(CASE WHEN was_sent AND expire_at > :now THEN 1 ELSE NULL END) AS count_sent_active
FROM offers WHERE active_ind = 1""".format( FROM offers WHERE active_ind = 1"""
now, now
) )
try: try:
cursor = self.openDB() cursor = self.openDB()
q = cursor.execute(q_bids_str).fetchone() q = cursor.execute(
q_bids_str, {"now": now, "received_state": int(BidStates.BID_RECEIVED)}
).fetchone()
bids_sent = q[0] bids_sent = q[0]
bids_sent_active = q[1] bids_sent_active = q[1]
bids_received = q[2] bids_received = q[2]
bids_available = q[3] bids_available = q[3]
bids_recv_active = q[4] bids_recv_active = q[4]
q = cursor.execute(q_offers_str).fetchone() q = cursor.execute(q_offers_str, {"now": now}).fetchone()
num_offers = q[0] num_offers = q[0]
num_sent_offers = q[1] num_sent_offers = q[1]
num_sent_active_offers = q[2] num_sent_active_offers = q[2]
@ -10257,8 +10269,8 @@ class BasicSwap(BaseApp):
), ),
cursor, 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 )" query_str = "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 )"
cursor.execute(query_str) cursor.execute(query_str, {"coin_id": coin_id, "info_type": info_type})
self.commitDB() self.commitDB()
except Exception as e: except Exception as e:
self.log.error(f"addWalletInfoRecord {e}") self.log.error(f"addWalletInfoRecord {e}")
@ -10326,15 +10338,15 @@ class BasicSwap(BaseApp):
rv = {} rv = {}
try: try:
cursor = self.openDB() cursor = self.openDB()
query_data: dict = {}
where_str = "" where_str = ""
if opts is not None and "coin_id" in opts: if opts is not None and "coin_id" in opts:
where_str = "WHERE coin_id = {}".format(opts["coin_id"]) where_str = "WHERE coin_id = :coin_id"
query_data["coin_id"] = opts["coin_id"]
inner_str = f"SELECT coin_id, balance_type, MAX(created_at) as max_created_at FROM wallets {where_str} GROUP BY coin_id, balance_type" inner_str = f"SELECT coin_id, balance_type, MAX(created_at) as max_created_at FROM wallets {where_str} GROUP BY coin_id, balance_type"
query_str = "SELECT a.coin_id, a.balance_type, wallet_data, created_at FROM wallets a, ({}) b WHERE a.coin_id = b.coin_id AND a.balance_type = b.balance_type AND a.created_at = b.max_created_at".format( query_str = f"SELECT a.coin_id, a.balance_type, wallet_data, created_at FROM wallets a, ({inner_str}) b WHERE a.coin_id = b.coin_id AND a.balance_type = b.balance_type AND a.created_at = b.max_created_at"
inner_str
)
q = cursor.execute(query_str) q = cursor.execute(query_str, query_data)
for row in q: for row in q:
coin_id = row[0] coin_id = row[0]
@ -10350,10 +10362,10 @@ class BasicSwap(BaseApp):
) )
# Ensure the latest addresses are displayed # Ensure the latest addresses are displayed
coin_name: str = chainparams[coin_id]["name"]
q = cursor.execute( q = cursor.execute(
'SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format( "SELECT key, value FROM kv_string WHERE key = ? OR key = ?",
chainparams[coin_id]["name"] (f"receive_addr_{coin_name}", f"stealth_addr_{coin_name}"),
)
) )
for row in q: for row in q:
if row[0].startswith("stealth"): if row[0].startswith("stealth"):
@ -10388,18 +10400,13 @@ class BasicSwap(BaseApp):
def countAcceptedBids(self, offer_id: bytes = None) -> int: def countAcceptedBids(self, offer_id: bytes = None) -> int:
cursor = self.openDB() cursor = self.openDB()
try: try:
query: str = "SELECT COUNT(*) FROM bids WHERE state >= :state_ind"
query_data: dict = {"state_ind": int(BidStates.BID_ACCEPTED)}
if offer_id: if offer_id:
q = cursor.execute( query += " AND offer_id = :offer_id"
"SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x'{}'".format( query_data["offer_id":offer_id]
BidStates.BID_ACCEPTED, offer_id.hex()
) q = cursor.execute(query, query_data).fetchone()
).fetchone()
else:
q = cursor.execute(
"SELECT COUNT(*) FROM bids WHERE state >= {}".format(
BidStates.BID_ACCEPTED
)
).fetchone()
return q[0] return q[0]
finally: finally:
self.closeDB(cursor, commit=False) self.closeDB(cursor, commit=False)
@ -10410,47 +10417,53 @@ class BasicSwap(BaseApp):
rv = [] rv = []
now: int = self.getTime() now: int = self.getTime()
query_suffix = "" query_suffix: str = ""
query_data: dict = {"now": now}
if sent: if sent:
query_suffix += " AND was_sent = 1" query_suffix += " AND was_sent = 1"
active_state = filters.get("active", "any") active_state = filters.get("active", "any")
if active_state == "active": if active_state == "active":
query_suffix += f" AND (expire_at > {now} AND active_ind = 1)" query_suffix += " AND (expire_at > :now AND active_ind = 1)"
elif active_state == "expired": elif active_state == "expired":
query_suffix += f" AND expire_at <= {now}" query_suffix += " AND expire_at <= :now"
elif active_state == "revoked": elif active_state == "revoked":
query_suffix += " AND active_ind != 1" query_suffix += " AND active_ind != 1"
else: else:
query_suffix += f" AND (expire_at > {now} AND active_ind = 1)" query_suffix += " AND (expire_at > :now AND active_ind = 1)"
filter_offer_id = filters.get("offer_id", None) filter_offer_id = filters.get("offer_id", None)
if filter_offer_id is not None: if filter_offer_id is not None:
query_suffix += f" AND offer_id = x'{filter_offer_id.hex()}'" query_suffix += " AND offer_id = :filter_offer_id"
query_data["filter_offer_id"] = filter_offer_id
filter_coin_from = filters.get("coin_from", None) filter_coin_from = filters.get("coin_from", None)
if filter_coin_from and filter_coin_from > -1: if filter_coin_from and filter_coin_from > -1:
query_suffix += f" AND coin_from = {int(filter_coin_from)}" query_suffix += " AND coin_from = :filter_coin_from"
query_data["filter_coin_from"] = int(filter_coin_from)
filter_coin_to = filters.get("coin_to", None) filter_coin_to = filters.get("coin_to", None)
if filter_coin_to and filter_coin_to > -1: if filter_coin_to and filter_coin_to > -1:
query_suffix += f" AND coin_to = {int(filter_coin_to)}" query_suffix += " AND coin_to = :filter_coin_to"
query_data["filter_coin_to"] = int(filter_coin_to)
filter_include_sent = filters.get("include_sent", None) filter_include_sent = filters.get("include_sent", None)
if filter_include_sent is not None and filter_include_sent is not True: if filter_include_sent is not None and filter_include_sent is not True:
query_suffix += " AND was_sent = 0" query_suffix += " AND was_sent = 0"
order_dir = filters.get("sort_dir", "desc") query_suffix += getOrderByStr(filters)
order_by = filters.get("sort_by", "created_at")
query_suffix += f" ORDER BY {order_by} {order_dir.upper()}"
limit = filters.get("limit", None) limit = filters.get("limit", None)
if limit is not None: if limit is not None:
query_suffix += f" LIMIT {limit}" query_suffix += " LIMIT :limit"
query_data["limit"] = limit
offset = filters.get("offset", None) offset = filters.get("offset", None)
if offset is not None: if offset is not None:
query_suffix += f" OFFSET {offset}" query_suffix += " OFFSET :offset"
query_data["offset"] = offset
q = self.query(Offer, cursor, query_suffix=query_suffix) q = self.query(
Offer, cursor, query_suffix=query_suffix, extra_query_data=query_data
)
for row in q: for row in q:
offer = row offer = row
# Show offers for enabled coins only # Show offers for enabled coins only
@ -10465,14 +10478,14 @@ class BasicSwap(BaseApp):
self.closeDB(cursor, commit=False) self.closeDB(cursor, commit=False)
def activeBidsQueryStr( def activeBidsQueryStr(
self, now: int, offer_table: str = "offers", bids_table: str = "bids" self, offer_table: str = "offers", bids_table: str = "bids"
) -> str: ) -> str:
offers_inset = ( offers_inset: str = ""
f" AND {offer_table}.expire_at > {now}" if offer_table != "" else "" if offer_table != "":
) offers_inset = f" AND {offer_table}.expire_at > :now"
inactive_states_str = ", ".join([str(int(s)) for s in inactive_states]) inactive_states_str = ", ".join([str(int(s)) for s in inactive_states])
return f" ({bids_table}.state NOT IN ({inactive_states_str}) AND ({bids_table}.state > {BidStates.BID_RECEIVED} OR ({bids_table}.expire_at > {now}{offers_inset}))) " return f" ({bids_table}.state NOT IN ({inactive_states_str}) AND ({bids_table}.state > {BidStates.BID_RECEIVED} OR ({bids_table}.expire_at > :now{offers_inset}))) "
def listBids( def listBids(
self, self,
@ -10486,7 +10499,15 @@ class BasicSwap(BaseApp):
rv = [] rv = []
now: int = self.getTime() now: int = self.getTime()
query_str = ( query_data: dict = {
"now": now,
"ads_swap": SwapTypes.XMR_SWAP,
"itx_type": TxTypes.ITX,
"ptx_type": TxTypes.PTX,
"al_type": TxTypes.XMR_SWAP_A_LOCK,
"bl_type": TxTypes.XMR_SWAP_B_LOCK,
}
query_str: str = (
"SELECT " "SELECT "
+ "bids.created_at, bids.expire_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, " + "bids.created_at, bids.expire_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, "
+ "tx1.state, tx2.state, offers.coin_from, bids.rate, bids.bid_addr, offers.bid_reversed, bids.amount_to, offers.coin_to " + "tx1.state, tx2.state, offers.coin_from, bids.rate, bids.bid_addr, offers.bid_reversed, bids.amount_to, offers.coin_to "
@ -10499,9 +10520,11 @@ class BasicSwap(BaseApp):
query_str += "WHERE bids.active_ind = 1 " query_str += "WHERE bids.active_ind = 1 "
filter_bid_id = filters.get("bid_id", None) filter_bid_id = filters.get("bid_id", None)
if filter_bid_id is not None: if filter_bid_id is not None:
query_str += "AND bids.bid_id = x'{}' ".format(filter_bid_id.hex()) query_str += "AND bids.bid_id = :filter_bid_id "
query_data["filter_bid_id"] = filter_bid_id
if offer_id is not None: if offer_id is not None:
query_str += "AND bids.offer_id = x'{}' ".format(offer_id.hex()) query_str += "AND bids.offer_id = :filter_offer_id "
query_data["filter_offer_id"] = offer_id
elif sent: elif sent:
query_str += "AND bids.was_sent = 1 " query_str += "AND bids.was_sent = 1 "
else: else:
@ -10509,41 +10532,31 @@ class BasicSwap(BaseApp):
bid_state_ind = filters.get("bid_state_ind", -1) bid_state_ind = filters.get("bid_state_ind", -1)
if bid_state_ind != -1: if bid_state_ind != -1:
query_str += "AND bids.state = {} ".format(bid_state_ind) query_str += "AND bids.state = :bid_state_ind "
query_data["bid_state_ind"] = bid_state_ind
with_available_or_active = filters.get("with_available_or_active", False) with_available_or_active = filters.get("with_available_or_active", False)
with_expired = filters.get("with_expired", True) with_expired = filters.get("with_expired", True)
if with_available_or_active: if with_available_or_active:
query_str += " AND " + self.activeBidsQueryStr(now) query_str += " AND " + self.activeBidsQueryStr()
else: else:
if with_expired is not True: if with_expired is not True:
query_str += ( query_str += (
"AND bids.expire_at > {} AND offers.expire_at > {} ".format( "AND bids.expire_at > :now AND offers.expire_at > :now "
now, now
)
) )
sort_dir = filters.get("sort_dir", "DESC").upper() query_str += getOrderByStr(filters, table_name="bids")
sort_by = filters.get("sort_by", "created_at")
query_str += f" ORDER BY bids.{sort_by} {sort_dir}"
limit = filters.get("limit", None) limit = filters.get("limit", None)
if limit is not None: if limit is not None:
query_str += f" LIMIT {limit}" query_str += " LIMIT :limit"
query_data["limit"] = limit
offset = filters.get("offset", None) offset = filters.get("offset", None)
if offset is not None: if offset is not None:
query_str += f" OFFSET {offset}" query_str += " OFFSET :offset"
query_data["offset"] = offset
q = cursor.execute( q = cursor.execute(query_str, query_data)
query_str,
{
"ads_swap": SwapTypes.XMR_SWAP,
"itx_type": TxTypes.ITX,
"ptx_type": TxTypes.PTX,
"al_type": TxTypes.XMR_SWAP_A_LOCK,
"bl_type": TxTypes.XMR_SWAP_B_LOCK,
},
)
for row in q: for row in q:
result = [x for x in row] result = [x for x in row]
coin_from = result[9] coin_from = result[9]
@ -10604,9 +10617,10 @@ class BasicSwap(BaseApp):
self.mxDB.release() self.mxDB.release()
def listAllSMSGAddresses(self, filters={}, cursor=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: str = (
query_str += " WHERE 1 = 1 " "SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses WHERE 1=1 "
query_data = {} )
query_data: dict = {}
if filters.get("exclude_inactive", True) is True: if filters.get("exclude_inactive", True) is True:
query_str += " AND active_ind = :active_ind " query_str += " AND active_ind = :active_ind "
@ -10621,15 +10635,15 @@ class BasicSwap(BaseApp):
query_str += " AND use_type = :addr_type " query_str += " AND use_type = :addr_type "
query_data["addr_type"] = filters["addr_type"] query_data["addr_type"] = filters["addr_type"]
sort_dir = filters.get("sort_dir", "DESC").upper() query_str += getOrderByStr(filters)
sort_by = filters.get("sort_by", "created_at")
query_str += f" ORDER BY {sort_by} {sort_dir}"
limit = filters.get("limit", None) limit = filters.get("limit", None)
if limit is not None: if limit is not None:
query_str += f" LIMIT {limit}" query_str += " LIMIT :limit"
query_data["limit"] = limit
offset = filters.get("offset", None) offset = filters.get("offset", None)
if offset is not None: if offset is not None:
query_str += f" OFFSET {offset}" query_str += " OFFSET :offset"
query_data["offset"] = offset
try: try:
use_cursor = self.openDB(cursor) use_cursor = self.openDB(cursor)
@ -10666,9 +10680,8 @@ class BasicSwap(BaseApp):
cursor = self.openDB() cursor = self.openDB()
rv = [] rv = []
q = cursor.execute( 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( "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",
use_type (use_type,),
)
) )
for row in q: for row in q:
rv.append((row[0], row[1])) rv.append((row[0], row[1]))
@ -10681,25 +10694,29 @@ class BasicSwap(BaseApp):
cursor = self.openDB() cursor = self.openDB()
rv = [] rv = []
query_str = "SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats" query_str: str = (
"SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats"
)
query_str += " WHERE strats.active_ind = 1 " query_str += " WHERE strats.active_ind = 1 "
query_data: dict = {}
type_ind = filters.get("type_ind", None) type_ind = filters.get("type_ind", None)
if type_ind is not None: if type_ind is not None:
query_str += f" AND strats.type_ind = {type_ind} " query_str += " AND strats.type_ind = :type_ind "
query_data["type_ind"] = type_ind
sort_dir = filters.get("sort_dir", "DESC").upper() query_str += getOrderByStr(filters, table_name="strats")
sort_by = filters.get("sort_by", "created_at")
query_str += f" ORDER BY strats.{sort_by} {sort_dir}"
limit = filters.get("limit", None) limit = filters.get("limit", None)
if limit is not None: if limit is not None:
query_str += f" LIMIT {limit}" query_str += " LIMIT :limit"
query_data["limit"] = limit
offset = filters.get("offset", None) offset = filters.get("offset", None)
if offset is not None: if offset is not None:
query_str += f" OFFSET {offset}" query_str += " OFFSET :offset"
query_data["offset"] = offset
q = cursor.execute(query_str) q = cursor.execute(query_str, query_data)
for row in q: for row in q:
rv.append(row) rv.append(row)
return rv return rv
@ -10733,11 +10750,10 @@ class BasicSwap(BaseApp):
query_str = ( query_str = (
"SELECT links.strategy_id, strats.label FROM automationlinks links" "SELECT links.strategy_id, strats.label FROM automationlinks links"
+ " LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id" + " LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id"
+ " WHERE links.linked_type = {} AND links.linked_id = x'{}' AND links.active_ind = 1".format( + " WHERE links.linked_type = :linked_type AND links.linked_id = :linked_id AND links.active_ind = 1"
int(linked_type), linked_id.hex()
) )
) query_data: dict = {"linked_type": int(linked_type), "linked_id": linked_id}
q = cursor.execute(query_str).fetchone() q = cursor.execute(query_str, query_data).fetchone()
return q return q
finally: finally:
self.closeDB(cursor, commit=False) self.closeDB(cursor, commit=False)

View file

@ -39,6 +39,35 @@ def firstOrNone(gen):
return all_rows[0] if len(all_rows) > 0 else None return all_rows[0] if len(all_rows) > 0 else None
def validColumnName(name: str) -> bool:
if not isinstance(name, str):
return False
if len(name) < 1:
return False
# First character must be alpha
if not name[0].isalpha():
return False
# Rest can be alphanumeric or underscores
for c in name[1:]:
if not c.isalnum() and c != "_":
return False
return True
def getOrderByStr(
filters: dict, default_sort_by: str = "created_at", table_name: str = ""
):
sort_by = filters.get("sort_by", default_sort_by)
if not validColumnName(sort_by):
raise ValueError("Invalid sort by")
if table_name != "":
sort_by = table_name + "." + sort_by
sort_dir = filters.get("sort_dir", "DESC").upper()
if sort_dir not in ("ASC", "DESC"):
raise ValueError("Invalid sort dir")
return f" ORDER BY {sort_by} {sort_dir}"
def pack_state(new_state: int, now: int) -> bytes: def pack_state(new_state: int, now: int) -> bytes:
return int(new_state).to_bytes(4, "little") + now.to_bytes(8, "little") return int(new_state).to_bytes(4, "little") + now.to_bytes(8, "little")
@ -851,6 +880,8 @@ class DBMethods:
if upsert: if upsert:
query += " ON CONFLICT DO UPDATE SET " query += " ON CONFLICT DO UPDATE SET "
for i, key in enumerate(values): for i, key in enumerate(values):
if not validColumnName(key):
raise ValueError(f"Invalid column: {key}")
if i > 0: if i > 0:
query += ", " query += ", "
query += f"{key}=:{key}" query += f"{key}=:{key}"
@ -858,7 +889,13 @@ class DBMethods:
cursor.execute(query, values) cursor.execute(query, values)
def query( def query(
self, table_class, cursor, constraints={}, order_by={}, query_suffix=None self,
table_class,
cursor,
constraints={},
order_by={},
query_suffix=None,
extra_query_data={},
): ):
if cursor is None: if cursor is None:
raise ValueError("Cursor is null") raise ValueError("Cursor is null")
@ -883,16 +920,25 @@ class DBMethods:
query += f" FROM {table_name} WHERE 1=1 " query += f" FROM {table_name} WHERE 1=1 "
for ck, cv in constraints.items(): for ck in constraints:
if not validColumnName(ck):
raise ValueError(f"Invalid constraint column: {ck}")
query += f" AND {ck} = :{ck} " query += f" AND {ck} = :{ck} "
for order_col, order_dir in order_by.items(): for order_col, order_dir in order_by.items():
query += f" ORDER BY {order_col} {order_dir.upper()}" if validColumnName(order_col) is False:
raise ValueError(f"Invalid sort by: {order_col}")
order_dir = order_dir.upper()
if order_dir not in ("ASC", "DESC"):
raise ValueError(f"Invalid sort dir: {order_dir}")
query += f" ORDER BY {order_col} {order_dir}"
if query_suffix: if query_suffix:
query += query_suffix query += query_suffix
rows = cursor.execute(query, constraints) query_data = constraints.copy()
query_data.update(extra_query_data)
rows = cursor.execute(query, query_data)
for row in rows: for row in rows:
obj = table_class() obj = table_class()
for i, column_info in enumerate(columns): for i, column_info in enumerate(columns):

View file

@ -14,14 +14,16 @@ def remove_expired_data(self, time_offset: int = 0):
try: try:
cursor = self.openDB() cursor = self.openDB()
active_bids_insert = self.activeBidsQueryStr(now, "", "b2") active_bids_insert: str = self.activeBidsQueryStr("", "b2")
query_str = f""" query_str = f"""
SELECT o.offer_id FROM offers o SELECT o.offer_id FROM offers o
WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert}) WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert})
""" """
num_offers = 0 num_offers = 0
num_bids = 0 num_bids = 0
offer_rows = cursor.execute(query_str, {"expired_at": now - time_offset}) offer_rows = cursor.execute(
query_str, {"now": now, "expired_at": now - time_offset}
)
for offer_row in offer_rows: for offer_row in offer_rows:
num_offers += 1 num_offers += 1
bid_rows = cursor.execute( bid_rows = cursor.execute(

View file

@ -659,13 +659,18 @@ def page_offer(self, url_split, post_string):
extra_options=extra_options, extra_options=extra_options,
).hex() ).hex()
sent_bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options).hex() sent_bid_id = swap_client.postBid(
offer_id,
amount_from,
addr_send_from=addr_from,
extra_options=extra_options,
).hex()
if debugind > -1: if debugind > -1:
swap_client.setBidDebugInd(bytes.fromhex(sent_bid_id), debugind) swap_client.setBidDebugInd(bytes.fromhex(sent_bid_id), debugind)
self.send_response(302) self.send_response(302)
self.send_header('Location', f'/bid/{sent_bid_id}') self.send_header("Location", f"/bid/{sent_bid_id}")
self.end_headers self.end_headers
return bytes return bytes

View file

@ -672,9 +672,9 @@ class Test(unittest.TestCase):
"name": "offer should fail", "name": "offer should fail",
"coin_from": "Particl", "coin_from": "Particl",
"coin_to": "XMR", "coin_to": "XMR",
"amount": 200, "amount": 20000,
"minrate": 0.05, "minrate": 0.05,
"ratetweakpercent": 50000000, "ratetweakpercent": 500000000,
"amount_variable": True, "amount_variable": True,
"address": -1, "address": -1,
"min_coin_from_amt": 20, "min_coin_from_amt": 20,