UNPKG

lisk-framework

Version:

Lisk blockchain application platform

393 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseCrossChainUpdateCommand = void 0; const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_validator_1 = require("@liskhq/lisk-validator"); const base_interoperability_command_1 = require("./base_interoperability_command"); const constants_1 = require("./constants"); const ccm_processed_1 = require("./events/ccm_processed"); const ccm_send_success_1 = require("./events/ccm_send_success"); const schemas_1 = require("./schemas"); const chain_account_1 = require("./stores/chain_account"); const utils_1 = require("./utils"); const chain_validators_1 = require("./stores/chain_validators"); const own_chain_account_1 = require("./stores/own_chain_account"); class BaseCrossChainUpdateCommand extends base_interoperability_command_1.BaseInteroperabilityCommand { constructor() { super(...arguments); this.schema = schemas_1.crossChainUpdateTransactionParams; } init(interopsMethod, tokenMethod) { this._tokenMethod = tokenMethod; this._interopsMethod = interopsMethod; } async verifyCommon(context, isMainchain) { const { params } = context; lisk_validator_1.validator.validate(schemas_1.crossChainUpdateTransactionParams, params); const ownChainAccount = await this.stores.get(own_chain_account_1.OwnChainAccountStore).get(context, constants_1.EMPTY_BYTES); if (params.sendingChainID.equals(ownChainAccount.chainID)) { throw new Error('The sending chain cannot be the same as the receiving chain.'); } if (params.certificate.length === 0 && (0, utils_1.isInboxUpdateEmpty)(params.inboxUpdate)) { throw new Error('A cross-chain update must contain a non-empty certificate and/or a non-empty inbox update.'); } const sendingChainAccount = await this.stores .get(chain_account_1.ChainAccountStore) .getOrUndefined(context, params.sendingChainID); if (!sendingChainAccount) { throw new Error('The sending chain is not registered.'); } let live; if (isMainchain) { live = await this.internalMethod.isLive(context, params.sendingChainID, context.header.timestamp); } else { live = await this.internalMethod.isLive(context, params.sendingChainID); } if (!live) { throw new Error('The sending chain is not live.'); } if (sendingChainAccount.status === 0 && params.certificate.length === 0) { throw new Error(`Cross-chain updates from chains with status ${0} must contain a non-empty certificate.`); } if (params.certificate.length > 0) { await this.internalMethod.verifyCertificate(context, params, context.header.timestamp); } const sendingChainValidators = await this.stores .get(chain_validators_1.ChainValidatorsStore) .get(context, params.sendingChainID); if (!(0, utils_1.emptyActiveValidatorsUpdate)(params.activeValidatorsUpdate) || params.certificateThreshold !== sendingChainValidators.certificateThreshold) { await this.internalMethod.verifyValidatorsUpdate(context, params); } if (!(0, utils_1.isInboxUpdateEmpty)(params.inboxUpdate)) { this.internalMethod.verifyOutboxRootWitness(context, params); } } async beforeCrossChainMessagesExecution(context, isMainchain) { const { params } = context; const { inboxUpdate } = params; const ccms = []; let ccm; for (const ccmBytes of inboxUpdate.crossChainMessages) { const ccmID = (0, utils_1.getIDFromCCMBytes)(ccmBytes); const ccmContext = { ...context, eventQueue: context.eventQueue.getChildQueue(Buffer.concat([constants_1.EVENT_TOPIC_CCM_EXECUTION, ccmID])), }; try { ccm = lisk_codec_1.codec.decode(schemas_1.ccmSchema, ccmBytes); } catch (error) { await this.internalMethod.terminateChainInternal(ccmContext, params.sendingChainID); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(ccmContext, params.sendingChainID, ccmContext.chainID, { ccm: constants_1.EmptyCCM, result: 3, code: 5, }); return [[], false]; } try { (0, utils_1.validateFormat)(ccm); } catch (error) { await this.internalMethod.terminateChainInternal(ccmContext, params.sendingChainID); ccm = { ...ccm, params: constants_1.EMPTY_BYTES }; this.events .get(ccm_processed_1.CcmProcessedEvent) .log(ccmContext, params.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 6, }); return [[], false]; } try { this.verifyRoutingRules(ccm, params, ccmContext.chainID, isMainchain); ccms.push(ccm); } catch (error) { await this.internalMethod.terminateChainInternal(ccmContext, params.sendingChainID); this.events .get(ccm_processed_1.CcmProcessedEvent) .log(ccmContext, params.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 7, }); return [[], false]; } } return [ccms, true]; } verifyRoutingRules(ccm, ccuParams, ownChainID, isMainchain) { if (isMainchain) { if (!ccm.sendingChainID.equals(ccuParams.sendingChainID)) { throw new Error('CCM is not from the sending chain.'); } if (ccm.status === 1) { throw new Error('CCM status channel unavailable can only be set on the mainchain.'); } } else if (!ownChainID.equals(ccm.receivingChainID)) { throw new Error('CCM is not directed to the sidechain.'); } if (ccm.receivingChainID.equals(ccm.sendingChainID)) { throw new Error('Sending and receiving chains must differ.'); } } async afterCrossChainMessagesExecute(context) { const { params } = context; const sendingChainValidators = await this.stores .get(chain_validators_1.ChainValidatorsStore) .get(context, params.sendingChainID); if (!(0, utils_1.emptyActiveValidatorsUpdate)(params.activeValidatorsUpdate) || params.certificateThreshold !== sendingChainValidators.certificateThreshold) { await this.internalMethod.updateValidators(context, params); } if (params.certificate.length > 0) { await this.internalMethod.updateCertificate(context, params); } if (!(0, utils_1.isInboxUpdateEmpty)(params.inboxUpdate) && params.certificate.length > 0) { await this.internalMethod.updatePartnerChainOutboxRoot(context, params); } } async _beforeCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID) { const { ccm, logger } = context; const { ccmID } = (0, utils_1.getEncodedCCMAndID)(ccm); try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.beforeCrossChainCommandExecute) { logger.debug({ moduleName: module, commandName: ccm.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute beforeCrossChainCommandExecute'); await method.beforeCrossChainCommandExecute(context); } } } catch (error) { context.eventQueue.restoreSnapshot(baseEventSnapshotID); context.stateStore.restoreSnapshot(baseStateSnapshotID); logger.info({ err: error, moduleName: ccm.module, commandName: ccm.crossChainCommand }, 'Fail to execute beforeCrossChainCommandExecute.'); await this.internalMethod.terminateChainInternal(context, ccm.sendingChainID); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 10, }); return false; } return true; } async _afterCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID) { const { ccm, logger } = context; const { ccmID } = (0, utils_1.getEncodedCCMAndID)(ccm); try { for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.afterCrossChainCommandExecute) { logger.debug({ moduleName: module, commandName: ccm.crossChainCommand, ccmID: ccmID.toString('hex'), }, 'Execute afterCrossChainCommandExecute'); await method.afterCrossChainCommandExecute(context); } } } catch (error) { context.eventQueue.restoreSnapshot(baseEventSnapshotID); context.stateStore.restoreSnapshot(baseStateSnapshotID); logger.info({ err: error, moduleName: ccm.module, commandName: ccm.crossChainCommand }, 'Fail to execute afterCrossChainCommandExecute.'); await this.internalMethod.terminateChainInternal(context, ccm.sendingChainID); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 11, }); return false; } return true; } async apply(context) { const { ccm, logger } = context; const { ccmID, encodedCCM } = (0, utils_1.getEncodedCCMAndID)(ccm); const valid = await this.verifyCCM(context, ccmID); if (!valid) { return; } const baseEventSnapshotID = context.eventQueue.createSnapshot(); const baseStateSnapshotID = context.stateStore.createSnapshot(); let statusCode; let processedCode; let crossChainCommand; const crossChainCommands = this.ccCommands.get(ccm.module); if (!crossChainCommands) { statusCode = 2; processedCode = 2; } else { crossChainCommand = crossChainCommands.find(com => com.name === ccm.crossChainCommand); if (!crossChainCommand) { statusCode = 3; processedCode = 3; } } if (!crossChainCommands || !crossChainCommand) { if (!(await this._beforeCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID))) { return; } if (!(await this._afterCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID))) { return; } await this.bounce(context, encodedCCM.length, statusCode, processedCode); return; } let decodedParams; try { decodedParams = lisk_codec_1.codec.decode(crossChainCommand.schema, context.ccm.params); lisk_validator_1.validator.validate(crossChainCommand.schema, decodedParams); } catch (error) { logger.info({ err: error, moduleName: ccm.module, commandName: ccm.crossChainCommand }, 'Invalid CCM params.'); await this.internalMethod.terminateChainInternal(context, ccm.sendingChainID); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 8, }); return; } if (crossChainCommand.verify) { try { await crossChainCommand.verify(context); } catch (error) { logger.info({ err: error, moduleName: ccm.module, commandName: ccm.crossChainCommand }, 'Fail to verify cross chain command.'); await this.internalMethod.terminateChainInternal(context, ccm.sendingChainID); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm, result: 3, code: 8, }); return; } } if (!(await this._beforeCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID))) { return; } const execEventSnapshotID = context.eventQueue.createSnapshot(); const execStateSnapshotID = context.stateStore.createSnapshot(); try { const sendingChainExists = await this.stores .get(chain_account_1.ChainAccountStore) .has(context, ccm.sendingChainID); const ccuParams = lisk_codec_1.codec.decode(schemas_1.crossChainUpdateTransactionParams, context.transaction.params); if (sendingChainExists && !ccuParams.sendingChainID.equals(ccm.sendingChainID)) { throw new Error('Cannot receive forwarded messages for a direct channel.'); } await crossChainCommand.execute({ ...context, params: decodedParams }); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm, result: 0, code: 0, }); } catch (error) { context.eventQueue.restoreSnapshot(execEventSnapshotID); context.stateStore.restoreSnapshot(execStateSnapshotID); if (!(await this._afterCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID))) { return; } await this.bounce(context, encodedCCM.length, 4, 4); return; } await this._afterCrossChainCommandExecute(context, baseEventSnapshotID, baseStateSnapshotID); } async bounce(context, ccmSize, ccmStatusCode, ccmProcessedCode) { const { ccm } = context; const minReturnFeePerByte = await this._interopsMethod.getMinReturnFeePerByte(context, ccm.sendingChainID); const minFee = minReturnFeePerByte * BigInt(ccmSize); if (ccm.status !== 0 || ccm.fee < minFee) { this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { code: ccmProcessedCode, result: 3, ccm, }); return; } this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { code: ccmProcessedCode, result: 2, ccm, }); const bouncedCCM = { ...ccm, status: ccmStatusCode, sendingChainID: ccm.receivingChainID, receivingChainID: ccm.sendingChainID, fee: BigInt(0), }; let partnerChainID; const mainchainID = (0, utils_1.getMainchainID)(bouncedCCM.receivingChainID); const ownChainAccount = await this.stores.get(own_chain_account_1.OwnChainAccountStore).get(context, constants_1.EMPTY_BYTES); if (ownChainAccount.chainID.equals(mainchainID)) { partnerChainID = bouncedCCM.receivingChainID; } else { const receivingChainExists = await this.stores .get(chain_account_1.ChainAccountStore) .has(context, bouncedCCM.receivingChainID); partnerChainID = !receivingChainExists ? mainchainID : bouncedCCM.receivingChainID; } await this.internalMethod.addToOutbox(context, partnerChainID, bouncedCCM); const newCcmID = lisk_cryptography_1.utils.hash(lisk_codec_1.codec.encode(schemas_1.ccmSchema, bouncedCCM)); this.events .get(ccm_send_success_1.CcmSendSuccessEvent) .log(context, bouncedCCM.sendingChainID, bouncedCCM.receivingChainID, newCcmID, { ccm: bouncedCCM, }); } async verifyCCM(context, ccmID) { const { ccm, logger } = context; try { const isLive = await this.internalMethod.isLive(context, ccm.sendingChainID); if (!isLive) { throw new Error(`Sending chain ${ccm.sendingChainID.toString('hex')} is not live.`); } for (const [module, method] of this.interoperableCCMethods.entries()) { if (method.verifyCrossChainMessage) { logger.debug({ module, ccmID: ccmID.toString('hex') }, 'verifying cross chain message'); await method.verifyCrossChainMessage(context); } } return true; } catch (error) { logger.info({ err: error, moduleName: ccm.module, commandName: ccm.crossChainCommand }, 'Fail to verify cross chain message.'); await this.internalMethod.terminateChainInternal(context, ccm.sendingChainID); this.events.get(ccm_processed_1.CcmProcessedEvent).log(context, ccm.sendingChainID, ccm.receivingChainID, { code: 8, result: 3, ccm, }); return false; } } async verifyCertificateSignatureAndPartnerChainOutboxRoot(context) { const { params, transaction } = context; const { inboxUpdate } = params; await this.internalMethod.verifyCertificateSignature(context, params); if (!(0, utils_1.isInboxUpdateEmpty)(inboxUpdate)) { await this.internalMethod.verifyPartnerChainOutboxRoot(context, params); const messageFeeTokenID = await this._interopsMethod.getMessageFeeTokenID(context, params.sendingChainID); await this._tokenMethod.initializeUserAccount(context, transaction.senderAddress, messageFeeTokenID); } } } exports.BaseCrossChainUpdateCommand = BaseCrossChainUpdateCommand; //# sourceMappingURL=base_cross_chain_update_command.js.map