def __init__(self, remote: Node, privkey: datatypes.PrivateKey, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, aes_secret: bytes, mac_secret: bytes, egress_mac: sha3.keccak_256, ingress_mac: sha3.keccak_256, headerdb: 'BaseAsyncHeaderDB', network_id: int, inbound: bool = False, ) -> None: super().__init__() self.remote = remote self.privkey = privkey self.reader = reader self.writer = writer self.base_protocol = P2PProtocol(self) self.headerdb = headerdb self.network_id = network_id self.inbound = inbound self._subscribers: List['asyncio.Queue[PEER_MSG_TYPE]'] = [] self.egress_mac = egress_mac self.ingress_mac = ingress_mac # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32 iv = b"\x00" * 16 aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend()) self.aes_enc = aes_cipher.encryptor() self.aes_dec = aes_cipher.decryptor() mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend()) self.mac_enc = mac_cipher.encryptor().update
async def process_p2p_handshake(self, cmd: protocol.Command, msg: protocol.PayloadType) -> None: msg = cast(Dict[str, Any], msg) if not isinstance(cmd, Hello): await self.disconnect(DisconnectReason.bad_protocol) raise HandshakeFailure( f"Expected a Hello msg, got {cmd}, disconnecting") # Check whether to support Snappy Compression or not # based on other peer's p2p protocol version snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION if snappy_support: # Now update the base protocol to support snappy compression # This is needed so that Trinity is compatible with parity since # parity sends Ping immediately after Handshake self.base_protocol = P2PProtocol(self, snappy_support=snappy_support) remote_capabilities = msg['capabilities'] try: self.sub_proto = self.select_sub_protocol(remote_capabilities, snappy_support) except NoMatchingPeerCapabilities: await self.disconnect(DisconnectReason.useless_peer) raise HandshakeFailure( f"No matching capabilities between us ({self.capabilities}) and {self.remote} " f"({remote_capabilities}), disconnecting") self.logger.debug( "Finished P2P handshake with %s, using sub-protocol %s", self.remote, self.sub_proto)
def __init__(self, remote: Node, privkey: datatypes.PrivateKey, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, aes_secret: bytes, mac_secret: bytes, egress_mac: sha3.keccak_256, ingress_mac: sha3.keccak_256, chaindb: AsyncChainDB, network_id: int, ) -> None: self._finished = asyncio.Event() self.remote = remote self.privkey = privkey self.reader = reader self.writer = writer self.base_protocol = P2PProtocol(self) self.chaindb = chaindb self.network_id = network_id self.sub_proto_msg_queue = asyncio.Queue() # type: asyncio.Queue[Tuple[protocol.Command, protocol._DecodedMsgType]] # noqa: E501 self.cancel_token = CancelToken('Peer') self.egress_mac = egress_mac self.ingress_mac = ingress_mac # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32 iv = b"\x00" * 16 aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend()) self.aes_enc = aes_cipher.encryptor() self.aes_dec = aes_cipher.decryptor() mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend()) self.mac_enc = mac_cipher.encryptor().update
def __init__(self, remote: Node, privkey: datatypes.PrivateKey, connection: PeerConnection, context: BasePeerContext, inbound: bool = False, token: CancelToken = None, ) -> None: super().__init__(token) # Any contextual information the peer may need. self.context = context # The `Node` that this peer is connected to self.remote = remote # The private key this peer uses for identification and encryption. self.privkey = privkey # The self-identifying string that the remote names itself. self.client_version_string = '' # Networking reader and writer objects for communication self.reader = connection.reader self.writer = connection.writer # Initially while doing the handshake, the base protocol shouldn't support # snappy compression self.base_protocol = P2PProtocol(self, snappy_support=False) # Flag indicating whether the connection this peer represents was # established from a dial-out or dial-in (True: dial-in, False: # dial-out) # TODO: rename to `dial_in` and have a computed property for `dial_out` self.inbound = inbound self._subscribers: List[PeerSubscriber] = [] # Uptime tracker for how long the peer has been running. # TODO: this should move to begin within the `_run` method (or maybe as # part of the `BaseService` API) self.start_time = datetime.datetime.now() # A counter of the number of messages this peer has received for each # message type. self.received_msgs: Dict[protocol.Command, int] = collections.defaultdict(int) # Encryption and Cryptography *stuff* self.egress_mac = connection.egress_mac self.ingress_mac = connection.ingress_mac # FIXME: Insecure Encryption: https://github.com/ethereum/devp2p/issues/32 iv = b"\x00" * 16 aes_secret = connection.aes_secret mac_secret = connection.mac_secret aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend()) self.aes_enc = aes_cipher.encryptor() self.aes_dec = aes_cipher.decryptor() mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend()) self.mac_enc = mac_cipher.encryptor().update # Manages the boot process self.boot_manager = self.get_boot_manager()
async def process_p2p_handshake(self, cmd: Command, msg: PayloadType) -> None: msg = cast(Dict[str, Any], msg) if not isinstance(cmd, Hello): await self.disconnect(DisconnectReason.bad_protocol) raise HandshakeFailure( f"Expected a Hello msg, got {cmd}, disconnecting") # limit number of chars to be displayed, and try to keep printable ones only # MAGIC 256: arbitrary, "should be enough for everybody" original_version = msg['client_version_string'] client_version_string = original_version[:256] + ( '...' if original_version[256:] else '') if client_version_string.isprintable(): self.client_version_string = client_version_string.strip() else: self.client_version_string = repr(client_version_string) # Check whether to support Snappy Compression or not # based on other peer's p2p protocol version snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION if snappy_support: # Now update the base protocol to support snappy compression # This is needed so that Trinity is compatible with parity since # parity sends Ping immediately after Handshake self.base_protocol = P2PProtocol( self.transport, snappy_support=snappy_support, capabilities=self.capabilities, listen_port=self.listen_port, ) remote_capabilities = msg['capabilities'] matched_proto_classes = match_protocols_with_capabilities( self.supported_sub_protocols, remote_capabilities, ) if len(matched_proto_classes) == 1: self.sub_proto = matched_proto_classes[0]( self.transport, self.base_protocol.cmd_length, snappy_support, ) elif len(matched_proto_classes) > 1: raise NotImplementedError( f"Peer {self.remote} connection matched on multiple protocols " f"{matched_proto_classes}. Support for multiple protocols is not " f"yet supported") else: await self.disconnect(DisconnectReason.useless_peer) raise HandshakeFailure( f"No matching capabilities between us ({self.capabilities}) and {self.remote} " f"({remote_capabilities}), disconnecting") self.logger.debug( "Finished P2P handshake with %s, using sub-protocol %s", self.remote, self.sub_proto)
def MultiplexerPairFactory(*, protocol_types: Tuple[Type[ProtocolAPI], ...] = (), transport_factory: Callable[..., TransportPair] = MemoryTransportPairFactory, # noqa: E501 alice_remote: NodeAPI = None, alice_private_key: keys.PrivateKey = None, bob_remote: NodeAPI = None, bob_private_key: keys.PrivateKey = None, snappy_support: bool = False, cancel_token: CancelToken = None, ) -> Tuple[MultiplexerAPI, MultiplexerAPI]: alice_transport, bob_transport = transport_factory( alice_remote=alice_remote, alice_private_key=alice_private_key, bob_remote=bob_remote, bob_private_key=bob_private_key, ) cmd_id_offsets = get_cmd_offsets(protocol_types) alice_protocols = tuple( protocol_class(alice_transport, offset, snappy_support) for protocol_class, offset in zip(protocol_types, cmd_id_offsets) ) bob_protocols = tuple( protocol_class(bob_transport, offset, snappy_support) for protocol_class, offset in zip(protocol_types, cmd_id_offsets) ) alice_p2p_protocol = P2PProtocol(alice_transport, snappy_support) alice_multiplexer = Multiplexer( transport=alice_transport, base_protocol=alice_p2p_protocol, protocols=alice_protocols, token=cancel_token, ) bob_p2p_protocol = P2PProtocol(bob_transport, False) bob_multiplexer = Multiplexer( transport=bob_transport, base_protocol=bob_p2p_protocol, protocols=bob_protocols, token=cancel_token, ) return alice_multiplexer, bob_multiplexer
async def _do_p2p_handshake( transport: TransportAPI, capabilites: Capabilities, p2p_handshake_params: DevP2PHandshakeParams, base_protocol: BaseP2PProtocol, token: CancelToken) -> Tuple[DevP2PReceipt, BaseP2PProtocol]: client_version_string, listen_port, p2p_version = p2p_handshake_params base_protocol.send_handshake(client_version_string, capabilites, listen_port, p2p_version) # The base `p2p` protocol handshake directly streams the messages as it has # strict requirements about receiving the `Hello` message first. async for _, cmd, msg in stream_transport_messages(transport, base_protocol, token=token): if not isinstance(cmd, Hello): raise HandshakeFailure( f"First message across the DevP2P connection must be a Hello " f"msg, got {cmd}, disconnecting") msg = cast(Dict[str, Any], msg) protocol: BaseP2PProtocol if base_protocol.version >= DEVP2P_V5: # Check whether to support Snappy Compression or not # based on other peer's p2p protocol version snappy_support = msg['version'] >= DEVP2P_V5 if snappy_support: # Now update the base protocol to support snappy compression # This is needed so that Trinity is compatible with parity since # parity sends Ping immediately after handshake protocol = P2PProtocol( transport, cmd_id_offset=0, snappy_support=True, ) else: protocol = base_protocol else: protocol = base_protocol devp2p_receipt = DevP2PReceipt( protocol=protocol, version=msg['version'], client_version_string=msg['client_version_string'], capabilities=msg['capabilities'], remote_public_key=msg['remote_pubkey'], listen_port=msg['listen_port'], ) break else: raise HandshakeFailure( "DevP2P message stream exited before finishing handshake") return devp2p_receipt, protocol
def __init__( self, transport: Transport, context: BasePeerContext, inbound: bool = False, event_bus: AsyncioEndpoint = None, token: CancelToken = None, ) -> None: super().__init__(token) # Transport instance for network communications self.transport = transport # Any contextual information the peer may need. self.context = context # The self-identifying string that the remote names itself. self.client_version_string = '' # Initially while doing the handshake, the base protocol shouldn't support # snappy compression self.base_protocol = P2PProtocol( transport=self.transport, snappy_support=False, capabilities=self.capabilities, listen_port=self.listen_port, ) # Optional event bus handle self._event_bus = event_bus # Flag indicating whether the connection this peer represents was # established from a dial-out or dial-in (True: dial-in, False: # dial-out) # TODO: rename to `dial_in` and have a computed property for `dial_out` self.inbound = inbound self._subscribers: List[PeerSubscriber] = [] # A counter of the number of messages this peer has received for each # message type. self.received_msgs: Dict[Command, int] = collections.defaultdict(int) # Manages the boot process self.boot_manager = self.get_boot_manager() self.connection_tracker = self.setup_connection_tracker()
async def process_p2p_handshake(self, cmd: protocol.Command, msg: protocol.PayloadType) -> None: msg = cast(Dict[str, Any], msg) if not isinstance(cmd, Hello): await self.disconnect(DisconnectReason.bad_protocol) raise HandshakeFailure( f"Expected a Hello msg, got {cmd}, disconnecting") # limit number of chars to be displayed, and try to keep printable ones only # MAGIC 256: arbitrary, "should be enough for everybody" original_version = msg['client_version_string'] client_version_string = original_version[:256] + ( '...' if original_version[256:] else '') if client_version_string.isprintable(): self.client_version_string = client_version_string.strip() else: self.client_version_string = repr(client_version_string) # Check whether to support Snappy Compression or not # based on other peer's p2p protocol version snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION if snappy_support: # Now update the base protocol to support snappy compression # This is needed so that Trinity is compatible with parity since # parity sends Ping immediately after Handshake self.base_protocol = P2PProtocol(self, snappy_support=snappy_support) remote_capabilities = msg['capabilities'] try: self.sub_proto = self.select_sub_protocol(remote_capabilities, snappy_support) except NoMatchingPeerCapabilities: await self.disconnect(DisconnectReason.useless_peer) raise HandshakeFailure( f"No matching capabilities between us ({self.capabilities}) and {self.remote} " f"({remote_capabilities}), disconnecting") self.logger.debug( "Finished P2P handshake with %s, using sub-protocol %s", self.remote, self.sub_proto)
def __init__( self, remote: Node, privkey: datatypes.PrivateKey, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, aes_secret: bytes, mac_secret: bytes, egress_mac: sha3.keccak_256, ingress_mac: sha3.keccak_256, headerdb: 'BaseAsyncHeaderDB', network_id: int, inbound: bool = False, token: CancelToken = None, ) -> None: super().__init__(token) self.remote = remote self.privkey = privkey self.reader = reader self.writer = writer self.base_protocol = P2PProtocol(self) self.headerdb = headerdb self.network_id = network_id self.inbound = inbound self._subscribers: List[PeerSubscriber] = [] self.start_time = datetime.datetime.now() self.received_msgs: Dict[protocol.Command, int] = collections.defaultdict(int) self.booted = asyncio.Event() self.egress_mac = egress_mac self.ingress_mac = ingress_mac # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32 iv = b"\x00" * 16 aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend()) self.aes_enc = aes_cipher.encryptor() self.aes_dec = aes_cipher.decryptor() mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend()) self.mac_enc = mac_cipher.encryptor().update
def __init__(self, supported_sub_protocols): self._supported_sub_protocols = supported_sub_protocols self.base_protocol = P2PProtocol(self)
def __init__(self, supported_sub_protocols, snappy_support): self.supported_sub_protocols = supported_sub_protocols self.base_protocol = P2PProtocol(self, snappy_support)
async def test_handshake(): # TODO: this test should be re-written to not depend on functionality in the `ETHPeer` class. cancel_token = CancelToken("test_handshake") use_eip8 = False initiator_remote = kademlia.Node( keys.PrivateKey(test_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(test_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( test_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(test_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(test_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( test_values['receiver_ephemeral_private_key']) # Check that the auth message generated by the initiator is what we expect. Notice that we # can't use the auth_init generated here because the non-deterministic prefix would cause the # derived secrets to not match the expected values. _auth_init = initiator.create_auth_message(test_values['initiator_nonce']) assert len(_auth_init) == len(test_values['auth_plaintext']) assert _auth_init[65:] == test_values['auth_plaintext'][ 65:] # starts with non deterministic k # Check that encrypting and decrypting the auth_init gets us the orig msg. _auth_init_ciphertext = initiator.encrypt_auth_message(_auth_init) assert _auth_init == ecies.decrypt(_auth_init_ciphertext, responder.privkey) # Check that the responder correctly decodes the auth msg. auth_msg_ciphertext = test_values['auth_ciphertext'] initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_msg_ciphertext, responder.privkey) assert initiator_nonce == test_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( test_values['initiator_ephemeral_private_key']).public_key) # Check that the auth_ack msg generated by the responder is what we expect. auth_ack_msg = responder.create_auth_ack_message( test_values['receiver_nonce']) assert auth_ack_msg == test_values['authresp_plaintext'] # Check that the secrets derived from ephemeral key agreements match the expected values. auth_ack_ciphertext = test_values['authresp_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, test_values['receiver_nonce'], initiator_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert aes_secret == test_values['aes_secret'] assert mac_secret == test_values['mac_secret'] # Test values are from initiator perspective, so they're reversed here. assert ingress_mac.digest() == test_values['initial_egress_MAC'] assert egress_mac.digest() == test_values['initial_ingress_MAC'] # Check that the initiator secrets match as well. responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( test_values['authresp_ciphertext']) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert initiator_aes_secret == aes_secret assert initiator_mac_secret == mac_secret assert initiator_ingress_mac.digest() == test_values['initial_ingress_MAC'] assert initiator_egress_mac.digest() == test_values['initial_egress_MAC'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() capabilities = (('paragon', 1), ) initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_p2p_protocol = P2PProtocol(initiator_transport, 0, False) initiator_multiplexer = Multiplexer( transport=initiator_transport, base_protocol=initiator_p2p_protocol, protocols=(), ) initiator_multiplexer.get_base_protocol().send_handshake( 'initiator', capabilities, 30303, DEVP2P_V5, ) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_p2p_protocol = P2PProtocol(responder_transport, 0, False) responder_multiplexer = Multiplexer( transport=responder_transport, base_protocol=responder_p2p_protocol, protocols=(), ) responder_multiplexer.get_base_protocol().send_handshake( 'responder', capabilities, 30303, DEVP2P_V5, ) async with initiator_multiplexer.multiplex(): async with responder_multiplexer.multiplex(): initiator_stream = initiator_multiplexer.stream_protocol_messages( initiator_p2p_protocol, ) responder_stream = responder_multiplexer.stream_protocol_messages( responder_p2p_protocol, ) initiator_hello, _ = await asyncio.wait_for( initiator_stream.asend(None), timeout=0.1) responder_hello, _ = await asyncio.wait_for( responder_stream.asend(None), timeout=0.1) await initiator_stream.aclose() await responder_stream.aclose() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)
async def test_handshake_eip8(): cancel_token = CancelToken("test_handshake_eip8") use_eip8 = True initiator_remote = kademlia.Node( keys.PrivateKey(eip8_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(eip8_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(eip8_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(eip8_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( eip8_values['receiver_ephemeral_private_key']) auth_init_ciphertext = eip8_values['auth_init_ciphertext'] # Check that we can decrypt/decode the EIP-8 auth init message. initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_init_ciphertext, responder.privkey) assert initiator_nonce == eip8_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']).public_key) responder_nonce = eip8_values['receiver_nonce'] auth_ack_ciphertext = eip8_values['auth_ack_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, responder_nonce, initiator_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the responder match the expected values. assert aes_secret == eip8_values['expected_aes_secret'] assert mac_secret == eip8_values['expected_mac_secret'] # Also according to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md, running B's # ingress-mac keccak state on the string "foo" yields the following hash: ingress_mac_copy = ingress_mac.copy() ingress_mac_copy.update(b'foo') assert ingress_mac_copy.hexdigest() == ( '0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5') responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( auth_ack_ciphertext) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the initiator match the expected values. assert initiator_aes_secret == eip8_values['expected_aes_secret'] assert initiator_mac_secret == eip8_values['expected_mac_secret'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() capabilities = (('testing', 1), ) initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_p2p_protocol = P2PProtocol(initiator_transport, 0, False) initiator_multiplexer = Multiplexer( transport=initiator_transport, base_protocol=initiator_p2p_protocol, protocols=(), ) initiator_multiplexer.get_base_protocol().send_handshake( 'initiator', capabilities, 30303, DEVP2P_V5, ) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_p2p_protocol = P2PProtocolV4(responder_transport, 0, False) responder_multiplexer = Multiplexer( transport=responder_transport, base_protocol=responder_p2p_protocol, protocols=(), ) responder_multiplexer.get_base_protocol().send_handshake( 'responder', capabilities, 30303, DEVP2P_V4, ) async with initiator_multiplexer.multiplex(): async with responder_multiplexer.multiplex(): initiator_stream = initiator_multiplexer.stream_protocol_messages( initiator_p2p_protocol, ) responder_stream = responder_multiplexer.stream_protocol_messages( responder_p2p_protocol, ) initiator_hello, _ = await initiator_stream.asend(None) responder_hello, _ = await responder_stream.asend(None) await initiator_stream.aclose() await responder_stream.aclose() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)