Example #1
0
def send_request_to_middleman(middleman_message: GolemMessageFrame) -> bytes:
    """
    Opens socket connection to Middleman, sends Frame to MiddleMan through MiddleMan Protocol and receive response.

    Returns raw Frame as bytes.
    """

    assert isinstance(middleman_message, GolemMessageFrame)

    with closing(socket.socket(socket.AF_INET,
                               socket.SOCK_STREAM)) as client_socket:
        client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        client_socket.connect(
            (settings.MIDDLEMAN_ADDRESS, settings.MIDDLEMAN_PORT))
        client_socket.settimeout(SCI_CALLBACK_MAXIMUM_TIMEOUT)

        # Send the TransactionSigningRequest.
        try:
            send_over_stream(
                connection=client_socket,
                raw_message=middleman_message,
                private_key=settings.CONCENT_PRIVATE_KEY,
            )

            # Read the response and close the connection.
            return next(unescape_stream(connection=client_socket))
        # Connection with Middleman times out before a complete response is received.
        except socket.timeout as exception:
            raise SCICallbackTimeoutError() from exception
def send_message_to_middleman_and_receive_response(
    message: AbstractFrame,
    config: configparser.ConfigParser,
    concent_private_key: bytes,
    concent_public_key: bytes,
) -> GolemMessageFrame:
    """ Sends message to MiddleMan using MiddleMan protocol and retrieves response. """
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    try:
        client_socket.connect((
            config.get(Components.MIDDLEMAN.value, 'host'),
            int(config.get(Components.MIDDLEMAN.value, 'port')),
        ))
        send_over_stream(
            connection=client_socket,
            raw_message=message,
            private_key=concent_private_key,
        )
        receive_frame_generator = unescape_stream(connection=client_socket)
        raw_response = next(receive_frame_generator)
        return GolemMessageFrame.deserialize(
            raw_message=raw_response,
            public_key=concent_public_key,
        )
    finally:
        client_socket.close()
Example #3
0
    def test_that_raising_error_in_generator_should_call_close_on_socket(
            self, unused_tcp_port):
        payload = Ping()
        middleman_message = GolemMessageFrame(payload, self.request_id)

        with closing(socket.socket(socket.AF_INET,
                                   socket.SOCK_STREAM)) as server_socket:
            server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
                                     True)
            with closing(socket.socket(socket.AF_INET,
                                       socket.SOCK_STREAM)) as client_socket:
                client_socket.setsockopt(socket.SOL_SOCKET,
                                         socket.SO_REUSEADDR, True)
                server_socket.bind(('127.0.0.1', unused_tcp_port))
                server_socket.listen(1)

                client_socket.connect(('127.0.0.1', unused_tcp_port))

                (connection, _address) = server_socket.accept()

                send_over_stream(connection=client_socket,
                                 raw_message=middleman_message,
                                 private_key=CONCENT_PRIVATE_KEY)

                split_stream_generator = split_stream(connection=connection)

                with mock.patch('middleman_protocol.stream.socket.socket.recv',
                                side_effect=Exception()):
                    with mock.patch(
                            'middleman_protocol.stream.socket.socket.close'
                    ) as mock_socket_close:
                        with pytest.raises(Exception):
                            next(split_stream_generator)

                mock_socket_close.assert_called_once()
