class ZmqDriver(Driver):
    def __init__(self, engine):
        super().__init__(engine)
        self._engine = engine
        self._stream = None
        self._exit = False
        self._updates = None

    def start(self, endpoint):
        self._stream = Stream(endpoint)

        startup_state = self._register()

        # Validators version 1.1 send startup info with the registration
        # response; newer versions will send an activation message with the
        # startup info
        if startup_state is None:
            startup_state = self._wait_until_active()

        self._updates = Queue()

        driver_thread = Thread(
            target=self._driver_loop)
        driver_thread.start()

        try:
            self._engine.start(
                self._updates,
                ZmqService(
                    stream=self._stream,
                    timeout=SERVICE_TIMEOUT),
                startup_state)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught engine exception")

        self.stop()
        driver_thread.join()

    def _driver_loop(self):
        try:
            future = self._stream.receive()
            while True:
                if self._exit:
                    self._engine.stop()
                    break

                try:
                    message = future.result(1)
                    future = self._stream.receive()
                except concurrent.futures.TimeoutError:
                    continue
                try:
                    result = self._process(message)
                    # if message was a ping ignore
                    if result[0] == Message.PING_REQUEST:
                        continue

                    self._updates.put(result)

                except exceptions.ReceiveError as err:
                    LOGGER.warning("%s", err)
                    continue
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught driver exception")

    def stop(self):
        self._exit = True
        self._engine.stop()
        self._stream.close()

    def _register(self):
        self._stream.wait_for_ready()

        request = consensus_pb2.ConsensusRegisterRequest(
            name=self._engine.name(),
            version=self._engine.version(),
        )

        for (name, version) in self._engine.additional_protocols():
            protocol = request.additional_protocols.add()
            protocol.name = name
            protocol.version = version

        while True:
            future = self._stream.send(
                message_type=Message.CONSENSUS_REGISTER_REQUEST,
                content=request.SerializeToString())
            response = consensus_pb2.ConsensusRegisterResponse()
            response.ParseFromString(future.result(REGISTER_TIMEOUT).content)

            if (
                response.status
                == consensus_pb2.ConsensusRegisterResponse.NOT_READY
            ):
                continue

            if response.status == consensus_pb2.ConsensusRegisterResponse.OK:
                if (
                    response.HasField('chain_head')
                    and response.HasField('local_peer_info')
                ):
                    return StartupState(
                        response.chain_head,
                        response.peers,
                        response.local_peer_info)

                return None

            raise exceptions.ReceiveError(
                'Registration failed with status {}'.format(response.status))

    def _wait_until_active(self):
        future = self._stream.receive()
        while True:
            try:
                message = future.result(1)
            except concurrent.futures.TimeoutError:
                continue

            if (
                message.message_type
                == Message.CONSENSUS_NOTIFY_ENGINE_ACTIVATED
            ):
                notification = \
                    consensus_pb2.ConsensusNotifyEngineActivated()
                notification.ParseFromString(message.content)

                startup_state = StartupState(
                    notification.chain_head,
                    notification.peers,
                    notification.local_peer_info)

                LOGGER.info(
                    'Received activation message with startup state: %s',
                    startup_state)

                self._stream.send_back(
                    message_type=Message.CONSENSUS_NOTIFY_ACK,
                    correlation_id=message.correlation_id,
                    content=consensus_pb2.ConsensusNotifyAck()
                                         .SerializeToString())

                return startup_state

            LOGGER.warning('Received message type %s while waiting for \
                activation message', message.message_type)
            future = self._stream.receive()

    def _process(self, message):
        type_tag = message.message_type

        if type_tag == Message.CONSENSUS_NOTIFY_PEER_CONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerConnected()
            notification.ParseFromString(message.content)

            data = notification.peer_info

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_DISCONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerDisconnected()
            notification.ParseFromString(message.content)

            data = notification.peer_id

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_MESSAGE:
            notification = consensus_pb2.ConsensusNotifyPeerMessage()
            notification.ParseFromString(message.content)

            header = consensus_pb2.ConsensusPeerMessageHeader()
            header.ParseFromString(notification.message.header)

            peer_message = PeerMessage(
                header=header,
                header_bytes=notification.message.header,
                header_signature=notification.message.header_signature,
                content=notification.message.content)

            data = peer_message, notification.sender_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_NEW:
            notification = consensus_pb2.ConsensusNotifyBlockNew()
            notification.ParseFromString(message.content)

            data = notification.block

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_VALID:
            notification = consensus_pb2.ConsensusNotifyBlockValid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_INVALID:
            notification = consensus_pb2.ConsensusNotifyBlockInvalid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_COMMIT:
            notification = consensus_pb2.ConsensusNotifyBlockCommit()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_ENGINE_DEACTIVATED:
            self.stop()
            data = None

        elif type_tag == Message.PING_REQUEST:
            data = None

        else:
            raise exceptions.ReceiveError(
                'Received unexpected message type: {}'.format(type_tag))

        self._stream.send_back(
            message_type=Message.CONSENSUS_NOTIFY_ACK,
            correlation_id=message.correlation_id,
            content=consensus_pb2.ConsensusNotifyAck().SerializeToString())

        return type_tag, data
