Skip to content

Commit

Permalink
Merge pull request #3517 from OriginTrail/v8/chore/add-on-chain-valid…
Browse files Browse the repository at this point in the history
…ation

Add on chain validation for publish and get
  • Loading branch information
Mihajlo-Pavlovic authored Dec 17, 2024
2 parents a0a035d + a1dbbbc commit 3d3b074
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,15 @@ class HandleGetRequestCommand extends HandleProtocolMessageCommand {
...(includeMetadata && metadata && { metadata }),
};

if (assertion.length) {
if (assertion?.public?.length) {
await this.operationIdService.updateOperationIdStatus(
operationId,
blockchain,
OPERATION_ID_STATUS.GET.GET_REMOTE_END,
);
}

return assertion.length
return assertion?.public?.length
? { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: responseData }
: {
messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/protocols/get/sender/local-get-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ERROR_TYPE,
LOCAL_STORE_TYPES,
PARANET_ACCESS_POLICY,
PRIVATE_ASSERTION_PREDICATE,
} from '../../../../constants/constants.js';

class PublishValidateAssetCommand extends ValidateAssetCommand {
Expand Down Expand Up @@ -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,
Expand Down
71 changes: 46 additions & 25 deletions src/modules/triple-store/implementation/ot-triple-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: <http://schema.org/>
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: <http://schema.org/>
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) {
Expand Down
12 changes: 9 additions & 3 deletions src/service/triple-store-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}.`,
);
}

Expand Down
58 changes: 51 additions & 7 deletions src/service/validation-service.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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;

0 comments on commit 3d3b074

Please sign in to comment.