1
0
mirror of https://github.com/oceanprotocol-archive/squid-js.git synced 2024-02-02 15:31:51 +01:00
squid-js/src/ocean/OceanAssets.ts

443 lines
14 KiB
TypeScript
Raw Normal View History

2019-06-20 00:20:09 +02:00
import { SearchQuery } from '../aquarius/Aquarius'
import { DDO } from '../ddo/DDO'
import { MetaData } from '../ddo/MetaData'
import { Service } from '../ddo/Service'
import Account from './Account'
import DID from './DID'
import {
fillConditionsWithDDO,
SubscribablePromise,
generateId,
zeroX
} from '../utils'
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
2019-01-21 17:48:40 +01:00
2019-05-07 13:08:26 +02:00
export enum CreateProgressStep {
EncryptingFiles,
FilesEncrypted,
GeneratingProof,
ProofGenerated,
RegisteringDid,
DidRegistred,
StoringDdo,
2019-06-20 00:20:09 +02:00
DdoStored
2019-05-07 13:08:26 +02:00
}
export enum OrderProgressStep {
2019-04-30 15:31:24 +02:00
CreatingAgreement,
AgreementInitialized,
2019-04-25 14:15:32 +02:00
LockingPayment,
2019-06-20 00:20:09 +02:00
LockedPayment
}
2019-01-21 17:48:40 +01:00
/**
* Assets submodule of Ocean Protocol.
*/
export class OceanAssets extends Instantiable {
2019-01-21 17:48:40 +01:00
/**
* Returns the instance of OceanAssets.
* @return {Promise<OceanAssets>}
*/
2019-06-20 00:20:09 +02:00
public static async getInstance(
config: InstantiableConfig
): Promise<OceanAssets> {
const instance = new OceanAssets()
instance.setInstanceConfig(config)
2019-01-21 17:48:40 +01:00
return instance
2019-01-21 17:48:40 +01:00
}
/**
* Returns a DDO by DID.
* @param {string} did Decentralized ID.
* @return {Promise<DDO>}
*/
public async resolve(did: string): Promise<DDO> {
const {
serviceEndpoint
} = await this.ocean.keeper.didRegistry.getAttributesByDid(did)
return this.ocean.aquarius.retrieveDDOByUrl(serviceEndpoint)
2019-01-21 17:48:40 +01:00
}
/**
* Creates a new DDO.
* @param {MetaData} metadata DDO metadata.
2019-02-14 11:26:12 +01:00
* @param {Account} publisher Publisher account.
2019-01-21 17:48:40 +01:00
* @return {Promise<DDO>}
*/
2019-06-20 00:20:09 +02:00
public create(
metadata: MetaData,
publisher: Account,
services: Service[] = []
): SubscribablePromise<CreateProgressStep, DDO> {
this.logger.log('Creating asset')
return new SubscribablePromise(async observer => {
const { secretStoreUri } = this.config
const { didRegistry, templates } = this.ocean.keeper
2019-05-07 13:08:26 +02:00
const did: DID = DID.generate()
2019-06-20 00:20:09 +02:00
this.logger.log('Encrypting files')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.EncryptingFiles)
2019-06-20 00:20:09 +02:00
const encryptedFiles = await this.ocean.secretStore.encrypt(
did.getId(),
2019-08-15 13:23:56 +02:00
metadata.main.files,
2019-06-20 00:20:09 +02:00
publisher
)
this.logger.log('Files encrypted')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.FilesEncrypted)
const serviceAgreementTemplate = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplate()
const serviceEndpoint = this.ocean.aquarius.getServiceEndpoint(did)
let serviceDefinitionIdCount = 0
// create ddo itself
const ddo: DDO = new DDO({
id: did.getDid(),
2019-06-20 00:20:09 +02:00
authentication: [
{
type: 'RsaSignatureAuthentication2018',
publicKey: did.getDid()
}
],
2019-05-07 13:08:26 +02:00
publicKey: [
{
id: did.getDid(),
2019-06-20 00:20:09 +02:00
type: 'EthereumECDSAKey',
owner: publisher.getId()
}
2019-05-07 13:08:26 +02:00
],
service: [
{
2019-08-16 14:12:31 +02:00
type: 'access',
2019-06-20 00:20:09 +02:00
creator: '',
2019-05-07 13:08:26 +02:00
purchaseEndpoint: this.ocean.brizo.getPurchaseEndpoint(),
serviceEndpoint: this.ocean.brizo.getConsumeEndpoint(),
2019-06-20 00:20:09 +02:00
name: 'dataAssetAccessServiceAgreement',
2019-05-07 13:08:26 +02:00
templateId: templates.escrowAccessSecretStoreTemplate.getAddress(),
2019-06-20 00:20:09 +02:00
serviceAgreementTemplate
2019-05-07 13:08:26 +02:00
},
{
2019-08-16 14:12:31 +02:00
type: 'authorization',
2019-06-20 00:20:09 +02:00
service: 'SecretStore',
serviceEndpoint: secretStoreUri
2019-05-07 13:08:26 +02:00
},
{
2019-08-16 14:12:31 +02:00
type: 'metadata',
2019-05-07 13:08:26 +02:00
serviceEndpoint,
metadata: {
// Default values
curation: {
rating: 0,
2019-06-20 00:20:09 +02:00
numVotes: 0
2019-05-07 13:08:26 +02:00
},
// Overwrites defaults
...metadata,
// Cleaning not needed information
2019-08-15 13:23:56 +02:00
main: {
...metadata.main,
2019-05-07 13:08:26 +02:00
contentUrls: undefined,
encryptedFiles,
2019-08-15 13:23:56 +02:00
files: metadata.main.files.map(
2019-06-20 00:20:09 +02:00
(file, index) => ({
2019-05-07 13:08:26 +02:00
...file,
index,
2019-06-20 00:20:09 +02:00
url: undefined
})
)
} as any
}
},
2019-06-20 00:20:09 +02:00
...services
2019-05-07 13:08:26 +02:00
]
// Remove duplications
.reverse()
2019-06-20 00:20:09 +02:00
.filter(
({ type }, i, list) =>
list.findIndex(({ type: t }) => t === type) === i
)
2019-05-07 13:08:26 +02:00
.reverse()
// Adding ID
2019-06-20 00:20:09 +02:00
.map(_ => ({
..._,
serviceDefinitionId: String(serviceDefinitionIdCount++)
})) as Service[]
2019-05-07 13:08:26 +02:00
})
2019-08-15 13:23:56 +02:00
// Overwrite initial service agreement conditions
2019-05-07 13:08:26 +02:00
const rawConditions = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplateConditions()
const conditions = fillConditionsWithDDO(rawConditions, ddo)
serviceAgreementTemplate.conditions = conditions
ddo.addChecksum()
2019-06-20 00:20:09 +02:00
this.logger.log('Generating proof')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.GeneratingProof)
2019-06-20 00:20:09 +02:00
await ddo.addProof(
this.ocean,
publisher.getId(),
publisher.getPassword()
)
this.logger.log('Proof generated')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.ProofGenerated)
2019-06-20 00:20:09 +02:00
this.logger.log('Registering DID')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.RegisteringDid)
await didRegistry.registerAttribute(
did.getId(),
ddo.getChecksum(),
[this.config.brizoAddress],
serviceEndpoint,
2019-06-20 00:20:09 +02:00
publisher.getId()
2019-05-07 13:08:26 +02:00
)
2019-06-20 00:20:09 +02:00
this.logger.log('DID registred')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.DidRegistred)
2019-06-20 00:20:09 +02:00
this.logger.log('Storing DDO')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.StoringDdo)
const storedDdo = await this.ocean.aquarius.storeDDO(ddo)
2019-06-20 00:20:09 +02:00
this.logger.log('DDO stored')
2019-05-07 13:08:26 +02:00
observer.next(CreateProgressStep.DdoStored)
return storedDdo
2019-01-21 17:48:40 +01:00
})
}
2019-06-20 00:20:09 +02:00
public async consume(
agreementId: string,
did: string,
serviceDefinitionId: string,
consumerAccount: Account,
resultPath: string,
index?: number,
useSecretStore?: boolean
2019-06-20 00:20:09 +02:00
): Promise<string>
public async consume(
agreementId: string,
did: string,
serviceDefinitionId: string,
consumerAccount: Account,
resultPath?: undefined | null,
index?: number,
useSecretStore?: boolean
2019-06-20 00:20:09 +02:00
): Promise<true>
2019-02-21 18:14:07 +01:00
public async consume(
2019-02-21 17:58:54 +01:00
agreementId: string,
did: string,
serviceDefinitionId: string,
consumerAccount: Account,
resultPath?: string,
index: number = -1,
useSecretStore?: boolean
2019-02-21 17:58:54 +01:00
): Promise<string | true> {
const ddo = await this.resolve(did)
2019-08-16 14:12:31 +02:00
const { metadata } = ddo.findServiceByType('metadata')
2019-02-21 17:58:54 +01:00
const accessService = ddo.findServiceById(serviceDefinitionId)
2019-08-15 13:23:56 +02:00
const { files } = metadata.main
2019-02-21 17:58:54 +01:00
2019-06-20 00:20:09 +02:00
const { serviceEndpoint } = accessService
2019-02-21 17:58:54 +01:00
if (!serviceEndpoint) {
2019-06-20 00:20:09 +02:00
throw new Error(
'Consume asset failed, service definition is missing the `serviceEndpoint`.'
)
2019-02-21 17:58:54 +01:00
}
2019-06-20 00:20:09 +02:00
this.logger.log('Consuming files')
2019-02-21 17:58:54 +01:00
2019-06-20 00:20:09 +02:00
resultPath = resultPath
? `${resultPath}/datafile.${ddo.shortId()}.${serviceDefinitionId}/`
: undefined
if (!useSecretStore) {
await this.ocean.brizo.consumeService(
agreementId,
serviceEndpoint,
consumerAccount,
files,
resultPath,
index
)
} else {
const files = await this.ocean.secretStore.decrypt(
did,
2019-08-16 14:12:31 +02:00
ddo.findServiceByType('metadata').metadata.main.encryptedFiles,
consumerAccount,
2019-08-16 14:12:31 +02:00
ddo.findServiceByType('authorization').serviceEndpoint
)
const downloads = files
2019-07-12 16:56:01 +02:00
.filter(({ index: i }) => index === -1 || index === i)
.map(({ url, index: i }) =>
this.ocean.utils.fetch.downloadFile(url, resultPath, i)
)
await Promise.all(downloads)
}
2019-06-20 00:20:09 +02:00
this.logger.log('Files consumed')
2019-02-21 17:58:54 +01:00
if (resultPath) {
return resultPath
}
return true
}
2019-01-21 17:48:40 +01:00
/**
2019-02-13 21:28:42 +01:00
* Start the purchase/order of an asset's service. Starts by signing the service agreement
* then sends the request to the publisher via the service endpoint (Brizo http service).
2019-01-21 17:48:40 +01:00
* @param {string} did Decentralized ID.
* @param {string} serviceDefinitionId Service definition ID.
* @param {Account} consumer Consumer account.
2019-02-13 13:05:08 +01:00
* @return {Promise<string>} Returns Agreement ID
2019-01-21 17:48:40 +01:00
*/
public order(
2019-01-21 17:48:40 +01:00
did: string,
serviceDefinitionId: string,
2019-06-20 00:20:09 +02:00
consumer: Account
): SubscribablePromise<OrderProgressStep, string> {
2019-06-20 00:20:09 +02:00
return new SubscribablePromise(async observer => {
const oceanAgreements = this.ocean.agreements
2019-01-21 17:48:40 +01:00
2019-04-30 15:31:24 +02:00
const agreementId = zeroX(generateId())
const ddo = await this.resolve(did)
2019-02-21 17:58:54 +01:00
2019-06-20 00:20:09 +02:00
const { keeper } = this.ocean
2019-08-16 14:12:31 +02:00
const templateName = ddo.findServiceByType('access')
2019-06-20 00:20:09 +02:00
.serviceAgreementTemplate.contractName
const template = keeper.getTemplateByName(templateName)
const accessCondition = keeper.conditions.accessSecretStoreCondition
2019-07-31 13:02:04 +02:00
// eslint-disable-next-line no-async-promise-executor
const paymentFlow = new Promise(async (resolve, reject) => {
await template.getAgreementCreatedEvent(agreementId).once()
2019-06-20 00:20:09 +02:00
this.logger.log('Agreement initialized')
observer.next(OrderProgressStep.AgreementInitialized)
2019-08-16 14:12:31 +02:00
const { metadata } = ddo.findServiceByType('metadata')
2019-06-20 00:20:09 +02:00
this.logger.log('Locking payment')
2019-06-20 00:20:09 +02:00
const accessGranted = accessCondition
.getConditionFulfilledEvent(agreementId)
.once()
2019-04-25 14:15:32 +02:00
observer.next(OrderProgressStep.LockingPayment)
2019-06-20 00:20:09 +02:00
const paid = await oceanAgreements.conditions.lockReward(
agreementId,
2019-08-15 13:23:56 +02:00
metadata.main.price,
2019-06-20 00:20:09 +02:00
consumer
)
observer.next(OrderProgressStep.LockedPayment)
if (paid) {
2019-06-20 00:20:09 +02:00
this.logger.log('Payment was OK')
} else {
2019-06-20 00:20:09 +02:00
this.logger.error('Payment was KO')
this.logger.error('Agreement ID: ', agreementId)
this.logger.error('DID: ', ddo.id)
2019-06-24 13:06:38 +02:00
reject(new Error('Error on payment'))
}
await accessGranted
2019-06-20 00:20:09 +02:00
this.logger.log('Access granted')
resolve()
})
2019-02-21 17:58:54 +01:00
2019-04-30 15:31:24 +02:00
observer.next(OrderProgressStep.CreatingAgreement)
2019-06-20 00:20:09 +02:00
this.logger.log('Creating agreement')
await oceanAgreements.create(
did,
agreementId,
serviceDefinitionId,
undefined,
consumer,
consumer
)
this.logger.log('Agreement created')
2019-01-21 17:48:40 +01:00
try {
await paymentFlow
} catch (e) {
2019-06-20 00:20:09 +02:00
throw new Error('Error paying the asset.')
}
2019-02-21 17:58:54 +01:00
return agreementId
})
2019-01-21 17:48:40 +01:00
}
2019-04-15 15:30:02 +02:00
/**
* Returns the owner of a asset.
* @param {string} did Decentralized ID.
* @return {Promise<string>} Returns Agreement ID
*/
public async owner(did: string): Promise<string> {
const ddo = await this.resolve(did)
const checksum = ddo.getChecksum()
2019-06-20 00:20:09 +02:00
const { creator, signatureValue } = ddo.proof
const signer = await this.ocean.utils.signature.verifyText(
checksum,
signatureValue
)
2019-04-15 15:30:02 +02:00
if (signer.toLowerCase() !== creator.toLowerCase()) {
2019-06-20 00:20:09 +02:00
this.logger.warn(
`Owner of ${ddo.id} doesn't match. Expected ${creator} instead of ${signer}.`
)
2019-04-15 15:30:02 +02:00
}
return creator
}
/**
* Returns the assets of a owner.
* @param {string} owner Owner address.
* @return {Promise<string[]>} List of DIDs.
*/
public async ownerAssets(owner: string) {
return this.ocean.keeper.didRegistry.getAttributesByOwner(owner)
}
/**
* Returns the assets of a consumer.
* @param {string} consumer Consumer address.
* @return {Promise<string[]>} List of DIDs.
*/
public async consumerAssets(consumer: string) {
2019-06-20 00:20:09 +02:00
return (await this.ocean.keeper.conditions.accessSecretStoreCondition.getGrantedDidByConsumer(
consumer
)).map(({ did }) => did)
}
2019-01-21 17:48:40 +01:00
/**
* Search over the assets using a query.
* @param {SearchQuery} query Query to filter the assets.
* @return {Promise<DDO[]>}
*/
2019-04-05 12:20:42 +02:00
public async query(query: SearchQuery) {
2019-03-26 10:53:22 +01:00
return this.ocean.aquarius.queryMetadata(query)
2019-01-21 17:48:40 +01:00
}
/**
* Search over the assets using a keyword.
* @param {SearchQuery} text Text to filter the assets.
* @return {Promise<DDO[]>}
*/
2019-04-05 12:20:42 +02:00
public async search(text: string) {
return this.ocean.aquarius.queryMetadataByText({
2019-01-21 17:48:40 +01:00
text,
2019-04-16 19:01:11 +02:00
page: 1,
2019-01-21 17:48:40 +01:00
offset: 100,
query: {
2019-06-20 00:20:09 +02:00
value: 1
2019-01-21 17:48:40 +01:00
},
sort: {
2019-06-20 00:20:09 +02:00
value: 1
}
2019-01-21 17:48:40 +01:00
} as SearchQuery)
}
}