swaps: use StoredObject to store data
This commit is contained in:
@@ -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] = {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
Reference in New Issue
Block a user