mirror of
https://github.com/oceanprotocol/ocean.js.git
synced 2024-11-26 20:39:05 +01:00
validate ddo before publish on chain (#873)
* validate ddo before publish on chain
This commit is contained in:
parent
29855184ce
commit
fba5965042
@ -10,3 +10,8 @@ export interface Metadata {
|
|||||||
curation?: Curation
|
curation?: Curation
|
||||||
status?: Status
|
status?: Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ValidateMetadata {
|
||||||
|
valid: Boolean
|
||||||
|
errors?: Object
|
||||||
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { DDO } from '../ddo/DDO'
|
import { DDO } from '../ddo/DDO'
|
||||||
import DID from '../ocean/DID'
|
import DID from '../ocean/DID'
|
||||||
import { EditableMetadata } from '../ddo/interfaces/EditableMetadata'
|
import { EditableMetadata } from '../ddo/interfaces/EditableMetadata'
|
||||||
import { Logger } from '../utils'
|
import { Logger, isDdo } from '../utils'
|
||||||
import { WebServiceConnector } from '../ocean/utils/WebServiceConnector'
|
import { WebServiceConnector } from '../ocean/utils/WebServiceConnector'
|
||||||
import { Response } from 'node-fetch'
|
import { Response } from 'node-fetch'
|
||||||
|
import { Metadata, ValidateMetadata } from '../ddo/interfaces'
|
||||||
|
|
||||||
const apiPath = '/api/v1/aquarius/assets/ddo'
|
const apiPath = '/api/v1/aquarius/assets/ddo'
|
||||||
|
|
||||||
@ -164,6 +165,39 @@ export class MetadataCache {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate Metadata
|
||||||
|
* @param {Metadata} metadata metadata to be validated. If it's a Metadata, it will be validated agains the local schema. Else, it's validated agains the remote schema
|
||||||
|
* @return {Promise<Boolean|Object>} Result.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async validateMetadata(metadata: Metadata | DDO): Promise<ValidateMetadata> {
|
||||||
|
const status: ValidateMetadata = {
|
||||||
|
valid: false
|
||||||
|
}
|
||||||
|
const path = isDdo(metadata) ? '/validate-remote' : '/validate'
|
||||||
|
try {
|
||||||
|
const response = await this.fetch.post(
|
||||||
|
`${this.url}${apiPath}${path}`,
|
||||||
|
JSON.stringify(metadata)
|
||||||
|
)
|
||||||
|
if (response.ok) {
|
||||||
|
const errors = await response.json()
|
||||||
|
if (errors === true) status.valid = true
|
||||||
|
else status.errors = errors
|
||||||
|
} else {
|
||||||
|
this.logger.error(
|
||||||
|
'validate Metadata failed:',
|
||||||
|
response.status,
|
||||||
|
response.statusText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error validating metadata: ', error)
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a DDO by DID.
|
* Retrieves a DDO by DID.
|
||||||
* @param {DID | string} did DID of the asset.
|
* @param {DID | string} did DID of the asset.
|
||||||
|
@ -66,14 +66,23 @@ export class OnChainMetadata {
|
|||||||
* @param {String} did
|
* @param {String} did
|
||||||
* @param {DDO} ddo
|
* @param {DDO} ddo
|
||||||
* @param {String} consumerAccount
|
* @param {String} consumerAccount
|
||||||
|
* @param {Boolean} encrypt If the DDO should be encrypted
|
||||||
|
* @param {Boolean} validate If the DDO should be validated against Aqua prior to publish
|
||||||
* @return {Promise<TransactionReceipt>} exchangeId
|
* @return {Promise<TransactionReceipt>} exchangeId
|
||||||
*/
|
*/
|
||||||
public async publish(
|
public async publish(
|
||||||
did: string,
|
did: string,
|
||||||
ddo: DDO,
|
ddo: DDO,
|
||||||
consumerAccount: string,
|
consumerAccount: string,
|
||||||
encrypt: boolean = false
|
encrypt: boolean = false,
|
||||||
|
validate: boolean = true
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
|
if (validate) {
|
||||||
|
const valid = await this.metadataCache.validateMetadata(ddo)
|
||||||
|
if (!valid.valid) {
|
||||||
|
throw new Error(`DDO has failed validation`)
|
||||||
|
}
|
||||||
|
}
|
||||||
const rawData = await this.prepareRawData(ddo, encrypt)
|
const rawData = await this.prepareRawData(ddo, encrypt)
|
||||||
if (!rawData) {
|
if (!rawData) {
|
||||||
throw new Error(`Could not prepare raw data for publish`)
|
throw new Error(`Could not prepare raw data for publish`)
|
||||||
@ -86,14 +95,23 @@ export class OnChainMetadata {
|
|||||||
* @param {String} did
|
* @param {String} did
|
||||||
* @param {DDO} ddo
|
* @param {DDO} ddo
|
||||||
* @param {String} consumerAccount
|
* @param {String} consumerAccount
|
||||||
|
* @param {Boolean} encrypt If the DDO should be encrypted
|
||||||
|
* @param {Boolean} validate If the DDO should be validated against Aqua prior to publish
|
||||||
* @return {Promise<TransactionReceipt>} exchangeId
|
* @return {Promise<TransactionReceipt>} exchangeId
|
||||||
*/
|
*/
|
||||||
public async update(
|
public async update(
|
||||||
did: string,
|
did: string,
|
||||||
ddo: DDO,
|
ddo: DDO,
|
||||||
consumerAccount: string,
|
consumerAccount: string,
|
||||||
encrypt: boolean = false
|
encrypt: boolean = false,
|
||||||
|
validate: boolean = true
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
|
if (validate) {
|
||||||
|
const valid = await this.metadataCache.validateMetadata(ddo)
|
||||||
|
if (!valid.valid) {
|
||||||
|
throw new Error(`DDO has failed validation`)
|
||||||
|
}
|
||||||
|
}
|
||||||
const rawData = await this.prepareRawData(ddo, encrypt)
|
const rawData = await this.prepareRawData(ddo, encrypt)
|
||||||
if (!rawData) {
|
if (!rawData) {
|
||||||
throw new Error(`Could not prepare raw data for udate`)
|
throw new Error(`Could not prepare raw data for udate`)
|
||||||
|
@ -6,7 +6,7 @@ export interface AssetResolved {
|
|||||||
ddo: DDO
|
ddo: DDO
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDdo(arg: any): arg is DDO {
|
export function isDdo(arg: any): arg is DDO {
|
||||||
return arg.id !== undefined
|
return arg.id !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +241,7 @@ describe('Compute flow', () => {
|
|||||||
type: 'dataset',
|
type: 'dataset',
|
||||||
name: 'UK Weather information 2011',
|
name: 'UK Weather information 2011',
|
||||||
dateCreated: dateCreated,
|
dateCreated: dateCreated,
|
||||||
|
datePublished: dateCreated,
|
||||||
author: 'Met Office',
|
author: 'Met Office',
|
||||||
license: 'CC-BY',
|
license: 'CC-BY',
|
||||||
files: [
|
files: [
|
||||||
@ -477,6 +478,7 @@ describe('Compute flow', () => {
|
|||||||
type: 'algorithm',
|
type: 'algorithm',
|
||||||
name: 'Test Algo',
|
name: 'Test Algo',
|
||||||
dateCreated: dateCreated,
|
dateCreated: dateCreated,
|
||||||
|
datePublished: dateCreated,
|
||||||
author: 'DevOps',
|
author: 'DevOps',
|
||||||
license: 'CC-BY',
|
license: 'CC-BY',
|
||||||
files: [
|
files: [
|
||||||
@ -529,6 +531,7 @@ describe('Compute flow', () => {
|
|||||||
type: 'algorithm',
|
type: 'algorithm',
|
||||||
name: 'Test Algo with Compute',
|
name: 'Test Algo with Compute',
|
||||||
dateCreated: dateCreated,
|
dateCreated: dateCreated,
|
||||||
|
datePublished: dateCreated,
|
||||||
author: 'DevOps',
|
author: 'DevOps',
|
||||||
license: 'CC-BY',
|
license: 'CC-BY',
|
||||||
files: [
|
files: [
|
||||||
@ -590,6 +593,7 @@ describe('Compute flow', () => {
|
|||||||
type: 'algorithm',
|
type: 'algorithm',
|
||||||
name: 'Remote Algorithm',
|
name: 'Remote Algorithm',
|
||||||
dateCreated: dateCreated,
|
dateCreated: dateCreated,
|
||||||
|
datePublished: dateCreated,
|
||||||
author: 'DevOps',
|
author: 'DevOps',
|
||||||
license: 'CC-BY',
|
license: 'CC-BY',
|
||||||
files: [
|
files: [
|
||||||
@ -654,6 +658,7 @@ describe('Compute flow', () => {
|
|||||||
type: 'algorithm',
|
type: 'algorithm',
|
||||||
name: 'Remote Algorithm',
|
name: 'Remote Algorithm',
|
||||||
dateCreated: dateCreated,
|
dateCreated: dateCreated,
|
||||||
|
datePublished: dateCreated,
|
||||||
author: 'DevOps',
|
author: 'DevOps',
|
||||||
license: 'CC-BY',
|
license: 'CC-BY',
|
||||||
files: [
|
files: [
|
||||||
|
@ -60,6 +60,7 @@ describe('Marketplace flow', () => {
|
|||||||
let assetWithPool
|
let assetWithPool
|
||||||
let assetWithBadUrl
|
let assetWithBadUrl
|
||||||
let assetWithEncrypt
|
let assetWithEncrypt
|
||||||
|
let assetInvalidNoName
|
||||||
let marketplace: Account
|
let marketplace: Account
|
||||||
let contracts: TestContractHandler
|
let contracts: TestContractHandler
|
||||||
let datatoken: DataTokens
|
let datatoken: DataTokens
|
||||||
@ -67,6 +68,7 @@ describe('Marketplace flow', () => {
|
|||||||
let tokenAddressWithPool: string
|
let tokenAddressWithPool: string
|
||||||
let tokenAddressForBadUrlAsset: string
|
let tokenAddressForBadUrlAsset: string
|
||||||
let tokenAddressEncrypted: string
|
let tokenAddressEncrypted: string
|
||||||
|
let tokenAddressInvalidNoName: string
|
||||||
let service1: ServiceAccess
|
let service1: ServiceAccess
|
||||||
let price: string
|
let price: string
|
||||||
let ocean: Ocean
|
let ocean: Ocean
|
||||||
@ -143,6 +145,15 @@ describe('Marketplace flow', () => {
|
|||||||
'AliceDT',
|
'AliceDT',
|
||||||
'DTA'
|
'DTA'
|
||||||
)
|
)
|
||||||
|
assert(tokenAddressEncrypted != null)
|
||||||
|
tokenAddressInvalidNoName = await datatoken.create(
|
||||||
|
blob,
|
||||||
|
alice.getId(),
|
||||||
|
'10000000000',
|
||||||
|
'AliceDT',
|
||||||
|
'DTA'
|
||||||
|
)
|
||||||
|
assert(tokenAddressInvalidNoName != null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Generates metadata', async () => {
|
it('Generates metadata', async () => {
|
||||||
@ -155,6 +166,7 @@ describe('Marketplace flow', () => {
|
|||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
|
index: 0,
|
||||||
url: 'https://s3.amazonaws.com/testfiles.oceanprotocol.com/info.0.json',
|
url: 'https://s3.amazonaws.com/testfiles.oceanprotocol.com/info.0.json',
|
||||||
checksum: 'efb2c764274b745f5fc37f97c6b0e761',
|
checksum: 'efb2c764274b745f5fc37f97c6b0e761',
|
||||||
contentLength: '4535431',
|
contentLength: '4535431',
|
||||||
@ -170,6 +182,7 @@ describe('Marketplace flow', () => {
|
|||||||
type: 'dataset',
|
type: 'dataset',
|
||||||
name: 'test-dataset-with-pools',
|
name: 'test-dataset-with-pools',
|
||||||
dateCreated: new Date(Date.now()).toISOString().split('.')[0] + 'Z', // remove milliseconds
|
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',
|
author: 'oceanprotocol-team',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
files: [
|
files: [
|
||||||
@ -189,6 +202,7 @@ describe('Marketplace flow', () => {
|
|||||||
type: 'dataset encrypted',
|
type: 'dataset encrypted',
|
||||||
name: 'test-dataset-encrypted',
|
name: 'test-dataset-encrypted',
|
||||||
dateCreated: new Date(Date.now()).toISOString().split('.')[0] + 'Z', // remove milliseconds
|
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',
|
author: 'oceanprotocol-team',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
files: [
|
files: [
|
||||||
@ -208,6 +222,26 @@ describe('Marketplace flow', () => {
|
|||||||
type: 'datasetWithBadUrl',
|
type: 'datasetWithBadUrl',
|
||||||
name: 'test-dataset-withBadUrl',
|
name: 'test-dataset-withBadUrl',
|
||||||
dateCreated: new Date(Date.now()).toISOString().split('.')[0] + 'Z', // remove milliseconds
|
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/nosuchfile',
|
||||||
|
checksum: 'efb2c764274b745f5fc37f97c6b0e761',
|
||||||
|
contentLength: '4535431',
|
||||||
|
contentType: 'text/csv',
|
||||||
|
encoding: 'UTF-8',
|
||||||
|
compression: 'zip'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assetInvalidNoName = {
|
||||||
|
main: {
|
||||||
|
type: 'datasetInvalid',
|
||||||
|
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',
|
author: 'oceanprotocol-team',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
files: [
|
files: [
|
||||||
@ -223,7 +257,14 @@ describe('Marketplace flow', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it('Should validate local metadata', async () => {
|
||||||
|
const valid = await ocean.metadataCache.validateMetadata(asset)
|
||||||
|
assert(valid.valid, 'This metadata should be valid')
|
||||||
|
})
|
||||||
|
it('Should invalidate invalid local metadata', async () => {
|
||||||
|
const valid = await ocean.metadataCache.validateMetadata(assetInvalidNoName)
|
||||||
|
assert(!valid.valid, 'This metadata should be invalid')
|
||||||
|
})
|
||||||
it('Alice publishes all datasets', async () => {
|
it('Alice publishes all datasets', async () => {
|
||||||
price = '10' // in datatoken
|
price = '10' // in datatoken
|
||||||
const publishedDate = new Date(Date.now()).toISOString().split('.')[0] + 'Z'
|
const publishedDate = new Date(Date.now()).toISOString().split('.')[0] + 'Z'
|
||||||
@ -234,6 +275,7 @@ describe('Marketplace flow', () => {
|
|||||||
publishedDate,
|
publishedDate,
|
||||||
timeout
|
timeout
|
||||||
)
|
)
|
||||||
|
asset.main.datePublished = asset.main.dateCreated
|
||||||
ddo = await ocean.assets.create(asset, alice, [service1], tokenAddress)
|
ddo = await ocean.assets.create(asset, alice, [service1], tokenAddress)
|
||||||
assert(ddo.dataToken === tokenAddress)
|
assert(ddo.dataToken === tokenAddress)
|
||||||
const storeTx = await ocean.onChainMetadata.publish(ddo.id, ddo, alice.getId())
|
const storeTx = await ocean.onChainMetadata.publish(ddo.id, ddo, alice.getId())
|
||||||
@ -331,6 +373,38 @@ describe('Marketplace flow', () => {
|
|||||||
await waitForAqua(ocean, ddoWithCredentials.id)
|
await waitForAqua(ocean, ddoWithCredentials.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Alice should fail to publish invalid dataset', async () => {
|
||||||
|
price = '10' // in datatoken
|
||||||
|
const publishedDate = new Date(Date.now()).toISOString().split('.')[0] + 'Z'
|
||||||
|
const timeout = 0
|
||||||
|
service1 = await ocean.assets.createAccessServiceAttributes(
|
||||||
|
alice,
|
||||||
|
price,
|
||||||
|
publishedDate,
|
||||||
|
timeout
|
||||||
|
)
|
||||||
|
const invalidDdo = await ocean.assets.create(
|
||||||
|
assetInvalidNoName,
|
||||||
|
alice,
|
||||||
|
[service1],
|
||||||
|
tokenAddressInvalidNoName
|
||||||
|
)
|
||||||
|
assert(invalidDdo.dataToken === tokenAddressInvalidNoName)
|
||||||
|
let storeTx
|
||||||
|
try {
|
||||||
|
storeTx = await ocean.onChainMetadata.publish(
|
||||||
|
invalidDdo.id,
|
||||||
|
invalidDdo,
|
||||||
|
alice.getId()
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
storeTx = null
|
||||||
|
}
|
||||||
|
console.error(storeTx)
|
||||||
|
assert(!storeTx, 'Alice should not be able to publish invalid datasets')
|
||||||
|
})
|
||||||
|
|
||||||
it('Marketplace should resolve asset using DID', async () => {
|
it('Marketplace should resolve asset using DID', async () => {
|
||||||
await ocean.assets.resolve(ddo.id).then((newDDO) => {
|
await ocean.assets.resolve(ddo.id).then((newDDO) => {
|
||||||
assert(newDDO.id === ddo.id)
|
assert(newDDO.id === ddo.id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user