Browse Source

swaps: use StoredObject to store data

master
ThomasV 6 years ago
parent
commit
3874f7ec77
  1. 34
      electrum/lnworker.py
  2. 114
      electrum/submarine_swaps.py
  3. 3
      electrum/wallet_db.py

34
electrum/lnworker.py

@ -631,17 +631,14 @@ class LNWallet(LNWorker):
'preimage': preimage, 'preimage': preimage,
} }
# add txid to merge item with onchain item # add txid to merge item with onchain item
swap_info = self.swap_manager.get_swap(preimage) swap = self.swap_manager.get_swap(payment_hash)
if swap_info: if swap:
is_reverse = swap_info.get('invoice') if swap.is_reverse:
if is_reverse: item['txid'] = swap.spending_txid
item['txid'] = swap_info.get('claim_txid') item['label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(swap.lightning_amount)
lightning_amount = swap_info.get('lightning_amount')
item['label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(lightning_amount)
else: else:
item['txid'] = swap_info.get('funding_txid') item['txid'] = swap.funding_txid
onchain_amount = swap_info["expectedAmount"] item['label'] = 'Normal swap' + ' ' + self.config.format_amount_and_units(swap.onchain_amount)
item['label'] = 'Normal swap' + ' ' + self.config.format_amount_and_units(onchain_amount)
# done # done
out[payment_hash] = item out[payment_hash] = item
return out return out
@ -683,21 +680,18 @@ class LNWallet(LNWorker):
# add submarine swaps # add submarine swaps
settled_payments = self.get_settled_payments() settled_payments = self.get_settled_payments()
current_height = self.network.get_local_height() current_height = self.network.get_local_height()
for preimage_hex, swap_info in self.swap_manager.swaps.items(): for payment_hash_hex, swap in self.swap_manager.swaps.items():
is_reverse = swap_info.get('invoice') txid = swap.spending_txid if swap.is_reverse else swap.funding_txid
txid = swap_info.get('claim_txid' if is_reverse else 'funding_txid')
if txid is None: if txid is None:
continue continue
payment_hash = sha256(bytes.fromhex(preimage_hex)) if payment_hash_hex in settled_payments:
if payment_hash.hex() in settled_payments: plist = settled_payments[payment_hash_hex]
plist = settled_payments[payment_hash.hex()] info = self.get_payment_info(bytes.fromhex(payment_hash_hex))
info = self.get_payment_info(payment_hash)
amount_msat, fee_msat, timestamp = self.get_payment_value(info, plist) amount_msat, fee_msat, timestamp = self.get_payment_value(info, plist)
else: else:
amount_msat = 0 amount_msat = 0
locktime = swap_info.get('timeoutBlockHeight') label = 'Reverse swap' if swap.is_reverse else 'Normal swap'
delta = current_height - locktime delta = current_height - swap.locktime
label = 'Reverse swap' if is_reverse else 'Normal swap'
if delta < 0: if delta < 0:
label += f' (refundable in {-delta} blocks)' label += f' (refundable in {-delta} blocks)'
out[txid] = { out[txid] = {

114
electrum/submarine_swaps.py

@ -1,3 +1,4 @@
import attr
import asyncio import asyncio
import json import json
import os import os
@ -12,11 +13,14 @@ from .util import log_exceptions
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
from .bitcoin import dust_threshold from .bitcoin import dust_threshold
from .logging import Logger from .logging import Logger
from .lnutil import hex_to_bytes
from .json_db import StoredObject
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
from .wallet import Abstract_Wallet from .wallet import Abstract_Wallet
API_URL = 'https://lightning.electrum.org/api' 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): def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amount_sat, locktime):
pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
if is_segwit_address(txin.address): if is_segwit_address(txin.address):
@ -75,41 +94,39 @@ def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amou
return tx return tx
class SwapManager(Logger): 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, swap):
if not self.lnwatcher.is_up_to_date(): if not self.lnwatcher.is_up_to_date():
return return
current_height = self.network.get_local_height() current_height = self.network.get_local_height()
delta = current_height - locktime delta = current_height - swap.locktime
is_reverse = bool(preimage) if not swap.is_reverse and delta < 0:
if not is_reverse and delta < 0:
# too early for refund # too early for refund
return return
txos = self.lnwatcher.get_addr_outputs(lockup_address) txos = self.lnwatcher.get_addr_outputs(swap.lockup_address)
swap = self.swaps[preimage.hex()]
for txin in txos.values(): 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') self.logger.info('amount too low, we should not reveal the preimage')
continue continue
spent_height = txin.spent_height spent_height = txin.spent_height
if spent_height is not None: if spent_height is not None:
if spent_height > 0 and current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY: if spent_height > 0 and current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
self.logger.info(f'stop watching swap {lockup_address}') self.logger.info(f'stop watching swap {swap.lockup_address}')
self.lnwatcher.remove_callback(lockup_address) self.lnwatcher.remove_callback(swap.lockup_address)
swap['redeemed'] = True swap.is_redeemed = True
continue 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')
continue continue
address = self.wallet.get_unused_address() 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) await self.network.broadcast_transaction(tx)
# save txid # save txid
swap['claim_txid' if preimage else 'refund_txid'] = tx.txid() swap.spending_txid = 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)
@ -121,28 +138,17 @@ class SwapManager(Logger):
self.lnworker = wallet.lnworker self.lnworker = wallet.lnworker
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 swap in self.swaps.values():
if data.get('redeemed'): if swap.is_redeemed:
continue continue
redeem_script = bytes.fromhex(data['redeemScript']) self.add_lnwatcher_callback(swap)
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)
def get_swap(self, preimage_hex): def get_swap(self, payment_hash):
return self.swaps.get(preimage_hex) return self.swaps.get(payment_hash.hex())
def add_lnwatcher_callback(self, lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime): def add_lnwatcher_callback(self, swap):
callback = lambda: self._claim_swap(lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime) callback = lambda: self._claim_swap(swap)
self.lnwatcher.add_callback(lockup_address, callback) self.lnwatcher.add_callback(swap.lockup_address, callback)
@log_exceptions @log_exceptions
async def normal_swap(self, lightning_amount, expected_onchain_amount, password): 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)] outputs = [PartialTxOutput.from_address_and_value(lockup_address, onchain_amount)]
tx = self.wallet.create_transaction(outputs=outputs, rbf=False, password=password) tx = self.wallet.create_transaction(outputs=outputs, rbf=False, password=password)
# save swap data in wallet in case we need a refund # save swap data in wallet in case we need a refund
data['privkey'] = privkey.hex() swap = SwapData(
data['preimage'] = preimage.hex() redeem_script = redeem_script,
data['lightning_amount'] = lightning_amount locktime = locktime,
data['funding_txid'] = tx.txid() privkey = privkey,
self.swaps[preimage.hex()] = data preimage = preimage,
self.add_lnwatcher_callback(lockup_address, onchain_amount, redeem_script, 0, privkey, locktime) 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) await self.network.broadcast_transaction(tx)
# #
attempt = await self.lnworker.await_payment(payment_hash) attempt = await self.lnworker.await_payment(payment_hash)
@ -244,14 +259,23 @@ class SwapManager(Logger):
# verify invoice preimage_hash # verify invoice preimage_hash
lnaddr = self.lnworker._check_invoice(invoice, amount_sat) lnaddr = self.lnworker._check_invoice(invoice, amount_sat)
assert lnaddr.paymenthash == preimage_hash assert lnaddr.paymenthash == preimage_hash
# save swap data in wallet in case payment fails # save swap data to wallet file
data['privkey'] = privkey.hex() swap = SwapData(
data['preimage'] = preimage.hex() redeem_script = redeem_script,
data['lightning_amount'] = amount_sat locktime = locktime,
# save data to wallet file privkey = privkey,
self.swaps[preimage.hex()] = data 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 # add callback to lnwatcher
self.add_lnwatcher_callback(lockup_address, onchain_amount, redeem_script, preimage, privkey, locktime) self.add_lnwatcher_callback(swap)
# initiate payment. # initiate payment.
success, log = await self.lnworker._pay(invoice, attempts=10) success, log = await self.lnworker._pay(invoice, attempts=10)
return { return {

3
electrum/wallet_db.py

@ -42,6 +42,7 @@ from .lnutil import ChannelConstraints, Outpoint, ShachainElement
from .json_db import StoredDict, JsonDB, locked, modifier from .json_db import StoredDict, JsonDB, locked, modifier
from .plugin import run_hook, plugin_loaders from .plugin import run_hook, plugin_loaders
from .paymentrequest import PaymentRequest from .paymentrequest import PaymentRequest
from .submarine_swaps import SwapData
if TYPE_CHECKING: if TYPE_CHECKING:
from .storage import WalletStorage from .storage import WalletStorage
@ -1133,6 +1134,8 @@ class WalletDB(JsonDB):
v = dict((k, UpdateAddHtlc.from_tuple(*x)) for k, x in v.items()) v = dict((k, UpdateAddHtlc.from_tuple(*x)) for k, x in v.items())
elif key == 'fee_updates': elif key == 'fee_updates':
v = dict((k, FeeUpdate(**x)) for k, x in v.items()) 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': elif key == 'channel_backups':
v = dict((k, ChannelBackupStorage(**x)) for k, x in v.items()) v = dict((k, ChannelBackupStorage(**x)) for k, x in v.items())
elif key == 'tx_fees': elif key == 'tx_fees':

Loading…
Cancel
Save