예제 #2
0
class TransactionProcessor(object):
    def __init__(self, url):
        self._stream = Stream(url)
        self._url = url
        self._handlers = []

    @property
    def zmq_id(self):
        return self._stream.zmq_id

    def add_handler(self, handler):
        """Add a transaction family handler
        :param handler:
        """
        self._handlers.append(handler)

    def _matches(self, handler, header):
        return header.family_name == handler.family_name \
            and header.family_version in handler.family_versions \
            and header.payload_encoding in handler.encodings

    def _find_handler(self, header):
        """Find a handler for a particular (family_name,
        family_versions, payload_encoding)
        :param header transaction_pb2.TransactionHeader:
        :return: handler
        """
        try:
            return next(handler for handler in self._handlers
                        if self._matches(handler, header))
        except StopIteration:
            LOGGER.debug("Missing handler for header: %s", header)
            return None

    def _register_requests(self):
        """Returns all of the TpRegisterRequests for handlers

        :return (list): list of TpRegisterRequests
        """
        return itertools.chain.from_iterable(  # flattens the nested list
            [[
                TpRegisterRequest(family=n,
                                  version=v,
                                  encoding=e,
                                  namespaces=h.namespaces)
                for n, v, e in itertools.product(
                    [h.family_name], h.family_versions, h.encodings)
            ] for h in self._handlers])

    def _unregister_request(self):
        """Returns a single TP_UnregisterRequest that requests
        that the validator stop sending transactions for previously
        registered handlers.

        :return (processor_pb2.TpUnregisterRequest):
        """
        return TpUnregisterRequest()

    def _process(self, msg):
        if msg.message_type != Message.TP_PROCESS_REQUEST:
            LOGGER.debug(
                "Transaction Processor recieved invalid message type. "
                "Message type should be TP_PROCESS_REQUEST,"
                " but is %s", Message.MessageType.Name(msg.message_type))
            return

        request = TpProcessRequest()
        request.ParseFromString(msg.content)
        state = State(self._stream, request.context_id)
        header = TransactionHeader()
        header.ParseFromString(request.header)
        try:
            if not self._stream.is_ready():
                raise ValidatorConnectionError()
            handler = self._find_handler(header)
            if handler is None:
                return
            handler.apply(request, state)
            self._stream.send_back(
                message_type=Message.TP_PROCESS_RESPONSE,
                correlation_id=msg.correlation_id,
                content=TpProcessResponse(
                    status=TpProcessResponse.OK).SerializeToString())
        except InvalidTransaction as it:
            LOGGER.warning("Invalid Transaction %s", it)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INVALID_TRANSACTION,
                        message=str(it),
                        extended_data=it.extended_data).SerializeToString())
            except ValidatorConnectionError as vce:
                # TP_PROCESS_REQUEST has made it through the
                # handler.apply and an INVALID_TRANSACTION would have been
                # sent back but the validator has disconnected and so it
                # doesn't care about the response.
                LOGGER.warning("during invalid transaction response: %s", vce)
        except InternalError as ie:
            LOGGER.warning("internal error: %s", ie)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INTERNAL_ERROR,
                        message=str(ie),
                        extended_data=ie.extended_data).SerializeToString())
            except ValidatorConnectionError as vce:
                # Same as the prior except block, but an internal error has
                # happened, but because of the disconnect the validator
                # probably doesn't care about the response.
                LOGGER.warning("during internal error response: %s", vce)
        except ValidatorConnectionError as vce:
            # Somewhere within handler.apply a future resolved with an
            # error status that the validator has disconnected. There is
            # nothing left to do but reconnect.
            LOGGER.warning(
                "during handler.apply a future was resolved "
                "with error status: %s", vce)

    def _process_future(self, future, timeout=None, sigint=False):
        try:
            msg = future.result(timeout)
        except CancelledError:
            # This error is raised when Task.cancel is called on
            # disconnect from the validator in stream.py, for
            # this future.
            return
        if msg is RECONNECT_EVENT:
            if sigint is False:
                LOGGER.info("reregistering with validator")
                self._stream.wait_for_ready()
                self._register()
        else:
            LOGGER.debug('received message of type: %s',
                         Message.MessageType.Name(msg.message_type))
            if msg.message_type == Message.TP_PING:
                self._stream.send_back(
                    message_type=Message.TP_PING_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpPingResponse(
                        status=TpPingResponse.OK).SerializeToString())
                return
            self._process(msg)

    def _register(self):
        futures = []
        for message in self._register_requests():
            self._stream.wait_for_ready()
            future = self._stream.send(
                message_type=Message.TP_REGISTER_REQUEST,
                content=message.SerializeToString())
            futures.append(future)

        for future in futures:
            resp = TpRegisterResponse()
            try:
                resp.ParseFromString(future.result().content)
                LOGGER.info("register attempt: %s",
                            TpRegisterResponse.Status.Name(resp.status))
            except ValidatorConnectionError as vce:
                LOGGER.info("during waiting for response on registration: %s",
                            vce)

    def _unregister(self):
        message = self._unregister_request()
        self._stream.wait_for_ready()
        future = self._stream.send(message_type=Message.TP_UNREGISTER_REQUEST,
                                   content=message.SerializeToString())
        response = TpUnregisterResponse()
        try:
            response.ParseFromString(future.result(1).content)
            LOGGER.info("unregister attempt: %s",
                        TpUnregisterResponse.Status.Name(response.status))
        except ValidatorConnectionError as vce:
            LOGGER.info("during waiting for response on unregistration: %s",
                        vce)

    def start(self):
        fut = None
        try:
            self._register()
            while True:
                # During long running processing this
                # is where the transaction processor will
                # spend most of its time
                fut = self._stream.receive()
                self._process_future(fut)
        except KeyboardInterrupt:
            try:
                # tell the validator to not send any more messages
                self._unregister()
                while True:
                    if fut is not None:
                        # process futures as long as the tp has them,
                        # if the TP_PROCESS_REQUEST doesn't come from
                        # zeromq->asyncio in 1 second raise a
                        # concurrent.futures.TimeOutError and be done.
                        self._process_future(fut, 1, sigint=True)
                        fut = self._stream.receive()
            except concurrent.futures.TimeoutError:
                # Where the tp will usually exit after
                # a KeyboardInterrupt. Caused by the 1 second
                # timeout in _process_future.
                pass
            except FutureTimeoutError:
                # If the validator is not able to respond to the
                # unregister request, exit.
                pass

    def stop(self):
        self._stream.close()
