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:
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