tswaps: stop watching address once utxo is spent and mined - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 04fb329c2e8feb02cddcb5f07c22eca0d0cee401
 (DIR) parent 252591832a68e7f6c38606f78d00c821a5a11c67
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Wed, 27 May 2020 12:12:14 +0200
       
       swaps: stop watching address once utxo is spent and mined
       
       Diffstat:
         M electrum/address_synchronizer.py    |      16 +++++++++++-----
         M electrum/submarine_swaps.py         |      31 ++++++++++++++++++++++---------
       
       2 files changed, 33 insertions(+), 14 deletions(-)
       ---
 (DIR) diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
       t@@ -760,22 +760,28 @@ class AddressSynchronizer(Logger):
                            sent[txi] = height
                return received, sent
        
       -    def get_addr_utxo(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
       +
       +    def get_addr_outputs(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
                coins, spent = self.get_addr_io(address)
       -        for txi in spent:
       -            coins.pop(txi)
                out = {}
                for prevout_str, v in coins.items():
                    tx_height, value, is_cb = v
                    prevout = TxOutpoint.from_str(prevout_str)
       -            utxo = PartialTxInput(prevout=prevout,
       -                                  is_coinbase_output=is_cb)
       +            utxo = PartialTxInput(prevout=prevout, is_coinbase_output=is_cb)
                    utxo._trusted_address = address
                    utxo._trusted_value_sats = value
                    utxo.block_height = tx_height
       +            utxo.spent_height = spent.get(prevout_str, None)
                    out[prevout] = utxo
                return out
        
       +    def get_addr_utxo(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
       +        out = self.get_addr_outputs(address)
       +        for k, v in list(out.items()):
       +            if v.spent_height is not None:
       +                out.pop(k)
       +        return out
       +
            # return the total amount ever received by an address
            def get_addr_received(self, address):
                received, sent = self.get_addr_io(address)
 (DIR) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
       t@@ -1,14 +1,16 @@
        import asyncio
        import json
        import os
       +from typing import TYPE_CHECKING
       +
        from .crypto import sha256, hash_160
        from .ecc import ECPrivkey
        from .bitcoin import address_to_script, script_to_p2wsh, redeem_script_to_address, opcodes, p2wsh_nested_script, push_script, is_segwit_address
        from .transaction import TxOutpoint, PartialTxInput, PartialTxOutput, PartialTransaction, construct_witness
        from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
        from .util import log_exceptions
       +from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
        from .bitcoin import dust_threshold
       -from typing import TYPE_CHECKING
        from .logging import Logger
        
        if TYPE_CHECKING:
       t@@ -78,17 +80,27 @@ class SwapManager(Logger):
        
            @log_exceptions
            async def _claim_swap(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime):
       -        utxos = self.lnwatcher.get_addr_utxo(lockup_address)
       -        if not utxos:
       +        if not self.lnwatcher.is_up_to_date():
                    return
       -        delta = self.network.get_local_height() - locktime
       -        if not preimage and delta < 0:
       -            self.logger.info(f'height not reached for refund {lockup_address} {delta}, {locktime}')
       +        current_height = self.network.get_local_height()
       +        delta = current_height - locktime
       +        is_reverse = bool(preimage)
       +        if not is_reverse and delta < 0:
       +            # too early for refund
                    return
       -        for txin in list(utxos.values()):
       +        txos = self.lnwatcher.get_addr_outputs(lockup_address)
       +        swap = self.swaps[preimage.hex()]
       +        for txin in txos.values():
                    if preimage and txin._trusted_value_sats < onchain_amount:
                        self.logger.info('amount too low, we should not reveal the preimage')
                        continue
       +            spent_height = txin.spent_height
       +            if spent_height is not None:
       +                if spent_height > 0 and current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
       +                    self.logger.info(f'stop watching swap {lockup_address}')
       +                    self.lnwatcher.remove_callback(lockup_address)
       +                    swap['redeemed'] = True
       +                continue
                    amount_sat = txin._trusted_value_sats - self.get_tx_fee()
                    if amount_sat < dust_threshold():
                        self.logger.info('utxo value below dust threshold')
       t@@ -97,8 +109,7 @@ class SwapManager(Logger):
                    tx = create_claim_tx(txin, redeem_script, preimage, privkey, address, amount_sat, locktime)
                    await self.network.broadcast_transaction(tx)
                    # save txid
       -            what = 'claim_txid' if preimage else 'refund_txid'
       -            self.swaps[preimage.hex()][what] = tx.txid()
       +            swap['claim_txid' if preimage else 'refund_txid'] = tx.txid()
        
            def get_tx_fee(self):
                return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True)
       t@@ -111,6 +122,8 @@ class SwapManager(Logger):
                self.lnwatcher = self.wallet.lnworker.lnwatcher
                self.swaps = self.wallet.db.get_dict('submarine_swaps')
                for data in self.swaps.values():
       +            if data.get('redeemed'):
       +                continue
                    redeem_script = bytes.fromhex(data['redeemScript'])
                    locktime = data['timeoutBlockHeight']
                    privkey = bytes.fromhex(data['privkey'])