Exemplo n.º 1
0
    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"]["proxy_worker_registry_contract_file"]
        worker_reg_contract_address = \
            config["ethereum"]["proxy_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)
Exemplo n.º 2
0
class EthereumWorkerRegistryImpl(WorkerRegistry):
    """
    This class provide worker APIs which interact with
    Ethereum blockchain. Detail method description will be
    available in interface
    """
    def __init__(self, config):
        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):
        """
        Lookup a worker identified worker_type, org_id and application_id
        all fields are optional and if present condition should match for all
        fields. If none passed it should return all workers.
        Returns tuple containing workers count, lookup tag and list of
        worker ids or on error returns None.
        """
        if (self.__contract_instance is not None):
            if not isinstance(worker_type, WorkerType):
                logging.info("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")):
                logging.info("Invalid organization id {}".format(org_id))
                return None
            if not is_valid_hex_str(
                    binascii.hexlify(application_id).decode("utf8")):
                logging.info(
                    "Invalid application id {}".format(application_id))
                return None
            lookupResult = self.__contract_instance.functions.workerLookUp(
                worker_type.value, org_id, application_id).call()
            return lookupResult
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_retrieve(self, worker_id):
        """
        Retrieve the worker identified by worker id
        Returns tuple containing worker status, worker type,
        organization id, list of application ids and worker
        details(json string)
        On error returns None
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(worker_id).decode("utf8")):
                logging.info("Invalid worker id {}".format(worker_id))
                return None

            workerDetails = self.__contract_instance.functions.workerRetrieve(
                worker_id).call()
            return workerDetails
        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):
        """
        Getting Additional Worker Lookup Results
        Inputs
        1. worker_type is a characteristic of Workers for which you may wish
        to search.
        2. organization_id is an organization to which a Worker belongs.
        3. application_type_id is an application type that has to be supported
        by the Worker.
        4. lookup_tag is returned by a previous call to either this function
        or to worker_lookup.
        5. id is used for json rpc request

        Outputs tuple containing following.
        1. total_count is a 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 workerLookUpNext to
        retrieve the rest of the ids.
        2. new_lookup_tag is an optional parameter. If it is returned, it
        means that there are more matching Worker ids than can be retrieved
        by calling this function again with this tag as an input parameter.
        3. ids is an 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.info("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(binascii.hexlify(org_id).decode("utf")):
                logging.info("Invalid organization id {}".format(org_id))
                return None
            if not is_valid_hex_str(
                    binascii.hexlify(application_id).decode("utf8")):
                logging.info("Invalid application id {}".format(org_id))
                return None
            lookupResult = self.__contract_instance.functions.workerLookUpNext(
                worker_type.value, org_id, application_id, lookup_tag).call()
            return lookupResult
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def __validate(self, config):
        """
        validates parameter from config parameters for existence.
        Returns false if validation fails and true if it success
        """
        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.
        """
        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.__eth_client.get_contract_instance(
            contract_file_name, contract_address)

    def worker_register(self, worker_id, worker_type, org_id, application_ids,
                        details):
        """
        Register new worker with details of worker
        Inputs
        1. worker_id is a worker id, e.g. an Ethereum address or
        a value derived from the worker's DID.
        2. worker_type defines the type of Worker. Currently defined types are:
            1. indicates "TEE-SGX": an Intel SGX Trusted Execution Environment
            2. indicates "MPC": Multi-Party Compute
            3. indicates "ZK": Zero-Knowledge
        3. organization_id is an optional parameter representing the
        organization that hosts the Worker, e.g. a bank in the consortium or
        anonymous entity.
        4. application_type_ids is an optional parameter that defines
        application types supported by the Worker.
        5. details is detailed information about the worker in JSON format as
        defined in
        https://entethalliance.github.io/trusted-computing/spec.html
        #common-data-for-all-worker-types
        Returns transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(worker_id).decode("utf8")):
                logging.info("Invalid worker id {}".format(worker_id))
                return None
            if not isinstance(worker_type, WorkerType):
                logging.info("Invalid workerType {}".format(worker_type))
                return None
            if not is_valid_hex_str(binascii.hexlify(org_id).decode("utf8")):
                logging.info("Invalid organization id {}".format(org_id))
                return None
            for aid in application_ids:
                if not is_valid_hex_str(binascii.hexlify(aid).decode("utf8")):
                    logging.info("Invalid application id {}".format(aid))
                    return None
            if details is not None:
                worker = WorkerDetails()
                is_valid = worker.validate_worker_details(details)
                if is_valid is not None:
                    return None

            txn_statusn_hash = \
                self.__contract_instance.functions.workerRegister(
                    worker_id, worker_type.value, org_id, application_ids,
                    details).buildTransaction(
                        self.__eth_client.get_transaction_params()
                        )
            txn_status = self.__eth_client.execute_transaction(
                txn_statusn_hash)
            return txn_status
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_set_status(self, worker_id, status):
        """
        Set the registry status identified by worker id
        status is worker type enum type
        Returns transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(worker_id).decode("utf8")):
                logging.info("Invalid worker id {}".format(worker_id))
                return None
            if not isinstance(status, WorkerStatus):
                logging.info("Invalid worker status {}".format(status))
                return None
            txn_statusn_hash = \
                self.__contract_instance.functions.workerSetStatus(
                    worker_id,
                    status.value).buildTransaction(
                        self.__eth_client.get_transaction_params()
                        )
            txn_status = self.__eth_client.execute_transaction(
                txn_statusn_hash)
            return txn_status
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None

    def worker_update(self, worker_id, details):
        """
        Update the worker with details data which is json string
        Updating a Worker
        Inputs
        1. worker_id is a worker id, e.g. an Ethereum address or
        a value derived from the worker's DID.
        2. details is detailed information about the worker in JSON format
        Returns transaction receipt on success or None on error.
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(worker_id).decode("utf8")):
                logging.error("Invalid worker id {}".format(worker_id))
                return None
            if details is not None:
                worker = WorkerDetails()
                is_valid = worker.validate_worker_details(details)
                if is_valid is not None:
                    logging.error(is_valid)
                    return None
            txn_statusn_hash = self.__contract_instance.functions.workerUpdate(
                worker_id, details).buildTransaction(
                    self.__eth_client.get_transaction_params())
            txn_status = self.__eth_client.execute_transaction(
                txn_statusn_hash)
            return txn_status
        else:
            logging.error(
                "worker registry contract instance is not initialized")
            return None