예제 #3
0
class ZmqDriver(Driver):
    def __init__(self, engine):
        super().__init__(engine)
        self._engine = engine
        self._stream = None
        self._exit = False
        self._updates = None

    def start(self, endpoint):
        self._stream = Stream(endpoint)

        (chain_head, peers) = self._register()

        self._updates = Queue()

        engine_thread = Thread(
            target=self._engine.start,
            args=(self._updates,
                  ZmqService(stream=self._stream,
                             timeout=SERVICE_TIMEOUT,
                             name=self._engine.name(),
                             version=self._engine.version()), chain_head,
                  peers))

        engine_thread.start()

        while True:
            if self._exit:
                self._engine.stop()
                engine_thread.join()
                break

            try:
                message = self._stream.receive().result(0.1)
            except concurrent.futures.TimeoutError:
                continue

            result = self._process(message)

            self._updates.put(result)

    def stop(self):
        self._exit = True
        self._stream.close()

    def _register(self):
        self._stream.wait_for_ready()

        request = consensus_pb2.ConsensusRegisterRequest(
            name=self._engine.name(),
            version=self._engine.version(),
        ).SerializeToString()

        while True:
            future = self._stream.send(
                message_type=Message.CONSENSUS_REGISTER_REQUEST,
                content=request)
            response = consensus_pb2.ConsensusRegisterResponse()
            response.ParseFromString(future.result(REGISTER_TIMEOUT).content)

            if (response.status ==
                    consensus_pb2.ConsensusRegisterResponse.NOT_READY):
                continue

            if response.status == consensus_pb2.ConsensusRegisterResponse.OK:
                return (response.chain_head, response.peers)

            raise exceptions.ReceiveError(
                'Registration failed with status {}'.format(response.status))

    def _process(self, message):
        type_tag = message.message_type

        if type_tag == Message.CONSENSUS_NOTIFY_PEER_CONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerConnected()
            notification.ParseFromString(message.content)

            data = notification.peer_info

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_DISCONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerDisconnected()
            notification.ParseFromString(message.content)

            data = notification.peer_id

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_MESSAGE:
            notification = consensus_pb2.ConsensusNotifyPeerMessage()
            notification.ParseFromString(message.content)

            data = notification.message, notification.sender_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_NEW:
            notification = consensus_pb2.ConsensusNotifyBlockNew()
            notification.ParseFromString(message.content)

            data = notification.block

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_VALID:
            notification = consensus_pb2.ConsensusNotifyBlockValid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_INVALID:
            notification = consensus_pb2.ConsensusNotifyBlockInvalid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_COMMIT:
            notification = consensus_pb2.ConsensusNotifyBlockCommit()
            notification.ParseFromString(message.content)

            data = notification.block_id

        else:
            raise exceptions.ReceiveError(
                'Received unexpected message type: {}'.format(type_tag))

        self._stream.send_back(
            message_type=Message.CONSENSUS_NOTIFY_ACK,
            correlation_id=message.correlation_id,
            content=consensus_pb2.ConsensusNotifyAck().SerializeToString())

        return type_tag, data
예제 #4
0
class ZmqDriver(Driver):
    def __init__(self, engine):
        super().__init__(engine)
        self._engine = engine
        self._stream = None
        self._exit = False
        self._updates = None

    def start(self, endpoint):
        self._stream = Stream(endpoint)

        startup_state = self._register()

        # Validators version 1.1 send startup info with the registration
        # response; newer versions will send an activation message with the
        # startup info
        if startup_state is None:
            startup_state = self._wait_until_active()

        self._updates = Queue()

        driver_thread = Thread(
            target=self._driver_loop)
        driver_thread.start()

        try:
            self._engine.start(
                self._updates,
                ZmqService(
                    stream=self._stream,
                    timeout=SERVICE_TIMEOUT),
                startup_state)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught engine exception")

        self.stop()
        driver_thread.join()

    def _driver_loop(self):
        try:
            future = self._stream.receive()
            while True:
                if self._exit:
                    self._engine.stop()
                    break

                try:
                    message = future.result(1)
                    future = self._stream.receive()
                except concurrent.futures.TimeoutError:
                    continue

                result = self._process(message)

                self._updates.put(result)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught driver exception")

    def stop(self):
        self._exit = True
        self._engine.stop()
        self._stream.close()

    def _register(self):
        self._stream.wait_for_ready()

        request = consensus_pb2.ConsensusRegisterRequest(
            name=self._engine.name(),
            version=self._engine.version(),
        ).SerializeToString()

        while True:
            future = self._stream.send(
                message_type=Message.CONSENSUS_REGISTER_REQUEST,
                content=request)
            response = consensus_pb2.ConsensusRegisterResponse()
            response.ParseFromString(future.result(REGISTER_TIMEOUT).content)

            if (
                response.status
                == consensus_pb2.ConsensusRegisterResponse.NOT_READY
            ):
                continue

            if response.status == consensus_pb2.ConsensusRegisterResponse.OK:
                if (
                    response.HasField('chain_head')
                    and response.HasField('local_peer_info')
                ):
                    return StartupState(
                        response.chain_head,
                        response.peers,
                        response.local_peer_info)

                return None

            raise exceptions.ReceiveError(
                'Registration failed with status {}'.format(response.status))

    def _wait_until_active(self):
        future = self._stream.receive()
        while True:
            try:
                message = future.result(1)
            except concurrent.futures.TimeoutError:
                continue

            if (
                message.message_type
                == Message.CONSENSUS_NOTIFY_ENGINE_ACTIVATED
            ):
                notification = \
                    consensus_pb2.ConsensusNotifyEngineActivated()
                notification.ParseFromString(message.content)

                startup_state = StartupState(
                    notification.chain_head,
                    notification.peers,
                    notification.local_peer_info)

                LOGGER.info(
                    'Received activation message with startup state: %s',
                    startup_state)

                self._stream.send_back(
                    message_type=Message.CONSENSUS_NOTIFY_ACK,
                    correlation_id=message.correlation_id,
                    content=consensus_pb2.ConsensusNotifyAck()
                                         .SerializeToString())

                return startup_state

            LOGGER.warning('Received message type %s while waiting for \
                activation message', message.message_type)
            future = self._stream.receive()

    def _process(self, message):
        type_tag = message.message_type

        if type_tag == Message.CONSENSUS_NOTIFY_PEER_CONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerConnected()
            notification.ParseFromString(message.content)

            data = notification.peer_info

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_DISCONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerDisconnected()
            notification.ParseFromString(message.content)

            data = notification.peer_id

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_MESSAGE:
            notification = consensus_pb2.ConsensusNotifyPeerMessage()
            notification.ParseFromString(message.content)

            header = consensus_pb2.ConsensusPeerMessageHeader()
            header.ParseFromString(notification.message.header)

            peer_message = PeerMessage(
                header=header,
                header_bytes=notification.message.header,
                header_signature=notification.message.header_signature,
                content=notification.message.content)

            data = peer_message, notification.sender_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_NEW:
            notification = consensus_pb2.ConsensusNotifyBlockNew()
            notification.ParseFromString(message.content)

            data = notification.block

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_VALID:
            notification = consensus_pb2.ConsensusNotifyBlockValid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_INVALID:
            notification = consensus_pb2.ConsensusNotifyBlockInvalid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_COMMIT:
            notification = consensus_pb2.ConsensusNotifyBlockCommit()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_ENGINE_DEACTIVATED:
            self.stop()
            data = None

        else:
            raise exceptions.ReceiveError(
                'Received unexpected message type: {}'.format(type_tag))

        self._stream.send_back(
            message_type=Message.CONSENSUS_NOTIFY_ACK,
            correlation_id=message.correlation_id,
            content=consensus_pb2.ConsensusNotifyAck().SerializeToString())

        return type_tag, data
