UNPKG

lisk-framework

Version:

Lisk blockchain application platform

382 lines 17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RecoverMessageCommand = void 0; const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_tree_1 = require("@liskhq/lisk-tree"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_chain_1 = require("@liskhq/lisk-chain"); const types_1 = require("../../../../state_machine/types"); const base_interoperability_command_1 = require("../../base_interoperability_command"); const utils_1 = require("../../utils"); const constants_1 = require("../../constants"); const schemas_1 = require("../../schemas"); const terminated_outbox_1 = require("../../stores/terminated_outbox"); const ccm_processed_1 = require("../../events/ccm_processed"); const invalid_rmt_verification_1 = require("../../events/invalid_rmt_verification"); class RecoverMessageCommand extends base_interoperability_command_1.BaseInteroperabilityCommand { constructor() { super(...arguments); this.schema = schemas_1.messageRecoveryParamsSchema; } async verify(context) { const { params: { chainID, crossChainMessages, idxs }, } = context; let terminatedOutboxAccount; try { terminatedOutboxAccount = await this.stores.get(terminated_outbox_1.TerminatedOutboxStore).get(context, chainID); } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Terminated outbox account does not exist.'), }; } throw error; } if (!crossChainMessages.length) { return { status: types_1.VerifyStatus.FAIL, error: new Error('No cross-chain messages to recover.'), }; } if (idxs.length !== crossChainMessages.length) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Inclusion proof indices and cross-chain messages do not have the same length.'), }; } for (let i = 0; i < idxs.length - 1; i += 1) { if (idxs[i] > idxs[i + 1]) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message indexes are not strictly increasing.'), }; } } if (idxs[0] <= 1) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message does not have a valid index.'), }; } const firstPosition = parseInt(idxs[0].toString(2).slice(1), 2); if (firstPosition < terminatedOutboxAccount.partnerChainInboxSize) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message is not pending.'), }; } const lastPosition = parseInt(idxs[idxs.length - 1].toString(2).slice(1), 2); if (terminatedOutboxAccount.outboxSize <= lastPosition) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message was never in the outbox.'), }; } for (const crossChainMessage of crossChainMessages) { const ccm = lisk_codec_1.codec.decode(schemas_1.ccmSchema, crossChainMessage); (0, utils_1.validateFormat)(ccm); if (ccm.status !== 0) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message status is not valid.'), }; } if (!ccm.receivingChainID.equals(chainID)) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message receiving chain ID is not valid.'), }; } const isLive = await this.internalMethod.isLive(context, ccm.sendingChainID, context.header.timestamp); if (!isLive) { return { status: types_1.VerifyStatus.FAIL, error: new Error('Cross-chain message sending chain is not live.'), }; } } return { status: types_1.VerifyStatus.OK, }; } async execute(context) { const { params } = context; const terminatedOutboxSubstore = this.stores.get(terminated_outbox_1.TerminatedOutboxStore); const terminatedOutboxAccount = await terminatedOutboxSubstore.get(context, context.params.chainID); const proof = { size: terminatedOutboxAccount.outboxSize, idxs: params.idxs, siblingHashes: params.siblingHashes, }; const isVerified = lisk_tree_1.regularMerkleTree.verifyDataBlock(params.crossChainMessages, proof, terminatedOutboxAccount.outboxRoot); if (!isVerified) { this.events.get(invalid_rmt_verification_1.InvalidRMTVerificationEvent).error(context); throw new Error('Message recovery proof of inclusion is not valid.'); } context.contextStore.set(constants_1.CONTEXT_STORE_KEY_CCM_PROCESSING, true); const recoveredCCMs = []; for (const crossChainMessage of params.crossChainMessages) { const { decodedCCM: ccm, ccmID } = (0, utils_1.getDecodedCCMAndID)(crossChainMessage); const ctx = { ...context, ccm, eventQueue: context.eventQueue.getChildQueue(Buffer.concat([constants_1.EVENT_TOPIC_CCM_EXECUTION, ccmID])), }; let recoveredCCM; if (ccm.sendingChainID.equals((0, utils_1.getMainchainID)(context.chainID))) { recoveredCCM = await this._applyRecovery(ctx); } else { recoveredCCM = await this._forwardRecovery(ctx); } recoveredCCMs.push(lisk_codec_1.codec.encode(schemas_1.ccmSchema, recoveredCCM)); } context.contextStore.set(constants_1.CONTEXT_STORE_KEY_CCM_PROCESSING, false); terminatedOutboxAccount.outboxRoot = lisk_tree_1.regularMerkleTree.calculateRootFromUpdateData(recoveredCCMs.map(ccm => lisk_cryptography_1.utils.hash(ccm)), { ...proof, indexes: proof.idxs }); await terminatedOutboxSubstore.set(context, context.params.chainID, terminatedOutboxAccount); } async _applyRecovery(context) { const { logger } = context; const { ccmID } = (0, utils_1.getEncodedCCMAndID)(context.ccm); const recoveredCCM = { ...context.ccm, status: 5, sendingChainID: context.ccm.receivingChainID, receivingChainID: context.ccm.sendingChainID, }; let ccmFailed = false; let ccmResult = 0; let ccmCode = 0; try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.verifyCrossChainMessage) { logger.debug({ moduleName: module, commandName: recoveredCCM.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute verifyCrossChainMessage'); await method.verifyCrossChainMessage(context); } } } catch (error) { logger.debug({ err: error, moduleName: recoveredCCM.module, commandName: recoveredCCM.crossChainCommand, }, 'Fail to verify cross chain message.'); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 8, result: 3, ccm: recoveredCCM, }); return recoveredCCM; } const commands = this.ccCommands.get(recoveredCCM.module); if (!commands) { ccmFailed = true; ccmResult = 3; ccmCode = 2; } const command = commands === null || commands === void 0 ? void 0 : commands.find(com => com.name === recoveredCCM.crossChainCommand); if (!ccmFailed && !command) { ccmFailed = true; ccmResult = 3; ccmCode = 3; } if (command === null || command === void 0 ? void 0 : command.verify) { try { await command.verify(context); } catch (error) { logger.debug({ err: error, moduleName: recoveredCCM.module, commandName: recoveredCCM.crossChainCommand, }, 'Fail to verify cross chain command.'); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 9, result: 3, ccm: recoveredCCM, }); return recoveredCCM; } } const baseEventSnapshotID = context.eventQueue.createSnapshot(); const baseStateSnapshotID = context.stateStore.createSnapshot(); try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.beforeCrossChainCommandExecute) { logger.debug({ moduleName: module, commandName: recoveredCCM.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute beforeCrossChainCommandExecute'); await method.beforeCrossChainCommandExecute(context); } } } catch (error) { context.eventQueue.restoreSnapshot(baseEventSnapshotID); context.stateStore.restoreSnapshot(baseStateSnapshotID); logger.debug({ err: error, moduleName: recoveredCCM.module, commandName: recoveredCCM.crossChainCommand, }, 'Fail to execute beforeCrossChainCommandExecute.'); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 10, result: 3, ccm: recoveredCCM, }); return recoveredCCM; } if (!ccmFailed) { const execEventSnapshotID = context.eventQueue.createSnapshot(); const execStateSnapshotID = context.stateStore.createSnapshot(); try { const params = (command === null || command === void 0 ? void 0 : command.schema) ? lisk_codec_1.codec.decode(command === null || command === void 0 ? void 0 : command.schema, recoveredCCM.params) : {}; await (command === null || command === void 0 ? void 0 : command.execute({ ...context, params })); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 0, result: 0, ccm: recoveredCCM, }); } catch (error) { context.eventQueue.restoreSnapshot(execEventSnapshotID); context.stateStore.restoreSnapshot(execStateSnapshotID); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 4, result: 3, ccm: recoveredCCM, }); } } try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.afterCrossChainCommandExecute) { logger.debug({ moduleName: module, commandName: recoveredCCM.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute afterCrossChainCommandExecute'); await method.afterCrossChainCommandExecute(context); } } } catch (error) { context.eventQueue.restoreSnapshot(baseEventSnapshotID); context.stateStore.restoreSnapshot(baseStateSnapshotID); logger.debug({ err: error, moduleName: module, commandName: recoveredCCM.crossChainCommand }, 'Fail to execute afterCrossChainCommandExecute'); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 11, result: 3, ccm: recoveredCCM, }); return recoveredCCM; } if (ccmFailed) { this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: ccmCode, result: ccmResult, ccm: recoveredCCM, }); } return recoveredCCM; } async _forwardRecovery(context) { const { logger } = context; const { ccmID } = (0, utils_1.getEncodedCCMAndID)(context.ccm); const recoveredCCM = { ...context.ccm, status: 5, sendingChainID: context.ccm.receivingChainID, receivingChainID: context.ccm.sendingChainID, }; try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.verifyCrossChainMessage) { logger.debug({ moduleName: module, commandName: recoveredCCM.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute verifyCrossChainMessage'); await method.verifyCrossChainMessage(context); } } } catch (error) { this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 8, result: 3, ccm: recoveredCCM, }); logger.debug({ err: error, moduleName: recoveredCCM.module, commandName: recoveredCCM.crossChainCommand, }, 'Fail to execute verifyCrossChainMessage.'); return recoveredCCM; } const baseEventSnapshotID = context.eventQueue.createSnapshot(); const baseStateSnapshotID = context.stateStore.createSnapshot(); try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.beforeCrossChainMessageForwarding) { logger.debug({ moduleName: module, commandName: recoveredCCM.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute beforeCrossChainMessageForwarding'); await method.beforeCrossChainMessageForwarding({ ...context, ccmFailed: false }); } } } catch (error) { context.eventQueue.restoreSnapshot(baseEventSnapshotID); context.stateStore.restoreSnapshot(baseStateSnapshotID); logger.debug({ err: error, moduleName: recoveredCCM.module, commandName: recoveredCCM.crossChainCommand, }, 'Fail to execute beforeCrossChainMessageForwarding.'); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 12, result: 3, ccm: recoveredCCM, }); return recoveredCCM; } await this.internalMethod.addToOutbox(context, recoveredCCM.receivingChainID, recoveredCCM); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(context, recoveredCCM.sendingChainID, recoveredCCM.receivingChainID, { code: 0, result: 1, ccm: recoveredCCM, }); return recoveredCCM; } } exports.RecoverMessageCommand = RecoverMessageCommand; //# sourceMappingURL=recover_message.js.map