def __init__(self, config): self._config = config self._eth_client = EthereumWrapper(config) tcf_home = os.environ.get("TCF_HOME", "../../") worker_reg_contract_file = tcf_home + "/" + \ config["ethereum"]["worker_registry_contract_file"] worker_reg_contract_address = \ config["ethereum"]["worker_registry_contract_address"] self._worker_reg_contract_instance,\ self._worker_reg_contract_instance_evt = self._eth_client\ .get_contract_instance( worker_reg_contract_file, worker_reg_contract_address) self._worker_registry = EthereumWorkerRegistryImpl(config) work_order_contract_file = tcf_home + "/" + \ config["ethereum"]["work_order_contract_file"] work_order_contract_address = \ config["ethereum"]["work_order_contract_address"] self._work_order_contract_instance,\ self._work_order_contract_instance_evt = self._eth_client\ .get_contract_instance( work_order_contract_file, work_order_contract_address) self._work_order_proxy = EthereumWorkOrderProxyImpl(config) # List of active available worker ids in Avalon self.__worker_ids = []
def __init__(self, config, blockchain_type): super().__init__() self._config = config if blockchain_type.lower() == 'fabric': self._worker_instance = FabricWorkerRegistryImpl(self._config) self._work_order_instance = FabricWorkOrderImpl(self._config) elif blockchain_type.lower() == 'ethereum': self._worker_instance = EthereumWorkerRegistryImpl(self._config) self._work_order_instance = EthereumWorkOrderProxyImpl( self._config) else: logging.error("Invalid blockchain type")
def __init__(self, config_file): super(TestEthereumWorkerRegistryImpl, self).__init__() if not path.isfile(config_file): raise FileNotFoundError("File not found at path: {0}".format( path.realpath(config_file))) try: with open(config_file) as fd: self.__config = toml.load(fd) except IOError as e: if e.errno != errno.ENOENT: raise Exception("Could not open config file: %s", e) self.__eth_conn = EthereumWorkerRegistryImpl(self.__config)
def _create_worker_registry_instance(blockchain_type, config): # create worker registry instance for direct/proxy model if constants.proxy_mode and blockchain_type == 'fabric': return FabricWorkerRegistryImpl(config) elif constants.proxy_mode and blockchain_type == 'ethereum': return EthereumWorkerRegistryImpl(config) else: logger.info("Direct SDK code path\n") return JRPCWorkerRegistryImpl(config)
def main(args=None): parser = argparse.ArgumentParser() parser.add_argument( "-c", "--config", help="The config file containing the Ethereum contract information", type=str) parser.add_argument("-u", "--uri", help="Direct API listener endpoint", default="http://avalon-listener:1947", type=str) options = parser.parse_args(args) config = _parse_config_file(options.config) if config is None: logging.error("\n Error in parsing config file: {}\n".format( options.config)) sys.exit(-1) # Http JSON RPC listener uri uri = options.uri if uri: config["tcf"]["json_rpc_uri"] = uri logging.info("About to start Ethereum connector service") eth_client = EthereumWrapper(config) worker_reg_contract_file = TCF_HOME + "/" + \ config["ethereum"]["worker_registry_contract_file"] worker_reg_contract_address = \ config["ethereum"]["worker_registry_contract_address"] worker_reg_contract_instance,\ worker_reg_contract_instance_evt = eth_client\ .get_contract_instance( worker_reg_contract_file, worker_reg_contract_address) worker_registry = EthereumWorkerRegistryImpl(config) work_order_contract_file = TCF_HOME + "/" + \ config["ethereum"]["work_order_contract_file"] work_order_contract_address = \ config["ethereum"]["work_order_contract_address"] work_order_contract_instance,\ wo_contract_instance_evt = eth_client\ .get_contract_instance( work_order_contract_file, work_order_contract_address) work_order_proxy = EthereumWorkOrderProxyImpl(config) eth_connector_svc = EthereumConnector(config, None, worker_registry, work_order_proxy, None, wo_contract_instance_evt) eth_connector_svc.start()
def _create_worker_registry_instance(blockchain_type, config): # create worker registry instance for direct/proxy model if blockchain_type == 'ethereum': return EthereumWorkerRegistryImpl(config) else: return JRPCWorkerRegistryImpl(config)
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"]["worker_registry_contract_file"] worker_reg_contract_address = \ config["ethereum"]["worker_registry_contract_address"] self._worker_reg_contract_instance,\ self._worker_reg_contract_instance_evt = self._eth_client\ .get_contract_instance( worker_reg_contract_file, worker_reg_contract_address) self._worker_registry = EthereumWorkerRegistryImpl(config) work_order_contract_file = tcf_home + "/" + \ config["ethereum"]["work_order_contract_file"] work_order_contract_address = \ config["ethereum"]["work_order_contract_address"] self._work_order_contract_instance,\ self._work_order_contract_instance_evt = self._eth_client\ .get_contract_instance( work_order_contract_file, work_order_contract_address) self._work_order_proxy = EthereumWorkOrderProxyImpl(config) # List of active available worker ids in Avalon self.__worker_ids = [] def _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("\nWorker lookup response from kv storage : {}\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 in kv storage") else: logging.error("Failed to lookup worker in kv storage") 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 from kv storage: {}".format( json.dumps(worker_info, indent=4))) if "error" in worker_info: logging.error("Unable to retrieve worker details from kv storage") return worker_info["result"] def _add_update_worker_to_chain(self, wids_onchain, jrpc_worker_registry): """ This function adds/updates a worker in the Ethereum blockchain """ for wid in self.__worker_ids: worker_info = self._retrieve_worker_details_from_kv_storage( jrpc_worker_registry, wid) worker_id = wid worker_type = WorkerType(worker_info["workerType"]) org_id = worker_info["organizationId"] app_type_id = 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 self.__worker_ids: worker_id = wid worker = self._worker_registry\ .worker_retrieve(wid, random.randint(0, 100000)) worker_status_onchain = worker["result"]["status"] # If worker is not already decommissioned, mark it # decommissioned 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, self._config["WorkerConfig"]["OrganizationId"], self._config["WorkerConfig"]["ApplicationTypeId"], jrpc_req_id) logging.info("Worker 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 in ethereum blockchain") else: logging.error("Failed to lookup worker in ethereum blockchain") 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) logging.info("About to submit work order to kv storage") 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))) if response and 'error' in response and \ response['error']['code'] == \ WorkOrderStatus.PENDING.value: # get the work order result 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))) # With Synchronous work order processing work order submit # return result elif response and 'result' in response: return response else: return None def _add_work_order_result_to_chain(self, work_order_id, response): """ This function adds a work order result to the Ethereum blockchain """ result = self._work_order_proxy.work_order_complete( work_order_id, 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"] logging.info("Received event from blockchain") if worker_id in self.__worker_ids: response = self\ ._submit_work_order_and_get_result(work_order_id, worker_id, requester_id, work_order_params) if response: self._add_work_order_result_to_chain(work_order_id, response) else: logging.info("Work order submit failed") 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() self.__worker_ids = self._lookup_workers_in_kv_storage( jrpc_worker_registry) self._add_update_worker_to_chain(worker_ids_onchain, 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())
class ProxyModelGenericClient(BaseGenericClient): """ Generic client class to test end to end test for direct and proxy model. """ def __init__(self, config, blockchain_type): super().__init__() self._config = config if blockchain_type.lower() == 'fabric': self._worker_instance = FabricWorkerRegistryImpl(self._config) self._work_order_instance = FabricWorkOrderImpl(self._config) elif blockchain_type.lower() == 'ethereum': self._worker_instance = EthereumWorkerRegistryImpl(self._config) self._work_order_instance = EthereumWorkOrderProxyImpl( self._config) else: logging.error("Invalid blockchain type") def get_worker_details(self, worker_id): """ Fetch worker details for given worker id """ status, _, _, _, details = self._worker_instance.worker_retrieve( worker_id) logging.info("\n Worker retrieve result: Worker status {}" " details : {}\n".format(status, json.dumps(details, indent=4))) if status == WorkerStatus.ACTIVE.value: # Initializing Worker Object worker_obj = SGXWorkerDetails() worker_obj.load_worker(json.loads(details)) return worker_obj else: logging.error("Worker is not active") return None def submit_work_order(self, wo_params): """ Submit work order request """ wo_submit_res = self._work_order_instance.work_order_submit( wo_params.get_work_order_id(), wo_params.get_worker_id(), wo_params.get_requester_id(), wo_params.to_string()) return wo_submit_res == ContractResponse.SUCCESS, wo_submit_res def get_work_order_result(self, work_order_id): """ Retrieve work order result for given work order id """ wo_get_result = self._work_order_instance.work_order_get_result( work_order_id) logging.info("Work order result {}".format(wo_get_result)) if wo_get_result and "result" in wo_get_result: return True, wo_get_result return False, wo_get_result def create_work_order_receipt(self, wo_params, client_private_key): # TODO Need to be implemented pass def retrieve_work_order_receipt(self, wo_id): # TODO Need to be implemented pass def verify_receipt_signature(self, receipt_update_retrieve): # TODO Need to be implemented pass
class TestEthereumWorkerRegistryImpl(unittest.TestCase): def __init__(self, config_file): super(TestEthereumWorkerRegistryImpl, self).__init__() if not path.isfile(config_file): raise FileNotFoundError("File not found at path: {0}".format( path.realpath(config_file))) try: with open(config_file) as fd: self.__config = toml.load(fd) except IOError as e: if e.errno != errno.ENOENT: raise Exception("Could not open config file: %s", e) self.__eth_conn = EthereumWorkerRegistryImpl(self.__config) def test_worker_register(self): self.__worker_id = urandom(32).hex() self.__worker_type = WorkerType.TEE_SGX self.__details = json.dumps( {"workOrderSyncUri": "http://worker-order:8008"}) self.__org_id = urandom(32).hex() self.__application_ids = [ urandom(32).hex(), urandom(32).hex(), urandom(32).hex() ] logging.info( "Calling worker_register contract..\n worker_id: %s\n " + "worker_type: %d\n " + "orgId: %s\n applicationIds %s\n details %s", self.__worker_id, self.__worker_type.value, self.__org_id, self.__application_ids, self.__details) result = self.__eth_conn.worker_register(self.__worker_id, self.__worker_type, self.__org_id, self.__application_ids, self.__details) logging.info("worker_register status \n %s", result) self.assertIsNotNone(result, "transaction execution failed") def test_worker_set_status(self): self.__status = WorkerStatus.DECOMMISSIONED logging.info( "Calling worker_set_status..\n worker_id: %s\n status: %d", self.__worker_id, self.__status.value) result = self.__eth_conn.worker_set_status(self.__worker_id, self.__status) logging.info("worker_set_status status \n%s", result) self.assertIsNotNone(result, "worker set status response not matched") def test_worker_update(self): self.__new_details = json.dumps({ "torkOrderSyncUri": "http://worker-order:8008", "workOrderNotifyUri": "http://worker-order-notify:9909" }) logging.info("Calling worker_update..\n worker_id: %s\n details: %s", self.__worker_id, self.__new_details) result = self.__eth_conn.worker_update(self.__worker_id, self.__new_details) logging.info("worker_update status \n %s", result) self.assertIsNotNone(result, "worker update response not matched") def test_worker_lookup(self): logging.info( "Calling worker_lookup..\n worker_type: %d\n orgId: %s\n " + "applicationId: %s", self.__worker_type.value, self.__org_id, self.__application_ids[0]) result = self.__eth_conn.worker_lookup( self.__worker_type, self.__org_id, self.__application_ids[0])["result"] logging.info("worker_lookup status [%d, %s, %s]", result["totalCount"], result["lookupTag"], result["ids"]) match = self.__worker_id in result["ids"] self.assertEqual(result["totalCount"], 1, "Worker lookup response should match") self.assertTrue(match, "Worker lookup response worker id doesn't match") def test_worker_retrieve(self): logging.info("Calling worker_retrieve..\n worker_id: %s", self.__worker_id) result = self.__eth_conn.worker_retrieve(self.__worker_id)["result"] logging.info("worker_retrieve status [%d, %s, %s, %s, %s]", result["status"], result["workerType"], result["organizationId"], result["applicationTypeId"], result["details"]) self.assertEqual(result["workerType"], self.__worker_type.value, "Worker retrieve response worker type doesn't match") self.assertEqual( result["organizationId"], self.__org_id, "Worker retrieve response organization id doesn't match") self.assertEqual( result["applicationTypeId"][0], self.__application_ids[0], "Worker retrieve response application id[0] doesn't match") self.assertEqual( result["applicationTypeId"][1], self.__application_ids[1], "Worker retrieve response application id[1] doesn't match") self.assertEqual( result["details"], json.loads(self.__new_details), "Worker retrieve response worker details doesn't match") self.assertEqual( result["status"], self.__status.value, "Worker retrieve response worker status doesn't match") def test_worker_lookup_next(self): lookUpTag = "" logging.info( "Calling worker_lookup_next..\n worker_type: %d\n" + "orgId: %s\n applicationId:%s\n lookUpTag: %s", self.__worker_type.value, self.__org_id, self.__application_ids[0], lookUpTag) result = self.__eth_conn.worker_lookup_next(self.__worker_type, self.__org_id, self.__application_ids[0], lookUpTag) logging.info(result) logging.info("worker_lookup_next status [%d, %s, %s]", result[0], result[1], result[2]) self.assertEqual(result[0], 0, "worker_lookup_next response count doesn't match")