From b400669919e0c1aa4ae4064d552d81101368cca8 Mon Sep 17 00:00:00 2001
From: tecnovert <tecnovert@tecnovert.net>
Date: Thu, 7 Nov 2024 10:10:21 +0200
Subject: [PATCH] Check BCH mercy tx value.

---
 basicswap/basicswap.py     | 30 +++++++++++++++++++++---------
 basicswap/interface/bch.py |  4 ++++
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index ccd6be8..c3354ec 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -4330,13 +4330,13 @@ class BasicSwap(BaseApp):
 
         watched.append(WatchedScript(bid_id, script, tx_type, swap_type))
 
-    def removeWatchedScript(self, coin_type, bid_id: bytes, script: bytes) -> None:
-        # Remove all for bid if txid is None
-        self.log.debug('removeWatchedScript %s %s', Coins(coin_type).name, bid_id.hex())
+    def removeWatchedScript(self, coin_type, bid_id: bytes, script: bytes, tx_type: TxTypes = None) -> None:
+        # Remove all for bid if script and type_ind is None
+        self.log.debug('removeWatchedScript {} {}{}'.format(Coins(coin_type).name, bid_id.hex(), (' type ' + str(tx_type)) if tx_type is not None else ''))
         old_len = len(self.coin_clients[coin_type]['watched_scripts'])
         for i in range(old_len - 1, -1, -1):
             ws = self.coin_clients[coin_type]['watched_scripts'][i]
-            if ws.bid_id == bid_id and (script is None or ws.script == script):
+            if ws.bid_id == bid_id and (script is None or ws.script == script) and (tx_type is None or ws.tx_type == tx_type):
                 del self.coin_clients[coin_type]['watched_scripts'][i]
                 self.log.debug('Removed watched script %s %s', Coins(coin_type).name, bid_id.hex())
 
@@ -4474,8 +4474,8 @@ class BasicSwap(BaseApp):
                     xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx)
 
                     if was_received:
-                        _, out_1, _, _, _ = ci_from.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script)
-                        self.addWatchedScript(ci_from.coin_type(), bid_id, out_1, TxTypes.BCH_MERCY)
+                        refund_to_script = ci_from.getRefundOutputScript(xmr_swap)
+                        self.addWatchedScript(ci_from.coin_type(), bid_id, refund_to_script, TxTypes.BCH_MERCY)
 
                 bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND)
                 self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, '', use_session)
@@ -4518,13 +4518,18 @@ class BasicSwap(BaseApp):
                 is_spending_lock_refund_tx = self.ci(coin_from).isSpendingLockRefundTx(spend_tx)
 
             if spending_txid == xmr_swap.a_lock_refund_spend_tx_id or (i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_refund_tx_id and is_spending_lock_refund_tx):
+                self.log.info('Found coin a lock refund spend tx, bid {}'.format(bid_id.hex()))
+
                 # bch txids change
                 if self.isBchXmrSwap(offer):
                     xmr_swap.a_lock_refund_spend_tx_id = spending_txid
                     xmr_swap.a_lock_refund_spend_tx = bytes.fromhex(spend_txn_hex)
 
-                self.log.info('Found coin a lock refund spend tx, bid {}'.format(bid_id.hex()))
+                    if was_received:
+                        self.removeWatchedScript(coin_from, bid_id, None, TxTypes.BCH_MERCY)
+
                 self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN, '', session)
+
                 if bid.xmr_a_lock_tx:
                     bid.xmr_a_lock_tx.setState(TxStates.TX_REFUNDED)
 
@@ -4602,7 +4607,15 @@ class BasicSwap(BaseApp):
 
     def processMercyTx(self, coin_type, watched_script, txid: bytes, vout: int, tx) -> None:
         bid_id = watched_script.bid_id
-        self.log.warning('Found mercy tx for bid: {}'.format(bid_id.hex()))
+        ci = self.ci(coin_type)
+        if len(tx['vout']) < 2 or \
+           ci.make_int(tx['vout'][vout]['value']) != 546:  # Dust limit
+
+            self.log.info('Found tx is not a mercy tx for bid: {}'.format(bid_id.hex()))
+            self.removeWatchedScript(coin_type, bid_id, watched_script.script)
+            return
+
+        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)
 
@@ -4610,7 +4623,6 @@ class BasicSwap(BaseApp):
             self.log.warning('Could not find active bid for found mercy tx: {}'.format(bid_id.hex()))
         else:
             remote_keyshare = bytes.fromhex(tx['vout'][0]['scriptPubKey']['asm'].split(' ')[2])
-            ci = self.ci(coin_type)
             ensure(ci.verifyKey(remote_keyshare), 'Invalid keyshare')
 
             bid = self.swaps_in_progress[bid_id][0]
diff --git a/basicswap/interface/bch.py b/basicswap/interface/bch.py
index b0fb233..3c4bd6b 100644
--- a/basicswap/interface/bch.py
+++ b/basicswap/interface/bch.py
@@ -799,6 +799,10 @@ class BCHInterface(BTCInterface):
     def isTxExistsError(self, err_str: str) -> bool:
         return 'transaction already in block chain' in err_str
 
+    def getRefundOutputScript(self, xmr_swap) -> bytes:
+        _, out_1, _, _, _ = self.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script)
+        return out_1
+
     def createMercyTx(self, refund_swipe_tx_bytes: bytes, refund_swipe_tx_id: bytes, lock_refund_tx_script: bytes, keyshare: bytes) -> str:
         refund_swipe_tx = self.loadTx(refund_swipe_tx_bytes)
         refund_output_value = refund_swipe_tx.vout[0].nValue