lisk-framework
Version:
Lisk blockchain application platform
291 lines • 14 kB
JavaScript
"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