electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 3874f7ec77bdd49f3dac02078e7b8a468efb5f88
parent a73f24e82664890316f760ba40732418855b1609
Author: ThomasV <thomasv@electrum.org>
Date:   Thu, 28 May 2020 10:59:20 +0200

swaps: use StoredObject to store data

Diffstat:
Melectrum/lnworker.py | 34++++++++++++++--------------------
Melectrum/submarine_swaps.py | 114++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Melectrum/wallet_db.py | 3+++
3 files changed, 86 insertions(+), 65 deletions(-)

diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -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 @@ -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] = { diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py @@ -1,3 +1,4 @@ +import attr import asyncio import json import os @@ -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' @@ -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): @@ -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) @@ -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): @@ -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) @@ -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 { diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py @@ -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 @@ -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':