diff --git a/electrum/channel_db.py b/electrum/channel_db.py index 31b234f15..243f6559c 100644 --- a/electrum/channel_db.py +++ b/electrum/channel_db.py @@ -103,7 +103,7 @@ class ChannelInfo(NamedTuple): class Policy(NamedTuple): key: bytes - cltv_expiry_delta: int + cltv_delta: int htlc_minimum_msat: int htlc_maximum_msat: Optional[int] fee_base_msat: int @@ -116,7 +116,7 @@ class Policy(NamedTuple): def from_msg(payload: dict) -> 'Policy': return Policy( key = payload['short_channel_id'] + payload['start_node'], - cltv_expiry_delta = payload['cltv_expiry_delta'], + cltv_delta = payload['cltv_expiry_delta'], htlc_minimum_msat = payload['htlc_minimum_msat'], htlc_maximum_msat = payload.get('htlc_maximum_msat', None), fee_base_msat = payload['fee_base_msat'], @@ -136,7 +136,7 @@ class Policy(NamedTuple): def from_route_edge(route_edge: 'RouteEdge') -> 'Policy': return Policy( key=route_edge.short_channel_id + route_edge.start_node, - cltv_expiry_delta=route_edge.cltv_expiry_delta, + cltv_delta=route_edge.cltv_delta, htlc_minimum_msat=0, htlc_maximum_msat=None, fee_base_msat=route_edge.fee_base_msat, @@ -441,10 +441,10 @@ class ChannelDB(SqlDB): def policy_changed(self, old_policy: Policy, new_policy: Policy, verbose: bool) -> bool: changed = False - if old_policy.cltv_expiry_delta != new_policy.cltv_expiry_delta: + if old_policy.cltv_delta != new_policy.cltv_delta: changed |= True if verbose: - self.logger.info(f'cltv_expiry_delta: {old_policy.cltv_expiry_delta} -> {new_policy.cltv_expiry_delta}') + self.logger.info(f'cltv_expiry_delta: {old_policy.cltv_delta} -> {new_policy.cltv_delta}') if old_policy.htlc_minimum_msat != new_policy.htlc_minimum_msat: changed |= True if verbose: diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 2bcc079e1..60c170c03 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -85,7 +85,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem: it = HTLCItem(_('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id)) it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format_msat(i.amount_msat))]) - it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))]) + it.appendRow([HTLCItem(_('CLTV expiry')), HTLCItem(str(i.cltv_abs))]) it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(i.payment_hash.hex())]) return it diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index f5b8b681a..791dba706 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path -from electrum.util import EventListener, event_listener +from electrum.util import EventListener, event_listener, get_logger from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST from electrum.logging import Logger from electrum.qrreader import MissingQrDetectionLib @@ -52,6 +52,8 @@ else: MONOSPACE_FONT = 'monospace' +_logger = get_logger(__name__) + dialogs = [] pr_icons = { diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index 65ee3e9dc..c409a705b 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -336,7 +336,7 @@ class LnAddr(object): ", ".join([k + '=' + str(v) for k, v in self.tags]) ) - def get_min_final_cltv_expiry(self) -> int: + def get_min_final_cltv_delta(self) -> int: cltv = self.get_tag('c') if cltv is None: return 18 @@ -375,7 +375,7 @@ class LnAddr(object): 'description': self.get_description(), 'exp': self.get_expiry(), 'time': self.date, - 'min_final_cltv_expiry': self.get_min_final_cltv_expiry(), + 'min_final_cltv_delta': self.get_min_final_cltv_delta(), 'features': self.get_features().get_names(), 'tags': self.tags, 'unknown_tags': self.unknown_tags, diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 7c0f24807..bf627df53 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -631,7 +631,7 @@ class Channel(AbstractChannel): # TODO enforce this ^ # our forwarding parameters for forwarding HTLCs through this channel - forwarding_cltv_expiry_delta = 144 + forwarding_cltv_delta = 144 forwarding_fee_base_msat = 1000 forwarding_fee_proportional_millionths = 1 @@ -784,7 +784,7 @@ class Channel(AbstractChannel): short_channel_id=scid, channel_flags=channel_flags, message_flags=b'\x01', - cltv_expiry_delta=self.forwarding_cltv_expiry_delta, + cltv_expiry_delta=self.forwarding_cltv_delta, htlc_minimum_msat=self.config[REMOTE].htlc_minimum_msat, htlc_maximum_msat=htlc_maximum_msat, fee_base_msat=self.forwarding_fee_base_msat, @@ -1549,7 +1549,7 @@ class Channel(AbstractChannel): remote_htlc_pubkey=other_htlc_pubkey, local_htlc_pubkey=this_htlc_pubkey, payment_hash=htlc.payment_hash, - cltv_expiry=htlc.cltv_expiry), htlc)) + cltv_abs=htlc.cltv_abs), htlc)) # note: maybe flip initiator here for fee purposes, we want LOCAL and REMOTE # in the resulting dict to correspond to the to_local and to_remote *outputs* of the ctx onchain_fees = calc_fees_for_commitment_tx( @@ -1656,24 +1656,24 @@ class Channel(AbstractChannel): # If there is a received HTLC for which we already released the preimage # but the remote did not revoke yet, and the CLTV of this HTLC is dangerously close # to the present, then unilaterally close channel - recv_htlc_deadline = lnutil.NBLOCK_DEADLINE_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS + recv_htlc_deadline_delta = lnutil.NBLOCK_DEADLINE_DELTA_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS for sub, dir, ctn in ((LOCAL, RECEIVED, self.get_latest_ctn(LOCAL)), (REMOTE, SENT, self.get_oldest_unrevoked_ctn(REMOTE)), (REMOTE, SENT, self.get_latest_ctn(REMOTE)),): for htlc_id, htlc in self.hm.htlcs_by_direction(subject=sub, direction=dir, ctn=ctn).items(): if not self.hm.was_htlc_preimage_released(htlc_id=htlc_id, htlc_proposer=REMOTE): continue - if htlc.cltv_expiry - recv_htlc_deadline > local_height: + if htlc.cltv_abs - recv_htlc_deadline_delta > local_height: continue htlcs_we_could_reclaim[(RECEIVED, htlc_id)] = htlc # If there is an offered HTLC which has already expired (+ some grace period after), we # will unilaterally close the channel and time out the HTLC - offered_htlc_deadline = lnutil.NBLOCK_DEADLINE_AFTER_EXPIRY_FOR_OFFERED_HTLCS + offered_htlc_deadline_delta = lnutil.NBLOCK_DEADLINE_DELTA_AFTER_EXPIRY_FOR_OFFERED_HTLCS for sub, dir, ctn in ((LOCAL, SENT, self.get_latest_ctn(LOCAL)), (REMOTE, RECEIVED, self.get_oldest_unrevoked_ctn(REMOTE)), (REMOTE, RECEIVED, self.get_latest_ctn(REMOTE)),): for htlc_id, htlc in self.hm.htlcs_by_direction(subject=sub, direction=dir, ctn=ctn).items(): - if htlc.cltv_expiry + offered_htlc_deadline > local_height: + if htlc.cltv_abs + offered_htlc_deadline_delta > local_height: continue htlcs_we_could_reclaim[(SENT, htlc_id)] = htlc diff --git a/electrum/lnonion.py b/electrum/lnonion.py index 0f3a7a000..71d1af179 100644 --- a/electrum/lnonion.py +++ b/electrum/lnonion.py @@ -212,23 +212,23 @@ def new_onion_packet( def calc_hops_data_for_payment( route: 'LNPaymentRoute', - amount_msat: int, - final_cltv: int, *, + amount_msat: int, # that final recipient receives + *, + final_cltv_abs: int, total_msat: int, payment_secret: bytes, ) -> Tuple[List[OnionHopsDataSingle], int, int]: - """Returns the hops_data to be used for constructing an onion packet, - and the amount_msat and cltv to be used on our immediate channel. + and the amount_msat and cltv_abs to be used on our immediate channel. """ if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH: raise PaymentFailure(f"too long route ({len(route)} edges)") # payload that will be seen by the last hop: amt = amount_msat - cltv = final_cltv + cltv_abs = final_cltv_abs hop_payload = { "amt_to_forward": {"amt_to_forward": amt}, - "outgoing_cltv_value": {"outgoing_cltv_value": cltv}, + "outgoing_cltv_value": {"outgoing_cltv_value": cltv_abs}, } # for multipart payments we need to tell the receiver about the total and # partial amounts @@ -244,19 +244,19 @@ def calc_hops_data_for_payment( is_trampoline = route_edge.is_trampoline() if is_trampoline: amt += route_edge.fee_for_edge(amt) - cltv += route_edge.cltv_expiry_delta + cltv_abs += route_edge.cltv_delta hop_payload = { "amt_to_forward": {"amt_to_forward": amt}, - "outgoing_cltv_value": {"outgoing_cltv_value": cltv}, + "outgoing_cltv_value": {"outgoing_cltv_value": cltv_abs}, "short_channel_id": {"short_channel_id": route_edge.short_channel_id}, } hops_data.append( OnionHopsDataSingle(payload=hop_payload)) if not is_trampoline: amt += route_edge.fee_for_edge(amt) - cltv += route_edge.cltv_expiry_delta + cltv_abs += route_edge.cltv_delta hops_data.reverse() - return hops_data, amt, cltv + return hops_data, amt, cltv_abs def _generate_filler(key_type: bytes, hops_data: Sequence[OnionHopsDataSingle], diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index bfe1ed505..cf54bd525 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -38,7 +38,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf funding_output_script, get_per_commitment_secret_from_seed, secret_to_pubkey, PaymentFailure, LnFeatures, LOCAL, REMOTE, HTLCOwner, - ln_compare_features, privkey_to_pubkey, MIN_FINAL_CLTV_EXPIRY_ACCEPTED, + ln_compare_features, privkey_to_pubkey, MIN_FINAL_CLTV_DELTA_ACCEPTED, LightningPeerConnectionClosed, HandshakeFailed, RemoteMisbehaving, ShortChannelID, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, @@ -1476,25 +1476,25 @@ class Peer(Logger): amount_msat: int, total_msat: int, payment_hash: bytes, - min_final_cltv_expiry: int, + min_final_cltv_delta: int, payment_secret: bytes, trampoline_onion: Optional[OnionPacket] = None, ): # add features learned during "init" for direct neighbour: route[0].node_features |= self.features local_height = self.network.get_local_height() - final_cltv = local_height + min_final_cltv_expiry - hops_data, amount_msat, cltv = calc_hops_data_for_payment( + final_cltv_abs = local_height + min_final_cltv_delta + hops_data, amount_msat, cltv_abs = calc_hops_data_for_payment( route, amount_msat, - final_cltv, + final_cltv_abs=final_cltv_abs, total_msat=total_msat, payment_secret=payment_secret) num_hops = len(hops_data) self.logger.info(f"lnpeer.pay len(route)={len(route)}") for i in range(len(route)): self.logger.info(f" {i}: edge={route[i].short_channel_id} hop_data={hops_data[i]!r}") - assert final_cltv <= cltv, (final_cltv, cltv) + assert final_cltv_abs <= cltv_abs, (final_cltv_abs, cltv_abs) session_key = os.urandom(32) # session_key # if we are forwarding a trampoline payment, add trampoline onion if trampoline_onion: @@ -1517,12 +1517,21 @@ class Peer(Logger): onion = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data=payment_hash) # must use another sessionkey self.logger.info(f"starting payment. len(route)={len(hops_data)}.") # create htlc - if cltv > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: - raise PaymentFailure(f"htlc expiry too far into future. (in {cltv-local_height} blocks)") - return onion, amount_msat, cltv, session_key - - def send_htlc(self, chan, payment_hash, amount_msat, cltv, onion, session_key=None) -> UpdateAddHtlc: - htlc = UpdateAddHtlc(amount_msat=amount_msat, payment_hash=payment_hash, cltv_expiry=cltv, timestamp=int(time.time())) + if cltv_abs > local_height + lnutil.NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE: + raise PaymentFailure(f"htlc expiry too far into future. (in {cltv_abs-local_height} blocks)") + return onion, amount_msat, cltv_abs, session_key + + def send_htlc( + self, + *, + chan: Channel, + payment_hash: bytes, + amount_msat: int, + cltv_abs: int, + onion: OnionPacket, + session_key: Optional[bytes] = None, + ) -> UpdateAddHtlc: + htlc = UpdateAddHtlc(amount_msat=amount_msat, payment_hash=payment_hash, cltv_abs=cltv_abs, timestamp=int(time.time())) htlc = chan.add_htlc(htlc) if session_key: chan.set_onion_key(htlc.htlc_id, session_key) # should it be the outer onion secret? @@ -1531,7 +1540,7 @@ class Peer(Logger): "update_add_htlc", channel_id=chan.channel_id, id=htlc.htlc_id, - cltv_expiry=htlc.cltv_expiry, + cltv_expiry=htlc.cltv_abs, amount_msat=htlc.amount_msat, payment_hash=htlc.payment_hash, onion_routing_packet=onion.to_bytes()) @@ -1544,7 +1553,7 @@ class Peer(Logger): amount_msat: int, total_msat: int, payment_hash: bytes, - min_final_cltv_expiry: int, + min_final_cltv_delta: int, payment_secret: bytes, trampoline_onion: Optional[OnionPacket] = None, ) -> UpdateAddHtlc: @@ -1553,16 +1562,23 @@ class Peer(Logger): assert len(route) > 0 if not chan.can_send_update_add_htlc(): raise PaymentFailure("Channel cannot send update_add_htlc") - onion, amount_msat, cltv, session_key = self.create_onion_for_route( + onion, amount_msat, cltv_abs, session_key = self.create_onion_for_route( route=route, amount_msat=amount_msat, total_msat=total_msat, payment_hash=payment_hash, - min_final_cltv_expiry=min_final_cltv_expiry, + min_final_cltv_delta=min_final_cltv_delta, payment_secret=payment_secret, trampoline_onion=trampoline_onion ) - htlc = self.send_htlc(chan, payment_hash, amount_msat, cltv, onion, session_key=session_key) + htlc = self.send_htlc( + chan=chan, + payment_hash=payment_hash, + amount_msat=amount_msat, + cltv_abs=cltv_abs, + onion=onion, + session_key=session_key, + ) return htlc def send_revoke_and_ack(self, chan: Channel): @@ -1619,21 +1635,21 @@ class Peer(Logger): def on_update_add_htlc(self, chan: Channel, payload): payment_hash = payload["payment_hash"] htlc_id = payload["id"] - cltv_expiry = payload["cltv_expiry"] + cltv_abs = payload["cltv_expiry"] amount_msat_htlc = payload["amount_msat"] onion_packet = payload["onion_routing_packet"] htlc = UpdateAddHtlc( amount_msat=amount_msat_htlc, payment_hash=payment_hash, - cltv_expiry=cltv_expiry, + cltv_abs=cltv_abs, timestamp=int(time.time()), htlc_id=htlc_id) self.logger.info(f"on_update_add_htlc. chan {chan.short_channel_id}. htlc={str(htlc)}") if chan.get_state() != ChannelState.OPEN: raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()!r}") - if cltv_expiry > bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX: + if cltv_abs > bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX: self.schedule_force_closing(chan.channel_id) - raise RemoteMisbehaving(f"received update_add_htlc with cltv_expiry > BLOCKHEIGHT_MAX. value was {cltv_expiry}") + raise RemoteMisbehaving(f"received update_add_htlc with {cltv_abs=} > BLOCKHEIGHT_MAX") # add htlc chan.receive_htlc(htlc, onion_packet) util.trigger_callback('htlc_added', chan, htlc, RECEIVED) @@ -1674,7 +1690,7 @@ class Peer(Logger): except Exception: raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') try: - next_cltv_expiry = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] + next_cltv_abs = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] except Exception: raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid) @@ -1693,16 +1709,16 @@ class Peer(Logger): if not next_chan.can_pay(next_amount_msat_htlc): log_fail_reason(f"transient error (likely due to insufficient funds): not next_chan.can_pay(amt)") raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message) - if htlc.cltv_expiry - next_cltv_expiry < next_chan.forwarding_cltv_expiry_delta: + if htlc.cltv_abs - next_cltv_abs < next_chan.forwarding_cltv_delta: log_fail_reason( f"INCORRECT_CLTV_EXPIRY. " - f"{htlc.cltv_expiry=} - {next_cltv_expiry=} < {next_chan.forwarding_cltv_expiry_delta=}") - data = htlc.cltv_expiry.to_bytes(4, byteorder="big") + outgoing_chan_upd_message + f"{htlc.cltv_abs=} - {next_cltv_abs=} < {next_chan.forwarding_cltv_delta=}") + data = htlc.cltv_abs.to_bytes(4, byteorder="big") + outgoing_chan_upd_message raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=data) - if htlc.cltv_expiry - lnutil.MIN_FINAL_CLTV_EXPIRY_ACCEPTED <= local_height \ - or next_cltv_expiry <= local_height: + if htlc.cltv_abs - lnutil.MIN_FINAL_CLTV_DELTA_ACCEPTED <= local_height \ + or next_cltv_abs <= local_height: raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_SOON, data=outgoing_chan_upd_message) - if max(htlc.cltv_expiry, next_cltv_expiry) > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: + if max(htlc.cltv_abs, next_cltv_abs) > local_height + lnutil.NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE: raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'') forwarding_fees = fee_for_edge_msat( forwarded_amount_msat=next_amount_msat_htlc, @@ -1722,7 +1738,13 @@ class Peer(Logger): log_fail_reason(f"next_peer offline ({next_chan.node_id.hex()})") raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message) try: - next_htlc = next_peer.send_htlc(next_chan, htlc.payment_hash, next_amount_msat_htlc, next_cltv_expiry, processed_onion.next_packet) + next_htlc = next_peer.send_htlc( + chan=next_chan, + payment_hash=htlc.payment_hash, + amount_msat=next_amount_msat_htlc, + cltv_abs=next_cltv_abs, + onion=processed_onion.next_packet, + ) except BaseException as e: log_fail_reason(f"error sending message to next_peer={next_chan.node_id.hex()}") raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message) @@ -1732,7 +1754,7 @@ class Peer(Logger): async def maybe_forward_trampoline( self, *, payment_hash: bytes, - cltv_expiry: int, + inc_cltv_abs: int, outer_onion: ProcessedOnionPacket, trampoline_onion: ProcessedOnionPacket): @@ -1751,7 +1773,7 @@ class Peer(Logger): try: outgoing_node_id = payload["outgoing_node_id"]["outgoing_node_id"] amt_to_forward = payload["amt_to_forward"]["amt_to_forward"] - cltv_from_onion = payload["outgoing_cltv_value"]["outgoing_cltv_value"] + out_cltv_abs = payload["outgoing_cltv_value"]["outgoing_cltv_value"] if "invoice_features" in payload: self.logger.info('forward_trampoline: legacy') next_trampoline_onion = None @@ -1777,11 +1799,11 @@ class Peer(Logger): # these are the fee/cltv paid by the sender # pay_to_node will raise if they are not sufficient - trampoline_cltv_delta = cltv_expiry - cltv_from_onion # cltv budget + trampoline_cltv_delta = inc_cltv_abs - out_cltv_abs # cltv budget total_msat = outer_onion.hop_data.payload["payment_data"]["total_msat"] trampoline_fee = total_msat - amt_to_forward self.logger.info(f'trampoline forwarding. fee_budget={trampoline_fee}') - self.logger.info(f'trampoline forwarding. cltv_budget={trampoline_cltv_delta}. (inc={cltv_expiry}. out={cltv_from_onion})') + self.logger.info(f'trampoline forwarding. cltv_budget={trampoline_cltv_delta}. (inc={inc_cltv_abs}. out={out_cltv_abs})') # To convert abs vs rel cltvs, we need to guess blockheight used by original sender as "current blockheight". # Blocks might have been mined since. # - if we skew towards the past, we decrease our own cltv_budget accordingly (which is ok) @@ -1789,7 +1811,7 @@ class Peer(Logger): # which can result in them failing the payment. # So we skew towards the past and guess that there has been 1 new block mined since the payment began: local_height_of_onion_creator = self.network.get_local_height() - 1 - cltv_budget_for_rest_of_route = cltv_from_onion - local_height_of_onion_creator + cltv_budget_for_rest_of_route = out_cltv_abs - local_height_of_onion_creator try: await self.lnworker.pay_to_node( @@ -1797,10 +1819,10 @@ class Peer(Logger): payment_hash=payment_hash, payment_secret=payment_secret, amount_to_pay=amt_to_forward, - # FIXME this API (min_cltv_expiry) is confusing. The value will be added to local_height + # FIXME this API (min_final_cltv_delta) is confusing. The value will be added to local_height # to form the abs cltv used on the last edge on the path to the *next trampoline* node. # We should rewrite pay_to_node to operate on a cltv-budget (and fee-budget). - min_cltv_expiry=cltv_budget_for_rest_of_route, + min_final_cltv_delta=cltv_budget_for_rest_of_route, r_tags=r_tags, invoice_features=invoice_features, fwd_trampoline_onion=next_trampoline_onion, @@ -1863,20 +1885,20 @@ class Peer(Logger): exc_incorrect_or_unknown_pd = OnionRoutingFailure( code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=amt_to_forward.to_bytes(8, byteorder="big") + local_height.to_bytes(4, byteorder="big")) - if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > htlc.cltv_expiry: - log_fail_reason(f"htlc.cltv_expiry is unreasonably close") + if local_height + MIN_FINAL_CLTV_DELTA_ACCEPTED > htlc.cltv_abs: + log_fail_reason(f"htlc.cltv_abs is unreasonably close") raise exc_incorrect_or_unknown_pd try: - cltv_from_onion = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] + cltv_abs_from_onion = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] except Exception: log_fail_reason(f"'outgoing_cltv_value' missing from onion") raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') - if cltv_from_onion > htlc.cltv_expiry: - log_fail_reason(f"cltv_from_onion != htlc.cltv_expiry") + if cltv_abs_from_onion > htlc.cltv_abs: + log_fail_reason(f"cltv_abs_from_onion != htlc.cltv_abs") raise OnionRoutingFailure( code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY, - data=htlc.cltv_expiry.to_bytes(4, byteorder="big")) + data=htlc.cltv_abs.to_bytes(4, byteorder="big")) try: total_msat = processed_onion.hop_data.payload["payment_data"]["total_msat"] except Exception: @@ -1943,7 +1965,7 @@ class Peer(Logger): else: callback = lambda: self.maybe_forward_trampoline( payment_hash=payment_hash, - cltv_expiry=htlc.cltv_expiry, # TODO: use max or enforce same value across mpp parts + inc_cltv_abs=htlc.cltv_abs, # TODO: use max or enforce same value across mpp parts outer_onion=processed_onion, trampoline_onion=trampoline_onion) return None, callback diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py index 75a195e1e..8b8ea91a0 100644 --- a/electrum/lnrouter.py +++ b/electrum/lnrouter.py @@ -35,7 +35,7 @@ from math import inf from .util import profiler, with_lock from .logging import Logger from .lnutil import (NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, LnFeatures, - NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE) + NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE) from .channel_db import ChannelDB, Policy, NodeInfo if TYPE_CHECKING: @@ -75,7 +75,7 @@ class PathEdge: class RouteEdge(PathEdge): fee_base_msat = attr.ib(type=int, kw_only=True) fee_proportional_millionths = attr.ib(type=int, kw_only=True) - cltv_expiry_delta = attr.ib(type=int, kw_only=True) + cltv_delta = attr.ib(type=int, kw_only=True) node_features = attr.ib(type=int, kw_only=True, repr=lambda val: str(int(val))) # note: for end node! def fee_for_edge(self, amount_msat: int) -> int: @@ -102,13 +102,13 @@ class RouteEdge(PathEdge): short_channel_id=ShortChannelID.normalize(short_channel_id), fee_base_msat=channel_policy.fee_base_msat, fee_proportional_millionths=channel_policy.fee_proportional_millionths, - cltv_expiry_delta=channel_policy.cltv_expiry_delta, + cltv_delta=channel_policy.cltv_delta, node_features=node_info.features if node_info else 0) def is_sane_to_use(self, amount_msat: int) -> bool: # TODO revise ad-hoc heuristics # cltv cannot be more than 2 weeks - if self.cltv_expiry_delta > 14 * 144: + if self.cltv_delta > 14 * 144: return False total_fee = self.fee_for_edge(amount_msat) if not is_fee_sane(total_fee, payment_amount_msat=amount_msat): @@ -135,23 +135,24 @@ class TrampolineEdge(RouteEdge): LNPaymentPath = Sequence[PathEdge] LNPaymentRoute = Sequence[RouteEdge] +LNPaymentTRoute = Sequence[TrampolineEdge] -def is_route_sane_to_use(route: LNPaymentRoute, invoice_amount_msat: int, min_final_cltv_expiry: int) -> bool: +def is_route_sane_to_use(route: LNPaymentRoute, invoice_amount_msat: int, min_final_cltv_delta: int) -> bool: """Run some sanity checks on the whole route, before attempting to use it. called when we are paying; so e.g. lower cltv is better """ if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH: return False amt = invoice_amount_msat - cltv = min_final_cltv_expiry + cltv_delta = min_final_cltv_delta for route_edge in reversed(route[1:]): if not route_edge.is_sane_to_use(amt): return False amt += route_edge.fee_for_edge(amt) - cltv += route_edge.cltv_expiry_delta + cltv_delta += route_edge.cltv_delta total_fee = amt - invoice_amount_msat # TODO revise ad-hoc heuristics - if cltv > NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: + if cltv_delta > NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE: return False # FIXME in case of MPP, the fee checks are done independently for each part, # which is ok for the proportional checks but not for the absolute ones. @@ -527,7 +528,7 @@ class LNPathFinder(Logger): if ignore_costs: return DEFAULT_PENALTY_BASE_MSAT, 0 fee_msat = route_edge.fee_for_edge(payment_amt_msat) - cltv_cost = route_edge.cltv_expiry_delta * payment_amt_msat * 15 / 1_000_000_000 + cltv_cost = route_edge.cltv_delta * payment_amt_msat * 15 / 1_000_000_000 # the liquidty penalty takes care we favor edges that should be able to forward # the payment and penalize edges that cannot liquidity_penalty = self.liquidity_hints.penalty(start_node, end_node, short_channel_id, payment_amt_msat) diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index 91b672899..fe5f78fb7 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -33,7 +33,7 @@ _logger = get_logger(__name__) class SweepInfo(NamedTuple): name: str csv_delay: int - cltv_expiry: int + cltv_abs: int gen_tx: Callable[[], Optional[Transaction]] @@ -177,7 +177,7 @@ def create_sweeptx_for_their_revoked_htlc( return SweepInfo( name='redeem_htlc2', csv_delay=0, - cltv_expiry=0, + cltv_abs=0, gen_tx=gen_tx) @@ -240,7 +240,7 @@ def create_sweeptxs_for_our_ctx( txs[prevout] = SweepInfo( name='our_ctx_to_local', csv_delay=to_self_delay, - cltv_expiry=0, + cltv_abs=0, gen_tx=sweep_tx) we_breached = ctn < chan.get_oldest_unrevoked_ctn(LOCAL) if we_breached: @@ -277,12 +277,12 @@ def create_sweeptxs_for_our_ctx( txs[htlc_tx.inputs()[0].prevout.to_str()] = SweepInfo( name='first-stage-htlc', csv_delay=0, - cltv_expiry=htlc_tx.locktime, + cltv_abs=htlc_tx.locktime, gen_tx=lambda: htlc_tx) txs[htlc_tx.txid() + ':0'] = SweepInfo( name='second-stage-htlc', csv_delay=to_self_delay, - cltv_expiry=0, + cltv_abs=0, gen_tx=sweep_tx) # offered HTLCs, in our ctx --> "timeout" @@ -381,7 +381,7 @@ def create_sweeptxs_for_their_ctx( txs[tx.inputs()[0].prevout.to_str()] = SweepInfo( name='to_local_for_revoked_ctx', csv_delay=0, - cltv_expiry=0, + cltv_abs=0, gen_tx=gen_tx) # prep our_htlc_privkey = derive_privkey(secret=int.from_bytes(our_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp) @@ -400,9 +400,9 @@ def create_sweeptxs_for_their_ctx( remote_htlc_pubkey=our_htlc_privkey.get_public_key_bytes(compressed=True), local_htlc_pubkey=their_htlc_pubkey, payment_hash=htlc.payment_hash, - cltv_expiry=htlc.cltv_expiry) + cltv_abs=htlc.cltv_abs) - cltv_expiry = htlc.cltv_expiry if is_received_htlc and not is_revocation else 0 + cltv_abs = htlc.cltv_abs if is_received_htlc and not is_revocation else 0 prevout = ctx.txid() + ':%d'%ctx_output_idx sweep_tx = lambda: create_sweeptx_their_ctx_htlc( ctx=ctx, @@ -412,12 +412,12 @@ def create_sweeptxs_for_their_ctx( output_idx=ctx_output_idx, privkey=our_revocation_privkey if is_revocation else our_htlc_privkey.get_secret_bytes(), is_revocation=is_revocation, - cltv_expiry=cltv_expiry, + cltv_abs=cltv_abs, config=chan.lnworker.config) txs[prevout] = SweepInfo( name=f'their_ctx_htlc_{ctx_output_idx}', csv_delay=0, - cltv_expiry=cltv_expiry, + cltv_abs=cltv_abs, gen_tx=sweep_tx) # received HTLCs, in their ctx --> "timeout" # offered HTLCs, in their ctx --> "success" @@ -480,8 +480,8 @@ def create_sweeptx_their_ctx_htlc( ctx: Transaction, witness_script: bytes, sweep_address: str, preimage: Optional[bytes], output_idx: int, privkey: bytes, is_revocation: bool, - cltv_expiry: int, config: SimpleConfig) -> Optional[PartialTransaction]: - assert type(cltv_expiry) is int + cltv_abs: int, config: SimpleConfig) -> Optional[PartialTransaction]: + assert type(cltv_abs) is int preimage = preimage or b'' # preimage is required iff (not is_revocation and htlc is offered) val = ctx.outputs()[output_idx].value prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx) @@ -495,7 +495,7 @@ def create_sweeptx_their_ctx_htlc( outvalue = val - fee if outvalue <= dust_threshold(): return None sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)] - tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2, locktime=cltv_expiry) + tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2, locktime=cltv_abs) sig = bfh(tx.sign_txin(0, privkey)) if not is_revocation: witness = construct_witness([sig, preimage, witness_script]) diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 5b8189c2b..eaa33ee62 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -446,20 +446,20 @@ MIN_FUNDING_SAT = 200_000 # the minimum cltv_expiry accepted for newly received HTLCs # note: when changing, consider Blockchain.is_tip_stale() -MIN_FINAL_CLTV_EXPIRY_ACCEPTED = 144 +MIN_FINAL_CLTV_DELTA_ACCEPTED = 144 # set it a tiny bit higher for invoices as blocks could get mined # during forward path of payment -MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE = MIN_FINAL_CLTV_EXPIRY_ACCEPTED + 3 +MIN_FINAL_CLTV_DELTA_FOR_INVOICE = MIN_FINAL_CLTV_DELTA_ACCEPTED + 3 # the deadline for offered HTLCs: # the deadline after which the channel has to be failed and timed out on-chain -NBLOCK_DEADLINE_AFTER_EXPIRY_FOR_OFFERED_HTLCS = 1 +NBLOCK_DEADLINE_DELTA_AFTER_EXPIRY_FOR_OFFERED_HTLCS = 1 # the deadline for received HTLCs this node has fulfilled: # the deadline after which the channel has to be failed and the HTLC fulfilled on-chain before its cltv_expiry -NBLOCK_DEADLINE_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS = 72 +NBLOCK_DEADLINE_DELTA_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS = 72 -NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE = 28 * 144 +NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE = 28 * 144 MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016 @@ -621,14 +621,19 @@ def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int, c_inputs = [txin] return c_inputs -def make_htlc_tx(*, cltv_expiry: int, inputs: List[PartialTxInput], output: PartialTxOutput) -> PartialTransaction: - assert type(cltv_expiry) is int +def make_htlc_tx(*, cltv_abs: int, inputs: List[PartialTxInput], output: PartialTxOutput) -> PartialTransaction: + assert type(cltv_abs) is int c_outputs = [output] - tx = PartialTransaction.from_io(inputs, c_outputs, locktime=cltv_expiry, version=2) + tx = PartialTransaction.from_io(inputs, c_outputs, locktime=cltv_abs, version=2) return tx -def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, - local_htlcpubkey: bytes, payment_hash: bytes) -> bytes: +def make_offered_htlc( + *, + revocation_pubkey: bytes, + remote_htlcpubkey: bytes, + local_htlcpubkey: bytes, + payment_hash: bytes, +) -> bytes: assert type(revocation_pubkey) is bytes assert type(remote_htlcpubkey) is bytes assert type(local_htlcpubkey) is bytes @@ -663,11 +668,17 @@ def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, ])) return script -def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, - local_htlcpubkey: bytes, payment_hash: bytes, cltv_expiry: int) -> bytes: +def make_received_htlc( + *, + revocation_pubkey: bytes, + remote_htlcpubkey: bytes, + local_htlcpubkey: bytes, + payment_hash: bytes, + cltv_abs: int, +) -> bytes: for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]: assert type(i) is bytes - assert type(cltv_expiry) is int + assert type(cltv_abs) is int script = bfh(construct_script([ opcodes.OP_DUP, @@ -693,7 +704,7 @@ def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP, - cltv_expiry, + cltv_abs, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, opcodes.OP_CHECKSIG, @@ -764,14 +775,21 @@ WITNESS_TEMPLATE_RECEIVED_HTLC = [ ] -def make_htlc_output_witness_script(is_received_htlc: bool, remote_revocation_pubkey: bytes, remote_htlc_pubkey: bytes, - local_htlc_pubkey: bytes, payment_hash: bytes, cltv_expiry: Optional[int]) -> bytes: +def make_htlc_output_witness_script( + *, + is_received_htlc: bool, + remote_revocation_pubkey: bytes, + remote_htlc_pubkey: bytes, + local_htlc_pubkey: bytes, + payment_hash: bytes, + cltv_abs: Optional[int], +) -> bytes: if is_received_htlc: return make_received_htlc(revocation_pubkey=remote_revocation_pubkey, remote_htlcpubkey=remote_htlc_pubkey, local_htlcpubkey=local_htlc_pubkey, payment_hash=payment_hash, - cltv_expiry=cltv_expiry) + cltv_abs=cltv_abs) else: return make_offered_htlc(revocation_pubkey=remote_revocation_pubkey, remote_htlcpubkey=remote_htlc_pubkey, @@ -789,7 +807,7 @@ def get_ordered_channel_configs(chan: 'AbstractChannel', for_us: bool) -> Tuple[ def possible_output_idxs_of_htlc_in_ctx(*, chan: 'Channel', pcp: bytes, subject: 'HTLCOwner', htlc_direction: 'Direction', ctx: Transaction, htlc: 'UpdateAddHtlc') -> Set[int]: - amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash + amount_msat, cltv_abs, payment_hash = htlc.amount_msat, htlc.cltv_abs, htlc.payment_hash for_us = subject == LOCAL conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us) @@ -801,7 +819,7 @@ def possible_output_idxs_of_htlc_in_ctx(*, chan: 'Channel', pcp: bytes, subject: remote_htlc_pubkey=other_htlc_pubkey, local_htlc_pubkey=htlc_pubkey, payment_hash=payment_hash, - cltv_expiry=cltv_expiry) + cltv_abs=cltv_abs) htlc_address = redeem_script_to_address('p2wsh', preimage_script.hex()) candidates = ctx.get_output_idxs_from_address(htlc_address) return {output_idx for output_idx in candidates @@ -814,9 +832,9 @@ def map_htlcs_to_ctx_output_idxs(*, chan: 'Channel', ctx: Transaction, pcp: byte htlc_to_ctx_output_idx_map = {} # type: Dict[Tuple[Direction, UpdateAddHtlc], int] unclaimed_ctx_output_idxs = set(range(len(ctx.outputs()))) offered_htlcs = chan.included_htlcs(subject, SENT, ctn=ctn) - offered_htlcs.sort(key=lambda htlc: htlc.cltv_expiry) + offered_htlcs.sort(key=lambda htlc: htlc.cltv_abs) received_htlcs = chan.included_htlcs(subject, RECEIVED, ctn=ctn) - received_htlcs.sort(key=lambda htlc: htlc.cltv_expiry) + received_htlcs.sort(key=lambda htlc: htlc.cltv_abs) for direction, htlcs in zip([SENT, RECEIVED], [offered_htlcs, received_htlcs]): for htlc in htlcs: cands = sorted(possible_output_idxs_of_htlc_in_ctx(chan=chan, @@ -841,7 +859,7 @@ def map_htlcs_to_ctx_output_idxs(*, chan: 'Channel', ctx: Transaction, pcp: byte def make_htlc_tx_with_open_channel(*, chan: 'Channel', pcp: bytes, subject: 'HTLCOwner', ctn: int, htlc_direction: 'Direction', commit: Transaction, ctx_output_idx: int, htlc: 'UpdateAddHtlc', name: str = None) -> Tuple[bytes, PartialTransaction]: - amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash + amount_msat, cltv_abs, payment_hash = htlc.amount_msat, htlc.cltv_abs, htlc.payment_hash for_us = subject == LOCAL conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us) @@ -864,14 +882,14 @@ def make_htlc_tx_with_open_channel(*, chan: 'Channel', pcp: bytes, subject: 'HTL remote_htlc_pubkey=other_htlc_pubkey, local_htlc_pubkey=htlc_pubkey, payment_hash=payment_hash, - cltv_expiry=cltv_expiry) + cltv_abs=cltv_abs) htlc_tx_inputs = make_htlc_tx_inputs( commit.txid(), ctx_output_idx, amount_msat=amount_msat, witness_script=preimage_script.hex()) if is_htlc_success: - cltv_expiry = 0 - htlc_tx = make_htlc_tx(cltv_expiry=cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) + cltv_abs = 0 + htlc_tx = make_htlc_tx(cltv_abs=cltv_abs, inputs=htlc_tx_inputs, output=htlc_tx_output) return witness_script_of_htlc_tx_output, htlc_tx def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes, @@ -1009,7 +1027,7 @@ def make_commitment( # the outputs are ordered in increasing cltv_expiry order." # so we sort by cltv_expiry now; and the later BIP69-sort is assumed to be *stable* htlcs = list(htlcs) - htlcs.sort(key=lambda x: x.htlc.cltv_expiry) + htlcs.sort(key=lambda x: x.htlc.cltv_abs) htlc_outputs, c_outputs_filtered = make_commitment_outputs( fees_per_participant=fees_per_participant, @@ -1595,21 +1613,21 @@ NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH class UpdateAddHtlc: amount_msat = attr.ib(type=int, kw_only=True) payment_hash = attr.ib(type=bytes, kw_only=True, converter=hex_to_bytes, repr=lambda val: val.hex()) - cltv_expiry = attr.ib(type=int, kw_only=True) + cltv_abs = attr.ib(type=int, kw_only=True) timestamp = attr.ib(type=int, kw_only=True) htlc_id = attr.ib(type=int, kw_only=True, default=None) @stored_in('adds', tuple) - def from_tuple(amount_msat, payment_hash, cltv_expiry, htlc_id, timestamp) -> 'UpdateAddHtlc': + def from_tuple(amount_msat, payment_hash, cltv_abs, htlc_id, timestamp) -> 'UpdateAddHtlc': return UpdateAddHtlc( amount_msat=amount_msat, payment_hash=payment_hash, - cltv_expiry=cltv_expiry, + cltv_abs=cltv_abs, htlc_id=htlc_id, timestamp=timestamp) def to_json(self): - return (self.amount_msat, self.payment_hash, self.cltv_expiry, self.htlc_id, self.timestamp) + return (self.amount_msat, self.payment_hash, self.cltv_abs, self.htlc_id, self.timestamp) class OnionFailureCodeMetaFlag(IntFlag): diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py index ce194b026..77ba89550 100644 --- a/electrum/lnwatcher.py +++ b/electrum/lnwatcher.py @@ -510,8 +510,8 @@ class LNWalletWatcher(LNWatcher): prev_txid, prev_index = prevout.split(':') can_broadcast = True local_height = self.network.get_local_height() - if sweep_info.cltv_expiry: - wanted_height = sweep_info.cltv_expiry + if sweep_info.cltv_abs: + wanted_height = sweep_info.cltv_abs if wanted_height - local_height > 0: can_broadcast = False # self.logger.debug(f"pending redeem for {prevout}. waiting for {name}: CLTV ({local_height=}, {wanted_height=})") diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 428c8ce32..798c970c1 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -61,7 +61,7 @@ from .lnutil import (Outpoint, LNPeerAddr, get_compressed_pubkey_from_bech32, extract_nodeid, PaymentFailure, split_host_port, ConnStringFormatError, generate_keypair, LnKeyFamily, LOCAL, REMOTE, - MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE, + MIN_FINAL_CLTV_DELTA_FOR_INVOICE, NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner, UpdateAddHtlc, Direction, LnFeatures, ShortChannelID, HtlcLog, derive_payment_secret_from_payment_preimage, @@ -662,7 +662,7 @@ class PaySession(Logger): initial_trampoline_fee_level: int, invoice_features: int, r_tags, - min_cltv_expiry: int, + min_final_cltv_delta: int, # delta for last edge (typically from invoice) amount_to_pay: int, # total payment amount final receiver will get invoice_pubkey: bytes, uses_trampoline: bool, # whether sender uses trampoline or gossip @@ -677,7 +677,7 @@ class PaySession(Logger): self.invoice_features = LnFeatures(invoice_features) self.r_tags = r_tags - self.min_cltv_expiry = min_cltv_expiry + self.min_final_cltv_delta = min_final_cltv_delta self.amount_to_pay = amount_to_pay self.invoice_pubkey = invoice_pubkey @@ -1387,7 +1387,7 @@ class LNWallet(LNWorker): ) -> Tuple[bool, List[HtlcLog]]: lnaddr = self._check_invoice(invoice, amount_msat=amount_msat) - min_cltv_expiry = lnaddr.get_min_final_cltv_expiry() + min_final_cltv_delta = lnaddr.get_min_final_cltv_delta() payment_hash = lnaddr.paymenthash key = payment_hash.hex() payment_secret = lnaddr.payment_secret @@ -1417,7 +1417,7 @@ class LNWallet(LNWorker): payment_hash=payment_hash, payment_secret=payment_secret, amount_to_pay=amount_to_pay, - min_cltv_expiry=min_cltv_expiry, + min_final_cltv_delta=min_final_cltv_delta, r_tags=r_tags, invoice_features=invoice_features, attempts=attempts, @@ -1447,7 +1447,7 @@ class LNWallet(LNWorker): payment_hash: bytes, payment_secret: bytes, amount_to_pay: int, # in msat - min_cltv_expiry: int, + min_final_cltv_delta: int, r_tags, invoice_features: int, attempts: int = None, @@ -1473,7 +1473,7 @@ class LNWallet(LNWorker): initial_trampoline_fee_level=self.config.INITIAL_TRAMPOLINE_FEE_LEVEL, invoice_features=invoice_features, r_tags=r_tags, - min_cltv_expiry=min_cltv_expiry, + min_final_cltv_delta=min_final_cltv_delta, amount_to_pay=amount_to_pay, invoice_pubkey=node_pubkey, uses_trampoline=self.uses_trampoline(), @@ -1502,7 +1502,7 @@ class LNWallet(LNWorker): await self.pay_to_route( paysession=paysession, sent_htlc_info=sent_htlc_info, - min_cltv_expiry=cltv_delta, + min_final_cltv_delta=cltv_delta, trampoline_onion=trampoline_onion, ) # invoice_status is triggered in self.set_invoice_status when it actually changes. @@ -1559,7 +1559,7 @@ class LNWallet(LNWorker): self, *, paysession: PaySession, sent_htlc_info: SentHtlcInfo, - min_cltv_expiry: int, + min_final_cltv_delta: int, trampoline_onion: Optional[OnionPacket] = None, ) -> None: """Sends a single HTLC.""" @@ -1578,7 +1578,7 @@ class LNWallet(LNWorker): amount_msat=shi.amount_msat, total_msat=shi.bucket_msat, payment_hash=paysession.payment_hash, - min_final_cltv_expiry=min_cltv_expiry, + min_final_cltv_delta=min_final_cltv_delta, payment_secret=shi.payment_secret_bucket, trampoline_onion=trampoline_onion) @@ -1735,10 +1735,10 @@ class LNWallet(LNWorker): if addr.amount is None: raise InvoiceError(_("Missing amount")) # check cltv - if addr.get_min_final_cltv_expiry() > lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: + if addr.get_min_final_cltv_delta() > lnutil.NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE: raise InvoiceError("{}\n{}".format( _("Invoice wants us to risk locking funds for unreasonably long."), - f"min_final_cltv_expiry: {addr.get_min_final_cltv_expiry()}")) + f"min_final_cltv_delta: {addr.get_min_final_cltv_delta()}")) # check features addr.validate_and_compare_features(self.features) return addr @@ -1858,13 +1858,13 @@ class LNWallet(LNWorker): trampoline_onion = None per_trampoline_secret = paysession.payment_secret per_trampoline_amount_with_fees = amount_msat - per_trampoline_cltv_delta = paysession.min_cltv_expiry + per_trampoline_cltv_delta = paysession.min_final_cltv_delta per_trampoline_fees = 0 else: trampoline_route, trampoline_onion, per_trampoline_amount_with_fees, per_trampoline_cltv_delta = create_trampoline_route_and_onion( amount_msat=per_trampoline_amount, total_msat=paysession.amount_to_pay, - min_cltv_expiry=paysession.min_cltv_expiry, + min_final_cltv_delta=paysession.min_final_cltv_delta, my_pubkey=self.node_keypair.pubkey, invoice_pubkey=paysession.invoice_pubkey, invoice_features=paysession.invoice_features, @@ -1896,7 +1896,7 @@ class LNWallet(LNWorker): short_channel_id=chan.short_channel_id, fee_base_msat=0, fee_proportional_millionths=0, - cltv_expiry_delta=0, + cltv_delta=0, node_features=trampoline_features) ] self.logger.info(f'adding route {part_amount_msat} {delta_fee} {margin}') @@ -1925,7 +1925,7 @@ class LNWallet(LNWorker): self.create_route_for_payment, amount_msat=part_amount_msat, invoice_pubkey=paysession.invoice_pubkey, - min_cltv_expiry=paysession.min_cltv_expiry, + min_final_cltv_delta=paysession.min_final_cltv_delta, r_tags=paysession.r_tags, invoice_features=paysession.invoice_features, my_sending_channels=[channel] if is_multichan_mpp else my_active_channels, @@ -1942,7 +1942,7 @@ class LNWallet(LNWorker): trampoline_fee_level=None, trampoline_route=None, ) - routes.append((shi, paysession.min_cltv_expiry, fwd_trampoline_onion)) + routes.append((shi, paysession.min_final_cltv_delta, fwd_trampoline_onion)) except NoPathFound: continue for route in routes: @@ -1955,7 +1955,7 @@ class LNWallet(LNWorker): self, *, amount_msat: int, invoice_pubkey: bytes, - min_cltv_expiry: int, + min_final_cltv_delta: int, r_tags, invoice_features: int, my_sending_channels: List[Channel], @@ -1978,7 +1978,7 @@ class LNWallet(LNWorker): self.logger.info(f'create_route: skipping alias {ShortChannelID(private_path[0][1])}') continue for end_node, edge_rest in zip(private_path_nodes, private_path_rest): - short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta = edge_rest + short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_delta = edge_rest short_channel_id = ShortChannelID(short_channel_id) # if we have a routing policy for this edge in the db, that takes precedence, # as it is likely from a previous failure @@ -1989,7 +1989,7 @@ class LNWallet(LNWorker): if channel_policy: fee_base_msat = channel_policy.fee_base_msat fee_proportional_millionths = channel_policy.fee_proportional_millionths - cltv_expiry_delta = channel_policy.cltv_expiry_delta + cltv_delta = channel_policy.cltv_delta node_info = self.channel_db.get_node_info_for_node_id(node_id=end_node) route_edge = RouteEdge( start_node=start_node, @@ -1997,7 +1997,7 @@ class LNWallet(LNWorker): short_channel_id=short_channel_id, fee_base_msat=fee_base_msat, fee_proportional_millionths=fee_proportional_millionths, - cltv_expiry_delta=cltv_expiry_delta, + cltv_delta=cltv_delta, node_features=node_info.features if node_info else 0) private_route_edges[route_edge.short_channel_id] = route_edge start_node = end_node @@ -2015,7 +2015,7 @@ class LNWallet(LNWorker): if not route: raise NoPathFound() # test sanity - if not is_route_sane_to_use(route, amount_msat, min_cltv_expiry): + if not is_route_sane_to_use(route, amount_msat, min_final_cltv_delta): self.logger.info(f"rejecting insane route {route}") raise NoPathFound() assert len(route) > 0 @@ -2059,7 +2059,7 @@ class LNWallet(LNWorker): amount=amount_btc, tags=[ ('d', message), - ('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE), + ('c', MIN_FINAL_CLTV_DELTA_FOR_INVOICE), ('x', expiry), ('9', invoice_features), ('f', fallback_address), @@ -2400,14 +2400,14 @@ class LNWallet(LNWorker): # (they will get the channel update from the onion error) # at least, that's the theory. https://github.com/lightningnetwork/lnd/issues/2066 fee_base_msat = fee_proportional_millionths = 0 - cltv_expiry_delta = 1 # lnd won't even try with zero + cltv_delta = 1 # lnd won't even try with zero missing_info = True if channel_info: policy = get_mychannel_policy(channel_info.short_channel_id, chan.node_id, scid_to_my_channels) if policy: fee_base_msat = policy.fee_base_msat fee_proportional_millionths = policy.fee_proportional_millionths - cltv_expiry_delta = policy.cltv_expiry_delta + cltv_delta = policy.cltv_delta missing_info = False if missing_info: self.logger.info( @@ -2418,7 +2418,7 @@ class LNWallet(LNWorker): alias_or_scid, fee_base_msat, fee_proportional_millionths, - cltv_expiry_delta)])) + cltv_delta)])) return routing_hints def delete_payment_info(self, payment_hash_hex: str): diff --git a/electrum/tests/test_bolt11.py b/electrum/tests/test_bolt11.py index 7586d1066..433af331c 100644 --- a/electrum/tests/test_bolt11.py +++ b/electrum/tests/test_bolt11.py @@ -142,19 +142,19 @@ class TestBolt11(ElectrumTestCase): def test_min_final_cltv_expiry_decoding(self): lnaddr = lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qsp5qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsdqqcqzys9qypqsqp2h6a5xeytuc3fad2ed4gxvhd593lwjdna3dxsyeem0qkzjx6guk44jend0xq4zzvp6f3fy07wnmxezazzsxgmvqee8shxjuqu2eu0qpnvc95x", net=constants.BitcoinSimnet) - self.assertEqual(144, lnaddr.get_min_final_cltv_expiry()) + self.assertEqual(144, lnaddr.get_min_final_cltv_delta()) lnaddr = lndecode("lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg", net=constants.BitcoinTestnet) - self.assertEqual(30, lnaddr.get_min_final_cltv_expiry()) + self.assertEqual(30, lnaddr.get_min_final_cltv_delta()) def test_min_final_cltv_expiry_roundtrip(self): for cltv in (1, 15, 16, 31, 32, 33, 150, 511, 512, 513, 1023, 1024, 1025): lnaddr = LnAddr( paymenthash=RHASH, payment_secret=b"\x01"*32, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('c', cltv), ('9', 33282)]) - self.assertEqual(cltv, lnaddr.get_min_final_cltv_expiry()) + self.assertEqual(cltv, lnaddr.get_min_final_cltv_delta()) invoice = lnencode(lnaddr, PRIVKEY) - self.assertEqual(cltv, lndecode(invoice).get_min_final_cltv_expiry()) + self.assertEqual(cltv, lndecode(invoice).get_min_final_cltv_delta()) def test_features(self): lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9qypqsztrz5v3jfnxskfv7g8chmyzyrfhf2vupcavuq5rce96kyt6g0zh337h206awccwp335zarqrud4wccgdn39vur44d8um4hmgv06aj0sgpdrv73z") diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py index 76cffd5f8..5e8a6fd8f 100644 --- a/electrum/tests/test_lnchannel.py +++ b/electrum/tests/test_lnchannel.py @@ -33,7 +33,7 @@ from electrum import lnpeer from electrum import lnchannel from electrum import lnutil from electrum import bip32 as bip32_utils -from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED +from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc from electrum.logging import console_stderr_handler from electrum.lnchannel import ChannelState from electrum.json_db import StoredDict @@ -240,7 +240,7 @@ class TestChannel(ElectrumTestCase): self.htlc_dict = { 'payment_hash' : paymentHash, 'amount_msat' : one_bitcoin_in_msat, - 'cltv_expiry' : 5, + 'cltv_abs' : 5, 'timestamp' : 0, } @@ -648,15 +648,15 @@ class TestAvailableToSpend(ElectrumTestCase): paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) - htlc_dict = { - 'payment_hash' : paymentHash, - 'amount_msat' : one_bitcoin_in_msat * 41 // 10, - 'cltv_expiry' : 5, - 'timestamp' : 0, - } - - alice_idx = alice_channel.add_htlc(htlc_dict).htlc_id - bob_idx = bob_channel.receive_htlc(htlc_dict).htlc_id + htlc = UpdateAddHtlc( + payment_hash=paymentHash, + amount_msat=one_bitcoin_in_msat * 41 // 10, + cltv_abs=5, + timestamp=0, + ) + + alice_idx = alice_channel.add_htlc(htlc).htlc_id + bob_idx = bob_channel.receive_htlc(htlc).htlc_id self.assertEqual(89984088000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) @@ -672,20 +672,20 @@ class TestAvailableToSpend(ElectrumTestCase): # available. # We try adding an HTLC of value 1 BTC, which should fail because the # balance is unavailable. - htlc_dict = { - 'payment_hash' : paymentHash, - 'amount_msat' : one_bitcoin_in_msat, - 'cltv_expiry' : 5, - 'timestamp' : 0, - } + htlc = UpdateAddHtlc( + payment_hash=paymentHash, + amount_msat=one_bitcoin_in_msat, + cltv_abs=5, + timestamp=0, + ) with self.assertRaises(lnutil.PaymentFailure): - alice_channel.add_htlc(htlc_dict) + alice_channel.add_htlc(htlc) # Now do a state transition, which will ACK the FailHTLC, making Alice # able to add the new HTLC. force_state_transition(alice_channel, bob_channel) self.assertEqual(499986152000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) - alice_channel.add_htlc(htlc_dict) + alice_channel.add_htlc(htlc) class TestChanReserve(ElectrumTestCase): @@ -722,7 +722,7 @@ class TestChanReserve(ElectrumTestCase): htlc_dict = { 'payment_hash' : paymentHash, 'amount_msat' : int(.5 * one_bitcoin_in_msat), - 'cltv_expiry' : 5, + 'cltv_abs' : 5, 'timestamp' : 0, } self.alice_channel.add_htlc(htlc_dict) @@ -761,7 +761,7 @@ class TestChanReserve(ElectrumTestCase): htlc_dict = { 'payment_hash' : paymentHash, 'amount_msat' : int(3.5 * one_bitcoin_in_msat), - 'cltv_expiry' : 5, + 'cltv_abs' : 5, } self.alice_channel.add_htlc(htlc_dict) self.bob_channel.receive_htlc(htlc_dict) @@ -785,7 +785,7 @@ class TestChanReserve(ElectrumTestCase): htlc_dict = { 'payment_hash' : paymentHash, 'amount_msat' : int(2 * one_bitcoin_in_msat), - 'cltv_expiry' : 5, + 'cltv_abs' : 5, 'timestamp' : 0, } alice_idx = self.alice_channel.add_htlc(htlc_dict).htlc_id @@ -830,7 +830,7 @@ class TestDust(ElectrumTestCase): htlc = { 'payment_hash' : paymentHash, 'amount_msat' : 1000 * htlcAmt, - 'cltv_expiry' : 5, # also in create_test_channels + 'cltv_abs' : 5, # also in create_test_channels 'timestamp' : 0, } diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index f77f90801..a9a6f4300 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -239,7 +239,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): initial_trampoline_fee_level=0, invoice_features=decoded_invoice.get_features(), r_tags=decoded_invoice.get_routing_info('r'), - min_cltv_expiry=decoded_invoice.get_min_final_cltv_expiry(), + min_final_cltv_delta=decoded_invoice.get_min_final_cltv_delta(), amount_to_pay=amount_msat, invoice_pubkey=decoded_invoice.pubkey.serialize(), uses_trampoline=False, @@ -455,7 +455,7 @@ class TestPeer(ElectrumTestCase): payment_preimage: bytes = None, payment_hash: bytes = None, invoice_features: LnFeatures = None, - min_final_cltv_expiry_delta: int = None, + min_final_cltv_delta: int = None, ) -> Tuple[LnAddr, str]: amount_btc = amount_msat/Decimal(COIN*1000) if payment_preimage is None and not payment_hash: @@ -477,13 +477,13 @@ class TestPeer(ElectrumTestCase): payment_secret = w2.get_payment_secret(payment_hash) else: payment_secret = None - if min_final_cltv_expiry_delta is None: - min_final_cltv_expiry_delta = lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE + if min_final_cltv_delta is None: + min_final_cltv_delta = lnutil.MIN_FINAL_CLTV_DELTA_FOR_INVOICE lnaddr1 = LnAddr( paymenthash=payment_hash, amount=amount_btc, tags=[ - ('c', min_final_cltv_expiry_delta), + ('c', min_final_cltv_delta), ('d', 'coffee'), ('9', invoice_features), ] + routing_hints, @@ -574,13 +574,13 @@ class TestPeerDirect(TestPeer): @staticmethod def _send_fake_htlc(peer: Peer, chan: Channel) -> UpdateAddHtlc: - htlc = UpdateAddHtlc(amount_msat=10000, payment_hash=os.urandom(32), cltv_expiry=999, timestamp=1) + htlc = UpdateAddHtlc(amount_msat=10000, payment_hash=os.urandom(32), cltv_abs=999, timestamp=1) htlc = chan.add_htlc(htlc) peer.send_message( "update_add_htlc", channel_id=chan.channel_id, id=htlc.htlc_id, - cltv_expiry=htlc.cltv_expiry, + cltv_expiry=htlc.cltv_abs, amount_msat=htlc.amount_msat, payment_hash=htlc.payment_hash, onion_routing_packet=1366 * b"0", @@ -798,7 +798,7 @@ class TestPeerDirect(TestPeer): with self.assertRaises(lnutil.IncompatibleOrInsaneFeatures): result, log = await w1.pay_invoice(pay_req) # too large CLTV - lnaddr, pay_req = self.prepare_invoice(w2, min_final_cltv_expiry_delta=10**6) + lnaddr, pay_req = self.prepare_invoice(w2, min_final_cltv_delta=10**6) with self.assertRaises(InvoiceError): result, log = await w1.pay_invoice(pay_req) raise SuccessfulTest() @@ -848,7 +848,7 @@ class TestPeerDirect(TestPeer): await w1.pay_to_route( sent_htlc_info=shi1, paysession=paysession1, - min_cltv_expiry=lnaddr2.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr2.get_min_final_cltv_delta(), ) p1.maybe_send_commitment = _maybe_send_commitment1 # bob sends htlc BUT NOT COMMITMENT_SIGNED @@ -868,7 +868,7 @@ class TestPeerDirect(TestPeer): await w2.pay_to_route( sent_htlc_info=shi2, paysession=paysession2, - min_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr1.get_min_final_cltv_delta(), ) p2.maybe_send_commitment = _maybe_send_commitment2 # sleep a bit so that they both receive msgs sent so far @@ -941,7 +941,7 @@ class TestPeerDirect(TestPeer): amount_msat=1000, total_msat=lnaddr1.get_amount_msat(), payment_hash=lnaddr1.paymenthash, - min_final_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr1.get_min_final_cltv_delta(), payment_secret=lnaddr1.payment_secret, ) p1.pay( @@ -950,7 +950,7 @@ class TestPeerDirect(TestPeer): amount_msat=lnaddr1.get_amount_msat() - 1000, total_msat=lnaddr1.get_amount_msat(), payment_hash=lnaddr2.paymenthash, - min_final_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr1.get_min_final_cltv_delta(), payment_secret=lnaddr1.payment_secret, ) @@ -1017,7 +1017,7 @@ class TestPeerDirect(TestPeer): amount_msat=1000, total_msat=2000, payment_hash=lnaddr1.paymenthash, - min_final_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr1.get_min_final_cltv_delta(), payment_secret=lnaddr1.payment_secret, ) p1.pay( @@ -1026,7 +1026,7 @@ class TestPeerDirect(TestPeer): amount_msat=1000, total_msat=lnaddr1.get_amount_msat(), payment_hash=lnaddr1.paymenthash, - min_final_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr1.get_min_final_cltv_delta(), payment_secret=lnaddr1.payment_secret, ) @@ -1121,7 +1121,7 @@ class TestPeerDirect(TestPeer): amount_msat=lnaddr.get_amount_msat(), total_msat=lnaddr.get_amount_msat(), payment_hash=lnaddr.paymenthash, - min_final_cltv_expiry=lnaddr.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr.get_min_final_cltv_delta(), payment_secret=lnaddr.payment_secret) # alice closes await p1.close_channel(alice_channel.channel_id) @@ -1264,7 +1264,7 @@ class TestPeerDirect(TestPeer): pay = w1.pay_to_route( sent_htlc_info=shi, paysession=paysession, - min_cltv_expiry=lnaddr.get_min_final_cltv_expiry(), + min_final_cltv_delta=lnaddr.get_min_final_cltv_delta(), ) await asyncio.gather(pay, p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch()) with self.assertRaises(PaymentFailure): diff --git a/electrum/tests/test_lnutil.py b/electrum/tests/test_lnutil.py index 592b9f7a2..f17f29d3a 100644 --- a/electrum/tests/test_lnutil.py +++ b/electrum/tests/test_lnutil.py @@ -482,26 +482,31 @@ class TestLNUtil(ElectrumTestCase): htlc_cltv_timeout = {} htlc_payment_preimage = {} htlc = {} + htlc_pubkeys = { + "revocation_pubkey": local_revocation_pubkey, + "remote_htlcpubkey": remote_htlcpubkey, + "local_htlcpubkey": local_htlcpubkey, + } htlc_cltv_timeout[2] = 502 htlc_payment_preimage[2] = b"\x02" * 32 - htlc[2] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[2])) + htlc[2] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[2])) htlc_cltv_timeout[3] = 503 htlc_payment_preimage[3] = b"\x03" * 32 - htlc[3] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[3])) + htlc[3] = make_offered_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[3])) htlc_cltv_timeout[0] = 500 htlc_payment_preimage[0] = b"\x00" * 32 - htlc[0] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[0]), htlc_cltv_timeout[0]) + htlc[0] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[0]), cltv_abs=htlc_cltv_timeout[0]) htlc_cltv_timeout[1] = 501 htlc_payment_preimage[1] = b"\x01" * 32 - htlc[1] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[1]), htlc_cltv_timeout[1]) + htlc[1] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[1]), cltv_abs=htlc_cltv_timeout[1]) htlc_cltv_timeout[4] = 504 htlc_payment_preimage[4] = b"\x04" * 32 - htlc[4] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[4]), htlc_cltv_timeout[4]) + htlc[4] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[4]), cltv_abs=htlc_cltv_timeout[4]) remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" @@ -512,7 +517,7 @@ class TestLNUtil(ElectrumTestCase): (1, 2000 * 1000), (3, 3000 * 1000), (4, 4000 * 1000)]: - htlc_obj[num] = UpdateAddHtlc(amount_msat=msat, payment_hash=bitcoin.sha256(htlc_payment_preimage[num]), cltv_expiry=0, htlc_id=None, timestamp=0) + htlc_obj[num] = UpdateAddHtlc(amount_msat=msat, payment_hash=bitcoin.sha256(htlc_payment_preimage[num]), cltv_abs=0, htlc_id=None, timestamp=0) htlcs = [ScriptHtlc(htlc[x], htlc_obj[x]) for x in range(5)] our_commit_tx = make_commitment( @@ -567,7 +572,7 @@ class TestLNUtil(ElectrumTestCase): local_feerate_per_kw, our_commit_tx)) - def htlc_tx(self, htlc, htlc_output_index, amount_msat, htlc_payment_preimage, remote_htlc_sig, success, cltv_timeout, local_feerate_per_kw, our_commit_tx): + def htlc_tx(self, htlc, htlc_output_index, amount_msat, htlc_payment_preimage, remote_htlc_sig, success, cltv_abs, local_feerate_per_kw, our_commit_tx): _script, our_htlc_tx_output = make_htlc_tx_output( amount_msat=amount_msat, local_feerate=local_feerate_per_kw, @@ -581,7 +586,7 @@ class TestLNUtil(ElectrumTestCase): amount_msat=amount_msat, witness_script=htlc.hex()) our_htlc_tx = make_htlc_tx( - cltv_expiry=cltv_timeout, + cltv_abs=cltv_abs, inputs=our_htlc_tx_inputs, output=our_htlc_tx_output) diff --git a/electrum/trampoline.py b/electrum/trampoline.py index ceb564193..c97ac4474 100644 --- a/electrum/trampoline.py +++ b/electrum/trampoline.py @@ -6,7 +6,7 @@ from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, from .lnutil import LnFeatures from .lnonion import calc_hops_data_for_payment, new_onion_packet -from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_sane_to_use +from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_sane_to_use, LNPaymentTRoute from .lnutil import NoPathFound, LNPeerAddr from . import constants from .logging import get_logger @@ -183,7 +183,7 @@ def _extend_trampoline_route( end_node=end_node, fee_base_msat=policy['fee_base_msat'] if pay_fees else 0, fee_proportional_millionths=policy['fee_proportional_millionths'] if pay_fees else 0, - cltv_expiry_delta=policy['cltv_expiry_delta'] if pay_fees else 0, + cltv_delta=policy['cltv_expiry_delta'] if pay_fees else 0, node_features=trampoline_features)) @@ -208,7 +208,7 @@ def _choose_second_trampoline( def create_trampoline_route( *, amount_msat: int, - min_cltv_expiry: int, + min_final_cltv_delta: int, invoice_pubkey: bytes, invoice_features: int, my_pubkey: bytes, @@ -217,7 +217,7 @@ def create_trampoline_route( trampoline_fee_level: int, use_two_trampolines: bool, failed_routes: Iterable[Sequence[str]], -) -> LNPaymentRoute: +) -> LNPaymentTRoute: # we decide whether to convert to a legacy payment is_legacy, invoice_trampolines = is_legacy_relay(invoice_features, r_tags) @@ -262,25 +262,25 @@ def create_trampoline_route( # check that we can pay amount and fees for edge in route[::-1]: amount_msat += edge.fee_for_edge(amount_msat) - if not is_route_sane_to_use(route, amount_msat, min_cltv_expiry): + if not is_route_sane_to_use(route, amount_msat, min_final_cltv_delta): raise NoPathFound("We cannot afford to pay the fees.") return route def create_trampoline_onion( *, - route, - amount_msat, - final_cltv, + route: LNPaymentTRoute, + amount_msat: int, + final_cltv_abs: int, total_msat: int, payment_hash: bytes, payment_secret: bytes, ): # all edges are trampoline - hops_data, amount_msat, cltv = calc_hops_data_for_payment( + hops_data, amount_msat, cltv_abs = calc_hops_data_for_payment( route, amount_msat, - final_cltv, + final_cltv_abs=final_cltv_abs, total_msat=total_msat, payment_secret=payment_secret) # detect trampoline hops. @@ -313,14 +313,14 @@ def create_trampoline_onion( trampoline_onion = new_onion_packet(payment_path_pubkeys, trampoline_session_key, hops_data, associated_data=payment_hash, trampoline=True) trampoline_onion._debug_hops_data = hops_data trampoline_onion._debug_route = route - return trampoline_onion, amount_msat, cltv + return trampoline_onion, amount_msat, cltv_abs def create_trampoline_route_and_onion( *, amount_msat, total_msat, - min_cltv_expiry, + min_final_cltv_delta: int, invoice_pubkey, invoice_features, my_pubkey: bytes, @@ -335,7 +335,7 @@ def create_trampoline_route_and_onion( # create route for the trampoline_onion trampoline_route = create_trampoline_route( amount_msat=amount_msat, - min_cltv_expiry=min_cltv_expiry, + min_final_cltv_delta=min_final_cltv_delta, my_pubkey=my_pubkey, invoice_pubkey=invoice_pubkey, invoice_features=invoice_features, @@ -345,16 +345,16 @@ def create_trampoline_route_and_onion( use_two_trampolines=use_two_trampolines, failed_routes=failed_routes) # compute onion and fees - final_cltv = local_height + min_cltv_expiry - trampoline_onion, amount_with_fees, bucket_cltv = create_trampoline_onion( + final_cltv_abs = local_height + min_final_cltv_delta + trampoline_onion, amount_with_fees, bucket_cltv_abs = create_trampoline_onion( route=trampoline_route, amount_msat=amount_msat, - final_cltv=final_cltv, + final_cltv_abs=final_cltv_abs, total_msat=total_msat, payment_hash=payment_hash, payment_secret=payment_secret) - bucket_cltv_delta = bucket_cltv - local_height - bucket_cltv_delta += trampoline_route[0].cltv_expiry_delta + bucket_cltv_delta = bucket_cltv_abs - local_height + bucket_cltv_delta += trampoline_route[0].cltv_delta # trampoline fee for this very trampoline trampoline_fee = trampoline_route[0].fee_for_edge(amount_with_fees) amount_with_fees += trampoline_fee