Exemplo n.º 3
0
class EthereumWorkOrderProxyImpl(WorkOrderProxy):
    """
    This class is meant to write work-order related data to Ethereum
    blockchain. Detailed method description is available in the interface
    """
    def __init__(self, config):
        if self.__validate(config) is True:
            self.__initialize(config)
        else:
            raise Exception("Invalid configuration parameter")

    def __validate(self, config):
        """
        Validates config parameters for existence.
        Returns false if validation fails and true if it succeeds
        """
        if config["ethereum"]["proxy_work_order_contract_file"] is None:
            logging.error("Missing work order contract file path!!")
            return False
        if config["ethereum"]["proxy_work_order_contract_address"] is None:
            logging.error("Missing work order 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"]["proxy_work_order_contract_file"]
        contract_address = \
            config["ethereum"]["proxy_work_order_contract_address"]
        self.__contract_instance = self.__eth_client.get_contract_instance(
            contract_file_name, contract_address)

    def _is_valid_work_order_json(self, work_order_id, worker_id, requester_id,
                                  work_order_request):
        """
        Validate following fields in JSON request against the ones
        provided outside the JSON - workOrderId, workerId, requesterId
        """
        json_request = json.load(work_order_request)
        if (work_order_id == json_request.get("workOrderId")
                and worker_id == json_request.get("workerId")
                and requester_id == json_request.get("requesterId")):
            return True
        else:
            return False

    def work_order_submit(self,
                          work_order_id,
                          worker_id,
                          requester_id,
                          work_order_request,
                          id=None):
        """
        Submit work order request
        work_order_id is a unique id to identify the work order request
        worker_id is the identifier for the worker
        requester_id is a unique id to identify the requester
        work_order_request is a json string(Complete definition at
        work_order.py interface file)
        Returns
            An error code, 0 - success, otherwise an error.
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(work_order_id).decode("utf8")):
                logging.error("Invalid work order id {}".format(work_order_id))
                return ERROR

            if not is_valid_hex_str(
                    binascii.hexlify(worker_id).decode("utf8")):
                logging.error("Invalid worker id {}".format(worker_id))
                return ERROR

            if not is_valid_hex_str(
                    binascii.hexlify(requester_id).decode("utf8")):
                logging.error("Invalid requester id {}".format(requester_id))
                return ERROR

            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 Execption as e:
                logging.error(
                    "execption occured 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 proxy model.
        params
            work_order_id is unique id to identify the work order request
            work_order_response is the Work Order response data in string
        Returns
            An error code, 0 - success, otherwise an error.
        """
        if (self.__contract_instance is not None):
            if not is_valid_hex_str(
                    binascii.hexlify(work_order_id).decode("utf8")):
                logging.error("Invalid work order id {}".format(work_order_id))
                return ERROR
            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 Execption as e:
                logging.error("execption occured 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 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
        """
        pass

    def encryption_key_start(self, tag, id=None):
        """
        Function to inform the Worker that it should start
        encryption key generation for this requester.
        """
        pass

    def encryption_key_set(self,
                           worker_id,
                           encryption_key,
                           encryption_nonce,
                           tag,
                           signature,
                           id=None):
        """
        Set Encryption Key Request Payload
        """
        pass
Exemplo n.º 4
0
class EthereumConnector:
    """
    This class is the bridge between the Ethereum blockchain and the Avalon
    core. It listens for events generated by the Ethereum blockchain.
    It handles event data corresponding to the event (eg: workOrderSubmitted
    and submits requests to Avalon on behalf of the client. The service also
    invokes smart contract APIs (eg: workOrderComplete).
    """
    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"]["proxy_worker_registry_contract_file"]
        worker_reg_contract_address = \
            config["ethereum"]["proxy_worker_registry_contract_address"]
        self._worker_reg_contract_instance = self._eth_client\
            .get_contract_instance(
                worker_reg_contract_file, worker_reg_contract_address)

        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._eth_client\
            .get_contract_instance(
                work_order_contract_file, work_order_contract_address)

    def _retrieve_first_worker_details(self):
        """
        This function retrieves the first worker from shared kv using
        worker_lookup direct API.
        Returns details of worker
        """
        jrpc_req_id = random.randint(0, 100000)
        worker_registry = JRPCWorkerRegistryImpl(self._config)

        # Get first worker from worker registry
        worker_id = None
        worker_lookup_result = worker_registry.worker_lookup(
            worker_type=WorkerType.TEE_SGX, id=jrpc_req_id)
        logging.info("\n Worker lookup response: {}\n".format(
            json.dumps(worker_lookup_result, indent=4)))
        if "result" in worker_lookup_result and \
                "ids" in worker_lookup_result["result"].keys():
            if worker_lookup_result["result"]["totalCount"] != 0:
                worker_id = worker_lookup_result["result"]["ids"][0]
            else:
                logging.error("No workers found")
                worker_id = None
        else:
            logging.error("Failed to lookup worker")
            worker_id = None
        if worker_id is None:
            logging.error("Unable to get a worker")
            sys.exit(-1)

        # Retrieve worker details
        jrpc_req_id += 1
        worker_info = worker_registry.worker_retrieve(worker_id, jrpc_req_id)
        logging.info("Worker retrieve response: {}".format(
            json.dumps(worker_info)))

        if "error" in worker_info:
            logging.error("Unable to retrieve worker details\n")
            sys.exit(1)
        return worker_id, worker_info["result"]

    def _add_worker_to_chain(self, worker_id, worker_info):
        """
        This function adds a worker to the Ethereum blockchain
        """
        worker_id = web3.Web3.toBytes(hexstr=worker_id)
        worker_type = worker_info["workerType"]
        org_id = web3.Web3.toBytes(hexstr=worker_info["organizationId"])
        app_type_ids = web3.Web3.toBytes(
            hexstr=worker_info["applicationTypeId"])
        details = json.dumps(worker_info["details"])

        txn_dict = self._worker_reg_contract_instance.functions.workerRegister(
            worker_id, worker_type, org_id, [app_type_ids], details)\
            .buildTransaction(self._eth_client.get_transaction_params())
        try:
            txn_receipt = self._eth_client.execute_transaction(txn_dict)
        except Exception as e:
            logging.error("Error while adding worker to ethereum" +
                          " blockchain : " + str(e))

    def _submit_work_order_and_get_result(self, work_order_id, worker_id,
                                          requester_id, work_order_params):
        """
        This function submits work order using work_order_submit direct API
        """
        work_order_impl = JRPCWorkOrderImpl(self._config)
        response = work_order_impl\
            .work_order_submit(work_order_id, worker_id, requester_id,
                               work_order_params, id=random.randint(0, 100000))
        logging.info("Work order submit response : {}".format(
            json.dumps(response, indent=4)))

        work_order_result = work_order_impl\
            .work_order_get_result(work_order_id,
                                   id=random.randint(0, 100000))

        logging.info("Work order get result : {} ".format(
            json.dumps(work_order_result, indent=4)))

        return work_order_result

    def _add_work_order_result_to_chain(self, work_order_id, response):
        """
        This function adds a work order result to the Ethereum blockchain
        """

        txn_dict = self._work_order_contract_instance.functions\
            .workOrderComplete(web3.Web3.toBytes(hexstr=work_order_id),
                               json.dumps(response))\
            .buildTransaction(self._eth_client.get_transaction_params())
        try:
            txn_receipt = self._eth_client.execute_transaction(txn_dict)
        except Exception as e:
            logging.error("Error adding work order result to ethereum" +
                          " blockchain : " + str(e))

    def handleEvent(self, event, account, contract):
        """
        The function retrieves pertinent information from the event received
        and makes request to listener using Direct API and writes back result
        to the blockchain
        """
        work_order_request = json.loads(event["args"]["workOrderRequest"])

        work_order_id = work_order_request["workOrderId"]
        worker_id = work_order_request["workerId"]
        requester_id = work_order_request["requesterId"]
        work_order_params = event["args"]["workOrderRequest"]

        response = self\
            ._submit_work_order_and_get_result(work_order_id, worker_id,
                                               requester_id, work_order_params)
        self._add_work_order_result_to_chain(work_order_id, response)

    def start(self):
        logging.info("Ethereum Connector service started")

        # Fetch first worker details from shared KV (via direct API)
        # and add the worker to block chain.
        # TODO: Fetch all workers from shared KV and block chain
        # and do 2-way sync.
        worker_id, worker_info = self._retrieve_first_worker_details()
        self._add_worker_to_chain(worker_id, worker_info)

        # Start an event listener that listens for events from the proxy
        # blockchain, extracts request payload from there and make a request
        # to avalon-listener

        w3 = BlockchainInterface(self._config["ethereum"]["provider"])

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

        try:
            daemon = EventProcessor()
            asyncio.get_event_loop().run_until_complete(
                daemon.start(
                    listener,
                    self.handleEvent,
                    account=None,
                    contract=contract,
                ))
        except KeyboardInterrupt:
            asyncio.get_event_loop().run_until_complete(daemon.stop())
Exemplo n.º 5
0
class EthereumConnector:

    """
    This class is the bridge between the Ethereum blockchain and the Avalon
    core. It listens for events generated by the Ethereum blockchain.
    It handles event data corresponding to the event (eg: workOrderSubmitted
    and submits requests to Avalon on behalf of the client. The service also
    invokes smart contract APIs (eg: workOrderComplete).
    """

    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"]["proxy_worker_registry_contract_file"]
        worker_reg_contract_address = \
            config["ethereum"]["proxy_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)

    def _lookup_workers_in_kv_storage(self, worker_registry):
        """
        This function retrieves the worker ids from shared kv using
        worker_lookup direct API.
        Returns list of worker id
        """
        jrpc_req_id = random.randint(0, 100000)

        worker_lookup_result = worker_registry.worker_lookup(
            worker_type=WorkerType.TEE_SGX, id=jrpc_req_id
        )
        logging.info("\n Worker lookup response: {}\n".format(
            json.dumps(worker_lookup_result, indent=4)
        ))
        if "result" in worker_lookup_result and \
                "ids" in worker_lookup_result["result"].keys():
            if worker_lookup_result["result"]["totalCount"] != 0:
                return worker_lookup_result["result"]["ids"]
            else:
                logging.error("No workers found")
        else:
            logging.error("Failed to lookup worker")
        return []

    def _retrieve_worker_details_from_kv_storage(self, worker_registry,
                                                 worker_id):
        # Retrieve worker details
        jrpc_req_id = random.randint(0, 100000)
        worker_info = worker_registry.worker_retrieve(worker_id, jrpc_req_id)
        logging.info("Worker retrieve response: {}"
                     .format(json.dumps(worker_info)))

        if "error" in worker_info:
            logging.error("Unable to retrieve worker details\n")
        return worker_info["result"]

    def _add_update_worker_to_chain(self, wids_onchain, wids_kv,
                                    jrpc_worker_registry):
        """
        This function adds/updates a worker in the Ethereum blockchain
        """

        for wid in wids_kv:
            worker_info = self._retrieve_worker_details_from_kv_storage(
                jrpc_worker_registry, wid)
            worker_id = self._eth_client.get_bytes_from_hex(wid)
            worker_type = worker_info["workerType"]
            org_id = self._eth_client\
                .get_bytes_from_hex(worker_info["organizationId"])
            app_type_id = self._eth_client.get_bytes_from_hex(
                worker_info["applicationTypeId"])
            details = json.dumps(worker_info["details"])

            result = None
            if wid in wids_onchain:
                logging.info("Updating worker {} on ethereum blockchain"
                             .format(wid))
                result = self._worker_registry.worker_update(
                    worker_id, details)
            else:
                logging.info("Adding new worker {} to ethereum blockchain"
                             .format(wid))
                result = self._worker_registry.worker_register(
                    worker_id, worker_type, org_id, [app_type_id], details
                )
            if result is None:
                logging.error("Error while adding/updating worker to ethereum"
                              + " blockchain")

        for wid in wids_onchain:
            # Mark all stale workers on blockchain as decommissioned
            if wid not in wids_kv:
                worker_id = self._eth_client.get_bytes_from_hex(wid)
                worker = self._worker_registry\
                    .worker_retrieve(wid, random.randint(0, 100000))
                worker_status_onchain = worker["result"]["status"]
                # If worker is not already decommissoined, mark it decommission
                # as it is no longer available in the kv storage
                if worker_status_onchain != WorkerStatus.DECOMMISSIONED.value:
                    self._worker_registry.worker_set_status(
                        worker_id, WorkerStatus.DECOMMISSIONED)
                    logging.info("Marked worker "+wid+" as decommissioned on"
                                 + " ethereum blockchain")

    def _lookup_workers_onchain(self):
        """
        Lookup all workers on chain to sync up with kv storage
        """
        jrpc_req_id = random.randint(0, 100000)
        # TODO: Remove hardcoding and pass wild characters instead
        worker_lookup_result = self._worker_registry.worker_lookup(
            WorkerType.TEE_SGX,
            'aabbcc1234ddeeff',
            '11aa22bb33cc44dd',
            jrpc_req_id
        )
        logging.info("\nWorker lookup response from blockchain: {}\n".format(
            json.dumps(worker_lookup_result, indent=4)
        ))
        if "result" in worker_lookup_result and \
                "ids" in worker_lookup_result["result"].keys():
            if worker_lookup_result["result"]["totalCount"] != 0:
                return worker_lookup_result["result"]["ids"]
            else:
                logging.error("No workers found")
        else:
            logging.error("Failed to lookup worker")
        return []

    def _submit_work_order_and_get_result(self, work_order_id, worker_id,
                                          requester_id, work_order_params):
        """
        This function submits work order using work_order_submit direct API
        """
        work_order_impl = JRPCWorkOrderImpl(self._config)
        response = work_order_impl\
            .work_order_submit(work_order_id, worker_id, requester_id,
                               work_order_params, id=random.randint(0, 100000))
        logging.info("Work order submit response : {}"
                     .format(json.dumps(response, indent=4)))

        work_order_result = work_order_impl\
            .work_order_get_result(work_order_id,
                                   id=random.randint(0, 100000))

        logging.info("Work order get result : {} "
                     .format(json.dumps(work_order_result, indent=4)))

        return work_order_result

    def _add_work_order_result_to_chain(self, work_order_id, response):
        """
        This function adds a work order result to the Ethereum blockchain
        """
        work_order_id_bytes = self._eth_client\
            .get_bytes_from_hex(work_order_id)
        result = self._work_order_proxy.work_order_complete(
            work_order_id_bytes, json.dumps(response))
        if result == SUCCESS:
            logging.info("Successfully added work order result to blockchain")
        else:
            logging.error("Error adding work order result to blockchain")
        return result

    def handleEvent(self, event, account, contract):
        """
        The function retrieves pertinent information from the event received
        and makes request to listener using Direct API and writes back result
        to the blockchain
        """

        work_order_request = json.loads(event["args"]["workOrderRequest"])

        work_order_id = work_order_request["workOrderId"]
        worker_id = work_order_request["workerId"]
        requester_id = work_order_request["requesterId"]
        work_order_params = event["args"]["workOrderRequest"]

        response = self\
            ._submit_work_order_and_get_result(work_order_id, worker_id,
                                               requester_id,
                                               work_order_params)
        self._add_work_order_result_to_chain(work_order_id, response)

    def start(self):
        logging.info("Ethereum Connector service started")

        # Fetch first worker details from shared KV (via direct API)
        # and add the worker to block chain.
        # TODO: Fetch all workers from shared KV and block chain
        # and do 2-way sync.
        jrpc_worker_registry = JRPCWorkerRegistryImpl(self._config)
        worker_ids_onchain = self._lookup_workers_onchain()
        worker_ids_kv = self._lookup_workers_in_kv_storage(
            jrpc_worker_registry)

        self._add_update_worker_to_chain(worker_ids_onchain, worker_ids_kv,
                                         jrpc_worker_registry)

        # Start an event listener that listens for events from the proxy
        # blockchain, extracts request payload from there and make a request
        # to avalon-listener

        w3 = BlockchainInterface(self._config)

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

        try:
            daemon = EventProcessor(self._config)
            asyncio.get_event_loop().run_until_complete(daemon.start(
                listener,
                self.handleEvent,
                account=None,
                contract=contract,
            ))
        except KeyboardInterrupt:
            asyncio.get_event_loop().run_until_complete(daemon.stop())
Exemplo n.º 6
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.
        """
        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