UNPKG

leasehold-http-api

Version:
218 lines (198 loc) 6.23 kB
/* * Copyright © 2019 Lisk Foundation * * See the LICENSE file at the top-level directory of this distribution * for licensing information. * * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, * no part of this software, including this file, may be copied, modified, * propagated, or distributed except according to the terms contained in the * LICENSE file. * * Removal or modification of this copyright notice is prohibited. */ 'use strict'; if (process.env.NEW_RELIC_LICENSE_KEY) { // eslint-disable-next-line global-require require('./helpers/newrelic_lisk'); } const { createLoggerComponent } = require('leasehold-lisk-framework/src/components/logger'); const { createCacheComponent, CACHE_KEYS_BLOCKS, CACHE_KEYS_DELEGATES, CACHE_KEYS_TRANSACTIONS, CACHE_KEYS_TRANSACTION_COUNT, } = require('leasehold-lisk-framework/src/components/cache'); const { createStorageComponent } = require('leasehold-lisk-framework/src/components/storage'); const { bootstrapStorage, setupServers, startListening, subscribeToEvents, bootstrapSwagger, bootstrapCache, } = require('./init_steps'); module.exports = class HttpApi { constructor(channel, options, appConfig) { options.root = __dirname; // TODO: See wy root comes defined for the chain module. this.channel = channel; this.options = options; this.chainModuleAlias = this.options.chainModuleAlias; this.chainModuleConfig = appConfig.modules[this.chainModuleAlias]; this.logger = null; this.scope = null; } async bootstrap() { global.constants = this.chainModuleConfig.constants; const { TRANSACTION_TYPES } = global.constants; // Logger const loggerConfig = await this.channel.invoke( 'app:getComponentConfig', { componentName: 'logger' }, ); this.logger = createLoggerComponent(loggerConfig); // Cache this.logger.debug('Initiating cache...'); const cacheConfig = await this.channel.invoke( 'app:getComponentConfig', { componentName: 'cache' }, ); const cache = createCacheComponent(cacheConfig, this.logger); // Storage this.logger.debug('Initiating storage...'); const storageConfigOptions = await this.channel.invoke( 'app:getComponentConfig', { componentName: 'storage' }, ); const leaseholdModuleOptions = await this.channel.invoke( `${this.chainModuleAlias}:getModuleOptions` ); const storageConfig = { ...storageConfigOptions, database: leaseholdModuleOptions.database }; const dbLogger = storageConfig.logFileName && storageConfig.logFileName === loggerConfig.logFileName ? this.logger : createLoggerComponent( Object.assign({}, loggerConfig, { logFileName: storageConfig.logFileName, }), ); const storage = createStorageComponent(storageConfig, dbLogger); const applicationState = await this.channel.invoke( 'app:getApplicationState', ); // Setup scope this.scope = { components: { cache, logger: this.logger, storage, }, chainModuleAlias: this.chainModuleAlias, channel: this.channel, config: this.options, lastCommitId: this.options.lastCommitId, buildVersion: this.options.buildVersion, applicationState, }; this.channel.subscribe('app:state:updated', event => { Object.assign(this.scope.applicationState, event.data); }); this.channel.subscribe(`${this.chainModuleAlias}:blocks:change`, async event => { await this.cleanCache( [CACHE_KEYS_BLOCKS, CACHE_KEYS_TRANSACTIONS], `${event.module}:${event.name}`, ); }); this.channel.subscribe(`${this.chainModuleAlias}:rounds:change`, async event => { await this.cleanCache( [CACHE_KEYS_DELEGATES], `${event.module}:${event.name}`, ); }); this.channel.subscribe( `${this.chainModuleAlias}:transactions:confirmed:change`, async event => { const transactions = event.data; // Default keys to clear const keysToClear = [CACHE_KEYS_TRANSACTION_COUNT]; // If there was a delegate registration clear delegates cache too const delegateTransaction = transactions.find( transaction => !!transaction && transaction.type === TRANSACTION_TYPES.DELEGATE, ); if (delegateTransaction) { keysToClear.push(CACHE_KEYS_DELEGATES); } // Only clear cache if the block actually includes transactions if (transactions.length) { await this.cleanCache(keysToClear, `${event.module}:${event.name}`); } }, ); // Bootstrap Cache component await bootstrapCache(this.scope); // Bootstrap Storage component await bootstrapStorage(this.scope, global.constants.ACTIVE_DELEGATES); // Set up Express and HTTP(s) and WS(s) servers const { expressApp, httpServer, httpsServer, wsServer, } = await setupServers(this.scope); // Bootstrap Swagger and attaches it to Express app await bootstrapSwagger(this.scope, expressApp); // Start listening for HTTP(s) requests await startListening(this.scope, { httpServer, httpsServer }); // Subscribe to channel events subscribeToEvents(this.scope, { wsServer }); } async cleanup(code, error) { const { components } = this.scope; if (error) { this.logger.fatal(error.toString()); if (code === undefined) { code = 1; } } else if (code === undefined || code === null) { code = 0; } this.logger.info('Cleaning HTTP API...'); try { if (components !== undefined) { Object.keys(components).forEach(async key => { if (components[key].cleanup) { await components[key].cleanup(); } }); } } catch (componentCleanupError) { this.logger.error(componentCleanupError); } this.logger.info('Cleaned up successfully'); } async cleanCache(cacheKeysToClear, eventInfo) { if ( this.scope.components && this.scope.components.cache && this.scope.components.cache.isReady() ) { const tasks = cacheKeysToClear.map(key => this.scope.components.cache.removeByPattern(key), ); try { this.logger.info( `Cache - Keys with patterns: '${cacheKeysToClear}' cleared from cache on '${eventInfo}'`, ); await Promise.all(tasks); } catch (error) { this.logger.error(`Cache - Error clearing keys on new Block: ${error}`); } } } };