예제 #5
0
파일: core.py 프로젝트: cianx/sawtooth-core
class TransactionProcessor:
    """TransactionProcessor is a generic class for communicating with a
    validator and routing transaction processing requests to a registered
    handler. It uses ZMQ and channels to handle requests concurrently.
    """

    def __init__(self, url):
        """
        Args:
            url (string): The URL of the validator
        """
        self._stream = Stream(url)
        self._url = url
        self._handlers = []

    @property
    def zmq_id(self):
        return self._stream.zmq_id

    def add_handler(self, handler):
        """Adds a transaction family handler
        Args:
            handler (TransactionHandler): the handler to be added
        """
        self._handlers.append(handler)

    def _matches(self, handler, header):
        return header.family_name == handler.family_name \
            and header.family_version in handler.family_versions

    def _find_handler(self, header):
        """Find a handler for a particular (family_name, family_versions)
        :param header transaction_pb2.TransactionHeader:
        :return: handler
        """
        try:
            return next(
                handler for handler in self._handlers
                if self._matches(handler, header))
        except StopIteration:
            LOGGER.debug("Missing handler for header: %s", header)
            return None

    def _register_requests(self):
        """Returns all of the TpRegisterRequests for handlers

        :return (list): list of TpRegisterRequests
        """
        return itertools.chain.from_iterable(  # flattens the nested list
            [
                [TpRegisterRequest(
                    family=n,
                    version=v,
                    namespaces=h.namespaces)
                 for n, v in itertools.product(
                    [h.family_name],
                     h.family_versions,)] for h in self._handlers])

    def _unregister_request(self):
        """Returns a single TP_UnregisterRequest that requests
        that the validator stop sending transactions for previously
        registered handlers.

        :return (processor_pb2.TpUnregisterRequest):
        """
        return TpUnregisterRequest()

    def _process(self, msg):
        if msg.message_type != Message.TP_PROCESS_REQUEST:
            LOGGER.debug(
                "Transaction Processor recieved invalid message type. "
                "Message type should be TP_PROCESS_REQUEST,"
                " but is %s", Message.MessageType.Name(msg.message_type))
            return

        request = TpProcessRequest()
        request.ParseFromString(msg.content)
        state = Context(self._stream, request.context_id)
        header = request.header
        try:
            if not self._stream.is_ready():
                raise ValidatorConnectionError()
            handler = self._find_handler(header)
            if handler is None:
                return
            handler.apply(request, state)
            self._stream.send_back(
                message_type=Message.TP_PROCESS_RESPONSE,
                correlation_id=msg.correlation_id,
                content=TpProcessResponse(
                    status=TpProcessResponse.OK
                ).SerializeToString())
        except InvalidTransaction as it:
            LOGGER.warning("Invalid Transaction %s", it)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INVALID_TRANSACTION,
                        message=str(it),
                        extended_data=it.extended_data
                    ).SerializeToString())
            except ValidatorConnectionError as vce:
                # TP_PROCESS_REQUEST has made it through the
                # handler.apply and an INVALID_TRANSACTION would have been
                # sent back but the validator has disconnected and so it
                # doesn't care about the response.
                LOGGER.warning("during invalid transaction response: %s", vce)
        except InternalError as ie:
            LOGGER.warning("internal error: %s", ie)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INTERNAL_ERROR,
                        message=str(ie),
                        extended_data=ie.extended_data
                    ).SerializeToString())
            except ValidatorConnectionError as vce:
                # Same as the prior except block, but an internal error has
                # happened, but because of the disconnect the validator
                # probably doesn't care about the response.
                LOGGER.warning("during internal error response: %s", vce)
        except ValidatorConnectionError as vce:
            # Somewhere within handler.apply a future resolved with an
            # error status that the validator has disconnected. There is
            # nothing left to do but reconnect.
            LOGGER.warning("during handler.apply a future was resolved "
                           "with error status: %s", vce)
        except AuthorizationException as ae:
            LOGGER.warning("AuthorizationException: %s", ae)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INVALID_TRANSACTION,
                        message=str(ae),
                    ).SerializeToString())
            except ValidatorConnectionError as vce:
                # TP_PROCESS_REQUEST has made it through the
                # handler.apply and an INVALID_TRANSACTION would have been
                # sent back but the validator has disconnected and so it
                # doesn't care about the response.
                LOGGER.warning("during invalid transaction response: %s", vce)

    def _process_future(self, future, timeout=None, sigint=False):
        try:
            msg = future.result(timeout)
        except CancelledError:
            # This error is raised when Task.cancel is called on
            # disconnect from the validator in stream.py, for
            # this future.
            return
        if msg is RECONNECT_EVENT:
            if sigint is False:
                LOGGER.info("reregistering with validator")
                self._stream.wait_for_ready()
                self._register()
        else:
            LOGGER.debug(
                'received message of type: %s',
                Message.MessageType.Name(msg.message_type))
            if msg.message_type == Message.PING_REQUEST:
                self._stream.send_back(
                    message_type=Message.PING_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=PingResponse().SerializeToString())
                return
            self._process(msg)

    def _register(self):
        futures = []
        for message in self._register_requests():
            self._stream.wait_for_ready()
            future = self._stream.send(
                message_type=Message.TP_REGISTER_REQUEST,
                content=message.SerializeToString())
            futures.append(future)

        for future in futures:
            resp = TpRegisterResponse()
            try:
                resp.ParseFromString(future.result().content)
                LOGGER.info("register attempt: %s",
                            TpRegisterResponse.Status.Name(resp.status))
            except ValidatorConnectionError as vce:
                LOGGER.info("during waiting for response on registration: %s",
                            vce)

    def _unregister(self):
        message = self._unregister_request()
        self._stream.wait_for_ready()
        future = self._stream.send(
            message_type=Message.TP_UNREGISTER_REQUEST,
            content=message.SerializeToString())
        response = TpUnregisterResponse()
        try:
            response.ParseFromString(future.result(1).content)
            LOGGER.info("unregister attempt: %s",
                        TpUnregisterResponse.Status.Name(response.status))
        except ValidatorConnectionError as vce:
            LOGGER.info("during waiting for response on unregistration: %s",
                        vce)

    def start(self):
        """Connects the transaction processor to a validator and starts
        listening for requests and routing them to an appropriate
        transaction handler.
        """
        fut = None
        try:
            self._register()
            while True:
                # During long running processing this
                # is where the transaction processor will
                # spend most of its time
                fut = self._stream.receive()
                self._process_future(fut)
        except KeyboardInterrupt:
            try:
                # tell the validator to not send any more messages
                self._unregister()
                while True:
                    if fut is not None:
                        # process futures as long as the tp has them,
                        # if the TP_PROCESS_REQUEST doesn't come from
                        # zeromq->asyncio in 1 second raise a
                        # concurrent.futures.TimeOutError and be done.
                        self._process_future(fut, 1, sigint=True)
                        fut = self._stream.receive()
            except concurrent.futures.TimeoutError:
                # Where the tp will usually exit after
                # a KeyboardInterrupt. Caused by the 1 second
                # timeout in _process_future.
                pass
            except FutureTimeoutError:
                # If the validator is not able to respond to the
                # unregister request, exit.
                pass

    def stop(self):
        """Closes the connection between the TransactionProcessor and the
        validator.
        """
        self._stream.close()
