commit d2d4d19fcbf75ad5d64e9ef78c34d0bfc3b2164f
parent 0973b869251895fb796d98afcb877adad4c327b8
Author: SomberNight <somber.night@protonmail.com>
Date: Wed, 14 Aug 2019 21:41:24 +0200
lnpeer: add a few sanity checks to payment-forwarding (and related)
Diffstat:
2 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/electrum/lnonion.py b/electrum/lnonion.py
@@ -378,8 +378,8 @@ class OnionFailureCode(IntEnum):
FEE_INSUFFICIENT = UPDATE | 12
INCORRECT_CLTV_EXPIRY = UPDATE | 13
EXPIRY_TOO_SOON = UPDATE | 14
- UNKNOWN_PAYMENT_HASH = PERM | 15
- INCORRECT_PAYMENT_AMOUNT = PERM | 16
+ INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS = PERM | 15
+ _LEGACY_INCORRECT_PAYMENT_AMOUNT = PERM | 16
FINAL_EXPIRY_TOO_SOON = 17
FINAL_INCORRECT_CLTV_EXPIRY = 18
FINAL_INCORRECT_HTLC_AMOUNT = 19
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
@@ -31,6 +31,7 @@ from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, ca
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage,
ProcessedOnionPacket)
from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture
+from . import lnutil
from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
funding_output_script, get_per_commitment_secret_from_seed,
@@ -39,7 +40,8 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
get_ln_flag_pair_of_bit, privkey_to_pubkey, UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_ACCEPTED,
LightningPeerConnectionClosed, HandshakeFailed, NotFoundChanAnnouncementForUpdate,
MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED, MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED,
- MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED, RemoteMisbehaving, DEFAULT_TO_SELF_DELAY)
+ MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED, RemoteMisbehaving, DEFAULT_TO_SELF_DELAY,
+ NBLOCK_OUR_CLTV_EXPIRY_DELTA, format_short_channel_id)
from .lnutil import FeeUpdate
from .lntransport import LNTransport, LNTransportBase
from .lnmsg import encode_msg, decode_msg
@@ -1110,6 +1112,8 @@ class Peer(Logger):
async def await_remote(self, chan: Channel, ctn: int):
self.maybe_send_commitment(chan)
+ # TODO review this. I suspect some callers want updates irrevocably committed,
+ # so comparision should use chan.get_oldest_unrevoked_ctn(REMOTE)
while chan.get_latest_ctn(REMOTE) <= ctn:
await self._remote_changed_events[chan.channel_id].wait()
@@ -1200,9 +1204,11 @@ class Peer(Logger):
onion_packet = OnionPacket.from_bytes(payload["onion_routing_packet"])
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
chan = self.channels[channel_id]
- assert chan.get_state() == "OPEN"
+ if chan.get_state() != "OPEN":
+ raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()}")
if cltv_expiry >= 500_000_000:
- pass # TODO fail the channel
+ self.lnworker.force_close_channel(channel_id)
+ raise RemoteMisbehaving(f"received update_add_htlc with cltv_expiry >= 500_000_000. value was {cltv_expiry}")
# add htlc
htlc = UpdateAddHtlc(amount_msat=amount_msat_htlc,
payment_hash=payment_hash,
@@ -1243,15 +1249,44 @@ class Peer(Logger):
return
dph = processed_onion.hop_data.per_hop
next_chan = self.lnworker.get_channel_by_short_id(dph.short_channel_id)
+ next_chan_scid = format_short_channel_id(dph.short_channel_id)
next_peer = self.lnworker.peers[next_chan.node_id]
- if next_chan is None or next_chan.get_state() != 'OPEN':
- self.logger.info(f"cannot forward htlc {next_chan.get_state() if next_chan else None}")
- reason = OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
+ local_height = self.network.get_local_height()
+ if next_chan is None:
+ self.logger.info(f"cannot forward htlc. cannot find next_chan {next_chan_scid}")
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'')
+ await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
+ return
+ if next_chan.get_state() != 'OPEN':
+ self.logger.info(f"cannot forward htlc. next_chan not OPEN: {next_chan_scid} in state {next_chan.get_state()}")
+ #reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=) # FIXME data
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
return
- self.logger.info(f'forwarding htlc to {next_chan.node_id}')
next_cltv_expiry = int.from_bytes(dph.outgoing_cltv_value, 'big')
+ if htlc.cltv_expiry - next_cltv_expiry < NBLOCK_OUR_CLTV_EXPIRY_DELTA:
+ #reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=) # FIXME data
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
+ await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
+ return
+ if htlc.cltv_expiry - lnutil.NBLOCK_DEADLINE_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS <= local_height \
+ or next_cltv_expiry <= local_height:
+ #reason = OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_SOON, data=) # FIXME data
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
+ await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
+ return
+ if max(htlc.cltv_expiry, next_cltv_expiry) > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE:
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'')
+ await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
+ return
next_amount_msat_htlc = int.from_bytes(dph.amt_to_forward, 'big')
+ if htlc.amount_msat - next_amount_msat_htlc < 0: # TODO fees?
+ # reason = OnionRoutingFailureMessage(code=OnionFailureCode.FEE_INSUFFICIENT, data=) # FIXME data
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
+ await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
+ return
+
+ self.logger.info(f'forwarding htlc to {next_chan.node_id}')
next_htlc = UpdateAddHtlc(amount_msat=next_amount_msat_htlc, payment_hash=htlc.payment_hash, cltv_expiry=next_cltv_expiry, timestamp=int(time.time()))
next_htlc = next_chan.add_htlc(next_htlc)
next_remote_ctn = next_chan.get_latest_ctn(REMOTE)
@@ -1280,13 +1315,13 @@ class Peer(Logger):
invoice = self.lnworker.get_invoice(htlc.payment_hash)
preimage = self.lnworker.get_preimage(htlc.payment_hash)
except UnknownPaymentHash:
- reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_PAYMENT_HASH, data=b'')
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
return
expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000) if invoice.amount is not None else None
if expected_received_msat is not None and \
- (htlc.amount_msat < expected_received_msat or htlc.amount_msat > 2 * expected_received_msat):
- reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_PAYMENT_AMOUNT, data=b'')
+ not (expected_received_msat <= htlc.amount_msat <= 2 * expected_received_msat):
+ reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
return
local_height = self.network.get_local_height()