tlnchannel: when adding HTLCs, run checks for both directions - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 5c8455d00b367da8d37cc54a15d1a655779cf687
 (DIR) parent 01207316aa078264d79dcad0ff88b3fd1417b613
 (HTM) Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 26 Mar 2020 06:42:08 +0100
       
       lnchannel: when adding HTLCs, run checks for both directions
       
       Diffstat:
         M electrum/lnchannel.py               |      49 ++++++++++++++++++-------------
         M electrum/tests/test_lnchannel.py    |       2 +-
       
       2 files changed, 29 insertions(+), 22 deletions(-)
       ---
 (DIR) diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -404,28 +404,35 @@ class Channel(Logger):
                if self.lnworker:
                    self.lnworker.network.trigger_callback('channel', self)
        
       -    def _assert_we_can_add_htlc(self, amount_msat: int) -> None:
       -        """Raises PaymentFailure if the local party cannot add this new HTLC.
       -        (this is relevant both for payments initiated by us and when forwarding)
       +    def _assert_can_add_htlc(self, *, htlc_proposer: HTLCOwner, amount_msat: int) -> None:
       +        """Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
       +        (this is relevant both for forwarding and endpoint)
                """
                # TODO check if this method uses correct ctns (should use "latest" + 1)
       +        # TODO review all these checks... e.g. shouldn't we check both parties' ctx sometimes?
       +        htlc_receiver = htlc_proposer.inverted()
                if self.is_closed():
                    raise PaymentFailure('Channel closed')
                if self.get_state() != channel_states.OPEN:
                    raise PaymentFailure('Channel not open', self.get_state())
       -        if not self.can_send_ctx_updates():
       -            raise PaymentFailure('Channel cannot send ctx updates')
       -        if not self.can_send_update_add_htlc():
       -            raise PaymentFailure('Channel cannot add htlc')
       -        if self.available_to_spend(LOCAL) < amount_msat:
       -            raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}')
       -        if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs:
       +        if htlc_proposer == LOCAL:
       +            if not self.can_send_ctx_updates():
       +                raise PaymentFailure('Channel cannot send ctx updates')
       +            if not self.can_send_update_add_htlc():
       +                raise PaymentFailure('Channel cannot add htlc')
       +        if amount_msat <= 0:
       +            raise PaymentFailure("HTLC value cannot must be >= 0")
       +        if self.available_to_spend(htlc_proposer) < amount_msat:
       +            raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(htlc_proposer)}, Need: {amount_msat}')
       +        if len(self.hm.htlcs(htlc_proposer)) + 1 > self.config[htlc_receiver].max_accepted_htlcs:
                    raise PaymentFailure('Too many HTLCs already in channel')
       -        current_htlc_sum = (htlcsum(self.hm.htlcs_by_direction(LOCAL, SENT).values())
       -                            + htlcsum(self.hm.htlcs_by_direction(LOCAL, RECEIVED).values()))
       -        if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat:
       -            raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat')
       -        if amount_msat < self.config[REMOTE].htlc_minimum_msat:
       +        current_htlc_sum = (htlcsum(self.hm.htlcs_by_direction(htlc_proposer, SENT).values())
       +                            + htlcsum(self.hm.htlcs_by_direction(htlc_proposer, RECEIVED).values()))
       +        if current_htlc_sum + amount_msat > self.config[htlc_receiver].max_htlc_value_in_flight_msat:
       +            raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat '
       +                                 f'plus new htlc: {amount_msat/1000} sat) '
       +                                 f'would exceed max allowed: {self.config[htlc_receiver].max_htlc_value_in_flight_msat/1000} sat')
       +        if amount_msat < self.config[htlc_receiver].htlc_minimum_msat:
                    raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
                if amount_msat > LN_MAX_HTLC_VALUE_MSAT and not self._ignore_max_htlc_value:
                    raise PaymentFailure(f"HTLC value over protocol maximum: {amount_msat} > {LN_MAX_HTLC_VALUE_MSAT} msat")
       t@@ -437,7 +444,7 @@ class Channel(Logger):
                if self.is_frozen():
                    return False
                try:
       -            self._assert_we_can_add_htlc(amount_msat)
       +            self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=amount_msat)
                except PaymentFailure:
                    return False
                return True
       t@@ -459,7 +466,7 @@ class Channel(Logger):
                if isinstance(htlc, dict):  # legacy conversion  # FIXME remove
                    htlc = UpdateAddHtlc(**htlc)
                assert isinstance(htlc, UpdateAddHtlc)
       -        self._assert_we_can_add_htlc(htlc.amount_msat)
       +        self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=htlc.amount_msat)
                if htlc.htlc_id is None:
                    htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(LOCAL))
                with self.db_lock:
       t@@ -478,12 +485,12 @@ class Channel(Logger):
                if isinstance(htlc, dict):  # legacy conversion  # FIXME remove
                    htlc = UpdateAddHtlc(**htlc)
                assert isinstance(htlc, UpdateAddHtlc)
       +        try:
       +            self._assert_can_add_htlc(htlc_proposer=REMOTE, amount_msat=htlc.amount_msat)
       +        except PaymentFailure as e:
       +            raise RemoteMisbehaving(e) from e
                if htlc.htlc_id is None:  # used in unit tests
                    htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(REMOTE))
       -        if 0 <= self.available_to_spend(REMOTE) < htlc.amount_msat:
       -            raise RemoteMisbehaving('Remote dipped below channel reserve.' +\
       -                    f' Available at remote: {self.available_to_spend(REMOTE)},' +\
       -                    f' HTLC amount: {htlc.amount_msat}')
                with self.db_lock:
                    self.hm.recv_htlc(htlc)
                    local_ctn = self.get_latest_ctn(LOCAL)
 (DIR) diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py
       t@@ -671,7 +671,7 @@ class TestAvailableToSpend(ElectrumTestCase):
                bob_channel._ignore_max_htlc_value = False
                with self.assertRaises(lnutil.PaymentFailure):
                    alice_channel.add_htlc(htlc_dict)
       -        with self.assertRaises(lnutil.PaymentFailure):
       +        with self.assertRaises(lnutil.RemoteMisbehaving):
                    bob_channel.receive_htlc(htlc_dict)
        
                alice_channel._ignore_max_htlc_value = True