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'])