URI: 
       tswaps: use StoredObject to store data - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 3874f7ec77bdd49f3dac02078e7b8a468efb5f88
   DIR parent a73f24e82664890316f760ba40732418855b1609
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 28 May 2020 10:59:20 +0200
       
       swaps: use StoredObject to store data
       
       Diffstat:
         M electrum/lnworker.py                |      34 +++++++++++++------------------
         M electrum/submarine_swaps.py         |     114 +++++++++++++++++++------------
         M electrum/wallet_db.py               |       3 +++
       
       3 files changed, 86 insertions(+), 65 deletions(-)
       ---
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -631,17 +631,14 @@ class LNWallet(LNWorker):
                        'preimage': preimage,
                    }
                    # add txid to merge item with onchain item
       -            swap_info = self.swap_manager.get_swap(preimage)
       -            if swap_info:
       -                is_reverse = swap_info.get('invoice')
       -                if is_reverse:
       -                    item['txid'] = swap_info.get('claim_txid')
       -                    lightning_amount = swap_info.get('lightning_amount')
       -                    item['label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(lightning_amount)
       +            swap = self.swap_manager.get_swap(payment_hash)
       +            if swap:
       +                if swap.is_reverse:
       +                    item['txid'] = swap.spending_txid
       +                    item['label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(swap.lightning_amount)
                        else:
       -                    item['txid'] = swap_info.get('funding_txid')
       -                    onchain_amount = swap_info["expectedAmount"]
       -                    item['label'] = 'Normal swap' + ' ' + self.config.format_amount_and_units(onchain_amount)
       +                    item['txid'] = swap.funding_txid
       +                    item['label'] = 'Normal swap' + ' ' + self.config.format_amount_and_units(swap.onchain_amount)
                    # done
                    out[payment_hash] = item
                return out
       t@@ -683,21 +680,18 @@ class LNWallet(LNWorker):
                # add submarine swaps
                settled_payments = self.get_settled_payments()
                current_height = self.network.get_local_height()
       -        for preimage_hex, swap_info in self.swap_manager.swaps.items():
       -            is_reverse = swap_info.get('invoice')
       -            txid = swap_info.get('claim_txid' if is_reverse else 'funding_txid')
       +        for payment_hash_hex, swap in self.swap_manager.swaps.items():
       +            txid = swap.spending_txid if swap.is_reverse else swap.funding_txid
                    if txid is None:
                        continue
       -            payment_hash = sha256(bytes.fromhex(preimage_hex))
       -            if payment_hash.hex() in settled_payments:
       -                plist = settled_payments[payment_hash.hex()]
       -                info = self.get_payment_info(payment_hash)
       +            if payment_hash_hex in settled_payments:
       +                plist = settled_payments[payment_hash_hex]
       +                info = self.get_payment_info(bytes.fromhex(payment_hash_hex))
                        amount_msat, fee_msat, timestamp = self.get_payment_value(info, plist)
                    else:
                        amount_msat = 0
       -            locktime = swap_info.get('timeoutBlockHeight')
       -            delta = current_height - locktime
       -            label = 'Reverse swap' if is_reverse else 'Normal swap'
       +            label = 'Reverse swap' if swap.is_reverse else 'Normal swap'
       +            delta = current_height - swap.locktime
                    if delta < 0:
                        label += f' (refundable in {-delta} blocks)'
                    out[txid] = {
   DIR diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
       t@@ -1,3 +1,4 @@
       +import attr
        import asyncio
        import json
        import os
       t@@ -12,11 +13,14 @@ from .util import log_exceptions
        from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
        from .bitcoin import dust_threshold
        from .logging import Logger
       +from .lnutil import hex_to_bytes
       +from .json_db import StoredObject
        
        if TYPE_CHECKING:
            from .network import Network
            from .wallet import Abstract_Wallet
        
       +
        API_URL = 'https://lightning.electrum.org/api'
        
        
       t@@ -56,6 +60,21 @@ WITNESS_TEMPLATE_REVERSE_SWAP = [
        ]
        
        
       +@attr.s
       +class SwapData(StoredObject):
       +    is_reverse = attr.ib(type=bool)
       +    locktime = attr.ib(type=int)
       +    onchain_amount = attr.ib(type=int)
       +    lightning_amount = attr.ib(type=int)
       +    redeem_script = attr.ib(type=bytes, converter=hex_to_bytes)
       +    preimage = attr.ib(type=bytes, converter=hex_to_bytes)
       +    privkey = attr.ib(type=bytes, converter=hex_to_bytes)
       +    lockup_address = attr.ib(type=str)
       +    funding_txid = attr.ib(type=str)
       +    spending_txid = attr.ib(type=str)
       +    is_redeemed = attr.ib(type=bool)
       +
       +
        def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amount_sat, locktime):
            pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
            if is_segwit_address(txin.address):
       t@@ -75,41 +94,39 @@ def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amou
            return tx
        
        
       -
        class SwapManager(Logger):
        
            @log_exceptions
       -    async def _claim_swap(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime):
       +    async def _claim_swap(self, swap):
                if not self.lnwatcher.is_up_to_date():
                    return
                current_height = self.network.get_local_height()
       -        delta = current_height - locktime
       -        is_reverse = bool(preimage)
       -        if not is_reverse and delta < 0:
       +        delta = current_height - swap.locktime
       +        if not swap.is_reverse and delta < 0:
                    # too early for refund
                    return
       -        txos = self.lnwatcher.get_addr_outputs(lockup_address)
       -        swap = self.swaps[preimage.hex()]
       +        txos = self.lnwatcher.get_addr_outputs(swap.lockup_address)
                for txin in txos.values():
       -            if preimage and txin._trusted_value_sats < onchain_amount:
       +            if swap.is_reverse and txin._trusted_value_sats < swap.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
       +                    self.logger.info(f'stop watching swap {swap.lockup_address}')
       +                    self.lnwatcher.remove_callback(swap.lockup_address)
       +                    swap.is_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')
                        continue
                    address = self.wallet.get_unused_address()
       -            tx = create_claim_tx(txin, redeem_script, preimage, privkey, address, amount_sat, locktime)
       +            preimage = swap.preimage if swap.is_reverse else 0
       +            tx = create_claim_tx(txin, swap.redeem_script, preimage, swap.privkey, address, amount_sat, swap.locktime)
                    await self.network.broadcast_transaction(tx)
                    # save txid
       -            swap['claim_txid' if preimage else 'refund_txid'] = tx.txid()
       +            swap.spending_txid = tx.txid()
        
            def get_tx_fee(self):
                return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True)
       t@@ -121,28 +138,17 @@ class SwapManager(Logger):
                self.lnworker = wallet.lnworker
                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'):
       +        for swap in self.swaps.values():
       +            if swap.is_redeemed:
                        continue
       -            redeem_script = bytes.fromhex(data['redeemScript'])
       -            locktime = data['timeoutBlockHeight']
       -            privkey = bytes.fromhex(data['privkey'])
       -            if data.get('invoice'):
       -                lockup_address = data['lockupAddress']
       -                onchain_amount = data["onchainAmount"]
       -                preimage = bytes.fromhex(data['preimage'])
       -            else:
       -                lockup_address = data['address']
       -                onchain_amount = data["expectedAmount"]
       -                preimage = 0
       -            self.add_lnwatcher_callback(lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime)
       +            self.add_lnwatcher_callback(swap)
        
       -    def get_swap(self, preimage_hex):
       -        return self.swaps.get(preimage_hex)
       +    def get_swap(self, payment_hash):
       +        return self.swaps.get(payment_hash.hex())
        
       -    def add_lnwatcher_callback(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime):
       -        callback = lambda: self._claim_swap(lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime)
       -        self.lnwatcher.add_callback(lockup_address, callback)
       +    def add_lnwatcher_callback(self, swap):
       +        callback = lambda: self._claim_swap(swap)
       +        self.lnwatcher.add_callback(swap.lockup_address, callback)
        
            @log_exceptions
            async def normal_swap(self, lightning_amount, expected_onchain_amount, password):
       t@@ -189,12 +195,21 @@ class SwapManager(Logger):
                outputs = [PartialTxOutput.from_address_and_value(lockup_address, onchain_amount)]
                tx = self.wallet.create_transaction(outputs=outputs, rbf=False, password=password)
                # save swap data in wallet in case we need a refund
       -        data['privkey'] = privkey.hex()
       -        data['preimage'] = preimage.hex()
       -        data['lightning_amount'] = lightning_amount
       -        data['funding_txid'] = tx.txid()
       -        self.swaps[preimage.hex()] = data
       -        self.add_lnwatcher_callback(lockup_address, onchain_amount, redeem_script, 0, privkey, locktime)
       +        swap = SwapData(
       +            redeem_script = redeem_script,
       +            locktime = locktime,
       +            privkey = privkey,
       +            preimage = preimage,
       +            lockup_address = lockup_address,
       +            onchain_amount = onchain_amount,
       +            lightning_amount = lightning_amount,
       +            is_reverse = False,
       +            is_redeemed = False,
       +            funding_txid = tx.txid(),
       +            spending_txid = None,
       +        )
       +        self.swaps[payment_hash.hex()] = swap
       +        self.add_lnwatcher_callback(swap)
                await self.network.broadcast_transaction(tx)
                #
                attempt = await self.lnworker.await_payment(payment_hash)
       t@@ -244,14 +259,23 @@ class SwapManager(Logger):
                # verify invoice preimage_hash
                lnaddr = self.lnworker._check_invoice(invoice, amount_sat)
                assert lnaddr.paymenthash == preimage_hash
       -        # save swap data in wallet in case payment fails
       -        data['privkey'] = privkey.hex()
       -        data['preimage'] = preimage.hex()
       -        data['lightning_amount'] = amount_sat
       -        # save data to wallet file
       -        self.swaps[preimage.hex()] = data
       +        # save swap data to wallet file
       +        swap = SwapData(
       +            redeem_script = redeem_script,
       +            locktime = locktime,
       +            privkey = privkey,
       +            preimage = preimage,
       +            lockup_address = lockup_address,
       +            onchain_amount = onchain_amount,
       +            lightning_amount = amount_sat,
       +            is_reverse = True,
       +            is_redeemed = False,
       +            funding_txid = None,
       +            spending_txid = None,
       +        )
       +        self.swaps[preimage_hash.hex()] = swap
                # add callback to lnwatcher
       -        self.add_lnwatcher_callback(lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime)
       +        self.add_lnwatcher_callback(swap)
                # initiate payment.
                success, log = await self.lnworker._pay(invoice, attempts=10)
                return {
   DIR diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py
       t@@ -42,6 +42,7 @@ from .lnutil import ChannelConstraints, Outpoint, ShachainElement
        from .json_db import StoredDict, JsonDB, locked, modifier
        from .plugin import run_hook, plugin_loaders
        from .paymentrequest import PaymentRequest
       +from .submarine_swaps import SwapData
        
        if TYPE_CHECKING:
            from .storage import WalletStorage
       t@@ -1133,6 +1134,8 @@ class WalletDB(JsonDB):
                    v = dict((k, UpdateAddHtlc.from_tuple(*x)) for k, x in v.items())
                elif key == 'fee_updates':
                    v = dict((k, FeeUpdate(**x)) for k, x in v.items())
       +        elif key == 'submarine_swaps':
       +            v = dict((k, SwapData(**x)) for k, x in v.items())
                elif key == 'channel_backups':
                    v = dict((k, ChannelBackupStorage(**x)) for k, x in v.items())
                elif key == 'tx_fees':