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
- run: npm run test:integration:cover
- uses: actions/upload-artifact@v2
with:
name: coverage

View File

@ -8,6 +8,8 @@ on:
jobs:
npm:
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
@ -15,6 +17,11 @@ jobs:
node-version: '16'
registry-url: https://registry.npmjs.org/
- 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
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ !contains(github.ref, 'next') }}

View File

@ -42,6 +42,7 @@ This is in alpha state and you can expect running into problems. If you run into
- [🛳 Production](#-production)
- [⬆️ Releases](#-releases)
- [Production](#production)
- [Pre-releases](#pre-releases)
- [🏛 License](#-license)
## 📚 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)
### 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
```

View File

@ -1,9 +1,23 @@
import { Metadata } from './Metadata'
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 interface ServiceCommonAttributes {
export interface ServiceCommonAttributes extends ServiceCustomParametersRequired {
main: { [key: string]: any }
additionalInformation?: { [key: string]: any }
status?: Status
@ -33,9 +47,9 @@ export interface publisherTrustedAlgorithm {
}
export interface ServiceComputePrivacy {
allowRawAlgorithm: boolean
allowNetworkAccess: boolean
allowAllPublishedAlgorithms: boolean
allowRawAlgorithm?: boolean
allowNetworkAccess?: boolean
allowAllPublishedAlgorithms?: boolean
publisherTrustedAlgorithms?: publisherTrustedAlgorithm[]
}

View File

@ -1,6 +1,11 @@
import { DDO } from '../ddo/DDO'
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 { EditableMetadata } from '../ddo/interfaces/EditableMetadata'
import Account from './Account'
@ -9,15 +14,11 @@ import { SubscribablePromise, didNoZeroX, didPrefixed, assetResolve } from '../u
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
import { WebServiceConnector } from './utils/WebServiceConnector'
import BigNumber from 'bignumber.js'
import { Provider } from '../provider/Provider'
import { Provider, UserCustomParameters } from '../provider/Provider'
import { isAddress } from 'web3-utils'
import { MetadataMain } from '../ddo/interfaces'
import { TransactionReceipt } from 'web3-core'
import {
CredentialType,
CredentialAction,
Credentials
} from '../ddo/interfaces/Credentials'
import { CredentialType } from '../ddo/interfaces/Credentials'
import { updateCredentialDetail, removeCredentialDetail } from './AssetsCredential'
import { Consumable } from '../ddo/interfaces/Consumable'
@ -71,7 +72,7 @@ export class Assets extends Instantiable {
* @param {String} name Token name
* @param {String} symbol Token symbol
* @param {String} providerUri
* @return {Promise<DDO>}
* @return {SubscribablePromise<CreateProgressStep, DDO>}
*/
public create(
metadata: Metadata,
@ -443,17 +444,20 @@ export class Assets extends Instantiable {
* @param {String} cost number of datatokens needed for this service
* @param {String} datePublished
* @param {Number} timeout
* @return {Promise<string>} service
* @param {String} providerUri
* @param {ServiceCustomParametersRequired} requiredParameters
* @return {Promise<ServiceAccess>} service
*/
public async createAccessServiceAttributes(
creator: Account,
cost: string,
datePublished: string,
timeout = 0,
providerUri?: string
timeout: number = 0,
providerUri?: string,
requiredParameters?: ServiceCustomParametersRequired
): Promise<ServiceAccess> {
return {
const service: ServiceAccess = {
type: 'access',
index: 2,
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 {Number} serviceIndex
* @param {String} serviceEndpoint
* @param {UserCustomParameters} userCustomParameters
* @return {Promise<any>} Order details
*/
public async initialize(
asset: DDO | string,
serviceType: string,
consumerAddress: string,
serviceIndex = -1,
serviceEndpoint: string
serviceIndex: number = -1,
serviceEndpoint: string,
userCustomParameters?: UserCustomParameters
): Promise<any> {
const provider = await Provider.getInstance(this.instanceConfig)
await provider.setBaseUrl(serviceEndpoint)
@ -492,7 +503,8 @@ export class Assets extends Instantiable {
asset,
serviceIndex,
serviceType,
consumerAddress
consumerAddress,
userCustomParameters
)
if (res === null) return null
const providerData = JSON.parse(res)
@ -507,15 +519,17 @@ export class Assets extends Instantiable {
* @param {Number} serviceIndex
* @param {String} mpAddress Marketplace fee collector address
* @param {String} consumerAddress Optionally, if the consumer is another address than payer
* @param {UserCustomParameters} userCustomParameters
* @return {Promise<String>} transactionHash of the payment
*/
public async order(
asset: DDO | string,
serviceType: string,
payerAddress: string,
serviceIndex = -1,
serviceIndex: number = -1,
mpAddress?: string,
consumerAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true
): Promise<string> {
let service: Service
@ -533,13 +547,25 @@ export class Assets extends Instantiable {
service = await this.getServiceByIndex(ddo, serviceIndex)
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 {
const providerData = await this.initialize(
ddo,
serviceType,
payerAddress,
serviceIndex,
service.serviceEndpoint
service.serviceEndpoint,
userCustomParameters
)
if (!providerData)
throw new Error(
@ -732,4 +758,25 @@ export class Assets extends Instantiable {
*/
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,
ServiceComputePrivacy,
ServiceCompute,
publisherTrustedAlgorithm
publisherTrustedAlgorithm,
ServiceCustomParametersRequired
} from '../ddo/interfaces/Service'
import Account from './Account'
import { SubscribablePromise, assetResolve, AssetResolved } from '../utils'
@ -14,7 +15,7 @@ import {
ComputeInput,
ComputeAlgorithm
} from './interfaces/Compute'
import { Provider } from '../provider/Provider'
import { Provider, UserCustomParameters } from '../provider/Provider'
import { SHA256 } from 'crypto-js'
export enum OrderProgressStep {
@ -121,6 +122,18 @@ export class Compute extends Instantiable {
const { did, ddo } = await assetResolve(asset, this.ocean)
const service = ddo.findServiceByType('compute')
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) {
const provider = await Provider.getInstance(this.instanceConfig)
await provider.setBaseUrl(serviceEndpoint)
@ -343,11 +356,12 @@ export class Compute extends Instantiable {
providerAttributes: any,
computePrivacy?: ServiceComputePrivacy,
timeout?: number,
providerUri?: string
providerUri?: string,
requiredCustomParameters?: ServiceCustomParametersRequired
): ServiceCompute {
const name = 'dataAssetComputingService'
if (!timeout) timeout = 3600
const service = {
const service: ServiceCompute = {
type: 'compute',
index: 3,
serviceEndpoint: providerUri || this.ocean.provider.url,
@ -365,6 +379,12 @@ export class Compute extends Instantiable {
}
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
}
@ -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} 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.
* @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,
* 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,
mpAddress?: string,
computeAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true
): SubscribablePromise<OrderProgressStep, string> {
return new SubscribablePromise(async (observer) => {
@ -543,6 +564,7 @@ export class Compute extends Instantiable {
-1,
mpAddress,
computeAddress,
userCustomParameters,
searchPreviousOrders
)
return order
@ -570,6 +592,7 @@ export class Compute extends Instantiable {
serviceIndex = -1,
mpAddress?: string,
consumerAddress?: string,
userCustomParameters?: UserCustomParameters,
searchPreviousOrders = true
): Promise<string> {
// this is only a convienince function, which calls ocean.assets.order
@ -581,6 +604,7 @@ export class Compute extends Instantiable {
serviceIndex,
mpAddress,
consumerAddress,
userCustomParameters,
searchPreviousOrders
)
} catch (error) {

View File

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

View File

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

View File

@ -42,6 +42,7 @@ describe('Compute flow', () => {
let algorithmAssetwithCompute: DDO
let algorithmAssetRemoteProvider: DDO
let algorithmAssetRemoteProviderWithCompute: DDO
let algorithmAssetWithCustomData: DDO
let contracts: TestContractHandler
let datatoken: DataTokens
let tokenAddress: string
@ -54,6 +55,7 @@ describe('Compute flow', () => {
let tokenAddressAlgorithmRemoteProviderWithCompute: string
let tokenAddressAdditional1: string
let tokenAddressAdditional2: string
let tokenAddressWithCustomData: string
let price: string
let ocean: Ocean
let data: { t: number; url: string }
@ -209,6 +211,18 @@ describe('Compute flow', () => {
'Add2'
)
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 () => {
@ -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 () => {
await datatoken.mint(tokenAddress, 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(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 () => {
let balance
const dTamount = '200'
await datatoken
.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
.then(async () => {
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.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
balance = await datatoken.balance(tokenAddress, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken
.transfer(tokenAddressWithBogusProvider, bob.getId(), dTamount, alice.getId())
.then(async () => {
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.transfer(tokenAddressNoRawAlgo, bob.getId(), dTamount, alice.getId())
balance = await datatoken.balance(tokenAddressNoRawAlgo, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken
.transfer(tokenAddressAdditional1, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressAdditional1, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken.transfer(
tokenAddressWithTrustedAlgo,
bob.getId(),
dTamount,
alice.getId()
)
balance = await datatoken.balance(tokenAddressWithTrustedAlgo, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken
.transfer(tokenAddressAdditional2, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressAdditional2, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken.transfer(
tokenAddressWithBogusProvider,
bob.getId(),
dTamount,
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 () => {
@ -1370,7 +1481,176 @@ describe('Compute flow', () => {
assert(response.status >= 1, 'Invalid response.status')
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 () => {
const computeService = ddo.findServiceByType('compute')
assert(computeService, 'ComputeIndex should be >0')

View File

@ -35,11 +35,13 @@ describe('Marketplace flow', () => {
let ddoWithCredentialsAllowList
let ddoWithCredentialsDenyList
let ddoWithCredentials
let ddoWithUserData
let asset
let assetWithPool
let assetWithBadUrl
let assetWithEncrypt
let assetInvalidNoName
let assetWithUserData
let marketplace: Account
let contracts: TestContractHandler
let datatoken: DataTokens
@ -48,6 +50,7 @@ describe('Marketplace flow', () => {
let tokenAddressForBadUrlAsset: string
let tokenAddressEncrypted: string
let tokenAddressInvalidNoName: string
let tokenAddressWithUserData
let service1: ServiceAccess
let price: string
let ocean: Ocean
@ -133,6 +136,15 @@ describe('Marketplace flow', () => {
'DTA'
)
assert(tokenAddressInvalidNoName != null)
tokenAddressWithUserData = await datatoken.create(
blob,
alice.getId(),
'10000000000',
'AliceDT',
'DTA'
)
assert(tokenAddressWithUserData != null)
})
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 () => {
const valid = await ocean.metadataCache.validateMetadata(asset)
@ -342,6 +375,46 @@ describe('Marketplace flow', () => {
false
)
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
await ocean.metadataCache.waitForAqua(ddo.id)
await ocean.metadataCache.waitForAqua(ddoWithBadUrl.id)
@ -350,6 +423,7 @@ describe('Marketplace flow', () => {
await ocean.metadataCache.waitForAqua(ddoWithCredentialsAllowList.id)
await ocean.metadataCache.waitForAqua(ddoWithCredentialsDenyList.id)
await ocean.metadataCache.waitForAqua(ddoWithCredentials.id)
await ocean.metadataCache.waitForAqua(ddoWithUserData.id)
})
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(tokenAddressEncrypted, 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
await datatoken.mint(ocean.pool.oceanAddress, owner.getId(), tokenAmount)
await datatoken.transfer(ocean.pool.oceanAddress, alice.getId(), '200', owner.getId())
@ -447,18 +522,28 @@ describe('Marketplace flow', () => {
it('Bob gets datatokens', async () => {
const dTamount = '20'
await datatoken
.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddress, bob.getId())
assert(balance.toString() === dTamount.toString())
})
await datatoken
.transfer(tokenAddressForBadUrlAsset, bob.getId(), dTamount, alice.getId())
.then(async () => {
const balance = await datatoken.balance(tokenAddressForBadUrlAsset, bob.getId())
assert(balance.toString() === dTamount.toString())
})
let balance
await datatoken.transfer(tokenAddress, bob.getId(), dTamount, alice.getId())
balance = await datatoken.balance(tokenAddress, bob.getId())
assert(balance.toString() === dTamount.toString())
await datatoken.transfer(
tokenAddressForBadUrlAsset,
bob.getId(),
dTamount,
alice.getId()
)
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 () => {
@ -772,4 +857,41 @@ describe('Marketplace flow', () => {
assert(consumable.status === 3)
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')
}
})
})