Example #4
0
    def _authenticate(
        self,
        receive_frame_generator: Iterator[Optional[bytes]],
        tcp_socket: socket.socket
    ) -> None:
        """ Handles authentication challenge. """

        # Set timeout on socket, after which, if AuthenticationChallengeFrame is not received,
        # SigningService will have to reconnect.
        tcp_socket.settimeout(RECEIVE_AUTHENTICATION_CHALLENGE_TIMEOUT)

        # After establishing a TCP connection start listening for the AuthenticationChallengeFrame.
        try:
            raw_message_received = next(receive_frame_generator)
            authentication_challenge_frame = AbstractFrame.deserialize(
                raw_message_received,
                public_key=self.concent_public_key,
            )
            # If SigningService receive any other message that AuthenticationChallengeFrame
            # disconnect, log the incident and treat it as a failure to connect.
            if not isinstance(authentication_challenge_frame, AuthenticationChallengeFrame):
                logger.info(
                    f'SigningService received {type(authentication_challenge_frame)} instead of AuthenticationChallengeFrame.'
                )
                raise socket.error()
        # If received message is invalid or
        # if nothing was received in a predefined time,
        # disconnect, log the incident and treat it as a failure to connect.
        except (MiddlemanProtocolError, socket.timeout) as exception:
            logger.info(f'SigningService failed to receive AuthenticationChallengeFrame with exception: {exception}.')
            raise socket.error()

        # If you receive a valid challenge, sign it with the private key of the service and
        # send AuthenticationResponseFrame with signature as payload.
        authentication_response_frame = AuthenticationResponseFrame(
            payload=self._get_authentication_challenge_signature(
                authentication_challenge_frame.payload,
            ),
            request_id=authentication_challenge_frame.request_id,
        )

        try:
            send_over_stream(
                connection=tcp_socket,
                raw_message=authentication_response_frame,
                private_key=self.signing_service_private_key,
            )
        # If the server disconnects, log the incident and treat it as a failure to connect.
        except socket.error as exception:
            logger.info(
                f'MiddleMan server disconnects after receiving AuthenticationResponseFrame with exception: {exception}.'
            )
            raise socket.error()
        logger.info('Authentication successful. ')
Example #5
0
    def test_that_receiving_a_series_of_messages_should_be_handled_correctly(
            self, unused_tcp_port):
        payload = Ping()
        middleman_message = GolemMessageFrame(payload, self.request_id)

        with closing(socket.socket(socket.AF_INET,
                                   socket.SOCK_STREAM)) as server_socket:
            server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
                                     True)
            with closing(socket.socket(socket.AF_INET,
                                       socket.SOCK_STREAM)) as client_socket:
                client_socket.setsockopt(socket.SOL_SOCKET,
                                         socket.SO_REUSEADDR, True)
                server_socket.bind(('127.0.0.1', unused_tcp_port))
                server_socket.listen(1)

                client_socket.connect(('127.0.0.1', unused_tcp_port))

                (connection, _address) = server_socket.accept()

                for _i in range(10):
                    send_over_stream(connection=client_socket,
                                     raw_message=middleman_message,
                                     private_key=CONCENT_PRIVATE_KEY)

                unescape_stream_generator = unescape_stream(
                    connection=connection)

                for _i in range(10):
                    raw_message_received = next(unescape_stream_generator)

                    deserialized_message = AbstractFrame.deserialize(
                        raw_message=raw_message_received,
                        public_key=CONCENT_PUBLIC_KEY,
                    )

                    assertpy.assert_that(deserialized_message).is_instance_of(
                        GolemMessageFrame)
                    assertpy.assert_that(
                        deserialized_message.payload).is_instance_of(Ping)
                    assertpy.assert_that(
                        deserialized_message.payload).is_equal_to(payload)
def send_message_to_middleman_without_response(
    message: AbstractFrame,
    config: configparser.ConfigParser,
    concent_private_key: bytes,
) -> GolemMessageFrame:
    """ Sends message to MiddleMan using MiddleMan protocol and does not retrieve response. """
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    try:
        client_socket.connect((
            config.get(Components.MIDDLEMAN.value, 'host'),
            int(config.get(Components.MIDDLEMAN.value, 'port')),
        ))
        send_over_stream(
            connection=client_socket,
            raw_message=message,
            private_key=concent_private_key,
        )
    finally:
        client_socket.close()
