From 7d8301a4d092b855948dd9c6ea6509fc2b09bed5 Mon Sep 17 00:00:00 2001 From: sitetester Date: Thu, 23 Nov 2023 12:34:53 +0200 Subject: [PATCH] Update tests for interop cross chain updates (#9138) * Update tests for interop cross chain updates * expect `verifyRoutingRules` to return error in all cases * Move repeated declarations to parent scope * Use ccmContext instead * Remove `// ***` * Remove `describe('isMainchain'` to have all checks inside a single `describe('verifyRoutingRules'` * use chainID other than mainchainID * `Sending and receiving chains must differ.` condition should run for both (mainchain & sidechain) cases * Address redundant assignments * Removing expectation, since following expects will fail, if there is no error throw from verifyRoutingRules --------- Co-authored-by: !shan --- .../base_cross_chain_update_command.ts | 5 +- .../submit_mainchain_cross_chain_update.ts | 2 +- .../submit_sidechain_cross_chain_update.ts | 3 +- .../base_cross_chain_update_command.spec.ts | 321 +++++++++--------- ...ubmit_mainchain_cross_chain_update.spec.ts | 66 +++- ...ubmit_sidechain_cross_chain_update.spec.ts | 51 ++- 6 files changed, 279 insertions(+), 169 deletions(-) diff --git a/framework/src/modules/interoperability/base_cross_chain_update_command.ts b/framework/src/modules/interoperability/base_cross_chain_update_command.ts index 0b9059efec..7036f0132e 100644 --- a/framework/src/modules/interoperability/base_cross_chain_update_command.ts +++ b/framework/src/modules/interoperability/base_cross_chain_update_command.ts @@ -123,6 +123,7 @@ export abstract class BaseCrossChainUpdateCommand< } } + // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0053.md#beforecrosschainmessagesexecution protected async beforeCrossChainMessagesExecution( context: CommandExecuteContext, isMainchain: boolean, @@ -195,6 +196,7 @@ export abstract class BaseCrossChainUpdateCommand< return [ccms, true]; } + // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0053.md#verifyroutingrules protected verifyRoutingRules( ccm: CCMsg, ccuParams: CrossChainUpdateTransactionParams, @@ -219,7 +221,8 @@ export abstract class BaseCrossChainUpdateCommand< } } - protected async afterCrossChainMessagesExecute( + // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0053.md#aftercrosschainmessagesexecution + protected async afterCrossChainMessagesExecution( context: CommandExecuteContext, ) { const { params } = context; diff --git a/framework/src/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.ts b/framework/src/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.ts index 7819b98c3d..6e87e1c0ac 100644 --- a/framework/src/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.ts +++ b/framework/src/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.ts @@ -128,7 +128,7 @@ export class SubmitMainchainCrossChainUpdateCommand extends BaseCrossChainUpdate context.contextStore.delete(CONTEXT_STORE_KEY_CCM_PROCESSING); } - await this.afterCrossChainMessagesExecute(context); + await this.afterCrossChainMessagesExecution(context); } private async _beforeCrossChainMessageForwarding( diff --git a/framework/src/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.ts b/framework/src/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.ts index 0c6b77b800..031c7af5cb 100644 --- a/framework/src/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.ts +++ b/framework/src/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.ts @@ -26,7 +26,6 @@ import { getIDFromCCMBytes } from '../../utils'; import { SidechainInteroperabilityInternalMethod } from '../internal_method'; // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0053.md#sidechaincrosschainupdate - export class SubmitSidechainCrossChainUpdateCommand extends BaseCrossChainUpdateCommand { public async verify( context: CommandVerifyContext, @@ -81,6 +80,6 @@ export class SubmitSidechainCrossChainUpdateCommand extends BaseCrossChainUpdate context.contextStore.delete(CONTEXT_STORE_KEY_CCM_PROCESSING); } - await this.afterCrossChainMessagesExecute(context); + await this.afterCrossChainMessagesExecution(context); } } diff --git a/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts b/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts index 51d4919429..414178d343 100644 --- a/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts +++ b/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts @@ -74,6 +74,7 @@ import { } from '../../../../src/modules/interoperability/stores/own_chain_account'; import { createStoreGetter } from '../../../../src/testing/utils'; import { EVENT_TOPIC_TRANSACTION_EXECUTION } from '../../../../src/state_machine/constants'; +import { TransactionContext } from '../../../../src/state_machine'; class CrossChainUpdateCommand extends BaseCrossChainUpdateCommand { // eslint-disable-next-line @typescript-eslint/require-await @@ -83,6 +84,8 @@ class CrossChainUpdateCommand extends BaseCrossChainUpdateCommand { + let executeContext: CommandExecuteContext; + let stateStore: PrefixedStateReadWriter; const interopsModule = new MainchainInteroperabilityModule(); const senderPublicKey = utils.getRandomBytes(32); const messageFeeTokenID = Buffer.alloc(8, 0); @@ -205,6 +208,7 @@ describe('BaseCrossChainUpdateCommand', () => { }; let context: CrossChainMessageContext; + let defaultTransactionContext: TransactionContext; let command: CrossChainUpdateCommand; let ccMethods: Map; let ccCommands: Map; @@ -212,6 +216,7 @@ describe('BaseCrossChainUpdateCommand', () => { beforeEach(() => { const interopModule = new MainchainInteroperabilityModule(); + stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); ccMethods = new Map(); ccMethods.set( 'token', @@ -265,10 +270,19 @@ describe('BaseCrossChainUpdateCommand', () => { context = createCrossChainMessageContext({ ccm: defaultCCM, }); + + defaultTransactionContext = createTransactionContext({ + chainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: command.name, + params: codec.encode(crossChainUpdateTransactionParams, params), + }), + }); }); describe('verifyCommon', () => { - let stateStore: PrefixedStateReadWriter; let verifyContext: CommandVerifyContext; const ownChainAccount: OwnChainAccount = { @@ -296,16 +310,7 @@ describe('BaseCrossChainUpdateCommand', () => { ].sort((v1, v2) => v2.blsKey.compare(v1.blsKey)); // unsorted list beforeEach(async () => { - stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); - verifyContext = createTransactionContext({ - chainID, - stateStore, - transaction: new Transaction({ - ...defaultTransaction, - command: command.name, - params: codec.encode(crossChainUpdateTransactionParams, params), - }), - }).createCommandVerifyContext(command.schema); + verifyContext = defaultTransactionContext.createCommandVerifyContext(command.schema); await command['stores'] .get(ChainValidatorsStore) @@ -530,12 +535,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); describe('verifyCertificateSignatureAndPartnerChainOutboxRoot', () => { - let executeContext: CommandExecuteContext; - let stateStore: PrefixedStateReadWriter; - beforeEach(async () => { - stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); - executeContext = createTransactionContext({ chainID, stateStore, @@ -635,29 +635,18 @@ describe('BaseCrossChainUpdateCommand', () => { // otherwise, they can fail due to some other check // also, we can simplify test cases by giving only one CCM to params.inboxUpdate.crossChainMessages array describe('beforeCrossChainMessagesExecution', () => { - let executeContext: CommandExecuteContext; - let stateStore: PrefixedStateReadWriter; - beforeEach(async () => { - stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); - - executeContext = createTransactionContext({ - chainID, - stateStore, - transaction: new Transaction({ - ...defaultTransaction, - command: command.name, - params: codec.encode(crossChainUpdateTransactionParams, params), - }), - }).createCommandExecuteContext(command.schema); jest.spyOn(interopsModule.events.get(CcmProcessedEvent), 'log'); + await interopsModule.stores .get(ChainAccountStore) .set(stateStore, defaultSendingChainID, partnerChainAccount); + await interopsModule.stores.get(ChainValidatorsStore).set(stateStore, defaultSendingChainID, { activeValidators, certificateThreshold: params.certificateThreshold, }); + await interopsModule.stores .get(ChannelDataStore) .set(stateStore, defaultSendingChainID, partnerChannel); @@ -694,10 +683,12 @@ describe('BaseCrossChainUpdateCommand', () => { await expect( command['beforeCrossChainMessagesExecution'](executeContext, true), ).resolves.toEqual([[], false]); + expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( expect.anything(), params.sendingChainID, ); + const invalidCCMID = getIDFromCCMBytes(codec.encode(ccmSchema, invalidCCM)); const ccmEventQueue = executeContext.eventQueue.getChildQueue( Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, invalidCCMID]), @@ -738,10 +729,12 @@ describe('BaseCrossChainUpdateCommand', () => { await expect( command['beforeCrossChainMessagesExecution'](executeContext, true), ).resolves.toEqual([[], false]); + expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( expect.anything(), params.sendingChainID, ); + const ccmID = getIDFromCCMBytes(codec.encode(ccmSchema, EmptyCCM)); const ccmEventQueue = executeContext.eventQueue.getChildQueue( Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, ccmID]), @@ -758,7 +751,12 @@ describe('BaseCrossChainUpdateCommand', () => { ); }); - it('should terminate the chain and add an event when CCM sending chain and ccu sending chain is not the same', async () => { + it('should terminate the chain and log an event when verifyRoutingRules throws error', async () => { + // we want to return error in all cases + jest.spyOn(command, 'verifyRoutingRules' as any).mockImplementation(() => { + throw new Error('blah'); + }); + const ccm = { crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, fee: BigInt(0), @@ -792,17 +790,30 @@ describe('BaseCrossChainUpdateCommand', () => { await expect( command['beforeCrossChainMessagesExecution'](executeContext, true), ).resolves.toEqual([[], false]); - expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( - expect.anything(), - params.sendingChainID, + + expect(command['verifyRoutingRules']).toHaveBeenCalledWith( + ccm, + executeContext.params, + executeContext.chainID, + true, ); - const ccmID = getIDFromCCMBytes(codec.encode(ccmSchema, ccm)); - const ccmEventQueue = executeContext.eventQueue.getChildQueue( - Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, ccmID]), + const ccmBytes = executeContext.params.inboxUpdate.crossChainMessages[0]; + const ccmID = getIDFromCCMBytes(ccmBytes); + const ccmContext = { + ...executeContext, + eventQueue: executeContext.eventQueue.getChildQueue( + Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, ccmID]), + ), + }; + + expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( + ccmContext, + executeContext.params.sendingChainID, ); + expect(command['events'].get(CcmProcessedEvent).log).toHaveBeenCalledWith( - { ...executeContext, eventQueue: ccmEventQueue }, + ccmContext, executeContext.params.sendingChainID, ccm.receivingChainID, { @@ -811,11 +822,32 @@ describe('BaseCrossChainUpdateCommand', () => { code: CCMProcessedCode.INVALID_CCM_ROUTING_EXCEPTION, }, ); + + expect(executeContext.eventQueue['_defaultTopics'][0]).toEqual( + Buffer.concat([EVENT_TOPIC_TRANSACTION_EXECUTION, executeContext.transaction.id]), + ); + }); + }); + + describe('verifyRoutingRules', () => { + beforeEach(async () => { + jest.spyOn(interopsModule.events.get(CcmProcessedEvent), 'log'); + + await interopsModule.stores + .get(ChainAccountStore) + .set(stateStore, defaultSendingChainID, partnerChainAccount); + + await interopsModule.stores.get(ChainValidatorsStore).set(stateStore, defaultSendingChainID, { + activeValidators, + certificateThreshold: params.certificateThreshold, + }); + + await interopsModule.stores + .get(ChannelDataStore) + .set(stateStore, defaultSendingChainID, partnerChannel); }); - // Sending and receiving chains must differ. - it('should terminate the chain and add an event when receiving chain is the same as sending chain', async () => { - const sendingChainID = chainID; + it('should return error when CCM sending chain and ccu sending chain is not the same', () => { const ccm = { crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, fee: BigInt(0), @@ -824,13 +856,14 @@ describe('BaseCrossChainUpdateCommand', () => { params: utils.getRandomBytes(10), // must be same as `context.chainID` to pass `!context.chainID.equals(ccm.receivingChainID)` check receivingChainID: chainID, - // will fail for `Sending and receiving chains must differ` - sendingChainID: chainID, + // this will fail for `!ccm.sendingChainID.equals(params.sendingChainID)` + // params.sendingChainID is `defaultSendingChainID` (line 158) + sendingChainID: Buffer.from([1, 2, 3, 4]), status: CCMStatusCode.OK, }; executeContext = createTransactionContext({ - chainID: sendingChainID, + chainID, stateStore, transaction: new Transaction({ ...defaultTransaction, @@ -841,46 +874,30 @@ describe('BaseCrossChainUpdateCommand', () => { ...params.inboxUpdate, crossChainMessages: [codec.encode(ccmSchema, ccm)], }, - // this is needed to pass `!ccm.sendingChainID.equals(params.sendingChainID)` check (previous test) - sendingChainID, }), }), }).createCommandExecuteContext(command.schema); - await expect( - command['beforeCrossChainMessagesExecution'](executeContext, true), - ).resolves.toEqual([[], false]); - expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( - expect.anything(), - sendingChainID, - ); - const ccmID = getIDFromCCMBytes(codec.encode(ccmSchema, ccm)); - const ccmEventQueue = executeContext.eventQueue.getChildQueue( - Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, ccmID]), - ); - expect(command['events'].get(CcmProcessedEvent).log).toHaveBeenCalledWith( - { ...executeContext, eventQueue: ccmEventQueue }, - executeContext.params.sendingChainID, - ccm.receivingChainID, - { - ccm, - result: CCMProcessedResult.DISCARDED, - code: CCMProcessedCode.INVALID_CCM_ROUTING_EXCEPTION, - }, - ); + try { + command['verifyRoutingRules'](ccm, executeContext.params, executeContext.chainID, true); + } catch (err: any) { + expect((err as Error).message).toBe('CCM is not from the sending chain.'); + } }); - it('should terminate the chain and add an event when CCM is not directed to the sidechain', async () => { + it('should return error when ccm status is CCMStatusCode.CHANNEL_UNAVAILABLE', () => { const ccm = { crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, fee: BigInt(0), module: MODULE_NAME_INTEROPERABILITY, nonce: BigInt(1), params: utils.getRandomBytes(10), - // will fail for `!context.chainID.equals(ccm.receivingChainID)` - receivingChainID: Buffer.from([0, 0, 3, 0]), + // must be same as `context.chainID` to pass `!context.chainID.equals(ccm.receivingChainID)` + receivingChainID: chainID, + // must be same as defaultSendingChainID to pass `!ccm.sendingChainID.equals(params.sendingChainID)` sendingChainID: defaultSendingChainID, - status: CCMStatusCode.OK, + // will fail for `CCMStatusCode.CHANNEL_UNAVAILABLE` + status: CCMStatusCode.CHANNEL_UNAVAILABLE, }; executeContext = createTransactionContext({ @@ -899,46 +916,31 @@ describe('BaseCrossChainUpdateCommand', () => { }), }).createCommandExecuteContext(command.schema); - await expect( - command['beforeCrossChainMessagesExecution'](executeContext, false), - ).resolves.toEqual([[], false]); - expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( - expect.anything(), - params.sendingChainID, - ); - const ccmID = getIDFromCCMBytes(codec.encode(ccmSchema, ccm)); - const ccmEventQueue = executeContext.eventQueue.getChildQueue( - Buffer.concat([EVENT_TOPIC_CCM_EXECUTION, ccmID]), - ); - expect(command['events'].get(CcmProcessedEvent).log).toHaveBeenCalledWith( - { ...executeContext, eventQueue: ccmEventQueue }, - executeContext.params.sendingChainID, - ccm.receivingChainID, - { - ccm, - result: CCMProcessedResult.DISCARDED, - code: CCMProcessedCode.INVALID_CCM_ROUTING_EXCEPTION, - }, - ); + try { + command['verifyRoutingRules'](ccm, executeContext.params, executeContext.chainID, true); + } catch (err: any) { + expect((err as Error).message).toBe( + 'CCM status channel unavailable can only be set on the mainchain.', + ); + } }); - it('should reject with terminate the chain and add an event when ccm status is CCMStatusCode.CHANNEL_UNAVAILABLE and mainchain is true', async () => { + it('should return error when CCM is not directed to the sidechain', () => { + const sidechainID = Buffer.from([1, 2, 3, 4]); const ccm = { crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, fee: BigInt(0), module: MODULE_NAME_INTEROPERABILITY, nonce: BigInt(1), params: utils.getRandomBytes(10), - // must be same as `context.chainID` to pass `!context.chainID.equals(ccm.receivingChainID)` - receivingChainID: chainID, - // must be same as defaultSendingChainID to pass `!ccm.sendingChainID.equals(params.sendingChainID)` + // will fail for `!context.chainID.equals(ccm.receivingChainID)` + receivingChainID: Buffer.from([0, 0, 3, 0]), sendingChainID: defaultSendingChainID, - // will fail for `CCMStatusCode.CHANNEL_UNAVAILABLE` - status: CCMStatusCode.CHANNEL_UNAVAILABLE, + status: CCMStatusCode.OK, }; executeContext = createTransactionContext({ - chainID, + chainID: sidechainID, stateStore, transaction: new Transaction({ ...defaultTransaction, @@ -953,67 +955,66 @@ describe('BaseCrossChainUpdateCommand', () => { }), }).createCommandExecuteContext(command.schema); - await expect( - command['beforeCrossChainMessagesExecution'](executeContext, true), - ).resolves.toEqual([[], false]); - expect(internalMethod.terminateChainInternal).toHaveBeenCalledWith( - expect.anything(), - params.sendingChainID, - ); - expect(command['events'].get(CcmProcessedEvent).log).toHaveBeenCalledWith( - expect.anything(), - executeContext.params.sendingChainID, - ccm.receivingChainID, - { - ccm, - result: CCMProcessedResult.DISCARDED, - code: CCMProcessedCode.INVALID_CCM_ROUTING_EXCEPTION, - }, - ); - expect(executeContext.eventQueue['_defaultTopics'][0]).toEqual( - Buffer.concat([EVENT_TOPIC_TRANSACTION_EXECUTION, executeContext.transaction.id]), - ); + try { + command['verifyRoutingRules'](ccm, executeContext.params, executeContext.chainID, false); + } catch (err: any) { + expect((err as Error).message).toBe('CCM is not directed to the sidechain.'); + } }); - it('should resolve when ccm status is CCMStatusCode.CHANNEL_UNAVAILABLE and mainchain is false', async () => { - executeContext = createTransactionContext({ - chainID: Buffer.from([0, 0, 2, 0]), - stateStore, - transaction: new Transaction({ - ...defaultTransaction, - command: command.name, - params: codec.encode(crossChainUpdateTransactionParams, { - ...params, - inboxUpdate: { - ...params.inboxUpdate, - crossChainMessages: [ - codec.encode(ccmSchema, { - crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, - fee: BigInt(0), - module: MODULE_NAME_INTEROPERABILITY, - nonce: BigInt(1), - params: utils.getRandomBytes(10), - receivingChainID: Buffer.from([0, 0, 2, 0]), - sendingChainID: Buffer.from([0, 0, 0, 4]), - status: CCMStatusCode.CHANNEL_UNAVAILABLE, - }), - ], - }, + it.each([ + ['true', 1], + ['false', 0], + ])( + 'should return error when receiving chain is the same as sending chain && isMainchain = %s', + (_s, isMainchain) => { + const sendingChainID = chainID; + const ccm = { + crossChainCommand: CROSS_CHAIN_COMMAND_REGISTRATION, + fee: BigInt(0), + module: MODULE_NAME_INTEROPERABILITY, + nonce: BigInt(1), + params: utils.getRandomBytes(10), + // must be same as `context.chainID` to pass `!context.chainID.equals(ccm.receivingChainID)` check + receivingChainID: chainID, + // will fail for `Sending and receiving chains must differ` + sendingChainID: chainID, + status: CCMStatusCode.OK, + }; + + executeContext = createTransactionContext({ + chainID: sendingChainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: command.name, + params: codec.encode(crossChainUpdateTransactionParams, { + ...params, + inboxUpdate: { + ...params.inboxUpdate, + crossChainMessages: [codec.encode(ccmSchema, ccm)], + }, + // this is needed to pass `!ccm.sendingChainID.equals(params.sendingChainID)` check (previous test) + sendingChainID, + }), }), - }), - }).createCommandExecuteContext(command.schema); - - await expect( - command['beforeCrossChainMessagesExecution'](executeContext, false), - ).resolves.toEqual([expect.toBeArrayOfSize(1), true]); - - expect(internalMethod.terminateChainInternal).not.toHaveBeenCalled(); - expect(command['events'].get(CcmProcessedEvent).log).not.toHaveBeenCalled(); - }); + }).createCommandExecuteContext(command.schema); + + try { + command['verifyRoutingRules']( + ccm, + executeContext.params, + executeContext.chainID, + Boolean(isMainchain), + ); + } catch (err: any) { + expect((err as Error).message).toBe('Sending and receiving chains must differ.'); + } + }, + ); }); - describe('afterCrossChainMessagesExecute', () => { - let executeContext: CommandExecuteContext; + describe('afterCrossChainMessagesExecution', () => { let chainValidatorsStore: ChainValidatorsStore; beforeEach(() => { @@ -1057,7 +1058,7 @@ describe('BaseCrossChainUpdateCommand', () => { } as any); await expect( - command['afterCrossChainMessagesExecute'](executeContext), + command['afterCrossChainMessagesExecution'](executeContext), ).resolves.toBeUndefined(); expect(command['internalMethod'].updateValidators).toHaveBeenCalledWith( expect.anything(), @@ -1068,7 +1069,7 @@ describe('BaseCrossChainUpdateCommand', () => { it('should update validators if activeValidatorsUpdate is empty but params.certificateThreshold !== sendingChainValidators.certificateThreshold', async () => { executeContext.params.activeValidatorsUpdate.bftWeightsUpdateBitmap = EMPTY_BUFFER; await expect( - command['afterCrossChainMessagesExecute'](executeContext), + command['afterCrossChainMessagesExecution'](executeContext), ).resolves.toBeUndefined(); expect(command['internalMethod'].updateValidators).toHaveBeenCalledWith( @@ -1080,7 +1081,7 @@ describe('BaseCrossChainUpdateCommand', () => { it('should not update certificate and updatePartnerChainOutboxRoot if certificate is empty', async () => { executeContext.params.certificate = EMPTY_BYTES; await expect( - command['afterCrossChainMessagesExecute'](executeContext), + command['afterCrossChainMessagesExecution'](executeContext), ).resolves.toBeUndefined(); expect(command['internalMethod'].updateCertificate).not.toHaveBeenCalled(); expect(command['internalMethod'].updatePartnerChainOutboxRoot).not.toHaveBeenCalled(); @@ -1096,7 +1097,7 @@ describe('BaseCrossChainUpdateCommand', () => { }, }; await expect( - command['afterCrossChainMessagesExecute'](executeContext), + command['afterCrossChainMessagesExecution'](executeContext), ).resolves.toBeUndefined(); expect(command['internalMethod'].updatePartnerChainOutboxRoot).not.toHaveBeenCalled(); @@ -1586,10 +1587,8 @@ describe('BaseCrossChainUpdateCommand', () => { const ccmStatus = CCMStatusCode.MODULE_NOT_SUPPORTED; const ccmProcessedEventCode = CCMProcessedCode.MODULE_NOT_SUPPORTED; const ccmSize = 100; - let stateStore: PrefixedStateReadWriter; beforeEach(async () => { - stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); await interopsModule.stores.get(OwnChainAccountStore).set(stateStore, EMPTY_BYTES, { chainID: Buffer.from('11111111', 'hex'), name: 'ownChain', diff --git a/framework/test/unit/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.spec.ts b/framework/test/unit/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.spec.ts index 84c3aec1b5..4b8cc0e0bc 100644 --- a/framework/test/unit/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.spec.ts +++ b/framework/test/unit/modules/interoperability/mainchain/commands/submit_mainchain_cross_chain_update.spec.ts @@ -421,7 +421,7 @@ describe('SubmitMainchainCrossChainUpdateCommand', () => { status: VerifyStatus.OK, }); - expect(mainchainCCUUpdateCommand['verifyCommon']).toHaveBeenCalled(); + expect(mainchainCCUUpdateCommand['verifyCommon']).toHaveBeenCalledWith(verifyContext, true); }); it('should call isLive with 3 params', async () => { @@ -490,6 +490,25 @@ describe('SubmitMainchainCrossChainUpdateCommand', () => { expect.toBeObject() as Certificate, ); }); + + it('should return status OK for valid context', async () => { + await expect( + mainchainCCUUpdateCommand.verify({ + ...verifyContext, + params: { + ...params, + inboxUpdate: { + crossChainMessages: [utils.getRandomBytes(100)], + messageWitnessHashes: [utils.getRandomBytes(32)], + outboxRootWitness: { + bitmap: utils.getRandomBytes(2), + siblingHashes: [utils.getRandomBytes(32)], + }, + }, + }, + }), + ).resolves.toEqual({ status: VerifyStatus.OK }); + }); }); describe('execute', () => { @@ -512,6 +531,31 @@ describe('SubmitMainchainCrossChainUpdateCommand', () => { .mockResolvedValue(undefined as never); }); + // verifyCertificateSignatureAndPartnerChainOutboxRoot relevant checks are covered in base_cross_chain_update_command.spec.ts + it('should call verifyCertificateSignatureAndPartnerChainOutboxRoot', async () => { + jest.spyOn( + mainchainCCUUpdateCommand, + 'verifyCertificateSignatureAndPartnerChainOutboxRoot' as never, + ); + + executeContext = createTransactionContext({ + chainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: mainchainCCUUpdateCommand.name, + params: codec.encode(crossChainUpdateTransactionParams, { + ...params, + }), + }), + }).createCommandExecuteContext(mainchainCCUUpdateCommand.schema); + + await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined(); + expect( + mainchainCCUUpdateCommand['verifyCertificateSignatureAndPartnerChainOutboxRoot'], + ).toHaveBeenCalledTimes(1); + }); + it('should call beforeCrossChainMessagesExecution', async () => { executeContext = createTransactionContext({ chainID, @@ -653,6 +697,26 @@ describe('SubmitMainchainCrossChainUpdateCommand', () => { expect(mockExit).toHaveBeenCalledWith(1); expect(mainchainCCUUpdateCommand['_forward']).toHaveBeenCalledTimes(1); }); + + it('should call afterCrossChainMessagesExecution', async () => { + jest.spyOn(mainchainCCUUpdateCommand, 'afterCrossChainMessagesExecution' as any); + executeContext = createTransactionContext({ + chainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: mainchainCCUUpdateCommand.name, + params: codec.encode(crossChainUpdateTransactionParams, { + ...params, + }), + }), + }).createCommandExecuteContext(mainchainCCUUpdateCommand.schema); + + await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined(); + expect(mainchainCCUUpdateCommand['afterCrossChainMessagesExecution']).toHaveBeenCalledTimes( + 1, + ); + }); }); describe('_forward', () => { diff --git a/framework/test/unit/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.spec.ts b/framework/test/unit/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.spec.ts index 44fecc3766..29a33db600 100644 --- a/framework/test/unit/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.spec.ts +++ b/framework/test/unit/modules/interoperability/sidechain/commands/submit_sidechain_cross_chain_update.spec.ts @@ -311,7 +311,7 @@ describe('SubmitSidechainCrossChainUpdateCommand', () => { status: VerifyStatus.OK, }); - expect(sidechainCCUUpdateCommand['verifyCommon']).toHaveBeenCalled(); + expect(sidechainCCUUpdateCommand['verifyCommon']).toHaveBeenCalledWith(verifyContext, false); }); it('should call isLive with only 2 params', async () => { @@ -355,6 +355,31 @@ describe('SubmitSidechainCrossChainUpdateCommand', () => { jest.spyOn(sidechainCCUUpdateCommand, 'apply' as never).mockResolvedValue(undefined as never); }); + // verifyCertificateSignatureAndPartnerChainOutboxRoot relevant checks are covered in base_cross_chain_update_command.spec.ts + it('should call verifyCertificateSignatureAndPartnerChainOutboxRoot', async () => { + jest.spyOn( + sidechainCCUUpdateCommand, + 'verifyCertificateSignatureAndPartnerChainOutboxRoot' as never, + ); + + executeContext = createTransactionContext({ + chainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: sidechainCCUUpdateCommand.name, + params: codec.encode(crossChainUpdateTransactionParams, { + ...params, + }), + }), + }).createCommandExecuteContext(sidechainCCUUpdateCommand.schema); + + await expect(sidechainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined(); + expect( + sidechainCCUUpdateCommand['verifyCertificateSignatureAndPartnerChainOutboxRoot'], + ).toHaveBeenCalledTimes(1); + }); + it('should call beforeCrossChainMessagesExecution', async () => { executeContext = createTransactionContext({ chainID, @@ -404,7 +429,7 @@ describe('SubmitSidechainCrossChainUpdateCommand', () => { expect(sidechainCCUUpdateCommand['apply']).toHaveBeenCalledTimes(1); }); - it('should call apply for ccm and add to the inbox where receiving chain is the main chain', async () => { + it('should call apply for ccm and add to the inbox', async () => { executeContext = createTransactionContext({ chainID, stateStore, @@ -430,7 +455,27 @@ describe('SubmitSidechainCrossChainUpdateCommand', () => { }); } expect(sidechainCCUUpdateCommand['internalMethod'].appendToInboxTree).toHaveBeenCalledTimes( - 3, + params.inboxUpdate.crossChainMessages.length, + ); + }); + + it('should call afterCrossChainMessagesExecution', async () => { + jest.spyOn(sidechainCCUUpdateCommand, 'afterCrossChainMessagesExecution' as any); + executeContext = createTransactionContext({ + chainID, + stateStore, + transaction: new Transaction({ + ...defaultTransaction, + command: sidechainCCUUpdateCommand.name, + params: codec.encode(crossChainUpdateTransactionParams, { + ...params, + }), + }), + }).createCommandExecuteContext(sidechainCCUUpdateCommand.schema); + + await expect(sidechainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined(); + expect(sidechainCCUUpdateCommand['afterCrossChainMessagesExecution']).toHaveBeenCalledTimes( + 1, ); }); });