commit 3a35f90aa0510546b653d4b822f702e776f47dcc
parent 36f32651cc970b3429b56aa1a763374c50eee980
Author: ThomasV <thomasv@electrum.org>
Date: Sun, 16 Feb 2020 14:26:07 +0100
Do not use network callback to update channel states; call LNWorker methods directly instead.
A callback was used because a single LNWnwatcher object used to be shared for all wallets.
Since wallet now have their own LNWatcher instance, this can be simplified.
Diffstat:
M | electrum/lnwatcher.py | | | 117 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
M | electrum/lnworker.py | | | 81 | +++++-------------------------------------------------------------------------- |
2 files changed, 106 insertions(+), 92 deletions(-)
diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
@@ -178,28 +178,26 @@ class LNWatcher(AddressSynchronizer):
keep_watching, spenders = self.inspect_tx_candidate(funding_outpoint, 0)
funding_txid = funding_outpoint.split(':')[0]
funding_height = self.get_tx_height(funding_txid)
- if funding_height.height == TX_HEIGHT_LOCAL:
- return
closing_txid = spenders.get(funding_outpoint)
closing_height = self.get_tx_height(closing_txid)
- if closing_height.height == TX_HEIGHT_LOCAL:
- self.network.trigger_callback('update_open_channel', funding_outpoint, funding_txid, funding_height)
- else:
+ await self.update_channel_state(
+ funding_outpoint, funding_txid,
+ funding_height, closing_txid,
+ closing_height, keep_watching)
+ if closing_txid:
closing_tx = self.db.get_transaction(closing_txid)
- if not closing_tx:
+ 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...")
- return
- self.network.trigger_callback('update_closed_channel', funding_outpoint, spenders,
- funding_txid, funding_height, closing_txid,
- closing_height, closing_tx, keep_watching) # FIXME sooo many args..
- # TODO: add tests for local_watchtower
- await self.do_breach_remedy(funding_outpoint, spenders)
if not keep_watching:
await self.unwatch_channel(address, funding_outpoint)
- async def do_breach_remedy(self, funding_outpoints, spenders):
- # overloaded in WatchTower
- pass
+ async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
+ raise NotImplementedError() # implemented by subclasses
+
+ async def update_channel_state(self, *args):
+ raise NotImplementedError() # implemented by subclasses
def inspect_tx_candidate(self, outpoint, n):
# FIXME: instead of stopping recursion at n == 2,
@@ -267,7 +265,7 @@ class WatchTower(LNWatcher):
for outpoint, address in l:
self.add_channel(outpoint, address)
- async def do_breach_remedy(self, funding_outpoint, spenders):
+ async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
for prevout, spender in spenders.items():
if spender is not None:
continue
@@ -315,3 +313,90 @@ class WatchTower(LNWatcher):
await self.sweepstore.remove_channel(funding_outpoint)
if funding_outpoint in self.tx_progress:
self.tx_progress[funding_outpoint].all_done.set()
+
+ async def update_channel_state(self, *args):
+ pass
+
+
+class LNWalletWatcher(LNWatcher):
+
+ def __init__(self, lnworker, network):
+ LNWatcher.__init__(self, network)
+ self.network = network
+ self.lnworker = lnworker
+
+ @ignore_exceptions
+ @log_exceptions
+ async def update_channel_state(self, funding_outpoint, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
+ chan = self.lnworker.channel_by_txo(funding_outpoint)
+ if not chan:
+ return
+ if funding_height.height == TX_HEIGHT_LOCAL:
+ return
+ elif closing_height.height == TX_HEIGHT_LOCAL:
+ await self.lnworker.update_open_channel(chan, funding_txid, funding_height)
+ else:
+ await self.lnworker.update_closed_channel(chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching)
+
+ async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
+ chan = self.lnworker.channel_by_txo(funding_outpoint)
+ if not chan:
+ return
+ # detect who closed and set sweep_info
+ sweep_info_dict = chan.sweep_ctx(closing_tx)
+ self.logger.info(f'sweep_info_dict length: {len(sweep_info_dict)}')
+ # create and broadcast transaction
+ for prevout, sweep_info in sweep_info_dict.items():
+ name = sweep_info.name
+ spender_txid = spenders.get(prevout)
+ if spender_txid is not None:
+ # TODO handle exceptions for network.get_transaction
+ # TODO don't do network request every time... save tx at least in memory, or maybe wallet file?
+ spender_tx = await self.network.get_transaction(spender_txid)
+ spender_tx = Transaction(spender_tx)
+ e_htlc_tx = chan.sweep_htlc(closing_tx, spender_tx)
+ if e_htlc_tx:
+ spender2 = spenders.get(spender_txid+':0')
+ if spender2:
+ self.logger.info(f'htlc is already spent {name}: {prevout}')
+ else:
+ self.logger.info(f'trying to redeem htlc {name}: {prevout}')
+ await self.try_redeem(spender_txid+':0', e_htlc_tx)
+ else:
+ self.logger.info(f'outpoint already spent {name}: {prevout}')
+ else:
+ self.logger.info(f'trying to redeem {name}: {prevout}')
+ await self.try_redeem(prevout, sweep_info)
+
+ @log_exceptions
+ async def try_redeem(self, prevout: str, sweep_info: 'SweepInfo') -> None:
+ name = sweep_info.name
+ prev_txid, prev_index = prevout.split(':')
+ broadcast = True
+ if sweep_info.cltv_expiry:
+ local_height = self.network.get_local_height()
+ remaining = sweep_info.cltv_expiry - local_height
+ if remaining > 0:
+ self.logger.info('waiting for {}: CLTV ({} > {}), prevout {}'
+ .format(name, local_height, sweep_info.cltv_expiry, prevout))
+ broadcast = False
+ if sweep_info.csv_delay:
+ prev_height = self.get_tx_height(prev_txid)
+ remaining = sweep_info.csv_delay - prev_height.conf
+ if remaining > 0:
+ self.logger.info('waiting for {}: CSV ({} >= {}), prevout: {}'
+ .format(name, prev_height.conf, sweep_info.csv_delay, prevout))
+ broadcast = False
+ tx = sweep_info.gen_tx()
+ if tx is None:
+ self.logger.info(f'{name} could not claim output: {prevout}, dust')
+ self.lnworker.wallet.set_label(tx.txid(), name)
+ if broadcast:
+ await self.network.try_broadcasting(tx, name)
+ else:
+ # it's OK to add local transaction, the fee will be recomputed
+ try:
+ self.lnworker.wallet.add_future_tx(tx, remaining)
+ self.logger.info(f'adding future tx: {name}. prevout: {prevout}')
+ except Exception as e:
+ self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -61,7 +61,7 @@ from .i18n import _
from .lnrouter import RouteEdge, LNPaymentRoute, is_route_sane_to_use
from .address_synchronizer import TX_HEIGHT_LOCAL
from . import lnsweep
-from .lnwatcher import LNWatcher
+from .lnwatcher import LNWalletWatcher
if TYPE_CHECKING:
from .network import Network
@@ -408,12 +408,10 @@ class LNWallet(LNWorker):
def start_network(self, network: 'Network'):
assert network
- self.lnwatcher = LNWatcher(network)
+ self.lnwatcher = LNWalletWatcher(self, network)
self.lnwatcher.start_network(network)
self.network = network
daemon = network.daemon
- self.network.register_callback(self.on_update_open_channel, ['update_open_channel'])
- self.network.register_callback(self.on_update_closed_channel, ['update_closed_channel'])
for chan_id, chan in self.channels.items():
self.lnwatcher.add_channel(chan.funding_outpoint.to_str(), chan.get_funding_address())
@@ -651,17 +649,10 @@ class LNWallet(LNWorker):
if chan.funding_outpoint.to_str() == txo:
return chan
- @ignore_exceptions
- @log_exceptions
- async def on_update_open_channel(self, event, funding_outpoint, funding_txid, funding_height):
- chan = self.channel_by_txo(funding_outpoint)
- if not chan:
- return
-
+ async def update_open_channel(self, chan, funding_txid, funding_height):
# return early to prevent overwriting closing_txid with None
if chan.is_closed():
return
-
# save timestamp regardless of state, so that funding tx is returned in get_history
self.channel_timestamps[bh2u(chan.channel_id)] = chan.funding_outpoint.txid, funding_height.height, funding_height.timestamp, None, None, None
@@ -699,12 +690,8 @@ class LNWallet(LNWorker):
self.logger.info('REBROADCASTING CLOSING TX')
await self.network.try_broadcasting(force_close_tx, 'force-close')
- @ignore_exceptions
- @log_exceptions
- async def on_update_closed_channel(self, event, funding_outpoint, spenders, funding_txid, funding_height, closing_txid, closing_height, closing_tx, keep_watching):
- chan = self.channel_by_txo(funding_outpoint)
- if not chan:
- return
+
+ async def update_closed_channel(self, chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
# fixme: this is wasteful
self.channel_timestamps[bh2u(chan.channel_id)] = funding_txid, funding_height.height, funding_height.timestamp, closing_txid, closing_height.height, closing_height.timestamp
@@ -719,64 +706,6 @@ class LNWallet(LNWorker):
if chan.get_state() == channel_states.CLOSED and not keep_watching:
chan.set_state(channel_states.REDEEMED)
- # detect who closed and set sweep_info
- sweep_info_dict = chan.sweep_ctx(closing_tx)
- self.logger.info(f'sweep_info_dict length: {len(sweep_info_dict)}')
- # create and broadcast transaction
- for prevout, sweep_info in sweep_info_dict.items():
- name = sweep_info.name
- spender_txid = spenders.get(prevout)
- if spender_txid is not None:
- # TODO handle exceptions for network.get_transaction
- # TODO don't do network request every time... save tx at least in memory, or maybe wallet file?
- spender_tx = await self.network.get_transaction(spender_txid)
- spender_tx = Transaction(spender_tx)
- e_htlc_tx = chan.sweep_htlc(closing_tx, spender_tx)
- if e_htlc_tx:
- spender2 = spenders.get(spender_txid+':0')
- if spender2:
- self.logger.info(f'htlc is already spent {name}: {prevout}')
- else:
- self.logger.info(f'trying to redeem htlc {name}: {prevout}')
- await self.try_redeem(spender_txid+':0', e_htlc_tx)
- else:
- self.logger.info(f'outpoint already spent {name}: {prevout}')
- else:
- self.logger.info(f'trying to redeem {name}: {prevout}')
- await self.try_redeem(prevout, sweep_info)
-
- @log_exceptions
- async def try_redeem(self, prevout: str, sweep_info: 'SweepInfo') -> None:
- name = sweep_info.name
- prev_txid, prev_index = prevout.split(':')
- broadcast = True
- if sweep_info.cltv_expiry:
- local_height = self.network.get_local_height()
- remaining = sweep_info.cltv_expiry - local_height
- if remaining > 0:
- self.logger.info('waiting for {}: CLTV ({} > {}), prevout {}'
- .format(name, local_height, sweep_info.cltv_expiry, prevout))
- broadcast = False
- if sweep_info.csv_delay:
- prev_height = self.lnwatcher.get_tx_height(prev_txid)
- remaining = sweep_info.csv_delay - prev_height.conf
- if remaining > 0:
- self.logger.info('waiting for {}: CSV ({} >= {}), prevout: {}'
- .format(name, prev_height.conf, sweep_info.csv_delay, prevout))
- broadcast = False
- tx = sweep_info.gen_tx()
- if tx is None:
- self.logger.info(f'{name} could not claim output: {prevout}, dust')
- self.wallet.set_label(tx.txid(), name)
- if broadcast:
- await self.network.try_broadcasting(tx, name)
- else:
- # it's OK to add local transaction, the fee will be recomputed
- try:
- self.wallet.add_future_tx(tx, remaining)
- self.logger.info(f'adding future tx: {name}. prevout: {prevout}')
- except Exception as e:
- self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
def should_channel_be_closed_due_to_expiring_htlcs(self, chan: Channel) -> bool:
local_height = self.network.get_local_height()