Browse Source

lnpeer: implement basic handling of "update_fail_malformed_htlc"

hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
b524460fdf
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 8
      electrum/lnchannel.py
  2. 44
      electrum/lnhtlc.py
  3. 7
      electrum/lnonion.py
  4. 124
      electrum/lnpeer.py
  5. 5
      electrum/lnworker.py

8
electrum/lnchannel.py

@ -41,7 +41,7 @@ from .bitcoin import redeem_script_to_address
from .crypto import sha256, sha256d from .crypto import sha256, sha256d
from .transaction import Transaction, PartialTransaction from .transaction import Transaction, PartialTransaction
from .logging import Logger from .logging import Logger
from .lnonion import decode_onion_error from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailureMessage
from . import lnutil from . import lnutil
from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints,
get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx, get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx,
@ -59,6 +59,7 @@ from .lnmsg import encode_msg, decode_msg
if TYPE_CHECKING: if TYPE_CHECKING:
from .lnworker import LNWallet from .lnworker import LNWallet
from .json_db import StoredDict from .json_db import StoredDict
from .lnrouter import RouteEdge
# lightning channel states # lightning channel states
@ -769,7 +770,8 @@ class Channel(Logger):
htlc = log['adds'][htlc_id] htlc = log['adds'][htlc_id]
return htlc.payment_hash return htlc.payment_hash
def decode_onion_error(self, reason, route, htlc_id): def decode_onion_error(self, reason: bytes, route: Sequence['RouteEdge'],
htlc_id: int) -> Tuple[OnionRoutingFailureMessage, int]:
failure_msg, sender_idx = decode_onion_error( failure_msg, sender_idx = decode_onion_error(
reason, reason,
[x.node_id for x in route], [x.node_id for x in route],
@ -791,7 +793,7 @@ class Channel(Logger):
with self.db_lock: with self.db_lock:
self.hm.send_fail(htlc_id) self.hm.send_fail(htlc_id)
def receive_fail_htlc(self, htlc_id, reason): def receive_fail_htlc(self, htlc_id: int, reason: bytes):
self.logger.info("receive_fail_htlc") self.logger.info("receive_fail_htlc")
with self.db_lock: with self.db_lock:
self.hm.recv_fail(htlc_id) self.hm.recv_fail(htlc_id)

44
electrum/lnhtlc.py

@ -88,16 +88,28 @@ class HTLCManager:
self._maybe_active_htlc_ids[REMOTE].add(htlc_id) self._maybe_active_htlc_ids[REMOTE].add(htlc_id)
def send_settle(self, htlc_id: int) -> None: def send_settle(self, htlc_id: int) -> None:
self.log[REMOTE]['settles'][htlc_id] = {LOCAL: None, REMOTE: self.ctn_latest(REMOTE) + 1} next_ctn = self.ctn_latest(REMOTE) + 1
if not self.is_htlc_active_at_ctn(ctx_owner=REMOTE, ctn=next_ctn, htlc_proposer=REMOTE, htlc_id=htlc_id):
raise Exception(f"(local) cannot remove htlc that is not there...")
self.log[REMOTE]['settles'][htlc_id] = {LOCAL: None, REMOTE: next_ctn}
def recv_settle(self, htlc_id: int) -> None: def recv_settle(self, htlc_id: int) -> None:
self.log[LOCAL]['settles'][htlc_id] = {LOCAL: self.ctn_latest(LOCAL) + 1, REMOTE: None} next_ctn = self.ctn_latest(LOCAL) + 1
if not self.is_htlc_active_at_ctn(ctx_owner=LOCAL, ctn=next_ctn, htlc_proposer=LOCAL, htlc_id=htlc_id):
raise Exception(f"(remote) cannot remove htlc that is not there...")
self.log[LOCAL]['settles'][htlc_id] = {LOCAL: next_ctn, REMOTE: None}
def send_fail(self, htlc_id: int) -> None: def send_fail(self, htlc_id: int) -> None:
self.log[REMOTE]['fails'][htlc_id] = {LOCAL: None, REMOTE: self.ctn_latest(REMOTE) + 1} next_ctn = self.ctn_latest(REMOTE) + 1
if not self.is_htlc_active_at_ctn(ctx_owner=REMOTE, ctn=next_ctn, htlc_proposer=REMOTE, htlc_id=htlc_id):
raise Exception(f"(local) cannot remove htlc that is not there...")
self.log[REMOTE]['fails'][htlc_id] = {LOCAL: None, REMOTE: next_ctn}
def recv_fail(self, htlc_id: int) -> None: def recv_fail(self, htlc_id: int) -> None:
self.log[LOCAL]['fails'][htlc_id] = {LOCAL: self.ctn_latest(LOCAL) + 1, REMOTE: None} next_ctn = self.ctn_latest(LOCAL) + 1
if not self.is_htlc_active_at_ctn(ctx_owner=LOCAL, ctn=next_ctn, htlc_proposer=LOCAL, htlc_id=htlc_id):
raise Exception(f"(remote) cannot remove htlc that is not there...")
self.log[LOCAL]['fails'][htlc_id] = {LOCAL: next_ctn, REMOTE: None}
def send_update_fee(self, feerate: int) -> None: def send_update_fee(self, feerate: int) -> None:
fee_update = FeeUpdate(rate=feerate, fee_update = FeeUpdate(rate=feerate,
@ -249,6 +261,20 @@ class HTLCManager:
##### Queries re HTLCs: ##### Queries re HTLCs:
def is_htlc_active_at_ctn(self, *, ctx_owner: HTLCOwner, ctn: int,
htlc_proposer: HTLCOwner, htlc_id: int) -> bool:
if htlc_id >= self.get_next_htlc_id(htlc_proposer):
return False
settles = self.log[htlc_proposer]['settles']
fails = self.log[htlc_proposer]['fails']
ctns = self.log[htlc_proposer]['locked_in'][htlc_id]
if ctns[ctx_owner] is not None and ctns[ctx_owner] <= ctn:
not_settled = htlc_id not in settles or settles[htlc_id][ctx_owner] is None or settles[htlc_id][ctx_owner] > ctn
not_failed = htlc_id not in fails or fails[htlc_id][ctx_owner] is None or fails[htlc_id][ctx_owner] > ctn
if not_settled and not_failed:
return True
return False
def htlcs_by_direction(self, subject: HTLCOwner, direction: Direction, def htlcs_by_direction(self, subject: HTLCOwner, direction: Direction,
ctn: int = None) -> Dict[int, UpdateAddHtlc]: ctn: int = None) -> Dict[int, UpdateAddHtlc]:
"""Return the dict of received or sent (depending on direction) HTLCs """Return the dict of received or sent (depending on direction) HTLCs
@ -264,19 +290,13 @@ class HTLCManager:
# subject's ctx # subject's ctx
# party is the proposer of the HTLCs # party is the proposer of the HTLCs
party = subject if direction == SENT else subject.inverted() party = subject if direction == SENT else subject.inverted()
settles = self.log[party]['settles']
fails = self.log[party]['fails']
if ctn >= self.ctn_oldest_unrevoked(subject): if ctn >= self.ctn_oldest_unrevoked(subject):
considered_htlc_ids = self._maybe_active_htlc_ids[party] considered_htlc_ids = self._maybe_active_htlc_ids[party]
else: # ctn is too old; need to consider full log (slow...) else: # ctn is too old; need to consider full log (slow...)
considered_htlc_ids = self.log[party]['locked_in'] considered_htlc_ids = self.log[party]['locked_in']
for htlc_id in considered_htlc_ids: for htlc_id in considered_htlc_ids:
ctns = self.log[party]['locked_in'][htlc_id] if self.is_htlc_active_at_ctn(ctx_owner=subject, ctn=ctn, htlc_proposer=party, htlc_id=htlc_id):
if ctns[subject] is not None and ctns[subject] <= ctn: d[htlc_id] = self.log[party]['adds'][htlc_id]
not_settled = htlc_id not in settles or settles[htlc_id][subject] is None or settles[htlc_id][subject] > ctn
not_failed = htlc_id not in fails or fails[htlc_id][subject] is None or fails[htlc_id][subject] > ctn
if not_settled and not_failed:
d[htlc_id] = self.log[party]['adds'][htlc_id]
return d return d
def htlcs(self, subject: HTLCOwner, ctn: int = None) -> Sequence[Tuple[Direction, UpdateAddHtlc]]: def htlcs(self, subject: HTLCOwner, ctn: int = None) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:

7
electrum/lnonion.py

@ -45,6 +45,7 @@ PER_HOP_HMAC_SIZE = 32
class UnsupportedOnionPacketVersion(Exception): pass class UnsupportedOnionPacketVersion(Exception): pass
class InvalidOnionMac(Exception): pass class InvalidOnionMac(Exception): pass
class InvalidOnionPubkey(Exception): pass
class OnionPerHop: class OnionPerHop:
@ -109,6 +110,8 @@ class OnionPacket:
self.public_key = public_key self.public_key = public_key
self.hops_data = hops_data # also called RoutingInfo in bolt-04 self.hops_data = hops_data # also called RoutingInfo in bolt-04
self.hmac = hmac self.hmac = hmac
if not ecc.ECPubkey.is_pubkey_bytes(public_key):
raise InvalidOnionPubkey()
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
ret = bytes([self.version]) ret = bytes([self.version])
@ -243,6 +246,8 @@ class ProcessedOnionPacket(NamedTuple):
# TODO replay protection # TODO replay protection
def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes, def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes,
our_onion_private_key: bytes) -> ProcessedOnionPacket: our_onion_private_key: bytes) -> ProcessedOnionPacket:
if not ecc.ECPubkey.is_pubkey_bytes(onion_packet.public_key):
raise InvalidOnionPubkey()
shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key)
# check message integrity # check message integrity
@ -322,7 +327,7 @@ def construct_onion_error(reason: OnionRoutingFailureMessage,
def _decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[bytes], def _decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[bytes],
session_key: bytes) -> (bytes, int): session_key: bytes) -> Tuple[bytes, int]:
"""Returns the decoded error bytes, and the index of the sender of the error.""" """Returns the decoded error bytes, and the index of the sender of the error."""
num_hops = len(payment_path_pubkeys) num_hops = len(payment_path_pubkeys)
hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key) hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key)

