UNPKG

lisk-framework

Version:

Lisk blockchain application platform

291 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Application = void 0; const fs = require("fs-extra"); const path = require("path"); const psList = require("ps-list"); const assert = require("assert"); const lisk_db_1 = require("@liskhq/lisk-db"); const lisk_validator_1 = require("@liskhq/lisk-validator"); const lisk_utils_1 = require("@liskhq/lisk-utils"); const constants_1 = require("./constants"); const system_dirs_1 = require("./system_dirs"); const controller_1 = require("./controller"); const schema_1 = require("./schema"); const logger_1 = require("./logger"); const errors_1 = require("./errors"); const endpoint_1 = require("./endpoint"); const validators_1 = require("./modules/validators"); const token_1 = require("./modules/token"); const auth_1 = require("./modules/auth"); const fee_1 = require("./modules/fee"); const random_1 = require("./modules/random"); const pos_1 = require("./modules/pos"); const genesis_block_1 = require("./genesis_block"); const state_machine_1 = require("./state_machine"); const abi_handler_1 = require("./abi_handler/abi_handler"); const interoperability_1 = require("./modules/interoperability"); const dynamic_reward_1 = require("./modules/dynamic_reward"); const engine_1 = require("./engine"); const isPidRunning = async (pid) => psList().then(list => list.some(x => x.pid === pid)); const registerProcessHooks = (app) => { const handleShutdown = async (code, message) => { await app.shutdown(code, message); }; process.on('uncaughtException', err => { app.logger.error({ err, }, 'System error: uncaughtException'); handleShutdown(1, err.message).catch((error) => app.logger.error({ error })); }); process.on('unhandledRejection', err => { app.logger.fatal({ err, }, 'System error: unhandledRejection'); handleShutdown(1, err.message).catch((error) => app.logger.error({ error })); }); process.once('SIGTERM', () => { handleShutdown(0, 'SIGTERM').catch((error) => app.logger.error({ error })); }); process.once('SIGINT', () => { handleShutdown(0, 'SIGINT').catch((error) => app.logger.error({ error })); }); process.once('exit', (code) => { handleShutdown(code, 'process.exit').catch((error) => app.logger.error({ error })); }); }; class Application { constructor(config = {}) { this._registeredModules = []; this._mutex = new lisk_utils_1.jobHandlers.Mutex(); const appConfig = lisk_utils_1.objects.cloneDeep(schema_1.applicationConfigSchema.default); const mergedConfig = lisk_utils_1.objects.mergeDeep({}, appConfig, config); lisk_validator_1.validator.validate(schema_1.applicationConfigSchema, mergedConfig); this.config = mergedConfig; const { plugins, ...rootConfigs } = this.config; this._controller = new controller_1.Controller({ appConfig: rootConfigs, pluginConfigs: plugins, chainID: Buffer.from(this.config.genesis.chainID, 'hex'), }); this._stateMachine = new state_machine_1.StateMachine(); } get channel() { if (!this._controller.channel) { throw new Error('Controller is not initialized yet.'); } return this._controller.channel; } static defaultApplication(config = {}, mainchain = false) { const application = new Application(config); const authModule = new auth_1.AuthModule(); const tokenModule = new token_1.TokenModule(); const feeModule = new fee_1.FeeModule(); const dynamicRewardModule = new dynamic_reward_1.DynamicRewardModule(); const randomModule = new random_1.RandomModule(); const validatorModule = new validators_1.ValidatorsModule(); const posModule = new pos_1.PoSModule(); let interoperabilityModule; if (mainchain) { interoperabilityModule = new interoperability_1.MainchainInteroperabilityModule(); interoperabilityModule.addDependencies(tokenModule.method, feeModule.method); } else { interoperabilityModule = new interoperability_1.SidechainInteroperabilityModule(); interoperabilityModule.addDependencies(validatorModule.method, tokenModule.method); } feeModule.addDependencies(tokenModule.method, interoperabilityModule.method); dynamicRewardModule.addDependencies(tokenModule.method, randomModule.method, validatorModule.method, posModule.method); posModule.addDependencies(randomModule.method, validatorModule.method, tokenModule.method, feeModule.method); tokenModule.addDependencies(interoperabilityModule.method, feeModule.method); interoperabilityModule.registerInteroperableModule(tokenModule); interoperabilityModule.registerInteroperableModule(feeModule); application._registerModule(authModule); application._registerModule(validatorModule); application._registerModule(tokenModule); application._registerModule(feeModule); application._registerModule(interoperabilityModule); application._registerModule(posModule); application._registerModule(randomModule); application._registerModule(dynamicRewardModule); return { app: application, method: { validator: validatorModule.method, token: tokenModule.method, auth: authModule.method, fee: feeModule.method, pos: posModule.method, random: randomModule.method, reward: dynamicRewardModule.method, interoperability: interoperabilityModule.method, }, }; } registerPlugin(plugin, options = { loadAsChildProcess: false }) { for (const registeredModule of this._registeredModules) { if (plugin.name === registeredModule.name) { throw new Error(`A module with name "${plugin.name}" is already registered.`); } } this._controller.registerPlugin(plugin, options); } registerModule(Module) { this._registerModule(Module); } registerInteroperableModule(interoperableModule) { const interoperabilityModule = this._registeredModules.find(module => module.name === interoperability_1.MODULE_NAME_INTEROPERABILITY); if (interoperabilityModule === undefined) { throw new Error(`${interoperability_1.MODULE_NAME_INTEROPERABILITY} module is not registered.`); } interoperabilityModule.registerInteroperableModule(interoperableModule); } getRegisteredModules() { return this._registeredModules; } getMetadata() { const modules = this._registeredModules.map(mod => { const meta = mod.metadata(); return { ...meta, name: mod.name, }; }); modules.sort((a, b) => a.name.localeCompare(b.name, 'en')); return modules; } async run() { Object.freeze(this.config); registerProcessHooks(this); await this._setupDirectories(); this.logger = this._initLogger(); this.logger.info(`Starting the app at ${this.config.system.dataPath}`); this.logger.info('If you experience any type of error, please open an issue on Lisk GitHub: https://github.com/LiskHQ/lisk-sdk/issues'); this.logger.info('Contribution guidelines can be found at Lisk-sdk: https://github.com/LiskHQ/lisk-sdk/blob/development/docs/CONTRIBUTING.md'); this.logger.info('Booting the application with Lisk Framework'); await this._validatePidFile(); const { data: dbFolder } = (0, system_dirs_1.systemDirs)(this.config.system.dataPath); this.logger.debug({ dbFolder }, `Create ${constants_1.MODULE_DB_NAME} database instance.`); this._moduleDB = new lisk_db_1.Database(path.join(dbFolder, constants_1.MODULE_DB_NAME)); this.logger.debug({ dbFolder }, `Create ${constants_1.STATE_DB_NAME} database instance.`); this._stateDB = new lisk_db_1.StateDB(path.join(dbFolder, constants_1.STATE_DB_NAME)); await this._mutex.runExclusive(async () => { this._controller.init({ logger: this.logger, stateDB: this._stateDB, moduleDB: this._moduleDB, endpoints: this._rootEndpoints(), events: [constants_1.APP_EVENT_READY.replace('app_', ''), constants_1.APP_EVENT_SHUTDOWN.replace('app_', '')], }); await this._stateMachine.init(this.logger, this.config.genesis, this.config.modules); this._abiHandler = new abi_handler_1.ABIHandler({ channel: this._controller.channel, config: this.config, logger: this.logger, moduleDB: this._moduleDB, modules: this._registeredModules, stateDB: this._stateDB, stateMachine: this._stateMachine, chainID: Buffer.from(this.config.genesis.chainID, 'hex'), }); await this._abiHandler.cacheGenesisState(); this._engineProcess = new engine_1.Engine(this._abiHandler, this.config); await this._engineProcess.start(); await this._controller.start(); for (const method of this._controller.getEndpoints()) { this.logger.info({ method }, `Registered endpoint`); } this.channel.publish(constants_1.APP_EVENT_READY); }); } async shutdown(errorCode = 0, message = '') { this.logger.info({ errorCode, message }, 'Application shutdown started'); const release = await this._mutex.acquire(); try { this.channel.publish(constants_1.APP_EVENT_SHUTDOWN); await this._stopEngine(); await this._controller.stop(errorCode, message); this._stateDB.close(); this._moduleDB.close(); await this._emptySocketsDirectory(); this._clearControllerPidFile(); this.logger.info({ errorCode, message }, 'Application shutdown completed'); } catch (error) { this.logger.fatal({ err: error }, 'Application shutdown failed'); } finally { this.config = lisk_utils_1.objects.mergeDeep({}, this.config); release(); process.removeAllListeners('exit'); process.exit(errorCode); } } async generateGenesisBlock(input) { if (!this.logger) { this.logger = this._initLogger(); } await this._stateMachine.init(this.logger, this.config.genesis, this.config.modules); return (0, genesis_block_1.generateGenesisBlock)(this._stateMachine, this.logger, input); } _registerModule(mod) { assert(mod, 'Module implementation is required'); if (Object.keys(this._controller.getRegisteredPlugins()).includes(mod.name)) { throw new Error(`A plugin with name "${mod.name}" is already registered.`); } this._registeredModules.push(mod); this._stateMachine.registerModule(mod); this._controller.registerEndpoint(mod.name, (0, endpoint_1.getEndpointHandlers)(mod.endpoint)); } _initLogger() { var _a, _b; return (0, logger_1.createLogger)({ logLevel: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.system.logLevel) !== null && _b !== void 0 ? _b : 'none', name: 'application', }); } _rootEndpoints() { const applicationEndpoint = new Map([ [ 'getRegisteredEndpoints', async (_) => this._controller.getEndpoints(), ], ['getRegisteredEvents', async (_) => this._controller.getEvents()], ]); return (0, endpoint_1.mergeEndpointHandlers)(applicationEndpoint, new Map()); } async _setupDirectories() { const dirs = (0, system_dirs_1.systemDirs)(this.config.system.dataPath); await Promise.all(Array.from(Object.values(dirs)).map(async (dirPath) => fs.ensureDir(dirPath))); } async _emptySocketsDirectory() { const { sockets } = (0, system_dirs_1.systemDirs)(this.config.system.dataPath); const socketFiles = fs.readdirSync(sockets); await Promise.all(socketFiles.map(async (aSocketFile) => fs.unlink(path.join(sockets, aSocketFile)))); } async _validatePidFile() { const dirs = (0, system_dirs_1.systemDirs)(this.config.system.dataPath); const pidPath = path.join(dirs.pids, 'controller.pid'); const pidExists = await fs.pathExists(pidPath); if (pidExists) { const pid = parseInt((await fs.readFile(pidPath)).toString(), 10); const pidRunning = await isPidRunning(pid); this.logger.info({ pid }, 'Previous Lisk PID'); this.logger.info({ pid: process.pid }, 'Current Lisk PID'); if (pidRunning && pid !== process.pid) { this.logger.error({ dataPath: this.config.system.dataPath }, 'An instance of application is already running, please change the application label to run another instance'); throw new errors_1.DuplicateAppInstanceError(this.config.system.dataPath, pidPath); } } await fs.writeFile(pidPath, process.pid.toString(), { mode: constants_1.OWNER_READ_WRITE }); } _clearControllerPidFile() { const dirs = (0, system_dirs_1.systemDirs)(this.config.system.dataPath); fs.unlinkSync(path.join(dirs.pids, 'controller.pid')); } async _stopEngine() { await this._engineProcess.stop(); } } exports.Application = Application; //# sourceMappingURL=application.js.map