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
def test_duplicates_with_identity_fn(elements, expected): dups = duplicates(elements) assert dups == expected