124
electrum/lnpeer.py

@ -30,7 +30,8 @@ from .transaction import Transaction, TxOutput, PartialTxOutput, match_script_ag
from .logging import Logger from .logging import Logger
from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment, from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage, process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage,
ProcessedOnionPacket) ProcessedOnionPacket, UnsupportedOnionPacketVersion, InvalidOnionMac, InvalidOnionPubkey,
OnionFailureCodeMetaFlag)
from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture, channel_states, peer_states from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture, channel_states, peer_states
from . import lnutil from . import lnutil
from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
@ -229,7 +230,7 @@ class Peer(Logger):
chan.set_remote_update(payload['raw']) chan.set_remote_update(payload['raw'])
self.logger.info("saved remote_update") self.logger.info("saved remote_update")
def on_announcement_signatures(self, chan, payload): def on_announcement_signatures(self, chan: Channel, payload):
if chan.config[LOCAL].was_announced: if chan.config[LOCAL].was_announced:
h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
else: else:
@ -900,7 +901,7 @@ class Peer(Logger):
if chan.config[LOCAL].funding_locked_received and chan.short_channel_id: if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
self.mark_open(chan) self.mark_open(chan)
def on_funding_locked(self, chan, payload): def on_funding_locked(self, chan: Channel, payload):
self.logger.info(f"on_funding_locked. channel: {bh2u(chan.channel_id)}") self.logger.info(f"on_funding_locked. channel: {bh2u(chan.channel_id)}")
if not chan.config[LOCAL].funding_locked_received: if not chan.config[LOCAL].funding_locked_received:
their_next_point = payload["next_per_commitment_point"] their_next_point = payload["next_per_commitment_point"]
@ -926,7 +927,7 @@ class Peer(Logger):
asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
@log_exceptions @log_exceptions
async def handle_announcements(self, chan): async def handle_announcements(self, chan: Channel):
h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get() announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get()
remote_node_sig = announcement_signatures_msg["node_signature"] remote_node_sig = announcement_signatures_msg["node_signature"]
@ -1002,11 +1003,11 @@ class Peer(Logger):
) )
return msg_hash, node_signature, bitcoin_signature return msg_hash, node_signature, bitcoin_signature
def on_update_fail_htlc(self, chan, payload): def on_update_fail_htlc(self, chan: Channel, payload):
htlc_id = int.from_bytes(payload["id"], "big") htlc_id = int.from_bytes(payload["id"], "big")
reason = payload["reason"] reason = payload["reason"]
self.logger.info(f"on_update_fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") self.logger.info(f"on_update_fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}")
chan.receive_fail_htlc(htlc_id, reason) chan.receive_fail_htlc(htlc_id, reason) # TODO handle exc and maybe fail channel (e.g. bad htlc_id)
self.maybe_send_commitment(chan) self.maybe_send_commitment(chan)
def maybe_send_commitment(self, chan: Channel): def maybe_send_commitment(self, chan: Channel):
@ -1057,7 +1058,7 @@ class Peer(Logger):
next_per_commitment_point=rev.next_per_commitment_point) next_per_commitment_point=rev.next_per_commitment_point)
self.maybe_send_commitment(chan) self.maybe_send_commitment(chan)
def on_commitment_signed(self, chan, payload): def on_commitment_signed(self, chan: Channel, payload):
if chan.peer_state == peer_states.BAD: if chan.peer_state == peer_states.BAD:
return return
self.logger.info(f'on_commitment_signed. chan {chan.short_channel_id}. ctn: {chan.get_next_ctn(LOCAL)}.') self.logger.info(f'on_commitment_signed. chan {chan.short_channel_id}. ctn: {chan.get_next_ctn(LOCAL)}.')
@ -1075,19 +1076,29 @@ class Peer(Logger):
chan.receive_new_commitment(payload["signature"], htlc_sigs) chan.receive_new_commitment(payload["signature"], htlc_sigs)
self.send_revoke_and_ack(chan) self.send_revoke_and_ack(chan)
def on_update_fulfill_htlc(self, chan, payload): def on_update_fulfill_htlc(self, chan: Channel, payload):
preimage = payload["payment_preimage"] preimage = payload["payment_preimage"]
payment_hash = sha256(preimage) payment_hash = sha256(preimage)
htlc_id = int.from_bytes(payload["id"], "big") htlc_id = int.from_bytes(payload["id"], "big")
self.logger.info(f"on_update_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") self.logger.info(f"on_update_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}")
chan.receive_htlc_settle(preimage, htlc_id) chan.receive_htlc_settle(preimage, htlc_id) # TODO handle exc and maybe fail channel (e.g. bad htlc_id)
self.lnworker.save_preimage(payment_hash, preimage) self.lnworker.save_preimage(payment_hash, preimage)
self.maybe_send_commitment(chan) self.maybe_send_commitment(chan)
def on_update_fail_malformed_htlc(self, chan, payload): def on_update_fail_malformed_htlc(self, chan: Channel, payload):
self.logger.info(f"on_update_fail_malformed_htlc. error {payload['data'].decode('ascii')}") htlc_id = payload["id"]
failure_code = payload["failure_code"]
self.logger.info(f"on_update_fail_malformed_htlc. chan {chan.get_id_for_log()}. "
f"htlc_id {htlc_id}. failure_code={failure_code}")
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 = b'' # TODO somehow propagate "failure_code" ?
chan.receive_fail_htlc(htlc_id, reason) # TODO handle exc and maybe fail channel (e.g. bad htlc_id)
self.maybe_send_commitment(chan)
# TODO when forwarding, we need to propagate this "update_fail_malformed_htlc" downstream
def on_update_add_htlc(self, chan, payload): def on_update_add_htlc(self, chan: Channel, payload):
payment_hash = payload["payment_hash"] payment_hash = payload["payment_hash"]
htlc_id = int.from_bytes(payload["id"], 'big') htlc_id = int.from_bytes(payload["id"], 'big')
self.logger.info(f"on_update_add_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") self.logger.info(f"on_update_add_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}")
@ -1211,19 +1222,27 @@ class Peer(Logger):
id=htlc_id, id=htlc_id,
payment_preimage=preimage) payment_preimage=preimage)
def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket, def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: Optional[OnionPacket],
reason: OnionRoutingFailureMessage): reason: OnionRoutingFailureMessage):
self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}. reason: {reason}") self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}. reason: {reason}")
assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}"
chan.fail_htlc(htlc_id) chan.fail_htlc(htlc_id)
error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey) if onion_packet:
self.send_message("update_fail_htlc", error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey)
channel_id=chan.channel_id, self.send_message("update_fail_htlc",
id=htlc_id, channel_id=chan.channel_id,
len=len(error_packet), id=htlc_id,
reason=error_packet) len=len(error_packet),
reason=error_packet)
def on_revoke_and_ack(self, chan, payload): else:
assert len(reason.data) == 32, f"unexpected reason when sending 'update_fail_malformed_htlc': {reason!r}"
self.send_message("update_fail_malformed_htlc",
channel_id=chan.channel_id,
id=htlc_id,
sha256_of_onion=reason.data,
failure_code=reason.code)
def on_revoke_and_ack(self, chan: Channel, payload):
if chan.peer_state == peer_states.BAD: if chan.peer_state == peer_states.BAD:
return return
self.logger.info(f'on_revoke_and_ack. chan {chan.short_channel_id}. ctn: {chan.get_oldest_unrevoked_ctn(REMOTE)}') self.logger.info(f'on_revoke_and_ack. chan {chan.short_channel_id}. ctn: {chan.get_oldest_unrevoked_ctn(REMOTE)}')
@ -1232,7 +1251,7 @@ class Peer(Logger):
self.lnworker.save_channel(chan) self.lnworker.save_channel(chan)
self.maybe_send_commitment(chan) self.maybe_send_commitment(chan)
def on_update_fee(self, chan, payload): def on_update_fee(self, chan: Channel, payload):
feerate = int.from_bytes(payload["feerate_per_kw"], "big") feerate = int.from_bytes(payload["feerate_per_kw"], "big")
chan.update_fee(feerate, False) chan.update_fee(feerate, False)
@ -1282,7 +1301,7 @@ class Peer(Logger):
return txid return txid
@log_exceptions @log_exceptions
async def on_shutdown(self, chan, payload): async def on_shutdown(self, chan: Channel, payload):
their_scriptpubkey = payload['scriptpubkey'] their_scriptpubkey = payload['scriptpubkey']
# BOLT-02 restrict the scriptpubkey to some templates: # BOLT-02 restrict the scriptpubkey to some templates:
if not (match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0) if not (match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0)
@ -1404,33 +1423,44 @@ class Peer(Logger):
if chan.get_oldest_unrevoked_ctn(REMOTE) <= remote_ctn: if chan.get_oldest_unrevoked_ctn(REMOTE) <= remote_ctn:
continue continue
chan.logger.info(f'found unfulfilled htlc: {htlc_id}') chan.logger.info(f'found unfulfilled htlc: {htlc_id}')
onion_packet = OnionPacket.from_bytes(bytes.fromhex(onion_packet_hex))
htlc = chan.hm.log[REMOTE]['adds'][htlc_id] htlc = chan.hm.log[REMOTE]['adds'][htlc_id]
payment_hash = htlc.payment_hash payment_hash = htlc.payment_hash
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) error = None # type: Optional[OnionRoutingFailureMessage]
preimage, error = None, None preimage = None
if processed_onion.are_we_final: onion_packet_bytes = bytes.fromhex(onion_packet_hex)
preimage, error = self.maybe_fulfill_htlc( onion_packet = None
chan=chan, try:
htlc=htlc, onion_packet = OnionPacket.from_bytes(onion_packet_bytes)
onion_packet=onion_packet, processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
processed_onion=processed_onion) except UnsupportedOnionPacketVersion:
elif not forwarded: error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_VERSION, data=sha256(onion_packet_bytes))
error = self.maybe_forward_htlc( except InvalidOnionPubkey:
chan=chan, error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_KEY, data=sha256(onion_packet_bytes))
htlc=htlc, except InvalidOnionMac:
onion_packet=onion_packet, error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_HMAC, data=sha256(onion_packet_bytes))
processed_onion=processed_onion)
if not error:
unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, True
else: else:
f = self.lnworker.pending_payments[payment_hash] if processed_onion.are_we_final:
if f.done(): preimage, error = self.maybe_fulfill_htlc(
success, preimage, error = f.result() chan=chan,
if preimage: htlc=htlc,
await self.lnworker.enable_htlc_settle.wait() onion_packet=onion_packet,
self.fulfill_htlc(chan, htlc.htlc_id, preimage) processed_onion=processed_onion)
done.add(htlc_id) elif not forwarded:
error = self.maybe_forward_htlc(
chan=chan,
htlc=htlc,
onion_packet=onion_packet,
processed_onion=processed_onion)
if not error:
unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, True
else:
f = self.lnworker.pending_payments[payment_hash]
if f.done():
success, preimage, error = f.result()
if preimage:
await self.lnworker.enable_htlc_settle.wait()
self.fulfill_htlc(chan, htlc.htlc_id, preimage)
done.add(htlc_id)
if error: if error:
self.fail_htlc(chan, htlc.htlc_id, onion_packet, error) self.fail_htlc(chan, htlc.htlc_id, onion_packet, error)
done.add(htlc_id) done.add(htlc_id)

