Esempio n. 1
0
async def negotiate_protocol_handshakes(
    transport: TransportAPI,
    p2p_handshake_params: DevP2PHandshakeParams,
    protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]],
) -> Tuple[MultiplexerAPI, DevP2PReceipt, Tuple[HandshakeReceiptAPI,
                                                ...]]:  # noqa: E501
    """
    Negotiate the handshakes for both the base `p2p` protocol and the
    appropriate sub protocols.  The basic logic follows the following steps.

    * perform the base `p2p` handshake.
    * using the capabilities exchanged during the `p2p` handshake, select the
      appropriate sub protocols.
    * allow each sub-protocol to perform its own handshake.
    * return the established `Multiplexer` as well as the `HandshakeReceipt`
      objects from each handshake.
    """
    # The `p2p` Protocol class that will be used.
    p2p_protocol_class = p2p_handshake_params.get_base_protocol_class()

    # Collect our local capabilities, the set of (name, version) pairs for all
    # of the protocols that we support.
    local_capabilities = tuple(handshaker.protocol_class.as_capability()
                               for handshaker in protocol_handshakers)

    # Verify that there are no duplicated local or remote capabilities
    duplicate_capabilities = duplicates(local_capabilities)
    if duplicate_capabilities:
        raise Exception(
            f"Duplicate local capabilities: {duplicate_capabilities}")

    # We create an *ephemeral* version of the base `p2p` protocol with snappy
    # compression disabled for the handshake.  As part of the handshake, a new
    # instance of this protocol will be created with snappy compression enabled
    # if it is supported by the protocol version.
    ephemeral_base_protocol = p2p_protocol_class(
        transport,
        command_id_offset=0,
        snappy_support=False,
    )

    # Perform the actual `p2p` protocol handshake.  We need the remote
    # capabilities data from the receipt to select the appropriate sub
    # protocols.
    devp2p_receipt, base_protocol = await _do_p2p_handshake(
        transport,
        local_capabilities,
        p2p_handshake_params,
        ephemeral_base_protocol,
    )

    # This data structure is simply for easy retrieval of the proper
    # `Handshaker` for each selected protocol.
    protocol_handshakers_by_capability = dict(
        zip(local_capabilities, protocol_handshakers))
    # Using our local capabilities and the ones transmitted by the remote
    # select the highest shared version of each shared protocol.
    selected_capabilities = _select_capabilities(
        devp2p_receipt.capabilities,
        local_capabilities,
    )
    # If there are no capability matches throw an exception.
    if len(selected_capabilities) < 1:
        raise NoMatchingPeerCapabilities(
            "Found no matching capabilities between self and peer:\n"
            f" - local : {tuple(sorted(local_capabilities))}\n"
            f" - remote: {devp2p_receipt.capabilities}")

    # Retrieve the handshakers which correspond to the selected protocols.
    # These are needed to perform the actual handshake logic for each protocol.
    selected_handshakers = tuple(protocol_handshakers_by_capability[capability]
                                 for capability in selected_capabilities)
    # Grab the `Protocol` class for each of the selected protocols.  We need
    # this to compute the offsets for each protocol's command ids, as well as
    # for instantiation of the protocol instances.
    selected_protocol_types = tuple(handshaker.protocol_class
                                    for handshaker in selected_handshakers)
    # Compute the offsets for each protocol's command ids
    protocol_cmd_offsets = get_cmd_offsets(selected_protocol_types)
    # Now instantiate instances of each of the protocol classes.
    selected_protocols = tuple(
        protocol_class(transport, command_id_offset,
                       base_protocol.snappy_support)
        for protocol_class, command_id_offset in zip(selected_protocol_types,
                                                     protocol_cmd_offsets))
    # Create `Multiplexer` to abstract all of the protocols into a single
    # interface to stream only messages relevant to the given protocol.
    multiplexer = Multiplexer(transport, base_protocol, selected_protocols)

    # This context manager runs a background task which reads messages off of
    # the `Transport` and feeds them into protocol specific queues.  Each
    # protocol is responsible for reading its own messages from that queue via
    # the `Multiplexer.stream_protocol_messages` API.
    await multiplexer.stream_in_background()
    # Concurrently perform the handshakes for each protocol, gathering up
    # the returned receipts.
    try:
        protocol_receipts = cast(
            Tuple[HandshakeReceiptAPI, ...], await
            asyncio.gather(*(handshaker.do_handshake(multiplexer, protocol)
                             for handshaker, protocol in zip(
                                 selected_handshakers, selected_protocols))))
    except BaseException as handshake_err:
        # If the multiplexer has a streaming error, that will certainly be the cause of
        # whatever handshake error we got, so raise that instead.
        multiplexer.raise_if_streaming_error()
        # Ok, no streaming error from the multiplexer, so stop it and raise the handshake error.
        await multiplexer.stop_streaming()
        raise handshake_err
    else:
        # The handshake was successful, but there's a chance the multiplexer's streaming stopped
        # after that, so we may raise that here to prevent an attempt to use a stopped multiplexer
        # further.
        multiplexer.raise_if_streaming_error()

    # Return the `Multiplexer` object as well as the handshake receipts.  The
    # `Multiplexer` object acts as a container for the individual protocol
    # instances.
    return multiplexer, devp2p_receipt, protocol_receipts
Esempio n. 2
0
def test_duplicates_with_identity_fn(elements, expected):
    dups = duplicates(elements)
    assert dups == expected