UNPKG

lisk-framework

Version:

Lisk blockchain application platform

251 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StateMachine = void 0; const lisk_validator_1 = require("@liskhq/lisk-validator"); const lisk_chain_1 = require("@liskhq/lisk-chain"); const lisk_codec_1 = require("@liskhq/lisk-codec"); const abi_1 = require("../abi"); const types_1 = require("./types"); const constants_1 = require("./constants"); class StateMachine { constructor() { this._modules = []; this._initialized = false; } registerModule(mod) { this._validateExisting(mod); this._modules.push(mod); } async init(logger, genesisConfig, moduleConfig = {}) { var _a; this._logger = logger; if (this._initialized) { return; } for (const mod of this._modules) { if (mod.init) { await mod.init({ moduleConfig: (_a = moduleConfig[mod.name]) !== null && _a !== void 0 ? _a : {}, genesisConfig, }); } this._logger.info(`Registered and initialized ${mod.name} module`); for (const command of mod.commands) { this._logger.info(`Registered ${mod.name} module has command ${command.name}`); } } this._initialized = true; } async executeGenesisBlock(ctx) { const initContext = ctx.createInitGenesisStateContext(); for (const mod of this._modules) { if (mod.initGenesisState) { this._logger.debug({ moduleName: mod.name }, 'Executing initGenesisState'); await mod.initGenesisState(initContext); this._logger.debug({ moduleName: mod.name }, 'Executed initGenesisState'); } } const finalizeContext = ctx.createFinalizeGenesisStateContext(); for (const mod of this._modules) { if (mod.finalizeGenesisState) { this._logger.debug({ moduleName: mod.name }, 'Executing finalizeGenesisState'); await mod.finalizeGenesisState(finalizeContext); this._logger.debug({ moduleName: mod.name }, 'Executed finalizeGenesisState'); } } } async insertAssets(ctx) { const initContext = ctx.getInsertAssetContext(); for (const mod of this._modules) { if (mod.insertAssets) { this._logger.debug({ moduleName: mod.name }, 'Executing insertAssets'); await mod.insertAssets(initContext); this._logger.debug({ moduleName: mod.name }, 'Executed insertAssets'); } } } async verifyTransaction(ctx, onlyCommand = false) { const transactionContext = ctx.createTransactionVerifyContext(); try { if (!onlyCommand) { for (const mod of this._modules) { if (mod.verifyTransaction) { this._logger.debug({ moduleName: mod.name }, 'Executing verifyTransaction'); const result = await mod.verifyTransaction(transactionContext); this._logger.debug({ moduleName: mod.name }, 'Executed verifyTransaction'); if (result.status !== types_1.VerifyStatus.OK) { this._logger.debug({ err: result.error, moduleName: mod.name }, 'Transaction verification failed'); return result; } } } } const command = this._getCommand(ctx.transaction.module, ctx.transaction.command); const commandContext = ctx.createCommandVerifyContext(command.schema); lisk_validator_1.validator.validate(command.schema, commandContext.params); if (command.verify) { this._logger.debug({ commandName: command.name, moduleName: ctx.transaction.module }, 'Executing command.verify'); const result = await command.verify(commandContext); this._logger.debug({ commandName: command.name, moduleName: ctx.transaction.module }, 'Executed command.verify'); if (result.status !== types_1.VerifyStatus.OK) { this._logger.debug({ err: result.error, moduleName: ctx.transaction.module, commandName: command.name }, 'Command verification failed'); return result; } } return { status: types_1.VerifyStatus.OK }; } catch (error) { this._logger.debug({ err: error, commandName: ctx.transaction.command, moduleName: ctx.transaction.module, }, 'Transaction verification failed'); return { status: types_1.VerifyStatus.FAIL, error: error }; } } async executeTransaction(ctx) { let status = abi_1.TransactionExecutionResult.OK; const transactionContext = ctx.createTransactionExecuteContext(); const eventQueueSnapshotID = ctx.eventQueue.createSnapshot(); const stateStoreSnapshotID = ctx.stateStore.createSnapshot(); for (const mod of this._modules) { if (mod.beforeCommandExecute) { try { this._logger.debug({ moduleName: mod.name }, 'Executing beforeCommandExecute'); await mod.beforeCommandExecute(transactionContext); this._logger.debug({ moduleName: mod.name }, 'Executed beforeCommandExecute'); } catch (error) { ctx.eventQueue.restoreSnapshot(eventQueueSnapshotID); ctx.stateStore.restoreSnapshot(stateStoreSnapshotID); this._logger.debug({ err: error, moduleName: mod.name }, 'Transaction beforeCommandExecution failed'); return abi_1.TransactionExecutionResult.INVALID; } } } const command = this._getCommand(ctx.transaction.module, ctx.transaction.command); const commandEventQueueSnapshotID = ctx.eventQueue.createSnapshot(); const commandStateStoreSnapshotID = ctx.stateStore.createSnapshot(); const commandContext = ctx.createCommandExecuteContext(command.schema); try { this._logger.debug({ commandName: command.name }, 'Executing command.execute'); await command.execute(commandContext); this._logger.debug({ commandName: command.name }, 'Executed command.execute'); } catch (error) { ctx.eventQueue.restoreSnapshot(commandEventQueueSnapshotID); ctx.stateStore.restoreSnapshot(commandStateStoreSnapshotID); status = abi_1.TransactionExecutionResult.FAIL; this._logger.debug({ err: error, moduleName: ctx.transaction.module, commandName: ctx.transaction.command, }, 'Command execution failed'); } for (const mod of this._modules) { if (mod.afterCommandExecute) { try { this._logger.debug({ moduleName: mod.name }, 'Executing afterCommandExecute'); await mod.afterCommandExecute(transactionContext); this._logger.debug({ moduleName: mod.name }, 'Executed afterCommandExecute'); } catch (error) { ctx.eventQueue.restoreSnapshot(eventQueueSnapshotID); ctx.stateStore.restoreSnapshot(stateStoreSnapshotID); this._logger.debug({ err: error, moduleName: mod.name }, 'Transaction afterCommandExecution failed'); return abi_1.TransactionExecutionResult.INVALID; } } } ctx.eventQueue.unsafeAdd(ctx.transaction.module, constants_1.EVENT_TRANSACTION_NAME, lisk_codec_1.codec.encode(lisk_chain_1.standardEventDataSchema, { success: status === abi_1.TransactionExecutionResult.OK }), [Buffer.concat([constants_1.EVENT_TOPIC_TRANSACTION_EXECUTION, ctx.transaction.id])]); return status; } async verifyAssets(ctx) { for (const asset of ctx.assets.getAll()) { if (this._findModule(asset.module) === undefined) { throw new Error(`Module ${asset.module} is not registered.`); } } const blockVerifyContext = ctx.getBlockVerifyExecuteContext(); for (const mod of this._modules) { if (mod.verifyAssets) { this._logger.debug({ moduleName: mod.name }, 'Executing verifyAssets'); await mod.verifyAssets(blockVerifyContext); this._logger.debug({ moduleName: mod.name }, 'Executed verifyAssets'); } } } async beforeTransactionsExecute(ctx) { const blockExecuteContext = ctx.getBlockExecuteContext(); for (const mod of this._modules) { if (mod.beforeTransactionsExecute) { this._logger.debug({ moduleName: mod.name }, 'Executing beforeTransactionsExecute'); await mod.beforeTransactionsExecute(blockExecuteContext); this._logger.debug({ moduleName: mod.name }, 'Executed beforeTransactionsExecute'); } } } async afterExecuteBlock(ctx) { const blockExecuteContext = ctx.getBlockAfterExecuteContext(); for (const mod of this._modules) { if (mod.afterTransactionsExecute) { this._logger.debug({ moduleName: mod.name }, 'Executing afterTransactionsExecute'); await mod.afterTransactionsExecute(blockExecuteContext); this._logger.debug({ moduleName: mod.name }, 'Executed afterTransactionsExecute'); } } } _findModule(name) { const existingModule = this._modules.find(m => m.name === name); if (existingModule) { return existingModule; } return undefined; } _getCommand(module, command) { const targetModule = this._findModule(module); if (!targetModule) { this._logger.debug(`Module ${module} is not registered`); throw new Error(`Module ${module} is not registered.`); } const targetCommand = targetModule.commands.find(c => c.name === command); if (!targetCommand) { this._logger.debug(`Module ${module} does not have command ${command} registered`); throw new Error(`Module ${module} does not have command ${command} registered.`); } return targetCommand; } _validateExisting(mod) { const existingModule = this._modules.find(m => m.name === mod.name); if (existingModule) { this._logger.debug(`Module ${mod.name} is registered`); throw new Error(`Module ${mod.name} is registered.`); } const allExistingEvents = this._modules.reduce((prev, curr) => { prev.push(...curr.events.keys()); return prev; }, []); for (const event of mod.events.values()) { const duplicate = allExistingEvents.find(k => k.equals(event.key)); if (duplicate) { this._logger.debug(`Module ${mod.name} has conflicting event ${event.name}`); throw new Error(`Module ${mod.name} has conflicting event ${event.name}. Please update the event name.`); } allExistingEvents.push(event.key); } const allExistingStores = this._modules.reduce((prev, curr) => { prev.push(...curr.stores.keys()); return prev; }, []); for (const store of mod.stores.values()) { const duplicate = allExistingStores.find(k => k.equals(store.key)); if (duplicate) { this._logger.debug(`Module ${mod.name} has conflicting store ${store.name}`); throw new Error(`Module ${mod.name} has conflicting store ${store.name}. Please update the store name.`); } allExistingStores.push(store.key); } } } exports.StateMachine = StateMachine; //# sourceMappingURL=state_machine.js.map