async def negotiate_protocol_handshakes(transport: TransportAPI, p2p_handshake_params: DevP2PHandshakeParams, protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]]) -> Tuple[(MultiplexerAPI, DevP2PReceipt, Tuple[(HandshakeReceiptAPI, ...)])]:
    '\n    Negotiate the handshakes for both the base `p2p` protocol and the\n    appropriate sub protocols.  The basic logic follows the following steps.\n\n    * perform the base `p2p` handshake.\n    * using the capabilities exchanged during the `p2p` handshake, select the\n      appropriate sub protocols.\n    * allow each sub-protocol to perform its own handshake.\n    * return the established `Multiplexer` as well as the `HandshakeReceipt`\n      objects from each handshake.\n    '
    p2p_protocol_class = p2p_handshake_params.get_base_protocol_class()
    local_capabilities = tuple((handshaker.protocol_class.as_capability() for handshaker in protocol_handshakers))
    duplicate_capabilities = duplicates(local_capabilities)
    if duplicate_capabilities:
        raise Exception(f'Duplicate local capabilities: {duplicate_capabilities}')
    ephemeral_base_protocol = p2p_protocol_class(transport, command_id_offset=0, snappy_support=False)
    (devp2p_receipt, base_protocol) = (await _do_p2p_handshake(transport, local_capabilities, p2p_handshake_params, ephemeral_base_protocol))
    protocol_handshakers_by_capability = dict(zip(local_capabilities, protocol_handshakers))
    selected_capabilities = _select_capabilities(devp2p_receipt.capabilities, local_capabilities)
    if (len(selected_capabilities) < 1):
        raise NoMatchingPeerCapabilities(f'''Found no matching capabilities between self and peer:
 - local : {tuple(sorted(local_capabilities))}
 - remote: {devp2p_receipt.capabilities}''')
    selected_handshakers = tuple((protocol_handshakers_by_capability[capability] for capability in selected_capabilities))
    selected_protocol_types = tuple((handshaker.protocol_class for handshaker in selected_handshakers))
    protocol_cmd_offsets = get_cmd_offsets(selected_protocol_types)
    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)))
    multiplexer = Multiplexer(transport, base_protocol, selected_protocols)
    (await multiplexer.stream_in_background())
    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:
        multiplexer.raise_if_streaming_error()
        (await multiplexer.stop_streaming())
        raise handshake_err
    else:
        multiplexer.raise_if_streaming_error()
    return (multiplexer, devp2p_receipt, protocol_receipts)
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.
async def negotiate_protocol_handshakes(transport: TransportAPI, p2p_handshake_params: DevP2PHandshakeParams, protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]]) -> Tuple[(MultiplexerAPI, DevP2PReceipt, Tuple[(HandshakeReceiptAPI, ...)])]:
    '\n    Negotiate the handshakes for both the base `p2p` protocol and the\n    appropriate sub protocols.  The basic logic follows the following steps.\n\n    * perform the base `p2p` handshake.\n    * using the capabilities exchanged during the `p2p` handshake, select the\n      appropriate sub protocols.\n    * allow each sub-protocol to perform its own handshake.\n    * return the established `Multiplexer` as well as the `HandshakeReceipt`\n      objects from each handshake.\n    '
    p2p_protocol_class = p2p_handshake_params.get_base_protocol_class()
    local_capabilities = tuple((handshaker.protocol_class.as_capability() for handshaker in protocol_handshakers))
    duplicate_capabilities = duplicates(local_capabilities)
    if duplicate_capabilities:
        raise Exception(f'Duplicate local capabilities: {duplicate_capabilities}')
    ephemeral_base_protocol = p2p_protocol_class(transport, command_id_offset=0, snappy_support=False)
    (devp2p_receipt, base_protocol) = (await _do_p2p_handshake(transport, local_capabilities, p2p_handshake_params, ephemeral_base_protocol))
    protocol_handshakers_by_capability = dict(zip(local_capabilities, protocol_handshakers))
    selected_capabilities = _select_capabilities(devp2p_receipt.capabilities, local_capabilities)
    if (len(selected_capabilities) < 1):
        raise NoMatchingPeerCapabilities(f'Found no matching capabilities between self and peer:
 - local : {tuple(sorted(local_capabilities))}
 - remote: {devp2p_receipt.capabilities}')
    selected_handshakers = tuple((protocol_handshakers_by_capability[capability] for capability in selected_capabilities))
    selected_protocol_types = tuple((handshaker.protocol_class for handshaker in selected_handshakers))
    protocol_cmd_offsets = get_cmd_offsets(selected_protocol_types)
    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)))
    multiplexer = Multiplexer(transport, base_protocol, selected_protocols)
    (await multiplexer.stream_in_background())
    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:
        multiplexer.raise_if_streaming_error()
        (await multiplexer.stop_streaming())
        raise handshake_err
    else:
        multiplexer.raise_if_streaming_error()
    return (multiplexer, devp2p_receipt, protocol_receipts)