Example #7
0
    def test_that_sending_message_over_tcp_socket_should_preserve_original_data(
        self,
        middleman_message_type,
        payload,
        unused_tcp_port,
    ):
        middleman_message = middleman_message_type(payload, self.request_id)

        with closing(socket.socket(socket.AF_INET,
                                   socket.SOCK_STREAM)) as server_socket:
            server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
                                     True)
            with closing(socket.socket(socket.AF_INET,
                                       socket.SOCK_STREAM)) as client_socket:
                client_socket.setsockopt(socket.SOL_SOCKET,
                                         socket.SO_REUSEADDR, True)
                server_socket.bind(('127.0.0.1', unused_tcp_port))
                server_socket.listen(1)

                client_socket.connect(('127.0.0.1', unused_tcp_port))

                (connection, _address) = server_socket.accept()

                send_over_stream(connection=client_socket,
                                 raw_message=middleman_message,
                                 private_key=CONCENT_PRIVATE_KEY)
                raw_message_received = next(
                    unescape_stream(connection=connection))

        deserialized_message = AbstractFrame.deserialize(
            raw_message=raw_message_received,
            public_key=CONCENT_PUBLIC_KEY,
        )

        assertpy.assert_that(deserialized_message).is_instance_of(
            middleman_message_type)
        assertpy.assert_that(deserialized_message.payload).is_instance_of(
            type(payload))
        assertpy.assert_that(deserialized_message.payload).is_equal_to(payload)
Example #8
0
def test_case_1_middleman_recovery(config: ConfigParser,
                                   **kwargs: Any) -> None:
    """
    1. Spawn MiddleMan and SigningService processes.
    2. Client sends GolemMessageFrame with correct TransactionSigningRequest to MiddleMan.
    3. Middleman is restarted. The connection and latest message is lost.
    4. Client sends GolemMessageFrame with correct TransactionSigningRequest to MiddleMan.
    5. Client receives response for latest message.
    """

    try:
        middleman_process = run_middleman()
        signing_service_process = run_signing_service()

        # Waiting for MiddleMan and SigningService to start.
        sleep(SLEEP_TIME_AFTER_SPAWNING_PROCESS)

        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

        # Create GolemMessageFrame with correct TransactionSigningRequest.
        golem_message_frame = create_golem_message_frame_with_correct_transaction_signing_request(
            request_id=get_current_utc_timestamp(), )

        client_socket.connect((
            config.get(Components.MIDDLEMAN.value, 'host'),
            int(config.get(Components.MIDDLEMAN.value, 'port')),
        ))
        send_over_stream(
            connection=client_socket,
            raw_message=golem_message_frame,
            private_key=CONCENT_PRIVATE_KEY,
        )

        middleman_process.kill()

        # Waiting for MiddleMan to finish.
        sleep(SLEEP_TIME_AFTER_KILLING_PROCESS)

        middleman_process = run_middleman()

        # Waiting for MiddleMan to start.
        sleep(SLEEP_TIME_AFTER_SPAWNING_PROCESS)

        receive_frame_generator = unescape_stream(connection=client_socket)
        try:
            next(receive_frame_generator)
        except socket.error as exception:
            assert_condition(
                exception.args[0], socket.errno.ECONNRESET,
                f'Connection should be reset by peer.')  # type: ignore

        # Create GolemMessageFrame with correct TransactionSigningRequest.
        golem_message_frame = create_golem_message_frame_with_correct_transaction_signing_request(
            request_id=get_current_utc_timestamp(), )

        # Send message through wrapper and receive deserialized response.
        response = send_message_to_middleman_and_receive_response(
            message=golem_message_frame,
            config=config,
            concent_private_key=CONCENT_PRIVATE_KEY,
            concent_public_key=CONCENT_PUBLIC_KEY,
        )

        # Check response.
        assert_condition(
            type(response), GolemMessageFrame,
            f'Deserialized response type is {type(response)} instead of GolemMessageFrame.'
        )
        assert_condition(
            type(response.payload), SignedTransaction,
            f'Deserialized response payload type is {type(response.payload)} instead of SignedTransaction.'
        )
        assert_condition(
            response.request_id, golem_message_frame.request_id,
            f'Deserialized response request_id is {response.request_id} instead of {golem_message_frame.request_id}.'
        )

    finally:
        middleman_process.kill()
        signing_service_process.kill()
