From d44f53fb6fb7ae9dbbaac61a134c7138b9e3b246 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Fri, 10 Dec 2021 00:07:52 +0200 Subject: [PATCH] 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') + } + } +}