Browse Source

lnchannel: when adding HTLCs, run checks for both directions

hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
5c8455d00b
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 49
      electrum/lnchannel.py
  2. 2
      electrum/tests/test_lnchannel.py

49
electrum/lnchannel.py

@ -404,28 +404,35 @@ class Channel(Logger):
if self.lnworker: if self.lnworker:
self.lnworker.network.trigger_callback('channel', self) self.lnworker.network.trigger_callback('channel', self)
def _assert_we_can_add_htlc(self, amount_msat: int) -> None: def _assert_can_add_htlc(self, *, htlc_proposer: HTLCOwner, amount_msat: int) -> None:
"""Raises PaymentFailure if the local party cannot add this new HTLC. """Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
(this is relevant both for payments initiated by us and when forwarding) (this is relevant both for forwarding and endpoint)
""" """
# TODO check if this method uses correct ctns (should use "latest" + 1) # 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(): if self.is_closed():
raise PaymentFailure('Channel closed') raise PaymentFailure('Channel closed')
if self.get_state() != channel_states.OPEN: if self.get_state() != channel_states.OPEN:
raise PaymentFailure('Channel not open', self.get_state()) raise PaymentFailure('Channel not open', self.get_state())
if not self.can_send_ctx_updates(): if htlc_proposer == LOCAL:
raise PaymentFailure('Channel cannot send ctx updates') if not self.can_send_ctx_updates():
if not self.can_send_update_add_htlc(): raise PaymentFailure('Channel cannot send ctx updates')
raise PaymentFailure('Channel cannot add htlc') if not self.can_send_update_add_htlc():
if self.available_to_spend(LOCAL) < amount_msat: raise PaymentFailure('Channel cannot add htlc')
raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}') if amount_msat <= 0:
if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs: 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') raise PaymentFailure('Too many HTLCs already in channel')
current_htlc_sum = (htlcsum(self.hm.htlcs_by_direction(LOCAL, SENT).values()) current_htlc_sum = (htlcsum(self.hm.htlcs_by_direction(htlc_proposer, SENT).values())
+ htlcsum(self.hm.htlcs_by_direction(LOCAL, RECEIVED).values())) + htlcsum(self.hm.htlcs_by_direction(htlc_proposer, RECEIVED).values()))
if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat: 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 plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat') raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat '
if amount_msat < self.config[REMOTE].htlc_minimum_msat: 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') 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: 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") 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(): if self.is_frozen():
return False return False
try: try:
self._assert_we_can_add_htlc(amount_msat) self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=amount_msat)
except PaymentFailure: except PaymentFailure:
return False return False
return True return True
@ -459,7 +466,7 @@ class Channel(Logger):
if isinstance(htlc, dict): # legacy conversion # FIXME remove if isinstance(htlc, dict): # legacy conversion # FIXME remove
htlc = UpdateAddHtlc(**htlc) htlc = UpdateAddHtlc(**htlc)
assert isinstance(htlc, UpdateAddHtlc) 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: if htlc.htlc_id is None:
htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(LOCAL)) htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(LOCAL))
with self.db_lock: with self.db_lock:
@ -478,12 +485,12 @@ class Channel(Logger):
if isinstance(htlc, dict): # legacy conversion # FIXME remove if isinstance(htlc, dict): # legacy conversion # FIXME remove
htlc = UpdateAddHtlc(**htlc) htlc = UpdateAddHtlc(**htlc)
assert isinstance(htlc, UpdateAddHtlc) 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 if htlc.htlc_id is None: # used in unit tests
htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(REMOTE)) 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: with self.db_lock:
self.hm.recv_htlc(htlc) self.hm.recv_htlc(htlc)
local_ctn = self.get_latest_ctn(LOCAL) local_ctn = self.get_latest_ctn(LOCAL)

2
electrum/tests/test_lnchannel.py

@ -671,7 +671,7 @@ class TestAvailableToSpend(ElectrumTestCase):
bob_channel._ignore_max_htlc_value = False bob_channel._ignore_max_htlc_value = False
with self.assertRaises(lnutil.PaymentFailure): with self.assertRaises(lnutil.PaymentFailure):
alice_channel.add_htlc(htlc_dict) alice_channel.add_htlc(htlc_dict)
with self.assertRaises(lnutil.PaymentFailure): with self.assertRaises(lnutil.RemoteMisbehaving):
bob_channel.receive_htlc(htlc_dict) bob_channel.receive_htlc(htlc_dict)
alice_channel._ignore_max_htlc_value = True alice_channel._ignore_max_htlc_value = True

Loading…
Cancel
Save