mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-27 23:41:35 +00:00
detect redeemed channels (fix #5963)
This commit is contained in:
parent
f8da0f87a7
commit
938fab86d1
5 changed files with 35 additions and 23 deletions
|
@ -88,6 +88,7 @@ Builder.load_string(r'''
|
||||||
id: popuproot
|
id: popuproot
|
||||||
data: []
|
data: []
|
||||||
is_closed: False
|
is_closed: False
|
||||||
|
is_redeemed: False
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
ScrollView:
|
ScrollView:
|
||||||
|
@ -115,7 +116,7 @@ Builder.load_string(r'''
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
text: _('Delete')
|
text: _('Delete')
|
||||||
on_release: root.remove_channel()
|
on_release: root.remove_channel()
|
||||||
disabled: not root.is_closed
|
disabled: not root.is_redeemed
|
||||||
Button:
|
Button:
|
||||||
size_hint: 0.5, None
|
size_hint: 0.5, None
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
|
@ -129,6 +130,7 @@ class ChannelDetailsPopup(Popup):
|
||||||
def __init__(self, chan, app, **kwargs):
|
def __init__(self, chan, app, **kwargs):
|
||||||
super(ChannelDetailsPopup,self).__init__(**kwargs)
|
super(ChannelDetailsPopup,self).__init__(**kwargs)
|
||||||
self.is_closed = chan.is_closed()
|
self.is_closed = chan.is_closed()
|
||||||
|
self.is_redeemed = chan.is_redeemed()
|
||||||
self.app = app
|
self.app = app
|
||||||
self.chan = chan
|
self.chan = chan
|
||||||
self.title = _('Channel details')
|
self.title = _('Channel details')
|
||||||
|
|
|
@ -112,7 +112,7 @@ class ChannelsList(MyTreeView):
|
||||||
if chan.peer_state == peer_states.GOOD:
|
if chan.peer_state == peer_states.GOOD:
|
||||||
menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id))
|
menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id))
|
||||||
menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id))
|
menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id))
|
||||||
else:
|
if chan.is_redeemed():
|
||||||
menu.addAction(_("Remove"), lambda: self.remove_channel(channel_id))
|
menu.addAction(_("Remove"), lambda: self.remove_channel(channel_id))
|
||||||
menu.exec_(self.viewport().mapToGlobal(position))
|
menu.exec_(self.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
|
|
|
@ -228,6 +228,9 @@ class Channel(Logger):
|
||||||
# the closing txid has been saved
|
# the closing txid has been saved
|
||||||
return self.get_state() >= channel_states.CLOSED
|
return self.get_state() >= channel_states.CLOSED
|
||||||
|
|
||||||
|
def is_redeemed(self):
|
||||||
|
return self.get_state() == channel_states.REDEEMED
|
||||||
|
|
||||||
def _check_can_pay(self, amount_msat: int) -> None:
|
def _check_can_pay(self, amount_msat: int) -> None:
|
||||||
# TODO check if this method uses correct ctns (should use "latest" + 1)
|
# TODO check if this method uses correct ctns (should use "latest" + 1)
|
||||||
if self.is_closed():
|
if self.is_closed():
|
||||||
|
|
|
@ -176,21 +176,24 @@ class LNWatcher(AddressSynchronizer):
|
||||||
await self.check_onchain_situation(address, outpoint)
|
await self.check_onchain_situation(address, outpoint)
|
||||||
|
|
||||||
async def check_onchain_situation(self, address, funding_outpoint):
|
async def check_onchain_situation(self, address, funding_outpoint):
|
||||||
keep_watching, spenders = self.inspect_tx_candidate(funding_outpoint, 0)
|
spenders = self.inspect_tx_candidate(funding_outpoint, 0)
|
||||||
funding_txid = funding_outpoint.split(':')[0]
|
funding_txid = funding_outpoint.split(':')[0]
|
||||||
funding_height = self.get_tx_height(funding_txid)
|
funding_height = self.get_tx_height(funding_txid)
|
||||||
closing_txid = spenders.get(funding_outpoint)
|
closing_txid = spenders.get(funding_outpoint)
|
||||||
closing_height = self.get_tx_height(closing_txid)
|
closing_height = self.get_tx_height(closing_txid)
|
||||||
|
if closing_txid:
|
||||||
|
closing_tx = self.db.get_transaction(closing_txid)
|
||||||
|
if closing_tx:
|
||||||
|
keep_watching = await self.do_breach_remedy(funding_outpoint, closing_tx, spenders)
|
||||||
|
else:
|
||||||
|
self.logger.info(f"channel {funding_outpoint} closed by {closing_txid}. still waiting for tx itself...")
|
||||||
|
keep_watching = True
|
||||||
|
else:
|
||||||
|
keep_watching = True
|
||||||
await self.update_channel_state(
|
await self.update_channel_state(
|
||||||
funding_outpoint, funding_txid,
|
funding_outpoint, funding_txid,
|
||||||
funding_height, closing_txid,
|
funding_height, closing_txid,
|
||||||
closing_height, keep_watching)
|
closing_height, keep_watching)
|
||||||
if closing_txid:
|
|
||||||
closing_tx = self.db.get_transaction(closing_txid)
|
|
||||||
if closing_tx:
|
|
||||||
await self.do_breach_remedy(funding_outpoint, closing_tx, spenders)
|
|
||||||
else:
|
|
||||||
self.logger.info(f"channel {funding_outpoint} closed by {closing_txid}. still waiting for tx itself...")
|
|
||||||
if not keep_watching:
|
if not keep_watching:
|
||||||
await self.unwatch_channel(address, funding_outpoint)
|
await self.unwatch_channel(address, funding_outpoint)
|
||||||
|
|
||||||
|
@ -201,32 +204,24 @@ class LNWatcher(AddressSynchronizer):
|
||||||
raise NotImplementedError() # implemented by subclasses
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
def inspect_tx_candidate(self, outpoint, n):
|
def inspect_tx_candidate(self, outpoint, n):
|
||||||
# FIXME: instead of stopping recursion at n == 2,
|
|
||||||
# we should detect which outputs are HTLCs
|
|
||||||
prev_txid, index = outpoint.split(':')
|
prev_txid, index = outpoint.split(':')
|
||||||
txid = self.db.get_spent_outpoint(prev_txid, int(index))
|
txid = self.db.get_spent_outpoint(prev_txid, int(index))
|
||||||
result = {outpoint:txid}
|
result = {outpoint:txid}
|
||||||
if txid is None:
|
if txid is None:
|
||||||
self.channel_status[outpoint] = 'open'
|
self.channel_status[outpoint] = 'open'
|
||||||
#self.logger.info('keep watching because outpoint is unspent')
|
return result
|
||||||
return True, result
|
if n == 0 and not self.is_deeply_mined(txid):
|
||||||
keep_watching = (self.get_tx_mined_depth(txid) != TxMinedDepth.DEEP)
|
|
||||||
if keep_watching:
|
|
||||||
self.channel_status[outpoint] = 'closed (%d)' % self.get_tx_height(txid).conf
|
self.channel_status[outpoint] = 'closed (%d)' % self.get_tx_height(txid).conf
|
||||||
#self.logger.info('keep watching because spending tx is not deep')
|
|
||||||
else:
|
else:
|
||||||
self.channel_status[outpoint] = 'closed (deep)'
|
self.channel_status[outpoint] = 'closed (deep)'
|
||||||
|
|
||||||
tx = self.db.get_transaction(txid)
|
tx = self.db.get_transaction(txid)
|
||||||
for i, o in enumerate(tx.outputs()):
|
for i, o in enumerate(tx.outputs()):
|
||||||
if o.address not in self.get_addresses():
|
if o.address not in self.get_addresses():
|
||||||
self.add_address(o.address)
|
self.add_address(o.address)
|
||||||
keep_watching = True
|
|
||||||
elif n < 2:
|
elif n < 2:
|
||||||
k, r = self.inspect_tx_candidate(txid+':%d'%i, n+1)
|
r = self.inspect_tx_candidate(txid+':%d'%i, n+1)
|
||||||
keep_watching |= k
|
|
||||||
result.update(r)
|
result.update(r)
|
||||||
return keep_watching, result
|
return result
|
||||||
|
|
||||||
def get_tx_mined_depth(self, txid: str):
|
def get_tx_mined_depth(self, txid: str):
|
||||||
if not txid:
|
if not txid:
|
||||||
|
@ -247,6 +242,9 @@ class LNWatcher(AddressSynchronizer):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def is_deeply_mined(self, txid):
|
||||||
|
return self.get_tx_mined_depth(txid) == TxMinedDepth.DEEP
|
||||||
|
|
||||||
|
|
||||||
class WatchTower(LNWatcher):
|
class WatchTower(LNWatcher):
|
||||||
|
|
||||||
|
@ -267,12 +265,16 @@ class WatchTower(LNWatcher):
|
||||||
self.add_channel(outpoint, address)
|
self.add_channel(outpoint, address)
|
||||||
|
|
||||||
async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
|
async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
|
||||||
|
keep_watching = False
|
||||||
for prevout, spender in spenders.items():
|
for prevout, spender in spenders.items():
|
||||||
if spender is not None:
|
if spender is not None:
|
||||||
|
keep_watching |= not self.is_deeply_mined(spender)
|
||||||
continue
|
continue
|
||||||
sweep_txns = await self.sweepstore.get_sweep_tx(funding_outpoint, prevout)
|
sweep_txns = await self.sweepstore.get_sweep_tx(funding_outpoint, prevout)
|
||||||
for tx in sweep_txns:
|
for tx in sweep_txns:
|
||||||
await self.broadcast_or_log(funding_outpoint, tx)
|
await self.broadcast_or_log(funding_outpoint, tx)
|
||||||
|
keep_watching = True
|
||||||
|
return keep_watching
|
||||||
|
|
||||||
async def broadcast_or_log(self, funding_outpoint: str, tx: Transaction):
|
async def broadcast_or_log(self, funding_outpoint: str, tx: Transaction):
|
||||||
height = self.get_tx_height(tx.txid()).height
|
height = self.get_tx_height(tx.txid()).height
|
||||||
|
@ -345,6 +347,7 @@ class LNWalletWatcher(LNWatcher):
|
||||||
return
|
return
|
||||||
# detect who closed and set sweep_info
|
# detect who closed and set sweep_info
|
||||||
sweep_info_dict = chan.sweep_ctx(closing_tx)
|
sweep_info_dict = chan.sweep_ctx(closing_tx)
|
||||||
|
keep_watching = False
|
||||||
self.logger.info(f'sweep_info_dict length: {len(sweep_info_dict)}')
|
self.logger.info(f'sweep_info_dict length: {len(sweep_info_dict)}')
|
||||||
# create and broadcast transaction
|
# create and broadcast transaction
|
||||||
for prevout, sweep_info in sweep_info_dict.items():
|
for prevout, sweep_info in sweep_info_dict.items():
|
||||||
|
@ -360,14 +363,19 @@ class LNWalletWatcher(LNWatcher):
|
||||||
spender2 = spenders.get(spender_txid+':0')
|
spender2 = spenders.get(spender_txid+':0')
|
||||||
if spender2:
|
if spender2:
|
||||||
self.logger.info(f'htlc is already spent {name}: {prevout}')
|
self.logger.info(f'htlc is already spent {name}: {prevout}')
|
||||||
|
keep_watching |= not self.is_deeply_mined(spender2)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f'trying to redeem htlc {name}: {prevout}')
|
self.logger.info(f'trying to redeem htlc {name}: {prevout}')
|
||||||
await self.try_redeem(spender_txid+':0', e_htlc_tx)
|
await self.try_redeem(spender_txid+':0', e_htlc_tx)
|
||||||
|
keep_watching = True
|
||||||
else:
|
else:
|
||||||
self.logger.info(f'outpoint already spent {name}: {prevout}')
|
self.logger.info(f'outpoint already spent {name}: {prevout}')
|
||||||
|
keep_watching |= not self.is_deeply_mined(spender_txid)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f'trying to redeem {name}: {prevout}')
|
self.logger.info(f'trying to redeem {name}: {prevout}')
|
||||||
await self.try_redeem(prevout, sweep_info)
|
await self.try_redeem(prevout, sweep_info)
|
||||||
|
keep_watching = True
|
||||||
|
return keep_watching
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def try_redeem(self, prevout: str, sweep_info: 'SweepInfo') -> None:
|
async def try_redeem(self, prevout: str, sweep_info: 'SweepInfo') -> None:
|
||||||
|
|
|
@ -1192,9 +1192,8 @@ class LNWallet(LNWorker):
|
||||||
return tx.txid()
|
return tx.txid()
|
||||||
|
|
||||||
def remove_channel(self, chan_id):
|
def remove_channel(self, chan_id):
|
||||||
# TODO: assert that closing tx is deep-mined and htlcs are swept
|
|
||||||
chan = self.channels[chan_id]
|
chan = self.channels[chan_id]
|
||||||
assert chan.is_closed()
|
assert chan.get_state() == channel_states.REDEEMED
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.channels.pop(chan_id)
|
self.channels.pop(chan_id)
|
||||||
self.channel_timestamps.pop(chan_id.hex())
|
self.channel_timestamps.pop(chan_id.hex())
|
||||||
|
|
Loading…
Add table
Reference in a new issue