Exemplo n.º 1
0
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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