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 """ 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. """ 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 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_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 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): 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 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 start_work_order_completed_event_handler(self, evt_handler, show_decrypted_output, verification_key, session_key, session_iv): """ Function to start event handler for handling workOrderCompleted event from Ethereum blockchain """ 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) asyncio.get_event_loop().run_until_complete( daemon.start(listener, evt_handler, show_decrypted_output, verification_key, session_key, session_iv)) 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 """ 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 def work_order_get_result(self, work_order_id, id=None): """ Get worker order result """ 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 """ pass
class EthereumWorkerRegistryListImpl(WorkerRegistryList): """ This class provide APIs to read/write registry entries of workers, which is stored in 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 It returns following 1. totalCount is the 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. 2. lookupTag is an 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. 3. ids is an array of the registry organization ids that match the input parameters Returns tuple containing count, lookup tag and list of organization ids on success and 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 It returns tuple containing following on success. 1. uri is string defines a URI for this registry that supports the Off-Chain Worker Registry JSON RPC API. It is going to be None for proxy model. 2. sc_addr Ethereum address for worker registry smart contract address. 3. application_type_ids list of application ids(array of byte[]) 4. 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): """ Getting Additional Registry Lookup Results This function is called to retrieve additional results of the Registry lookup initiated by the registryLookUp call. Inputs 1. application_type_id is an application type that has to be supported by the workers retrieved. 2. lookup_tag is returned by a previous call to either this function or to registryLookUp. Outputs tuple on success containing the below. 1. total_count is a 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 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 registry ids that can be retrieved by calling this function again with this tag as an input parameter. 3. ids is an 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. Returns false if validation fails and true if it success """ 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): """ Adding a new registry Inputs 1. organization_id bytes[] identifies organization that hosts the registry, e.g. a bank in the consortium or anonymous entity. 2. uri string defines a URI for this registry that supports the Off-Chain Worker Registry JSON RPC API. 3. sc_addr bytes[] defines an Ethereum address that runs the Worker Registry Smart Contract API smart contract for this registry. 4. 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 txn_dict = self.__contract_instance.functions.registryAdd( org_id, uri, org_id, app_type_ids).buildTransaction( self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) 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 Inputs 1. organization_id bytes[] identifies organization that hosts the registry, e.g. a bank in the consortium or anonymous entity. 2. uri string defines a URI for this registry that supports the Off-Chain Worker Registry JSON RPC API. 3. sc_addr bytes[] defines an Ethereum address that runs a Worker Registry Smart Contract API smart contract for this registry. 4. application_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 txn_dict = \ self.__contract_instance.functions.registryUpdate( org_id, uri, sc_addr, app_type_ids).buildTransaction( self.__eth_client.get_transaction_params() ) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "direct registry contract instance is not initialized") return None def registry_set_status(self, org_id, status): """ Setting Registry Status Inputs 1. organization_id bytes[] identifies organization that hosts the registry 2. status defines registry status to set. The currently defined values are: 1 - indicates that the registry is active 2 - indicates that the registry is "off-line" (temporarily) 3 - indicates that 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 txn_dict = \ self.__contract_instance.functions.registrySetStatus( org_id, status.value).buildTransaction( self.__eth_client.get_transaction_params() ) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "direct registry contract instance is not initialized") return None
class EthereumWorkerRegistryImpl(WorkerRegistry): """ This class is meant to set/get worker related information to/from 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 worker_lookup(self, worker_type, org_id, application_id, id=None): """ Initiating Worker lookup This function retrieves a list of Worker ids that match the input parameters. The Worker must match all input parameters (AND mode) to be included in the list. 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. All input parameters are optional and can be provided in any combination to select Workers. Inputs 1. worker_type is a characteristic of Workers for which you may wish to search 2. organization_id is an id of an organization that can be used to search for one or more Workers that belong to this organization 3. application_type_id is an application type that is supported by the Worker Returns 1. total_count is a total number of entries matching a specified lookup criteria. If this number is bigger than size of ids array, the caller should use lookupTag to call workerLookUpNext to retrieve the rest of the ids. 2. lookup_tag is an optional parameter. If it is returned, it means that there are more matching Worker ids that can be retrieved by calling function workerLookUpNext with this tag as an input parameter. 3. ids is an array of the Worker ids that match the input parameters. """ 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 lookup_result = self.__contract_instance.functions.workerLookUp( worker_type.value, org_id, application_id).call() return _convert_lookup_result_to_json(lookup_result) else: logging.error( "worker registry contract instance is not initialized") return None def worker_retrieve(self, worker_id, id=None): """ Retrieve worker by worker id Inputs 1. worker_id is the id of the registry whose details are requested. Returns: The same as the input parameters to the corresponding call to worker_register() plus status as defined in worker_set_status. """ 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 worker_details = self.__contract_instance.functions.workerRetrieve( worker_id).call() return _convert_retrieve_result_to_json(worker_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): """ 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. Returns: 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. """ 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): """ Registering a New 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 None - if registration does not succeed Transaction receipt - if registration succeeds """ 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 """ txn_dict = self.__contract_instance.functions.workerRegister( worker_id, worker_type.value, organization_id, application_type_ids, details)\ .buildTransaction( self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction( txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None def worker_update(self, worker_id, details): """ 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 """ 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 """ txn_dict = self.__contract_instance.functions.workerUpdate( worker_id, details)\ .buildTransaction(self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None def worker_set_status(self, worker_id, status): """ Set the worker status identified by worker id Inputs 1. worker_id is a worker id 2. status defines Worker status. The currently defined values are: 1 - indicates that the worker is active 2 - indicates that the worker is "off-line" (temporarily) 3 - indicates that the worker is decommissioned 4 - indicates that the worker is compromised """ 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 txn_dict = self.__contract_instance.functions.workerSetStatus( worker_id, status.value)\ .buildTransaction(self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None 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): """ 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"]["proxy_worker_registry_contract_file"] contract_address = \ config["ethereum"]["proxy_worker_registry_contract_address"] self.__contract_instance, self.__contract_instance_evt = \ self.__eth_client.get_contract_instance( contract_file_name, contract_address )
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 lookup_result = self.__contract_instance.functions.workerLookUp( worker_type.value, org_id, application_id).call() return _convert_lookup_result_to_json(lookup_result) 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): if not is_valid_hex_str(worker_id): logging.error("Invalid worker id {}".format(worker_id)) return None worker_details = self.__contract_instance.functions.workerRetrieve( worker_id).call() return _convert_retrieve_result_to_json(worker_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 """ txn_dict = self.__contract_instance.functions.workerRegister( worker_id, worker_type.value, organization_id, application_type_ids, details)\ .buildTransaction( self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None 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 """ txn_dict = self.__contract_instance.functions.workerUpdate( worker_id, details)\ .buildTransaction(self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None 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 txn_dict = self.__contract_instance.functions.workerSetStatus( worker_id, status.value)\ .buildTransaction(self.__eth_client.get_transaction_params()) txn_receipt = self.__eth_client.execute_transaction(txn_dict) return txn_receipt else: logging.error( "worker registry contract instance is not initialized") return None 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"]["proxy_worker_registry_contract_file"] contract_address = \ config["ethereum"]["proxy_worker_registry_contract_address"] self.__contract_instance, self.__contract_instance_evt = \ self.__eth_client.get_contract_instance( contract_file_name, contract_address )
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
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
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