def process_unfulfilled_htlc(
            self, *,
            chan: Channel,
            htlc: UpdateAddHtlc,
            forwarding_key: Optional[str],
            onion_packet_bytes: bytes,
            onion_packet: OnionPacket) -> Tuple[Optional[bytes], Optional[str], Optional[bytes]]:
        """
        return (preimage, payment_key, error_bytes) with at most a single element that is not 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=payment_hash,
            onion_packet_bytes=onion_packet_bytes)

        preimage, forwarding_info = self.maybe_fulfill_htlc(
            chan=chan,
            htlc=htlc,
            processed_onion=processed_onion,
            onion_packet_bytes=onion_packet_bytes,
            already_forwarded=bool(forwarding_key))

        if not forwarding_key:
            if forwarding_info:
                # HTLC we are supposed to forward, but haven't forwarded yet
                payment_key, forwarding_callback = forwarding_info
                if not self.lnworker.enable_htlc_forwarding:
                    return None, None, None
                if payment_key not in self.lnworker.active_forwardings:
                    async def wrapped_callback():
                        forwarding_coro = forwarding_callback()
                        try:
                            next_htlc = await forwarding_coro
                            if next_htlc:
                                htlc_key = serialize_htlc_key(chan.get_scid_or_local_alias(), htlc.htlc_id)
                                self.lnworker.active_forwardings[payment_key].append(next_htlc)
                                self.lnworker.downstream_to_upstream_htlc[next_htlc] = htlc_key
                        except OnionRoutingFailure as e:
                            if len(self.lnworker.active_forwardings[payment_key]) == 0:
                                self.lnworker.save_forwarding_failure(payment_key, failure_message=e)
                        # TODO what about other errors? e.g. TxBroadcastError for a swap.
                        #        - malicious electrum server could fake TxBroadcastError
                        #      Could we "catch-all Exception" and fail back the htlcs with e.g. TEMPORARY_NODE_FAILURE?
                        #        - we don't want to fail the inc-HTLC for a syntax error that happens in the callback
                        #      If we don't call save_forwarding_failure(), the inc-HTLC gets stuck until expiry
                        #      and then the inc-channel will get force-closed.
                        #      => forwarding_callback() could have an API with two exceptions types:
                        #        - type1, such as OnionRoutingFailure, that signals we need to fail back the inc-HTLC
                        #        - type2, such as TxBroadcastError, that signals we want to retry the callback
                    # add to list
                    assert len(self.lnworker.active_forwardings.get(payment_key, [])) == 0
                    self.lnworker.active_forwardings[payment_key] = []
                    fut = asyncio.ensure_future(wrapped_callback())
                # return payment_key so this branch will not be executed again
                return None, payment_key, None
            elif preimage:
                return preimage, None, None
            else:
                # we are waiting for mpp consolidation or preimage
                return None, None, None
        else:
            # HTLC we are supposed to forward, and have already forwarded
            # for final trampoline onions, forwarding failures are stored with forwarding_key (which is the inner key)
            payment_key = forwarding_key
            preimage = self.lnworker.get_preimage(payment_hash)
            error_bytes, error_reason = self.lnworker.get_forwarding_failure(payment_key)
            if error_bytes:
                return None, None, error_bytes
            if error_reason:
                raise error_reason
            if preimage:
                return preimage, None, None
            return None, None, None

        return (preimage, payment_key, error_bytes) with at most a single element that is not None
        raise an OnionRoutingFailure if we need to fail the htlc
        
electrum/lnpeer.py