예제 #6
0
class SyncProcessor:
    future: Optional["Future"] = None
    _stream: Stream

    def __init__(self, url=None):
        self._stream = Stream(url or VALIDATOR_URL)
        self._handlers = []

    def add_handler(self, handler: Type["HandlerBase"]):
        if handler not in self._handlers:
            LOG.info(f"Add handler {handler.__name__}")
            self._handlers.append(handler)

    def start(self):
        LOG.info("Start SyncProcessor")

        future = None
        try:
            self._register()
            while True:
                future = self._stream.receive()
                self._process_future(future)
        except KeyboardInterrupt:
            LOG.warning("break")
            try:
                # tell the validator to not send any more messages
                self._unregister()
                while True:
                    if future is not None:
                        # process futures as long as the tp has them,
                        # if the TP_PROCESS_REQUEST doesn't come from
                        # zeromq->asyncio in 1 second raise a
                        # concurrent.futures.TimeOutError and be done.
                        self._process_future(future, 1, sigint=True)
                        future = self._stream.receive()
            except ConcurrentTimeoutError:
                # Where the tp will usually exit after
                # a KeyboardInterrupt. Caused by the 1 second
                # timeout in _process_future.
                pass
            except FutureTimeoutError:
                # If the validator is not able to respond to the
                # unregister request, exit.
                pass

    def _unregister(self):
        LOG.warning("unregister")
        pass

    def _register(self):
        LOG.debug(f"Register handlers")
        self._stream.wait_for_ready()
        future = self._stream.send(
            message_type=Message.CLIENT_EVENTS_SUBSCRIBE_REQUEST,
            content=ClientEventsSubscribeRequest(
                last_known_block_ids=[NULL_BLOCK_ID],
                subscriptions=[
                    EventSubscription(event_type="sawtooth/block-commit"),
                    EventSubscription(
                        event_type="sawtooth/state-delta",
                        filters=[
                            EventFilter(
                                key="address",
                                match_string=f"^{Namespaces.GA_NAMESPACE}.*",
                                filter_type=EventFilter.REGEX_ANY,
                            )
                        ],
                    ),
                ],
            ).SerializeToString(),
        )

        resp = TpRegisterResponse()
        try:
            resp.ParseFromString(future.result().content)
            LOG.info("Register response: %s",
                     TpRegisterResponse.Status.Name(resp.status))
        except ValidatorConnectionError as vce:
            LOG.info("During waiting for response on registration: %s", vce)
        except Exception as e:
            LOG.info("During waiting for response on registration: %s", e)

    def _process_future(self,
                        future: "Future",
                        timeout=None,
                        sigint=False) -> None:
        try:
            msg = future.result(timeout)
        except CancelledError:
            # This error is raised when Task.cancel is called on
            # disconnect from the validator in stream.py, for
            # this future.
            LOG.error("Cancelled")
            return

        if msg is RECONNECT_EVENT:
            if sigint is False:
                LOG.info("Reregistering with validator")
                self._stream.wait_for_ready()
                self._register()
        else:
            LOG.debug(f"Received message of type: "
                      f"{Message.MessageType.Name(msg.message_type)}")

            if msg.message_type == Message.PING_REQUEST:
                self._stream.send_back(
                    message_type=Message.PING_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=PingResponse().SerializeToString(),
                )
                return
            elif msg.message_type == Message.CLIENT_EVENTS:
                self._process(msg)
                return

            LOG.warning(f"SyncProcessor got wrong message type: "
                        f"{Message.MessageType.Name(msg.message_type)}")

    def _process(self, msg: Message) -> None:
        event_list = EventList()
        event_list.ParseFromString(msg.content)
        events = event_list.events
        raw_block = next(
            (e for e in events if e.event_type == "sawtooth/block-commit"), [])
        block = {b.key: b.value for b in raw_block.attributes}
        LOG.debug(f"Block: {block}")
        event = next(
            (e for e in events if e.event_type == "sawtooth/state-delta"),
            None)
        if event:
            state_changes = StateChangeList()
            state_changes.ParseFromString(event.data)
            changes = list(state_changes.state_changes)
            LOG.debug(f"Found {len(changes)} state changes")
            self._handle_changes(block, changes)

    def _handle_changes(self, block: dict, changes: list):
        # r.table(Tbl.BLOCKS).insert(block).run()
        for change in changes:
            for handler in self._handlers:
                if change.address.startswith(handler.prefix):
                    handler.process(change)
