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

Commit

Permalink
🐛 Add liveness check and fix test descriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
shuse2 committed Nov 17, 2022
1 parent bb1fcad commit 2dbbafc
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import { codec } from '@liskhq/lisk-codec';
import { utils } from '@liskhq/lisk-cryptography';
import { validator } from '@liskhq/lisk-validator';
import { certificateSchema } from '../../../../engine/consensus/certificate_generation/schema';
import { Certificate } from '../../../../engine/consensus/certificate_generation/types';
import {
CommandExecuteContext,
CommandVerifyContext,
Expand All @@ -27,6 +29,7 @@ import {
CCMStatusCode,
CROSS_CHAIN_COMMAND_NAME_SIDECHAIN_TERMINATED,
EMPTY_FEE_ADDRESS,
LIVENESS_LIMIT,
MODULE_NAME_INTEROPERABILITY,
} from '../../constants';
import {
Expand Down Expand Up @@ -56,14 +59,23 @@ export class MainchainCCUpdateCommand extends BaseCrossChainUpdateCommand<Mainch
context.params,
);

const isLive = await this.internalMethod.isLive(context, params.sendingChainID, context.header.timestamp);
const isLive = await this.internalMethod.isLive(
context,
params.sendingChainID,
context.header.timestamp,
);
if (!isLive) {
throw new Error('The sending chain is not live.');
}

const sendingChainAccount = await this.stores
.get(ChainAccountStore)
.get(context, params.sendingChainID);

if (sendingChainAccount.status === ChainStatus.REGISTERED) {
this._verifyLivenessConditionForRegisteredChains(context);
}

if (sendingChainAccount.status === ChainStatus.REGISTERED && params.certificate.length === 0) {
throw new Error('The first CCU must contain a non-empty certificate.');
}
Expand Down Expand Up @@ -283,4 +295,20 @@ export class MainchainCCUpdateCommand extends BaseCrossChainUpdateCommand<Mainch
result: CCMProcessedResult.FORWARDED,
});
}

private _verifyLivenessConditionForRegisteredChains(
context: CommandVerifyContext<CrossChainUpdateTransactionParams>,
): void {
if (context.params.certificate.length === 0 || isInboxUpdateEmpty(context.params.inboxUpdate)) {
return;
}
const certificate = codec.decode<Certificate>(certificateSchema, context.params.certificate);
if (context.header.timestamp - certificate.timestamp > LIVENESS_LIMIT / 2) {
throw new Error(
`The first CCU with a non-empty inbox update cannot contain a certificate older than ${
LIVENESS_LIMIT / 2
} seconds.`,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ import {
CcmProcessedEvent,
CCMProcessedResult,
} from '../../../../../../src/modules/interoperability/events/ccm_processed';
import { MainchainInteroperabilityInternalMethod } from '../../../../../../src/modules/interoperability/mainchain/internal_method';

jest.mock('@liskhq/lisk-cryptography', () => ({
...jest.requireActual('@liskhq/lisk-cryptography'),
}));

describe('CrossChainUpdateCommand', () => {
const interopMod = new MainchainInteroperabilityModule();
Expand Down Expand Up @@ -281,20 +276,15 @@ describe('CrossChainUpdateCommand', () => {
params: codec.encode(crossChainUpdateTransactionParams, params),
}),
}).createCommandVerifyContext(mainchainCCUUpdateCommand.schema);
jest.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'isLive').mockResolvedValue(true);
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'isLive')
.mockResolvedValue(true);
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'isLive')
.mockResolvedValue(true);
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'verifyCertificate')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'verifyCertificate')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'verifyValidatorsUpdate')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'verifyValidatorsUpdate')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'verifyPartnerChainOutboxRoot')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'verifyPartnerChainOutboxRoot')
.mockResolvedValue();
});

Expand All @@ -308,14 +298,35 @@ describe('CrossChainUpdateCommand', () => {
});

it('should return error when sending chain not live', async () => {
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'isLive')
.mockResolvedValue(false);
jest.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'isLive').mockResolvedValue(false);
await expect(mainchainCCUUpdateCommand.verify(verifyContext)).rejects.toThrow(
'The sending chain is not live',
);
});

it('should reject when first CCU contains a certificate older than LIVENESS_LIMIT / 2', async () => {
await interopMod.stores.get(ChainAccountStore).set(stateStore, params.sendingChainID, {
...partnerChainAccount,
status: ChainStatus.REGISTERED,
});

await expect(
mainchainCCUUpdateCommand.verify({
...verifyContext,
header: { timestamp: Math.floor(Date.now() / 1000), height: 0 },
params: {
...params,
certificate: codec.encode(certificateSchema, {
...defaultCertificateValues,
timestamp: 0,
}),
},
}),
).rejects.toThrow(
'The first CCU with a non-empty inbox update cannot contain a certificate older',
);
});

