Browse Source

swaps: stop watching address once utxo is spent and mined

bip39-recovery
ThomasV 5 years ago
parent
commit
04fb329c2e
  1. 16
      electrum/address_synchronizer.py
  2. 31
      electrum/submarine_swaps.py

16
electrum/address_synchronizer.py

@ -760,22 +760,28 @@ class AddressSynchronizer(Logger):
sent[txi] = height sent[txi] = height
return received, sent 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) coins, spent = self.get_addr_io(address)
for txi in spent:
coins.pop(txi)
out = {} out = {}
for prevout_str, v in coins.items(): for prevout_str, v in coins.items():
tx_height, value, is_cb = v tx_height, value, is_cb = v
prevout = TxOutpoint.from_str(prevout_str) prevout = TxOutpoint.from_str(prevout_str)
utxo = PartialTxInput(prevout=prevout, utxo = PartialTxInput(prevout=prevout, is_coinbase_output=is_cb)
is_coinbase_output=is_cb)
utxo._trusted_address = address utxo._trusted_address = address
utxo._trusted_value_sats = value utxo._trusted_value_sats = value
utxo.block_height = tx_height utxo.block_height = tx_height
utxo.spent_height = spent.get(prevout_str, None)
out[prevout] = utxo out[prevout] = utxo
return out 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 # return the total amount ever received by an address
def get_addr_received(self, address): def get_addr_received(self, address):
received, sent = self.get_addr_io(address) received, sent = self.get_addr_io(address)

31
electrum/submarine_swaps.py

@ -1,14 +1,16 @@
import asyncio import asyncio
import json import json
import os import os
from typing import TYPE_CHECKING
from .crypto import sha256, hash_160 from .crypto import sha256, hash_160
from .ecc import ECPrivkey 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 .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 TxOutpoint, PartialTxInput, PartialTxOutput, PartialTransaction, construct_witness
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
from .util import log_exceptions from .util import log_exceptions
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
from .bitcoin import dust_threshold from .bitcoin import dust_threshold
from typing import TYPE_CHECKING
from .logging import Logger from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
@ -78,17 +80,27 @@ class SwapManager(Logger):
@log_exceptions @log_exceptions
async def _claim_swap(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime): async def _claim_swap(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime):
utxos = self.lnwatcher.get_addr_utxo(lockup_address) if not self.lnwatcher.is_up_to_date():
if not utxos:
return return
delta = self.network.get_local_height() - locktime current_height = self.network.get_local_height()
if not preimage and delta < 0: delta = current_height - locktime
self.logger.info(f'height not reached for refund {lockup_address} {delta}, {locktime}') is_reverse = bool(preimage)
if not is_reverse and delta < 0:
# too early for refund
return 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: if preimage and txin._trusted_value_sats < onchain_amount:
self.logger.info('amount too low, we should not reveal the preimage') self.logger.info('amount too low, we should not reveal the preimage')
continue 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() amount_sat = txin._trusted_value_sats - self.get_tx_fee()
if amount_sat < dust_threshold(): if amount_sat < dust_threshold():
self.logger.info('utxo value below dust threshold') self.logger.info('utxo value below dust threshold')
@ -97,8 +109,7 @@ class SwapManager(Logger):
tx = create_claim_tx(txin, redeem_script, preimage, privkey, address, amount_sat, locktime) tx = create_claim_tx(txin, redeem_script, preimage, privkey, address, amount_sat, locktime)
await self.network.broadcast_transaction(tx) await self.network.broadcast_transaction(tx)
# save txid # save txid
what = 'claim_txid' if preimage else 'refund_txid' swap['claim_txid' if preimage else 'refund_txid'] = tx.txid()
self.swaps[preimage.hex()][what] = tx.txid()
def get_tx_fee(self): def get_tx_fee(self):
return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True) return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True)
@ -111,6 +122,8 @@ class SwapManager(Logger):
self.lnwatcher = self.wallet.lnworker.lnwatcher self.lnwatcher = self.wallet.lnworker.lnwatcher
self.swaps = self.wallet.db.get_dict('submarine_swaps') self.swaps = self.wallet.db.get_dict('submarine_swaps')
for data in self.swaps.values(): for data in self.swaps.values():
if data.get('redeemed'):
continue
redeem_script = bytes.fromhex(data['redeemScript']) redeem_script = bytes.fromhex(data['redeemScript'])
locktime = data['timeoutBlockHeight'] locktime = data['timeoutBlockHeight']
privkey = bytes.fromhex(data['privkey']) privkey = bytes.fromhex(data['privkey'])

Loading…
Cancel
Save