electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 87a080d30e0897a5e9ec865a266eba7ee74a87d0
parent fa1762792a5bb5b9056b0fa6565a15138086fc1e
Author: ThomasV <thomasv@electrum.org>
Date:   Wed, 10 Feb 2021 13:16:33 +0100

split code in htlc_switch:
 - raise OnionRoutingFailure whenever we want to fail a htlc
 - catch that exception in htlc_switch
 - this will avoid code duplication in the case of trampoline

Diffstat:
Melectrum/lnchannel.py | 13++++++-------
Melectrum/lnonion.py | 13++++++-------
Melectrum/lnpeer.py | 182++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Melectrum/lnworker.py | 6+++---
4 files changed, 112 insertions(+), 102 deletions(-)

diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py @@ -41,7 +41,7 @@ from .bitcoin import redeem_script_to_address from .crypto import sha256, sha256d from .transaction import Transaction, PartialTransaction, TxInput from .logging import Logger -from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailureMessage +from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure from . import lnutil from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx, @@ -528,7 +528,7 @@ class Channel(AbstractChannel): self._chan_ann_without_sigs = None # type: Optional[bytes] self.revocation_store = RevocationStore(state["revocation_store"]) self._can_send_ctx_updates = True # type: bool - self._receive_fail_reasons = {} # type: Dict[int, (bytes, OnionRoutingFailureMessage)] + self._receive_fail_reasons = {} # type: Dict[int, (bytes, OnionRoutingFailure)] self._ignore_max_htlc_value = False # used in tests def is_initiator(self): @@ -1008,8 +1008,7 @@ class Channel(AbstractChannel): self, htlc_id: int, error_bytes: Optional[bytes], - failure_message: Optional['OnionRoutingFailureMessage'], - ): + failure_message: Optional['OnionRoutingFailure']): error_hex = error_bytes.hex() if error_bytes else None failure_hex = failure_message.to_bytes().hex() if failure_message else None self.hm.log['fail_htlc_reasons'][htlc_id] = (error_hex, failure_hex) @@ -1017,7 +1016,7 @@ class Channel(AbstractChannel): def pop_fail_htlc_reason(self, htlc_id): error_hex, failure_hex = self.hm.log['fail_htlc_reasons'].pop(htlc_id, (None, None)) error_bytes = bytes.fromhex(error_hex) if error_hex else None - failure_message = OnionRoutingFailureMessage.from_bytes(bytes.fromhex(failure_hex)) if failure_hex else None + failure_message = OnionRoutingFailure.from_bytes(bytes.fromhex(failure_hex)) if failure_hex else None return error_bytes, failure_message def extract_preimage_from_htlc_txin(self, txin: TxInput) -> None: @@ -1238,7 +1237,7 @@ class Channel(AbstractChannel): return htlc.payment_hash def decode_onion_error(self, reason: bytes, route: Sequence['RouteEdge'], - htlc_id: int) -> Tuple[OnionRoutingFailureMessage, int]: + htlc_id: int) -> Tuple[OnionRoutingFailure, int]: failure_msg, sender_idx = decode_onion_error( reason, [x.node_id for x in route], @@ -1267,7 +1266,7 @@ class Channel(AbstractChannel): def receive_fail_htlc(self, htlc_id: int, *, error_bytes: Optional[bytes], - reason: Optional[OnionRoutingFailureMessage] = None) -> None: + reason: Optional[OnionRoutingFailure] = None) -> None: """Fail a pending offered HTLC. Action must be initiated by REMOTE. """ diff --git a/electrum/lnonion.py b/electrum/lnonion.py @@ -381,7 +381,7 @@ def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes, class FailedToDecodeOnionError(Exception): pass -class OnionRoutingFailureMessage: +class OnionRoutingFailure(Exception): def __init__(self, code: int, data: bytes): self.code = code @@ -403,15 +403,14 @@ class OnionRoutingFailureMessage: except ValueError: pass # uknown failure code failure_data = failure_msg[2:] - return OnionRoutingFailureMessage(failure_code, failure_data) + return OnionRoutingFailure(failure_code, failure_data) def code_name(self) -> str: if isinstance(self.code, OnionFailureCode): return str(self.code.name) return f"Unknown error ({self.code!r})" - -def construct_onion_error(reason: OnionRoutingFailureMessage, +def construct_onion_error(reason: OnionRoutingFailure, onion_packet: OnionPacket, our_onion_private_key: bytes) -> bytes: # create payload @@ -453,19 +452,19 @@ def _decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[byte def decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[bytes], - session_key: bytes) -> (OnionRoutingFailureMessage, int): + session_key: bytes) -> (OnionRoutingFailure, int): """Returns the failure message, and the index of the sender of the error.""" decrypted_error, sender_index = _decode_onion_error(error_packet, payment_path_pubkeys, session_key) failure_msg = get_failure_msg_from_onion_error(decrypted_error) return failure_msg, sender_index -def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRoutingFailureMessage: +def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRoutingFailure: # get failure_msg bytes from error packet failure_len = int.from_bytes(decrypted_error_packet[32:34], byteorder='big') failure_msg = decrypted_error_packet[34:34+failure_len] # create failure message object - return OnionRoutingFailureMessage.from_bytes(failure_msg) + return OnionRoutingFailure.from_bytes(failure_msg) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py @@ -25,7 +25,7 @@ from . import transaction from .transaction import PartialTxOutput, match_script_against_template from .logging import Logger from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment, - process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage, + process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailure, ProcessedOnionPacket, UnsupportedOnionPacketVersion, InvalidOnionMac, InvalidOnionPubkey, OnionFailureCodeMetaFlag) from .lnchannel import Channel, RevokeAndAck, RemoteCtnTooFarInFuture, ChannelState, PeerState @@ -1267,7 +1267,7 @@ class Peer(Logger): if failure_code & OnionFailureCodeMetaFlag.BADONION == 0: asyncio.ensure_future(self.lnworker.try_force_closing(chan.channel_id)) raise RemoteMisbehaving(f"received update_fail_malformed_htlc with unexpected failure code: {failure_code}") - reason = OnionRoutingFailureMessage(code=failure_code, data=payload["sha256_of_onion"]) + reason = OnionRoutingFailure(code=failure_code, data=payload["sha256_of_onion"]) chan.receive_fail_htlc(htlc_id, error_bytes=None, reason=reason) self.maybe_send_commitment(chan) @@ -1295,56 +1295,56 @@ class Peer(Logger): def maybe_forward_htlc(self, chan: Channel, htlc: UpdateAddHtlc, *, onion_packet: OnionPacket, processed_onion: ProcessedOnionPacket - ) -> Tuple[Optional[bytes], Optional[int], Optional[OnionRoutingFailureMessage]]: + ) -> Tuple[Optional[bytes], Optional[int], Optional[OnionRoutingFailure]]: # Forward HTLC # FIXME: there are critical safety checks MISSING here forwarding_enabled = self.network.config.get('lightning_forward_payments', False) if not forwarding_enabled: self.logger.info(f"forwarding is disabled. failing htlc.") - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'') + raise OnionRoutingFailure(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'') chain = self.network.blockchain() if chain.is_tip_stale(): - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') + raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') try: next_chan_scid = processed_onion.hop_data.payload["short_channel_id"]["short_channel_id"] except: - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid) local_height = chain.height() if next_chan is None: self.logger.info(f"cannot forward htlc. cannot find next_chan {next_chan_scid}") - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'') + raise OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'') outgoing_chan_upd = next_chan.get_outgoing_gossip_channel_update()[2:] outgoing_chan_upd_len = len(outgoing_chan_upd).to_bytes(2, byteorder="big") if not next_chan.can_send_update_add_htlc(): self.logger.info(f"cannot forward htlc. next_chan {next_chan_scid} cannot send ctx updates. " f"chan state {next_chan.get_state()!r}, peer state: {next_chan.peer_state!r}") data = outgoing_chan_upd_len + outgoing_chan_upd - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data) + raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data) try: next_cltv_expiry = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] except: - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') if htlc.cltv_expiry - next_cltv_expiry < NBLOCK_OUR_CLTV_EXPIRY_DELTA: data = htlc.cltv_expiry.to_bytes(4, byteorder="big") + outgoing_chan_upd_len + outgoing_chan_upd - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=data) + 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: data = outgoing_chan_upd_len + outgoing_chan_upd - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_SOON, data=data) + raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_SOON, data=data) if max(htlc.cltv_expiry, next_cltv_expiry) > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'') + raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'') try: next_amount_msat_htlc = processed_onion.hop_data.payload["amt_to_forward"]["amt_to_forward"] except: - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') forwarding_fees = fee_for_edge_msat( forwarded_amount_msat=next_amount_msat_htlc, fee_base_msat=lnutil.OUR_FEE_BASE_MSAT, fee_proportional_millionths=lnutil.OUR_FEE_PROPORTIONAL_MILLIONTHS) if htlc.amount_msat - next_amount_msat_htlc < forwarding_fees: data = next_amount_msat_htlc.to_bytes(8, byteorder="big") + outgoing_chan_upd_len + outgoing_chan_upd - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.FEE_INSUFFICIENT, data=data) + raise OnionRoutingFailure(code=OnionFailureCode.FEE_INSUFFICIENT, data=data) self.logger.info(f'forwarding htlc to {next_chan.node_id}') next_htlc = UpdateAddHtlc( amount_msat=next_amount_msat_htlc, @@ -1366,19 +1366,18 @@ class Peer(Logger): except BaseException as e: self.logger.info(f"failed to forward htlc: error sending message. {e}") data = outgoing_chan_upd_len + outgoing_chan_upd - return None, None, OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data) - return next_chan_scid, next_htlc.htlc_id, None + raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data) + return next_chan_scid, next_htlc.htlc_id def maybe_fulfill_htlc( self, *, chan: Channel, htlc: UpdateAddHtlc, - processed_onion: ProcessedOnionPacket) -> Tuple[Optional[bytes], Optional[OnionRoutingFailureMessage]]: + processed_onion: ProcessedOnionPacket) -> Tuple[Optional[bytes], Optional[OnionRoutingFailure]]: info = self.lnworker.get_payment_info(htlc.payment_hash) if info is None: - reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') preimage = self.lnworker.get_preimage(htlc.payment_hash) try: payment_secret_from_onion = processed_onion.hop_data.payload["payment_data"]["payment_secret"] @@ -1386,59 +1385,49 @@ class Peer(Logger): pass # skip else: if payment_secret_from_onion != derive_payment_secret_from_payment_preimage(preimage): - reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') expected_received_msat = info.amount_msat # Check that our blockchain tip is sufficiently recent so that we have an approx idea of the height. # We should not release the preimage for an HTLC that its sender could already time out as # then they might try to force-close and it becomes a race. chain = self.network.blockchain() if chain.is_tip_stale(): - reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') local_height = chain.height() if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > htlc.cltv_expiry: - reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'') try: cltv_from_onion = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"] except: - reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') if cltv_from_onion != htlc.cltv_expiry: - reason = OnionRoutingFailureMessage( + raise OnionRoutingFailure( code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY, data=htlc.cltv_expiry.to_bytes(4, byteorder="big")) - return None, reason try: amt_to_forward = processed_onion.hop_data.payload["amt_to_forward"]["amt_to_forward"] except: - reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') try: total_msat = processed_onion.hop_data.payload["payment_data"]["total_msat"] except: total_msat = amt_to_forward # fall back to "amt_to_forward" if amt_to_forward != htlc.amount_msat: - reason = OnionRoutingFailureMessage( + raise OnionRoutingFailure( code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT, data=total_msat.to_bytes(8, byteorder="big")) - return None, reason if expected_received_msat is None: - return preimage, None + return preimage if not (expected_received_msat <= total_msat <= 2 * expected_received_msat): - reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') accepted, expired = self.lnworker.htlc_received(chan.short_channel_id, htlc, expected_received_msat) if accepted: - return preimage, None + return preimage elif expired: - reason = OnionRoutingFailureMessage(code=OnionFailureCode.MPP_TIMEOUT, data=b'') - return None, reason + raise OnionRoutingFailure(code=OnionFailureCode.MPP_TIMEOUT, data=b'') else: - # waiting for more htlcs - return None, None + return None def fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes): self.logger.info(f"_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") @@ -1461,7 +1450,7 @@ class Peer(Logger): len=len(error_bytes), reason=error_bytes) - def fail_malformed_htlc(self, *, chan: Channel, htlc_id: int, reason: OnionRoutingFailureMessage): + def fail_malformed_htlc(self, *, chan: Channel, htlc_id: int, reason: OnionRoutingFailure): self.logger.info(f"fail_malformed_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.") assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" chan.fail_htlc(htlc_id) @@ -1679,60 +1668,29 @@ class Peer(Logger): for htlc_id, (local_ctn, remote_ctn, onion_packet_hex, forwarding_info) in unfulfilled.items(): if not chan.hm.is_add_htlc_irrevocably_committed_yet(htlc_proposer=REMOTE, htlc_id=htlc_id): continue - #chan.logger.info(f'found unfulfilled htlc: {htlc_id}') htlc = chan.hm.get_htlc_by_id(REMOTE, htlc_id) - payment_hash = htlc.payment_hash - error_reason = None # type: Optional[OnionRoutingFailureMessage] + error_reason = None # type: Optional[OnionRoutingFailure] error_bytes = None # type: Optional[bytes] preimage = None + fw_info = None onion_packet_bytes = bytes.fromhex(onion_packet_hex) onion_packet = None try: onion_packet = OnionPacket.from_bytes(onion_packet_bytes) - processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) - except UnsupportedOnionPacketVersion: - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_VERSION, data=sha256(onion_packet_bytes)) - except InvalidOnionPubkey: - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_KEY, data=sha256(onion_packet_bytes)) - except InvalidOnionMac: - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_HMAC, data=sha256(onion_packet_bytes)) - except Exception as e: - self.logger.info(f"error processing onion packet: {e!r}") - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_VERSION, data=sha256(onion_packet_bytes)) + except OnionRoutingFailure as e: + error_reason = e else: - if self.network.config.get('test_fail_malformed_htlc'): - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_VERSION, data=sha256(onion_packet_bytes)) - if self.network.config.get('test_fail_htlcs_with_temp_node_failure'): - error_reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') - if not error_reason: - if processed_onion.are_we_final: - preimage, error_reason = self.maybe_fulfill_htlc( - chan=chan, - htlc=htlc, - processed_onion=processed_onion) - elif not forwarding_info: - next_chan_id, next_htlc_id, error_reason = self.maybe_forward_htlc( - chan=chan, - htlc=htlc, - onion_packet=onion_packet, - processed_onion=processed_onion) - if next_chan_id: - fw_info = (next_chan_id.hex(), next_htlc_id) - unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, fw_info - else: - preimage = self.lnworker.get_preimage(payment_hash) - next_chan_id_hex, htlc_id = forwarding_info - next_chan = self.lnworker.get_channel_by_short_id(bytes.fromhex(next_chan_id_hex)) - if next_chan: - error_bytes, error_reason = next_chan.pop_fail_htlc_reason(htlc_id) + try: + preimage, fw_info, error_bytes = self.process_unfulfilled_htlc(chan, htlc_id, htlc, forwarding_info, onion_packet_bytes, onion_packet) + except OnionRoutingFailure as e: + error_bytes = construct_onion_error(e, onion_packet, our_onion_private_key=self.privkey) + if fw_info: + unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, fw_info + elif preimage or error_reason or error_bytes: if preimage: await self.lnworker.enable_htlc_settle.wait() self.fulfill_htlc(chan, htlc.htlc_id, preimage) - done.add(htlc_id) - if error_reason or error_bytes: - if onion_packet and error_reason: - error_bytes = construct_onion_error(error_reason, onion_packet, our_onion_private_key=self.privkey) - if error_bytes: + elif error_bytes: self.fail_htlc( chan=chan, htlc_id=htlc.htlc_id, @@ -1746,3 +1704,57 @@ class Peer(Logger): # cleanup for htlc_id in done: unfulfilled.pop(htlc_id) + + def process_unfulfilled_htlc(self, chan, htlc_id, htlc, forwarding_info, onion_packet_bytes, onion_packet): + """ + returns either preimage or fw_info or error_bytes or (None, None, None) + raise an OnionRoutingFailure if we need to fail the htlc + """ + payment_hash = htlc.payment_hash + processed_onion = self.process_onion_packet(onion_packet, payment_hash, onion_packet_bytes) + if processed_onion.are_we_final: + preimage = self.maybe_fulfill_htlc( + chan=chan, + htlc=htlc, + processed_onion=processed_onion) + elif not forwarding_info: + next_chan_id, next_htlc_id = self.maybe_forward_htlc( + chan=chan, + htlc=htlc, + onion_packet=onion_packet, + processed_onion=processed_onion) + if next_chan_id: + fw_info = (next_chan_id.hex(), next_htlc_id) + return None, fw_info, None + else: + preimage = self.lnworker.get_preimage(payment_hash) + next_chan_id_hex, htlc_id = forwarding_info + next_chan = self.lnworker.get_channel_by_short_id(bytes.fromhex(next_chan_id_hex)) + if next_chan: + error_bytes, error_reason = next_chan.pop_fail_htlc_reason(htlc_id) + if error_bytes: + return None, None, error_bytes + if error_reason: + raise error_reason + if preimage: + return preimage, None, None + return None, None, None + + def process_onion_packet(self, onion_packet, payment_hash, onion_packet_bytes): + failure_data = sha256(onion_packet_bytes) + try: + processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) + except UnsupportedOnionPacketVersion: + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=failure_data) + except InvalidOnionPubkey: + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_KEY, data=failure_data) + except InvalidOnionMac: + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_HMAC, data=failure_data) + except Exception as e: + self.logger.info(f"error processing onion packet: {e!r}") + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=failure_data) + if self.network.config.get('test_fail_malformed_htlc'): + raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=failure_data) + if self.network.config.get('test_fail_htlcs_with_temp_node_failure'): + raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') + return processed_onion diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -59,7 +59,7 @@ from .lnutil import (Outpoint, LNPeerAddr, HtlcLog, derive_payment_secret_from_payment_preimage) from .lnutil import ln_dummy_address, ln_compare_features, IncompatibleLightningFeatures from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput -from .lnonion import OnionFailureCode, process_onion_packet, OnionPacket, OnionRoutingFailureMessage +from .lnonion import OnionFailureCode, process_onion_packet, OnionPacket, OnionRoutingFailure from .lnmsg import decode_msg from .i18n import _ from .lnrouter import (RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_sane_to_use, @@ -1379,7 +1379,7 @@ class LNWallet(LNWorker): htlc_id: int, amount_msat:int, error_bytes: Optional[bytes], - failure_message: Optional['OnionRoutingFailureMessage']): + failure_message: Optional['OnionRoutingFailure']): route = self.htlc_routes.get((payment_hash, chan.short_channel_id, htlc_id)) if not route: @@ -1392,7 +1392,7 @@ class LNWallet(LNWorker): failure_message, sender_idx = chan.decode_onion_error(error_bytes, route, htlc_id) except Exception as e: sender_idx = None - failure_message = OnionRoutingFailureMessage(-1, str(e)) + failure_message = OnionRoutingFailure(-1, str(e)) else: # probably got "update_fail_malformed_htlc". well... who to penalise now? assert failure_message is not None