def validate_usage(self): """Verify that the tokens have been transferred to the provider's wallet.""" tx_id = self.data.get("transferTxId") token_address = self.asset._other_values["dataToken"] try: _tx, _order_log, _transfer_log = validate_order( self.consumer_address, token_address, float(self.service.get_cost()), tx_id, add_0x_prefix(did_to_id(self.did)) if self.did.startswith("did:") else self.did, self.service.index, ) validate_transfer_not_used_for_other_service( self.did, self.service.index, tx_id, self.consumer_address, token_address, ) record_consume_request( self.did, self.service.index, tx_id, self.consumer_address, token_address, self.service.get_cost(), ) except Exception: self.error = f"Order for serviceId {self.service.index} is not valid." return False return True
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 download(): """Allows download of asset data file. --- tags: - services consumes: - application/json parameters: - name: consumerAddress in: query description: The consumer address. required: true type: string - name: documentId in: query description: The ID of the asset/document (the DID). required: true type: string - name: url in: query description: This URL is only valid if Provider acts as a proxy. Consumer can't download using the URL if it's not through the Provider. required: true type: string - name: signature in: query description: Signature of the documentId to verify that the consumer has rights to download the asset. - name: index in: query description: Index of the file in the array of files. responses: 200: description: Redirect to valid asset url. 400: description: One of the required attributes is missing. 401: description: Invalid asset data. 500: description: Error """ data = get_request_data(request) try: asset, service, did, consumer_address, token_address = process_consume_request( # noqa data, 'download', user_nonce=user_nonce, additional_params=["transferTxId", "fileIndex"]) service_id = data.get('serviceId') service_type = data.get('serviceType') signature = data.get('signature') tx_id = data.get("transferTxId") if did.startswith('did:'): did = add_0x_prefix(did_to_id(did)) _tx, _order_log, _transfer_log = validate_order( consumer_address, token_address, float(service.get_cost()), tx_id, 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()) assert service_type == ServiceTypes.ASSET_ACCESS file_index = int(data.get('fileIndex')) file_attributes = asset.metadata['main']['files'][file_index] content_type = file_attributes.get('contentType', None) url = get_asset_url_at_index(file_index, asset, provider_wallet) download_url = get_download_url(url, app.config['CONFIG_FILE']) logger.info(f'Done processing consume request for asset {did}, ' f' url {download_url}') user_nonce.increment_nonce(consumer_address) return build_download_response(request, requests_session, url, download_url, content_type) except InvalidSignatureError as e: msg = f'Consumer signature failed verification: {e}' logger.error(msg, exc_info=1) return jsonify(error=msg), 401 except Exception as e: logger.error( f'Error: {e}. \n' f'Payload was: documentId={did}, ' f'consumerAddress={consumer_address},' f'signature={signature}' f'serviceId={service_id}' f'serviceType={service_type}', exc_info=1) return jsonify(error=str(e)), 500
def _build_and_validate_algo(self, algo_data): """Returns False if invalid, otherwise sets the validated_algo_dict attribute.""" algorithm_did = algo_data.get("algorithmDid") self.algo_service = None if algorithm_did and not algo_data.get("algorithmMeta"): algorithm_token_address = algo_data.get("algorithmDataToken") algorithm_tx_id = algo_data.get("algorithmTransferTxId") algo = get_asset_from_metadatastore(get_metadata_url(), algorithm_did) try: asset_type = algo.metadata["main"]["type"] except ValueError: asset_type = None if asset_type != "algorithm": self.error = f"DID {algorithm_did} is not a valid algorithm" return False try: dt = DataToken(self.consumer_address) tx_receipt = dt.get_tx_receipt(algorithm_tx_id) event_logs = dt.events.OrderStarted().processReceipt( tx_receipt) order_log = event_logs[0] if event_logs else None algo_service_id = order_log.args.serviceId self.algo_service = get_service_at_index(algo, algo_service_id) if self.algo_service.type == ServiceTypes.CLOUD_COMPUTE: asset_urls = get_asset_download_urls( algo, self.provider_wallet, config_file=app.config["CONFIG_FILE"], ) if not asset_urls: self.error = "Services in algorithm with compute type must be in the same provider you are calling." return False if not self.algo_service: self.error = "Failed to retrieve purchased algorithm service id." return False _tx, _order_log, _transfer_log = validate_order( self.consumer_address, algorithm_token_address, float(self.algo_service.get_cost()), algorithm_tx_id, add_0x_prefix(did_to_id(algorithm_did)) if algorithm_did.startswith("did:") else algorithm_did, self.algo_service.index, ) validate_transfer_not_used_for_other_service( algorithm_did, self.algo_service.index, algorithm_tx_id, self.consumer_address, algorithm_token_address, ) record_consume_request( algorithm_did, self.algo_service.index, algorithm_tx_id, self.consumer_address, algorithm_token_address, self.algo_service.get_cost(), ) except Exception: self.error = "Algorithm is already in use or can not be found on chain." return False algorithm_dict = StageAlgoSerializer(self.consumer_address, self.provider_wallet, algo_data, self.algo_service).serialize() valid, error_msg = validate_formatted_algorithm_dict( algorithm_dict, algorithm_did) if not valid: self.error = error_msg return False self.validated_algo_dict = algorithm_dict return True
def download(): """Allows download of asset data file. --- tags: - services consumes: - application/json parameters: - name: consumerAddress in: query description: The consumer address. required: true type: string - name: documentId in: query description: The ID of the asset/document (the DID). required: true type: string - name: url in: query description: This URL is only valid if Provider acts as a proxy. Consumer can't download using the URL if it's not through the Provider. required: true type: string - name: signature in: query description: Signature of the documentId to verify that the consumer has rights to download the asset. - name: index in: query description: Index of the file in the array of files. responses: 200: description: Redirect to valid asset url. 400: description: One of the required attributes is missing. 401: description: Invalid asset data. 500: description: Error """ data = get_request_data(request) try: ( asset, service, did, consumer_address, token_address, ) = process_consume_request(data) service_id = data.get("serviceId") service_type = data.get("serviceType") tx_id = data.get("transferTxId") if did.startswith("did:"): did = add_0x_prefix(did_to_id(did)) _tx, _order_log, _transfer_log = validate_order( consumer_address, token_address, float(service.get_cost()), tx_id, 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()) assert service_type == ServiceTypes.ASSET_ACCESS file_index = int(data.get("fileIndex")) file_attributes = asset.metadata["main"]["files"][file_index] content_type = file_attributes.get("contentType", None) url = get_asset_url_at_index(file_index, asset, provider_wallet) if not url: return jsonify(error="Cannot decrypt files for this asset."), 400 download_url = get_download_url(url, app.config["CONFIG_FILE"]) logger.info(f"Done processing consume request for asset {did}, " f" url {download_url}") increment_nonce(consumer_address) return build_download_response(request, requests_session, url, download_url, content_type) except Exception as e: logger.error( f"Error: {e}. \n" f"Payload was: documentId={data.get('did')}, " f"consumerAddress={data.get('consumerAddress')}," f"serviceId={data.get('serviceId')}" f"serviceType={data.get('serviceType')}", exc_info=1, ) return jsonify(error=str(e)), 500