예제 #7
0
class ZmqDriver(Driver):
    def __init__(self, engine):
        super().__init__(engine)
        self._engine = engine
        self._stream = None
        self._exit = False
        self._updates = None

    def start(self, endpoint):
        self._stream = Stream(endpoint)

        startup_state = self._register()

        self._updates = Queue()

        driver_thread = Thread(
            target=self._driver_loop)
        driver_thread.start()

        try:
            self._engine.start(
                self._updates,
                ZmqService(
                    stream=self._stream,
                    timeout=SERVICE_TIMEOUT,
                    name=self._engine.name(),
                    version=self._engine.version()),
                startup_state)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught engine exception")

        self.stop()
        driver_thread.join()

    def _driver_loop(self):
        try:
            future = self._stream.receive()
            while True:
                if self._exit:
                    self._engine.stop()
                    break

                try:
                    message = future.result(1)
                    future = self._stream.receive()
                except concurrent.futures.TimeoutError:
                    continue

                result = self._process(message)

                self._updates.put(result)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught driver exception")

    def stop(self):
        self._exit = True
        self._engine.stop()
        self._stream.close()

    def _register(self):
        self._stream.wait_for_ready()

        request = consensus_pb2.ConsensusRegisterRequest(
            name=self._engine.name(),
            version=self._engine.version(),
        ).SerializeToString()

        while True:
            future = self._stream.send(
                message_type=Message.CONSENSUS_REGISTER_REQUEST,
                content=request)
            response = consensus_pb2.ConsensusRegisterResponse()
            response.ParseFromString(future.result(REGISTER_TIMEOUT).content)

            if (
                response.status
                == consensus_pb2.ConsensusRegisterResponse.NOT_READY
            ):
                continue

            if response.status == consensus_pb2.ConsensusRegisterResponse.OK:
                return StartupState(
                    response.chain_head,
                    response.peers,
                    response.local_peer_info)

            raise exceptions.ReceiveError(
                'Registration failed with status {}'.format(response.status))

    def _process(self, message):
        type_tag = message.message_type

        if type_tag == Message.CONSENSUS_NOTIFY_PEER_CONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerConnected()
            notification.ParseFromString(message.content)

            data = notification.peer_info

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_DISCONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerDisconnected()
            notification.ParseFromString(message.content)

            data = notification.peer_id

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_MESSAGE:
            notification = consensus_pb2.ConsensusNotifyPeerMessage()
            notification.ParseFromString(message.content)

            data = notification.message, notification.sender_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_NEW:
            notification = consensus_pb2.ConsensusNotifyBlockNew()
            notification.ParseFromString(message.content)

            data = notification.block

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_VALID:
            notification = consensus_pb2.ConsensusNotifyBlockValid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_INVALID:
            notification = consensus_pb2.ConsensusNotifyBlockInvalid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_COMMIT:
            notification = consensus_pb2.ConsensusNotifyBlockCommit()
            notification.ParseFromString(message.content)

            data = notification.block_id

        else:
            raise exceptions.ReceiveError(
                'Received unexpected message type: {}'.format(type_tag))

        self._stream.send_back(
            message_type=Message.CONSENSUS_NOTIFY_ACK,
            correlation_id=message.correlation_id,
            content=consensus_pb2.ConsensusNotifyAck().SerializeToString())

        return type_tag, data
