Skip to content

Commit

Permalink
swaps: use longer final_cltv_delta for client-normal-swap
Browse files Browse the repository at this point in the history
This gives more time for the client to come back online.

see #8940

- re note on submarine_swaps.py#L53:
  lnpeer.Peer.maybe_fulfill_htlc only checks against MIN_FINAL_CLTV_DELTA_ACCEPTED(=144),
  so this increased cltv_delta is not enforced when receiving the htlc on ln.
  It is put in the invoice, so the sender is supposed to honour it ofc.
  It would be nice to enforce it (make the check in maybe_fulfill_htlc dependent on
  what was in the invoice).
  • Loading branch information
SomberNight committed Mar 12, 2024
1 parent ff50487 commit af6a1f3
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 2 deletions.
1 change: 1 addition & 0 deletions electrum/lnpeer.py
Expand Up @@ -2119,6 +2119,7 @@ def log_fail_reason(reason: str):
else:
raise Exception(f"unexpected {mpp_resolution=}")

# TODO check against actual min_final_cltv_expiry_delta from invoice (and give 2-3 blocks of leeway?)
if local_height + MIN_FINAL_CLTV_DELTA_ACCEPTED > htlc.cltv_abs:
if not already_forwarded:
log_fail_reason(f"htlc.cltv_abs is unreasonably close")
Expand Down
7 changes: 5 additions & 2 deletions electrum/lnworker.py
Expand Up @@ -2146,9 +2146,10 @@ def get_bolt11_invoice(
payment_hash: bytes,
amount_msat: Optional[int],
message: str,
expiry: int,
expiry: int, # expiration of invoice (in seconds, relative)
fallback_address: Optional[str],
channels: Optional[Sequence[Channel]] = None,
min_final_cltv_expiry_delta: Optional[int] = None,
) -> Tuple[LnAddr, str]:
assert isinstance(payment_hash, bytes), f"expected bytes, but got {type(payment_hash)}"

Expand All @@ -2169,12 +2170,14 @@ def get_bolt11_invoice(
amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None
if expiry == 0:
expiry = LN_EXPIRY_NEVER
if min_final_cltv_expiry_delta is None:
min_final_cltv_expiry_delta = MIN_FINAL_CLTV_DELTA_FOR_INVOICE
lnaddr = LnAddr(
paymenthash=payment_hash,
amount=amount_btc,
tags=[
('d', message),
('c', MIN_FINAL_CLTV_DELTA_FOR_INVOICE),
('c', min_final_cltv_expiry_delta),
('x', expiry),
('9', invoice_features),
('f', fallback_address),
Expand Down
12 changes: 12 additions & 0 deletions electrum/submarine_swaps.py
Expand Up @@ -50,9 +50,11 @@
MIN_LOCKTIME_DELTA = 60
LOCKTIME_DELTA_REFUND = 70
MAX_LOCKTIME_DELTA = 100
MIN_FINAL_CLTV_DELTA_FOR_CLIENT = 3 * 144 # note: put in invoice, but is not enforced by receiver in lnpeer.py
assert MIN_LOCKTIME_DELTA <= LOCKTIME_DELTA_REFUND <= MAX_LOCKTIME_DELTA
assert MAX_LOCKTIME_DELTA < lnutil.MIN_FINAL_CLTV_DELTA_ACCEPTED
assert MAX_LOCKTIME_DELTA < lnutil.MIN_FINAL_CLTV_DELTA_FOR_INVOICE
assert MAX_LOCKTIME_DELTA < MIN_FINAL_CLTV_DELTA_FOR_CLIENT


# The script of the reverse swaps has one extra check in it to verify
Expand Down Expand Up @@ -443,6 +445,7 @@ def add_normal_swap(
our_privkey: bytes,
prepay: bool,
channels: Optional[Sequence['Channel']] = None,
min_final_cltv_expiry_delta: Optional[int] = None,
) -> Tuple[SwapData, str, str]:
"""creates a hold invoice"""
if prepay:
Expand All @@ -458,6 +461,7 @@ def add_normal_swap(
expiry=300,
fallback_address=None,
channels=channels,
min_final_cltv_expiry_delta=min_final_cltv_expiry_delta,
)
# add payment info to lnworker
self.lnworker.add_payment_info_for_hold_invoice(payment_hash, invoice_amount_sat)
Expand All @@ -471,6 +475,7 @@ def add_normal_swap(
expiry=300,
fallback_address=None,
channels=channels,
min_final_cltv_expiry_delta=min_final_cltv_expiry_delta,
)
self.lnworker.bundle_payments([payment_hash, prepay_hash])
self.prepayments[prepay_hash] = payment_hash
Expand Down Expand Up @@ -666,6 +671,13 @@ async def request_normal_swap(
our_privkey=refund_privkey,
prepay=False,
channels=channels,
# When the client is doing a normal swap, we create a ln-invoice with larger than usual final_cltv_delta.
# If the user goes offline after broadcasting the funding tx (but before it is mined and
# the server claims it), they need to come back online before the held ln-htlc expires (see #8940).
# If the held ln-htlc expires, and the funding tx got confirmed, the server will have claimed the onchain
# funds, and the ln-htlc will be timed out onchain (and channel force-closed). i.e. the user loses the swap
# amount. Increasing the final_cltv_delta the user puts in the invoice extends this critical window.
min_final_cltv_expiry_delta=MIN_FINAL_CLTV_DELTA_FOR_CLIENT,
)
return swap, invoice

Expand Down

0 comments on commit af6a1f3

Please sign in to comment.