electrum

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

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:
Melectrum/lnwatcher.py | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Melectrum/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()