def __initialize(self, config):
     """
     Initialize the parameters from config to instance variables.
     """
     self.__eth_client = EthereumWrapper(config)
     tcf_home = environ.get("TCF_HOME", "../../../")
     contract_file_name = tcf_home + "/" + \
         config["ethereum"]["direct_registry_contract_file"]
     contract_address = \
         config["ethereum"]["direct_registry_contract_address"]
     self.__contract_instance, _ = self.__eth_client.get_contract_instance(
         contract_file_name, contract_address)
    def __init__(self, config):

        self._config = config
        self._eth_client = EthereumWrapper(config)
        tcf_home = os.environ.get("TCF_HOME", "../../")

        worker_reg_contract_file = tcf_home + "/" + \
            config["ethereum"]["worker_registry_contract_file"]
        worker_reg_contract_address = \
            config["ethereum"]["worker_registry_contract_address"]
        self._worker_reg_contract_instance,\
            self._worker_reg_contract_instance_evt = self._eth_client\
            .get_contract_instance(
                worker_reg_contract_file, worker_reg_contract_address)

        self._worker_registry = EthereumWorkerRegistryImpl(config)

        work_order_contract_file = tcf_home + "/" + \
            config["ethereum"]["work_order_contract_file"]
        work_order_contract_address = \
            config["ethereum"]["work_order_contract_address"]
        self._work_order_contract_instance,\
            self._work_order_contract_instance_evt = self._eth_client\
            .get_contract_instance(
                work_order_contract_file, work_order_contract_address)

        self._work_order_proxy = EthereumWorkOrderProxyImpl(config)
        # List of active available worker ids in Avalon
        self.__worker_ids = []
    def __initialize(self, config):
        """
        Initialize the parameters from config to instance variables.

        Parameters:
        config    Ethereum-specific configuration parameters to initialize
        """
        self.__eth_client = EthereumWrapper(config)
        tcf_home = environ.get("TCF_HOME", "../../../")
        contract_file_name = tcf_home + "/" + \
            config["ethereum"]["worker_registry_contract_file"]
        contract_address = \
            config["ethereum"]["worker_registry_contract_address"]
        self.__contract_instance, self.__contract_instance_evt = \
            self.__eth_client.get_contract_instance(
                contract_file_name, contract_address
            )
Beispiel #4
0
def main(args=None):

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--config",
        help="The config file containing the Ethereum contract information",
        type=str)
    parser.add_argument("-u",
                        "--uri",
                        help="Direct API listener endpoint",
                        default="http://avalon-listener:1947",
                        type=str)

    options = parser.parse_args(args)

    config = _parse_config_file(options.config)
    if config is None:
        logging.error("\n Error in parsing config file: {}\n".format(
            options.config))
        sys.exit(-1)

    # Http JSON RPC listener uri
    uri = options.uri
    if uri:
        config["tcf"]["json_rpc_uri"] = uri

    logging.info("About to start Ethereum connector service")
    eth_client = EthereumWrapper(config)

    worker_reg_contract_file = TCF_HOME + "/" + \
        config["ethereum"]["worker_registry_contract_file"]
    worker_reg_contract_address = \
        config["ethereum"]["worker_registry_contract_address"]
    worker_reg_contract_instance,\
        worker_reg_contract_instance_evt = eth_client\
        .get_contract_instance(
            worker_reg_contract_file, worker_reg_contract_address)

    worker_registry = EthereumWorkerRegistryImpl(config)

    work_order_contract_file = TCF_HOME + "/" + \
        config["ethereum"]["work_order_contract_file"]
    work_order_contract_address = \
        config["ethereum"]["work_order_contract_address"]
    work_order_contract_instance,\
        wo_contract_instance_evt = eth_client\
        .get_contract_instance(
            work_order_contract_file, work_order_contract_address)

    work_order_proxy = EthereumWorkOrderProxyImpl(config)
    eth_connector_svc = EthereumConnector(config, None, worker_registry,
                                          work_order_proxy, None,
                                          wo_contract_instance_evt)
    eth_connector_svc.start()
