UNPKG

lisk-framework

Version:

Lisk blockchain application platform

396 lines 18.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ABIHandler = exports.EVENT_ENGINE_READY = void 0; const events_1 = require("events"); const lisk_chain_1 = require("@liskhq/lisk-chain"); const lisk_db_1 = require("@liskhq/lisk-db"); const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const abi_1 = require("../abi"); const state_machine_1 = require("../state_machine"); const generator_context_1 = require("../state_machine/generator_context"); const prefixed_state_read_writer_1 = require("../state_machine/prefixed_state_read_writer"); const genesis_block_1 = require("../utils/genesis_block"); const constants_1 = require("../constants"); const backup_1 = require("../utils/backup"); exports.EVENT_ENGINE_READY = 'EVENT_ENGINE_READY'; class ABIHandler { constructor(args) { this.event = new events_1.EventEmitter(); this._config = args.config; this._logger = args.logger; this._stateMachine = args.stateMachine; this._stateDB = args.stateDB; this._moduleDB = args.moduleDB; this._modules = args.modules; this._channel = args.channel; this._chainID = args.chainID; } get chainID() { if (!this._chainID) { throw new Error('Chain ID is not set.'); } return this._chainID; } async cacheGenesisState() { var _a, _b; const { version, root } = await this._stateDB.getCurrentState(); if (version !== 0 || !root.equals(lisk_cryptography_1.utils.hash(Buffer.alloc(0)))) { this._logger.debug({ version, root: root.toString('hex') }, 'Skip caching genesis state as state is already initialized'); return; } const genesisBlock = (0, genesis_block_1.readGenesisBlock)(this._config, this._logger); this._logger.info({ stateRoot: (_a = genesisBlock.header.stateRoot) === null || _a === void 0 ? void 0 : _a.toString('hex'), height: genesisBlock.header.height, }, 'Start caching genesis state'); const stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter()); const context = new state_machine_1.GenesisBlockContext({ eventQueue: new state_machine_1.EventQueue(genesisBlock.header.height), header: genesisBlock.header, logger: this._logger, stateStore, assets: genesisBlock.assets, chainID: this.chainID, }); let stateRoot; try { await this._stateMachine.executeGenesisBlock(context); stateRoot = await this._stateDB.commit(stateStore.inner, genesisBlock.header.height, lisk_cryptography_1.utils.hash(Buffer.alloc(0)), { readonly: false, expectedRoot: genesisBlock.header.stateRoot, checkRoot: true, }); } finally { stateStore.inner.close(); } this._cachedGenesisState = { stateRoot, events: context.eventQueue.getEvents().map(e => e.toObject()), certificateThreshold: context.nextValidators.certificateThreshold, nextValidators: context.nextValidators.validators, preCommitThreshold: context.nextValidators.precommitThreshold, }; this._logger.info({ stateRoot: (_b = genesisBlock.header.stateRoot) === null || _b === void 0 ? void 0 : _b.toString('hex'), height: genesisBlock.header.height, }, 'Cached genesis state'); } async init(req) { this._chainID = Buffer.from(this._config.genesis.chainID, 'hex'); const currentState = await this._stateDB.getCurrentState(); if (req.lastBlockHeight > currentState.version) { throw new Error(`Invalid engine state. Conflict at engine height ${req.lastBlockHeight} and application state ${currentState.version}.`); } if (req.lastBlockHeight < currentState.version) { this._logger.info({ engineHeight: req.lastBlockHeight, applicationHeight: currentState.version }, 'Application is in invalid state. Trying to recover'); const diff = currentState.version - req.lastBlockHeight; for (let i = 0; i < diff; i += 1) { const previousRoot = await this._stateDB.revert(currentState.root, currentState.version); currentState.root = previousRoot; currentState.version -= 1; } if (!currentState.root.equals(req.lastStateRoot)) { throw new Error(`State cannot be recovered. Conflict at height ${req.lastBlockHeight} Engine state ${req.lastStateRoot.toString('hex')} and application state ${currentState.root.toString('hex')}.`); } } this.event.emit(exports.EVENT_ENGINE_READY); return {}; } async initStateMachine(req) { if (this._executionContext !== undefined) { throw new Error(`Execution context is already initialized with ${this._executionContext.id.toString('hex')}`); } const id = lisk_cryptography_1.utils.hash(lisk_codec_1.codec.encode(abi_1.blockHeaderSchema, req.header)); this._executionContext = { id, header: new lisk_chain_1.BlockHeader(req.header), stateStore: new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter()), contextStore: new Map(), moduleStore: new lisk_chain_1.StateStore(this._moduleDB), }; return { contextID: id, }; } async initGenesisState(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } if (this._cachedGenesisState) { this._logger.debug({ stateRoot: this._cachedGenesisState.stateRoot }, 'Responding with cached genesis state'); return this._cachedGenesisState; } const genesisBlock = (0, genesis_block_1.readGenesisBlock)(this._config, this._logger); const context = new state_machine_1.GenesisBlockContext({ eventQueue: new state_machine_1.EventQueue(genesisBlock.header.height), header: genesisBlock.header, logger: this._logger, stateStore: this._executionContext.stateStore, assets: genesisBlock.assets, chainID: this.chainID, }); await this._stateMachine.executeGenesisBlock(context); return { events: context.eventQueue.getEvents().map(e => e.toObject()), certificateThreshold: context.nextValidators.certificateThreshold, nextValidators: context.nextValidators.validators, preCommitThreshold: context.nextValidators.precommitThreshold, }; } async insertAssets(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const context = new generator_context_1.GenerationContext({ header: this._executionContext.header, logger: this._logger, stateStore: this._executionContext.stateStore, contextStore: this._executionContext.contextStore, chainID: this.chainID, generatorStore: this._executionContext.moduleStore, finalizedHeight: req.finalizedHeight, }); await this._stateMachine.insertAssets(context); const batch = new lisk_db_1.Batch(); this._executionContext.moduleStore.finalize(batch); await this._moduleDB.write(batch); return { assets: context.assets.getAll(), }; } async verifyAssets(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const context = new state_machine_1.BlockContext({ header: this._executionContext.header, logger: this._logger, stateStore: this._executionContext.stateStore, contextStore: this._executionContext.contextStore, chainID: this.chainID, assets: new lisk_chain_1.BlockAssets(req.assets), eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height), transactions: [], }); await this._stateMachine.verifyAssets(context); return {}; } async beforeTransactionsExecute(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const context = new state_machine_1.BlockContext({ header: this._executionContext.header, logger: this._logger, stateStore: this._executionContext.stateStore, contextStore: this._executionContext.contextStore, chainID: this.chainID, assets: new lisk_chain_1.BlockAssets(req.assets), eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height), transactions: [], }); await this._stateMachine.beforeTransactionsExecute(context); return { events: context.eventQueue.getEvents().map(e => e.toObject()), }; } async afterTransactionsExecute(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const context = new state_machine_1.BlockContext({ header: this._executionContext.header, logger: this._logger, stateStore: this._executionContext.stateStore, contextStore: this._executionContext.contextStore, chainID: this.chainID, assets: new lisk_chain_1.BlockAssets(req.assets), eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height), transactions: req.transactions.map(tx => new lisk_chain_1.Transaction(tx)), }); await this._stateMachine.afterExecuteBlock(context); return { certificateThreshold: context.nextValidators.certificateThreshold, nextValidators: context.nextValidators.validators, preCommitThreshold: context.nextValidators.precommitThreshold, events: context.eventQueue.getEvents().map(e => e.toObject()), }; } async verifyTransaction(req) { var _a, _b, _c, _d; let stateStore; let contextStore; let header; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter()); contextStore = new Map(); header = new lisk_chain_1.BlockHeader(req.header); } else { stateStore = this._executionContext.stateStore; contextStore = this._executionContext.contextStore; header = this._executionContext.header; } const context = new state_machine_1.TransactionContext({ eventQueue: new state_machine_1.EventQueue(0), logger: this._logger, transaction: new lisk_chain_1.Transaction(req.transaction), stateStore, contextStore, chainID: this.chainID, header, }); const result = await this._stateMachine.verifyTransaction(context, req.onlyCommand); if (!((_b = this._executionContext) === null || _b === void 0 ? void 0 : _b.id.equals(req.contextID))) { stateStore.inner.close(); } return { result: result.status, errorMessage: (_d = (_c = result.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : '', }; } async executeTransaction(req) { var _a; let stateStore; let contextStore; let header; if (!req.dryRun) { if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } stateStore = this._executionContext.stateStore; contextStore = this._executionContext.contextStore; header = this._executionContext.header; } else { stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter()); contextStore = new Map(); header = new lisk_chain_1.BlockHeader(req.header); } const context = new state_machine_1.TransactionContext({ eventQueue: new state_machine_1.EventQueue(header.height), logger: this._logger, transaction: new lisk_chain_1.Transaction(req.transaction), stateStore, contextStore, chainID: this.chainID, assets: new lisk_chain_1.BlockAssets(req.assets), header, }); const status = await this._stateMachine.executeTransaction(context); if (req.dryRun) { stateStore.inner.close(); } return { events: context.eventQueue.getEvents().map(e => e.toObject()), result: status, }; } async commit(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const currentState = await this._stateDB.getCurrentState(); if (req.expectedStateRoot.equals(currentState.root)) { this._logger.debug({ stateRoot: currentState.root }, 'Skipping commit. Current state is already expected state'); this._executionContext.stateStore.inner.close(); return { stateRoot: currentState.root }; } try { const stateRoot = await this._stateDB.commit(this._executionContext.stateStore.inner, this._executionContext.header.height, req.stateRoot, { checkRoot: req.expectedStateRoot.length > 0, readonly: req.dryRun, expectedRoot: req.expectedStateRoot, }); return { stateRoot, }; } finally { this._executionContext.stateStore.inner.close(); if (this._config.system.backup.height > 0 && this._executionContext.header.height === this._config.system.backup.height) { (0, backup_1.backupDatabase)(this._config.system.dataPath, constants_1.STATE_DB_NAME, this._stateDB).catch(err => { this._logger.error({ err: err, height: this._config.system.backup.height }, 'Failed to create backup for stateDB'); }); } } } async revert(req) { var _a; if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) { throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`); } const stateRoot = await this._stateDB.revert(req.stateRoot, this._executionContext.header.height); return { stateRoot, }; } async clear(_req) { this._executionContext = undefined; return {}; } async finalize(req) { if (req.finalizedHeight === 0) { return {}; } await this._stateDB.finalize(req.finalizedHeight); return {}; } async getMetadata(_req) { const modules = this._modules.map(mod => { const meta = mod.metadata(); return { ...meta, name: mod.name, }; }); modules.sort((a, b) => a.name.localeCompare(b.name, 'en')); const data = Buffer.from(JSON.stringify({ modules }), 'utf-8'); return { data, }; } async query(req) { const params = JSON.parse(req.params.toString('utf8')); try { const resp = await this._channel.invoke({ context: { header: req.header, }, methodName: req.method, params, }); this._logger.info({ method: req.method }, 'Called ABI query successfully'); return { data: Buffer.from(JSON.stringify(resp !== null && resp !== void 0 ? resp : '{}'), 'utf-8'), }; } catch (error) { this._logger.info({ method: req.method, err: error }, 'Failed to call ABI query'); return { data: Buffer.from(JSON.stringify({ error: { message: error.message, }, }), 'utf-8'), }; } } async prove(req) { const proof = await this._stateDB.prove(req.stateRoot, req.keys); return { proof, }; } } exports.ABIHandler = ABIHandler; //# sourceMappingURL=abi_handler.js.map