UNPKG

origintrail-node

Version:

OriginTrail Node - Decentralized Knowledge Graph Node Library

278 lines (252 loc) 9.73 kB
import { OPERATION_ID_STATUS, ERROR_TYPE, OPERATION_REQUEST_STATUS, NETWORK_MESSAGE_TYPES, NETWORK_SIGNATURES_FOLDER, PUBLISHER_NODE_SIGNATURES_FOLDER, NETWORK_MESSAGE_TIMEOUT_MILLS, COMMAND_PRIORITY, } from '../../../../constants/constants.js'; import Command from '../../../command.js'; class PublishReplicationCommand extends Command { constructor(ctx) { super(ctx); this.operationIdService = ctx.operationIdService; this.operationService = ctx.publishService; this.shardingTableService = ctx.shardingTableService; this.networkModuleManager = ctx.networkModuleManager; this.blockchainModuleManager = ctx.blockchainModuleManager; this.signatureService = ctx.signatureService; this.cryptoService = ctx.cryptoService; this.messagingService = ctx.messagingService; this.errorType = ERROR_TYPE.LOCAL_STORE.LOCAL_STORE_ERROR; } async execute(command) { const { operationId, blockchain, datasetRoot, minimumNumberOfNodeReplications, batchSize } = command.data; this.logger.debug( `Searching for shard for operationId: ${operationId}, dataset root: ${datasetRoot}`, ); try { await this.operationIdService.updateOperationIdStatus( operationId, blockchain, OPERATION_ID_STATUS.FIND_NODES_START, ); const minAckResponses = this.operationService.getMinAckResponses( minimumNumberOfNodeReplications, ); const networkProtocols = this.operationService.getNetworkProtocols(); const shardNodes = []; let nodePartOfShard = false; const currentPeerId = this.networkModuleManager.getPeerId().toB58String(); const foundNodes = await this.findShardNodes(blockchain); for (const node of foundNodes) { if (node.id === currentPeerId) { nodePartOfShard = true; } else { shardNodes.push({ id: node.id, protocol: networkProtocols[0] }); } } this.logger.debug( `Found ${ shardNodes.length + (nodePartOfShard ? 1 : 0) } node(s) for operationId: ${operationId}`, ); this.logger.trace( `Found shard: ${JSON.stringify( shardNodes.map((node) => node.id), null, 2, )}`, ); if (shardNodes.length + (nodePartOfShard ? 1 : 0) < minAckResponses) { await this.handleError( operationId, blockchain, `Unable to find enough nodes for operationId: ${operationId}. Minimum number of nodes required: ${minAckResponses}`, this.errorType, true, ); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); return Command.empty(); } try { await this.operationIdService.updateOperationIdStatus( operationId, blockchain, OPERATION_ID_STATUS.PUBLISH.PUBLISH_REPLICATE_START, ); const batchSizePar = this.operationService.getBatchSize(batchSize); const { identityId, v, r, s, vs } = await this.createSignatures( blockchain, nodePartOfShard, datasetRoot, operationId, ); const updatedData = { ...command.data, batchSize: batchSizePar, minAckResponses, numberOfFoundNodes: shardNodes.length + (nodePartOfShard ? 1 : 0), }; // eslint-disable-next-line no-param-reassign command.data = updatedData; if (nodePartOfShard) { await this.operationService.processResponse( { ...command, data: updatedData }, OPERATION_REQUEST_STATUS.COMPLETED, { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: { identityId, v, r, s, vs }, }, null, ); } } catch (e) { await this.handleError(operationId, blockchain, e.message, this.errorType, true); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); return Command.empty(); } const { dataset } = await this.operationIdService.getCachedOperationIdData(operationId); const message = { dataset: dataset.public, datasetRoot, blockchain, }; // Run all message sending operations in parallel await Promise.all( shardNodes.map((node) => this.sendAndHandleMessage(node, operationId, message, command, blockchain), ), ); } catch (e) { await this.handleError(operationId, blockchain, e.message, this.errorType, true); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); return Command.empty(); } return Command.empty(); } async sendAndHandleMessage(node, operationId, message, command, blockchain) { const response = await this.messagingService.sendProtocolMessage( node, operationId, message, NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST, NETWORK_MESSAGE_TIMEOUT_MILLS.PUBLISH.REQUEST, ); const responseData = response.data; if (response.header.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.ACK) { // eslint-disable-next-line no-await-in-loop await this.signatureService.addSignatureToStorage( NETWORK_SIGNATURES_FOLDER, operationId, responseData.identityId, responseData.v, responseData.r, responseData.s, responseData.vs, ); // eslint-disable-next-line no-await-in-loop await this.operationService.processResponse( command, OPERATION_REQUEST_STATUS.COMPLETED, responseData, ); } else { // eslint-disable-next-line no-await-in-loop await this.operationService.processResponse( command, OPERATION_REQUEST_STATUS.FAILED, responseData, ); this.operationIdService.emitChangeEvent( OPERATION_ID_STATUS.FAILED, operationId, blockchain, ); } } async findShardNodes(blockchainId) { const shardNodes = await this.shardingTableService.findShard( blockchainId, true, // filter inactive nodes ); // TODO: Optimize this so it's returned by shardingTableService.findShard const nodesFound = await Promise.all( shardNodes.map(({ peerId }) => this.shardingTableService.findPeerAddressAndProtocols(peerId), ), ); return nodesFound; } async createSignatures(blockchain, nodePartOfShard, datasetRoot, operationId) { let v; let r; let s; let vs; const identityId = await this.blockchainModuleManager.getIdentityId(blockchain); if (nodePartOfShard) { ({ v, r, s, vs } = await this.signatureService.signMessage(blockchain, datasetRoot)); await this.signatureService.addSignatureToStorage( NETWORK_SIGNATURES_FOLDER, operationId, identityId, v, r, s, vs, ); } const { v: publisherNodeV, r: publisherNodeR, s: publisherNodeS, vs: publisherNodeVS, } = await this.signatureService.signMessage( blockchain, this.cryptoService.keccak256EncodePacked( ['uint72', 'bytes32'], [identityId, datasetRoot], ), ); await this.signatureService.addSignatureToStorage( PUBLISHER_NODE_SIGNATURES_FOLDER, operationId, identityId, publisherNodeV, publisherNodeR, publisherNodeS, publisherNodeVS, ); return { identityId, v, r, s, vs }; } /** * Builds default localStoreCommand * @param map * @returns {{add, data: *, delay: *, deadline: *}} */ default(map) { const command = { name: 'publishReplicationCommand', transactional: false, priority: COMMAND_PRIORITY.HIGHEST, }; Object.assign(command, map); return command; } } export default PublishReplicationCommand;