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