1
0
mirror of https://github.com/oceanprotocol/ocean.js.git synced 2024-11-26 20:39:05 +01:00

Custom user parameters (#944)

* enable create / passthrough of userData & algoData

* refactor bassed on reviews

* refactor balance check async flows

* more provider logs

* more debugs for provider

* provider debug

* more provider logs

* more provider debug

* revert ci changes

* naming & typings

* Release 0.18.0-next.0

* deal with pre-releases

* clarify instructions

* fix merge errors

Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
Alex Coseru 2021-09-24 14:52:11 +03:00 committed by GitHub
parent ae1c2ff22f
commit 02cc911ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 641 additions and 121 deletions

View File

@ -111,6 +111,7 @@ jobs:
cd .. && ./scripts/waitforcontracts.sh cd .. && ./scripts/waitforcontracts.sh
- run: npm run test:integration:cover - run: npm run test:integration:cover
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: coverage name: coverage

View File

@ -8,6 +8,8 @@ on:
jobs: jobs:
npm: npm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
@ -15,6 +17,11 @@ jobs:
node-version: '16' node-version: '16'
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
- run: npm ci - run: npm ci
# pre-releases, triggered by `next` as part of git tag
- run: npm publish --tag next
if: ${{ contains(github.ref, 'next') }}
# production releases
- run: npm publish - run: npm publish
env: if: ${{ !contains(github.ref, 'next') }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -42,6 +42,7 @@ This is in alpha state and you can expect running into problems. If you run into
- [🛳 Production](#-production) - [🛳 Production](#-production)
- [⬆️ Releases](#-releases) - [⬆️ Releases](#-releases)
- [Production](#production) - [Production](#production)
- [Pre-releases](#pre-releases)
- [🏛 License](#-license) - [🏛 License](#-license)
## 📚 Prerequisites ## 📚 Prerequisites
@ -229,6 +230,16 @@ The task does the following:
For the GitHub releases steps a GitHub personal access token, exported as `GITHUB_TOKEN` is required. [Setup](https://github.com/release-it/release-it#github-releases) For the GitHub releases steps a GitHub personal access token, exported as `GITHUB_TOKEN` is required. [Setup](https://github.com/release-it/release-it#github-releases)
### Pre-Releases
For pre-releases, this is required for the first one like `v0.18.0-next.0`:
```bash
./node_modules/.bin/release-it major|minor|patch --preRelease=next
```
Further releases afterwards can be done with `npm run release` again and selecting the appropriate next version, in this case `v0.18.0-next.1` and so on.
## 🏛 License ## 🏛 License
``` ```

View File

@ -1,9 +1,23 @@
import { Metadata } from './Metadata' import { Metadata } from './Metadata'
import { Status } from './Status' import { Status } from './Status'
export interface ServiceCustomParameter {
name: string
type: string
label: string
required: boolean
options?: any
description: string
}
export interface ServiceCustomParametersRequired {
userCustomParameters?: ServiceCustomParameter[]
algoCustomParameters?: ServiceCustomParameter[]
}
export type ServiceType = 'authorization' | 'metadata' | 'access' | 'compute' export type ServiceType = 'authorization' | 'metadata' | 'access' | 'compute'
export interface ServiceCommonAttributes { export interface ServiceCommonAttributes extends ServiceCustomParametersRequired {
main: { [key: string]: any } main: { [key: string]: any }
additionalInformation?: { [key: string]: any } additionalInformation?: { [key: string]: any }
status?: Status status?: Status
@ -33,9 +47,9 @@ export interface publisherTrustedAlgorithm {
} }
export interface ServiceComputePrivacy { export interface ServiceComputePrivacy {
allowRawAlgorithm: boolean allowRawAlgorithm?: boolean
allowNetworkAccess: boolean allowNetworkAccess?: boolean
allowAllPublishedAlgorithms: boolean allowAllPublishedAlgorithms?: boolean
publisherTrustedAlgorithms?: publisherTrustedAlgorithm[] publisherTrustedAlgorithms?: publisherTrustedAlgorithm[]
} }

View File

@ -1,6 +1,11 @@
import { DDO } from '../ddo/DDO' import { DDO } from '../ddo/DDO'
import { Metadata } from '../ddo/interfaces/Metadata' import { Metadata } from '../ddo/interfaces/Metadata'
import { Service, ServiceAccess } from '../ddo/interfaces/Service' import {
Service,
ServiceAccess,
ServiceCustomParameter,
ServiceCustomParametersRequired
} from '../ddo/interfaces/Service'
import { SearchQuery } from '../metadatacache/MetadataCache' import { SearchQuery } from '../metadatacache/MetadataCache'
import { EditableMetadata } from '../ddo/interfaces/EditableMetadata' import { EditableMetadata } from '../ddo/interfaces/EditableMetadata'
import Account from './Account' import Account from './Account'
@ -9,15 +14,11 @@ import { SubscribablePromise, didNoZeroX, didPrefixed, assetResolve } from '../u
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
import { WebServiceConnector } from './utils/WebServiceConnector' import { WebServiceConnector } from './utils/WebServiceConnector'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { Provider } from '../provider/Provider' import { Provider, UserCustomParameters } from '../provider/Provider'
import { isAddress } from 'web3-utils' import { isAddress } from 'web3-utils'
import { MetadataMain } from '../ddo/interfaces' import { MetadataMain } from '../ddo/interfaces'
import { TransactionReceipt } from 'web3-core' import { TransactionReceipt } from 'web3-core'
import { import { CredentialType } from '../ddo/interfaces/Credentials'
CredentialType,
CredentialAction,
Credentials
} from '../ddo/interfaces/Credentials'
import { updateCredentialDetail, removeCredentialDetail } from './AssetsCredential' import { updateCredentialDetail, removeCredentialDetail } from './AssetsCredential'
import { Consumable } from '../ddo/interfaces/Consumable' import { Consumable } from '../ddo/interfaces/Consumable'
@ -71,7 +72,7 @@ export class Assets extends Instantiable {
* @param {String} name Token name * @param {String} name Token name
* @param {String} symbol Token symbol * @param {String} symbol Token symbol
* @param {String} providerUri * @param {String} providerUri
* @return {Promise<DDO>} * @return {SubscribablePromise<CreateProgressStep, DDO>}
*/ */
public create( public create(
metadata: Metadata, metadata: Metadata,
@ -443,17 +444,20 @@ export class Assets extends Instantiable {
* @param {String} cost number of datatokens needed for this service * @param {String} cost number of datatokens needed for this service
* @param {String} datePublished * @param {String} datePublished
* @param {Number} timeout * @param {Number} timeout
* @return {Promise<string>} service * @param {String} providerUri
* @param {ServiceCustomParametersRequired} requiredParameters
* @return {Promise<ServiceAccess>} service
*/ */
public async createAccessServiceAttributes( public async createAccessServiceAttributes(
creator: Account, creator: Account,
cost: string, cost: string,
datePublished: string, datePublished: string,
timeout = 0, timeout: number = 0,
providerUri?: string providerUri?: string,
requiredParameters?: ServiceCustomParametersRequired
): Promise<ServiceAccess> { ): Promise<ServiceAccess> {
return { const service: ServiceAccess = {
type: 'access', type: 'access',
index: 2, index: 2,
serviceEndpoint: providerUri || this.ocean.provider.url, serviceEndpoint: providerUri || this.ocean.provider.url,
@ -467,6 +471,11 @@ export class Assets extends Instantiable {
} }
} }
} }
if (requiredParameters?.userCustomParameters)
service.attributes.userCustomParameters = requiredParameters.userCustomParameters
if (requiredParameters?.algoCustomParameters)
service.attributes.algoCustomParameters = requiredParameters.algoCustomParameters
return service
} }
/** /**
@ -477,14 +486,16 @@ export class Assets extends Instantiable {
* @param {String} consumerAddress * @param {String} consumerAddress
* @param {Number} serviceIndex * @param {Number} serviceIndex
* @param {String} serviceEndpoint * @param {String} serviceEndpoint
* @param {UserCustomParameters} userCustomParameters
* @return {Promise<any>} Order details * @return {Promise<any>} Order details
*/ */
public async initialize( public async initialize(
asset: DDO | string, asset: DDO | string,
serviceType: string, serviceType: string,
consumerAddress: string, consumerAddress: string,
serviceIndex = -1, serviceIndex: number = -1,
serviceEndpoint: string serviceEndpoint: string,
userCustomParameters?: UserCustomParameters
): Promise<any> { ): Promise<any> {
const provider = await Provider.getInstance(this.instanceConfig) const provider = await Provider.getInstance(this.instanceConfig)
await provider.setBaseUrl(serviceEndpoint) await provider.setBaseUrl(serviceEndpoint)
@ -492,7 +503,8 @@ export class Assets extends Instantiable {
asset, asset,
serviceIndex, serviceIndex,
serviceType, serviceType,
consumerAddress consumerAddress,
userCustomParameters
) )
if (res === null) return null if (res === null) return null
const providerData = JSON.parse(res) const providerData = JSON.parse(res)
@ -507,15 +519,17 @@ export class Assets extends Instantiable {
* @param {Number} serviceIndex * @param {Number} serviceIndex
* @param {String} mpAddress Marketplace fee collector address * @param {String} mpAddress Marketplace fee collector address
* @param {String} consumerAddress Optionally, if the consumer is another address than payer * @param {String} consumerAddress Optionally, if the consumer is another address than payer
* @param {UserCustomParameters} userCustomParameters
* @return {Promise<String>} transactionHash of the payment * @return {Promise<String>} transactionHash of the payment
*/ */
public async order( public async order(
asset: DDO | string, asset: DDO | string,
serviceType: string, serviceType: string,
payerAddress: string, payerAddress: string,
serviceIndex = -1, serviceIndex: number = -1,
mpAddress?: string, mpAddress?: string,
consumerAddress?: string, consumerAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true searchPreviousOrders = true
): Promise<string> { ): Promise<string> {
let service: Service let service: Service
@ -533,13 +547,25 @@ export class Assets extends Instantiable {
service = await this.getServiceByIndex(ddo, serviceIndex) service = await this.getServiceByIndex(ddo, serviceIndex)
serviceType = service.type serviceType = service.type
} }
// TODO validate userCustomParameters
if (
!(await this.isUserCustomParametersValid(
service.attributes.userCustomParameters,
userCustomParameters
))
) {
throw new Error(
`Order asset failed, Missing required fiels in userCustomParameters`
)
}
try { try {
const providerData = await this.initialize( const providerData = await this.initialize(
ddo, ddo,
serviceType, serviceType,
payerAddress, payerAddress,
serviceIndex, serviceIndex,
service.serviceEndpoint service.serviceEndpoint,
userCustomParameters
) )
if (!providerData) if (!providerData)
throw new Error( throw new Error(
@ -732,4 +758,25 @@ export class Assets extends Instantiable {
*/ */
return { status, message, result } return { status, message, result }
} }
/**
* Validate custom user parameters (user & algorithms)
* @param {ServiceCustomParameter[]} serviceCustomParameters
* @param {UserCustomParameters} userCustomParameters
* @return {Promise<Boolean>}
*/
public async isUserCustomParametersValid(
serviceCustomParameters: ServiceCustomParameter[],
userCustomParameters?: UserCustomParameters
): Promise<boolean> {
if (serviceCustomParameters)
for (const data of serviceCustomParameters) {
const keyname = data.name
if (!userCustomParameters || !userCustomParameters[keyname]) {
this.logger.error('Missing key: ' + keyname + ' from customData')
return false
}
}
return true
}
} }

View File

@ -3,7 +3,8 @@ import {
Service, Service,
ServiceComputePrivacy, ServiceComputePrivacy,
ServiceCompute, ServiceCompute,
publisherTrustedAlgorithm publisherTrustedAlgorithm,
ServiceCustomParametersRequired
} from '../ddo/interfaces/Service' } from '../ddo/interfaces/Service'
import Account from './Account' import Account from './Account'
import { SubscribablePromise, assetResolve, AssetResolved } from '../utils' import { SubscribablePromise, assetResolve, AssetResolved } from '../utils'
@ -14,7 +15,7 @@ import {
ComputeInput, ComputeInput,
ComputeAlgorithm ComputeAlgorithm
} from './interfaces/Compute' } from './interfaces/Compute'
import { Provider } from '../provider/Provider' import { Provider, UserCustomParameters } from '../provider/Provider'
import { SHA256 } from 'crypto-js' import { SHA256 } from 'crypto-js'
export enum OrderProgressStep { export enum OrderProgressStep {
@ -121,6 +122,18 @@ export class Compute extends Instantiable {
const { did, ddo } = await assetResolve(asset, this.ocean) const { did, ddo } = await assetResolve(asset, this.ocean)
const service = ddo.findServiceByType('compute') const service = ddo.findServiceByType('compute')
const { serviceEndpoint } = service const { serviceEndpoint } = service
if (algorithm.serviceIndex) {
const { ddo } = await assetResolve(algorithm.did, this.ocean)
const algoService: Service = ddo.findServiceById(algorithm.serviceIndex)
if (
!(await this.ocean.assets.isUserCustomParametersValid(
algoService.attributes.algoCustomParameters,
algorithm.algoCustomParameters
))
) {
return null
}
}
if (did && txId) { if (did && txId) {
const provider = await Provider.getInstance(this.instanceConfig) const provider = await Provider.getInstance(this.instanceConfig)
await provider.setBaseUrl(serviceEndpoint) await provider.setBaseUrl(serviceEndpoint)
@ -343,11 +356,12 @@ export class Compute extends Instantiable {
providerAttributes: any, providerAttributes: any,
computePrivacy?: ServiceComputePrivacy, computePrivacy?: ServiceComputePrivacy,
timeout?: number, timeout?: number,
providerUri?: string providerUri?: string,
requiredCustomParameters?: ServiceCustomParametersRequired
): ServiceCompute { ): ServiceCompute {
const name = 'dataAssetComputingService' const name = 'dataAssetComputingService'
if (!timeout) timeout = 3600 if (!timeout) timeout = 3600
const service = { const service: ServiceCompute = {
type: 'compute', type: 'compute',
index: 3, index: 3,
serviceEndpoint: providerUri || this.ocean.provider.url, serviceEndpoint: providerUri || this.ocean.provider.url,
@ -365,6 +379,12 @@ export class Compute extends Instantiable {
} }
if (computePrivacy) service.attributes.main.privacy = computePrivacy if (computePrivacy) service.attributes.main.privacy = computePrivacy
if (requiredCustomParameters?.userCustomParameters)
service.attributes.userCustomParameters =
requiredCustomParameters.userCustomParameters
if (requiredCustomParameters?.algoCustomParameters)
service.attributes.algoCustomParameters =
requiredCustomParameters.algoCustomParameters
return service as ServiceCompute return service as ServiceCompute
} }
@ -508,7 +528,7 @@ export class Compute extends Instantiable {
* @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset. * @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset.
* @param {string} algorithmServiceIndex The index of the service in the algorithm * @param {string} algorithmServiceIndex The index of the service in the algorithm
* @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified. * @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified.
* @return {Promise<string>} Returns the transaction details * @return {SubscribablePromise<OrderProgressStep, string>} Returns the transaction details
* *
* Note: algorithmDid and algorithmMeta are optional, but if they are not passed, * Note: algorithmDid and algorithmMeta are optional, but if they are not passed,
* you can end up in the situation that you are ordering and paying for your compute job, * you can end up in the situation that you are ordering and paying for your compute job,
@ -521,6 +541,7 @@ export class Compute extends Instantiable {
algorithm: ComputeAlgorithm, algorithm: ComputeAlgorithm,
mpAddress?: string, mpAddress?: string,
computeAddress?: string, computeAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true searchPreviousOrders = true
): SubscribablePromise<OrderProgressStep, string> { ): SubscribablePromise<OrderProgressStep, string> {
return new SubscribablePromise(async (observer) => { return new SubscribablePromise(async (observer) => {
@ -543,6 +564,7 @@ export class Compute extends Instantiable {
-1, -1,
mpAddress, mpAddress,
computeAddress, computeAddress,
userCustomParameters,
searchPreviousOrders searchPreviousOrders
) )
return order return order
@ -570,6 +592,7 @@ export class Compute extends Instantiable {
serviceIndex = -1, serviceIndex = -1,
mpAddress?: string, mpAddress?: string,
consumerAddress?: string, consumerAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true searchPreviousOrders = true
): Promise<string> { ): Promise<string> {
// this is only a convienince function, which calls ocean.assets.order // this is only a convienince function, which calls ocean.assets.order
@ -581,6 +604,7 @@ export class Compute extends Instantiable {
serviceIndex, serviceIndex,
mpAddress, mpAddress,
consumerAddress, consumerAddress,
userCustomParameters,
searchPreviousOrders searchPreviousOrders
) )
} catch (error) { } catch (error) {

View File

@ -43,4 +43,5 @@ export interface ComputeAlgorithm {
meta?: MetadataAlgorithm meta?: MetadataAlgorithm
transferTxId?: string transferTxId?: string
dataToken?: string dataToken?: string
algoCustomParameters?: { [key: string]: any }
} }

View File

@ -18,6 +18,10 @@ export interface ServiceEndpoint {
urlPath: string urlPath: string
} }
export interface UserCustomParameters {
[key: string]: any
}
/** /**
* Provides an interface for provider service. * Provides an interface for provider service.
* Provider service is the technical component executed * Provider service is the technical component executed
@ -188,7 +192,8 @@ export class Provider extends Instantiable {
asset: DDO | string, asset: DDO | string,
serviceIndex: number, serviceIndex: number,
serviceType: string, serviceType: string,
consumerAddress: string consumerAddress: string,
userCustomParameters?: UserCustomParameters
): Promise<string> { ): Promise<string> {
const { did, ddo } = await assetResolve(asset, this.ocean) const { did, ddo } = await assetResolve(asset, this.ocean)
let initializeUrl = this.getInitializeEndpoint() let initializeUrl = this.getInitializeEndpoint()
@ -200,6 +205,8 @@ export class Provider extends Instantiable {
initializeUrl += `&serviceType=${serviceType}` initializeUrl += `&serviceType=${serviceType}`
initializeUrl += `&dataToken=${ddo.dataToken}` initializeUrl += `&dataToken=${ddo.dataToken}`
initializeUrl += `&consumerAddress=${consumerAddress}` initializeUrl += `&consumerAddress=${consumerAddress}`
if (userCustomParameters)
initializeUrl += '&userdata=' + encodeURI(JSON.stringify(userCustomParameters))
try { try {
const response = await this.ocean.utils.fetch.get(initializeUrl) const response = await this.ocean.utils.fetch.get(initializeUrl)
return await response.text() return await response.text()
@ -218,7 +225,8 @@ export class Provider extends Instantiable {
destination: string, destination: string,
account: Account, account: Account,
files: File[], files: File[],
index = -1 index = -1,
userCustomParameters?: UserCustomParameters
): Promise<any> { ): Promise<any> {
await this.getNonce(account.getId()) await this.getNonce(account.getId())
const signature = await this.createSignature(account, did + this.nonce) const signature = await this.createSignature(account, did + this.nonce)
@ -236,7 +244,8 @@ export class Provider extends Instantiable {
consumeUrl += `&transferTxId=${txId}` consumeUrl += `&transferTxId=${txId}`
consumeUrl += `&consumerAddress=${account.getId()}` consumeUrl += `&consumerAddress=${account.getId()}`
consumeUrl += `&signature=${signature}` consumeUrl += `&signature=${signature}`
if (userCustomParameters)
consumeUrl += '&userdata=' + encodeURI(JSON.stringify(userCustomParameters))
try { try {
!destination !destination
? await this.ocean.utils.fetch.downloadFileBrowser(consumeUrl) ? await this.ocean.utils.fetch.downloadFileBrowser(consumeUrl)
@ -262,7 +271,8 @@ export class Provider extends Instantiable {
serviceIndex?: string, serviceIndex?: string,
serviceType?: string, serviceType?: string,
tokenAddress?: string, tokenAddress?: string,
additionalInputs?: ComputeInput[] additionalInputs?: ComputeInput[],
userCustomParameters?: UserCustomParameters
): Promise<ComputeJob | ComputeJob[]> { ): Promise<ComputeJob | ComputeJob[]> {
const address = consumerAccount.getId() const address = consumerAccount.getId()
await this.getNonce(consumerAccount.getId()) await this.getNonce(consumerAccount.getId())
@ -291,6 +301,9 @@ export class Provider extends Instantiable {
if (tokenAddress) payload.dataToken = tokenAddress if (tokenAddress) payload.dataToken = tokenAddress
if (additionalInputs) payload.additionalInputs = additionalInputs if (additionalInputs) payload.additionalInputs = additionalInputs
if (userCustomParameters) payload.userData = userCustomParameters
if (algorithm.algoCustomParameters)
payload.algouserdata = algorithm.algoCustomParameters
const path = this.getComputeStartEndpoint() const path = this.getComputeStartEndpoint()
? this.getComputeStartEndpoint().urlPath ? this.getComputeStartEndpoint().urlPath
: null : null

View File

@ -42,6 +42,7 @@ describe('Compute flow', () => {
let algorithmAssetwithCompute: DDO let algorithmAssetwithCompute: DDO
let algorithmAssetRemoteProvider: DDO let algorithmAssetRemoteProvider: DDO
let algorithmAssetRemoteProviderWithCompute: DDO let algorithmAssetRemoteProviderWithCompute: DDO
let algorithmAssetWithCustomData: DDO
let contracts: TestContractHandler let contracts: TestContractHandler
let datatoken: DataTokens let datatoken: DataTokens
let tokenAddress: string let tokenAddress: string
@ -54,6 +55,7 @@ describe('Compute flow', () => {
let tokenAddressAlgorithmRemoteProviderWithCompute: string let tokenAddressAlgorithmRemoteProviderWithCompute: string
let tokenAddressAdditional1: string let tokenAddressAdditional1: string
let tokenAddressAdditional2: string let tokenAddressAdditional2: string
let tokenAddressWithCustomData: string
let price: string let price: string
let ocean: Ocean let ocean: Ocean
let data: { t: number; url: string } let data: { t: number; url: string }
@ -209,6 +211,18 @@ describe('Compute flow', () => {
'Add2' 'Add2'
) )
assert(tokenAddressAdditional2 != null, 'Creation of tokenAddressAdditional2 failed') assert(tokenAddressAdditional2 != null, 'Creation of tokenAddressAdditional2 failed')
tokenAddressWithCustomData = await datatoken.create(
blob,
alice.getId(),
'10000000000',
'WCD',
'WCD'
)
assert(
tokenAddressWithCustomData != null,
'Creation of tokenAddressWithCustomData failed'
)
}) })
it('Generates metadata', async () => { it('Generates metadata', async () => {
@ -704,6 +718,95 @@ describe('Compute flow', () => {
) )
}) })
it('should publish an algorithm whith CustomData', async () => {
const assetWithCustomData: Metadata = {
main: {
type: 'algorithm',
name: 'Test Algo with CustomData',
dateCreated: dateCreated,
datePublished: dateCreated,
author: 'DevOps',
license: 'CC-BY',
files: [
{
url: 'https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js',
contentType: 'text/js',
encoding: 'UTF-8'
}
],
algorithm: {
language: 'js',
format: 'docker-image',
version: '0.1',
container: {
entrypoint: 'node $ALGO',
image: 'node',
tag: '10'
}
}
}
}
const customdata = {
userCustomParameters: [
{
name: 'firstname',
type: 'text',
label: 'Your first name',
required: true,
description: 'Your name'
},
{
name: 'lastname',
type: 'text',
label: 'Your last name',
required: false,
description: 'Your last name'
}
],
algoCustomParameters: [
{
name: 'iterations',
type: 'number',
label: 'Iterations',
required: true,
description: 'No of passes'
},
{
name: 'chunk',
type: 'number',
label: 'Chunks',
required: false,
description: 'No of chunks'
}
]
}
const service1 = await ocean.assets.createAccessServiceAttributes(
alice,
price,
dateCreated,
0,
null,
customdata
)
algorithmAssetWithCustomData = await ocean.assets.create(
assetWithCustomData,
alice,
[service1],
tokenAddressWithCustomData
)
assert(
algorithmAssetWithCustomData.dataToken === tokenAddressWithCustomData,
'algorithmAssetWithCustomData.dataToken !== tokenAddressWithCustomData'
)
const storeTx = await ocean.onChainMetadata.publish(
algorithmAssetWithCustomData.id,
algorithmAssetWithCustomData,
alice.getId()
)
assert(storeTx)
await ocean.metadataCache.waitForAqua(algorithmAssetWithCustomData.id)
})
it('Alice mints 100 DTs and tranfers them to the compute marketplace', async () => { it('Alice mints 100 DTs and tranfers them to the compute marketplace', async () => {
await datatoken.mint(tokenAddress, alice.getId(), tokenAmount) await datatoken.mint(tokenAddress, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressNoRawAlgo, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressNoRawAlgo, alice.getId(), tokenAmount)
@ -719,90 +822,98 @@ describe('Compute flow', () => {
) )
await datatoken.mint(tokenAddressAdditional1, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressAdditional1, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressAdditional2, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressAdditional2, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressWithCustomData, alice.getId(), tokenAmount)
}) })
it('Bob gets datatokens from Alice to be able to try the compute service', async () => { it('Bob gets datatokens from Alice to be able to try the compute service', async () => {
let balance
const dTamount = '200' const dTamount = '200'
await datatoken await datatoken.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
.transfer(tokenAddress, bob.getId(), dTamount, alice.getId()) balance = await datatoken.balance(tokenAddress, bob.getId())
.then(async () => { assert(balance.toString() === dTamount.toString())
const balance = await datatoken.balance(tokenAddress, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressNoRawAlgo, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressNoRawAlgo, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressWithTrustedAlgo, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressWithTrustedAlgo, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken await datatoken.transfer(tokenAddressNoRawAlgo, bob.getId(), dTamount, alice.getId())
.transfer(tokenAddressWithBogusProvider, bob.getId(), dTamount, alice.getId()) balance = await datatoken.balance(tokenAddressNoRawAlgo, bob.getId())
.then(async () => { assert(balance.toString() === dTamount.toString())
const balance = await datatoken.balance(
tokenAddressWithBogusProvider,
bob.getId()
)
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressAlgorithm, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressAlgorithm, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressAlgorithmwithCompute, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(
tokenAddressAlgorithmwithCompute,
bob.getId()
)
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressAlgorithmRemoteProvider, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(
tokenAddressAlgorithmRemoteProvider,
bob.getId()
)
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(
tokenAddressAlgorithmRemoteProviderWithCompute,
bob.getId(),
dTamount,
alice.getId()
)
.then(async () => {
const balance = await datatoken.balance(
tokenAddressAlgorithmRemoteProviderWithCompute,
bob.getId()
)
assert(balance.toString() === dTamount.toString())
})
await datatoken await datatoken.transfer(
.transfer(tokenAddressAdditional1, bob.getId(), dTamount, alice.getId()) tokenAddressWithTrustedAlgo,
.then(async () => { bob.getId(),
const balance = await datatoken.balance(tokenAddressAdditional1, bob.getId()) dTamount,
assert(balance.toString() === dTamount.toString()) alice.getId()
}) )
balance = await datatoken.balance(tokenAddressWithTrustedAlgo, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken await datatoken.transfer(
.transfer(tokenAddressAdditional2, bob.getId(), dTamount, alice.getId()) tokenAddressWithBogusProvider,
.then(async () => { bob.getId(),
const balance = await datatoken.balance(tokenAddressAdditional2, bob.getId()) dTamount,
assert(balance.toString() === dTamount.toString()) alice.getId()
}) )
balance = await datatoken.balance(tokenAddressWithBogusProvider, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(tokenAddressAlgorithm, bob.getId(), dTamount, alice.getId())
balance = await datatoken.balance(tokenAddressAlgorithm, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressAlgorithmwithCompute,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressAlgorithmwithCompute, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressAlgorithmRemoteProvider,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressAlgorithmRemoteProvider, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressAlgorithmRemoteProviderWithCompute,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(
tokenAddressAlgorithmRemoteProviderWithCompute,
bob.getId()
)
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressAdditional1,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressAdditional1, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressAdditional2,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressAdditional2, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressWithCustomData,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressWithCustomData, bob.getId())
assert(balance.toString() === dTamount.toString())
}) })
it('Bob starts compute job with a raw Algo', async () => { it('Bob starts compute job with a raw Algo', async () => {
@ -1370,7 +1481,176 @@ describe('Compute flow', () => {
assert(response.status >= 1, 'Invalid response.status') assert(response.status >= 1, 'Invalid response.status')
assert(response.jobId, 'Invalid jobId') assert(response.jobId, 'Invalid jobId')
}) })
it('should not be able start a compute job with a published algo that requires userdata without providing that', async () => {
const computeService = ddo.findServiceByType('compute')
assert(algorithmAssetWithCustomData != null, 'algorithmAsset should not be null')
const serviceAlgo = algorithmAssetWithCustomData.findServiceByType('access')
// get the compute address first
computeAddress = await ocean.compute.getComputeAddress(ddo.id, computeService.index)
assert(ddo != null, 'ddo should not be null')
const algoDefinition: ComputeAlgorithm = {
did: algorithmAssetWithCustomData.id,
serviceIndex: serviceAlgo.index
}
// check if asset is orderable. otherwise, you might pay for it, but it has some algo restrictions
const allowed = await ocean.compute.isOrderable(
ddo,
computeService.index,
algoDefinition,
algorithmAssetWithCustomData
)
assert(allowed === true)
const order = await ocean.compute.orderAsset(
bob.getId(),
ddo,
computeService.index,
algoDefinition,
null, // no marketplace fee
computeAddress // CtD is the consumer of the dataset
)
assert(order != null, 'Order should not be null')
// order the algorithm, without providing userdata
try {
const orderalgo = await ocean.compute.orderAlgorithm(
algorithmAssetWithCustomData,
serviceAlgo.type,
bob.getId(),
serviceAlgo.index,
null, // no marketplace fee
computeAddress // CtD is the consumer of the dataset
)
assert(orderalgo === null, 'Order should be null')
} catch (error) {
assert(error != null, 'Order should throw error')
}
})
it('should not be able to start a compute job with a published algo that requires algodata without providing that', async () => {
const output = {}
const computeService = ddo.findServiceByType('compute')
assert(algorithmAssetWithCustomData != null, 'algorithmAsset should not be null')
const serviceAlgo = algorithmAssetWithCustomData.findServiceByType('access')
// get the compute address first
computeAddress = await ocean.compute.getComputeAddress(ddo.id, computeService.index)
assert(ddo != null, 'ddo should not be null')
const algoDefinition: ComputeAlgorithm = {
did: algorithmAssetWithCustomData.id,
serviceIndex: serviceAlgo.index
}
// check if asset is orderable. otherwise, you might pay for it, but it has some algo restrictions
const allowed = await ocean.compute.isOrderable(
ddo,
computeService.index,
algoDefinition,
algorithmAssetWithCustomData
)
assert(allowed === true)
const bobUserData = {
firstname: 'Bob',
lastname: 'Doe'
}
const order = await ocean.compute.orderAsset(
bob.getId(),
ddo,
computeService.index,
algoDefinition,
null, // no marketplace fee
computeAddress // CtD is the consumer of the dataset
)
assert(order != null, 'Order should not be null')
const orderalgo = await ocean.compute.orderAlgorithm(
algorithmAssetWithCustomData,
serviceAlgo.type,
bob.getId(),
serviceAlgo.index,
null, // no marketplace fee
computeAddress, // CtD is the consumer of the dataset
bobUserData
)
assert(orderalgo != null, 'Order should be null')
algoDefinition.transferTxId = orderalgo
algoDefinition.dataToken = algorithmAsset.dataToken
const response = await ocean.compute.start(
ddo,
order,
tokenAddress,
bob,
algoDefinition,
output,
`${computeService.index}`,
computeService.type,
undefined
)
assert(response === null, 'Compute should not start')
})
it('should be able to start a compute job with a published algo that requires algodata by providing that', async () => {
const output = {}
const computeService = ddo.findServiceByType('compute')
assert(algorithmAssetWithCustomData != null, 'algorithmAsset should not be null')
const serviceAlgo = algorithmAssetWithCustomData.findServiceByType('access')
// get the compute address first
computeAddress = await ocean.compute.getComputeAddress(ddo.id, computeService.index)
assert(ddo != null, 'ddo should not be null')
const algoDefinition: ComputeAlgorithm = {
did: algorithmAssetWithCustomData.id,
serviceIndex: serviceAlgo.index,
algoCustomParameters: {
iterations: 20,
chunk: 1
}
}
// check if asset is orderable. otherwise, you might pay for it, but it has some algo restrictions
const allowed = await ocean.compute.isOrderable(
ddo,
computeService.index,
algoDefinition,
algorithmAssetWithCustomData
)
assert(allowed === true)
const bobUserData = {
firstname: 'Bob',
lastname: 'Doe'
}
const order = await ocean.compute.orderAsset(
bob.getId(),
ddo,
computeService.index,
algoDefinition,
null, // no marketplace fee
computeAddress // CtD is the consumer of the dataset
)
assert(order != null, 'Order should not be null')
const orderalgo = await ocean.compute.orderAlgorithm(
algorithmAssetWithCustomData,
serviceAlgo.type,
bob.getId(),
serviceAlgo.index,
null, // no marketplace fee
computeAddress, // CtD is the consumer of the dataset
bobUserData
)
assert(orderalgo != null, 'Order should be null')
algoDefinition.transferTxId = orderalgo
algoDefinition.dataToken = algorithmAssetWithCustomData.dataToken
const response = await ocean.compute.start(
ddo,
order,
tokenAddress,
bob,
algoDefinition,
output,
`${computeService.index}`,
computeService.type,
undefined
)
assert(response, 'Compute error')
jobId = response.jobId
assert(response.status >= 1, 'Invalid response status')
assert(response.jobId, 'Invalid jobId')
})
it('Alice updates Compute Privacy, allowing some published algos', async () => { it('Alice updates Compute Privacy, allowing some published algos', async () => {
const computeService = ddo.findServiceByType('compute') const computeService = ddo.findServiceByType('compute')
assert(computeService, 'ComputeIndex should be >0') assert(computeService, 'ComputeIndex should be >0')

View File

@ -35,11 +35,13 @@ describe('Marketplace flow', () => {
let ddoWithCredentialsAllowList let ddoWithCredentialsAllowList
let ddoWithCredentialsDenyList let ddoWithCredentialsDenyList
let ddoWithCredentials let ddoWithCredentials
let ddoWithUserData
let asset let asset
let assetWithPool let assetWithPool
let assetWithBadUrl let assetWithBadUrl
let assetWithEncrypt let assetWithEncrypt
let assetInvalidNoName let assetInvalidNoName
let assetWithUserData
let marketplace: Account let marketplace: Account
let contracts: TestContractHandler let contracts: TestContractHandler
let datatoken: DataTokens let datatoken: DataTokens
@ -48,6 +50,7 @@ describe('Marketplace flow', () => {
let tokenAddressForBadUrlAsset: string let tokenAddressForBadUrlAsset: string
let tokenAddressEncrypted: string let tokenAddressEncrypted: string
let tokenAddressInvalidNoName: string let tokenAddressInvalidNoName: string
let tokenAddressWithUserData
let service1: ServiceAccess let service1: ServiceAccess
let price: string let price: string
let ocean: Ocean let ocean: Ocean
@ -133,6 +136,15 @@ describe('Marketplace flow', () => {
'DTA' 'DTA'
) )
assert(tokenAddressInvalidNoName != null) assert(tokenAddressInvalidNoName != null)
tokenAddressWithUserData = await datatoken.create(
blob,
alice.getId(),
'10000000000',
'AliceDT',
'DTA'
)
assert(tokenAddressWithUserData != null)
}) })
it('Generates metadata', async () => { it('Generates metadata', async () => {
@ -235,6 +247,27 @@ describe('Marketplace flow', () => {
] ]
} }
} }
assetWithUserData = {
main: {
type: 'dataset',
name: 'test-dataset-with-pools',
dateCreated: new Date(Date.now()).toISOString().split('.')[0] + 'Z', // remove milliseconds
datePublished: new Date(Date.now()).toISOString().split('.')[0] + 'Z', // remove milliseconds
author: 'oceanprotocol-team',
license: 'MIT',
files: [
{
url: 'https://s3.amazonaws.com/testfiles.oceanprotocol.com/info.0.json',
checksum: 'efb2c764274b745f5fc37f97c6b0e761',
contentLength: '4535431',
contentType: 'text/csv',
encoding: 'UTF-8',
compression: 'zip'
}
]
}
}
}) })
it('Should validate local metadata', async () => { it('Should validate local metadata', async () => {
const valid = await ocean.metadataCache.validateMetadata(asset) const valid = await ocean.metadataCache.validateMetadata(asset)
@ -342,6 +375,46 @@ describe('Marketplace flow', () => {
false false
) )
assert(storeTxWithCredentials) assert(storeTxWithCredentials)
const userdata = {
userCustomParameters: [
{
name: 'firstname',
type: 'text',
label: 'Your first name',
required: true,
description: 'Your name'
},
{
name: 'lastname',
type: 'text',
label: 'Your last name',
required: false,
description: 'Your last name'
}
]
}
const serviceWithUserData = await ocean.assets.createAccessServiceAttributes(
alice,
price,
publishedDate,
timeout,
null,
userdata
)
ddoWithUserData = await ocean.assets.create(
asset,
alice,
[serviceWithUserData],
tokenAddressWithUserData
)
const storeTxWithUserData = await ocean.onChainMetadata.publish(
ddoWithUserData.id,
ddoWithUserData,
alice.getId(),
false
)
assert(storeTxWithUserData)
// wait for all this assets to be published // wait for all this assets to be published
await ocean.metadataCache.waitForAqua(ddo.id) await ocean.metadataCache.waitForAqua(ddo.id)
await ocean.metadataCache.waitForAqua(ddoWithBadUrl.id) await ocean.metadataCache.waitForAqua(ddoWithBadUrl.id)
@ -350,6 +423,7 @@ describe('Marketplace flow', () => {
await ocean.metadataCache.waitForAqua(ddoWithCredentialsAllowList.id) await ocean.metadataCache.waitForAqua(ddoWithCredentialsAllowList.id)
await ocean.metadataCache.waitForAqua(ddoWithCredentialsDenyList.id) await ocean.metadataCache.waitForAqua(ddoWithCredentialsDenyList.id)
await ocean.metadataCache.waitForAqua(ddoWithCredentials.id) await ocean.metadataCache.waitForAqua(ddoWithCredentials.id)
await ocean.metadataCache.waitForAqua(ddoWithUserData.id)
}) })
it('Alice should fail to publish invalid dataset', async () => { it('Alice should fail to publish invalid dataset', async () => {
@ -404,6 +478,7 @@ describe('Marketplace flow', () => {
await datatoken.mint(tokenAddressForBadUrlAsset, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressForBadUrlAsset, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressEncrypted, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressEncrypted, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressWithPool, alice.getId(), tokenAmount) await datatoken.mint(tokenAddressWithPool, alice.getId(), tokenAmount)
await datatoken.mint(tokenAddressWithUserData, alice.getId(), tokenAmount)
// since we are in barge, we can do this // since we are in barge, we can do this
await datatoken.mint(ocean.pool.oceanAddress, owner.getId(), tokenAmount) await datatoken.mint(ocean.pool.oceanAddress, owner.getId(), tokenAmount)
await datatoken.transfer(ocean.pool.oceanAddress, alice.getId(), '200', owner.getId()) await datatoken.transfer(ocean.pool.oceanAddress, alice.getId(), '200', owner.getId())
@ -447,18 +522,28 @@ describe('Marketplace flow', () => {
it('Bob gets datatokens', async () => { it('Bob gets datatokens', async () => {
const dTamount = '20' const dTamount = '20'
await datatoken let balance
.transfer(tokenAddress, bob.getId(), dTamount, alice.getId()) await datatoken.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
.then(async () => { balance = await datatoken.balance(tokenAddress, bob.getId())
const balance = await datatoken.balance(tokenAddress, bob.getId()) assert(balance.toString() === dTamount.toString())
assert(balance.toString() === dTamount.toString())
}) await datatoken.transfer(
await datatoken tokenAddressForBadUrlAsset,
.transfer(tokenAddressForBadUrlAsset, bob.getId(), dTamount, alice.getId()) bob.getId(),
.then(async () => { dTamount,
const balance = await datatoken.balance(tokenAddressForBadUrlAsset, bob.getId()) alice.getId()
assert(balance.toString() === dTamount.toString()) )
}) balance = await datatoken.balance(tokenAddressForBadUrlAsset, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressWithUserData,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressWithUserData, bob.getId())
assert(balance.toString() === dTamount.toString())
}) })
it('Bob consumes asset 1', async () => { it('Bob consumes asset 1', async () => {
@ -772,4 +857,41 @@ describe('Marketplace flow', () => {
assert(consumable.status === 3) assert(consumable.status === 3)
assert(consumable.result === false) assert(consumable.result === false)
}) })
it('Bob tries to order asset with Custom Data, but he does not provide all the params', async () => {
try {
const order = await ocean.assets.order(
ddoWithUserData.id,
accessService.type,
bob.getId()
)
assert(order === null, 'Order should be null')
} catch (error) {
assert(error != null, 'Order should throw error')
}
})
it('Bob tries to order asset with Custom Data, providing all required user inputs', async () => {
const bobUserData = {
firstname: 'Bob',
lastname: 'Doe'
}
try {
const service = ddoWithUserData.findServiceByType('access')
const serviceIndex = service.index
const order = await ocean.assets.order(
ddoWithUserData.id,
accessService.type,
bob.getId(),
serviceIndex,
null,
null,
bobUserData
)
assert(order != null, 'Order should not be null')
} catch (error) {
assert(error === null, 'Order should not throw error')
}
})
}) })