diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df00ab5ae..1f8ecf252 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -255,10 +255,11 @@ jobs: DB_URL: 'http://localhost:9200' FEE_TOKENS: '{ "1": "0x967da4048cD07aB37855c090aAF366e4ce1b9F48", "137": "0x282d8efCe846A88B159800bd4130ad77443Fa1A1", "80001": "0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8", "56": "0xDCe07662CA8EbC241316a15B611c89711414Dd1a" }' FEE_AMOUNT: '{ "amount": 1, "unit": "MB" }' - AUTHORIZED_DECRYPTERS: '["0xe2DD09d719Da89e5a3D0F2549c7E24566e947260"]' + AUTHORIZED_DECRYPTERS: '["0xe2DD09d719Da89e5a3D0F2549c7E24566e947260","0x529043886F21D9bc1AE0feDb751e34265a246e47"]' P2P_ENABLE_UPNP: 'false' P2P_ENABLE_AUTONAT: 'false' ALLOWED_ADMINS: '["0xe2DD09d719Da89e5a3D0F2549c7E24566e947260"]' + AUTHORIZED_PUBLISHERS: '["0xe2DD09d719Da89e5a3D0F2549c7E24566e947260","0x529043886F21D9bc1AE0feDb751e34265a246e47"]' DB_TYPE: 'elasticsearch' MAX_REQ_PER_MINUTE: 320 MAX_CONNECTIONS_PER_MINUTE: 320 diff --git a/src/@types/DDO/Nft.ts b/src/@types/DDO/Nft.ts index 307756fce..5af028b5e 100644 --- a/src/@types/DDO/Nft.ts +++ b/src/@types/DDO/Nft.ts @@ -7,3 +7,10 @@ export interface Nft { owner?: string created?: string } + +export interface NftRoles { + manager: boolean + deployERC20: boolean + updateMetadata: boolean + store: boolean +} diff --git a/src/@types/commands.ts b/src/@types/commands.ts index e41e219e0..0e561384a 100644 --- a/src/@types/commands.ts +++ b/src/@types/commands.ts @@ -79,6 +79,9 @@ export interface FindDDOCommand extends DDOCommand { // https://github.com/oceanprotocol/ocean-node/issues/47 export interface ValidateDDOCommand extends Command { ddo: DDO + publisherAddress?: string + signature?: string + nonce?: string } export interface StatusCommand extends Command { diff --git a/src/components/core/handler/ddoHandler.ts b/src/components/core/handler/ddoHandler.ts index f787ec5d0..33f16350b 100644 --- a/src/components/core/handler/ddoHandler.ts +++ b/src/components/core/handler/ddoHandler.ts @@ -11,16 +11,17 @@ import { } from '../utils/findDdoHandler.js' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { GENERIC_EMOJIS, LOG_LEVELS_STR } from '../../../utils/logging/Logger.js' -import { sleep, readStream } from '../../../utils/util.js' +import { sleep, readStream, isDefined } from '../../../utils/util.js' import { DDO } from '../../../@types/DDO/DDO.js' import { CORE_LOGGER } from '../../../utils/logging/common.js' -import { Blockchain } from '../../../utils/blockchain.js' +import { Blockchain, getBlockchainHandler } from '../../../utils/blockchain.js' import { ethers, isAddress } from 'ethers' import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' assert { type: 'json' } import AccessListContract from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessList.sol/AccessList.json' assert { type: 'json' } // import lzma from 'lzma-native' import lzmajs from 'lzma-purejs-requirejs' import { + getNftPermissions, getValidationSignature, makeDid, validateObject @@ -43,6 +44,11 @@ import { getNetworkHeight, wasNFTDeployedByOurFactory } from '../../Indexer/utils.js' +import { checkNonce } from '../utils/nonceHandler.js' +import { + checkCredentialOnAccessList, + existsAccessListConfigurationForChain +} from '../../../utils/credentials.js' import { deleteIndexedMetadataIfExists, validateDDOHash } from '../../../utils/asset.js' const MAX_NUM_PROVIDERS = 5 @@ -852,10 +858,136 @@ export class ValidateDDOHandler extends CommandHandler { status: { httpStatus: 400, error: `Validation error: ${validation[1]}` } } } - const signature = await getValidationSignature(JSON.stringify(task.ddo)) + + // command contains optional parameter publisherAddress + // command contains optional parameter nonce and nonce is valid for publisherAddress + // command contains optional parameter signature which is the signed message based on nonce by publisherAddress + // ddo.nftAddress exists and it's valid (done above on validateObject()) + // publisherAddress has updateMetadata role on ddo.nftAddress contract + // publisherAddress has publishing rights on this node (see #815) (TODO needs other PR merged first) + + if (task.publisherAddress && task.nonce && task.signature) { + const nonceDB = this.getOceanNode().getDatabase().nonce + const nonceValid = await checkNonce( + nonceDB, + task.publisherAddress, + Number(task.nonce), + task.signature, + task.ddo.id + task.nonce + ) + + if (!nonceValid.valid) { + // BAD NONCE OR SIGNATURE + return { + stream: null, + status: { httpStatus: 403, error: 'Invalid nonce' } + } + } + + const chain = String(task.ddo.chainId) + // has publishing rights on this node? + const { authorizedPublishers, authorizedPublishersList, supportedNetworks } = + await getConfiguration() + const validChain = isDefined(supportedNetworks[chain]) + // first check if chain is valid + if (validChain) { + const blockChain = getBlockchainHandler(supportedNetworks[chain]) + + // check also NFT permissions + const hasUpdateMetadataPermissions = await ( + await getNftPermissions( + blockChain.getSigner(), + task.ddo.nftAddress, + ERC721Template.abi, + task.publisherAddress + ) + ).updateMetadata + console.log('hasUpdateMetadataPermissions:', hasUpdateMetadataPermissions) + + if (!hasUpdateMetadataPermissions) { + // Has no update metadata permissions + return { + stream: null, + status: { + httpStatus: 400, + error: `Validation error: Publisher: ${task.publisherAddress} does not have "updateMetadata" permissions` + } + } + } + + let hasPublisherRights = false + + // 1 ) check if publisher address is part of AUTHORIZED_PUBLISHERS + const isAuthorizedPublisher = + authorizedPublishers.length > 0 && + authorizedPublishers.filter( + (publisher) => + publisher.toLowerCase() === task.publisherAddress.toLowerCase() + ).length > 0 + + if (isAuthorizedPublisher) { + hasPublisherRights = true + } else { + // 2 ) check if there is an access list for this chain: AUTHORIZED_PUBLISHERS_LIST + const existsAccessList = existsAccessListConfigurationForChain( + authorizedPublishersList, + chain + ) + if (existsAccessList) { + // check access list contracts + hasPublisherRights = await checkCredentialOnAccessList( + authorizedPublishersList, + chain, + task.publisherAddress, + await blockChain.getSigner() + ) + } + } + + if (!hasPublisherRights) { + return { + stream: null, + status: { + httpStatus: 400, + error: `Validation error: publisher address is invalid for this node` + } + } + } + } else { + // the chain is not supported, so we can't validate on this node + return { + stream: null, + status: { + httpStatus: 400, + error: `Validation error: DDO chain is invalid for this node` + } + } + } + + // ALL GOOD - ADD SIGNATURE + const signature = await getValidationSignature(JSON.stringify(task.ddo)) + return { + stream: Readable.from(JSON.stringify(signature)), + status: { httpStatus: 200 } + } + } + // Missing signature, nonce or publisher address + // DDO is a valid object, but we cannot verify the signatures + // const msg = + // 'Partial validation: DDO is valid, but none of "publisher address", "signature" or "nonce" are present. Cannot add validation signature' + // return { + // stream: Readable.from(JSON.stringify(msg)), + // status: { + // httpStatus: 200, + // error: msg + // } + // } return { - stream: Readable.from(JSON.stringify(signature)), - status: { httpStatus: 200 } + stream: null, + status: { + httpStatus: 400, + error: `Validation error: Either publisher address is missing or there is an invalid signature/nonce` + } } } catch (error) { CORE_LOGGER.logMessageWithEmoji( diff --git a/src/components/core/handler/downloadHandler.ts b/src/components/core/handler/downloadHandler.ts index e8bc725f7..606dcfd27 100644 --- a/src/components/core/handler/downloadHandler.ts +++ b/src/components/core/handler/downloadHandler.ts @@ -61,10 +61,7 @@ export function isOrderingAllowedForAsset(asset: DDO): OrdableAssetResponse { } } - return { - isOrdable: true, - reason: '' - } + return { isOrdable: true, reason: '' } } export async function handleDownloadUrlCommand( @@ -163,19 +160,13 @@ export async function handleDownloadUrlCommand( return { stream: inputStream.stream.pipe(cipher), - status: { - httpStatus: inputStream.httpStatus, - headers - } + status: { httpStatus: inputStream.httpStatus, headers } } } else { // Download request is not using encryption! return { stream: inputStream.stream, - status: { - httpStatus: inputStream.httpStatus, - headers - } + status: { httpStatus: inputStream.httpStatus, headers } } } } catch (err) { @@ -242,23 +233,14 @@ export class DownloadHandler extends CommandHandler { ) return { stream: null, - status: { - httpStatus: 500, - error: 'No DDO found for asset' - } + status: { httpStatus: 500, error: 'No DDO found for asset' } } } const isOrdable = isOrderingAllowedForAsset(ddo) if (!isOrdable.isOrdable) { CORE_LOGGER.error(isOrdable.reason) - return { - stream: null, - status: { - httpStatus: 500, - error: isOrdable.reason - } - } + return { stream: null, status: { httpStatus: 500, error: isOrdable.reason } } } // 2. Validate ddo and credentials @@ -266,10 +248,7 @@ export class DownloadHandler extends CommandHandler { CORE_LOGGER.logMessage('Error: DDO malformed or disabled', true) return { stream: null, - status: { - httpStatus: 500, - error: 'Error: DDO malformed or disabled' - } + status: { httpStatus: 500, error: 'Error: DDO malformed or disabled' } } } @@ -322,13 +301,7 @@ export class DownloadHandler extends CommandHandler { nonceCheckResult.error, true ) - return { - stream: null, - status: { - httpStatus: 500, - error: nonceCheckResult.error - } - } + return { stream: null, status: { httpStatus: 500, error: nonceCheckResult.error } } } // from now on, we need blockchain checks const config = await getConfiguration() @@ -341,22 +314,13 @@ export class DownloadHandler extends CommandHandler { if (!ready) { return { stream: null, - status: { - httpStatus: 400, - error: `Download handler: ${error}` - } + status: { httpStatus: 400, error: `Download handler: ${error}` } } } provider = blockchain.getProvider() } catch (e) { CORE_LOGGER.error('Download JsonRpcProvider ERROR: ' + e.message) - return { - stream: null, - status: { - httpStatus: 500, - error: 'JsonRpcProvider ERROR' - } - } + return { stream: null, status: { httpStatus: 500, error: 'JsonRpcProvider ERROR' } } } if (!rpc) { CORE_LOGGER.logMessage( @@ -432,13 +396,7 @@ export class DownloadHandler extends CommandHandler { if (!computeAddrs.includes(task.consumerAddress?.toLowerCase())) { const msg = 'Not allowed to download this asset of type compute' CORE_LOGGER.logMessage(msg) - return { - stream: null, - status: { - httpStatus: 500, - error: msg - } - } + return { stream: null, status: { httpStatus: 500, error: msg } } } } // 5. check that the provider fee transaction is valid @@ -451,13 +409,7 @@ export class DownloadHandler extends CommandHandler { null ) if (!validFee.isValid) { - return { - stream: null, - status: { - httpStatus: 500, - error: 'ERROR checking fees' - } - } + return { stream: null, status: { httpStatus: 500, error: 'ERROR checking fees' } } } // 6. Call the validateOrderTransaction function to check order transaction @@ -484,10 +436,7 @@ export class DownloadHandler extends CommandHandler { ) return { stream: null, - status: { - httpStatus: 500, - error: paymentValidation.message - } + status: { httpStatus: 500, error: paymentValidation.message } } } // policyServer check @@ -502,13 +451,7 @@ export class DownloadHandler extends CommandHandler { task.policyServer ) if (!policyStatus.success) { - return { - stream: null, - status: { - httpStatus: 405, - error: policyStatus.message - } - } + return { stream: null, status: { httpStatus: 405, error: policyStatus.message } } } try { @@ -528,13 +471,7 @@ export class DownloadHandler extends CommandHandler { const errorMsg = 'Cannot decrypt DDO files, Template 4 is not active for confidential EVM!' CORE_LOGGER.error(errorMsg) - return { - stream: null, - status: { - httpStatus: 403, - error: errorMsg - } - } + return { stream: null, status: { httpStatus: 403, error: errorMsg } } } else { // TODO decrypt using Oasis SDK CORE_LOGGER.info( @@ -589,13 +526,7 @@ export class DownloadHandler extends CommandHandler { }) } catch (e) { CORE_LOGGER.logMessage('Decryption error: ' + e, true) - return { - stream: null, - status: { - httpStatus: 500, - error: 'Failed to decrypt' - } - } + return { stream: null, status: { httpStatus: 500, error: 'Failed to decrypt' } } } } } diff --git a/src/components/core/handler/fileInfoHandler.ts b/src/components/core/handler/fileInfoHandler.ts index bf4434caa..6bb2838e0 100644 --- a/src/components/core/handler/fileInfoHandler.ts +++ b/src/components/core/handler/fileInfoHandler.ts @@ -100,13 +100,7 @@ export class FileInfoHandler extends CommandHandler { const errorMessage = 'Invalid arguments. Please provide either file && Type OR did && serviceId' CORE_LOGGER.error(errorMessage) - return { - stream: null, - status: { - httpStatus: 400, - error: errorMessage - } - } + return { stream: null, status: { httpStatus: 400, error: errorMessage } } } CORE_LOGGER.logMessage( 'File Info Response: ' + JSON.stringify(fileInfo, null, 2), @@ -115,19 +109,11 @@ export class FileInfoHandler extends CommandHandler { return { stream: Readable.from(JSON.stringify(fileInfo)), - status: { - httpStatus: 200 - } + status: { httpStatus: 200 } } } catch (error) { CORE_LOGGER.error(error.message) - return { - stream: null, - status: { - httpStatus: 500, - error: error.message - } - } + return { stream: null, status: { httpStatus: 500, error: error.message } } } } } diff --git a/src/components/core/utils/nonceHandler.ts b/src/components/core/utils/nonceHandler.ts index f920ef406..da14f0603 100644 --- a/src/components/core/utils/nonceHandler.ts +++ b/src/components/core/utils/nonceHandler.ts @@ -125,6 +125,8 @@ export async function checkNonce( // get nonce from db let previousNonce = 0 // if none exists const existingNonce = await db.retrieve(consumer) + console.log('existing:', existingNonce) + console.log('task:', nonce) if (existingNonce && existingNonce.nonce !== null) { previousNonce = existingNonce.nonce } @@ -136,6 +138,7 @@ export async function checkNonce( signature, message // String(ddoId + nonce) ) + console.log('validate: ', validate) if (validate.valid) { const updateStatus = await updateNonce(db, consumer, nonce) return updateStatus diff --git a/src/components/core/utils/validateDdoHandler.ts b/src/components/core/utils/validateDdoHandler.ts index a16e30699..36e305c1f 100644 --- a/src/components/core/utils/validateDdoHandler.ts +++ b/src/components/core/utils/validateDdoHandler.ts @@ -6,11 +6,13 @@ import SHACLValidator from 'rdf-validate-shacl' import formats from '@rdfjs/formats-common' import { fromRdf } from 'rdf-literal' import { createHash } from 'crypto' -import { ethers, getAddress } from 'ethers' +import { ethers, getAddress, Signer } from 'ethers' import { CORE_LOGGER } from '../../../utils/logging/common.js' import { create256Hash } from '../../../utils/crypt.js' import { getProviderWallet } from './feesHandler.js' import { Readable } from 'stream' +import { NftRoles } from '../../../@types/DDO/Nft.js' +import { getContract } from '../../../utils/blockchain.js' import { deleteIndexedMetadataIfExists } from '../../../utils/asset.js' const CURRENT_VERSION = '4.7.0' @@ -159,3 +161,14 @@ export async function getValidationSignature(ddo: string): Promise { return { hash: '', publicKey: '', r: '', s: '', v: '' } } } + +export async function getNftPermissions( + signer: Signer, + nftAddress: string, // smart contract address + nftAbi: any, // smart contract ABI + addressToCheck: string // user account address +): Promise { + const nftContract = getContract(nftAddress, nftAbi, signer) + const roles = await nftContract.getPermissions(addressToCheck) + return roles +} diff --git a/src/components/database/ElasticSearchDatabase.ts b/src/components/database/ElasticSearchDatabase.ts index 57f1adac8..9b44d8dbe 100644 --- a/src/components/database/ElasticSearchDatabase.ts +++ b/src/components/database/ElasticSearchDatabase.ts @@ -33,10 +33,7 @@ export class ElasticsearchIndexerDatabase extends AbstractIndexerDatabase { index: this.index, body: { mappings: { - properties: { - id: { type: 'keyword' }, - lastIndexedBlock: { type: 'long' } - } + properties: { id: { type: 'keyword' }, lastIndexedBlock: { type: 'long' } } } } }) @@ -69,10 +66,7 @@ export class ElasticsearchIndexerDatabase extends AbstractIndexerDatabase { async retrieve(network: number) { try { - const result = await this.client.get({ - index: this.index, - id: network.toString() - }) + const result = await this.client.get({ index: this.index, id: network.toString() }) return result._source } catch (error) { const errorMsg = `Error when retrieving indexer entry on network ${network}: ${error.message}` @@ -97,9 +91,7 @@ export class ElasticsearchIndexerDatabase extends AbstractIndexerDatabase { await this.client.update({ index: this.index, id: network.toString(), - body: { - doc: { lastIndexedBlock } - }, + body: { doc: { lastIndexedBlock } }, refresh: 'wait_for' }) } else { @@ -208,10 +200,7 @@ export class ElasticsearchDdoStateDatabase extends AbstractDdoStateDatabase { async retrieve(did: string) { try { - const result = await this.client.get({ - index: this.index, - id: did - }) + const result = await this.client.get({ index: this.index, id: did }) return result._source } catch (error) { const errorMessage = `Error when retrieving the state of the ddo with id: ${did}: ${error.message}` @@ -229,11 +218,7 @@ export class ElasticsearchDdoStateDatabase extends AbstractDdoStateDatabase { try { const result = await this.client.search({ index: this.index, - query: { - match: { - [query.query_by]: query.q - } - } + query: { match: { [query.query_by]: query.q } } }) return result.hits.hits.map((hit: any) => { return normalizeDocumentId(hit._source, hit._id) @@ -261,18 +246,13 @@ export class ElasticsearchDdoStateDatabase extends AbstractDdoStateDatabase { errorMsg: string = ' ' ) { try { - const exists = await this.client.exists({ - index: this.index, - id: did - }) + const exists = await this.client.exists({ index: this.index, id: did }) if (exists) { await this.client.update({ index: this.index, id: did, - body: { - doc: { chainId, did, nft: nftAddress, txId, valid, error: errorMsg } - }, + body: { doc: { chainId, did, nft: nftAddress, txId, valid, error: errorMsg } }, refresh: 'wait_for' }) } else { @@ -294,11 +274,7 @@ export class ElasticsearchDdoStateDatabase extends AbstractDdoStateDatabase { async delete(did: string) { try { - await this.client.delete({ - index: this.index, - id: did, - refresh: 'wait_for' - }) + await this.client.delete({ index: this.index, id: did, refresh: 'wait_for' }) return { id: did } } catch (error) { const errorMessage = `Error when deleting ddo state ${did}: ${error.message}` @@ -336,9 +312,7 @@ export class ElasticsearchOrderDatabase extends AbstractOrderDatabase { const searchParams = { index: this.getSchema().index, body: { - query: { - match: q ? { _all: q } : queryObj - }, + query: { match: q ? { _all: q } : queryObj }, from: (pageNumber - 1) * maxResultsPerPage || 0, size: maxResultsPerPage || 10 } @@ -429,10 +403,7 @@ export class ElasticsearchOrderDatabase extends AbstractOrderDatabase { await this.provider.update({ index: this.getSchema().index, id: orderId, - body: { - doc: document, - doc_as_upsert: true - } + body: { doc: document, doc_as_upsert: true } }) return document } catch (error) { @@ -446,10 +417,7 @@ export class ElasticsearchOrderDatabase extends AbstractOrderDatabase { async delete(orderId: string) { try { - await this.provider.delete({ - index: this.getSchema().index, - id: orderId - }) + await this.provider.delete({ index: this.getSchema().index, id: orderId }) return { id: orderId } } catch (error) { const errorMsg = `Error when deleting order ${orderId}: ` + error.message @@ -529,11 +497,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { try { const response = await this.client.search({ index, - body: { - ...queryWithoutIndex, - from, - size: maxPerPage - } + body: { ...queryWithoutIndex, from, size: maxPerPage } }) if (response.hits?.hits.length > 0) { const nomalizedResponse = response.hits.hits.map((hit: any) => { @@ -555,11 +519,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { try { const response = await this.client.search({ index: schema.index, - body: { - ...query, - from, - size: maxPerPage - } + body: { ...query, from, size: maxPerPage } }) if (response.hits?.hits.length > 0) { const nomalizedResponse = response.hits.hits.map((hit: any) => { @@ -618,10 +578,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { let ddo = null for (const schema of this.getSchemas()) { try { - const response = await this.client.get({ - index: schema.index, - id - }) + const response = await this.client.get({ index: schema.index, id }) if (response.found) { ddo = response._source break @@ -666,9 +623,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { const response: any = await this.client.update({ index: schema.index, id: ddo.id, - body: { - doc: ddo - } + body: { doc: ddo } }) // make sure we do not have different responses 4 between DBs // do the same thing on other methods @@ -705,10 +660,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { let isDeleted = false for (const schema of this.getSchemas()) { try { - const response = await this.client.delete({ - index: schema.index, - id - }) + const response = await this.client.delete({ index: schema.index, id }) isDeleted = response.result === 'deleted' if (isDeleted) { DATABASE_LOGGER.debug( @@ -745,11 +697,7 @@ export class ElasticsearchDdoDatabase extends AbstractDdoDatabase { // add batch size logic const response = await this.client.deleteByQuery({ index: schema.index, - body: { - query: { - match: { chainId } - } - } + body: { query: { match: { chainId } } } }) DATABASE_LOGGER.debug( @@ -836,10 +784,7 @@ export class ElasticsearchLogDatabase extends AbstractLogDatabase { async retrieveLog(id: string): Promise | null> { try { - const result = await this.client.get({ - index: this.index, - id - }) + const result = await this.client.get({ index: this.index, id }) return normalizeDocumentId(result._source, result._id) } catch (error) { const errorMsg = `Error when retrieving log entry: ${error.message}` @@ -863,9 +808,7 @@ export class ElasticsearchLogDatabase extends AbstractLogDatabase { ): Promise[]> { try { const filterConditions: any = { - bool: { - must: [{ range: { timestamp: { gte: startTime, lte: endTime } } }] - } + bool: { must: [{ range: { timestamp: { gte: startTime, lte: endTime } } }] } } if (moduleName) { @@ -891,10 +834,7 @@ export class ElasticsearchLogDatabase extends AbstractLogDatabase { } const result = await this.client.search({ index: this.index, - body: { - query: filterConditions, - sort: [{ timestamp: { order: 'desc' } }] - }, + body: { query: filterConditions, sort: [{ timestamp: { order: 'desc' } }] }, size, from }) @@ -919,11 +859,7 @@ export class ElasticsearchLogDatabase extends AbstractLogDatabase { throw new Error('Log ID is required for deletion.') } try { - await this.client.delete({ - index: this.index, - id: logId, - refresh: 'wait_for' - }) + await this.client.delete({ index: this.index, id: logId, refresh: 'wait_for' }) DATABASE_LOGGER.logMessageWithEmoji( `Deleted log with ID: ${logId}`, true, @@ -970,9 +906,7 @@ export class ElasticsearchLogDatabase extends AbstractLogDatabase { async getLogsCount(): Promise { try { - const res = await this.client.count({ - index: this.index - }) + const res = await this.client.count({ index: this.index }) return res && res.count ? res.count : 0 } catch (e) { DATABASE_LOGGER.error('Unable to retrieve logs count: ' + e.message) diff --git a/src/components/httpRoutes/aquarius.ts b/src/components/httpRoutes/aquarius.ts index 102c2521e..55dfbf6ab 100644 --- a/src/components/httpRoutes/aquarius.ts +++ b/src/components/httpRoutes/aquarius.ts @@ -7,7 +7,7 @@ import { FindDdoHandler, ValidateDDOHandler } from '../core/handler/ddoHandler.j import { QueryDdoStateHandler, QueryHandler } from '../core/handler/queryHandler.js' import { HTTP_LOGGER } from '../../utils/logging/common.js' import { DDO } from '../../@types/DDO/DDO.js' -import { QueryCommand } from '../../@types/commands.js' +import { QueryCommand, ValidateDDOCommand } from '../../@types/commands.js' import { DatabaseFactory } from '../database/DatabaseFactory.js' import { SearchQuery } from '../../@types/DDO/SearchQuery.js' import { getConfiguration } from '../../utils/index.js' @@ -139,18 +139,21 @@ aquariusRoutes.post(`${AQUARIUS_API_BASE_PATH}/assets/ddo/validate`, async (req, res.status(400).send('Missing DDO object') return } - const ddo = JSON.parse(req.body) as DDO - - if (!ddo.version) { - res.status(400).send('Missing DDO version') + const data: any = JSON.parse(req.body) + if (!data.ddo || !data.ddo.version) { + res.status(400).send('Invalid DDO data or missing DDO version') return } + const task: ValidateDDOCommand = { + ddo: data.ddo as DDO, + nonce: data.nonce, + publisherAddress: data.publisherAddress, + signature: data.signature, + command: PROTOCOL_COMMANDS.VALIDATE_DDO + } const node = req.oceanNode - const result = await new ValidateDDOHandler(node).handle({ - ddo, - command: PROTOCOL_COMMANDS.VALIDATE_DDO - }) + const result = await new ValidateDDOHandler(node).handle(task) if (result.stream) { const validationResult = JSON.parse(await streamToString(result.stream as Readable)) res.json(validationResult) diff --git a/src/test/integration/credentials.test.ts b/src/test/integration/credentials.test.ts index 372efe06a..e98dcaaff 100644 --- a/src/test/integration/credentials.test.ts +++ b/src/test/integration/credentials.test.ts @@ -34,6 +34,7 @@ import { GetDdoHandler } from '../../components/core/handler/ddoHandler.js' import { Readable } from 'stream' import { OceanNodeConfig } from '../../@types/OceanNode.js' +import { getContract } from '../../utils/blockchain.js' import { DEFAULT_TEST_TIMEOUT, @@ -55,7 +56,7 @@ import { ganachePrivateKeys } from '../utils/addresses.js' import { homedir } from 'os' import AccessListFactory from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessListFactory.sol/AccessListFactory.json' assert { type: 'json' } import AccessList from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessList.sol/AccessList.json' assert { type: 'json' } -import { deployAccessListContract, getContract } from '../utils/contracts.js' +import { deployAccessListContract } from '../utils/contracts.js' describe('Should run a complete node flow.', () => { let config: OceanNodeConfig diff --git a/src/test/utils/contracts.ts b/src/test/utils/contracts.ts index 1426a8d80..ac0e5cc88 100644 --- a/src/test/utils/contracts.ts +++ b/src/test/utils/contracts.ts @@ -1,15 +1,5 @@ -import { Contract, ethers, Signer } from 'ethers' - -/** - * Returns a contract instance for the given address - * @param {string} address - The address of the contract - * @param {AbiItem[]} [abi] - The ABI of the contract - * @returns {Contract} - The contract instance - */ -export function getContract(address: string, abi: any, signer: Signer): Contract { - const contract = new ethers.Contract(address, abi, signer) - return contract -} +import { Signer } from 'ethers' +import { getContract } from '../../utils/blockchain.js' export function getEventFromTx(txReceipt: { logs: any[] }, eventName: string) { return txReceipt?.logs?.filter((log) => { diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index 0dc7d1356..63142bdee 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -14,7 +14,7 @@ import { import { getConfiguration } from './config.js' import { CORE_LOGGER } from './logging/common.js' import { sleep } from './util.js' -import { ConnectionStatus } from '../@types/blockchain.js' +import { ConnectionStatus, SupportedNetwork } from '../@types/blockchain.js' import { ValidateChainId } from '../@types/commands.js' export class Blockchain { @@ -222,3 +222,25 @@ export async function getJsonRpcProvider( } return new JsonRpcProvider(checkResult.networkRpc) } + +// useful for getting a Blockchain instance, as we repeat this piece of code often +export function getBlockchainHandler(network: SupportedNetwork): Blockchain { + const blockChain = new Blockchain( + network.rpc, + network.network, + network.chainId, + network.fallbackRPCs + ) + return blockChain +} + +/** + * Returns a contract instance for the given address + * @param {string} address - The address of the contract + * @param {AbiItem[]} [abi] - The ABI of the contract + * @returns {Contract} - The contract instance + */ +export function getContract(address: string, abi: any, signer: Signer): Contract { + const contract = new ethers.Contract(address, abi, signer) + return contract +} diff --git a/src/utils/credentials.ts b/src/utils/credentials.ts index d02973999..f9786ee84 100644 --- a/src/utils/credentials.ts +++ b/src/utils/credentials.ts @@ -101,6 +101,21 @@ export function areKnownCredentialTypes(credentials: Credentials): boolean { return true } +/** + * @param accessList the access list contract address + * @param chainId the chain id to check + * @returns true if the config exists, false otherwise + */ +export function existsAccessListConfigurationForChain( + accessList: AccessListContract, + chainId: string +) { + if (!accessList) return false + const chainsListed = Object.keys(accessList) + // check the access lists for this chain + return chainsListed.length > 0 && chainsListed.includes(chainId) +} + // utility function that can be used on multiple access lists /** * @param accessList the access list contract address @@ -115,12 +130,12 @@ export async function checkCredentialOnAccessList( addressToCheck: string, signer: Signer ): Promise { - if (!accessList) { + const existsAccessList = existsAccessListConfigurationForChain(accessList, chainId) + if (!existsAccessList) { return true } - const chainsListed = Object.keys(accessList) // check the access lists for this chain - if (chainsListed.length > 0 && chainsListed.includes(chainId)) { + if (existsAccessList) { let isAuthorized = false for (const accessListAddress of accessList[chainId]) { const accessListContract = new ethers.Contract( diff --git a/src/utils/logging/Logger.ts b/src/utils/logging/Logger.ts index 7c47c89b5..12042188b 100644 --- a/src/utils/logging/Logger.ts +++ b/src/utils/logging/Logger.ts @@ -198,7 +198,7 @@ const alignedWithColorsAndTime: winston.Logform.Format = winston.format.combine( winston.format.timestamp(), winston.format.align(), winston.format.printf( - (info) => `${info.timestamp} ${info.level}: ${info.message.trim()}` + (info: any) => `${info.timestamp} ${info.level}: ${info.message.trim()}` ) ) const consoleColorFormatting: winston.Logform.Format | Record = { @@ -270,9 +270,7 @@ export function buildCustomFileTransport( } } - return new winston.transports.File({ - ...options - }) + return new winston.transports.File({ ...options }) } export function getDefaultLoggerTransports( @@ -314,9 +312,7 @@ export function buildCustomStreamTransport( } } - return new winston.transports.Stream({ - ...options - }) + return new winston.transports.Stream({ ...options }) } /**