UNPKG

origintrail-node

Version:

OriginTrail Node - Decentralized Knowledge Graph Node Library

475 lines (411 loc) 19.6 kB
import DeepExtend from 'deep-extend'; import rc from 'rc'; import EventEmitter from 'events'; import { createRequire } from 'module'; import { execSync } from 'child_process'; import DependencyInjection from './src/service/dependency-injection.js'; import Logger from './src/logger/logger.js'; import { MIN_NODE_VERSION, PARANET_ACCESS_POLICY } from './src/constants/constants.js'; import FileService from './src/service/file-service.js'; import OtnodeUpdateCommand from './src/commands/common/otnode-update-command.js'; import OtAutoUpdater from './src/modules/auto-updater/implementation/ot-auto-updater.js'; import MigrationExecutor from './src/migration/migration-executor.js'; const require = createRequire(import.meta.url); const { setTimeout } = require('timers/promises'); const pjson = require('./package.json'); const configjson = require('./config/config.json'); class OTNode { constructor(config) { this.initializeConfiguration(config); this.initializeLogger(); this.initializeFileService(); this.initializeAutoUpdaterModule(); this.checkNodeVersion(); // Only set up process event listeners if not running as library if (!process.env.OT_NODE_LIBRARY_MODE) { process.on('SIGINT', () => this.handleExit()); // Ctrl+C process.on('SIGTERM', () => this.handleExit()); // kill command or Docker stop } } async start() { await this.checkForUpdate(); await this.removeUpdateFile(); await MigrationExecutor.executeTripleStoreUserConfigurationMigration( this.container, this.logger, this.config, ); await MigrationExecutor.executeRedisSetupMigration( this.container, this.logger, this.config, ); this.logger.info('██████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ '); this.logger.info('██╔══██╗██║ ██╔╝██╔════╝ ██║ ██║██╔══██╗'); this.logger.info('██║ ██║█████╔╝ ██║ ███╗ ██║ ██║╚█████╔╝'); this.logger.info('██║ ██║██╔═██╗ ██║ ██║ ╚██╗ ██╔╝██╔══██╗'); this.logger.info('██████╔╝██║ ██╗╚██████╔╝ ╚████╔╝ ╚█████╔╝'); this.logger.info('╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚════╝ '); this.logger.info('======================================================'); this.logger.info(` OriginTrail Node v${pjson.version}`); this.logger.info('======================================================'); this.logger.info(`Node is running in ${process.env.NODE_ENV} environment`); await this.initializeDependencyContainer(); this.initializeEventEmitter(); await this.initializeModules(); this.initializeBlockchainEventsService(); await this.initializeShardingTableService(); await this.initializeParanets(); await this.createProfiles(); await this.initializeCommandExecutor(); await this.initializeRouters(); await this.startNetworkModule(); this.resumeCommandExecutor(); await this.initializeProofing(); await this.initializeClaimRewards(); await this.initializeSyncService(); await this.initializeBlazegraphHealthService(); this.logger.info('Node is up and running!'); } checkNodeVersion() { const nodeMajorVersion = process.versions.node.split('.')[0]; this.logger.warn('======================================================'); this.logger.warn(`Using node.js version: ${process.versions.node}`); if (nodeMajorVersion < MIN_NODE_VERSION) { this.logger.warn( `This node was tested with node.js version 16. To make sure that your node is running properly please update your node version!`, ); } this.logger.warn('======================================================'); } initializeLogger() { const logLevel = process.env.LOG_LEVEL || this.config.logging.defaultLevel; this.logger = new Logger(logLevel); // Suppress console output if silent mode is enabled if (process.env.OT_NODE_SILENT === 'true') { this.logger.silent = true; } } initializeFileService() { this.fileService = new FileService({ config: this.config, logger: this.logger }); } initializeAutoUpdaterModule() { this.autoUpdaterModuleManager = new OtAutoUpdater(); this.autoUpdaterModuleManager.initialize( this.config.modules.autoUpdater.implementation['ot-auto-updater'].config, this.logger, ); } initializeConfiguration(userConfig) { const defaultConfig = JSON.parse(JSON.stringify(configjson[process.env.NODE_ENV])); if (userConfig) { this.config = DeepExtend(defaultConfig, userConfig); } else { this.config = rc(pjson.name, defaultConfig); } if (!this.config.configFilename) { // set default user configuration filename this.config.configFilename = '.origintrail_noderc'; } // Support for external data path if (process.env.OT_NODE_DATA_PATH) { this.config.appDataPath = process.env.OT_NODE_DATA_PATH; } } async initializeDependencyContainer() { this.container = await DependencyInjection.initialize(); DependencyInjection.registerValue(this.container, 'config', this.config); DependencyInjection.registerValue(this.container, 'logger', this.logger); this.logger.info('Dependency injection module is initialized'); } async initializeModules() { const initializationPromises = []; for (const moduleName in this.config.modules) { const moduleManagerName = `${moduleName}ModuleManager`; const moduleManager = this.container.resolve(moduleManagerName); initializationPromises.push(moduleManager.initialize()); } try { await Promise.all(initializationPromises); this.logger.info(`All modules initialized!`); } catch (e) { this.logger.error(`Module initialization failed. Error message: ${e.message}`); this.stop(1); } } initializeEventEmitter() { const eventEmitter = new EventEmitter(); DependencyInjection.registerValue(this.container, 'eventEmitter', eventEmitter); this.logger.info('Event emitter initialized'); } async initializeRouters() { try { this.logger.info('Initializing http api and rpc router'); const routerNames = ['httpApiRouter', 'rpcRouter']; await Promise.all( routerNames.map(async (routerName) => { const router = this.container.resolve(routerName); try { await router.initialize(); } catch (error) { this.logger.error( `${routerName} initialization failed. Error message: ${error.message}, ${error.stackTrace}`, ); this.stop(1); } }), ); this.logger.info('Routers initialized successfully'); } catch (error) { this.logger.error( `Failed to initialize routers: ${error.message}, ${error.stackTrace}`, ); this.stop(1); } } async createProfiles() { const cryptoService = this.container.resolve('cryptoService'); const blockchainModuleManager = this.container.resolve('blockchainModuleManager'); const networkModuleManager = this.container.resolve('networkModuleManager'); const peerId = networkModuleManager.getPeerId().toB58String(); const createProfilesPromises = blockchainModuleManager .getImplementationNames() .map(async (blockchain) => { try { const identityExists = await blockchainModuleManager.identityIdExists( blockchain, ); if (!identityExists) { this.logger.info(`Creating profile on network: ${blockchain}`); await blockchainModuleManager.createProfile(blockchain, peerId); if ( process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test' ) { const blockchainConfig = blockchainModuleManager.getModuleConfiguration(blockchain); execSync( `npm run set-ask -- --rpcEndpoint=${ blockchainConfig.rpcEndpoints[0] } --ask=${1 + Math.random() * 0.5} --privateKey=${ blockchainConfig.operationalWallets[0].privateKey } --hubContractAddress=${blockchainConfig.hubContractAddress}`, { stdio: 'inherit' }, ); await setTimeout(10000 + Math.random() * 10000); execSync( `npm run set-stake -- --rpcEndpoint=${blockchainConfig.rpcEndpoints[0]} --stake=${blockchainConfig.initialStakeAmount} --operationalWalletPrivateKey=${blockchainConfig.operationalWallets[0].privateKey} --managementWalletPrivateKey=${blockchainConfig.evmManagementWalletPrivateKey} --hubContractAddress=${blockchainConfig.hubContractAddress}`, { stdio: 'inherit' }, ); } } const identityId = await blockchainModuleManager.getIdentityId(blockchain); this.logger.info(`Identity ID: ${identityId}`); if (identityExists) { const onChainNodeId = await blockchainModuleManager.getNodeId( blockchain, identityId, ); const onChainPeerId = cryptoService.convertHexToAscii(onChainNodeId); if (peerId !== onChainPeerId) { this.logger.warn( `Local peer id: ${peerId} doesn't match on chain peer id: ${onChainPeerId} for blockchain: ${blockchain}, identity id: ${identityId}.`, ); blockchainModuleManager.removeImplementation(blockchain); } } } catch (error) { this.logger.warn( `Unable to create ${blockchain} blockchain profile. Removing implementation. Error: ${error.message}`, ); blockchainModuleManager.removeImplementation(blockchain); } }); await Promise.all(createProfilesPromises); if (!blockchainModuleManager.getImplementationNames().length) { this.logger.error(`Unable to create blockchain profiles. OT-node shutting down...`); this.stop(1); } } async initializeCommandExecutor() { try { const commandExecutor = this.container.resolve('commandExecutor'); await commandExecutor.pauseCommandExecutor(); await commandExecutor.addDefaultCommands(); // commandExecutor // .replayOldCommands() // .then(() => this.logger.info('Finished replaying old commands')); } catch (e) { this.logger.error( `Command executor initialization failed. Error message: ${e.message}`, ); this.stop(1); } } resumeCommandExecutor() { try { const commandExecutor = this.container.resolve('commandExecutor'); commandExecutor.resumeCommandExecutor(); } catch (e) { this.logger.error( `Unable to resume command executor queue. Error message: ${e.message}`, ); this.stop(1); } } async startNetworkModule() { const networkModuleManager = this.container.resolve('networkModuleManager'); await networkModuleManager.start(); } async initializeShardingTableService() { try { const shardingTableService = this.container.resolve('shardingTableService'); await shardingTableService.initialize(); this.logger.info('Sharding Table Service initialized successfully'); } catch (error) { this.logger.error( `Unable to initialize sharding table service. Error message: ${error.message} OT-node shutting down...`, ); this.stop(1); } } initializeBlockchainEventsService() { try { const blockchainEventsService = this.container.resolve('blockchainEventsService'); blockchainEventsService.initializeBlockchainEventsServices(); this.logger.info('Blockchain Events Service initialized successfully'); } catch (error) { this.logger.error( `Unable to initialize Blockchain Events Service. Error message: ${error.message} OT-node shutting down...`, ); this.stop(1); } } async removeUpdateFile() { const updateFilePath = this.fileService.getUpdateFilePath(); await this.fileService.removeFile(updateFilePath).catch((error) => { this.logger.warn(`Unable to remove update file. Error: ${error}`); }); this.config.otNodeUpdated = true; } async checkForUpdate() { const autoUpdaterCommand = new OtnodeUpdateCommand({ logger: this.logger, config: this.config, fileService: this.fileService, autoUpdaterModuleManager: this.autoUpdaterModuleManager, }); await autoUpdaterCommand.execute(); } async initializeParanets() { const blockchainModuleManager = this.container.resolve('blockchainModuleManager'); const tripleStoreService = this.container.resolve('tripleStoreService'); const paranetService = this.container.resolve('paranetService'); const ualService = this.container.resolve('ualService'); const validParanets = []; const syncParanets = this.config.assetSync && this.config.assetSync.syncParanets ? this.config.assetSync.syncParanets : []; for (const paranetUAL of syncParanets) { if (!ualService.isUAL(paranetUAL)) { this.logger.warn( `Unable to initialize Paranet with id ${paranetUAL} because of invalid UAL format`, ); continue; } const { blockchain, contract, knowledgeCollectionId, knowledgeAssetId } = ualService.resolveUAL(paranetUAL); if (!knowledgeAssetId) { this.logger.warn( `Invalid paranet UAL: ${paranetUAL} . Knowledge asset token id is required!`, ); continue; } if (!blockchainModuleManager.getImplementationNames().includes(blockchain)) { this.logger.warn( `Unable to initialize Paranet with id ${paranetUAL} because of unsupported blockchain implementation`, ); continue; } const paranetId = paranetService.constructParanetId( contract, knowledgeCollectionId, knowledgeAssetId, ); // eslint-disable-next-line no-await-in-loop const paranetExists = await blockchainModuleManager.paranetExists( blockchain, paranetId, ); if (!paranetExists) { this.logger.warn( `Unable to initialize Paranet with id ${paranetUAL} because it doesn't exist`, ); continue; } // eslint-disable-next-line no-await-in-loop const nodesAccessPolicy = await blockchainModuleManager.getNodesAccessPolicy( blockchain, paranetId, ); if (nodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) { // eslint-disable-next-line no-await-in-loop const identityId = await blockchainModuleManager.getIdentityId(blockchain); // eslint-disable-next-line no-await-in-loop const isPermissionedNode = await blockchainModuleManager.isPermissionedNode( blockchain, paranetId, identityId, ); if (!isPermissionedNode) { this.logger.warn( `Unable to initialize Paranet with id ${paranetUAL} because node with id ${identityId} is not a permissioned node`, ); continue; } } validParanets.push(paranetUAL); // eslint-disable-next-line no-await-in-loop await paranetService.initializeParanetRecord(blockchain, paranetId); } this.config.assetSync.syncParanets = validParanets; tripleStoreService.initializeRepositories(); } async initializeProofing() { const proofingService = this.container.resolve('proofingService'); await proofingService.initialize(); } async initializeClaimRewards() { const claimRewardsService = this.container.resolve('claimRewardsService'); await claimRewardsService.initialize(); } async initializeSyncService() { const syncService = this.container.resolve('syncService'); await syncService.initialize(); } async initializeBlazegraphHealthService() { const blazegraphHealthService = this.container.resolve('blazegraphHealthService'); await blazegraphHealthService.initialize(); this.logger.info('Blazegraph Health Service initialized successfully'); } stop(code = 0) { this.logger.info('Stopping node...'); // Only exit process if not running as library if (!process.env.OT_NODE_LIBRARY_MODE) { process.exit(code); } } async handleExit() { this.logger.info('SIGINT or SIGTERM received. Shutting down...'); const commandExecutor = this.container?.resolve('commandExecutor'); if (commandExecutor) { await commandExecutor.commandExecutorShutdown(); } // Only exit process if not running as library if (!process.env.OT_NODE_LIBRARY_MODE) { process.exit(0); } } } export default OTNode;