URI: 
       tlnpeer: make reestablish_channel saner - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c8b19aec2ac45eb6464454a35413ad0baf79cb44
   DIR parent a3fd6b3ce8d7a498ffadbbe4292db6f00edfa3f6
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Fri,  2 Aug 2019 20:30:47 +0200
       
       lnpeer: make reestablish_channel saner
       
       clear up expectations about ctns
       
       Diffstat:
         M electrum/lnpeer.py                  |      97 +++++++++++++++++--------------
         M electrum/tests/test_lnchannel.py    |      16 ----------------
       
       2 files changed, 53 insertions(+), 60 deletions(-)
       ---
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -695,75 +695,83 @@ class Peer(Logger):
                    return
                chan.set_state('REESTABLISHING')
                self.network.trigger_callback('channel', chan)
       -        current_remote_ctn = chan.config[REMOTE].ctn
       +        # ctns
       +        oldest_unrevoked_local_ctn = chan.config[LOCAL].ctn
       +        latest_local_ctn = chan.hm.ctn_latest(LOCAL)
       +        next_local_ctn = latest_local_ctn + 1
       +        oldest_unrevoked_remote_ctn = chan.config[REMOTE].ctn
       +        latest_remote_ctn = chan.hm.ctn_latest(REMOTE)
       +        next_remote_ctn = latest_remote_ctn + 1
                # send message
                if self.localfeatures & LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_OPT:
       -            if current_remote_ctn == 0:
       +            if oldest_unrevoked_remote_ctn == 0:
                        last_rev_secret = 0
                    else:
                        revocation_store = chan.config[REMOTE].revocation_store
       -                last_rev_index = current_remote_ctn - 1
       +                last_rev_index = oldest_unrevoked_remote_ctn - 1
                        last_rev_secret = revocation_store.retrieve_secret(RevocationStore.START_INDEX - last_rev_index)
       -            current_local_ctn = chan.get_current_ctn(LOCAL)
       -            last_secret, last_point = chan.get_secret_and_point(LOCAL, current_local_ctn)
       +            latest_secret, latest_point = chan.get_secret_and_point(LOCAL, latest_local_ctn)
                    self.send_message(
                        "channel_reestablish",
                        channel_id=chan_id,
       -                next_local_commitment_number=chan.config[LOCAL].ctn+1,
       -                next_remote_revocation_number=current_remote_ctn,
       +                next_local_commitment_number=next_local_ctn,
       +                next_remote_revocation_number=oldest_unrevoked_remote_ctn,
                        your_last_per_commitment_secret=last_rev_secret,
       -                my_current_per_commitment_point=last_point)
       +                my_current_per_commitment_point=latest_point)
                else:
                    self.send_message(
                        "channel_reestablish",
                        channel_id=chan_id,
       -                next_local_commitment_number=chan.config[LOCAL].ctn+1,
       -                next_remote_revocation_number=current_remote_ctn)
       +                next_local_commitment_number=next_local_ctn,
       +                next_remote_revocation_number=oldest_unrevoked_remote_ctn)
        
                channel_reestablish_msg = await self.channel_reestablished[chan_id].get()
                chan.set_state('OPENING')
       -        # compare remote ctns
                their_next_local_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big')
       -        their_next_remote_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big')
       -        if their_next_local_ctn != chan.config[REMOTE].ctn + 1:
       -            self.logger.info("expected remote ctn {}, got {}".format(chan.config[REMOTE].ctn + 1, their_next_local_ctn))
       -            # TODO iff their ctn is lower than ours, we should force close instead
       -            self.try_to_get_remote_to_force_close_with_their_latest(chan_id)
       -            return
       +        their_oldest_unrevoked_remote_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big')
       +
       +        should_close_we_are_ahead = False
       +        should_close_they_are_ahead = False
       +        # compare remote ctns
       +        if next_remote_ctn != their_next_local_ctn:
       +            self.logger.warning(f"channel_reestablish: expected remote ctn {next_remote_ctn}, got {their_next_local_ctn}")
       +            if their_next_local_ctn < next_remote_ctn:
       +                should_close_we_are_ahead = True
       +            else:
       +                should_close_they_are_ahead = True
                # compare local ctns
       -        our_local_ctn = chan.get_current_ctn(LOCAL)
       -        if our_local_ctn != their_next_remote_ctn:
       -            if our_local_ctn == their_next_remote_ctn + 1:
       +        if oldest_unrevoked_local_ctn != their_oldest_unrevoked_remote_ctn:
       +            if oldest_unrevoked_local_ctn - 1 == their_oldest_unrevoked_remote_ctn:
                        # A node:
       -                #    if next_remote_revocation_number is equal to the
       -                #    commitment number of the last revoke_and_ack
       -                #    the receiving node sent, AND the receiving node
       -                #    hasn't already received a closing_signed:
       +                #    if next_remote_revocation_number is equal to the commitment number of the last revoke_and_ack
       +                #    the receiving node sent, AND the receiving node hasn't already received a closing_signed:
                        #        MUST re-send the revoke_and_ack.
       -                last_secret, last_point = chan.get_secret_and_point(LOCAL, our_local_ctn - 1)
       -                next_secret, next_point = chan.get_secret_and_point(LOCAL, our_local_ctn + 1)
       +                last_secret, last_point = chan.get_secret_and_point(LOCAL, oldest_unrevoked_local_ctn - 1)
       +                next_secret, next_point = chan.get_secret_and_point(LOCAL, oldest_unrevoked_local_ctn + 1)
                        self.send_message(
                            "revoke_and_ack",
                            channel_id=chan.channel_id,
                            per_commitment_secret=last_secret,
                            next_per_commitment_point=next_point)
                    else:
       -                self.logger.info(f"expected local ctn {chan.config[LOCAL].ctn}, got {their_next_remote_ctn}")
       -                # TODO iff their ctn is lower than ours, we should force close instead
       -                self.try_to_get_remote_to_force_close_with_their_latest(chan_id)
       -                return
       -        # compare per commitment points (needs data_protect option)
       -        their_pcp = channel_reestablish_msg.get("my_current_per_commitment_point", None)
       -        if their_pcp is not None:
       -            our_pcp = chan.config[REMOTE].current_per_commitment_point
       -            if our_pcp is None:
       -                our_pcp = chan.config[REMOTE].next_per_commitment_point
       -            if our_pcp != their_pcp:
       -                self.logger.info(f"Remote PCP mismatch: {bh2u(our_pcp)} {bh2u(their_pcp)}")
       -                # FIXME ...what now?
       -                self.try_to_get_remote_to_force_close_with_their_latest(chan_id)
       -                return
       -        if their_next_local_ctn == our_local_ctn + 1 == 1 and chan.short_channel_id:
       +                self.logger.warning(f"channel_reestablish: expected local ctn {oldest_unrevoked_local_ctn}, got {their_oldest_unrevoked_remote_ctn}")
       +                if their_oldest_unrevoked_remote_ctn < oldest_unrevoked_local_ctn:
       +                    should_close_we_are_ahead = True
       +                else:
       +                    should_close_they_are_ahead = True
       +
       +        # TODO option_data_loss_protect
       +
       +        if should_close_they_are_ahead:
       +            self.logger.warning(f"channel_reestablish: remote is ahead of us! trying to get them to force-close.")
       +            self.try_to_get_remote_to_force_close_with_their_latest(chan_id)
       +            return
       +        elif should_close_we_are_ahead:
       +            self.logger.warning(f"channel_reestablish: we are ahead of remote! trying to force-close.")
       +            self.lnworker.force_close_channel(chan_id)
       +            return
       +
       +        if their_next_local_ctn == next_local_ctn == 1 and chan.short_channel_id:
                    self.send_funding_locked(chan)
                # checks done
                if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
       t@@ -948,6 +956,7 @@ class Peer(Logger):
                return h, node_signature, bitcoin_signature
        
            def on_update_fail_htlc(self, payload):
       +        self.logger.info("on_update_fail_htlc")
                channel_id = payload["channel_id"]
                htlc_id = int.from_bytes(payload["id"], "big")
                chan = self.channels[channel_id]
       t@@ -1103,7 +1112,7 @@ class Peer(Logger):
                self.send_revoke_and_ack(chan)
        
            def on_update_fulfill_htlc(self, update_fulfill_htlc_msg):
       -        self.logger.info("update_fulfill")
       +        self.logger.info("on_update_fulfill_htlc")
                chan = self.channels[update_fulfill_htlc_msg["channel_id"]]
                preimage = update_fulfill_htlc_msg["payment_preimage"]
                htlc_id = int.from_bytes(update_fulfill_htlc_msg["id"], "big")
       t@@ -1118,7 +1127,7 @@ class Peer(Logger):
                self.payment_preimages[sha256(preimage)].put_nowait(preimage)
        
            def on_update_fail_malformed_htlc(self, payload):
       -        self.logger.info(f"error {payload['data'].decode('ascii')}")
       +        self.logger.info(f"on_update_fail_malformed_htlc. error {payload['data'].decode('ascii')}")
        
            def on_update_add_htlc(self, payload):
                # no onion routing for the moment: we assume we are the end node
   DIR diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py
       t@@ -350,7 +350,6 @@ class TestChannel(unittest.TestCase):
                self.assertEqual(len(com().outputs()), 2)
                self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
        
       -        self.assertEqual(alice_channel.hm.log.keys(), set([LOCAL, REMOTE]))
                self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
                alice_channel.serialize()
        
       t@@ -621,21 +620,6 @@ class TestChannel(unittest.TestCase):
                    self.alice_channel.add_htlc(new)
                self.assertIn('Not enough local balance', cm.exception.args[0])
        
       -    def test_sign_commitment_is_pure(self):
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -        self.htlc_dict['payment_hash'] = bitcoin.sha256(b'\x02' * 32)
       -        self.alice_channel.add_htlc(self.htlc_dict)
       -        before_signing = self.alice_channel.to_save()
       -        self.alice_channel.sign_next_commitment()
       -        after_signing = self.alice_channel.to_save()
       -        try:
       -            self.assertEqual(before_signing, after_signing)
       -        except:
       -            try:
       -                from deepdiff import DeepDiff
       -            except ImportError:
       -                raise
       -            raise Exception(pformat(DeepDiff(before_signing, after_signing)))
        
        class TestAvailableToSpend(unittest.TestCase):
            def test_DesyncHTLCs(self):