URI: 
       tLNWatcher: Distinguish between blockchain-triggered channel state transitions, and actions taken as a result. - state transitions are performed in lnchannel.update_onchain_state() - peer actions are in LNWorker.on_channel_update() - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 06dfe1699cca8d116914138147301b0ec9b289bc
   DIR parent 9ca445bd5d8cff6df049f21e374bbb915b77d23b
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Fri,  3 Apr 2020 16:25:42 +0200
       
       LNWatcher: Distinguish between blockchain-triggered channel state
       ttransitions, and actions taken as a result.
       - state transitions are performed in lnchannel.update_onchain_state()
       - peer actions are in LNWorker.on_channel_update()
       
       Diffstat:
         M electrum/lnchannel.py               |      61 +++++++++++++++++++++++++++++++
         M electrum/lnwatcher.py               |      16 ++--------------
         M electrum/lnworker.py                |      58 ++++---------------------------
       
       3 files changed, 69 insertions(+), 66 deletions(-)
       ---
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -54,6 +54,7 @@ from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
        from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
        from .lnhtlc import HTLCManager
        from .lnmsg import encode_msg, decode_msg
       +from .address_synchronizer import TX_HEIGHT_LOCAL
        
        if TYPE_CHECKING:
            from .lnworker import LNWallet
       t@@ -1092,3 +1093,63 @@ class Channel(Logger):
                min_value_worth_closing_channel_over_sat = max(num_htlcs * 10 * self.config[REMOTE].dust_limit_sat,
                                                               500_000)
                return total_value_sat > min_value_worth_closing_channel_over_sat
       +
       +    def update_onchain_state(self, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
       +        # note: state transitions are irreversible, but
       +        # save_funding_height, save_closing_height are reversible
       +        if funding_height.height == TX_HEIGHT_LOCAL:
       +            self.update_unfunded_state()
       +        elif closing_height.height == TX_HEIGHT_LOCAL:
       +            self.update_funded_state(funding_txid, funding_height)
       +        else:
       +            self.update_closed_state(funding_txid, funding_height, closing_txid, closing_height, keep_watching)
       +
       +    def update_unfunded_state(self):
       +        self.delete_funding_height()
       +        self.delete_closing_height()
       +        if self.get_state() in [channel_states.PREOPENING, channel_states.OPENING, channel_states.FORCE_CLOSING] and self.lnworker:
       +            if self.constraints.is_initiator:
       +                # set channel state to REDEEMED so that it can be removed manually
       +                # to protect ourselves against a server lying by omission,
       +                # we check that funding_inputs have been double spent and deeply mined
       +                inputs = self.storage.get('funding_inputs', [])
       +                if not inputs:
       +                    self.logger.info(f'channel funding inputs are not provided')
       +                    self.set_state(channel_states.REDEEMED)
       +                for i in inputs:
       +                    spender_txid = self.lnworker.wallet.db.get_spent_outpoint(*i)
       +                    if spender_txid is None:
       +                        continue
       +                    if spender_txid != self.funding_outpoint.txid:
       +                        tx_mined_height = self.lnworker.wallet.get_tx_height(spender_txid)
       +                        if tx_mined_height.conf > lnutil.REDEEM_AFTER_DOUBLE_SPENT_DELAY:
       +                            self.logger.info(f'channel is double spent {inputs}')
       +                            self.set_state(channel_states.REDEEMED)
       +                            break
       +            else:
       +                now = int(time.time())
       +                if now - self.storage.get('init_timestamp', 0) > CHANNEL_OPENING_TIMEOUT:
       +                    self.lnworker.remove_channel(self.channel_id)
       +
       +    def update_funded_state(self, funding_txid, funding_height):
       +        self.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
       +        self.delete_closing_height()
       +        if self.get_state() == channel_states.OPENING:
       +            if self.short_channel_id is None:
       +                self.lnworker.maybe_save_short_chan_id(self, funding_height)
       +            if self.short_channel_id:
       +                self.set_state(channel_states.FUNDED)
       +
       +    def update_closed_state(self, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
       +        self.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
       +        self.save_closing_height(closing_txid, closing_height.height, closing_height.timestamp)
       +        if self.get_state() < channel_states.CLOSED:
       +            conf = closing_height.conf
       +            if conf > 0:
       +                self.set_state(channel_states.CLOSED)
       +            else:
       +                # we must not trust the server with unconfirmed transactions
       +                # if the remote force closed, we remain OPEN until the closing tx is confirmed
       +                pass
       +        if self.get_state() == channel_states.CLOSED and not keep_watching:
       +            self.set_state(channel_states.REDEEMED)
   DIR diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
       t@@ -341,23 +341,11 @@ class LNWalletWatcher(LNWatcher):
            @ignore_exceptions
            @log_exceptions
            async def update_channel_state(self, funding_outpoint, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
       -        # note: state transitions are irreversible, but
       -        # save_funding_height, save_closing_height are reversible
                chan = self.lnworker.channel_by_txo(funding_outpoint)
                if not chan:
                    return
       -        if funding_height.height == TX_HEIGHT_LOCAL:
       -            chan.delete_funding_height()
       -            chan.delete_closing_height()
       -            await self.lnworker.update_unfunded_channel(chan, funding_txid)
       -        elif closing_height.height == TX_HEIGHT_LOCAL:
       -            chan.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
       -            chan.delete_closing_height()
       -            await self.lnworker.update_open_channel(chan, funding_txid, funding_height)
       -        else:
       -            chan.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
       -            chan.save_closing_height(closing_txid, closing_height.height, closing_height.timestamp)
       -            await self.lnworker.update_closed_channel(chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching)
       +        chan.update_onchain_state(funding_txid, funding_height, closing_txid, closing_height, keep_watching)
       +        await self.lnworker.on_channel_update(chan)
        
            async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
                chan = self.lnworker.channel_by_txo(funding_outpoint)
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -696,57 +696,24 @@ class LNWallet(LNWorker):
                    if chan.funding_outpoint.to_str() == txo:
                        return chan
        
       -    async def update_unfunded_channel(self, chan, funding_txid):
       -        if chan.get_state() in [channel_states.PREOPENING, channel_states.OPENING, channel_states.FORCE_CLOSING]:
       -            if chan.constraints.is_initiator:
       -                # set channel state to REDEEMED so that it can be removed manually
       -                # to protect ourselves against a server lying by omission,
       -                # we check that funding_inputs have been double spent and deeply mined
       -                inputs = chan.storage.get('funding_inputs', [])
       -                if not inputs:
       -                    self.logger.info(f'channel funding inputs are not provided')
       -                    chan.set_state(channel_states.REDEEMED)
       -                for i in inputs:
       -                    spender_txid = self.wallet.db.get_spent_outpoint(*i)
       -                    if spender_txid is None:
       -                        continue
       -                    if spender_txid != funding_txid:
       -                        tx_mined_height = self.wallet.get_tx_height(spender_txid)
       -                        if tx_mined_height.conf > lnutil.REDEEM_AFTER_DOUBLE_SPENT_DELAY:
       -                            self.logger.info(f'channel is double spent {inputs}')
       -                            chan.set_state(channel_states.REDEEMED)
       -                            break
       -            else:
       -                now = int(time.time())
       -                if now - chan.storage.get('init_timestamp', 0) > CHANNEL_OPENING_TIMEOUT:
       -                    self.remove_channel(chan.channel_id)
        
       -    async def update_open_channel(self, chan, funding_txid, funding_height):
       +    async def on_channel_update(self, chan):
        
                if chan.get_state() == channel_states.OPEN and chan.should_be_closed_due_to_expiring_htlcs(self.network.get_local_height()):
                    self.logger.info(f"force-closing due to expiring htlcs")
                    await self.try_force_closing(chan.channel_id)
       -            return
        
       -        if chan.get_state() == channel_states.OPENING:
       -            if chan.short_channel_id is None:
       -                self.maybe_save_short_chan_id(chan, funding_height)
       -            if chan.short_channel_id:
       -                chan.set_state(channel_states.FUNDED)
       -
       -        if chan.get_state() == channel_states.FUNDED:
       +        elif chan.get_state() == channel_states.FUNDED:
                    peer = self.peers.get(chan.node_id)
                    if peer and peer.is_initialized():
                        peer.send_funding_locked(chan)
        
                elif chan.get_state() == channel_states.OPEN:
                    peer = self.peers.get(chan.node_id)
       -            if peer is None:
       -                self.logger.info("peer not found for {}".format(bh2u(chan.node_id)))
       -                return
       -            await peer.maybe_update_fee(chan)
       -            conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf
       -            peer.on_network_update(chan, conf)
       +            if peer:
       +                await peer.maybe_update_fee(chan)
       +                conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf
       +                peer.on_network_update(chan, conf)
        
                elif chan.get_state() == channel_states.FORCE_CLOSING:
                    force_close_tx = chan.force_close_tx()
       t@@ -757,19 +724,6 @@ class LNWallet(LNWorker):
                        await self.network.try_broadcasting(force_close_tx, 'force-close')
        
        
       -    async def update_closed_channel(self, chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
       -
       -        if chan.get_state() < channel_states.CLOSED:
       -            conf = closing_height.conf
       -            if conf > 0:
       -                chan.set_state(channel_states.CLOSED)
       -            else:
       -                # we must not trust the server with unconfirmed transactions
       -                # if the remote force closed, we remain OPEN until the closing tx is confirmed
       -                pass
       -
       -        if chan.get_state() == channel_states.CLOSED and not keep_watching:
       -            chan.set_state(channel_states.REDEEMED)
        
        
            @log_exceptions