2019-04-01 12:40:45 +02:00
|
|
|
import { SearchQuery } from "../aquarius/Aquarius"
|
2019-02-04 11:46:24 +01:00
|
|
|
import { DDO } from "../ddo/DDO"
|
|
|
|
import { MetaData } from "../ddo/MetaData"
|
2019-04-01 12:40:45 +02:00
|
|
|
import { Service } from "../ddo/Service"
|
2019-01-21 17:48:40 +01:00
|
|
|
import Account from "./Account"
|
|
|
|
import DID from "./DID"
|
2019-04-24 04:08:22 +02:00
|
|
|
import { fillConditionsWithDDO, noZeroX, SubscribablePromise } from "../utils"
|
2019-03-21 02:56:58 +01:00
|
|
|
import { Instantiable, InstantiableConfig } from "../Instantiable.abstract"
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
export enum OrderProgressStep {
|
2019-04-25 14:15:32 +02:00
|
|
|
Preparing,
|
2019-04-24 04:08:22 +02:00
|
|
|
Prepared,
|
2019-04-25 14:15:32 +02:00
|
|
|
SendingAgreement,
|
2019-04-24 04:08:22 +02:00
|
|
|
AgreementInitialized,
|
2019-04-25 14:15:32 +02:00
|
|
|
LockingPayment,
|
2019-04-24 04:08:22 +02:00
|
|
|
LockedPayment,
|
|
|
|
}
|
|
|
|
|
2019-01-21 17:48:40 +01:00
|
|
|
/**
|
|
|
|
* Assets submodule of Ocean Protocol.
|
|
|
|
*/
|
2019-03-21 02:56:58 +01:00
|
|
|
export class OceanAssets extends Instantiable {
|
2019-01-21 17:48:40 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the instance of OceanAssets.
|
|
|
|
* @return {Promise<OceanAssets>}
|
|
|
|
*/
|
2019-03-21 02:56:58 +01:00
|
|
|
public static async getInstance(config: InstantiableConfig): Promise<OceanAssets> {
|
|
|
|
const instance = new OceanAssets()
|
|
|
|
instance.setInstanceConfig(config)
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-03-21 02:56:58 +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 d: DID = DID.parse(did)
|
2019-03-21 02:56:58 +01:00
|
|
|
return this.ocean.aquarius.retrieveDDO(d)
|
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-02-07 11:32:50 +01:00
|
|
|
public async create(metadata: MetaData, publisher: Account, services: Service[] = []): Promise<DDO> {
|
2019-03-21 02:56:58 +01:00
|
|
|
const {secretStoreUri} = this.config
|
|
|
|
const {didRegistry, templates} = this.ocean.keeper
|
2019-01-21 17:48:40 +01:00
|
|
|
|
|
|
|
const did: DID = DID.generate()
|
|
|
|
|
2019-04-01 12:40:45 +02:00
|
|
|
const encryptedFiles = await this.ocean.secretStore.encrypt(did.getId(), metadata.base.files, publisher)
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-03-14 21:28:51 +01:00
|
|
|
const serviceAgreementTemplate = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplate()
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-03-21 02:56:58 +01:00
|
|
|
const serviceEndpoint = this.ocean.aquarius.getServiceEndpoint(did)
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-02-07 11:29:55 +01:00
|
|
|
let serviceDefinitionIdCount = 0
|
2019-01-21 17:48:40 +01:00
|
|
|
// create ddo itself
|
|
|
|
const ddo: DDO = new DDO({
|
2019-03-14 15:47:31 +01:00
|
|
|
id: did.getDid(),
|
2019-01-21 17:48:40 +01:00
|
|
|
authentication: [{
|
|
|
|
type: "RsaSignatureAuthentication2018",
|
2019-04-29 13:31:03 +02:00
|
|
|
publicKey: did.getDid(),
|
2019-02-04 11:46:24 +01:00
|
|
|
}],
|
2019-01-21 17:48:40 +01:00
|
|
|
publicKey: [
|
|
|
|
{
|
2019-04-29 13:31:03 +02:00
|
|
|
id: did.getDid(),
|
|
|
|
type: "EthereumECDSAKey",
|
|
|
|
owner: publisher.getId(),
|
2019-02-04 11:46:24 +01:00
|
|
|
},
|
2019-01-21 17:48:40 +01:00
|
|
|
],
|
2019-02-07 14:21:16 +01:00
|
|
|
service: [
|
2019-01-21 17:48:40 +01:00
|
|
|
{
|
2019-03-14 15:47:31 +01:00
|
|
|
type: "Access",
|
2019-04-29 13:31:03 +02:00
|
|
|
creator: "",
|
2019-03-21 02:56:58 +01:00
|
|
|
purchaseEndpoint: this.ocean.brizo.getPurchaseEndpoint(),
|
|
|
|
serviceEndpoint: this.ocean.brizo.getConsumeEndpoint(),
|
2019-04-29 13:31:03 +02:00
|
|
|
name: "dataAssetAccessServiceAgreement",
|
2019-03-14 15:47:31 +01:00
|
|
|
templateId: templates.escrowAccessSecretStoreTemplate.getAddress(),
|
|
|
|
serviceAgreementTemplate,
|
2019-02-04 11:46:24 +01:00
|
|
|
},
|
2019-02-15 23:40:55 +01:00
|
|
|
{
|
|
|
|
type: "Authorization",
|
2019-04-29 13:31:03 +02:00
|
|
|
service: "SecretStore",
|
2019-02-15 23:40:55 +01:00
|
|
|
serviceEndpoint: secretStoreUri,
|
|
|
|
},
|
2019-01-21 17:48:40 +01:00
|
|
|
{
|
|
|
|
type: "Metadata",
|
|
|
|
serviceEndpoint,
|
2019-02-04 17:45:49 +01:00
|
|
|
metadata: {
|
|
|
|
// Default values
|
|
|
|
curation: {
|
|
|
|
rating: 0,
|
|
|
|
numVotes: 0,
|
|
|
|
},
|
|
|
|
// Overwrites defaults
|
|
|
|
...metadata,
|
2019-02-06 13:10:24 +01:00
|
|
|
// Cleaning not needed information
|
|
|
|
base: {
|
|
|
|
...metadata.base,
|
2019-04-29 13:31:03 +02:00
|
|
|
contentUrls: undefined,
|
2019-02-07 14:21:16 +01:00
|
|
|
encryptedFiles,
|
2019-03-28 09:43:39 +01:00
|
|
|
files: metadata.base.files
|
|
|
|
.map((file, index) => ({
|
|
|
|
...file,
|
|
|
|
index,
|
|
|
|
url: undefined,
|
|
|
|
})),
|
2019-02-06 13:37:31 +01:00
|
|
|
} as any,
|
2019-02-04 17:45:49 +01:00
|
|
|
},
|
2019-02-04 11:46:24 +01:00
|
|
|
},
|
2019-04-29 13:31:03 +02:00
|
|
|
...services,
|
2019-02-15 23:40:55 +01:00
|
|
|
]
|
|
|
|
// Remove duplications
|
|
|
|
.reverse()
|
|
|
|
.filter(({type}, i, list) => list.findIndex(({type: t}) => t === type) === i)
|
2019-04-29 13:31:03 +02:00
|
|
|
.reverse()
|
|
|
|
// Adding ID
|
|
|
|
.map((_) => ({..._, serviceDefinitionId: String(serviceDefinitionIdCount++)})) as Service[],
|
2019-01-21 17:48:40 +01:00
|
|
|
})
|
|
|
|
|
2019-03-14 15:47:31 +01:00
|
|
|
// Overwritte initial service agreement conditions
|
2019-03-14 21:28:51 +01:00
|
|
|
const rawConditions = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplateConditions()
|
|
|
|
const conditions = fillConditionsWithDDO(rawConditions, ddo)
|
2019-03-14 15:47:31 +01:00
|
|
|
serviceAgreementTemplate.conditions = conditions
|
|
|
|
|
2019-02-04 17:45:49 +01:00
|
|
|
ddo.addChecksum()
|
2019-03-28 12:20:22 +01:00
|
|
|
await ddo.addProof(this.ocean, publisher.getId(), publisher.getPassword())
|
2019-02-04 17:45:49 +01:00
|
|
|
|
2019-01-21 17:48:40 +01:00
|
|
|
await didRegistry.registerAttribute(
|
|
|
|
did.getId(),
|
2019-02-06 12:54:06 +01:00
|
|
|
ddo.getChecksum(),
|
2019-03-25 13:47:21 +01:00
|
|
|
[this.config.brizoAddress],
|
2019-01-21 17:48:40 +01:00
|
|
|
serviceEndpoint,
|
2019-02-06 13:10:24 +01:00
|
|
|
publisher.getId(),
|
|
|
|
)
|
2019-04-23 01:44:34 +02:00
|
|
|
const storedDdo = await this.ocean.aquarius.storeDDO(ddo)
|
2019-01-21 17:48:40 +01:00
|
|
|
|
|
|
|
return storedDdo
|
|
|
|
}
|
|
|
|
|
2019-02-21 18:14:07 +01:00
|
|
|
// tslint:disable-next-line
|
2019-04-03 17:57:47 +02:00
|
|
|
public async consume(agreementId: string, did: string, serviceDefinitionId: string, consumerAccount: Account, resultPath: string, index?: number): Promise<string>
|
|
|
|
// tslint:disable-next-line
|
|
|
|
public async consume(agreementId: string, did: string, serviceDefinitionId: string, consumerAccount: Account, resultPath?: undefined | null, index?: number): 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,
|
2019-04-03 17:57:47 +02:00
|
|
|
index: number = -1,
|
2019-02-21 17:58:54 +01:00
|
|
|
): Promise<string | true> {
|
|
|
|
|
|
|
|
const ddo = await this.resolve(did)
|
2019-02-21 18:14:07 +01:00
|
|
|
const {metadata} = ddo.findServiceByType("Metadata")
|
2019-02-21 17:58:54 +01:00
|
|
|
|
|
|
|
const accessService = ddo.findServiceById(serviceDefinitionId)
|
|
|
|
|
2019-04-01 12:40:45 +02:00
|
|
|
const files = metadata.base.files
|
2019-02-21 17:58:54 +01:00
|
|
|
|
|
|
|
const {serviceEndpoint} = accessService
|
|
|
|
|
|
|
|
if (!serviceEndpoint) {
|
2019-02-21 18:14:07 +01:00
|
|
|
throw new Error("Consume asset failed, service definition is missing the `serviceEndpoint`.")
|
2019-02-21 17:58:54 +01:00
|
|
|
}
|
|
|
|
|
2019-03-21 02:56:58 +01:00
|
|
|
this.logger.log("Consuming files")
|
2019-02-21 17:58:54 +01:00
|
|
|
|
2019-04-23 00:54:43 +02:00
|
|
|
resultPath = resultPath ? `${resultPath}/datafile.${ddo.shortId()}.${serviceDefinitionId}/` : undefined
|
2019-03-21 02:56:58 +01:00
|
|
|
await this.ocean.brizo.consumeService(
|
2019-02-21 17:58:54 +01:00
|
|
|
agreementId,
|
|
|
|
serviceEndpoint,
|
|
|
|
consumerAccount,
|
2019-04-01 12:40:45 +02:00
|
|
|
files,
|
2019-02-21 17:58:54 +01:00
|
|
|
resultPath,
|
2019-04-03 17:57:47 +02:00
|
|
|
index,
|
2019-02-21 17:58:54 +01:00
|
|
|
)
|
2019-03-21 02:56:58 +01: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
|
|
|
*/
|
2019-04-24 04:08:22 +02:00
|
|
|
public order(
|
2019-01-21 17:48:40 +01:00
|
|
|
did: string,
|
|
|
|
serviceDefinitionId: string,
|
|
|
|
consumer: Account,
|
2019-04-24 04:08:22 +02:00
|
|
|
): SubscribablePromise<OrderProgressStep, string> {
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-04-24 13:25:37 +02:00
|
|
|
return new SubscribablePromise(async (observer) => {
|
2019-04-24 04:08:22 +02:00
|
|
|
const oceanAgreements = this.ocean.agreements
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-04-25 14:15:32 +02:00
|
|
|
observer.next(OrderProgressStep.Preparing)
|
2019-04-24 04:08:22 +02:00
|
|
|
this.logger.log("Asking for agreement signature")
|
|
|
|
const {agreementId, signature} = await oceanAgreements.prepare(did, serviceDefinitionId, consumer)
|
|
|
|
this.logger.log(`Agreement ${agreementId} signed`)
|
|
|
|
observer.next(OrderProgressStep.Prepared)
|
2019-02-21 17:58:54 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
const ddo = await this.resolve(did)
|
2019-02-21 17:58:54 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
const keeper = this.ocean.keeper
|
|
|
|
const templateName = ddo.findServiceByType("Access").serviceAgreementTemplate.contractName
|
|
|
|
const template = keeper.getTemplateByName(templateName)
|
|
|
|
const accessCondition = keeper.conditions.accessSecretStoreCondition
|
2019-03-14 15:47:31 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
const paymentFlow = new Promise(async (resolve, reject) => {
|
|
|
|
await template.getAgreementCreatedEvent(agreementId).once()
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
this.logger.log("Agreement initialized")
|
|
|
|
observer.next(OrderProgressStep.AgreementInitialized)
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
const {metadata} = ddo.findServiceByType("Metadata")
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
this.logger.log("Locking payment")
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
const accessGranted = accessCondition.getConditionFulfilledEvent(agreementId).once()
|
2019-04-11 14:02:03 +02:00
|
|
|
|
2019-04-25 14:15:32 +02:00
|
|
|
observer.next(OrderProgressStep.LockingPayment)
|
2019-04-24 04:08:22 +02:00
|
|
|
const paid = await oceanAgreements.conditions.lockReward(agreementId, metadata.base.price, consumer)
|
|
|
|
observer.next(OrderProgressStep.LockedPayment)
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
if (paid) {
|
|
|
|
this.logger.log("Payment was OK")
|
|
|
|
} else {
|
|
|
|
this.logger.error("Payment was KO")
|
|
|
|
this.logger.error("Agreement ID: ", agreementId)
|
|
|
|
this.logger.error("DID: ", ddo.id)
|
|
|
|
reject("Error on payment")
|
|
|
|
}
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
await accessGranted
|
2019-03-21 02:56:58 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
this.logger.log("Access granted")
|
|
|
|
resolve()
|
|
|
|
})
|
2019-02-21 17:58:54 +01:00
|
|
|
|
2019-04-25 14:15:32 +02:00
|
|
|
observer.next(OrderProgressStep.SendingAgreement)
|
2019-04-24 04:08:22 +02:00
|
|
|
this.logger.log("Sending agreement request")
|
|
|
|
await oceanAgreements.send(did, agreementId, serviceDefinitionId, signature, consumer)
|
|
|
|
this.logger.log("Agreement request sent")
|
2019-01-21 17:48:40 +01:00
|
|
|
|
2019-04-24 04:08:22 +02:00
|
|
|
try {
|
|
|
|
await paymentFlow
|
|
|
|
} catch (e) {
|
|
|
|
throw new Error("Error paying the asset.")
|
|
|
|
}
|
2019-02-21 17:58:54 +01:00
|
|
|
|
2019-04-24 04:08:22 +02: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()
|
|
|
|
const {creator, signatureValue} = ddo.proof
|
|
|
|
const signer = await this.ocean.utils.signature.verifyText(checksum, signatureValue)
|
|
|
|
|
|
|
|
if (signer.toLowerCase() !== creator.toLowerCase()) {
|
|
|
|
this.logger.warn(`Owner of ${ddo.id} doesn't match. Expected ${creator} instead of ${signer}.`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return creator
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:27:37 +02:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
}
|
|
|
|
|
2019-04-16 14:03:16 +02:00
|
|
|
/**
|
|
|
|
* Returns the assets of a consumer.
|
|
|
|
* @param {string} consumer Consumer address.
|
|
|
|
* @return {Promise<string[]>} List of DIDs.
|
|
|
|
*/
|
|
|
|
public async consumerAssets(consumer: string) {
|
|
|
|
return this.ocean.keeper.conditions.accessSecretStoreCondition.getGrantedDidByConsumer(consumer)
|
|
|
|
}
|
|
|
|
|
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) {
|
2019-03-21 02:56:58 +01:00
|
|
|
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: {
|
|
|
|
value: 1,
|
|
|
|
},
|
|
|
|
sort: {
|
|
|
|
value: 1,
|
|
|
|
},
|
|
|
|
} as SearchQuery)
|
|
|
|
}
|
|
|
|
}
|