Example #9
0
    def _handle_connection(
        self,
        receive_frame_generator: Iterator[Optional[bytes]],
        tcp_socket: socket.socket
    ) -> None:
        """ Inner loop that handles data exchange over socket. """
        # Set socket back blocking mode.
        tcp_socket.setblocking(True)
        for raw_message_received in receive_frame_generator:
            try:
                middleman_message = AbstractFrame.deserialize(
                    raw_message_received,
                    public_key=self.concent_public_key,
                )
                # Heartbeat is received: connection is still active and Signing Service doesn't have to respond.
                if middleman_message.payload_type == PayloadType.HEARTBEAT:
                    continue

                if (
                    not middleman_message.payload_type == PayloadType.GOLEM_MESSAGE or
                    not isinstance(middleman_message.payload, TransactionSigningRequest)
                ):
                    raise SigningServiceUnexpectedMessageError

            # Is the frame correct according to the protocol? If not, error code is InvalidFrame.
            except (
                FrameInvalidMiddlemanProtocolError,
                PayloadTypeInvalidMiddlemanProtocolError,
                RequestIdInvalidTypeMiddlemanProtocolError,
            ) as exception:
                middleman_message_response = self._prepare_error_response(ErrorCode.InvalidFrame, exception)
            # Is frame signature correct? If not, error code is InvalidFrameSignature.
            except SignatureInvalidMiddlemanProtocolError as exception:
                middleman_message_response = self._prepare_error_response(ErrorCode.InvalidFrameSignature, exception)
            # Is the content of the message valid? Do types match the schema and all values are within allowed ranges?
            # If not, error code is InvalidPayload.
            # Can the payload be decoded as a Golem message? If not, error code is InvalidPayload.
            # Is payload message signature correct? If not, error code is InvalidPayload.
            except (MessageError, PayloadInvalidMiddlemanProtocolError) as exception:
                middleman_message_response = self._prepare_error_response(ErrorCode.InvalidPayload, exception)
            # Is frame type GOLEM_MESSAGE? If not, error code is UnexpectedMessage.
            # Is Golem message type TransactionSigningRequest? If not, error code is UnexpectedMessage.
            except SigningServiceUnexpectedMessageError as exception:
                middleman_message_response = self._prepare_error_response(ErrorCode.UnexpectedMessage, exception)
            # If received frame is correct, validate transaction.
            else:
                self._update_daily_transactions_limit_file_name()
                golem_message_response = self._get_signed_transaction(middleman_message.payload)
                if isinstance(golem_message_response, SignedTransaction):
                    transaction_sum_combined = self.signing_service_daily_transaction_sum_so_far + middleman_message.payload.value
                    if transaction_sum_combined > MAXIMUM_DAILY_THRESHOLD:
                        logger.warning(
                            f'Signing Service is unable to transact more then {MAXIMUM_DAILY_THRESHOLD} GNTB today.'
                            f'Transaction from {middleman_message.payload.from_address} rejected.'
                        )
                        self.notifier.send(
                            f'Signing Service is unable to transact more then {MAXIMUM_DAILY_THRESHOLD} GNTB today.'
                        )
                        golem_message_response = TransactionRejected(
                            reason=TransactionRejected.REASON.DailyLimitExceeded,
                            nonce=middleman_message.payload.nonce,
                        )
                    elif transaction_sum_combined > WARNING_DAILY_THRESHOLD:
                        logger.warning(f'Signing Service has signed transactions worth {transaction_sum_combined} GNTB today.')
                        self.notifier.send(
                            f'Signing Service has signed transactions worth {transaction_sum_combined} GNTB today.'
                        )
                        self._add_payload_value_to_daily_transactions_sum(transaction_sum_combined)
                    else:
                        self._add_payload_value_to_daily_transactions_sum(transaction_sum_combined)
                golem_message_response.sign_message(private_key=self.signing_service_private_key)
                middleman_message_response = GolemMessageFrame(
                    payload=golem_message_response,
                    request_id=middleman_message.request_id,
                )

            logger.info(
                f'Sending Middleman protocol message with request_id: {middleman_message_response.request_id}.'
            )
            send_over_stream(
                connection=tcp_socket,
                raw_message=middleman_message_response,
                private_key=self.signing_service_private_key,
            )