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