def computeDelete(): """Deletes a workflow. --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of the documentId to verify that the consumer has rights to download the asset. type: string - name: documentId in: query description: The ID of the asset required: true type: string - name: consumerAddress in: query description: The consumer address. required: true type: string - name: jobId in: query description: JobId. type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Invalid asset data. 500: description: Error """ data = get_request_data(request) try: body = process_compute_request(data, user_nonce) response = requests_session.delete( get_compute_endpoint(), params=body, headers={'content-type': 'application/json'}) user_nonce.increment_nonce(body['owner']) return Response(response.content, response.status_code, headers={'content-type': 'application/json'}) except BadRequestError as e: return jsonify(error=str(e)), 400 except InvalidSignatureError as e: msg = f'Consumer signature failed verification: {e}' logger.error(msg, exc_info=1) return jsonify(error=msg), 401 except (ValueError, Exception) as e: logger.error(f'Error- {str(e)}', exc_info=1) return jsonify(error=f'Error : {str(e)}'), 500
def computeStop(): """Stop the execution of a workflow. --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this compute job/asset. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: documentId in: query description: The ID of the asset. If not provided, all currently running compute jobs will be stopped for the specified consumerAddress required: true type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: jobId in: query description: The ID of the compute job. If not provided, all running compute jobs of the specified consumerAddress/documentId are suspended type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification. 500: description: General server error """ data = get_request_data(request) try: body = process_compute_request(data) response = requests_session.put( get_compute_endpoint(), params=body, headers={"content-type": "application/json"}, ) increment_nonce(body["owner"]) return Response( response.content, response.status_code, headers={"content-type": "application/json"}, ) except (ValueError, Exception) as e: logger.error(f"Error- {str(e)}", exc_info=1) return jsonify(error=f"Error : {str(e)}"), 500
def computeDelete(): """Deletes a workflow. --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of the documentId to verify that the consumer has rights to download the asset. type: string - name: documentId in: query description: The ID of the asset required: true type: string - name: consumerAddress in: query description: The consumer address. required: true type: string - name: jobId in: query description: JobId. type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Invalid asset data. 500: description: Error """ data = get_request_data(request) try: body = process_compute_request(data) response = requests_session.delete( get_compute_endpoint(), params=body, headers={"content-type": "application/json"}, ) increment_nonce(body["owner"]) return Response( response.content, response.status_code, headers={"content-type": "application/json"}, ) except (ValueError, Exception) as e: logger.error(f"Error- {str(e)}", exc_info=1) return jsonify(error=f"Error : {str(e)}"), 500
def computeStart(): """Call the execution of a workflow. --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this asset/compute job. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: algorithmDid in: query description: The DID of the algorithm Asset to be executed required: false type: string - name: algorithmMeta in: query description: json object that define the algorithm attributes and url or raw code required: false type: json string - name: output in: query description: json object that define the output section required: true type: json string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification 500: description: General server error """ data = get_request_data(request) try: asset, service, did, consumer_address, token_address = process_consume_request( # noqa data, 'compute_start_job', additional_params=["transferTxId", "output"], require_signature=False) service_id = data.get('serviceId') service_type = data.get('serviceType') signature = data.get('signature') tx_id = data.get("transferTxId") # Verify that the number of required tokens has been # transferred to the provider's wallet. _tx, _order_log, _transfer_log = validate_order( consumer_address, token_address, float(service.get_cost()), tx_id, add_0x_prefix(did_to_id(did)) if did.startswith('did:') else did, service_id) validate_transfer_not_used_for_other_service(did, service_id, tx_id, consumer_address, token_address) record_consume_request(did, service_id, tx_id, consumer_address, token_address, service.get_cost()) algorithm_did = data.get('algorithmDid') algorithm_token_address = data.get('algorithmDataToken') algorithm_meta = data.get('algorithmMeta') algorithm_tx_id = data.get('algorithmTransferTxId') output_def = data.get('output', dict()) assert service_type == ServiceTypes.CLOUD_COMPUTE # Validate algorithm choice if not (algorithm_meta or algorithm_did): msg = f'Need an `algorithmMeta` or `algorithmDid` to run, otherwise don\'t bother.' # noqa logger.error(msg, exc_info=1) return jsonify(error=msg), 400 # algorithmDid also requires algorithmDataToken # and algorithmTransferTxId if algorithm_did: if not (algorithm_token_address and algorithm_tx_id): msg = ( f'Using `algorithmDid` requires the `algorithmDataToken` and ' # noqa f'`algorithmTransferTxId` values in the request payload. ' f'algorithmDataToken is the DataToken address for the algorithm asset. ' # noqa f'algorithmTransferTxId is the transaction id (hash) of transferring ' # noqa f'data tokens from consumer wallet to this providers wallet.' ) logger.error(msg, exc_info=1) return jsonify(error=msg), 400 # Consumer signature original_msg = f'{consumer_address}{did}' verify_signature(consumer_address, signature, original_msg, user_nonce.get_nonce(consumer_address)) ######################## # Valid service? if service is None: return jsonify( error=f'This DID has no compute service {did}.'), 400 ######################### # Check privacy privacy_options = service.main.get('privacy', {}) if (algorithm_meta and privacy_options.get('allowRawAlgorithm', True) is False): return jsonify( error=f'cannot run raw algorithm on this did {did}.'), 400 trusted_algorithms = privacy_options.get('trustedAlgorithms', []) if (algorithm_did and trusted_algorithms and algorithm_did not in trusted_algorithms): return jsonify( error=f'cannot run raw algorithm on this did {did}.'), 400 ######################### # Validate ALGORITHM meta if algorithm_meta: algorithm_meta = json.loads(algorithm_meta) if isinstance( algorithm_meta, str) else algorithm_meta algorithm_dict = build_stage_algorithm_dict( consumer_address, algorithm_did, algorithm_token_address, algorithm_tx_id, algorithm_meta, provider_wallet) error_msg, status_code = validate_algorithm_dict( algorithm_dict, algorithm_did) if error_msg: return jsonify(error=error_msg), status_code ######################### # INPUT asset_urls = get_asset_download_urls( asset, provider_wallet, config_file=app.config['CONFIG_FILE']) if not asset_urls: return jsonify(error=f'cannot get url(s) in input did {did}.'), 400 input_dict = dict({'index': 0, 'id': did, 'url': asset_urls}) ######################### # OUTPUT if output_def: output_def = json.loads(output_def) if isinstance( output_def, str) else output_def output_dict = build_stage_output_dict(output_def, asset, consumer_address, provider_wallet) ######################### # STAGE stage = build_stage_dict(input_dict, algorithm_dict, output_dict) ######################### # WORKFLOW workflow = dict({'stages': list([stage])}) # workflow is ready, push it to operator logger.info('Sending: %s', workflow) msg_to_sign = f'{provider_wallet.address}{did}' msg_hash = add_ethereum_prefix_and_hash_msg(msg_to_sign) payload = { 'workflow': workflow, 'providerSignature': Web3Helper.sign_hash(msg_hash, provider_wallet), 'documentId': did, 'agreementId': tx_id, 'owner': consumer_address, 'providerAddress': provider_wallet.address } response = requests_session.post( get_compute_endpoint(), data=json.dumps(payload), headers={'content-type': 'application/json'}) user_nonce.increment_nonce(consumer_address) return Response(response.content, response.status_code, headers={'content-type': 'application/json'}) except InvalidSignatureError as e: msg = f'Consumer signature failed verification: {e}' logger.error(msg, exc_info=1) return jsonify(error=msg), 401 except (ValueError, KeyError, Exception) as e: logger.error(f'Error- {str(e)}', exc_info=1) return jsonify(error=f'Error : {str(e)}'), 500
def computeStatus(): """Get status for a specific jobId/documentId/owner --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this asset/compute job. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: documentId in: query description: The ID of the asset. If not provided, the status of all currently running and old compute jobs for the specified consumerAddress will be returned. required: true type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: jobId in: query description: The ID of the compute job. If not provided, all running compute jobs of the specified consumerAddress/documentId are suspended type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification. 500: description: General server error """ data = get_request_data(request) try: signed_request = False try: body = process_compute_request(data, user_nonce) signed_request = True except Exception: body = process_compute_request(data, user_nonce, require_signature=False) response = requests_session.get( get_compute_endpoint(), params=body, headers={'content-type': 'application/json'}) user_nonce.increment_nonce(body['owner']) _response = response.content # Filter status info if signature is not given or failed validation if not signed_request: resp_content = json.loads(response.content.decode('utf-8')) if not isinstance(resp_content, list): resp_content = [resp_content] _response = [] keys_to_filter = [ 'resultsUrl', 'algorithmLogUrl', 'resultsDid', 'owner', ] for job_info in resp_content: for k in keys_to_filter: job_info.pop(k) _response.append(job_info) _response = json.dumps(_response) return Response(_response, response.status_code, headers={'content-type': 'application/json'}) except BadRequestError as e: return jsonify(error=str(e)), 400 except InvalidSignatureError as e: msg = f'Consumer signature failed verification: {e}' logger.error(msg, exc_info=1) return jsonify(error=msg), 401 except (ValueError, Exception) as e: logger.error(f'Error- {str(e)}', exc_info=1) return jsonify(error=f'Error : {str(e)}'), 500
def compute_get_status_job(): """Get status for a specific jobId/documentId/owner --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this asset/compute job. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: documentId in: query description: The ID of the asset. If not provided, the status of all currently running and old compute jobs for the specified consumerAddress will be returned. required: true type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: jobId in: query description: The ID of the compute job. If not provided, all running compute jobs of the specified consumerAddress/documentId are suspended type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification. 500: description: General server error """ data = get_request_data(request) try: body = process_compute_request(data, user_nonce) response = requests_session.get( get_compute_endpoint(), params=body, headers={'content-type': 'application/json'}) user_nonce.increment_nonce(body['owner']) return Response( response.content, response.status_code, headers={'content-type': 'application/json'} ) except BadRequestError as e: return jsonify(error=e), 400 except InvalidSignatureError as e: msg = f'Consumer signature failed verification: {e}' logger.error(msg, exc_info=1) return jsonify(error=msg), 401 except (ValueError, Exception) as e: logger.error(f'Error- {str(e)}', exc_info=1) return jsonify(error=f'Error : {str(e)}'), 500
def computeStart(): """Call the execution of a workflow. --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this asset/compute job. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: algorithmDid in: query description: The DID of the algorithm Asset to be executed required: false type: string - name: algorithmMeta in: query description: json object that define the algorithm attributes and url or raw code required: false type: json string - name: output in: query description: json object that define the output section required: true type: json string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification 500: description: General server error """ data = get_request_data(request) try: consumer_address = data.get("consumerAddress") validator = WorkflowValidator(consumer_address, provider_wallet, data) status = validator.validate() if not status: return jsonify(error=validator.error), 400 workflow = validator.workflow # workflow is ready, push it to operator logger.info("Sending: %s", workflow) tx_id = data.get("transferTxId") did = data.get("documentId") msg_to_sign = f"{provider_wallet.address}{did}" msg_hash = add_ethereum_prefix_and_hash_msg(msg_to_sign) payload = { "workflow": workflow, "providerSignature": Web3Helper.sign_hash(msg_hash, provider_wallet), "documentId": did, "agreementId": tx_id, "owner": consumer_address, "providerAddress": provider_wallet.address, } response = requests_session.post( get_compute_endpoint(), data=json.dumps(payload), headers={"content-type": "application/json"}, ) increment_nonce(consumer_address) return Response( response.content, response.status_code, headers={"content-type": "application/json"}, ) except (ValueError, KeyError, Exception) as e: logger.error(f"Error- {str(e)}", exc_info=1) return jsonify(error=f"Error : {str(e)}"), 500
def computeStatus(): """Get status for a specific jobId/documentId/owner --- tags: - services consumes: - application/json parameters: - name: signature in: query description: Signature of (consumerAddress+jobId+documentId) to verify the consumer of this asset/compute job. The signature uses ethereum based signing method (see https://github.com/ethereum/EIPs/pull/683) type: string - name: documentId in: query description: The ID of the asset. If not provided, the status of all currently running and old compute jobs for the specified consumerAddress will be returned. required: true type: string - name: consumerAddress in: query description: The consumer ethereum address. required: true type: string - name: jobId in: query description: The ID of the compute job. If not provided, all running compute jobs of the specified consumerAddress/documentId are suspended type: string responses: 200: description: Call to the operator-service was successful. 400: description: One of the required attributes is missing. 401: description: Consumer signature is invalid or failed verification. 500: description: General server error """ data = get_request_data(request) try: body = process_compute_request(data) response = requests_session.get( get_compute_endpoint(), params=body, headers={"content-type": "application/json"}, ) _response = response.content signed_request = bool(data.get("signature")) if signed_request: owner = data.get("consumerAddress") did = data.get("documentId") jobId = data.get("jobId") original_msg = f"{owner}{jobId}{did}" try: verify_signature( owner, data.get("signature"), original_msg, get_nonce(owner) ) except InvalidSignatureError: signed_request = False increment_nonce(owner) # Filter status info if signature is not given or failed validation if not signed_request: resp_content = json.loads(response.content.decode("utf-8")) if not isinstance(resp_content, list): resp_content = [resp_content] _response = [] keys_to_filter = ["resultsUrl", "algorithmLogUrl", "resultsDid", "owner"] for job_info in resp_content: for k in keys_to_filter: job_info.pop(k) _response.append(job_info) _response = json.dumps(_response) return Response( _response, response.status_code, headers={"content-type": "application/json"}, ) except (ValueError, Exception) as e: logger.error(f"Error- {str(e)}", exc_info=1) return jsonify(error=f"Error : {str(e)}"), 500