# Buggy code
def _check_sigs_and_hashes(self, room_version, pdus):
    """Checks that each of the received events is correctly signed by the
    sending server.

    Args:
        room_version (str): The room version of the PDUs
        pdus (list[FrozenEvent]): the events to be checked

    Returns:
        list[Deferred]: for each input event, a deferred which:
          * returns the original event if the checks pass
          * returns a redacted version of the event (if the signature
            matched but the hash did not)
          * throws a SynapseError if the signature check failed.
        The deferreds run their callbacks in the sentinel
    """
    deferreds = _check_sigs_on_pdus(self.keyring, room_version, pdus)

    ctx = LoggingContext.current_context()

    def callback(_, pdu):
        with PreserveLoggingContext(ctx):
            if not check_event_content_hash(pdu):
                # let's try to distinguish between failures because the event was
                # redacted (which are somewhat expected) vs actual ball-tampering
                # incidents.
                #
                # This is just a heuristic, so we just assume that if the keys are
                # about the same between the redacted and received events, then the
                # received event was probably a redacted copy (but we then use our
                # *actual* redacted copy to be on the safe side.)
                redacted_event = prune_event(pdu)
                if set(redacted_event.keys()) == set(pdu.keys()) and set(
                    six.iterkeys(redacted_event.content)
                ) == set(six.iterkeys(pdu.content)):
                    logger.info(
                        "Event %s seems to have been redacted; using our redacted copy",
                        pdu.event_id,
                    )
                else:
                    logger.warning(
                        "Event %s content has been tampered, redacting",
                        pdu.event_id,
                    )
                return redacted_event

            if self.spam_checker.check_event_for_spam(pdu):
                logger.warning(
                    "Event contains spam, redacting %s: %s",
                    pdu.event_id,
                    pdu.get_pdu_json(),
                )
                return prune_event(pdu)

            return pdu

    def errback(failure, pdu):
        failure.trap(SynapseError)
        with PreserveLoggingContext(ctx):
            logger.warning(
                "Signature check failed for %s: %s",
                pdu.event_id,
                failure.getErrorMessage(),
            )
        return failure

    for deferred, pdu in zip(deferreds, pdus):
        deferred.addCallbacks(callback, errback, callbackArgs=[pdu], errbackArgs=[pdu])

    return deferreds


# Traceback (AttributeError)
2020-02-24 14:00:10,088 - synapse.federation.federation_client - 421 - WARNING - POST-1957- Failed to send_join via matrix.org
...
Traceback (most recent call last):
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 402, in _try_destination_list
res = await callback(destination)
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 589, in send_request
valid_pdus_map = {p.event_id: p for p in valid_pdus}
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 589, in <dictcomp>
valid_pdus_map = {p.event_id: p for p in valid_pdus}
AttributeError: 'coroutine' object has no attribute 'event_id'
2020-02-24 14:00:10,154 - synapse.http.server - 81 - INFO - POST-1957- <SynapseRequest at 0x7ff1bf3efd30 method='POST' uri='/_matrix/client/r0/join/%23synapse%3Amatrix.org' clientproto='HTTP/1.1' site=8447> SynapseError: 502 - Failed to send_join via any server

# Fixed code
def _check_sigs_and_hashes(
    self, room_version: str, pdus: List[EventBase]
) -> List[Deferred]:
    """Checks that each of the received events is correctly signed by the
    sending server.

    Args:
        room_version: The room version of the PDUs
        pdus: the events to be checked

    Returns:
        For each input event, a deferred which:
          * returns the original event if the checks pass
          * returns a redacted version of the event (if the signature
            matched but the hash did not)
          * throws a SynapseError if the signature check failed.
        The deferreds run their callbacks in the sentinel
    """
    deferreds = _check_sigs_on_pdus(self.keyring, room_version, pdus)

    ctx = LoggingContext.current_context()

    def callback(_, pdu: EventBase):
        with PreserveLoggingContext(ctx):
            if not check_event_content_hash(pdu):
                # let's try to distinguish between failures because the event was
                # redacted (which are somewhat expected) vs actual ball-tampering
                # incidents.
                #
                # This is just a heuristic, so we just assume that if the keys are
                # about the same between the redacted and received events, then the
                # received event was probably a redacted copy (but we then use our
                # *actual* redacted copy to be on the safe side.)
                redacted_event = prune_event(pdu)
                if set(redacted_event.keys()) == set(pdu.keys()) and set(
                    six.iterkeys(redacted_event.content)
                ) == set(six.iterkeys(pdu.content)):
                    logger.info(
                        "Event %s seems to have been redacted; using our redacted copy",
                        pdu.event_id,
                    )
                else:
                    logger.warning(
                        "Event %s content has been tampered, redacting",
                        pdu.event_id,
                    )
                return redacted_event

            if self.spam_checker.check_event_for_spam(pdu):
                logger.warning(
                    "Event contains spam, redacting %s: %s",
                    pdu.event_id,
                    pdu.get_pdu_json(),
                )
                return prune_event(pdu)

            return pdu

    def errback(failure: Failure, pdu: EventBase):
        failure.trap(SynapseError)
        with PreserveLoggingContext(ctx):
            logger.warning(
                "Signature check failed for %s: %s",
                pdu.event_id,
                failure.getErrorMessage(),
            )
        return failure

    for deferred, pdu in zip(deferreds, pdus):
        deferred.addCallbacks(callback, errback, callbackArgs=[pdu], errbackArgs=[pdu])

    return deferreds