it('should reject when sending chain status is registered but certificate is empty', async () => {
await interopMod.stores.get(ChainAccountStore).set(stateStore, params.sendingChainID, {
...partnerChainAccount,
Expand Down Expand Up @@ -347,7 +358,7 @@ describe('CrossChainUpdateCommand', () => {
).resolves.toEqual({ status: VerifyStatus.OK });

expect(
MainchainInteroperabilityInternalMethod.prototype.verifyValidatorsUpdate,
mainchainCCUUpdateCommand['internalMethod'].verifyValidatorsUpdate,
).toHaveBeenCalledTimes(1);
});

Expand All @@ -363,7 +374,7 @@ describe('CrossChainUpdateCommand', () => {
).resolves.toEqual({ status: VerifyStatus.OK });

expect(
MainchainInteroperabilityInternalMethod.prototype.verifyValidatorsUpdate,
mainchainCCUUpdateCommand['internalMethod'].verifyValidatorsUpdate,
).toHaveBeenCalledTimes(1);
});

Expand All @@ -386,7 +397,7 @@ describe('CrossChainUpdateCommand', () => {
).resolves.toEqual({ status: VerifyStatus.OK });

expect(
MainchainInteroperabilityInternalMethod.prototype.verifyPartnerChainOutboxRoot,
mainchainCCUUpdateCommand['internalMethod'].verifyPartnerChainOutboxRoot,
).toHaveBeenCalledTimes(1);
});
});
Expand All @@ -404,19 +415,19 @@ describe('CrossChainUpdateCommand', () => {
}).createCommandExecuteContext(mainchainCCUUpdateCommand.schema);
jest.spyOn(interopMod.events.get(CcmProcessedEvent), 'log');
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'verifyCertificateSignature')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'verifyCertificateSignature')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'terminateChainInternal')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'terminateChainInternal')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'updateValidators')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'updateValidators')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'updateCertificate')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'updateCertificate')
.mockResolvedValue();
jest
.spyOn(MainchainInteroperabilityInternalMethod.prototype, 'appendToInboxTree')
.spyOn(mainchainCCUUpdateCommand['internalMethod'], 'appendToInboxTree')
.mockResolvedValue();
jest.spyOn(mainchainCCUUpdateCommand, 'apply' as never).mockResolvedValue(undefined as never);
jest
Expand All @@ -439,7 +450,7 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.verifyCertificateSignature,
mainchainCCUUpdateCommand['internalMethod'].verifyCertificateSignature,
).toHaveBeenCalledTimes(1);
});

Expand Down Expand Up @@ -499,7 +510,7 @@ describe('CrossChainUpdateCommand', () => {
).not.toHaveBeenCalled();
});

