Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7142 from LiskHQ/7023-interop-msg-rec
Implement message recovery commands - Closes #7023
- Loading branch information
Showing
12 changed files
with
1,285 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
framework/src/modules/interoperability/mainchain/commands/message_recovery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/* | ||
* Copyright © 2022 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
*/ | ||
|
||
import { codec } from '@liskhq/lisk-codec'; | ||
import { regularMerkleTree } from '@liskhq/lisk-tree'; | ||
import { hash } from '@liskhq/lisk-cryptography'; | ||
import { | ||
CCM_STATUS_RECOVERED, | ||
CHAIN_ACTIVE, | ||
COMMAND_ID_MESSAGE_RECOVERY, | ||
EMPTY_FEE_ADDRESS, | ||
} from '../../constants'; | ||
import { ccmSchema, messageRecoveryParams } from '../../schema'; | ||
import { CommandExecuteContext, VerificationResult } from '../../../../node/state_machine'; | ||
import { CCMsg, StoreCallback, MessageRecoveryParams } from '../../types'; | ||
import { BaseInteroperabilityCommand } from '../../base_interoperability_command'; | ||
import { MainchainInteroperabilityStore } from '../store'; | ||
import { getIDAsKeyForStore, swapReceivingAndSendingChainIDs } from '../../utils'; | ||
import { BaseInteroperableAPI } from '../../base_interoperable_api'; | ||
import { createCCCommandExecuteContext } from '../../context'; | ||
|
||
export class MessageRecoveryCommand extends BaseInteroperabilityCommand { | ||
public id = COMMAND_ID_MESSAGE_RECOVERY; | ||
public name = 'messageRecovery'; | ||
public schema = messageRecoveryParams; | ||
|
||
// TODO | ||
// eslint-disable-next-line @typescript-eslint/require-await | ||
public async verify(): Promise<VerificationResult> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
|
||
public async execute(context: CommandExecuteContext<MessageRecoveryParams>): Promise<void> { | ||
const { transaction, params, getAPIContext, logger, networkIdentifier, getStore } = context; | ||
const apiContext = getAPIContext(); | ||
const { eventQueue } = apiContext; | ||
|
||
const chainIdAsBuffer = getIDAsKeyForStore(params.chainID); | ||
|
||
const updatedCCMs: Buffer[] = []; | ||
const deserializedCCMs = params.crossChainMessages.map(serializedCCMsg => | ||
codec.decode<CCMsg>(ccmSchema, serializedCCMsg), | ||
); | ||
for (const ccm of deserializedCCMs) { | ||
const apisWithBeforeRecoverCCM = [...this.interoperableCCAPIs.values()].filter(api => | ||
Reflect.has(api, 'beforeRecoverCCM'), | ||
) as Pick<Required<BaseInteroperableAPI>, 'beforeRecoverCCM'>[]; | ||
for (const api of apisWithBeforeRecoverCCM) { | ||
await api.beforeRecoverCCM({ | ||
ccm, | ||
trsSender: transaction.senderAddress, | ||
eventQueue: apiContext.eventQueue, | ||
getAPIContext, | ||
logger, | ||
networkIdentifier, | ||
getStore, | ||
feeAddress: EMPTY_FEE_ADDRESS, | ||
}); | ||
} | ||
|
||
const recoveryCCM: CCMsg = { | ||
...ccm, | ||
fee: BigInt(0), | ||
status: CCM_STATUS_RECOVERED, | ||
}; | ||
const encodedUpdatedCCM = codec.encode(ccmSchema, recoveryCCM); | ||
updatedCCMs.push(encodedUpdatedCCM); | ||
} | ||
|
||
const interoperabilityStore = this.getInteroperabilityStore(getStore); | ||
|
||
const doesTerminatedOutboxAccountExist = await interoperabilityStore.terminatedOutboxAccountExist( | ||
chainIdAsBuffer, | ||
); | ||
|
||
if (!doesTerminatedOutboxAccountExist) { | ||
throw new Error('Terminated outbox account does not exist.'); | ||
} | ||
|
||
const terminatedChainOutboxAccount = await interoperabilityStore.getTerminatedOutboxAccount( | ||
chainIdAsBuffer, | ||
); | ||
const terminatedChainOutboxSize = terminatedChainOutboxAccount.outboxSize; | ||
|
||
const proof = { | ||
size: terminatedChainOutboxSize, | ||
indexes: params.idxs, | ||
siblingHashes: params.siblingHashes, | ||
}; | ||
|
||
const hashedUpdatedCCMs = updatedCCMs.map(ccm => hash(ccm)); | ||
|
||
const outboxRoot = regularMerkleTree.calculateRootFromUpdateData(hashedUpdatedCCMs, proof); | ||
|
||
await interoperabilityStore.setTerminatedOutboxAccount(chainIdAsBuffer, { | ||
outboxRoot, | ||
}); | ||
|
||
const ownChainAccount = await interoperabilityStore.getOwnChainAccount(); | ||
for (const ccm of deserializedCCMs) { | ||
const newCcm = swapReceivingAndSendingChainIDs(ccm); | ||
|
||
if (ownChainAccount.id === ccm.receivingChainID) { | ||
const ccCommands = this.ccCommands.get(newCcm.moduleID); | ||
|
||
if (!ccCommands) { | ||
continue; | ||
} | ||
|
||
const ccCommand = ccCommands.find(command => command.ID === newCcm.crossChainCommandID); | ||
|
||
if (!ccCommand) { | ||
continue; | ||
} | ||
|
||
const ccCommandExecuteContext = createCCCommandExecuteContext({ | ||
ccm: newCcm, | ||
eventQueue, | ||
feeAddress: EMPTY_FEE_ADDRESS, | ||
getAPIContext, | ||
getStore, | ||
logger, | ||
networkIdentifier, | ||
}); | ||
|
||
await ccCommand.execute(ccCommandExecuteContext); | ||
continue; | ||
} | ||
|
||
const ccmChainIdAsBuffer = getIDAsKeyForStore(newCcm.receivingChainID); | ||
const chainAccountExist = await interoperabilityStore.chainAccountExist(ccmChainIdAsBuffer); | ||
const isLive = await interoperabilityStore.isLive(ccmChainIdAsBuffer, Date.now()); | ||
|
||
if (!chainAccountExist || !isLive) { | ||
continue; | ||
} | ||
|
||
const chainAccount = await interoperabilityStore.getChainAccount(ccmChainIdAsBuffer); | ||
|
||
if (chainAccount.status !== CHAIN_ACTIVE) { | ||
continue; | ||
} | ||
|
||
await interoperabilityStore.addToOutbox(ccmChainIdAsBuffer, newCcm); | ||
} | ||
} | ||
|
||
protected getInteroperabilityStore(getStore: StoreCallback): MainchainInteroperabilityStore { | ||
return new MainchainInteroperabilityStore(this.moduleID, getStore, this.interoperableCCAPIs); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.