UNPKG

origintrail-node

Version:

OriginTrail Node - Decentralized Knowledge Graph Node Library

265 lines (237 loc) 9.63 kB
import Command from '../../command.js'; import { OPERATION_ID_STATUS, ERROR_TYPE, MAX_RETRIES_READ_CACHED_PUBLISH_DATA, RETRY_DELAY_READ_CACHED_PUBLISH_DATA, TRIPLE_STORE_REPOSITORIES, NETWORK_MESSAGE_TYPES, NETWORK_MESSAGE_TIMEOUT_MILLS, COMMAND_PRIORITY, } from '../../../constants/constants.js'; class PublishFinalizationCommand extends Command { constructor(ctx) { super(ctx); this.ualService = ctx.ualService; this.fileService = ctx.fileService; this.messagingService = ctx.messagingService; this.operationService = ctx.finalityService; this.errorType = ERROR_TYPE.STORE_ASSERTION_ERROR; this.tripleStoreService = ctx.tripleStoreService; this.operationIdService = ctx.operationIdService; this.networkModuleManager = ctx.networkModuleManager; this.repositoryModuleManager = ctx.repositoryModuleManager; this.dataService = ctx.dataService; this.blockchainModuleManager = ctx.blockchainModuleManager; } async execute(command) { const { event } = command.data; const eventData = JSON.parse(event.data); const { txHash, blockNumber } = event; const { id, publishOperationId, merkleRoot, byteSize } = eventData; const { blockchain, contractAddress } = event; const operationId = this.operationIdService.generateId(); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_START, operationId, blockchain, publishOperationId, ); let transaction; let blockTimestamp; try { [transaction, blockTimestamp] = await Promise.all([ this.blockchainModuleManager.getTransaction(blockchain, txHash), this.blockchainModuleManager.getBlockTimestamp(blockchain, blockNumber), ]); } catch (error) { this.logger.error(`Failed to get transaction or block timestamp: ${error.message}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, publishOperationId, ); return Command.empty(); } const metadata = { publisherKey: transaction.from.toLowerCase(), blockNumber, txHash, blockTimestamp, }; let publisherPeerId; let cachedMerkleRoot; let assertion; try { const result = await this.readWithRetries(publishOperationId); cachedMerkleRoot = result.merkleRoot; assertion = result.assertion; publisherPeerId = result.remotePeerId; } catch (error) { this.logger.error(`Failed to read cached publish data: ${error.message}`); // TODO: Make this log more descriptive this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, publishOperationId, ); return Command.empty(); } const ual = this.ualService.deriveUAL(blockchain, contractAddress, id); try { await this.validatePublishData( operationId, blockchain, merkleRoot, cachedMerkleRoot, byteSize, assertion, ual, ); } catch (e) { this.logger.error(`Failed to validate publish data: ${e.message}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, publishOperationId, ); return Command.empty(); } try { await this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_STORE_ASSERTION_START, operationId, blockchain, ); const totalTriples = await this.tripleStoreService.insertKnowledgeCollection( TRIPLE_STORE_REPOSITORIES.DKG, ual, assertion, metadata, ); await this.repositoryModuleManager.incrementInsertedTriples(totalTriples ?? 0); this.logger.info(`Number of triples added to the database +${totalTriples}`); await this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_STORE_ASSERTION_END, operationId, blockchain, ); const myPeerId = this.networkModuleManager.getPeerId().toB58String(); if (publisherPeerId === myPeerId) { await this.repositoryModuleManager.saveFinalityAck( publishOperationId, ual, publisherPeerId, ); for (const status of this.operationService.completedStatuses) { this.operationIdService.emitChangeEvent(status, operationId, blockchain); } } else { const networkProtocols = this.operationService.getNetworkProtocols(); const node = { id: publisherPeerId, protocol: networkProtocols[0] }; const message = { ual, publishOperationId, blockchain, operationId }; // TODO: Add retry logic maybe const response = await this.messagingService.sendProtocolMessage( node, operationId, message, NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST, NETWORK_MESSAGE_TIMEOUT_MILLS.FINALITY.REQUEST, ); await this.messagingService.handleProtocolResponse( response, this.operationService, blockchain, operationId, ); } } catch (e) { this.logger.error(`Command error (${this.errorType}): ${e.message}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, publishOperationId, ); } return Command.empty(); } async validatePublishData( operationId, blockchain, merkleRoot, cachedMerkleRoot, byteSize, assertion, ual, ) { try { if (merkleRoot !== cachedMerkleRoot) { const errorMessage = `Invalid Merkle Root for Knowledge Collection: ${ual}. Received value from blockchain: ${merkleRoot}, Cached value from publish operation: ${cachedMerkleRoot}`; this.logger.error(`Command error (${this.errorType}): ${errorMessage}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); } const calculatedAssertionSize = this.dataService.calculateAssertionSize( assertion.public ?? assertion, ); if (byteSize.toString() !== calculatedAssertionSize.toString()) { const errorMessage = `Invalid Assertion Size for Knowledge Collection: ${ual}. Received value from blockchain: ${byteSize}, Calculated value: ${calculatedAssertionSize}`; this.logger.error(`Command error (${this.errorType}): ${errorMessage}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); } } catch (e) { this.logger.error(`Command error (${this.errorType}): ${e.message}`); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); throw e; } } async readWithRetries(publishOperationId) { let attempt = 0; while (attempt < MAX_RETRIES_READ_CACHED_PUBLISH_DATA) { try { const datasetPath = this.fileService.getPendingStorageDocumentPath(publishOperationId); // eslint-disable-next-line no-await-in-loop const cachedData = await this.fileService.readFile(datasetPath, true); return cachedData; } catch (error) { attempt += 1; // eslint-disable-next-line no-await-in-loop await new Promise((resolve) => { setTimeout(resolve, RETRY_DELAY_READ_CACHED_PUBLISH_DATA); }); } } // TODO: Mark this operation as failed throw new Error('Failed to read cached publish data'); } /** * Builds default readCachedPublishDataCommand * @param map * @returns {{add, data: *, delay: *, deadline: *}} */ default(map) { const command = { name: 'publishFinalizationCommand', transactional: false, priority: COMMAND_PRIORITY.HIGHEST, }; Object.assign(command, map); return command; } } export default PublishFinalizationCommand;