class EthereumWorkOrderProxyImpl(WorkOrderProxy):
    """
    This class is meant to write work order-related data to the Ethereum
    blockchain.
    Detailed method descriptions are available in the interfaces.
    """
    def __init__(self, config):
        self.WORK_ORDER_GET_RESULT_TIMEOUT = 30
        if self.__validate(config) is True:
            self.__initialize(config)
        else:
            raise Exception("Invalid configuration parameter")

    def __validate(self, config):
        """
        Validate configuration parameters for existence.

        Parameters:
        config    Configuration parameters

        Returns:
        True if validation succeeds or false if validation fails.
        """
        try:
            if config["ethereum"]["work_order_contract_file"] is None:
                logging.error("Missing work order contract file path!!")
                return False
            if config["ethereum"]["work_order_contract_address"] is None:
                logging.error("Missing work order contract address!!")
                return False
            if config["ethereum"]["provider"] is None:
                logging.error("Missing Ethereum provider url!!")
                return False
        except KeyError as ex:
            logging.error("Required configs not present: {}".format(ex))
            return False
        return True

    def __initialize(self, config):
        """
        Initialize the parameters from config to instance variables.

        Parameters:
        config    Configuration parameters to initialize
        """
        self.__eth_client = EthereumWrapper(config)
        self._config = config
        tcf_home = environ.get("TCF_HOME", "../../../")
        contract_file_name = tcf_home + "/" + \
            config["ethereum"]["work_order_contract_file"]
        contract_address = \
            config["ethereum"]["work_order_contract_address"]
        self.__contract_instance, self.__contract_instance_evt =\
            self.__eth_client.get_contract_instance(
                contract_file_name, contract_address
            )

    def work_order_submit(self,
                          work_order_id,
                          worker_id,
                          requester_id,
                          work_order_request,
                          id=None):
        """
        Submit work order request to the Ethereum block chain.

        Parameters:
        work_order_id      Unique ID of the work order request
        worker_id          Identifier for the worker
        requester_id       Unique id to identify the requester
        work_order_request JSON RPC string work order request.
                           Complete definition at work_order.py and
                           defined in EEA specification 6.1.1.
        id                 Optional JSON RPC request ID

        Returns:
        0 on success and non-zero on error.
        """
        if (self.__contract_instance is not None):

            if not _is_valid_work_order_json(work_order_id, worker_id,
                                             requester_id, work_order_request):
                logging.error(
                    "Invalid request string {}".format(work_order_request))
                return ContractResponse.ERROR

            try:
                contract_func = \
                    self.__contract_instance.functions.workOrderSubmit(
                        work_order_id, worker_id,
                        requester_id, work_order_request)
                txn_receipt = self.__eth_client.build_exec_txn(contract_func)
                if txn_receipt['status'] == 1:
                    return ContractResponse.SUCCESS
                else:
                    return ContractResponse.ERROR
            except Exception as e:
                logging.error(
                    "Exception occurred when trying to execute " +
                    "workOrderSubmit transaction on chain {}".format(e))
                return ContractResponse.ERROR
        else:
            logging.error("Work order contract instance is not initialized")
            return ContractResponse.ERROR

    def work_order_complete(self, work_order_id, work_order_response):
        """
        This function is called by the Worker Service to
        complete a work order successfully or in error.
        This API is for the proxy model.

        Parameters:
        work_order_id       Unique ID to identify the work order request
        work_order_response Work order response data in a string

        Returns:
        errorCode           0 on success or non-zero on error.
        """
        if (self.__contract_instance is not None):
            try:
                contract_func = \
                    self.__contract_instance.functions.workOrderComplete(
                        work_order_id, work_order_response)
                txn_receipt = self.__eth_client.build_exec_txn(contract_func)
                if txn_receipt['status'] == 1:
                    return ContractResponse.SUCCESS
                else:
                    return ContractResponse.ERROR
            except Exception as e:
                logging.error(
                    "Exception occurred when trying to execute " +
                    "workOrderComplete transaction on chain {}".format(e))
                return ContractResponse.ERROR
        else:
            logging.error("Work order contract instance is not initialized")
            return ContractResponse.ERROR

    def work_order_get_result(self, work_order_id, id=None):
        """
        Query blockchain to get a work order result.
        This function starts an event handler for handling the
        workOrderCompleted event from the Ethereum blockchain.

        Parameters:
        work_order_id Work Order ID that was sent in the
                      corresponding work_order_submit request
        id            Optional JSON RPC request ID

        Returns:
        Tuple containing work order status, worker id, work order request,
        work order response, and error code.
        None on error.
        """
        # Call the contract to get the result from blockchain
        # If we get the result from chain then return
        # Otherwise start the event listener to get work order
        # completed event.
        if (self.__contract_instance is not None):
            try:
                # workOrderGet returns tuple containing work order
                # result params as defined in EEA spec 6.10.5
                _, _, _, work_order_res, _ = \
                    self.__contract_instance.functions.workOrderGet(
                        work_order_id).call()
                if len(work_order_res) > 0:
                    return json.loads(work_order_res)
                else:
                    logging.warn("Work order seems to be not computed yet, "
                                 "going to listen for workOrderCompleted "
                                 "event")
            except Exception as e:
                logging.error("Failed to call contract {}".format(e))
                return None
        else:
            logging.error("Work order contract instance is not initialized")
            return None

        # Start an event listener that listens for events from the proxy
        # blockchain, extracts response payload from there and passes it
        # on to the requestor

        w3 = BlockchainInterface(self._config)

        contract = self.__contract_instance_evt
        # Listening only for workOrderCompleted event now
        listener = w3.newListener(contract, "workOrderCompleted")

        daemon = EventProcessor(self._config)
        # Wait for the workOrderCompleted event after starting the
        # listener and handler
        try:
            event = asyncio.get_event_loop().run_until_complete(
                asyncio.wait_for(
                    daemon.get_event_synchronously(listener,
                                                   is_wo_id_in_event,
                                                   wo_id=work_order_id),
                    timeout=self.WORK_ORDER_GET_RESULT_TIMEOUT,
                ))
            # Get the first element as this is a list of one event
            # obtained from gather() in ethereum_listener
            work_order_response = event[0]["args"]["workOrderResponse"]

            return json.loads(work_order_response)
        except asyncio.TimeoutError:
            logging.error("Work order get result timed out."
                          " Either work order id is invalid or"
                          " Work order execution not completed yet")
        except Exception as e:
            logging.error("Exception occurred while listening"
                          " to an event: {}".format(e))
        finally:
            asyncio.get_event_loop().run_until_complete(daemon.stop())
        return None

    def encryption_key_retrieve(self,
                                worker_id,
                                last_used_key_nonce,
                                tag,
                                requester_id,
                                signature_nonce=None,
                                signature=None,
                                id=None):
        """
        Get Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_start(self, tag, id=None):
        """
        Inform the Worker that it should start
        encryption key generation for this requester.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_set(self,
                           worker_id,
                           encryption_key,
                           encryption_nonce,
                           tag,
                           signature,
                           id=None):
        """
        Set Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_get(self,
                           worker_id,
                           requester_id,
                           last_used_key_nonce=None,
                           tag=None,
                           signature_nonce=None,
                           signature=None,
                           id=None):
        """
        Get Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass
