commit 5c8455d00b367da8d37cc54a15d1a655779cf687
parent 01207316aa078264d79dcad0ff88b3fd1417b613
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:
2 files changed, 29 insertions(+), 22 deletions(-)
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -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")
@@ -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
@@ -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:
@@ -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)
diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py
@@ -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