Beispiel #1
0
    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)
Beispiel #2
0
    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))
Beispiel #3
0
    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']
Beispiel #4
0
 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
Beispiel #5
0
 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']))
Beispiel #6
0
    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)
Beispiel #7
0
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}")
Beispiel #8
0
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)
Beispiel #9
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
Beispiel #10
0
    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
Beispiel #11
0
    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)
        }
Beispiel #12
0
    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)
        }
Beispiel #13
0
 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()}")
Beispiel #14
0
 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:])
Beispiel #15
0
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)