diff --git a/.travis.yml b/.travis.yml index 33a8834..f035cfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,10 +23,10 @@ before_script: - ganache-cli --port 18545 > ganache-cli.log & - git clone https://github.com/oceanprotocol/barge - cd barge - - export AQUARIUS_VERSION=v1.0.5 - - export BRIZO_VERSION=v0.8.1 + - export AQUARIUS_VERSION=v1.0.7 + - export BRIZO_VERSION=v0.9.3 - export KEEPER_VERSION=v0.13.2 - - export EVENTS_HANDLER_VERSION=v0.4.4 + - export EVENTS_HANDLER_VERSION=v0.4.5 - export KEEPER_OWNER_ROLE_ADDRESS="0xe2DD09d719Da89e5a3D0F2549c7E24566e947260" - rm -rf "${HOME}/.ocean/keeper-contracts/artifacts" - bash -x start_ocean.sh --no-commons --no-dashboard 2>&1 > start_ocean.log & diff --git a/CHANGELOG.md b/CHANGELOG.md index 4761733..56c2542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +4,55 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -#### [v1.3.0](https://github.com/oceanprotocol/squid-js/compare/v1.2.0...v1.3.0) +#### [v2.0.0-beta.3](https://github.com/oceanprotocol/squid-js/compare/v2.0.0-beta.2...v2.0.0-beta.3) + +> 20 February 2020 + +- Adding output section [`#372`](https://github.com/oceanprotocol/squid-js/pull/372) +- add Output Object passed to brizo [`a60e415`](https://github.com/oceanprotocol/squid-js/commit/a60e4159255aadeba00bec2e72cf0fbb4b71489c) +- lint fix [`6e2289b`](https://github.com/oceanprotocol/squid-js/commit/6e2289b27ddc8643076385ee3fe8cfb743d4ecfb) +- add Output interface [`9cf716e`](https://github.com/oceanprotocol/squid-js/commit/9cf716ebe472ec24603c1bcf052848ad4464d9dd) + +#### [v2.0.0-beta.2](https://github.com/oceanprotocol/squid-js/compare/v1.3.0...v2.0.0-beta.2) + +> 31 January 2020 + +- Release 2.0.0-beta.2 [`4e45a77`](https://github.com/oceanprotocol/squid-js/commit/4e45a773d67b0f4199ffd5eae00b06213ba5622f) + +#### [v2.0.0-beta.1](https://github.com/oceanprotocol/squid-js/compare/v2.0.0-beta.0...v2.0.0-beta.1) + +> 28 January 2020 + +- remove `index` parameter from ocean.assets.consume() [`138a6bf`](https://github.com/oceanprotocol/squid-js/commit/138a6bf75abc402396606fab3bc3701875d7a393) +- Release 2.0.0-beta.1 [`1d7105c`](https://github.com/oceanprotocol/squid-js/commit/1d7105cfb1e80cb45711517ae6840d9ac22e80a6) + +#### [v2.0.0-beta.0](https://github.com/oceanprotocol/squid-js/compare/v1.2.0...v2.0.0-beta.0) + +> 28 January 2020 + +- Update cross-env to the latest version 🚀 [`#363`](https://github.com/oceanprotocol/squid-js/pull/363) +- Update mocha to the latest version 🚀 [`#364`](https://github.com/oceanprotocol/squid-js/pull/364) +- new ocean.utils.services [`40754ca`](https://github.com/oceanprotocol/squid-js/commit/40754ca46a10578cfdf0f460a7c33ba37bb2ffde) +- DDO & compute test tweaks [`e7acadb`](https://github.com/oceanprotocol/squid-js/commit/e7acadb2fe91739eee1bff3589801f12c7ab0b1b) +- test data consolidation [`ac39369`](https://github.com/oceanprotocol/squid-js/commit/ac39369543779370b9be6f00b0f2063a7e50c763) + +#### [v1.3.0](https://github.com/oceanprotocol/squid-js/compare/v2.0.0-beta.1...v1.3.0) > 31 January 2020 - switch to @ethereum-navigator for network lookup [`#366`](https://github.com/oceanprotocol/squid-js/pull/366) - consolidate test files [`#371`](https://github.com/oceanprotocol/squid-js/pull/371) - package updates [`#368`](https://github.com/oceanprotocol/squid-js/pull/368) -- Update cross-env to the latest version 🚀 [`#363`](https://github.com/oceanprotocol/squid-js/pull/363) -- Update mocha to the latest version 🚀 [`#364`](https://github.com/oceanprotocol/squid-js/pull/364) -- chore(package): update lockfile package-lock.json [`46c4def`](https://github.com/oceanprotocol/squid-js/commit/46c4defee5beb43fbea41006c5957d086721aff9) -- switch to @ethereum-navigator/navigator for network lookup [`92dbaae`](https://github.com/oceanprotocol/squid-js/commit/92dbaaeb25b26e28df644be356c347058ca1256a) -- chore(package): update lockfile package-lock.json [`eb141a6`](https://github.com/oceanprotocol/squid-js/commit/eb141a6d3d3131a65e4385f781568d015dac8429) +- service interface refactor and cleanup [`768c69b`](https://github.com/oceanprotocol/squid-js/commit/768c69bdbdc0591e2e747d87082e859bc52cd7d2) +- fix compute unit tests [`e37420d`](https://github.com/oceanprotocol/squid-js/commit/e37420dabfd23d5f307be695992cac7324d47dd0) +- job status cleanup [`f11eaca`](https://github.com/oceanprotocol/squid-js/commit/f11eacaabdc15285ba78a8096e492fd8863c933a) #### [v1.2.0](https://github.com/oceanprotocol/squid-js/compare/v1.1.0...v1.2.0) > 23 January 2020 - Decouple aquarius from ocean [`#354`](https://github.com/oceanprotocol/squid-js/pull/354) +- merge fixes [`c8ea5f7`](https://github.com/oceanprotocol/squid-js/commit/c8ea5f77c2ec541fdbfb6f77cdabcd5691e4bffa) - Release 1.2.0 [`56f7d11`](https://github.com/oceanprotocol/squid-js/commit/56f7d1113a6aa1f3318040fb32cfdcc22f5dc13b) #### [v1.1.0](https://github.com/oceanprotocol/squid-js/compare/v1.1.0-beta.0...v1.1.0) diff --git a/MIGRATION.md b/MIGRATION.md index 3201302..736674d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,34 @@ # Migration Guide -Instructions on how to migrate between breaking versions. +Instructions on how to migrate between versions with breaking changes. + +## v1.2.0 → v2.0.0 + +### Ocean Protocol Components Requirements + +squid-js v2.0.0 only works against: + +- Aquarius v1.0.7+ +- Brizo v0.9.3+ +- Events Handler v0.4.5+ +- Keeper Contracts v0.13.2+ + +### Service index parameter removal from `ocean.assets` methods + +Removes the need to get the respective service from the DDO, the `ocean.assets` methods will now do this on their own automatically. + +```js +// old +const service = ddo.findServiceByType('access') +const did = ddo.id +const agreementId = await ocean.assets.order(did, service.index, account) +const path = await ocean.assets.consume(agreementId, did, service.index, account, folder) + +// NEW +const did = ddo.id +const agreementId = await ocean.assets.order(did, account) +const path = await ocean.assets.consume(agreementId, did, account, folder) +``` ## v0.8.3 → v1.0.0 diff --git a/library.json b/library.json index 9d0cb91..1bfdb58 100644 --- a/library.json +++ b/library.json @@ -11,15 +11,15 @@ }, { "name": "brizo", - "version": "~0.8.1" + "version": "~0.9.0" }, { "name": "aquarius", - "version": "~1.0.5" + "version": "~1.0.7" }, { "name": "events-handler", - "version": "~0.4.1" + "version": "~0.4.4" } ] } diff --git a/package-lock.json b/package-lock.json index 3178b3f..2f37168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oceanprotocol/squid", - "version": "1.3.0", + "version": "2.0.0-beta.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -400,6 +400,42 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, + "@sinonjs/commons": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz", + "integrity": "sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", + "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^4.2.0" + } + }, + "@sinonjs/samsam": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", + "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -845,6 +881,12 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz", + "integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz", @@ -7362,6 +7404,12 @@ "object.assign": "^4.1.0" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "keccak": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/keccak/-/keccak-2.1.0.tgz", @@ -7699,6 +7747,15 @@ "chalk": "^2.4.2" } }, + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8355,6 +8412,37 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-3.0.1.tgz", + "integrity": "sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/formatio": "^4.0.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -10593,6 +10681,29 @@ "resolved": "https://registry.npmjs.org/simple-mime/-/simple-mime-0.1.0.tgz", "integrity": "sha1-lfUXxPRm18/1YacfydqyWW6p7y4=" }, + "sinon": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-8.1.1.tgz", + "integrity": "sha512-E+tWr3acRdoe1nXbHMu86SSqA1WGM7Yw3jZRLvlCMnXwTHP8lgFFVn5BnKnF26uc5SfZ3D7pA9sN7S3Y2jG4Ew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/formatio": "^4.0.1", + "@sinonjs/samsam": "^4.2.2", + "diff": "^4.0.2", + "lolex": "^5.1.2", + "nise": "^3.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index 0e9dfe9..eae6082 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oceanprotocol/squid", - "version": "1.3.0", + "version": "2.0.0-beta.3", "description": "JavaScript client library for Ocean Protocol", "main": "./dist/node/squid.js", "typings": "./dist/node/squid.d.ts", @@ -70,6 +70,7 @@ "@types/mocha": "^7.0.1", "@types/node": "^13.5.1", "@types/node-fetch": "^2.5.4", + "@types/sinon": "^7.5.1", "@typescript-eslint/eslint-plugin": "^2.18.0", "@typescript-eslint/parser": "^2.18.0", "auto-changelog": "^1.16.2", @@ -86,6 +87,7 @@ "nyc": "^15.0.0", "ora": "^4.0.2", "prettier": "^1.19.1", + "sinon": "^8.1.1", "source-map-support": "^0.5.16", "ts-node": "^8.6.2", "typedoc": "^0.16.9", diff --git a/src/brizo/Brizo.ts b/src/brizo/Brizo.ts index 6dd6055..461200a 100644 --- a/src/brizo/Brizo.ts +++ b/src/brizo/Brizo.ts @@ -1,7 +1,10 @@ -import { File } from '../ddo/MetaData' +import { File, MetaData } from '../ddo/MetaData' import Account from '../ocean/Account' import { noZeroX } from '../utils' import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' +import { DDO } from '../ddo/DDO' +import { ServiceType } from '../ddo/Service' +import { ComputeJob, Output } from '../ocean/OceanCompute' const apiPath = '/api/v1/brizo/services' @@ -35,15 +38,22 @@ export class Brizo extends Instantiable { return `${this.url}${apiPath}/publish` } - public getComputeEndpoint( - pubKey: string, - serviceIndex: number, - _notUsed: string, - container: string - ) { + public getComputeEndpoint() { return `${this.url}${apiPath}/compute` } + public async getEndpointFromAgreement( + type: ServiceType, + agreementId: string + ): Promise { + const { assets, keeper } = this.ocean + const { did } = await keeper.agreementStoreManager.getAgreement(agreementId) + const ddo: DDO = await assets.resolve(did) + const { serviceEndpoint } = ddo.findServiceByType(type) + + return serviceEndpoint + } + public async initializeServiceAgreement( did: string, serviceAgreementId: string, @@ -78,12 +88,7 @@ export class Brizo extends Instantiable { destination: string, index: number = -1 ): Promise { - const signature = - (await account.getToken()) || - (await this.ocean.utils.signature.signText( - noZeroX(agreementId), - account.getId() - )) + const signature = await this.createSignature(account, agreementId) const filesPromises = files .filter((_, i) => index === -1 || i === index) .map(async ({ index: i }) => { @@ -105,6 +110,90 @@ export class Brizo extends Instantiable { return destination } + public async compute( + method: string, + serviceAgreementId: string, + consumerAccount: Account, + algorithmDid?: string, + algorithmMeta?: MetaData, + jobId?: string, + output?: Output + ): Promise { + const signature = await this.createSignature(consumerAccount, serviceAgreementId) + const address = consumerAccount.getId() + const serviceEndpoint = await this.getEndpointFromAgreement( + 'compute', + serviceAgreementId + ) + + if (!serviceEndpoint) { + throw new Error( + 'Computing on asset failed, service definition is missing the `serviceEndpoint`.' + ) + } + + // construct Brizo URL + let url = serviceEndpoint + url += `?signature=${signature}` + url += `&consumerAddress=${address}` + url += `&serviceAgreementId=${noZeroX(serviceAgreementId)}` + url += (algorithmDid && `&algorithmDid=${algorithmDid}`) || '' + url += (algorithmMeta && `&algorithmMeta=${algorithmMeta}`) || '' + url += (output && `&output=${JSON.stringify(output)}`) || '' + url += (jobId && `&jobId=${jobId}`) || '' + + // switch fetch method + let fetch + + switch (method) { + case 'post': + fetch = this.ocean.utils.fetch.post(url, '') + break + case 'put': + fetch = this.ocean.utils.fetch.put(url, '') + break + case 'delete': + fetch = this.ocean.utils.fetch.delete(url) + break + default: + fetch = this.ocean.utils.fetch.get(url) + break + } + + const result = await fetch + .then((response: any) => { + if (response.ok) { + return response.json() + } + + this.logger.error( + 'Compute job failed:', + response.status, + response.statusText + ) + + return null + }) + .catch((error: Error) => { + this.logger.error('Error with compute job') + this.logger.error(error.message) + throw error + }) + + return result + } + + public async createSignature(account: Account, agreementId: string): Promise { + const signature = + (await account.getToken()) || + (await this.ocean.utils.signature.signText( + noZeroX(agreementId), + account.getId() + )) + + return signature + } + public async encrypt( did: string, signature: string, diff --git a/src/ddo/ComputingProvider.ts b/src/ddo/ComputingProvider.ts deleted file mode 100644 index dc9b2f6..0000000 --- a/src/ddo/ComputingProvider.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface Provider { - type: string - description: string - environment: { - cluster: { - type: string - url: string - } - supportedContainers: { - image: string - tag: string - checksum: string - }[] - supportedServers: { - serverId: string - serverType: string - price: string - cpu: string - gpu: string - memory: string - disk: string - maxExecutionTime: number - }[] - } -} diff --git a/src/ddo/MetaData.ts b/src/ddo/MetaData.ts index f16bae6..11ff7ab 100644 --- a/src/ddo/MetaData.ts +++ b/src/ddo/MetaData.ts @@ -1,72 +1,3 @@ -export interface StageRequirements { - container: { - image: string - tag: string - checksum: string - } -} - -export interface StageInput { - index: number - id: string -} - -export interface StageTransformation { - id: string -} - -export interface StageOutput { - metadataUrl: string - secretStoreUrl: string - accessProxyUrl: string - metadata: MetaDataMain -} - -export interface Stage { - index: number - stageType?: string - requirements: StageRequirements - input: StageInput - transformation: StageTransformation - output: StageOutput -} - -export interface Workflow { - stages: Stage[] -} - -export interface Algorithm { - language: string - format?: string - version?: string - entrypoint: string - requirements: { - requirement: string - version: string - } -} - -export interface ServiceDefinition { - auth: { - type: string - user?: string - password?: string - token?: string - } - endpoints: { - index: number - url: string - method: string - contentTypes: string[] - } -} - -export interface Service { - spec?: string - specChecksum?: string - definition: ServiceDefinition -} - export interface File { /** * File name. @@ -132,6 +63,17 @@ export interface File { compression?: string } +export interface MetaDataAlgorithm { + language?: string + format?: string + version?: string + container: { + entrypoint: string + image: string + tag: string + } +} + /** * Main attributes of assets metadata. * @see https://github.com/oceanprotocol/OEPs/tree/master/8 @@ -145,12 +87,11 @@ export interface MetaDataMain { name: string /** - * Type of the Asset. Helps to filter by the type of asset, - * initially ("dataset", "algorithm", "container", "workflow", "other"). + * Type of the Asset. Helps to filter by the type of asset ("dataset" or "algorithm"). * @type {string} * @example "dataset" */ - type: 'dataset' | 'algorithm' | 'container' | 'workflow' | 'other' + type: 'dataset' | 'algorithm' /** * The date on which the asset was created by the originator in @@ -185,7 +126,7 @@ export interface MetaDataMain { license: string /** - * Price of the asset. + * Price of the asset in vodka (attoOCEAN). It must be an integer encoded as a string. * @type {string} * @example "1000000000000000000" */ @@ -197,13 +138,11 @@ export interface MetaDataMain { */ files: File[] - encryptedService?: any - - workflow?: Workflow - - algorithm?: Algorithm - - service?: Service + /** + * Metadata used only for assets with type `algorithm`. + * @type {MetaDataAlgorithm} + */ + algorithm?: MetaDataAlgorithm } /** diff --git a/src/ddo/Service.ts b/src/ddo/Service.ts index 8cdad3b..42a7497 100644 --- a/src/ddo/Service.ts +++ b/src/ddo/Service.ts @@ -1,21 +1,65 @@ import { MetaData } from './MetaData' import { ServiceAgreementTemplate } from './ServiceAgreementTemplate' -import { Provider } from './ComputingProvider' -export type ServiceType = - | 'authorization' - | 'metadata' - | 'access' - | 'compute' - | 'computing' - | 'fitchainCompute' +export type ServiceType = 'authorization' | 'metadata' | 'access' | 'compute' export interface ServiceCommon { type: ServiceType index: number serviceEndpoint?: string - attributes: any & { - main: { [key: string]: any } + attributes: ServiceCommonAttributes +} + +export interface ServiceCommonAttributes { + main: { [key: string]: any } + additionalInformation?: { [key: string]: any } + serviceAgreementTemplate?: ServiceAgreementTemplate +} + +export interface ServiceAccessAttributes extends ServiceCommonAttributes { + main: { + creator: string + name: string + datePublished: string + price: string + timeout: number + } +} + +export interface ServiceComputeAttributes extends ServiceCommonAttributes { + main: { + creator: string + datePublished: string + price: string + timeout: number + provider?: ServiceComputeProvider + name: string + } +} + +export interface ServiceComputeProvider { + type: string + description: string + environment: { + cluster: { + type: string + url: string + } + supportedContainers: { + image: string + tag: string + checksum: string + }[] + supportedServers: { + serverId: string + serverType: string + price: string + cpu: string + gpu: string + memory: string + disk: string + maxExecutionTime: number + }[] } } @@ -32,30 +76,13 @@ export interface ServiceMetadata extends ServiceCommon { export interface ServiceAccess extends ServiceCommon { type: 'access' templateId?: string - attributes: { - main: { - creator: string - name: string - datePublished: string - price: string - timeout: number - } - serviceAgreementTemplate?: ServiceAgreementTemplate - additionalInformation: { - description: string - } - } -} - -export interface ServiceComputing extends ServiceCommon { - type: 'computing' - templateId?: string - provider?: Provider - serviceAgreementTemplate?: ServiceAgreementTemplate + attributes: ServiceAccessAttributes } export interface ServiceCompute extends ServiceCommon { + type: 'compute' templateId?: string + attributes: ServiceComputeAttributes } export type Service< @@ -64,8 +91,6 @@ export type Service< ? ServiceAuthorization : T extends 'metadata' ? ServiceMetadata - : T extends 'computing' - ? ServiceComputing : T extends 'access' ? ServiceAccess : T extends 'compute' diff --git a/src/keeper/Keeper.ts b/src/keeper/Keeper.ts index f85757d..75f26d3 100644 --- a/src/keeper/Keeper.ts +++ b/src/keeper/Keeper.ts @@ -95,24 +95,19 @@ export class Keeper extends Instantiable { computeExecutionCondition: keeper.instances.computeExecutionCondition } // Templates - keeper.instances.escrowAccessSecretStoreTemplate = new EscrowAccessSecretStoreTemplate( + keeper.templates = Object() + keeper.templates.escrowAccessSecretStoreTemplate = new EscrowAccessSecretStoreTemplate( keeper.templateStoreManager, keeper.agreementStoreManager, keeper.didRegistry, keeper.conditions ) - keeper.instances.escrowComputeExecutionTemplate = new EscrowComputeExecutionTemplate( + keeper.templates.escrowComputeExecutionTemplate = new EscrowComputeExecutionTemplate( keeper.templateStoreManager, keeper.agreementStoreManager, keeper.didRegistry, keeper.conditions ) - keeper.templates = { - escrowAccessSecretStoreTemplate: - keeper.instances.escrowAccessSecretStoreTemplate, - escrowComputeExecutionTemplate: - keeper.instances.escrowComputeExecutionTemplate - } // Utils keeper.utils = { eventHandler: new EventHandler(config) diff --git a/src/keeper/contracts/templates/EscrowAccess.serviceAgreementTemplate.ts b/src/keeper/contracts/templates/EscrowAccess.serviceAgreementTemplate.ts index 651ec6e..cf1b361 100644 --- a/src/keeper/contracts/templates/EscrowAccess.serviceAgreementTemplate.ts +++ b/src/keeper/contracts/templates/EscrowAccess.serviceAgreementTemplate.ts @@ -4,10 +4,10 @@ export const escrowAccessServiceAgreementTemplate: ServiceAgreementTemplate = { contractName: 'EscrowAccessSecretStoreTemplate', events: [ { - name: 'AgreementCreated', - actorType: 'consumer', + name: 'AgreementActorAdded', + actorType: 'provider', handler: { - moduleName: 'escrowAccessSecretStoreTemplate', + moduleName: '', functionName: 'fulfillLockRewardCondition', version: '0.1' } @@ -45,7 +45,7 @@ export const escrowAccessServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { moduleName: 'lockRewardCondition', functionName: 'fulfillAccessSecretStoreCondition', @@ -75,7 +75,7 @@ export const escrowAccessServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { moduleName: 'accessSecretStore', functionName: 'fulfillEscrowRewardCondition', @@ -87,7 +87,7 @@ export const escrowAccessServiceAgreementTemplate: ServiceAgreementTemplate = { actorType: 'consumer', handler: { moduleName: 'accessSecretStore', - functionName: 'fulfillEscrowRewardCondition', + functionName: 'refundReward', version: '0.1' } } @@ -129,7 +129,7 @@ export const escrowAccessServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { moduleName: 'escrowRewardCondition', functionName: 'verifyRewardTokens', diff --git a/src/keeper/contracts/templates/EscrowCompute.serviceAgreementTemplate.ts b/src/keeper/contracts/templates/EscrowCompute.serviceAgreementTemplate.ts index 732c20d..5c35f85 100644 --- a/src/keeper/contracts/templates/EscrowCompute.serviceAgreementTemplate.ts +++ b/src/keeper/contracts/templates/EscrowCompute.serviceAgreementTemplate.ts @@ -4,10 +4,10 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { contractName: 'EscrowComputeExecutionTemplate', events: [ { - name: 'AgreementCreated', - actorType: 'consumer', + name: 'AgreementActorAdded', + actorType: 'provider', handler: { - moduleName: 'serviceExecutionTemplate', + moduleName: '', functionName: 'fulfillLockRewardCondition', version: '0.1' } @@ -15,13 +15,13 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { ], fulfillmentOrder: [ 'lockReward.fulfill', - 'serviceExecution.fulfill', + 'computeExecution.fulfill', 'escrowReward.fulfill' ], conditionDependency: { lockReward: [], serviceExecution: [], - escrowReward: ['lockReward', 'serviceExecution'] + escrowReward: ['lockReward', 'computeExecution'] }, conditions: [ { @@ -45,17 +45,17 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { - moduleName: 'lockRewardCondition', - functionName: 'fulfillServiceExecutionCondition', + moduleName: 'lockRewardExecutionCondition', + functionName: 'fulfillComputeExecutionCondition', version: '0.1' } } ] }, { - name: 'serviceExecution', + name: 'computeExecution', timelock: 0, timeout: 0, contractName: 'ComputeExecutionCondition', @@ -75,10 +75,10 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { - moduleName: 'serviceExecution', - functionName: 'fulfillServiceExecutionCondition', + moduleName: 'accessSecretStore', + functionName: 'fulfillEscrowRewardCondition', version: '0.1' } }, @@ -86,8 +86,8 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { name: 'TimedOut', actorType: 'consumer', handler: { - moduleName: 'serviceExec', - functionName: 'fulfillServiceExecutionCondition', + moduleName: 'accessSecretStore', + functionName: 'refundReward', version: '0.1' } } @@ -129,7 +129,7 @@ export const escrowComputeServiceAgreementTemplate: ServiceAgreementTemplate = { events: [ { name: 'Fulfilled', - actorType: 'publisher', + actorType: 'provider', handler: { moduleName: 'escrowRewardCondition', functionName: 'verifyRewardTokens', diff --git a/src/ocean/Ocean.ts b/src/ocean/Ocean.ts index dd618f2..c31a077 100644 --- a/src/ocean/Ocean.ts +++ b/src/ocean/Ocean.ts @@ -2,6 +2,7 @@ import { OceanAccounts } from './OceanAccounts' import { OceanAgreements } from './OceanAgreements' import { OceanAssets } from './OceanAssets' import { OceanAuth } from './OceanAuth' +import { OceanCompute } from './OceanCompute' import { OceanSecretStore } from './OceanSecretStore' import { OceanTokens } from './OceanTokens' import { OceanVersions } from './OceanVersions' @@ -49,6 +50,7 @@ export class Ocean extends Instantiable { instance.accounts = await OceanAccounts.getInstance(instanceConfig) instance.auth = await OceanAuth.getInstance(instanceConfig) instance.assets = await OceanAssets.getInstance(instanceConfig) + instance.compute = await OceanCompute.getInstance(instanceConfig) instance.agreements = await OceanAgreements.getInstance(instanceConfig) instance.secretStore = await OceanSecretStore.getInstance(instanceConfig) instance.tokens = await OceanTokens.getInstance(instanceConfig) @@ -99,6 +101,12 @@ export class Ocean extends Instantiable { */ public agreements: OceanAgreements + /** + * Ocean compute submodule + * @type {OceanCompute} + */ + public compute: OceanCompute + /** * Ocean secretStore submodule * @type {OceanSecretStore} diff --git a/src/ocean/OceanAgreements.ts b/src/ocean/OceanAgreements.ts index 8cf8247..c853248 100644 --- a/src/ocean/OceanAgreements.ts +++ b/src/ocean/OceanAgreements.ts @@ -7,6 +7,7 @@ import { AgreementConditionsStatus } from '../keeper/contracts/templates/Agreeme import { ConditionState } from '../keeper/contracts/conditions/Condition.abstract' import { OceanAgreementsConditions } from './OceanAgreementsConditions' +import { Service } from '../ddo/Service' export interface AgreementPrepareResult { agreementId: string @@ -121,7 +122,7 @@ export class OceanAgreements extends Instantiable { ) { const d: DID = DID.parse(did) const ddo = await this.ocean.aquarius.retrieveDDO(d) - const service = ddo.findServiceById(index) + const service: Service = ddo.findServiceById(index) const templateName = service.attributes.serviceAgreementTemplate.contractName return this.ocean.keeper .getTemplateByName(templateName) diff --git a/src/ocean/OceanAgreementsConditions.ts b/src/ocean/OceanAgreementsConditions.ts index 5fd67da..6f23e04 100644 --- a/src/ocean/OceanAgreementsConditions.ts +++ b/src/ocean/OceanAgreementsConditions.ts @@ -81,13 +81,13 @@ export class OceanAgreementsConditions extends Instantiable { } /** - * Authorize the consumer defined in the agreement to execute a remote service associated with this asset. + * Authorize the consumer defined in the agreement to compute on this asset. * @param {string} agreementId Agreement ID. * @param {string} did Asset ID. * @param {string} grantee Consumer address. * @param {Account} from Account of sender. */ - public async grantServiceExecution( + public async grantCompute( agreementId: string, did: string, grantee: string, diff --git a/src/ocean/OceanAssets.ts b/src/ocean/OceanAssets.ts index 537fd8b..dd94884 100644 --- a/src/ocean/OceanAssets.ts +++ b/src/ocean/OceanAssets.ts @@ -5,8 +5,9 @@ import { MetaData } from '../ddo/MetaData' import { Service } from '../ddo/Service' import Account from './Account' import DID from './DID' -import { fillConditionsWithDDO, SubscribablePromise, generateId, zeroX } from '../utils' +import { fillConditionsWithDDO, SubscribablePromise } from '../utils' import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' +import { OrderProgressStep } from './utils/ServiceUtils' export enum CreateProgressStep { EncryptingFiles, @@ -19,13 +20,6 @@ export enum CreateProgressStep { DdoStored } -export enum OrderProgressStep { - CreatingAgreement, - AgreementInitialized, - LockingPayment, - LockedPayment -} - /** * Assets submodule of Ocean Protocol. */ @@ -165,9 +159,18 @@ export class OceanAssets extends Instantiable { }) // Overwrite initial service agreement conditions - const rawConditions = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplateConditions() - const conditions = fillConditionsWithDDO(rawConditions, ddo) - serviceAgreementTemplate.conditions = conditions + serviceAgreementTemplate.conditions = fillConditionsWithDDO( + await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplateConditions(), + ddo + ) + for (const service of services) { + if (service.type === 'compute') { + service.attributes.serviceAgreementTemplate.conditions = fillConditionsWithDDO( + await templates.escrowComputeExecutionTemplate.getServiceAgreementTemplateConditions(), + ddo + ) + } + } this.logger.log('Generating proof') observer.next(CreateProgressStep.GeneratingProof) @@ -200,17 +203,16 @@ export class OceanAssets extends Instantiable { public async consume( agreementId: string, did: string, - serviceIndex: number, consumerAccount: Account, resultPath: string, index?: number, useSecretStore?: boolean ): Promise + /* eslint-disable no-dupe-class-members */ public async consume( agreementId: string, did: string, - serviceIndex: number, consumerAccount: Account, resultPath?: undefined | null, index?: number, @@ -220,7 +222,6 @@ export class OceanAssets extends Instantiable { public async consume( agreementId: string, did: string, - serviceIndex: number, consumerAccount: Account, resultPath?: string, index: number = -1, @@ -228,8 +229,7 @@ export class OceanAssets extends Instantiable { ): Promise { const ddo = await this.resolve(did) const { attributes } = ddo.findServiceByType('metadata') - - const accessService = ddo.findServiceById(serviceIndex) + const accessService = ddo.findServiceByType('access') const { files } = attributes.main @@ -244,7 +244,7 @@ export class OceanAssets extends Instantiable { this.logger.log('Consuming files') resultPath = resultPath - ? `${resultPath}/datafile.${ddo.shortId()}.${serviceIndex}/` + ? `${resultPath}/datafile.${ddo.shortId()}.${accessService.index}/` : undefined if (!useSecretStore) { @@ -277,109 +277,43 @@ export class OceanAssets extends Instantiable { } return true } + /* eslint-enable no-dupe-class-members */ /** * Start the purchase/order of an asset's service. Starts by signing the service agreement * then sends the request to the publisher via the service endpoint (Brizo http service). * @param {string} did Decentralized ID. - * @param {number} index Service index. - * @param {Account} consumer Consumer account. + * @param {Account} consumerAccount Consumer account. * @param {string} provider ethereum address of service provider (optional) * @return {Promise} Returns Agreement ID */ public order( did: string, - index: number, - consumer: Account, + consumerAccount: Account, provider?: string ): SubscribablePromise { return new SubscribablePromise(async observer => { - const oceanAgreements = this.ocean.agreements + const { keeper, utils } = this.ocean + const ddo: DDO = await this.resolve(did) + const condition = keeper.conditions.accessSecretStoreCondition - const agreementId = zeroX(generateId()) - const ddo = await this.resolve(did) - - const { keeper } = this.ocean - const templateName = ddo.findServiceByType('access').attributes - .serviceAgreementTemplate.contractName - const template = keeper.getTemplateByName(templateName) - const accessCondition = keeper.conditions.accessSecretStoreCondition - - // eslint-disable-next-line no-async-promise-executor - const paymentFlow = new Promise(async (resolve, reject) => { - await template.getAgreementCreatedEvent(agreementId).once() - - this.logger.log('Agreement initialized') - observer.next(OrderProgressStep.AgreementInitialized) - - const { attributes } = ddo.findServiceByType('metadata') - - this.logger.log('Locking payment') - - const accessGranted = accessCondition - .getConditionFulfilledEvent(agreementId) - .once() - - observer.next(OrderProgressStep.LockingPayment) - const paid = await oceanAgreements.conditions.lockReward( - agreementId, - attributes.main.price, - consumer - ) - observer.next(OrderProgressStep.LockedPayment) - - if (paid) { - this.logger.log('Payment was OK') - } else { - this.logger.error('Payment was KO') - this.logger.error('Agreement ID: ', agreementId) - this.logger.error('DID: ', ddo.id) - reject(new Error('Error on payment')) - } - - await accessGranted - - this.logger.log('Access granted') - resolve() - }) - - observer.next(OrderProgressStep.CreatingAgreement) - this.logger.log('Creating agreement') - - // Get provider from didRegistry if not given in arguments - let _provider = provider - if (!provider) { - const providers = await keeper.didRegistry.getDIDProviders(ddo.shortId()) - if (providers) { - _provider = providers[0] - } - } - - await oceanAgreements.create( - did, - agreementId, - index, - undefined, - consumer, - _provider, - consumer + const agreementId = await utils.services.order( + 'access', + condition, + observer, + consumerAccount, + ddo, + provider ) - this.logger.log('Agreement created') - - try { - await paymentFlow - } catch (e) { - throw new Error('Error paying the asset.') - } return agreementId }) } /** - * Returns the owner of a asset. + * Returns the owner of an asset. * @param {string} did Decentralized ID. - * @return {Promise} Returns Agreement ID + * @return {Promise} Returns Account ID */ public async owner(did: string): Promise { const ddo = await this.resolve(did) diff --git a/src/ocean/OceanCompute.ts b/src/ocean/OceanCompute.ts new file mode 100644 index 0000000..7a0f171 --- /dev/null +++ b/src/ocean/OceanCompute.ts @@ -0,0 +1,264 @@ +import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' +import { MetaData } from '../ddo/MetaData' +import Account from './Account' +import { DDO } from '../ddo/DDO' +import { SubscribablePromise } from '../utils' +import { OrderProgressStep } from './utils/ServiceUtils' +import { DID } from '../squid' + +export const ComputeJobStatus = Object.freeze({ + Started: 10, + ConfiguringVolumes: 20, + ProvisioningSuccess: 30, + DataProvisioningFailed: 31, + AlgorithmProvisioningFailed: 32, + RunningAlgorithm: 40, + FilteringResults: 50, + PublishingResult: 60, + Completed: 70, + Stopped: 80, + Deleted: 90 +}) + +export interface Output { + publishAlgorithmLog?: boolean + publishOutput?: boolean + brizoAddress?: string + brizoUri?: string + metadata?: MetaData + metadataUri?: string + nodeUri?: string + owner?: string + secretStoreUri?: string + whitelist?: string[] +} + +export interface ComputeJob { + owner: string + agreementId: string + jobId: string + dateCreated: string + dateFinished: string + status: number + statusText: string + algorithmLogUrl: string + resultsUrls: string[] + resultsDid?: DID +} + +/** + * Compute submodule of Ocean Protocol. + */ +export class OceanCompute extends Instantiable { + /** + * Returns the instance of OceanCompute. + * @return {Promise} + */ + public static async getInstance(config: InstantiableConfig): Promise { + const instance = new OceanCompute() + instance.setInstanceConfig(config) + + return instance + } + + /** + * Starts an order of a compute service that is defined in an asset's services. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} datasetDid The DID of the dataset asset (of type `dataset`) to run the algorithm on. + * @return {Promise} Returns the Service Agreement ID, representation of `bytes32` ID. + */ + public order( + consumerAccount: Account, + datasetDid: string, + provider?: string + ): SubscribablePromise { + return new SubscribablePromise(async observer => { + const { assets, keeper, utils } = this.ocean + const ddo: DDO = await assets.resolve(datasetDid) + const condition = keeper.conditions.computeExecutionCondition + + const agreementId = await utils.services.order( + 'compute', + condition, + observer, + consumerAccount, + ddo, + provider + ) + + return agreementId + }) + } + + /** + * Check the output object and add default properties if needed + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {Output} output Output section used for publishing the result. + * @return {Promise} Returns output object + */ + public checkOutput(consumerAccount: Account, output?: Output): Output { + const isDefault = + !output || (!output.publishAlgorithmLog && !output.publishOutput) + + if (isDefault) { + return { + publishAlgorithmLog: false, + publishOutput: false + } + } + + return { + publishAlgorithmLog: output.publishAlgorithmLog, + publishOutput: output.publishOutput, + brizoAddress: output.brizoAddress || this.config.brizoAddress, + brizoUri: output.brizoUri || this.config.brizoUri, + metadataUri: output.metadataUri || this.config.aquariusUri, + nodeUri: output.nodeUri || this.config.nodeUri, + owner: output.owner || consumerAccount.getId(), + secretStoreUri: output.secretStoreUri || this.config.secretStoreUri + } + } + + /** + * Start the execution of a compute job. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset. + * @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified. + * @param {Output} output Define algorithm output publishing. Publishing the result of a compute job is turned off by default. + * @return {Promise} Returns compute job ID under status.jobId + */ + public async start( + consumerAccount: Account, + agreementId: string, + algorithmDid?: string, + algorithmMeta?: MetaData, + output?: Output + ): Promise { + output = this.checkOutput(consumerAccount, output) + const computeJobsList = await this.ocean.brizo.compute( + 'post', + agreementId, + consumerAccount, + algorithmDid, + algorithmMeta, + undefined, + output + ) + + return computeJobsList[0] as ComputeJob + } + + /** + * Ends a running compute job. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} jobId The ID of the compute job to be stopped + * @return {Promise} Returns the new status of a job + */ + public async stop( + consumerAccount: Account, + agreementId: string, + jobId: string + ): Promise { + const computeJobsList = await this.ocean.brizo.compute( + 'put', + agreementId, + consumerAccount, + undefined, + undefined, + jobId + ) + + return computeJobsList[0] as ComputeJob + } + + /** + * Deletes a compute job and all resources associated with the job. If job is running it will be stopped first. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} jobId The ID of the compute job to be stopped + * @return {Promise} Returns the new status of a job + */ + public async delete( + consumerAccount: Account, + agreementId: string, + jobId: string + ): Promise { + const computeJobsList = await this.ocean.brizo.compute( + 'delete', + agreementId, + consumerAccount, + undefined, + undefined, + jobId + ) + + return computeJobsList[0] as ComputeJob + } + + /** + * Ends a running compute job and starts it again. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} jobId The ID of the compute job to be stopped + * @return {Promise} Returns the new status of a job + */ + public async restart( + consumerAccount: Account, + agreementId: string, + jobId: string + ): Promise { + await this.stop(consumerAccount, agreementId, jobId) + const result = await this.start(consumerAccount, agreementId, jobId) + return result + } + + /** + * Returns information about the status of all compute jobs, or a single compute job. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} jobId The ID of the compute job to be stopped + * @return {Promise} Returns the status + */ + public async status( + consumerAccount: Account, + agreementId?: string, + jobId?: string + ): Promise { + const computeJobsList = await this.ocean.brizo.compute( + 'get', + agreementId, + consumerAccount, + undefined, + undefined, + jobId + ) + + return computeJobsList as ComputeJob[] + } + + /** + * Returns the final result of a specific compute job published as an asset. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} agreementId The service agreement ID. + * @param {string} jobId The ID of the compute job to be stopped. + * @return {Promise} Returns the DDO of the result asset. + */ + public async result( + consumerAccount: Account, + agreementId: string, + jobId: string + ): Promise { + const computeJobsList = await this.ocean.brizo.compute( + 'get', + agreementId, + consumerAccount, + undefined, + undefined, + jobId + ) + + return computeJobsList[0] as ComputeJob + } +} diff --git a/src/ocean/utils/OceanUtils.ts b/src/ocean/utils/OceanUtils.ts index 581398b..5fda842 100644 --- a/src/ocean/utils/OceanUtils.ts +++ b/src/ocean/utils/OceanUtils.ts @@ -1,5 +1,6 @@ import { Instantiable, InstantiableConfig } from '../../Instantiable.abstract' +import { ServiceUtils } from './ServiceUtils' import { ServiceAgreement } from './ServiceAgreement' import { SignatureUtils } from './SignatureUtils' import { WebServiceConnector } from './WebServiceConnector' @@ -21,6 +22,7 @@ export class OceanUtils extends Instantiable { config.logger, config.web3 ) + instance.services = new ServiceUtils(config.ocean, config.logger) instance.signature = new SignatureUtils(config.web3, config.logger) instance.fetch = new WebServiceConnector(config.logger) @@ -33,6 +35,12 @@ export class OceanUtils extends Instantiable { */ public agreements: ServiceAgreement + /** + * Service utils. + * @type {ServiceUtils} + */ + public services: ServiceUtils + /** * Signature utils. * @type {SignatureUtils} diff --git a/src/ocean/utils/ServiceUtils.ts b/src/ocean/utils/ServiceUtils.ts new file mode 100644 index 0000000..f499757 --- /dev/null +++ b/src/ocean/utils/ServiceUtils.ts @@ -0,0 +1,115 @@ +import { DDO } from '../../ddo/DDO' +import Account from '../Account' +import { zeroX, Logger, generateId } from '../../utils' +import { Ocean } from '../../squid' +import { Condition } from '../../keeper/contracts/conditions' +import { ServiceType, Service } from '../../ddo/Service' + +export enum OrderProgressStep { + CreatingAgreement, + AgreementInitialized, + LockingPayment, + LockedPayment +} + +export class ServiceUtils { + private ocean: Ocean + private logger: Logger + + constructor(ocean: Ocean, logger: Logger) { + this.ocean = ocean + this.logger = logger + } + + public async order( + type: ServiceType, + condition: Condition, + observer: any, + consumerAccount: Account, + ddo: DDO, + provider?: string + ): Promise { + const { keeper, agreements } = this.ocean + + const agreementId = zeroX(generateId()) + const service: Service = ddo.findServiceByType(type) + const metadata = ddo.findServiceByType('metadata') + + const templateName = service.attributes.serviceAgreementTemplate.contractName + const template = keeper.getTemplateByName(templateName) + + // use price from compute service, + // otherwise always take the price from metadata + const price = + type === 'compute' + ? service.attributes.main.price + : metadata.attributes.main.price + + // eslint-disable-next-line no-async-promise-executor + const paymentFlow = new Promise(async (resolve, reject) => { + await template.getAgreementCreatedEvent(agreementId).once() + + this.logger.log('Agreement initialized') + observer.next(OrderProgressStep.AgreementInitialized) + + this.logger.log('Locking payment') + + const serviceGranted = condition + .getConditionFulfilledEvent(agreementId) + .once() + + observer.next(OrderProgressStep.LockingPayment) + const paid = await agreements.conditions.lockReward( + agreementId, + price, + consumerAccount + ) + observer.next(OrderProgressStep.LockedPayment) + + if (paid) { + this.logger.log('Payment was OK') + } else { + this.logger.error('Payment was KO') + this.logger.error('Agreement ID: ', agreementId) + this.logger.error('DID: ', ddo.id) + reject(new Error('Error on payment')) + } + + await serviceGranted + + this.logger.log(`Service ${type} granted`) + resolve() + }) + + observer.next(OrderProgressStep.CreatingAgreement) + this.logger.log('Creating agreement') + + // Get provider from didRegistry if not given in arguments + let _provider = provider + if (!provider) { + const providers = await keeper.didRegistry.getDIDProviders(ddo.shortId()) + if (providers) { + _provider = providers[0] + } + } + + await agreements.create( + ddo.id, + agreementId, + service.index, + undefined, + consumerAccount, + _provider, + consumerAccount + ) + this.logger.log('Agreement created') + + try { + await paymentFlow + } catch (e) { + throw new Error(`Error paying the ${type} service.`) + } + + return agreementId + } +} diff --git a/src/ocean/utils/WebServiceConnector.ts b/src/ocean/utils/WebServiceConnector.ts index 04c4f8f..d34e6fe 100644 --- a/src/ocean/utils/WebServiceConnector.ts +++ b/src/ocean/utils/WebServiceConnector.ts @@ -43,6 +43,15 @@ export class WebServiceConnector { }) } + public delete(url: string): Promise { + return this.fetch(url, { + method: 'DELETE', + headers: { + 'Content-type': 'application/json' + } + }) + } + public async downloadFile( url: string, destination?: string, diff --git a/src/squid.ts b/src/squid.ts index 2709151..1eb831f 100644 --- a/src/squid.ts +++ b/src/squid.ts @@ -14,7 +14,9 @@ import * as utils from './utils' export * from './ddo/DDO' export * from './ddo/MetaData' -export { OrderProgressStep, CreateProgressStep } from './ocean/OceanAssets' +export { CreateProgressStep } from './ocean/OceanAssets' +export { ComputeJob, ComputeJobStatus } from './ocean/OceanCompute' +export { OrderProgressStep } from './ocean/utils/ServiceUtils' export { OceanPlatformTechStatus, OceanPlatformTech, diff --git a/test/integration/ocean/AssetOwners.test.ts b/test/integration/ocean/AssetOwners.test.ts index 798971c..33297cb 100644 --- a/test/integration/ocean/AssetOwners.test.ts +++ b/test/integration/ocean/AssetOwners.test.ts @@ -86,13 +86,16 @@ describe('Asset Owners', () => { // Granting access try { await account2.requestTokens( - +metadata.main.price * 10 ** -(await ocean.keeper.token.decimals()) + parseInt( + ( + +metadata.main.price * + 10 ** -(await ocean.keeper.token.decimals()) + ).toString() + ) ) } catch {} - const { index } = ddo.findServiceByType('access') - - await ocean.assets.order(ddo.id, index, account2) + await ocean.assets.order(ddo.id, account2) // Access granted const { length: finalLength2 } = await ocean.assets.consumerAssets( diff --git a/test/integration/ocean/Compute.test.ts b/test/integration/ocean/Compute.test.ts new file mode 100644 index 0000000..893129e --- /dev/null +++ b/test/integration/ocean/Compute.test.ts @@ -0,0 +1,96 @@ +import { assert } from 'chai' + +import { config } from '../config' +import { Ocean, Account, DDO, MetaData, ComputeJobStatus, Config } from '../../../src' +import { getMetadata, createComputeService } from '../utils' +import { ServiceCompute } from '../../../src/ddo/Service' + +const metadataAsset = getMetadata() +const metadataAlgorithm = getMetadata(0, 'algorithm') + +const customConfig: Config = { + ...config, + // nodeUri: 'https://nile.dev-ocean.com', + // aquariusUri: 'https://aquarius.nile.dev-ocean.com', + // brizoUri: 'http://89.46.156.10:8030', + // secretStoreUri: 'https://secret-store.nile.dev-ocean.com', + // brizoAddress: '0x413c9ba0a05b8a600899b41b0c62dd661e689354', + verbose: true +} + +describe('Compute', () => { + let ocean: Ocean + let account: Account + let agreementId: string + let dataset: DDO + let algorithm: DDO + let computeService: ServiceCompute + + before(async () => { + ocean = await Ocean.getInstance(customConfig) + ;[account] = await ocean.accounts.list() + computeService = await createComputeService( + ocean, + account, + '1000', + metadataAsset.main.datePublished + ) + }) + + it('should authenticate the consumer account', async () => { + await account.authenticate() + }) + + it('should publish a dataset with a compute service object', async () => { + const stepsAsset = [] + + dataset = await ocean.assets + .create(metadataAsset as MetaData, account, [computeService]) + .next(step => stepsAsset.push(step)) + + assert.instanceOf(dataset, DDO) + assert.isDefined( + dataset.findServiceByType('compute'), + `DDO compute service doesn't exist` + ) + assert.deepEqual(stepsAsset, [0, 1, 2, 3, 4, 5, 6, 7]) + }) + + it('should publish an algorithm', async () => { + const stepsAlgorithm = [] + algorithm = await ocean.assets + .create(metadataAlgorithm as MetaData, account) + .next(step => stepsAlgorithm.push(step)) + + assert.instanceOf(algorithm, DDO) + assert.deepEqual(stepsAlgorithm, [0, 1, 2, 3, 4, 5, 6, 7]) + }) + + it('should order the compute service of the dataset', async () => { + const steps = [] + try { + await account.requestTokens( + parseInt( + ( + +computeService.attributes.main.price * + 10 ** -(await ocean.keeper.token.decimals()) + ).toString() + ) + ) + + agreementId = await ocean.compute + .order(account, dataset.id) + .next(step => steps.push(step)) + + console.log(agreementId) + assert.isDefined(agreementId) + assert.deepEqual(steps, [0, 1, 2, 3]) + } catch {} + }) + + it('should start a compute job', async () => { + const response = await ocean.compute.start(account, agreementId, algorithm.id) + + assert.equal(response.status, ComputeJobStatus.Started) + }) +}) diff --git a/test/integration/ocean/ConsumeAsset.test.ts b/test/integration/ocean/ConsumeAsset.test.ts index 67a2658..6d11835 100644 --- a/test/integration/ocean/ConsumeAsset.test.ts +++ b/test/integration/ocean/ConsumeAsset.test.ts @@ -45,8 +45,12 @@ describe('Consume Asset', () => { it('should be able to request tokens for consumer', async () => { const initialBalance = (await consumer.getBalance()).ocn - const claimedTokens = - +metadata.main.price * 10 ** -(await ocean.keeper.token.decimals()) + const claimedTokens = parseInt( + ( + +metadata.main.price * + 10 ** -(await ocean.keeper.token.decimals()) + ).toString() + ) try { await consumer.requestTokens(claimedTokens) @@ -156,13 +160,10 @@ describe('Consume Asset', () => { }) it('should consume and store the assets', async () => { - const accessService = ddo.findServiceByType('access') - const folder = '/tmp/ocean/squid-js-1' const path = await ocean.assets.consume( serviceAgreementSignatureResult.agreementId, ddo.id, - accessService.index, consumer, folder ) @@ -183,13 +184,10 @@ describe('Consume Asset', () => { }) it('should consume and store one asset', async () => { - const accessService = ddo.findServiceByType('access') - const folder = '/tmp/ocean/squid-js-2' const path = await ocean.assets.consume( serviceAgreementSignatureResult.agreementId, ddo.id, - accessService.index, consumer, folder, 1 diff --git a/test/integration/ocean/ConsumeAssetBrizo.test.ts b/test/integration/ocean/ConsumeAssetBrizo.test.ts index 722ed95..b87407a 100644 --- a/test/integration/ocean/ConsumeAssetBrizo.test.ts +++ b/test/integration/ocean/ConsumeAssetBrizo.test.ts @@ -50,34 +50,30 @@ describe('Consume Asset (Brizo)', () => { }) it('should order the asset', async () => { - const accessService = ddo.findServiceByType('access') + const steps = [] try { await consumer.requestTokens( - +metadata.main.price * 10 ** -(await ocean.keeper.token.decimals()) + parseInt( + ( + +metadata.main.price * + 10 ** -(await ocean.keeper.token.decimals()) + ).toString() + ) ) - } catch {} - const steps = [] - agreementId = await ocean.assets - .order(ddo.id, accessService.index, consumer) - .next(step => steps.push(step)) + agreementId = await ocean.assets + .order(ddo.id, consumer) + .next(step => steps.push(step)) + } catch {} assert.isDefined(agreementId) assert.deepEqual(steps, [0, 1, 2, 3]) }) it('should consume and store the assets', async () => { - const accessService = ddo.findServiceByType('access') - const folder = '/tmp/ocean/squid-js' - const path = await ocean.assets.consume( - agreementId, - ddo.id, - accessService.index, - consumer, - folder - ) + const path = await ocean.assets.consume(agreementId, ddo.id, consumer, folder) assert.include(path, folder, 'The storage path is not correct.') diff --git a/test/integration/ocean/ConsumeBigAsset.test.ts b/test/integration/ocean/ConsumeBigAsset.test.ts index a5d135e..a2e0567 100644 --- a/test/integration/ocean/ConsumeBigAsset.test.ts +++ b/test/integration/ocean/ConsumeBigAsset.test.ts @@ -50,30 +50,25 @@ xdescribe('Consume Asset (Large size)', () => { }) it('should order the asset', async () => { - const accessService = ddo.findServiceByType('access') - try { await consumer.requestTokens( - +metadata.main.price * 10 ** -(await ocean.keeper.token.decimals()) + parseInt( + ( + +metadata.main.price * + 10 ** -(await ocean.keeper.token.decimals()) + ).toString() + ) ) } catch {} - agreementId = await ocean.assets.order(ddo.id, accessService.index, consumer) + agreementId = await ocean.assets.order(ddo.id, consumer) assert.isDefined(agreementId) }) it('should consume and store the assets', async () => { - const accessService = ddo.findServiceByType('access') - const folder = '/tmp/ocean/squid-js' - const path = await ocean.assets.consume( - agreementId, - ddo.id, - accessService.index, - consumer, - folder - ) + const path = await ocean.assets.consume(agreementId, ddo.id, consumer, folder) assert.include(path, folder, 'The storage path is not correct.') diff --git a/test/integration/ocean/RegisterEscrowComputeExecutionTemplate.test.ts b/test/integration/ocean/RegisterEscrowComputeExecutionTemplate.test.ts index 735f1de..5c5dde3 100644 --- a/test/integration/ocean/RegisterEscrowComputeExecutionTemplate.test.ts +++ b/test/integration/ocean/RegisterEscrowComputeExecutionTemplate.test.ts @@ -26,7 +26,7 @@ describe('Register Escrow Compute Execution Template', () => { before(async () => { ocean = await Ocean.getInstance(config) - keeper = ocean.keeper + ;({ keeper } = ocean) template = keeper.templates.escrowComputeExecutionTemplate @@ -36,9 +36,11 @@ describe('Register Escrow Compute Execution Template', () => { consumer = (await ocean.accounts.list())[2] // Conditions - computeExecutionCondition = keeper.conditions.computeExecutionCondition - lockRewardCondition = keeper.conditions.lockRewardCondition - escrowReward = keeper.conditions.escrowReward + ;({ + computeExecutionCondition, + lockRewardCondition, + escrowReward + } = keeper.conditions) if (!ocean.keeper.dispenser) { escrowAmount = 0 @@ -274,7 +276,7 @@ describe('Register Escrow Compute Execution Template', () => { }) it('should fulfill the conditions from computing side', async () => { - await ocean.agreements.conditions.grantServiceExecution( + await ocean.agreements.conditions.grantCompute( agreementId, did, consumer.getId(), diff --git a/test/integration/ocean/Versions.test.ts b/test/integration/ocean/Versions.test.ts index 19ce70f..91e8a85 100644 --- a/test/integration/ocean/Versions.test.ts +++ b/test/integration/ocean/Versions.test.ts @@ -11,18 +11,17 @@ describe('Versions', () => { ocean = await Ocean.getInstance(config) }) - // TODO: enable again after new versions of Brizo - xit('should return the versions', async () => { + it('should return the versions', async () => { const versions = await ocean.versions.get() assert.equal(versions.aquarius.status, OceanPlatformTechStatus.Working) assert.equal(versions.brizo.status, OceanPlatformTechStatus.Working) assert.equal(versions.squid.status, OceanPlatformTechStatus.Working) - assert.deepEqual(versions.status, { - ok: true, - contracts: true, - network: true - }) + // assert.deepEqual(versions.status, { + // ok: true, + // contracts: true, + // network: true + // }) }) }) diff --git a/test/integration/utils/ddo-metadata-generator.ts b/test/integration/utils/ddo-metadata-generator.ts index ed488e4..d79a4dd 100644 --- a/test/integration/utils/ddo-metadata-generator.ts +++ b/test/integration/utils/ddo-metadata-generator.ts @@ -1,9 +1,10 @@ -import { MetaData } from '../../../src' // @oceanprotocol/squid +import { MetaData, MetaDataAlgorithm, Ocean, Account } from '../../../src' // @oceanprotocol/squid +import { ServiceCompute } from '../../../src/ddo/Service' const metadata: Partial = { main: { name: undefined, - type: 'dataset', + type: undefined, dateCreated: '2012-10-10T17:00:00Z', datePublished: '2012-10-10T17:00:00Z', author: 'Met Office', @@ -46,16 +47,61 @@ const metadata: Partial = { } } -export const generateMetadata = (name: string, price?: number): Partial => ({ - ...metadata, +const algorithmMeta: MetaDataAlgorithm = { + language: 'scala', + format: 'docker-image', + version: '0.1', + container: { + entrypoint: 'ocean-entrypoint.sh', + image: 'node', + tag: '10' + } +} + +export const generateMetadata = ( + name: string, + type?: 'dataset' | 'algorithm', + price?: number +): Partial => ({ main: { ...metadata.main, name, - price: (price || 21) + '0'.repeat(18) + type: type || 'dataset', + price: (price || 21) + '0'.repeat(18), + algorithm: type === 'algorithm' ? algorithmMeta : undefined }, additionalInformation: { ...metadata.additionalInformation } }) -export const getMetadata = (price?: number) => generateMetadata('TestAsset', price) +export const getMetadata = (price?: number, type?: 'dataset' | 'algorithm') => + generateMetadata('TestAsset', type, price) + +export const createComputeService = async ( + ocean: Ocean, + publisher: Account, + price: string, + datePublished: string +): Promise => { + const { templates } = ocean.keeper + const serviceAgreementTemplate = await templates.escrowComputeExecutionTemplate.getServiceAgreementTemplate() + + const name = 'dataAssetComputingServiceAgreement' + return { + type: 'compute', + index: 3, + serviceEndpoint: ocean.brizo.getComputeEndpoint(), + templateId: templates.escrowComputeExecutionTemplate.getId(), + attributes: { + main: { + creator: publisher.getId(), + datePublished, + price, + timeout: 3600, + name + }, + serviceAgreementTemplate + } + } +} diff --git a/test/unit/__fixtures__/ddo.json b/test/unit/__fixtures__/ddo.json new file mode 100644 index 0000000..939b4a6 --- /dev/null +++ b/test/unit/__fixtures__/ddo.json @@ -0,0 +1,459 @@ +{ + "@context": "https://w3id.org/future-method/v1", + "id": "did:op:08a429b8529856d59867503f8056903a680935a76950bb9649785cc97869a43d", + "publicKey": [ + { + "id": "did:op:123456789abcdefghi#keys-1", + "type": "RsaVerificationKey2018", + "owner": "did:op:123456789abcdefghi", + "publicKeyPem": "-----BEGIN PUBLIC KEY...END PUBLIC KEY-----\r\n" + }, + { + "id": "did:op:123456789abcdefghi#keys-2", + "type": "Ed25519VerificationKey2018", + "owner": "did:op:123456789abcdefghi", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + }, + { + "id": "did:op:123456789abcdefghi#keys-3", + "type": "RsaPublicKeyExchangeKey2018", + "owner": "did:op:123456789abcdefghi", + "publicKeyPem": "-----BEGIN PUBLIC KEY...END PUBLIC KEY-----\r\n" + } + ], + "authentication": [ + { + "type": "RsaSignatureAuthentication2018", + "publicKey": "did:op:123456789abcdefghi#keys-1" + }, + { + "type": "ieee2410Authentication2018", + "publicKey": "did:op:123456789abcdefghi#keys-2" + } + ], + "proof": { + "type": "UUIDSignature", + "created": "2016-02-08T16:02:20Z", + "creator": "did:example:8uQhQMGzWxR8vw5P3UWH1ja", + "signatureValue": "QNB13Y7Q9...1tzjn4w==" + }, + "service": [ + { + "type": "access", + "index": 0, + "serviceEndpoint": "http://mybrizo.org/api/v1/brizo/services/consume?pubKey=${pubKey}&serviceId={serviceId}&url={url}", + "templateId": "044852b2a670ade5407e78fb2863c51000000000000000000000000000000000", + "attributes": { + "main": { + "name": "dataAssetAccessServiceAgreement", + "creator": "0x36A7f3383A63279cDaF4DfC0F3ABc07d90252C6b", + "datePublished": "2019-11-15T14:11:23Z", + "price": "", + "timeout": 36000 + }, + "serviceAgreementTemplate": { + "contractName": "EscrowAccessSecretStoreTemplate", + "events": [ + { + "name": "AgreementCreated", + "actorType": "consumer", + "handler": { + "moduleName": "escrowAccessSecretStoreTemplate", + "functionName": "fulfillLockRewardCondition", + "version": "0.1" + } + } + ], + "fulfillmentOrder": [ + "lockReward.fulfill", + "accessSecretStore.fulfill", + "escrowReward.fulfill" + ], + "conditionDependency": { + "lockReward": [], + "accessSecretStore": [], + "escrowReward": ["lockReward", "accessSecretStore"] + }, + "conditions": [ + { + "name": "lockReward", + "timelock": 0, + "timeout": 0, + "contractName": "LockRewardCondition", + "functionName": "fulfill", + "parameters": [ + { + "name": "_rewardAddress", + "type": "address", + "value": "0x36A7f3383A63279cDaF4DfC0F3ABc07d90252C6b" + }, + { + "name": "_amount", + "type": "uint256", + "value": "0" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "lockRewardCondition", + "functionName": "fulfillAccessSecretStoreCondition", + "version": "0.1" + } + } + ] + }, + { + "name": "accessSecretStore", + "timelock": 0, + "timeout": 0, + "contractName": "AccessSecretStoreCondition", + "functionName": "fulfill", + "parameters": [ + { + "name": "_documentId", + "type": "bytes32", + "value": "c678e7e5963d4fdc99afea49ac221d4d4177790f30204417823319d4d35f851f" + }, + { + "name": "_grantee", + "type": "address", + "value": "" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "accessSecretStore", + "functionName": "fulfillEscrowRewardCondition", + "version": "0.1" + } + }, + { + "name": "TimedOut", + "actorType": "consumer", + "handler": { + "moduleName": "accessSecretStore", + "functionName": "fulfillEscrowRewardCondition", + "version": "0.1" + } + } + ] + }, + { + "name": "escrowReward", + "timelock": 0, + "timeout": 0, + "contractName": "EscrowReward", + "functionName": "fulfill", + "parameters": [ + { + "name": "_amount", + "type": "uint256", + "value": "0" + }, + { + "name": "_receiver", + "type": "address", + "value": "" + }, + { + "name": "_sender", + "type": "address", + "value": "" + }, + { + "name": "_lockCondition", + "type": "bytes32", + "value": "" + }, + { + "name": "_releaseCondition", + "type": "bytes32", + "value": "" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "escrowRewardCondition", + "functionName": "verifyRewardTokens", + "version": "0.1" + } + } + ] + } + ] + } + } + }, + { + "type": "compute", + "index": 1, + "serviceEndpoint": "http://localhost:8030/api/v1/brizo/services/compute", + "templateId": "", + "attributes": { + "main": { + "name": "dataAssetComputingServiceAgreement", + "creator": "0x36A7f3383A63279cDaF4DfC0F3ABc07d90252C6b", + "datePublished": "2019-04-09T19:02:11Z", + "price": "10", + "timeout": 86400, + "provider": { + "type": "Azure", + "description": "", + "environment": { + "cluster": { + "type": "Kubernetes", + "url": "http://10.0.0.17/xxx" + }, + "supportedContainers": [ + { + "image": "tensorflow/tensorflow", + "tag": "latest", + "checksum": "sha256:cb57ecfa6ebbefd8ffc7f75c0f00e57a7fa739578a429b6f72a0df19315deadc" + }, + { + "image": "tensorflow/tensorflow", + "tag": "latest", + "checksum": "sha256:cb57ecfa6ebbefd8ffc7f75c0f00e57a7fa739578a429b6f72a0df19315deadc" + } + ], + "supportedServers": [ + { + "serverId": "1", + "serverType": "xlsize", + "price": "50", + "cpu": "16", + "gpu": "0", + "memory": "128gb", + "disk": "160gb", + "maxExecutionTime": 86400 + }, + { + "serverId": "2", + "serverType": "medium", + "price": "10", + "cpu": "2", + "gpu": "0", + "memory": "8gb", + "disk": "80gb", + "maxExecutionTime": 86400 + } + ] + } + } + }, + "additionalInformation": {}, + "serviceAgreementTemplate": { + "contractName": "EscrowComputeExecutionTemplate", + "events": [ + { + "name": "AgreementCreated", + "actorType": "consumer", + "handler": { + "moduleName": "serviceExecutionTemplate", + "functionName": "fulfillLockRewardCondition", + "version": "0.1" + } + } + ], + "fulfillmentOrder": [ + "lockReward.fulfill", + "serviceExecution.fulfill", + "escrowReward.fulfill" + ], + "conditionDependency": { + "lockReward": [], + "serviceExecution": [], + "releaseReward": ["lockReward", "serviceExecution"] + }, + "conditions": [ + { + "name": "lockReward", + "timelock": 0, + "timeout": 0, + "contractName": "LockRewardCondition", + "functionName": "fulfill", + "parameters": [ + { + "name": "_rewardAddress", + "type": "address", + "value": "" + }, + { + "name": "_amount", + "type": "uint256", + "value": "" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "lockRewardCondition", + "functionName": "fulfillServiceExecutionCondition", + "version": "0.1" + } + } + ] + }, + { + "name": "serviceExecution", + "timelock": 0, + "timeout": 0, + "contractName": "ComputeExecutionCondition", + "functionName": "fulfill", + "parameters": [ + { + "name": "_documentId", + "type": "bytes32", + "value": "" + }, + { + "name": "_grantee", + "type": "address", + "value": "" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "serviceExecution", + "functionName": "fulfillServiceExecutionCondition", + "version": "0.1" + } + }, + { + "name": "TimedOut", + "actorType": "consumer", + "handler": { + "moduleName": "serviceExec", + "functionName": "fulfillServiceExecutionCondition", + "version": "0.1" + } + } + ] + }, + { + "name": "escrowReward", + "timelock": 0, + "timeout": 0, + "contractName": "EscrowReward", + "functionName": "fulfill", + "parameters": [ + { + "name": "_amount", + "type": "uint256", + "value": "" + }, + { + "name": "_receiver", + "type": "address", + "value": "" + }, + { + "name": "_sender", + "type": "address", + "value": "" + }, + { + "name": "_lockCondition", + "type": "bytes32", + "value": "" + }, + { + "name": "_releaseCondition", + "type": "bytes32", + "value": "" + } + ], + "events": [ + { + "name": "Fulfilled", + "actorType": "publisher", + "handler": { + "moduleName": "escrowRewardCondition", + "functionName": "verifyRewardTokens", + "version": "0.1" + } + } + ] + } + ] + } + } + }, + { + "type": "metadata", + "index": 2, + "serviceEndpoint": "http://myaquarius.org/api/v1/provider/assets/metadata/{did}", + "attributes": { + "main": { + "name": "UK Weather information 2011", + "type": "dataset", + "dateCreated": "2012-10-10T17:00:000Z", + "author": "Met Office", + "license": "CC-BY", + "price": "1000000000000000000", + "files": [ + { + "index": 0, + "url": "https://testocnfiles.blob.core.windows.net/testfiles/testzkp.zip", + "checksum": "085340abffh21495345af97c6b0e761", + "contentLength": "12324" + }, + { + "url": "https://testocnfiles.blob.core.windows.net/testfiles/testzkp2.zip" + } + ] + }, + "curation": { + "rating": 0.93, + "numVotes": 123, + "schema": "Binary Voting" + }, + "additionalInformation": { + "description": "Weather information of UK including temperature and humidity", + "copyrightHolder": "Met Office", + "workExample": "423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68", + "links": [ + { + "name": "Sample of Asset Data", + "type": "sample", + "url": "https://foo.com/sample.csv" + }, + { + "name": "Data Format Definition", + "type": "format", + "AssetID": "4d517500da0acb0d65a716f61330969334630363ce4a6a9d39691026ac7908ea" + } + ], + "inLanguage": "en", + "categories": ["Economy", "Data Science"], + "tags": ["weather", "uk", "2011", "temperature", "humidity"], + "updateFrequency": "yearly", + "structuredMarkup": [ + { + "uri": "http://skos.um.es/unescothes/C01194/jsonld", + "mediaType": "application/ld+json" + }, + { + "uri": "http://skos.um.es/unescothes/C01194/turtle", + "mediaType": "text/turtle" + } + ] + } + } + } + ] +} diff --git a/test/unit/mocks/Aquarius.mock.ts b/test/unit/__mocks__/Aquarius.mock.ts similarity index 100% rename from test/unit/mocks/Aquarius.mock.ts rename to test/unit/__mocks__/Aquarius.mock.ts diff --git a/test/unit/mocks/ContractBase.Mock.ts b/test/unit/__mocks__/ContractBase.Mock.ts similarity index 100% rename from test/unit/mocks/ContractBase.Mock.ts rename to test/unit/__mocks__/ContractBase.Mock.ts diff --git a/test/unit/mocks/SecretStore.mock.ts b/test/unit/__mocks__/SecretStore.mock.ts similarity index 100% rename from test/unit/mocks/SecretStore.mock.ts rename to test/unit/__mocks__/SecretStore.mock.ts diff --git a/test/unit/mocks/WebServiceConnector.mock.ts b/test/unit/__mocks__/WebServiceConnector.mock.ts similarity index 89% rename from test/unit/mocks/WebServiceConnector.mock.ts rename to test/unit/__mocks__/WebServiceConnector.mock.ts index 1a7ac55..7e7dc5e 100644 --- a/test/unit/mocks/WebServiceConnector.mock.ts +++ b/test/unit/__mocks__/WebServiceConnector.mock.ts @@ -1,4 +1,4 @@ -import WebServiceConnector from '../../src/utils/WebServiceConnector' +import { WebServiceConnector } from '../../src/ocean/utils/WebServiceConnector' // @ts-ignore export default class WebServiceConnectorMock extends WebServiceConnector { diff --git a/test/unit/ddo/DDO.test.ts b/test/unit/ddo/DDO.test.ts index 1c65d14..6f06ca8 100644 --- a/test/unit/ddo/DDO.test.ts +++ b/test/unit/ddo/DDO.test.ts @@ -7,7 +7,7 @@ import { Ocean } from '../../../src/ocean/Ocean' import config from '../config' import TestContractHandler from '../keeper/TestContractHandler' -import * as jsonDDO from '../testdata/ddo.json' +import * as jsonDDO from '../__fixtures__/ddo.json' use(spies) diff --git a/test/unit/keeper/ContractBase.test.ts b/test/unit/keeper/ContractBase.test.ts index 2e0d20f..43e9e2a 100644 --- a/test/unit/keeper/ContractBase.test.ts +++ b/test/unit/keeper/ContractBase.test.ts @@ -2,7 +2,7 @@ import { assert } from 'chai' import Account from '../../../src/ocean/Account' import { Ocean } from '../../../src/ocean/Ocean' import config from '../config' -import ContractBaseMock from '../mocks/ContractBase.Mock' +import ContractBaseMock from '../__mocks__/ContractBase.Mock' import TestContractHandler from './TestContractHandler' const wrappedContract = new ContractBaseMock('OceanToken') diff --git a/test/unit/mocks/Brizo.mock.ts b/test/unit/mocks/Brizo.mock.ts deleted file mode 100644 index 4d48e2a..0000000 --- a/test/unit/mocks/Brizo.mock.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Brizo } from '../../../src/brizo/Brizo' - -export default class BrizoMock extends Brizo { - public async initializeServiceAgreement( - did: string, - serviceAgreementId: string, - index: number, - signature: string, - consumerPublicKey: string - ): Promise { - return true - } -} diff --git a/test/unit/ocean/OceanCompute.test.ts b/test/unit/ocean/OceanCompute.test.ts new file mode 100644 index 0000000..a91156e --- /dev/null +++ b/test/unit/ocean/OceanCompute.test.ts @@ -0,0 +1,131 @@ +import { assert } from 'chai' +import sinon from 'sinon' + +import { Ocean } from '../../../src/ocean/Ocean' +import config from '../config' +import { Account } from '../../../src/squid' +import { OceanCompute, ComputeJobStatus } from '../../../src/ocean/OceanCompute' +import TestIdGenerator from '../TestIdGenerator' + +describe('OceanCompute', () => { + let ocean: Ocean + let account: Account + let compute: OceanCompute + let agreementId: string + + before(async () => { + ocean = await Ocean.getInstance(config) + ;[account] = await ocean.accounts.list() + compute = ocean.compute // eslint-disable-line prefer-destructuring + agreementId = TestIdGenerator.generatePrefixedId() + }) + + afterEach(() => { + sinon.reset() + sinon.restore() + }) + + describe('#start()', () => { + it('should start a new job', async () => { + sinon.stub(ocean.brizo, 'compute').returns([{ jobId: 'my-job-id' }] as any) + const response = await compute.start(account, agreementId, 'did:op:0xxx') + assert(response.jobId === 'my-job-id') + }) + }) + + describe('#stop()', () => { + it('should stop a job', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([{ status: ComputeJobStatus.Completed }] as any) + + const response = await compute.stop(account, agreementId, 'xxx') + assert(response.status === ComputeJobStatus.Completed) + }) + }) + + describe('#restart()', () => { + it('should restart a job', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([ + { status: ComputeJobStatus.Started, jobId: 'my-job-id' } + ] as any) + + const response = await compute.restart(account, agreementId, 'xxx') + assert(response.jobId === 'my-job-id') + }) + }) + + describe('#delete()', () => { + it('should delete a job', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([{ status: ComputeJobStatus.Deleted }] as any) + + const response = await compute.delete(account, agreementId, 'xxx') + assert(response.status === ComputeJobStatus.Deleted) + }) + }) + + describe('#status()', () => { + it('should get the status of one job', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([{ status: ComputeJobStatus.Started }] as any) + + const response = await compute.status(account, agreementId, 'xxx') + assert(response.length === 1) + assert(response[0].status === ComputeJobStatus.Started) + }) + + it('should get the status of multiple jobs', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([ + { status: ComputeJobStatus.Started }, + { status: ComputeJobStatus.Started } + ] as any) + + const response = await compute.status(account, agreementId) + assert(response.length === 2) + assert(response[0].status === ComputeJobStatus.Started) + }) + + it('should get all jobs for one owner', async () => { + sinon + .stub(ocean.brizo, 'compute') + .returns([ + { status: ComputeJobStatus.Started }, + { status: ComputeJobStatus.Started } + ] as any) + + const response = await compute.status(account) + assert(response.length === 2) + assert(response[0].status === ComputeJobStatus.Started) + }) + }) + + describe('#checkOutput()', () => { + it('should return default values', async () => { + const defaultOutput = { publishAlgorithmLog: false, publishOutput: false } + const output = compute.checkOutput(account, undefined) + assert.deepEqual(output, defaultOutput) + }) + + it('should return output values', async () => { + const newOutput = { + publishAlgorithmLog: true, + publishOutput: true, + brizoAddress: 'hello', + brizoUri: 'hello', + metadataUri: 'hello', + nodeUri: 'hello', + owner: '0xhello', + secretStoreUri: 'hello' + } + const output = compute.checkOutput(account, newOutput) + assert.deepEqual(output, newOutput) + }) + }) +}) diff --git a/test/unit/testdata/ddo.json b/test/unit/testdata/ddo.json deleted file mode 100644 index 8cffe79..0000000 --- a/test/unit/testdata/ddo.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "@context": "https://w3id.org/future-method/v1", - "id": "did:op:08a429b8529856d59867503f8056903a680935a76950bb9649785cc97869a43d", - "publicKey": [ - { - "id": "did:op:123456789abcdefghi#keys-1", - "type": "RsaVerificationKey2018", - "owner": "did:op:123456789abcdefghi", - "publicKeyPem": "-----BEGIN PUBLIC KEY...END PUBLIC KEY-----\r\n" - }, - { - "id": "did:op:123456789abcdefghi#keys-2", - "type": "Ed25519VerificationKey2018", - "owner": "did:op:123456789abcdefghi", - "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" - }, - { - "id": "did:op:123456789abcdefghi#keys-3", - "type": "RsaPublicKeyExchangeKey2018", - "owner": "did:op:123456789abcdefghi", - "publicKeyPem": "-----BEGIN PUBLIC KEY...END PUBLIC KEY-----\r\n" - } - ], - "authentication": [ - { - "type": "RsaSignatureAuthentication2018", - "publicKey": "did:op:123456789abcdefghi#keys-1" - }, - { - "type": "ieee2410Authentication2018", - "publicKey": "did:op:123456789abcdefghi#keys-2" - } - ], - "proof": { - "type": "UUIDSignature", - "created": "2016-02-08T16:02:20Z", - "creator": "did:example:8uQhQMGzWxR8vw5P3UWH1ja", - "signatureValue": "QNB13Y7Q9...1tzjn4w==" - }, - "service": [ - { - "type": "access", - "index": 0, - "serviceEndpoint": "http://mybrizo.org/api/v1/brizo/services/consume?pubKey=${pubKey}&serviceId={serviceId}&url={url}", - "purchaseEndpoint": "http://mybrizo.org/api/v1/brizo/services/access/purchase?", - "templateId": "044852b2a670ade5407e78fb2863c51000000000000000000000000000000000", - "conditions": [ - { - "name": "lockPayment", - "timeout": 0, - "conditionKey": { - "contractAddress": "0x...", - "fingerprint": "0x..." - }, - "parameters": { - "assetId": "bytes32", - "price": "integer" - }, - "events": { - "PaymentLocked": { - "actorType": ["publisher"], - "handlers": [ - { - "moduleName": "accessControl", - "functionName": "grantAccess", - "version": "0.1" - } - ] - } - } - }, - { - "name": "releasePayment", - "timeout": 0, - "conditionKey": { - "contractAddress": "0x...", - "fingerprint": "0xXXXXXXXX" - }, - "parameters": { - "assetId": "bytes32", - "price": "integer" - }, - "events": { - "PaymentReleased": { - "actorType": ["publisher"], - "handlers": [ - { - "moduleName": "serviceAgreement", - "functionName": "fulfillAgreement", - "version": "0.1" - } - ] - } - } - }, - { - "name": "grantAccess", - "timeout": 0, - "conditionKey": { - "contractAddress": "0x", - "fingerprint": "0xXXXXXXXX" - }, - "parameters": { - "assetId": "bytes32", - "documentKeyId": "bytes32" - }, - "events": { - "AccessGranted": { - "actorType": ["consumer"], - "handlers": [ - { - "moduleName": "asset", - "functionName": "consumeService", - "version": "0.1" - } - ] - } - } - }, - { - "name": "refundPayment", - "timeout": 1, - "condition_key": { - "contractAddress": "0x...", - "fingerprint": "0xXXXXXXXX" - }, - "parameters": { - "assetId": "bytes32", - "price": "int" - }, - "events": { - "PaymentRefund": { - "actorType": ["consumer"], - "handlers": [ - { - "moduleName": "serviceAgreement", - "functionName": "fulfillAgreement", - "version": "0.1" - } - ] - } - } - } - ] - }, - { - "type": "compute", - "index": 1, - "serviceEndpoint": "http://mybrizo.org/api/v1/brizo/services/compute?pubKey=${pubKey}&serviceId={serviceId}&algo={algo}&container={container}", - "templateId": "044852b2a670ade5407e78fb2863c51000000000000000000000000000000002" - }, - { - "type": "metadata", - "index": 2, - "serviceEndpoint": "http://myaquarius.org/api/v1/provider/assets/metadata/{did}", - "attributes": { - "main": { - "name": "UK Weather information 2011", - "type": "dataset", - "dateCreated": "2012-10-10T17:00:000Z", - "author": "Met Office", - "license": "CC-BY", - "price": 10, - "files": [ - { - "index": 0, - "url": "https://testocnfiles.blob.core.windows.net/testfiles/testzkp.zip", - "checksum": "085340abffh21495345af97c6b0e761", - "contentLength": "12324" - }, - { - "url": "https://testocnfiles.blob.core.windows.net/testfiles/testzkp2.zip" - } - ] - }, - "curation": { - "rating": 0.93, - "numVotes": 123, - "schema": "Binary Voting" - }, - "additionalInformation": { - "description": "Weather information of UK including temperature and humidity", - "copyrightHolder": "Met Office", - "workExample": "423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68", - "links": [ - { - "name": "Sample of Asset Data", - "type": "sample", - "url": "https://foo.com/sample.csv" - }, - { - "name": "Data Format Definition", - "type": "format", - "AssetID": "4d517500da0acb0d65a716f61330969334630363ce4a6a9d39691026ac7908ea" - } - ], - "inLanguage": "en", - "categories": ["Economy", "Data Science"], - "tags": ["weather", "uk", "2011", "temperature", "humidity"], - "updateFrequency": "yearly", - "structuredMarkup": [ - { - "uri": "http://skos.um.es/unescothes/C01194/jsonld", - "mediaType": "application/ld+json" - }, - { - "uri": "http://skos.um.es/unescothes/C01194/turtle", - "mediaType": "text/turtle" - } - ] - } - } - } - ] -}