From d44f53fb6fb7ae9dbbaac61a134c7138b9e3b246 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 00:07:52 +0200 Subject: [PATCH 1/5] added c2d methods types and some utils --- src/@types/Compute.ts | 53 ++++++++ src/@types/index.ts | 1 + src/provider/Provider.ts | 264 +++++++++++++++++++++++++++++++++++- src/utils/SignatureUtils.ts | 27 ++++ 4 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 src/@types/Compute.ts diff --git a/src/@types/Compute.ts b/src/@types/Compute.ts new file mode 100644 index 00000000..84ebe36f --- /dev/null +++ b/src/@types/Compute.ts @@ -0,0 +1,53 @@ +import { Metadata, MetadataAlgorithm } from './Metadata' + +export type ComputeResultType = 'algorithmLog' | 'output' + +export interface ComputeResult { + filename: string + filesize: number + type: ComputeResultType + index: number +} + +export interface ComputeJob { + owner: string + did?: string + jobId: string + dateCreated: string + dateFinished: string + status: number + statusText: string + results: ComputeResult[] + inputDID?: string[] + algoDID?: string + agreementId?: string + expireTimestamp: number +} + +export interface ComputeOutput { + publishAlgorithmLog?: boolean + publishOutput?: boolean + providerAddress?: string + providerUri?: string + metadata?: Metadata + metadataUri?: string + nodeUri?: string + owner?: string + secretStoreUri?: string + whitelist?: string[] +} + +export interface ComputeInput { + documentId: string + serviceId: number + transferTxId?: string +} + +export interface ComputeAlgorithm { + did?: string + serviceIndex?: number + meta?: MetadataAlgorithm + transferTxId?: string + dataToken?: string + algoCustomParameters?: { [key: string]: any } +} diff --git a/src/@types/index.ts b/src/@types/index.ts index f3678e36..01dba92c 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -4,3 +4,4 @@ export * from './Service' export * from './Credentials' export * from './Metadata' export * from './FileMetadata' +export * from './Compute' diff --git a/src/provider/Provider.ts b/src/provider/Provider.ts index cea9eae9..2a6dc6ff 100644 --- a/src/provider/Provider.ts +++ b/src/provider/Provider.ts @@ -1,8 +1,14 @@ import Web3 from 'web3' import { LoggerInstance } from '../utils' -import { Asset, FileMetadata } from '../@types/' +import { + Asset, + FileMetadata, + ComputeJob, + ComputeOutput, + ComputeAlgorithm +} from '../@types/' import { noZeroX } from '../utils/ConversionTypeHelper' -import { signText } from '../utils/SignatureUtils' +import { signText, signWithHash } from '../utils/SignatureUtils' export interface ServiceEndpoint { serviceName: string @@ -89,6 +95,24 @@ export class Provider { } } + public async createSignature( + web3: Web3, + accountId: string, + agreementId: string + ): Promise { + const signature = await signText(web3, noZeroX(agreementId), accountId) + return signature + } + + public async createHashSignature( + web3: Web3, + accountId: string, + message: string + ): Promise { + const signature = await signWithHash(web3, message, accountId) + return signature + } + /** Encrypt DDO using the Provider's own symmetric key * @param {string} did Identifier of the asset to be registered in ocean * @param {string} accountId Publisher address @@ -260,13 +284,239 @@ export class Provider { return destination } - public async createSignature( + /** Instruct the provider to start a compute job + */ + public async computeStart( + did: string, + consumerAddress: string, + algorithm: ComputeAlgorithm, + output?: ComputeOutput, + providerUri: string, web3: Web3, + fetchMethod: any + ): Promise { + const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) + const serviceEndpoints = await this.getServiceEndpoints( + providerUri, + providerEndpoints + ) + const computeStartUrl = this.getEndpointURL(serviceEndpoints, 'computeStart') + ? this.getEndpointURL(serviceEndpoints, 'computeStart').urlPath + : null + + const nonce = await this.getNonce( + providerUri, + consumerAddress, + fetchMethod, + providerEndpoints, + serviceEndpoints + ) + + let signatureMessage = consumerAddress + signatureMessage += (did && `${noZeroX(did)}`) || '' + signatureMessage += nonce + const signature = await this.createHashSignature( + web3, + consumerAddress, + signatureMessage + ) + + const payload = Object() + payload.consumerAddress = consumerAddress + payload.signature = signature + payload.algorithmDid = algorithm.did + payload.algorithmMeta = algorithm.meta + payload.algorithmServiceId = algorithm.serviceIndex + if (output) payload.output = output + + if (!computeStartUrl) return null + try { + const response = await fetchMethod(computeStartUrl, JSON.stringify(payload)) + if (response?.ok) { + const params = await response.json() + return params + } + console.error('Compute start failed:', response.status, response.statusText) + LoggerInstance.error('Payload was:', payload) + return null + } catch (e) { + LoggerInstance.error('Compute start failed:') + LoggerInstance.error(e) + LoggerInstance.error('Payload was:', payload) + return null + } + } + + /** Instruct the provider to stop a compute job + */ + public async computeStop( + did: string, + consumerAddress: string, + jobId: string, + providerUri: string, + web3: Web3, + fetchMethod: any + ): Promise { + const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) + const serviceEndpoints = await this.getServiceEndpoints( + providerUri, + providerEndpoints + ) + const computeStopUrl = this.getEndpointURL(serviceEndpoints, 'computeStop') + ? this.getEndpointURL(serviceEndpoints, 'computeStop').urlPath + : null + + const nonce = await this.getNonce( + providerUri, + consumerAddress, + fetchMethod, + providerEndpoints, + serviceEndpoints + ) + + let signatureMessage = consumerAddress + signatureMessage += jobId || '' + signatureMessage += (did && `${noZeroX(did)}`) || '' + signatureMessage += nonce + const signature = await this.createHashSignature( + web3, + consumerAddress, + signatureMessage + ) + + const payload = Object() + payload.signature = signature + payload.documentId = noZeroX(did) + payload.consumerAddress = consumerAddress + if (jobId) payload.jobId = jobId + + if (!computeStopUrl) return null + try { + const response = await fetchMethod(computeStopUrl, JSON.stringify(payload)) + if (response?.ok) { + const params = await response.json() + return params + } + LoggerInstance.error('Compute stop failed:', response.status, response.statusText) + LoggerInstance.error('Payload was:', payload) + return null + } catch (e) { + LoggerInstance.error('Compute stop failed:') + LoggerInstance.error(e) + LoggerInstance.error('Payload was:', payload) + return null + } + } + + public async computeStatus( + did: string, + consumerAddress: string, + jobId?: string, + providerUri: string, + web3: Web3, + fetchMethod: any + ): Promise { + const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) + const serviceEndpoints = await this.getServiceEndpoints( + providerUri, + providerEndpoints + ) + const computeStatusUrl = this.getEndpointURL(serviceEndpoints, 'computeStatus') + ? this.getEndpointURL(serviceEndpoints, 'computeStatus').urlPath + : null + + const nonce = await this.getNonce( + providerUri, + consumerAddress, + fetchMethod, + providerEndpoints, + serviceEndpoints + ) + + let signatureMessage = consumerAddress + signatureMessage += jobId || '' + signatureMessage += (did && `${noZeroX(did)}`) || '' + signatureMessage += nonce + const signature = await this.createHashSignature( + web3, + consumerAddress, + signatureMessage + ) + + let url = '?documentId=' + noZeroX(did) + url += `&consumerAddress=${consumerAddress}` + url += (signature && `&signature=${signature}`) || '' + url += (jobId && `&jobId=${jobId}`) || '' + + if (!computeStatusUrl) return null + try { + const response = await fetchMethod(computeStatusUrl + url) + if (response?.ok) { + const params = await response.json() + return params + } + LoggerInstance.error( + 'Get compute status failed:', + response.status, + response.statusText + ) + return null + } catch (e) { + LoggerInstance.error('Get compute status failed') + LoggerInstance.error(e) + return null + } + } + + public async computeResult( + jobId: string, + index: number, + destination: string, accountId: string, - agreementId: string - ): Promise { - const signature = await signText(web3, noZeroX(agreementId), accountId) - return signature + providerUri: string, + web3: Web3, + fetchMethod: any + ): Promise { + const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) + const serviceEndpoints = await this.getServiceEndpoints( + providerUri, + providerEndpoints + ) + const computeResultUrl = this.getEndpointURL(serviceEndpoints, 'computeResult') + ? this.getEndpointURL(serviceEndpoints, 'computeResult').urlPath + : null + + const nonce = await this.getNonce( + providerUri, + accountId, + fetchMethod, + providerEndpoints, + serviceEndpoints + ) + + let signatureMessage = accountId + signatureMessage += jobId + signatureMessage += String(index) + signatureMessage += nonce + const signature = await this.createHashSignature(web3, accountId, signatureMessage) + + if (!computeResultUrl) return null + let consumeUrl = computeResultUrl + consumeUrl += `?consumerAddress=${accountId}` + consumeUrl += `&jobId=${jobId}` + consumeUrl += `&index=${String(index)}` + consumeUrl += (signature && `&signature=${signature}`) || '' + + try { + !destination + ? await fetchMethod.downloadFileBrowser(consumeUrl) + : await fetchMethod.downloadFile(consumeUrl, destination, index) + } catch (e) { + LoggerInstance.error('Error getting job result') + LoggerInstance.error(e) + throw e + } + return destination } /** Check for a valid provider at URL diff --git a/src/utils/SignatureUtils.ts b/src/utils/SignatureUtils.ts index 719dbe7d..dc3dc10f 100644 --- a/src/utils/SignatureUtils.ts +++ b/src/utils/SignatureUtils.ts @@ -26,3 +26,30 @@ export async function signText( } } } + +export async function signWithHash( + web3: Web3, + text: string, + publicKey: string, + password?: string +): Promise { + const hash = web3.utils.utf8ToHex(text) + const isMetaMask = + web3 && web3.currentProvider && (web3.currentProvider as any).isMetaMask + try { + return await web3.eth.personal.sign(hash, publicKey, password) + } catch (e) { + if (isMetaMask) { + throw e + } + LoggerInstance.warn('Error on personal sign.') + LoggerInstance.warn(e) + try { + return await web3.eth.sign(hash, publicKey) + } catch (e2) { + LoggerInstance.error('Error on sign.') + LoggerInstance.error(e2) + throw new Error('Error executing personal sign') + } + } +} From 9ff909468e6a289c92eafd216a1087dc8f3efcc9 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 00:10:11 +0200 Subject: [PATCH 2/5] fixed lint --- src/provider/Provider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/provider/Provider.ts b/src/provider/Provider.ts index 2a6dc6ff..e183a7e7 100644 --- a/src/provider/Provider.ts +++ b/src/provider/Provider.ts @@ -290,10 +290,10 @@ export class Provider { did: string, consumerAddress: string, algorithm: ComputeAlgorithm, - output?: ComputeOutput, providerUri: string, web3: Web3, - fetchMethod: any + fetchMethod: any, + output?: ComputeOutput ): Promise { const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) const serviceEndpoints = await this.getServiceEndpoints( @@ -411,10 +411,10 @@ export class Provider { public async computeStatus( did: string, consumerAddress: string, - jobId?: string, providerUri: string, web3: Web3, - fetchMethod: any + fetchMethod: any, + jobId?: string ): Promise { const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) const serviceEndpoints = await this.getServiceEndpoints( From ba9cd6a6dab02a52fe97ec072b2e45cf502808e6 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 09:47:03 +0200 Subject: [PATCH 3/5] added compute delete method --- src/provider/Provider.ts | 67 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/provider/Provider.ts b/src/provider/Provider.ts index e183a7e7..0f9d537d 100644 --- a/src/provider/Provider.ts +++ b/src/provider/Provider.ts @@ -500,13 +500,13 @@ export class Provider { signatureMessage += nonce const signature = await this.createHashSignature(web3, accountId, signatureMessage) - if (!computeResultUrl) return null let consumeUrl = computeResultUrl consumeUrl += `?consumerAddress=${accountId}` consumeUrl += `&jobId=${jobId}` consumeUrl += `&index=${String(index)}` consumeUrl += (signature && `&signature=${signature}`) || '' + if (!computeResultUrl) return null try { !destination ? await fetchMethod.downloadFileBrowser(consumeUrl) @@ -519,6 +519,71 @@ export class Provider { return destination } + /** Instruct the provider to stop & delete all resources for a compute job + */ + public async computeDelete( + did: string, + consumerAddress: string, + jobId: string, + providerUri: string, + web3: Web3, + fetchMethod: any + ): Promise { + const providerEndpoints = await this.getEndpoints(providerUri, fetchMethod) + const serviceEndpoints = await this.getServiceEndpoints( + providerUri, + providerEndpoints + ) + const computeDeleteUrl = this.getEndpointURL(serviceEndpoints, 'computeDelete') + ? this.getEndpointURL(serviceEndpoints, 'computeDelete').urlPath + : null + + const nonce = await this.getNonce( + providerUri, + consumerAddress, + fetchMethod, + providerEndpoints, + serviceEndpoints + ) + + let signatureMessage = consumerAddress + signatureMessage += jobId || '' + signatureMessage += (did && `${noZeroX(did)}`) || '' + signatureMessage += nonce + const signature = await this.createHashSignature( + web3, + consumerAddress, + signatureMessage + ) + + const payload = Object() + payload.documentId = noZeroX(did) + payload.consumerAddress = consumerAddress + payload.jobId = jobId + if (signature) payload.signature = signature + + if (!computeDeleteUrl) return null + try { + const response = await fetchMethod(computeDeleteUrl, JSON.stringify(payload)) + if (response?.ok) { + const params = await response.json() + return params + } + LoggerInstance.error( + 'Delete compute job failed:', + response.status, + response.statusText + ) + LoggerInstance.error('Payload was:', payload) + return null + } catch (e) { + LoggerInstance.error('Delete compute job failed:') + LoggerInstance.error(e) + LoggerInstance.error('Payload was:', payload) + return null + } + } + /** Check for a valid provider at URL * @param {String} url provider uri address * @param {String} fetchMethod fetch client instance From 06f914907886a72f08edc17e73594c4f0d34f897 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 10:58:56 +0200 Subject: [PATCH 4/5] added docs for compute methods --- src/provider/Provider.ts | 56 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/provider/Provider.ts b/src/provider/Provider.ts index 0f9d537d..ab6b67c9 100644 --- a/src/provider/Provider.ts +++ b/src/provider/Provider.ts @@ -230,6 +230,18 @@ export class Provider { } } + /** Allows download of asset data file. + * @param {string} did + * @param {string} destination + * @param {string} accountId + * @param {FileMetadata[]} files + * @param {-1} index + * @param {string} providerUri + * @param {Web3} web3 + * @param {any} fetchMethod + * @param {UserCustomParameters} userCustomParameters + * @return {Promise} + */ public async download( did: string, destination: string, @@ -285,6 +297,14 @@ export class Provider { } /** Instruct the provider to start a compute job + * @param {string} did + * @param {string} consumerAddress + * @param {ComputeAlgorithm} algorithm + * @param {string} providerUri + * @param {Web3} web3 + * @param {any} fetchMethod + * @param {ComputeOutput} output + * @return {Promise} */ public async computeStart( did: string, @@ -347,7 +367,14 @@ export class Provider { } } - /** Instruct the provider to stop a compute job + /** Instruct the provider to Stop the execution of a to stop a compute job. + * @param {string} did + * @param {string} consumerAddress + * @param {string} jobId + * @param {string} providerUri + * @param {Web3} web3 + * @param {any} fetchMethod + * @return {Promise} */ public async computeStop( did: string, @@ -408,6 +435,15 @@ export class Provider { } } + /** Get status for a specific jobId/documentId/owner. + * @param {string} did + * @param {string} consumerAddress + * @param {string} providerUri + * @param {Web3} web3 + * @param {any} fetchMethod + * @param {string} jobId + * @return {Promise} + */ public async computeStatus( did: string, consumerAddress: string, @@ -468,6 +504,15 @@ export class Provider { } } + /** Get status for a specific jobId/documentId/owner. + * @param {string} jobId + * @param {number} index + * @param {string} providerUri + * @param {string} destination + * @param {Web3} web3 + * @param {any} fetchMethod + * @return {Promise} + */ public async computeResult( jobId: string, index: number, @@ -519,7 +564,14 @@ export class Provider { return destination } - /** Instruct the provider to stop & delete all resources for a compute job + /** Deletes a compute job. + * @param {string} did + * @param {string} consumerAddress + * @param {string} jobId + * @param {string} providerUri + * @param {Web3} web3 + * @param {any} fetchMethod + * @return {Promise} */ public async computeDelete( did: string, From cdd080a9e3c4b047d0899b05ae98e2e13f7c9e2e Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 11:01:03 +0200 Subject: [PATCH 5/5] make index optional in ComputeResult --- src/@types/Compute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/@types/Compute.ts b/src/@types/Compute.ts index 84ebe36f..e1e2b678 100644 --- a/src/@types/Compute.ts +++ b/src/@types/Compute.ts @@ -6,7 +6,7 @@ export interface ComputeResult { filename: string filesize: number type: ComputeResultType - index: number + index?: number } export interface ComputeJob {