Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Implement tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mehmetegemen committed May 5, 2022
1 parent e8e4f54 commit 001f95c
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ export abstract class BaseInteroperabilityStore {
return true;
}

public async terminatedOutboxAccountExist(chainID: Buffer) {
const terminatedOutboxSubstore = this.getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_TERMINATED_OUTBOX,
);

const doesOutboxExist = await terminatedOutboxSubstore.has(chainID);

return doesOutboxExist;
}

public async getTerminatedOutboxAccount(chainID: Buffer) {
const terminatedOutboxSubstore = this.getStore(
MODULE_ID_INTEROPERABILITY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ export class MessageRecoveryCommand extends BaseInteroperabilityCommand {

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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,47 @@
import { Transaction } from '@liskhq/lisk-chain';
import { codec } from '@liskhq/lisk-codec';
import { getRandomBytes } from '@liskhq/lisk-cryptography';
import { regularMerkleTree } from '@liskhq/lisk-tree';
import { when } from 'jest-when';
import { CommandExecuteContext } from '../../../../../../src';
import { BaseCCCommand } from '../../../../../../src/modules/interoperability/base_cc_command';
import { BaseInteroperableAPI } from '../../../../../../src/modules/interoperability/base_interoperable_api';
import {
CHAIN_ACTIVE,
COMMAND_ID_MESSAGE_RECOVERY,
MODULE_ID_INTEROPERABILITY,
} from '../../../../../../src/modules/interoperability/constants';
import { MessageRecoveryCommand } from '../../../../../../src/modules/interoperability/mainchain/commands/message_recovery';
import { MainchainInteroperabilityStore } from '../../../../../../src/modules/interoperability/mainchain/store';
import { MessageRecoveryParams } from '../../../../../../src/modules/interoperability/mainchain/types';
import {
ccmSchema,
messageRecoveryParams,
} from '../../../../../../src/modules/interoperability/schema';
import { CCMsg } from '../../../../../../src/modules/interoperability/types';
import { getIDAsKeyForStore } from '../../../../../../src/modules/interoperability/utils';
import { TransactionContext } from '../../../../../../src/node/state_machine';
import { createTransactionContext } from '../../../../../../src/testing';

type Mocked<Type extends Pick<Type, Methods>, Methods extends keyof Type> = Pick<
{ [Key in keyof Type]: jest.Mock<ReturnType<Type[Key]>> },
Methods
>;

describe('Mainchain MessageRecoveryCommand', () => {
type StoreMock = Mocked<
MainchainInteroperabilityStore,
| 'isLive'
| 'addToOutbox'
| 'getChainAccount'
| 'setTerminatedOutboxAccount'
| 'getTerminatedOutboxAccount'
| 'chainAccountExist'
| 'terminatedOutboxAccountExist'
>;

const networkID = getRandomBytes(32);

let messageRecoveryCommand: MessageRecoveryCommand;
let commandExecuteContext: CommandExecuteContext<MessageRecoveryParams>;
let interoperableCCAPIs: Map<number, BaseInteroperableAPI>;
Expand All @@ -41,6 +64,8 @@ describe('Mainchain MessageRecoveryCommand', () => {
let transactionParams: MessageRecoveryParams;
let encodedTransactionParams: Buffer;
let transactionContext: TransactionContext;
let storeMock: StoreMock;
let ccms: CCMsg[];

beforeEach(() => {
interoperableCCAPIs = new Map();
Expand All @@ -52,22 +77,24 @@ describe('Mainchain MessageRecoveryCommand', () => {
ccCommands,
);

const ccm1: CCMsg = {
nonce: BigInt(0),
moduleID: 1,
crossChainCommandID: 1,
sendingChainID: 2,
receivingChainID: 3,
fee: BigInt(1),
status: 1,
params: Buffer.alloc(0),
};
ccms = [
{
nonce: BigInt(0),
moduleID: 1,
crossChainCommandID: 1,
sendingChainID: 2,
receivingChainID: 3,
fee: BigInt(1),
status: 1,
params: Buffer.alloc(0),
},
];

const ccm1Encoded = codec.encode(ccmSchema, ccm1);
const ccmsEncoded = ccms.map(ccm => codec.encode(ccmSchema, ccm));

transactionParams = {
chainID: 3,
crossChainMessages: [ccm1Encoded],
crossChainMessages: [...ccmsEncoded],
idxs: [0],
siblingHashes: [getRandomBytes(32)],
};
Expand All @@ -91,6 +118,77 @@ describe('Mainchain MessageRecoveryCommand', () => {
commandExecuteContext = transactionContext.createCommandExecuteContext<MessageRecoveryParams>(
messageRecoveryParams,
);

storeMock = {
addToOutbox: jest.fn(),
getChainAccount: jest.fn(),
getTerminatedOutboxAccount: jest.fn(),
setTerminatedOutboxAccount: jest.fn(),
chainAccountExist: jest.fn().mockResolvedValue(true),
isLive: jest.fn().mockResolvedValue(true),
terminatedOutboxAccountExist: jest.fn().mockResolvedValue(true),
};

jest
.spyOn(messageRecoveryCommand, 'getInteroperabilityStore' as any)
.mockImplementation(() => storeMock);
jest.spyOn(regularMerkleTree, 'calculateRootFromUpdateData').mockReturnValue(Buffer.alloc(32));

let chainID;
for (const ccm of ccms) {
chainID = getIDAsKeyForStore(ccm.sendingChainID);

when(storeMock.getChainAccount)
.calledWith(chainID)
.mockResolvedValue({
name: `chain${chainID.toString('hex')}`,
status: CHAIN_ACTIVE,
networkID,
lastCertificate: {
height: 1,
timestamp: 10,
stateRoot: Buffer.alloc(0),
validatorsHash: Buffer.alloc(0),
},
});
}

chainID = getIDAsKeyForStore(transactionParams.chainID);

when(storeMock.getTerminatedOutboxAccount)
.calledWith(chainID)
.mockResolvedValue({
outboxRoot: getRandomBytes(32),
outboxSize: 1,
partnerChainInboxSize: 1,
});
});

it('should successfully process recovery transaction', async () => {
// Global Setup
await messageRecoveryCommand.execute(commandExecuteContext);
expect.assertions(ccms.length + 1);

{
// Assign & Arrange
const chainID = getIDAsKeyForStore(transactionParams.chainID);
const outboxRoot = Buffer.alloc(32);

// Assert
expect(storeMock.setTerminatedOutboxAccount).toHaveBeenCalledWith(
chainID,
expect.objectContaining({
outboxRoot,
}),
);
}

for (const ccm of ccms) {
// Assign
const chainID = getIDAsKeyForStore(ccm.sendingChainID);
// Assert
expect(storeMock.addToOutbox).toHaveBeenCalledWith(chainID, ccm);
}
});

it('should throw when beforeRecoverCCM of ccAPIs of the ccm fails', async () => {
Expand All @@ -112,15 +210,84 @@ describe('Mainchain MessageRecoveryCommand', () => {
});

it('should throw when there are no CCMs in the transaction params', async () => {
// Assign & Arrange
transactionParams.crossChainMessages = [];
encodedTransactionParams = codec.encode(messageRecoveryParams, transactionParams);
(transaction as any).params = encodedTransactionParams;
commandExecuteContext = transactionContext.createCommandExecuteContext<MessageRecoveryParams>(
messageRecoveryParams,
);

// Assert
await expect(messageRecoveryCommand.execute(commandExecuteContext)).rejects.toThrow(
'Transaction parameter has no CCMs',
);
});

it('should throw when terminated chain outbox does not exist', async () => {
// Assign & Arrange
const chainID = getIDAsKeyForStore(transactionParams.chainID);

when(storeMock.terminatedOutboxAccountExist).calledWith(chainID).mockResolvedValue(false);

// Assert
await expect(messageRecoveryCommand.execute(commandExecuteContext)).rejects.toThrow(
'Terminated outbox account does not exist',
);
});

it('should not add CCM to outbox when sending chain of the CCM does not exist', async () => {
// Assign & Arrange & Act
for (const ccm of ccms) {
const chainID = getIDAsKeyForStore(ccm.sendingChainID);

when(storeMock.chainAccountExist).calledWith(chainID).mockResolvedValue(false);
}

await messageRecoveryCommand.execute(commandExecuteContext);

// Assert
expect.assertions(ccms.length);
for (const _ of ccms) {
expect(storeMock.addToOutbox).not.toHaveBeenCalled();
}
});

it('should not add CCM to outbox when sending chain of the CCM is not live', async () => {
// Assign & Arrange & Act
for (const ccm of ccms) {
const chainID = getIDAsKeyForStore(ccm.sendingChainID);

when(storeMock.isLive).calledWith(chainID).mockResolvedValue(false);
}

await messageRecoveryCommand.execute(commandExecuteContext);

// Assert
expect.assertions(ccms.length);
for (const _ of ccms) {
expect(storeMock.addToOutbox).not.toHaveBeenCalled();
}
});

it('should not add CCM to outbox when sending chain of the CCM is not active', async () => {
// Assign & Arrange & Act
for (const ccm of ccms) {
const chainID = getIDAsKeyForStore(ccm.sendingChainID);

when(storeMock.getChainAccount)
.calledWith(chainID)
.mockResolvedValue({
status: -1,
} as any);
}

await messageRecoveryCommand.execute(commandExecuteContext);

// Assert
expect.assertions(ccms.length);
for (const _ of ccms) {
expect(storeMock.addToOutbox).not.toHaveBeenCalled();
}
});
});

0 comments on commit 001f95c

Please sign in to comment.