class EthereumWorkerRegistryImpl(WorkerRegistry):
    """
    This class is sets and gets worker-related information to and from
    the Ethereum blockchain.
    Detailed method descriptions are available in the WorkerRegistry
    interfaces.
    """

    def __init__(self, config):
        """
        Parameters:
        config    Dictionary containing Ethereum-specific parameters
        """
        if self.__validate(config) is True:
            self.__initialize(config)
        else:
            raise Exception("Invalid configuration parameter")

    def worker_lookup(self, worker_type, org_id, application_id, id=None):
        """
        Lookup a worker identified by worker_type, org_id, and application_id.
        All fields are optional and, if present, condition should match for
        all fields. If none are passed it should return all workers.

        If the list is too large to fit into a single response (the maximum
        number of entries in a single response is implementation specific),
        the smart contract should return the first batch of the results
        and provide a lookupTag that can be used by the caller to
        retrieve the next batch by calling worker_lookup_next.

        Parameters:
        worker_type    Optional characteristic of workers for which you may
                       wish to search
        org_id         Optional organization ID that can be used to search
                       for one or more workers that belong to this
                       organization
        application_id Optional application type ID that is supported by
                       the worker
        id             Optional JSON RPC request ID

        Returns:
        Tuple containing workers count, lookup tag, and list of
        worker IDs:
        total_count Total number of entries matching a specified
                    lookup criteria. If this number is larger than the
                    size of the IDs array, the caller should use
                    lookupTag to call worker_lookup_next to retrieve
                    the rest of the IDs
        lookup_tag  Optional parameter. If it is returned, it means
                    that there are more matching worker IDs, which can then
                    be retrieved by calling function worker_lookup_next
                    with this tag as an input parameter
        ids         Array of the worker IDs that match the input parameters
        On error returns None.
        """

        if (self.__contract_instance is not None):
            if not isinstance(worker_type, WorkerType):
                logging.error("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(org_id):
                logging.error("Invalid organization id {}".format(org_id))
                return None
            if not is_valid_hex_str(application_id):
                logging.error(
                    "Invalid application id {}".format(application_id))
                return None
            w_count, lookup_tag, w_ids = \
                self.__contract_instance.functions.workerLookUp(
                    worker_type.value, org_id, application_id).call()

            return w_count, lookup_tag, _convert_byte32_arr_to_hex_arr(
                w_ids)
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_retrieve(self, worker_id, id=None):
        """
        Retrieve the worker identified by worker ID.

        Parameters:
        worker_id  Worker ID of the registry whose details are requested
        id         Optional JSON RPC request ID

        Returns:
        Tuple containing worker status (defined in worker_set_status),
        worker type, organization ID, list of application IDs, and worker
        details (JSON RPC string).

        On error returns None.
        """

        if (self.__contract_instance is not None):
            status, w_type, org_id, app_ids, details = \
                self.__contract_instance.functions.workerRetrieve(
                    worker_id).call()
            return (status, w_type, org_id,
                    _convert_byte32_arr_to_hex_arr(app_ids), details)
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_lookup_next(self, worker_type, org_id, application_id,
                           lookup_tag):
        """
        Retrieve additional worker lookup results after calling worker_lookup.

        Parameters:
        worker_type         Characteristic of Workers for which you may wish
                            to search
        org_id              Organization ID to which a Worker belongs
        application_id      Optional application type ID that is
                            supported by the worker
        lookup_tag          is returned by a previous call to either this
                            function or to worker_lookup
        id                  Optional Optional JSON RPC request ID


        Returns:
        Tuple containing the following:
        total_count    Total number of entries matching this lookup
                       criteria.  If this number is larger than the number
                       of IDs returned so far, the caller should use
                       lookupTag to call worker_lookup_next to retrieve
                       the rest of the IDs
        new_lookup_tag Optional parameter. If it is returned, it
                       means that there are more matching worker IDs that
                       can be retrieved by calling this function again with
                       this tag as an input parameter
        ids            Array of the worker IDs that match the input parameters

        On error returns None.
        """

        if (self.__contract_instance is not None):
            if not isinstance(worker_type, WorkerType):
                logging.error("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(org_id):
                logging.error("Invalid organization id {}".format(org_id))
                return None
            if not is_valid_hex_str(application_id):
                logging.error("Invalid application id {}".format(org_id))
                return None
            lookup_result = self.__contract_instance.functions\
                .workerLookUpNext(
                    worker_type.value,
                    org_id, application_id, lookup_tag).call()
            return lookup_result
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_register(self, worker_id, worker_type, organization_id,
                        application_type_ids, details):
        """
        Register a new worker with details of the worker.

        Parameters:
        worker_id       Worker ID value. E.g., an Ethereum address or
                        a value derived from the worker's DID
        worker_type     Type of Worker. Currently defined types are:
                        * "TEE-SGX": an Intel SGX Trusted Execution
                          Environment
                        * "MPC": Multi-Party Compute
                        * "ZK": Zero-Knowledge
        organization_id Optional parameter representing the
                        organization that hosts the Worker,
                        e.g. a bank in the consortium or
                        anonymous entity
        application_ids Optional parameter that defines
                        application types supported by the Worker
        details         Detailed information about the worker in
                        JSON RPC format as defined in
                https://entethalliance.github.io/trusted-computing/spec.html
                #common-data-for-all-worker-types

        Returns:
        Transaction receipt if registration succeeds.
        None if registration does not succeed.
        """

        if (self.__contract_instance is not None):
            if not is_valid_hex_str(worker_id):
                logging.error("Invalid worker id {}".format(worker_id))
                return None
            if not isinstance(worker_type, WorkerType):
                logging.error("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(organization_id):
                logging.error("Invalid organization id {}"
                              .format(organization_id))
                return None
            for app_id in application_type_ids:
                if not is_valid_hex_str(app_id):
                    logging.error("Invalid application id {}".format(app_id))
                    return None
            # TODO : Validate worker details. As of now worker details are not
            # strictly following the spec
            """if details is not None:
                worker = WorkerDetails()
                is_valid = worker.validate_worker_details(details)
                if is_valid is not None:
                    logging.error(
                        "Worker details not valid : {}".format(is_valid))
                    return None
            """
            contract_func = \
                self.__contract_instance.functions.workerRegister(
                    worker_id, worker_type.value, organization_id,
                    application_type_ids, details)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            if txn_receipt['status'] == 1:
                return ContractResponse.SUCCESS
            else:
                return ContractResponse.ERROR
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return ContractResponse.ERROR

    def worker_update(self, worker_id, details):
        """
        Update a worker with details data.

        Parameters:
        worker_id  Worker ID value. E.g., an Ethereum address or
                   a value derived from the worker's DID
        details    Detailed information about the worker in JSON format

        Returns:
        Transaction receipt if registration succeeds.
        None if registration does not succeed.
        """

        if (self.__contract_instance is not None):
            if not is_valid_hex_str(worker_id):
                logging.error("Invalid worker id {}".format(worker_id))
                return None

            # TODO : Validate worker details. As of now worker details are not
            # strictly following the spec
            """if details is not None:
                worker = WorkerDetails()
                is_valid = worker.validate_worker_details(details)
                if is_valid is not None:
                    logging.error(
                        "Worker details not valid : {}".format(is_valid))
                    return None
            """
            contract_func = \
                self.__contract_instance.functions.workerUpdate(
                    worker_id, details)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            if txn_receipt['status'] == 1:
                return ContractResponse.SUCCESS
            else:
                return ContractResponse.ERROR
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return ContractResponse.ERROR

    def worker_set_status(self, worker_id, status):
        """
        Set the worker status identified by worker ID.

        Parameters:
        worker_id Worker ID value. E.g., an Ethereum address or
                  a value derived from the worker's DID
        status    Worker status. The currently defined values are:
                  1 - worker is active
                  2 - worker is temporarily "off-line"
                  3 - worker is decommissioned
                  4 - worker is compromised

        Returns:
        Transaction receipt if registration succeeds.
        None if registration does not succeed.
        """

        if (self.__contract_instance is not None):
            if not is_valid_hex_str(worker_id):
                logging.error("Invalid worker id {}".format(worker_id))
                return None
            if not isinstance(status, WorkerStatus):
                logging.error("Invalid workerStatus {}".format(status))
                return None

            contract_func = \
                self.__contract_instance.functions.workerSetStatus(
                    worker_id, status.value)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            if txn_receipt['status'] == 1:
                return ContractResponse.SUCCESS
            else:
                return ContractResponse.ERROR
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return ContractResponse.ERROR

    def _is_valid_json(self, json_string):
        try:
            json.loads(json_string)
        except ValueError as e:
            logging.error(e)
            return False
        return True

    def __validate(self, config):
        """
        Validate configuration parameters for existence.

        Parameters:
        config    Ethereum-specific configuration parameters

        Returns:
        True if validation succeeds or false if validation fails.
        """
        if config["ethereum"]["worker_registry_contract_file"] is None:
            logging.error("Missing worker registry contract file path!!")
            return False
        if config["ethereum"]["worker_registry_contract_address"] is None:
            logging.error("Missing worker registry contract address!!")
            return False
        return True

    def __initialize(self, config):
        """
        Initialize the parameters from config to instance variables.

        Parameters:
        config    Ethereum-specific configuration parameters to initialize
        """
        self.__eth_client = EthereumWrapper(config)
        tcf_home = environ.get("TCF_HOME", "../../../")
        contract_file_name = tcf_home + "/" + \
            config["ethereum"]["worker_registry_contract_file"]
        contract_address = \
            config["ethereum"]["worker_registry_contract_address"]
        self.__contract_instance, self.__contract_instance_evt = \
            self.__eth_client.get_contract_instance(
                contract_file_name, contract_address
            )
class EthereumWorkerRegistryListImpl(WorkerRegistryList):
    """
    This class provide APIs to read/write registry entries of workers,
    which is stored in the Ethereum blockchain.
    """
    def __init__(self, config):
        if self.__validate(config):
            self.__initialize(config)
        else:
            raise Exception("Invalid or missing config parameter")

    def registry_lookup(self, app_type_id=None):
        """
        Registry Lookup identified by application type ID.

        Parameters:
        app_type_id  Application type ID to lookup in the registry

        Returns:
        Returns tuple containing totalCount, lookupTag, ids on success:
        totalCount Total number of entries matching a specified
                   lookup criteria.  If this number is larger than the size
                   of the IDs array, the caller should use the lookupTag to
                   call workerLookUpNext to retrieve the rest of the IDs
        lookupTag  Optional parameter. If it is returned, it means that
                   there are more matching registry IDs that can be retrieved
                   by calling the function registry_lookup_next with this tag
                   as an input parameter
        ids        Array of the registry organization IDs that match the
                   input parameters

        Returns None on error.
        """
        if (self.__contract_instance is not None):
            if app_type_id is not None:
                if is_valid_hex_str(
                        binascii.hexlify(app_type_id).decode("utf8")):
                    lookupResult = \
                        self.__contract_instance.functions.registryLookUp(
                            app_type_id).call()
                else:
                    logging.info(
                        "Invalid application type id {}".format(app_type_id))
                    return None
            else:
                lookup_result = \
                    self.__contract_instance.functions.registryLookUp(b"") \
                    .call()
            return lookup_result
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None

    def registry_retrieve(self, org_id):
        """
        Retrieving Registry Information identified by organization ID.

        Parameters:
        org_id     Organization ID to lookup

        Returns:
        Tuple containing following on success:
        uri                  string defining a URI for this registry that
                             supports the Off-Chain Worker Registry JSON
                             RPC API. It will be None for the proxy model
        sc_addr              Ethereum address for worker registry
                             smart contract address
        application_type_ids List of application ids(array of byte[])
        status               Status of the registry

        Returns None on error.
        """
        if (self.__contract_instance is not None):
            if (is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")) is
                    False):
                logging.info("Invalid Org id {}".format(org_id))
                return None
            else:
                registry_details = \
                    self.__contract_instance.functions.registryRetrieve(
                        org_id).call()
                return registry_details
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None

    def registry_lookup_next(self, app_type_id, lookup_tag):
        """
        Get additional registry lookup results.
        This function is called to retrieve additional results of the
        Registry lookup initiated by the registry_lookUp call.

        Parameters:
        app_type_id    Application type that has to be
                       supported by the workers retrieved
        lookup_tag     Returned by a previous call to either this
                       function or to registry_lookup

        Returns:
        Outputs tuple on success containing the following:
        total_count    Total number of entries matching the lookup
                       criteria. If this number is larger than the number
                       of IDs returned so far, the caller should use
                       lookup_tag to call registry_lookup_next to
                       the rest of the ids
        new_lookup_tag Optional parameter. If it is returned, it means
                       that there are more matching registry IDs that
                       can be retrieved by calling this function again
                       with this tag as an input parameter
        ids            Array of the registry IDs that match the input
                       parameters

        Returns None on error.
        """
        if (self.__contract_instance is not None):
            if is_valid_hex_str(binascii.hexlify(app_type_id).decode("utf8")):
                lookup_result = \
                    self.__contract_instance.functions.registryLookUpNext(
                        app_type_id, lookup_tag).call()
                return lookup_result
            else:
                logging.info(
                    "Invalid application type id {}".format(app_type_id))
                return None
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None

    def __validate(self, config):
        """
        Validates parameter from config parameters for existence.

        Parameters:
        config    Configuration parameters to validate

        Returns:
        true on success or false if validation fails.
        """
        if config["ethereum"]["direct_registry_contract_file"] is None:
            logging.error("Missing direct registry contract file path!!")
            return False
        if config["ethereum"]["direct_registry_contract_address"] is None:
            logging.error("Missing direct registry contract address!!")
            return False
        return True

    def __initialize(self, config):
        """
        Initialize the parameters from config to instance variables.
        """
        self.__eth_client = EthereumWrapper(config)
        tcf_home = environ.get("TCF_HOME", "../../../")
        contract_file_name = tcf_home + "/" + \
            config["ethereum"]["direct_registry_contract_file"]
        contract_address = \
            config["ethereum"]["direct_registry_contract_address"]
        self.__contract_instance, _ = self.__eth_client.get_contract_instance(
            contract_file_name, contract_address)

    def registry_add(self, org_id, uri, sc_addr, app_type_ids):
        """
        Add a new registry.

        Parameters:
        org_id       bytes[] identifies organization that hosts the
                     registry, e.g. a bank in the consortium or an
                     anonymous entity
        uri          String defines a URI for this registry that
                     supports the Off-Chain Worker Registry
                     JSON RPC API.
        sc_addr      bytes[] defines an Ethereum address that
                     runs the Worker Registry Smart Contract API
                     smart contract for this registry
        app_type_ids []bytes[] is an optional parameter that defines
                     application types supported by the worker
                     managed by the registry

        Returns:
        Transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if (is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")) is
                    False):
                logging.info("Invalid Org id {}".format(org_id))
                return None
            if (sc_addr is not None and is_valid_hex_str(
                    binascii.hexlify(sc_addr).decode("utf8")) is False):
                logging.info("Invalid smart contract address {}")
                return None
            if (not uri):
                logging.info("Empty uri {}".format(uri))
                return None
            for aid in app_type_ids:
                if (is_valid_hex_str(binascii.hexlify(aid).decode("utf8")) is
                        False):
                    logging.info("Invalid application id {}".format(aid))
                    return None
            contract_func = \
                self.__contract_instance.functions.registryAdd(
                    org_id, uri, org_id, app_type_ids)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            return txn_receipt
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None

    def registry_update(self, org_id, uri, sc_addr, app_type_ids):
        """
        Update a registry.

        Parameters:
        org_id               bytes[] identifies organization that hosts the
                             registry, e.g. a bank in the consortium or
                             an anonymous entity
        uri                  string defines a URI for this registry that
                             supports the Off-Chain Worker Registry
                             JSON RPC API
        sc_addr              bytes[] defines an Ethereum address that
                             runs a Worker Registry Smart Contract API
                             smart contract for this registry
        app_type_ids         []bytes[] is an optional parameter that defines
                             application types supported by the worker
                             managed by the registry

        Returns:
        Transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if (is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")) is
                    False):
                logging.error("Invalid Org id {}".format(org_id))
                return None
            if (sc_addr is not None and is_valid_hex_str(
                    binascii.hexlify(sc_addr).decode("utf8")) is False):
                logging.error(
                    "Invalid smart contract address {}".format(sc_addr))
                return None
            if (not uri):
                logging.error("Empty uri {}".format(uri))
                return None
            for aid in app_type_ids:
                if (is_valid_hex_str(binascii.hexlify(aid).decode("utf8")) is
                        False):
                    logging.error("Invalid application id {}".format(aid))
                    return None
            contract_func = \
                self.__contract_instance.functions.registryUpdate(
                    org_id, uri, sc_addr, app_type_ids)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            return txn_receipt
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None

    def registry_set_status(self, org_id, status):
        """
        Set registry status.

        Parameters:
        org_id  bytes[] identifies organization that hosts
                the registry
        status  Defines registry status to set.
                The currently defined values are:
                1 - the registry is active
                2 - the registry is temporarily "off-line"
                3 - the registry is decommissioned

        Returns:
        Transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if (is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")) is
                    False):
                logging.info("Invalid Org id {}".format(org_id))
                return None
            if not isinstance(status, RegistryStatus):
                logging.info("Invalid registry status {}".format(status))
                return None
            contract_func = \
                self.__contract_instance.functions.registrySetStatus(
                    org_id, status.value)
            txn_receipt = self.__eth_client.build_exec_txn(contract_func)
            return txn_receipt
        else:
            logging.error(
                "direct registry contract instance is not initialized")
            return None
Beispiel #8
0
class EthereumWorkOrderProxyImpl(WorkOrderProxy):
    """
    This class is meant to write work order-related data to the Ethereum
    blockchain.
    Detailed method descriptions are available in the interfaces.
    """
    def __init__(self, config):
        if self.__validate(config) is True:
            self.__initialize(config)
        else:
            raise Exception("Invalid configuration parameter")

    def __validate(self, config):
        """
        Validate configuration parameters for existence.

        Parameters:
        config    Configuration parameters

        Returns:
        True if validation succeeds or false if validation fails.
        """
        try:
            if config["ethereum"]["work_order_contract_file"] is None:
                logging.error("Missing work order contract file path!!")
                return False
            if config["ethereum"]["work_order_contract_address"] is None:
                logging.error("Missing work order contract address!!")
                return False
            if config["ethereum"]["provider"] is None:
                logging.error("Missing Ethereum provider url!!")
                return False
        except KeyError as ex:
            logging.error("Required configs not present".format(ex))
            return False
        return True

    def __initialize(self, config):
        """
        Initialize the parameters from config to instance variables.

        Parameters:
        config    Configuration parameters to initialize
        """
        self.__eth_client = EthereumWrapper(config)
        self._config = config
        tcf_home = environ.get("TCF_HOME", "../../../")
        contract_file_name = tcf_home + "/" + \
            config["ethereum"]["work_order_contract_file"]
        contract_address = \
            config["ethereum"]["work_order_contract_address"]
        self.__contract_instance, self.__contract_instance_evt =\
            self.__eth_client.get_contract_instance(
                contract_file_name, contract_address
            )

    def work_order_submit(self,
                          work_order_id,
                          worker_id,
                          requester_id,
                          work_order_request,
                          id=None):
        """
        Submit work order request to the Ethereum block chain.

        Parameters:
        work_order_id      Unique ID of the work order request
        worker_id          Identifier for the worker
        requester_id       Unique id to identify the requester
        work_order_request JSON RPC string work order request.
                           Complete definition at work_order.py and
                           defined in EEA specification 6.1.1.
        id                 Optional JSON RPC request ID

        Returns:
        0 on success and non-zero on error.
        """
        logging.info("Inside Ethereum work order submit\n")
        if (self.__contract_instance is not None):

            if not _is_valid_work_order_json(work_order_id, worker_id,
                                             requester_id, work_order_request):
                logging.error(
                    "Invalid request string {}".format(work_order_request))
                return ERROR

            txn_dict = self.__contract_instance.functions.workOrderSubmit(
                work_order_id, worker_id, requester_id,
                work_order_request).buildTransaction(
                    self.__eth_client.get_transaction_params())
            try:
                txn_receipt = self.__eth_client.execute_transaction(txn_dict)
                return SUCCESS
            except Exception as e:
                logging.error("Exception occurred when trying to execute " +
                              "workOrderSubmit transaction on chain " + str(e))
                return ERROR
        else:
            logging.error("Work order contract instance is not initialized")
            return ERROR

    def work_order_complete(self, work_order_id, work_order_response):
        """
        This function is called by the Worker Service to
        complete a work order successfully or in error.
        This API is for the proxy model.

        Parameters:
        work_order_id       Unique ID to identify the work order request
        work_order_response Work order response data in a string

        Returns:
        errorCode           0 on success or non-zero on error.
        """
        if (self.__contract_instance is not None):

            txn_dict = self.__contract_instance.functions.workOrderComplete(
                work_order_id, work_order_response).buildTransaction(
                    self.__eth_client.get_transaction_params())
            try:
                txn_receipt = self.__eth_client.execute_transaction(txn_dict)
                return SUCCESS
            except Exception as e:
                logging.error("Execption occurred when trying to execute " +
                              "workOrderComplete transaction on chain " +
                              str(e))
                return ERROR
        else:
            logging.error("Work order contract instance is not initialized")
            return ERROR

    def work_order_get_result(self, work_order_id, id=None):
        """
        Query blockchain to get a work order result.
        This function starts an event handler for handling the
        workOrderCompleted event from the Ethereum blockchain.

        Parameters:
        work_order_id Work Order ID that was sent in the
                      corresponding work_order_submit request
        id            Optional JSON RPC request ID

        Returns:
        Tuple containing work order status, worker id, work order request,
        work order response, and error code.
        None on error.
        """
        logging.info("About to start Ethereum event handler")

        # Start an event listener that listens for events from the proxy
        # blockchain, extracts response payload from there and passes it
        # on to the requestor

        w3 = BlockchainInterface(self._config)

        contract = self.__contract_instance_evt
        # Listening only for workOrderCompleted event now
        listener = w3.newListener(contract, "workOrderCompleted")

        try:
            daemon = EventProcessor(self._config)
            # Wait for the workOrderCompleted event after starting the
            # listener and handler
            event = asyncio.get_event_loop()\
                .run_until_complete(daemon.get_event_synchronously(
                    listener, is_wo_id_in_event, wo_id=work_order_id))

            # Get the first element as this is a list of one event
            # obtained from gather() in ethereum_listener
            work_order_response = event[0]["args"]["workOrderResponse"]

            return json.loads(work_order_response)
        except KeyboardInterrupt:
            asyncio.get_event_loop().run_until_complete(daemon.stop())

    def encryption_key_retrieve(self,
                                worker_id,
                                last_used_key_nonce,
                                tag,
                                requester_id,
                                signature_nonce=None,
                                signature=None,
                                id=None):
        """
        Get Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_start(self, tag, id=None):
        """
        Inform the Worker that it should start
        encryption key generation for this requester.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_set(self,
                           worker_id,
                           encryption_key,
                           encryption_nonce,
                           tag,
                           signature,
                           id=None):
        """
        Set Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass

    def encryption_key_get(self,
                           worker_id,
                           requester_id,
                           last_used_key_nonce=None,
                           tag=None,
                           signature_nonce=None,
                           signature=None,
                           id=None):
        """
        Get Encryption Key Request Payload.
        Not supported for Ethereum.
        """
        pass