5
electrum/lnworker.py

@ -958,6 +958,9 @@ class LNWallet(LNWorker):
if success: if success:
failure_log = None failure_log = None
else: else:
# TODO this blacklisting is fragile, consider (who to ban/penalize?):
# - we might not be able to decode "reason" (coming from update_fail_htlc).
# - handle update_fail_malformed_htlc case, where there is (kinda) no "reason"
failure_msg, sender_idx = chan.decode_onion_error(reason, route, htlc.htlc_id) failure_msg, sender_idx = chan.decode_onion_error(reason, route, htlc.htlc_id)
blacklist = self.handle_error_code_from_failed_htlc(failure_msg, sender_idx, route, peer) blacklist = self.handle_error_code_from_failed_htlc(failure_msg, sender_idx, route, peer)
if blacklist: if blacklist:
@ -1216,7 +1219,7 @@ class LNWallet(LNWorker):
info = info._replace(status=status) info = info._replace(status=status)
self.save_payment_info(info) self.save_payment_info(info)
def payment_failed(self, chan, payment_hash: bytes, reason): def payment_failed(self, chan, payment_hash: bytes, reason: bytes):
self.set_payment_status(payment_hash, PR_UNPAID) self.set_payment_status(payment_hash, PR_UNPAID)
key = payment_hash.hex() key = payment_hash.hex()
f = self.pending_payments.get(payment_hash) f = self.pending_payments.get(payment_hash)

Loading…
Cancel
Save