diff --git a/src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-get-request-command.js b/src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-get-request-command.js index 173b42767..14c7e09b6 100644 --- a/src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-get-request-command.js +++ b/src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-get-request-command.js @@ -149,7 +149,7 @@ class HandleGetRequestCommand extends HandleProtocolMessageCommand { ...(includeMetadata && metadata && { metadata }), }; - if (assertion.length) { + if (assertion?.public?.length) { await this.operationIdService.updateOperationIdStatus( operationId, blockchain, @@ -157,7 +157,7 @@ class HandleGetRequestCommand extends HandleProtocolMessageCommand { ); } - return assertion.length + return assertion?.public?.length ? { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: responseData } : { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK, diff --git a/src/commands/protocols/get/sender/local-get-command.js b/src/commands/protocols/get/sender/local-get-command.js index 0e501025f..3060c0f98 100644 --- a/src/commands/protocols/get/sender/local-get-command.js +++ b/src/commands/protocols/get/sender/local-get-command.js @@ -137,7 +137,7 @@ class LocalGetCommand extends Command { assertion, ...(includeMetadata && metadata && { metadata }), }; - if (assertion.length) { + if (assertion?.public?.length || assertion?.private?.length) { await this.operationService.markOperationAsCompleted( operationId, blockchain, diff --git a/src/commands/protocols/get/sender/v1.0.0/v1-0-0-get-request-command.js b/src/commands/protocols/get/sender/v1.0.0/v1-0-0-get-request-command.js index 9a453f5e5..786001ab3 100644 --- a/src/commands/protocols/get/sender/v1.0.0/v1-0-0-get-request-command.js +++ b/src/commands/protocols/get/sender/v1.0.0/v1-0-0-get-request-command.js @@ -62,20 +62,30 @@ class GetRequestCommand extends ProtocolRequestCommand { } async handleAck(command, responseData) { - if (responseData?.assertion) { - // TODO: Add this validation - try { - await this.validationService.validateDatasetOnBlockchain( - command.data.knowledgeCollectionId, - responseData.assertion, - command.data.blockchain, - ); - } catch (e) { - return this.handleNack(command, { - errorMessage: e.message, - }); - } + const { blockchain, contract, knowledgeCollectionId, knowledgeAssetId } = command.data; + if (responseData?.assertion?.public) { + // Only whole collection can be validated not particular KA + if (!knowledgeAssetId) { + try { + await this.validationService.validateDatasetOnBlockchain( + responseData.assertion.public, + blockchain, + contract, + knowledgeCollectionId, + ); + // This is added as support when get starts supporting private for curated paranet + if (responseData.assertion?.private?.length) + await this.validationService.validatePrivateMerkleRoot( + responseData.assertion.public, + responseData.assertion.private, + ); + } catch (e) { + return this.handleNack(command, { + errorMessage: e.message, + }); + } + } await this.operationService.processResponse( command, OPERATION_REQUEST_STATUS.COMPLETED, diff --git a/src/commands/protocols/publish/sender/publish-validate-asset-blockchain-command.js b/src/commands/protocols/publish/sender/publish-validate-asset-blockchain-command.js index c51f6247c..808629850 100644 --- a/src/commands/protocols/publish/sender/publish-validate-asset-blockchain-command.js +++ b/src/commands/protocols/publish/sender/publish-validate-asset-blockchain-command.js @@ -1,11 +1,11 @@ import ValidateAssetCommand from '../../../common/validate-asset-command.js'; -import Command from '../../../command.js'; -import { OPERATION_ID_STATUS, ZERO_BYTES32 } from '../../../../constants/constants.js'; +import { OPERATION_ID_STATUS } from '../../../../constants/constants.js'; class PublishValidateAssetBlockchainCommand extends ValidateAssetCommand { constructor(ctx) { super(ctx); this.operationService = ctx.publishService; + this.validationService = ctx.validationService; } async handleError(operationId, blockchain, errorMessage, errorType) { @@ -29,37 +29,17 @@ class PublishValidateAssetBlockchainCommand extends ValidateAssetCommand { blockchain, OPERATION_ID_STATUS.VALIDATE_ASSET_BLOCKCHAIN_START, ); - - const blockchainAssertionId = - await this.blockchainModuleManager.getKnowledgeCollectionMerkleRoot( - blockchain, - contract, - tokenId, - ); - if (!blockchainAssertionId || blockchainAssertionId === ZERO_BYTES32) { - return Command.retry(); - } - - const { dataset: cachedData } = await this.operationIdService.getCachedOperationIdData( - operationId, - ); const ual = this.ualService.deriveUAL(blockchain, contract, tokenId); this.logger.debug( `Validating asset's public assertion with id: ${datasetRoot} ual: ${ual}`, ); - if (blockchainAssertionId !== datasetRoot) { - await this.handleError( - operationId, - blockchain, - `Invalid assertion id for asset ${ual}. Received value from blockchain: ${blockchainAssertionId}, received value from request: ${datasetRoot}`, - this.errorType, - true, - ); - return Command.empty(); - } - - await this.validationService.validateDatasetRoot(cachedData, datasetRoot); + await this.validationService.validateDatasetRootOnBlockchain( + datasetRoot, + blockchain, + contract, + tokenId, + ); await this.operationIdService.updateOperationIdStatus( operationId, diff --git a/src/commands/protocols/publish/sender/publish-validate-asset-command.js b/src/commands/protocols/publish/sender/publish-validate-asset-command.js index d6e1fa84c..bfdce4339 100644 --- a/src/commands/protocols/publish/sender/publish-validate-asset-command.js +++ b/src/commands/protocols/publish/sender/publish-validate-asset-command.js @@ -5,7 +5,6 @@ import { ERROR_TYPE, LOCAL_STORE_TYPES, PARANET_ACCESS_POLICY, - PRIVATE_ASSERTION_PREDICATE, } from '../../../../constants/constants.js'; class PublishValidateAssetCommand extends ValidateAssetCommand { @@ -62,18 +61,11 @@ class PublishValidateAssetCommand extends ValidateAssetCommand { ); await this.validationService.validateDatasetRoot(cachedData.dataset.public, datasetRoot); - const privateAssertionTriple = cachedData.dataset.public.find((triple) => - triple.includes(PRIVATE_ASSERTION_PREDICATE), - ); - - if (privateAssertionTriple) { - const privateAssertionRoot = privateAssertionTriple.split(' ')[2].slice(1, -1); - - await this.validationService.validateDatasetRoot( + if (cachedData.dataset?.private?.length) + await this.validationService.validatePrivateMerkleRoot( + cachedData.dataset.public, cachedData.dataset.private, - privateAssertionRoot, ); - } this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.PUBLISH.PUBLISH_VALIDATE_DATASET_ROOT_END, diff --git a/src/modules/triple-store/implementation/ot-triple-store.js b/src/modules/triple-store/implementation/ot-triple-store.js index 2051af4f3..66d90fb03 100644 --- a/src/modules/triple-store/implementation/ot-triple-store.js +++ b/src/modules/triple-store/implementation/ot-triple-store.js @@ -291,35 +291,56 @@ class OtTripleStore { await this.queryVoid(repository, query); } - async getKnowledgeCollectionNamedGraphs(repository, ual, visibility, sort) { - let visibilityFilter; - switch (visibility) { - case TRIPLES_VISIBILITY.PUBLIC: - case TRIPLES_VISIBILITY.PRIVATE: - visibilityFilter = `&& STRENDS(STR(?g), "${visibility}")`; - break; - case TRIPLES_VISIBILITY.ALL: - visibilityFilter = ''; - break; - default: - throw new Error(`Unsupported visibility: ${visibility}`); - } - const query = ` - PREFIX schema: <${SCHEMA_CONTEXT}> - CONSTRUCT { ?s ?p ?o . } + async getKnowledgeCollectionNamedGraphs(repository, ual, visibility) { + const assertion = {}; + if (visibility === TRIPLES_VISIBILITY.PUBLIC || visibility === TRIPLES_VISIBILITY.ALL) { + const query = ` + PREFIX schema: + CONSTRUCT { + ?s ?p ?o . + } WHERE { - GRAPH ?g { + { + SELECT ?s ?p ?o ?g + WHERE { + GRAPH ?g { + ?s ?p ?o . + } + FILTER ( + STRSTARTS(STR(?g), "${ual}") + && STRENDS(STR(?g), "${TRIPLES_VISIBILITY.PUBLIC}") + ) + } + ORDER BY ?g ?s ?p ?o + } + }`; + assertion.public = await this.construct(repository, query); + } + if (visibility === TRIPLES_VISIBILITY.PRIVATE || visibility === TRIPLES_VISIBILITY.ALL) { + const query = ` + PREFIX schema: + CONSTRUCT { ?s ?p ?o . } - FILTER( - STRSTARTS(STR(?g), "${ual}/") - ${visibilityFilter} - ) - } - ${sort ? 'ORDER BY ?s' : ''} - `; + WHERE { + { + SELECT ?s ?p ?o ?g + WHERE { + GRAPH ?g { + ?s ?p ?o . + } + FILTER ( + STRSTARTS(STR(?g), "${ual}") + && STRENDS(STR(?g), "${TRIPLES_VISIBILITY.PRIVATE}") + ) + } + ORDER BY ?g ?s ?p ?o + } + }`; + assertion.private = await this.construct(repository, query); + } - return this.construct(repository, query); + return assertion; } async knowledgeCollectionNamedGraphsExist(repository, ual) { diff --git a/src/service/triple-store-service.js b/src/service/triple-store-service.js index 57fab6bfb..a45d654da 100644 --- a/src/service/triple-store-service.js +++ b/src/service/triple-store-service.js @@ -419,18 +419,24 @@ class TripleStoreService { visibility, ); } + if (nquads?.public) { + nquads.public = nquads.public.split('\n').filter((line) => line !== ''); + } + if (nquads?.private) { + nquads.private = nquads.private.split('\n').filter((line) => line !== ''); + } - nquads = nquads.split('\n').filter((line) => line !== ''); + const numberOfnquads = (nquads?.public?.length ?? 0) + (nquads?.private?.length ?? 0); this.logger.debug( `Assertion: ${ual} ${ - nquads.length ? '' : 'is not' + numberOfnquads ? '' : 'is not' } found in the Triple Store's ${repository} repository.`, ); if (nquads.length) { this.logger.debug( - `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${nquads.length}.`, + `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${numberOfnquads}.`, ); } diff --git a/src/service/validation-service.js b/src/service/validation-service.js index 743d49c4b..286f483ca 100644 --- a/src/service/validation-service.js +++ b/src/service/validation-service.js @@ -1,4 +1,4 @@ -import { ZERO_ADDRESS } from '../constants/constants.js'; +import { ZERO_ADDRESS, PRIVATE_ASSERTION_PREDICATE } from '../constants/constants.js'; class ValidationService { constructor(ctx) { @@ -38,15 +38,43 @@ class ValidationService { this.logger.info(`Assertion integrity validated! AssertionId: ${assertionId}`); } - async validateDatasetRootOnBlockchain(knowledgeCollectionId, assertionId, blockchain) { - // TODO: call contract TO DO, dont return anything or return true - return { knowledgeCollectionId, assertionId, blockchain }; + async validateDatasetRootOnBlockchain( + knowledgeCollectionMerkleRoot, + blockchain, + assetStorageContractAddress, + knowledgeCollectionId, + ) { + const blockchainAssertionRoot = + await this.blockchainModuleManager.getKnowledgeCollectionLatestMerkleRoot( + blockchain, + assetStorageContractAddress, + knowledgeCollectionId, + ); + + if (knowledgeCollectionMerkleRoot !== blockchainAssertionRoot) { + throw new Error( + `Merkle Root validation failed. Merkle Root on chain: ${blockchainAssertionRoot}; Calculated Merkle Root: ${knowledgeCollectionMerkleRoot}`, + ); + } } - async validateDatasetOnBlockchain(knowledgeCollectionId, assertion, blockchain) { - const assertionId = await this.validationModuleManager.calculateRoot(assertion); + // Used to validate assertion node received through network get + async validateDatasetOnBlockchain( + assertion, + blockchain, + assetStorageContractAddress, + knowledgeCollectionId, + ) { + const knowledgeCollectionMerkleRoot = await this.validationModuleManager.calculateRoot( + assertion, + ); - await this.validateDatasetRootOnBlockchain(knowledgeCollectionId, assertionId, blockchain); + await this.validateDatasetRootOnBlockchain( + knowledgeCollectionMerkleRoot, + blockchain, + assetStorageContractAddress, + knowledgeCollectionId, + ); } async validateDatasetRoot(dataset, datasetRoot) { @@ -58,6 +86,22 @@ class ValidationService { ); } } + + async validatePrivateMerkleRoot(publicAssertion, privateAssertion) { + const privateAssertionTriple = publicAssertion.find((triple) => + triple.includes(PRIVATE_ASSERTION_PREDICATE), + ); + + if (privateAssertionTriple) { + const privateAssertionRoot = privateAssertionTriple.split(' ')[2].slice(1, -1); + + await this.validateDatasetRoot(privateAssertion, privateAssertionRoot); + } else { + throw new Error( + `Merkle Root validation failed. Private Merkle Root not present in public assertion.`, + ); + } + } } export default ValidationService;