it('should reject terminate the chain and add an event when ccm format is invalid', async () => {
it('should reject and terminate the chain and add an event when ccm format is invalid', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand Down Expand Up @@ -530,8 +541,8 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.terminateChainInternal,
).toHaveBeenCalledWith(params.sendingChainID, expect.anything());
mainchainCCUUpdateCommand['internalMethod'].terminateChainInternal,
).toHaveBeenCalledWith(expect.anything(), params.sendingChainID);
expect(interopMod.events.get(CcmProcessedEvent).log).toHaveBeenCalledWith(
expect.anything(),
params.sendingChainID,
Expand All @@ -544,7 +555,7 @@ describe('CrossChainUpdateCommand', () => {
);
});

it('should reject terminate the chain and add an event when CCM sending chain and ccu sending chain is not the same', async () => {
it('should reject and terminate the chain and add an event when CCM sending chain and ccu sending chain is not the same', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand Down Expand Up @@ -575,8 +586,8 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.terminateChainInternal,
).toHaveBeenCalledWith(params.sendingChainID, expect.anything());
mainchainCCUUpdateCommand['internalMethod'].terminateChainInternal,
).toHaveBeenCalledWith(expect.anything(), params.sendingChainID);
expect(interopMod.events.get(CcmProcessedEvent).log).toHaveBeenCalledWith(
expect.anything(),
params.sendingChainID,
Expand All @@ -589,7 +600,7 @@ describe('CrossChainUpdateCommand', () => {
);
});

it('should reject terminate the chain and add an event when receiving chain is the same as sending chain', async () => {
it('should reject and terminate the chain and add an event when receiving chain is the same as sending chain', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand Down Expand Up @@ -620,8 +631,8 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.terminateChainInternal,
).toHaveBeenCalledWith(params.sendingChainID, expect.anything());
mainchainCCUUpdateCommand['internalMethod'].terminateChainInternal,
).toHaveBeenCalledWith(expect.anything(), params.sendingChainID);
expect(interopMod.events.get(CcmProcessedEvent).log).toHaveBeenCalledWith(
expect.anything(),
params.sendingChainID,
Expand All @@ -634,7 +645,7 @@ describe('CrossChainUpdateCommand', () => {
);
});

it('should reject terminate the chain and add an event when ccm status is CCMStatusCode.CHANNEL_UNAVAILABLE', async () => {
it('should reject and terminate the chain and add an event when ccm status is CCMStatusCode.CHANNEL_UNAVAILABLE', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand Down Expand Up @@ -665,8 +676,8 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.terminateChainInternal,
).toHaveBeenCalledWith(params.sendingChainID, expect.anything());
mainchainCCUUpdateCommand['internalMethod'].terminateChainInternal,
).toHaveBeenCalledWith(expect.anything(), params.sendingChainID);
expect(interopMod.events.get(CcmProcessedEvent).log).toHaveBeenCalledWith(
expect.anything(),
params.sendingChainID,
Expand Down Expand Up @@ -699,9 +710,7 @@ describe('CrossChainUpdateCommand', () => {
}).createCommandExecuteContext(mainchainCCUUpdateCommand.schema);

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.updateValidators,
).toHaveBeenCalledTimes(1);
expect(mainchainCCUUpdateCommand['internalMethod'].updateValidators).toHaveBeenCalledTimes(1);
});

it('should update validators when certificate threshold is different', async () => {
Expand All @@ -722,9 +731,7 @@ describe('CrossChainUpdateCommand', () => {
}).createCommandExecuteContext(mainchainCCUUpdateCommand.schema);

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.updateValidators,
).toHaveBeenCalledTimes(1);
expect(mainchainCCUUpdateCommand['internalMethod'].updateValidators).toHaveBeenCalledTimes(1);
});

it('should update certificate when certificate is not empty', async () => {
Expand All @@ -742,9 +749,9 @@ describe('CrossChainUpdateCommand', () => {
}).createCommandExecuteContext(mainchainCCUUpdateCommand.schema);

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.updateCertificate,
).toHaveBeenCalledTimes(1);
expect(mainchainCCUUpdateCommand['internalMethod'].updateCertificate).toHaveBeenCalledTimes(
1,
);
});

it('should not update certificate when certificate is empty', async () => {
Expand All @@ -762,12 +769,10 @@ describe('CrossChainUpdateCommand', () => {
}).createCommandExecuteContext(mainchainCCUUpdateCommand.schema);

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(
MainchainInteroperabilityInternalMethod.prototype.updateCertificate,
).not.toHaveBeenCalled();
expect(mainchainCCUUpdateCommand['internalMethod'].updateCertificate).not.toHaveBeenCalled();
});

it('should call apply for ccm and add to the inbox where receivign chain is the main chain', async () => {
it('should call apply for ccm and add to the inbox where receiving chain is the main chain', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand All @@ -782,12 +787,12 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(mainchainCCUUpdateCommand['apply']).toHaveBeenCalledTimes(1);
expect(
MainchainInteroperabilityInternalMethod.prototype.appendToInboxTree,
).toHaveBeenCalledTimes(3);
expect(mainchainCCUUpdateCommand['internalMethod'].appendToInboxTree).toHaveBeenCalledTimes(
3,
);
});

it('should call forward for ccm and add to the inbox where receivign chain is not the mainchain', async () => {
it('should call forward for ccm and add to the inbox where receiving chain is not the mainchain', async () => {
executeContext = createTransactionContext({
chainID,
stateStore,
Expand All @@ -802,9 +807,9 @@ describe('CrossChainUpdateCommand', () => {

await expect(mainchainCCUUpdateCommand.execute(executeContext)).resolves.toBeUndefined();
expect(mainchainCCUUpdateCommand['_forward']).toHaveBeenCalledTimes(2);
expect(
MainchainInteroperabilityInternalMethod.prototype.appendToInboxTree,
).toHaveBeenCalledTimes(3);
expect(mainchainCCUUpdateCommand['internalMethod'].appendToInboxTree).toHaveBeenCalledTimes(
3,
);
});
});

Expand Down

0 comments on commit 2dbbafc

Please sign in to comment.