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