class TransactionProcessor:
    """TransactionProcessor is a generic class for communicating with a
    validator and routing transaction processing requests to a registered
    handler. It uses ZMQ and channels to handle requests concurrently.
    """

    # This is the version used by SDK to match if validator supports feature
    # it requested during registration. It should only be incremented when
    # there are changes in TpRegisterRequest. Remember to sync this
    # information in validator if changed.
    # Note: SDK_PROTOCOL_VERSION is the highest version the SDK supports
    class _FeatureVersion(Enum):
        FEATURE_UNUSED = 0
        FEATURE_CUSTOM_HEADER_STYLE = 1
        SDK_PROTOCOL_VERSION = 1

    def __init__(self, url):
        """
        Args:
            url (string): The URL of the validator
        """
        self._stream = Stream(url)
        self._url = url
        self._handlers = []
        self._highest_sdk_feature_requested = \
            self._FeatureVersion.FEATURE_UNUSED
        self._header_style = TpRegisterRequest.HEADER_STYLE_UNSET

    @property
    def zmq_id(self):
        return self._stream.zmq_id

    def add_handler(self, handler):
        """Adds a transaction family handler
        Args:
            handler (TransactionHandler): the handler to be added
        """
        self._handlers.append(handler)

    def set_header_style(self, style):
        """Sets a flag to request the validator for custom transaction header
        style in TpProcessRequest.
        Args:
            style (TpProcessRequestHeaderStyle): enum value to set header style
        """
        if self._FeatureVersion.FEATURE_CUSTOM_HEADER_STYLE.value > \
                self._highest_sdk_feature_requested.value:
            self._highest_sdk_feature_requested = \
                self._FeatureVersion.FEATURE_CUSTOM_HEADER_STYLE
        self._header_style = style

    def _matches(self, handler, header):
        return header.family_name == handler.family_name \
            and header.family_version in handler.family_versions

    def _find_handler(self, header):
        """Find a handler for a particular (family_name, family_versions)
        :param header transaction_pb2.TransactionHeader:
        :return: handler
        """
        try:
            return next(handler for handler in self._handlers
                        if self._matches(handler, header))
        except StopIteration:
            LOGGER.debug("Missing handler for header: %s", header)
            return None

    def _register_requests(self):
        """Returns all of the TpRegisterRequests for handlers

        :return (list): list of TpRegisterRequests
        """
        return itertools.chain.from_iterable(  # flattens the nested list
            [[
                TpRegisterRequest(
                    family=n,
                    version=v,
                    namespaces=h.namespaces,
                    protocol_version=self._highest_sdk_feature_requested.value,
                    request_header_style=self._header_style)
                for n, v in itertools.product(
                    [h.family_name],
                    h.family_versions,
                )
            ] for h in self._handlers])

    def _unregister_request(self):
        """Returns a single TP_UnregisterRequest that requests
        that the validator stop sending transactions for previously
        registered handlers.

        :return (processor_pb2.TpUnregisterRequest):
        """
        return TpUnregisterRequest()

    def _process(self, msg):
        if msg.message_type != Message.TP_PROCESS_REQUEST:
            LOGGER.debug(
                "Transaction Processor recieved invalid message type. "
                "Message type should be TP_PROCESS_REQUEST,"
                " but is %s", Message.MessageType.Name(msg.message_type))
            return

        request = TpProcessRequest()
        request.ParseFromString(msg.content)
        state = Context(self._stream, request.context_id)
        if self._header_style == TpRegisterRequest.RAW:
            header = TransactionHeader()
            header.ParseFromString(request.header_bytes)
        else:
            header = request.header
        try:
            if not self._stream.is_ready():
                raise ValidatorConnectionError()
            handler = self._find_handler(header)
            if handler is None:
                return
            handler.apply(request, state)
            self._stream.send_back(
                message_type=Message.TP_PROCESS_RESPONSE,
                correlation_id=msg.correlation_id,
                content=TpProcessResponse(
                    status=TpProcessResponse.OK).SerializeToString())
        except InvalidTransaction as it:
            LOGGER.warning("Invalid Transaction %s", it)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INVALID_TRANSACTION,
                        message=str(it),
                        extended_data=it.extended_data).SerializeToString())
            except ValidatorConnectionError as vce:
                # TP_PROCESS_REQUEST has made it through the
                # handler.apply and an INVALID_TRANSACTION would have been
                # sent back but the validator has disconnected and so it
                # doesn't care about the response.
                LOGGER.warning("during invalid transaction response: %s", vce)
        except InternalError as ie:
            LOGGER.warning("internal error: %s", ie)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INTERNAL_ERROR,
                        message=str(ie),
                        extended_data=ie.extended_data).SerializeToString())
            except ValidatorConnectionError as vce:
                # Same as the prior except block, but an internal error has
                # happened, but because of the disconnect the validator
                # probably doesn't care about the response.
                LOGGER.warning("during internal error response: %s", vce)
        except ValidatorConnectionError as vce:
            # Somewhere within handler.apply a future resolved with an
            # error status that the validator has disconnected. There is
            # nothing left to do but reconnect.
            LOGGER.warning(
                "during handler.apply a future was resolved "
                "with error status: %s", vce)
        except AuthorizationException as ae:
            LOGGER.warning("AuthorizationException: %s", ae)
            try:
                self._stream.send_back(
                    message_type=Message.TP_PROCESS_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=TpProcessResponse(
                        status=TpProcessResponse.INVALID_TRANSACTION,
                        message=str(ae),
                    ).SerializeToString())
            except ValidatorConnectionError as vce:
                # TP_PROCESS_REQUEST has made it through the
                # handler.apply and an INVALID_TRANSACTION would have been
                # sent back but the validator has disconnected and so it
                # doesn't care about the response.
                LOGGER.warning("during invalid transaction response: %s", vce)

    def _process_future(self, future, timeout=None, sigint=False):
        try:
            msg = future.result(timeout)
        except CancelledError:
            # This error is raised when Task.cancel is called on
            # disconnect from the validator in stream.py, for
            # this future.
            return
        if msg is RECONNECT_EVENT:
            if sigint is False:
                LOGGER.info("reregistering with validator")
                self._stream.wait_for_ready()
                self._register()
        else:
            LOGGER.debug('received message of type: %s',
                         Message.MessageType.Name(msg.message_type))
            if msg.message_type == Message.PING_REQUEST:
                self._stream.send_back(
                    message_type=Message.PING_RESPONSE,
                    correlation_id=msg.correlation_id,
                    content=PingResponse().SerializeToString())
                return
            self._process(msg)

    def _register(self):
        futures = []
        for message in self._register_requests():
            self._stream.wait_for_ready()
            future = self._stream.send(
                message_type=Message.TP_REGISTER_REQUEST,
                content=message.SerializeToString())
            futures.append(future)

        for future in futures:
            resp = TpRegisterResponse()
            try:
                resp.ParseFromString(future.result().content)
                if resp.protocol_version != \
                        self._highest_sdk_feature_requested.value:
                    LOGGER.error(
                        "Validator version %s does not support "
                        "requested feature by SDK version %s. "
                        "Unregistering with the validator.",
                        str(resp.protocol_version),
                        str(self._highest_sdk_feature_requested.value))
                    raise ValidatorVersionError()
                LOGGER.info("register attempt: %s",
                            TpRegisterResponse.Status.Name(resp.status))
                if resp.status == TpRegisterResponse.ERROR:
                    raise RuntimeError("Transaction processor registration "
                                       "failed")
            except ValidatorConnectionError as vce:
                LOGGER.info("during waiting for response on registration: %s",
                            vce)

    def _unregister(self):
        message = self._unregister_request()
        self._stream.wait_for_ready()
        future = self._stream.send(message_type=Message.TP_UNREGISTER_REQUEST,
                                   content=message.SerializeToString())
        response = TpUnregisterResponse()
        try:
            response.ParseFromString(future.result(1).content)
            LOGGER.info("unregister attempt: %s",
                        TpUnregisterResponse.Status.Name(response.status))
        except ValidatorConnectionError as vce:
            LOGGER.info("during waiting for response on unregistration: %s",
                        vce)

    def start(self):
        """Connects the transaction processor to a validator and starts
        listening for requests and routing them to an appropriate
        transaction handler.
        """
        fut = None
        try:
            self._register()
            while True:
                # During long running processing this
                # is where the transaction processor will
                # spend most of its time
                fut = self._stream.receive()
                self._process_future(fut)
        except (KeyboardInterrupt, ValidatorVersionError):
            try:
                # tell the validator to not send any more messages
                self._unregister()
                while True:
                    if fut is not None:
                        # process futures as long as the tp has them,
                        # if the TP_PROCESS_REQUEST doesn't come from
                        # zeromq->asyncio in 1 second raise a
                        # concurrent.futures.TimeOutError and be done.
                        self._process_future(fut, 1, sigint=True)
                        fut = self._stream.receive()
            except concurrent.futures.TimeoutError:
                # Where the tp will usually exit after
                # a KeyboardInterrupt. Caused by the 1 second
                # timeout in _process_future.
                pass
            except FutureTimeoutError:
                # If the validator is not able to respond to the
                # unregister request, exit.
                pass
        except RuntimeError as e:
            LOGGER.error("Error: %s", e)
            self.stop()

    def stop(self):
        """Closes the connection between the TransactionProcessor and the
        validator.
        """
        self._stream.close()
