async def recv(self) -> MessageAPI: header_bytes = await self.read(HEADER_LEN + MAC_LEN) try: padded_header = self._decrypt_header(header_bytes) except (ValueError, DecryptionError) as err: self.logger.info("Bad message header from peer %s: Error: %r", self, err) raise MalformedMessage(*err.args) from err # TODO: use `int.from_bytes(...)` frame_size = self._get_frame_size(padded_header) # The frame_size specified in the header does not include the padding to 16-byte boundary, # so need to do this here to ensure we read all the frame's data. read_size = roundup_16(frame_size) frame_data = await self.read(read_size + MAC_LEN) try: body = self._decrypt_body(frame_data, frame_size) except (ValueError, DecryptionError) as err: self.logger.info("Bad message body from peer %s: Error: %r", self, err) raise MalformedMessage(*err.args) from err # Decode the header data and re-encode to recover the unpadded header size. try: header_data = _decode_header_data(padded_header[3:]) except rlp.exceptions.DeserializationError as err: raise MalformedMessage(*err.args) from err header = padded_header[:3] + rlp.encode(header_data) return Message(header, body)
async def _normalize_response(self, msg: Tuple[bytes, ...] ) -> Tuple[Tuple[Hash32, bytes], ...]: if not isinstance(msg, tuple): raise MalformedMessage("Invalid msg, must be tuple of byte strings") elif not all(isinstance(item, bytes) for item in msg): raise MalformedMessage("Invalid msg, must be tuple of byte strings") node_keys = await self._run_in_executor(tuple, map(keccak, msg)) return tuple(zip(node_keys, msg))
async def _normalize_response( self, msg: Dict[str, Any]) -> Tuple[BlockHeader, ...]: if not isinstance(msg, dict): raise MalformedMessage("msg must be a dictionary") elif 'headers' not in msg: raise MalformedMessage("No 'headers' key found in response") elif not all(isinstance(item, BlockHeader) for item in msg['headers']): raise MalformedMessage( "`headers` key must be a tuple of `BlockHeader` instances") return msg['headers']
def decode(self, data: bytes) -> TCommandPayload: try: return self._process_inbound_payload_fn( rlp.decode(data, strict=self.decode_strict, sedes=self.sedes, recursive_cache=True) ) except rlp.DecodingError as err: raise MalformedMessage(*err.args) from err
def decode(self, data: bytes) -> _DecodedMsgType: try: raw_decoded = cast(Dict[str, int], super().decode(data)) except rlp.exceptions.ListDeserializationError: self.logger.warn("Malformed Disconnect message: %s", data) raise MalformedMessage(f"Malformed Disconnect message: {data}") return assoc(raw_decoded, 'reason_name', self.get_reason_name(raw_decoded['reason']))
def decode(self, data: bytes) -> Payload: packet_type = get_devp2p_cmd_id(data) if packet_type != self.cmd_id: raise MalformedMessage(f"Wrong packet type: {packet_type}, expected {self.cmd_id}") compressed_payload = data[1:] encoded_payload = self.decompress_payload(compressed_payload) return self.decode_payload(encoded_payload)
def extract_forkid(enr: ENR) -> ForkID: try: eth_cap = enr[b'eth'] except KeyError: raise ENRMissingForkID() try: [forkid] = rlp.sedes.List([ForkID]).deserialize(eth_cap) return forkid except rlp.exceptions.ListDeserializationError: raise MalformedMessage("Unable to extract ForkID from {eth_cap}")
async def stream_transport_messages(transport: TransportAPI, base_protocol: BaseP2PProtocol, *protocols: ProtocolAPI, ) -> AsyncIterator[Tuple[ProtocolAPI, CommandAPI[Any]]]: """ Streams 2-tuples of (Protocol, Command) over the provided `Transport` """ # A cache for looking up the proper protocol instance for a given command # id. command_id_cache: Dict[int, ProtocolAPI] = {} while not transport.is_closing: try: msg = await transport.recv() except PeerConnectionLost: return command_id = msg.command_id if msg.command_id not in command_id_cache: if command_id < base_protocol.command_length: command_id_cache[command_id] = base_protocol else: for protocol in protocols: if command_id < protocol.command_id_offset + protocol.command_length: command_id_cache[command_id] = protocol break else: protocol_infos = ' '.join(tuple( ( f"{proto.name}@{proto.version}" f"[offset={proto.command_id_offset}," f"command_length={proto.command_length}]" ) for proto in cons(base_protocol, protocols) )) raise UnknownProtocolCommand( f"No protocol found for command_id {command_id}: Available " f"protocol/offsets are: {protocol_infos}" ) msg_proto = command_id_cache[command_id] command_type = msg_proto.get_command_type_for_command_id(command_id) try: cmd = command_type.decode(msg, msg_proto.snappy_support) except (rlp.exceptions.DeserializationError, snappy_CompressedLengthError) as err: raise MalformedMessage(f"Failed to decode {msg} for {command_type}") from err yield msg_proto, cmd # yield to the event loop for a moment to allow `transport.is_closing` # a chance to update. await asyncio.sleep(0)
async def _normalize_response( self, response: Tuple[BlockBody, ...]) -> BlockBodyBundles: if not isinstance(response, tuple): raise MalformedMessage( "`GetBlockBodies` response must be a tuple. Got: {0}".format( type(response))) elif not all(isinstance(item, BlockBody) for item in response): raise MalformedMessage( "`GetBlockBodies` response must be a tuple of block bodies") uncles_hashes = await self._run_in_executor( tuple, map(compose(keccak, rlp.encode), tuple(body.uncles for body in response)), ) transaction_roots_and_trie_data = await self._run_in_executor( tuple, map(make_trie_root_and_nodes, tuple(body.transactions for body in response)), ) body_bundles = tuple( zip(response, transaction_roots_and_trie_data, uncles_hashes)) return body_bundles
async def _normalize_response( self, response: Tuple[Tuple[Receipt, ...], ...], ) -> ReceiptsBundles: if not isinstance(response, tuple): raise MalformedMessage( "`GetReceipts` response must be a tuple. Got: {0}".format( type(response))) elif not all(isinstance(item, tuple) for item in response): raise MalformedMessage( "`GetReceipts` response must be a tuple of tuples") for item in response: if not all(isinstance(value, Receipt) for value in item): raise MalformedMessage( "Response must be a tuple of tuples of `BlockHeader` objects" ) trie_roots_and_data = await self._run_in_executor( tuple, map(make_trie_root_and_nodes, response), ) receipt_bundles = tuple(zip(response, trie_roots_and_data)) return receipt_bundles
def decode_payload(self, rlp_data: bytes) -> PayloadType: if isinstance(self.structure, sedes.CountableList): decoder = self.structure else: decoder = sedes.List([type_ for _, type_ in self.structure], strict=self.decode_strict) try: data = rlp.decode(rlp_data, sedes=decoder, recursive_cache=True) except rlp.DecodingError as err: raise MalformedMessage( f"Malformed {type(self).__name__} message: {err!r}") from err if isinstance(self.structure, sedes.CountableList): return data return { field_name: value for ((field_name, _), value) in zip(self.structure, data) }
def decode_payload(self, rlp_data: bytes) -> _DecodedMsgType: if isinstance(self.structure, sedes.CountableList): decoder = self.structure else: decoder = sedes.List([type_ for _, type_ in self.structure], strict=self.decode_strict) try: data = rlp.decode(rlp_data, sedes=decoder) except rlp.DecodingError as err: raise MalformedMessage("Malformed %s message: %r".format( type(self).__name__, err)) from err if isinstance(self.structure, sedes.CountableList): return data return { field_name: value for ((field_name, _), value) in zip(self.structure, data) }
def decode(self, data: bytes) -> None: if data == b'\xc0': return None else: raise MalformedMessage( f"Should be empty. Got {len(data)} bytes: {data.hex()}")
def decode(self, data: bytes) -> _DecodedMsgType: packet_type = get_devp2p_cmd_id(data) if packet_type != self.cmd_id: raise MalformedMessage("Wrong packet type: {}".format(packet_type)) return self.decode_payload(data[1:])
async def stream_transport_messages(transport: TransportAPI, base_protocol: BaseP2PProtocol, *protocols: ProtocolAPI, ) -> AsyncIterator[Tuple[ProtocolAPI, CommandAPI[Any]]]: """ Streams 2-tuples of (Protocol, Command) over the provided `Transport` Raises a TimeoutError if nothing is received in constants.CONN_IDLE_TIMEOUT seconds. """ # A cache for looking up the proper protocol instance for a given command # id. command_id_cache: Dict[int, ProtocolAPI] = {} loop = asyncio.get_event_loop() while not transport.is_closing: try: msg = await transport.recv() except PeerConnectionLost: transport.logger.debug( "Lost connection to %s, leaving stream_transport_messages()", transport.remote) return command_id = msg.command_id if msg.command_id not in command_id_cache: if command_id < base_protocol.command_length: command_id_cache[command_id] = base_protocol else: for protocol in protocols: if command_id < protocol.command_id_offset + protocol.command_length: command_id_cache[command_id] = protocol break else: protocol_infos = ' '.join(tuple( ( f"{proto.name}@{proto.version}" f"[offset={proto.command_id_offset}," f"command_length={proto.command_length}]" ) for proto in cons(base_protocol, protocols) )) raise UnknownProtocolCommand( f"No protocol found for command_id {command_id}: Available " f"protocol/offsets are: {protocol_infos}" ) msg_proto = command_id_cache[command_id] command_type = msg_proto.get_command_type_for_command_id(command_id) try: if len(msg.body) > MAX_IN_LOOP_DECODE_SIZE: cmd = await loop.run_in_executor( None, command_type.decode, msg, msg_proto.snappy_support, ) else: cmd = command_type.decode(msg, msg_proto.snappy_support) except (rlp.exceptions.DeserializationError, snappy_CompressedLengthError) as err: raise MalformedMessage(f"Failed to decode {msg} for {command_type}") from err yield msg_proto, cmd # yield to the event loop for a moment to allow `transport.is_closing` # a chance to update. await asyncio.sleep(0)