예제 #9
0
class ZmqDriver(Driver):
    def __init__(self, engine):
        super().__init__(engine)
        self._engine = engine
        self._stream = None
        self._exit = False
        self._updates = None

    def start(self, endpoint):
        LOGGER.debug('ZmqDriver: start endpoint=%s', endpoint)
        self._stream = Stream(endpoint)

        startup_state = self._register()

        self._updates = Queue()

        driver_thread = Thread(target=self._driver_loop)
        driver_thread.start()

        try:
            self._engine.start(
                self._updates,
                ZmqService(stream=self._stream,
                           timeout=SERVICE_TIMEOUT,
                           name=self._engine.name(),
                           version=self._engine.version()), startup_state)
        except Exception as ex:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught engine exception(%s)", ex)

        self.stop()
        driver_thread.join()

    def _driver_loop(self):
        try:
            future = self._stream.receive()
            LOGGER.debug('ZmqDriver: _driver_loop future=%s', future)
            while True:
                if self._exit:
                    self._engine.stop()
                    break

                try:
                    message = future.result(1)
                    future = self._stream.receive()
                except concurrent.futures.TimeoutError:
                    continue
                #LOGGER.debug('ZmqDriver: _driver_loop _process')
                result = self._process(message)

                self._updates.put(result)
        except Exception:  # pylint: disable=broad-except
            LOGGER.exception("Uncaught driver exception")

    def stop(self):
        self._exit = True
        self._engine.stop()
        self._stream.close()

    def _register(self):
        self._stream.wait_for_ready()

        request = consensus_pb2.ConsensusRegisterRequest(
            name=self._engine.name(),
            version=self._engine.version(),
        ).SerializeToString()

        while True:
            future = self._stream.send(
                message_type=Message.CONSENSUS_REGISTER_REQUEST,
                content=request)
            response = consensus_pb2.ConsensusRegisterResponse()
            response.ParseFromString(future.result(REGISTER_TIMEOUT).content)

            if (response.status ==
                    consensus_pb2.ConsensusRegisterResponse.NOT_READY):
                #LOGGER.debug('ZmqDriver:register NOT_READY: url=%s',self._stream._url)
                continue

            if response.status == consensus_pb2.ConsensusRegisterResponse.OK:
                LOGGER.debug('ZmqDriver:register DONE: url=%s',
                             self._stream._url)
                return StartupState(response.chain_head, response.peers,
                                    response.local_peer_info,
                                    response.peering_mode)

            raise exceptions.ReceiveError(
                'Registration failed with status {}'.format(response.status))

    def _process(self, message):
        type_tag = message.message_type

        if type_tag == Message.CONSENSUS_NOTIFY_PEER_CONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerConnected()
            notification.ParseFromString(message.content)

            data = notification.peer_info, notification.status, notification.mode, notification.info

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_DISCONNECTED:
            notification = consensus_pb2.ConsensusNotifyPeerDisconnected()
            notification.ParseFromString(message.content)

            data = notification.peer_id

        elif type_tag == Message.CONSENSUS_NOTIFY_PEER_MESSAGE:
            notification = consensus_pb2.ConsensusNotifyPeerMessage()
            notification.ParseFromString(message.content)

            data = notification.message, notification.sender_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_NEW:
            notification = consensus_pb2.ConsensusNotifyBlockNew()
            notification.ParseFromString(message.content)

            data = notification.block

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_VALID:
            notification = consensus_pb2.ConsensusNotifyBlockValid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_INVALID:
            notification = consensus_pb2.ConsensusNotifyBlockInvalid()
            notification.ParseFromString(message.content)

            data = notification.block_id

        elif type_tag == Message.CONSENSUS_NOTIFY_BLOCK_COMMIT:
            notification = consensus_pb2.ConsensusNotifyBlockCommit()
            notification.ParseFromString(message.content)

            data = notification.block_id

        else:
            raise exceptions.ReceiveError(
                'Received unexpected message type: {}'.format(type_tag))

        self._stream.send_back(
            message_type=Message.CONSENSUS_NOTIFY_ACK,
            correlation_id=message.correlation_id,
            content=consensus_pb2.ConsensusNotifyAck().SerializeToString())

        return type_tag, data