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

Update sidechain ccu command #7787

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@

import { codec } from '@liskhq/lisk-codec';
import { utils } from '@liskhq/lisk-cryptography';
import { CommandExecuteContext, CommandVerifyContext } from '../../state_machine';
import { BaseInteroperabilityCommand } from './base_interoperability_command';
import { BaseInteroperabilityInternalMethod } from './base_interoperability_internal_methods';
import { BaseInteroperabilityMethod } from './base_interoperability_method';
import { CCMStatusCode, MIN_RETURN_FEE } from './constants';
import { CCMProcessedCode, CcmProcessedEvent, CCMProcessedResult } from './events/ccm_processed';
import { CcmSendSuccessEvent } from './events/ccm_send_success';
import { ccmSchema, crossChainUpdateTransactionParams } from './schemas';
import { CrossChainMessageContext, TokenMethod } from './types';
import { ChainAccountStore } from './stores/chain_account';
import { getMainchainID } from './utils';
import {
CCMsg,
CrossChainMessageContext,
CrossChainUpdateTransactionParams,
TokenMethod,
} from './types';
import { ChainAccountStore, ChainStatus } from './stores/chain_account';
import { getMainchainID, isInboxUpdateEmpty, validateFormat } from './utils';
import { ChainValidatorsStore } from './stores/chain_validators';
import { ChannelDataStore } from './stores/channel_data';

export abstract class BaseCrossChainUpdateCommand<
T extends BaseInteroperabilityInternalMethod
Expand All @@ -38,6 +46,108 @@ export abstract class BaseCrossChainUpdateCommand<
this._interopsMethod = interopsMethod;
}

protected async verifyCommon(context: CommandVerifyContext<CrossChainUpdateTransactionParams>) {
const { params } = context;
const sendingChainAccount = await this.stores
.get(ChainAccountStore)
.get(context, params.sendingChainID);
if (sendingChainAccount.status === ChainStatus.REGISTERED && params.certificate.length === 0) {
throw new Error('The first CCU 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(ChainValidatorsStore)
.get(context, params.sendingChainID);
if (
params.activeValidatorsUpdate.length > 0 ||
params.certificateThreshold !== sendingChainValidators.certificateThreshold
) {
await this.internalMethod.verifyValidatorsUpdate(context, params);
}

if (!isInboxUpdateEmpty(params.inboxUpdate)) {
await this.internalMethod.verifyPartnerChainOutboxRoot(context, params);
}
}

protected async executeCommon(
context: CommandExecuteContext<CrossChainUpdateTransactionParams>,
isMainchain: boolean,
): Promise<[CCMsg[], boolean]> {
const { params, transaction } = context;
await this.internalMethod.verifyCertificateSignature(context, params);

if (!isInboxUpdateEmpty(params.inboxUpdate)) {
// Initialize the relayer account for the message fee token.
// This is necessary to ensure that the relayer can receive the CCM fees
// If the account already exists, nothing is done.
const messageFeeTokenID = await this._interopsMethod.getMessageFeeTokenID(
context,
params.sendingChainID,
);
// FIXME: When updating fee logic, the fix value should be removed.
await this._tokenMethod.initializeUserAccount(
context,
transaction.senderAddress,
messageFeeTokenID,
transaction.senderAddress,
BigInt(500000000),
);
}

const decodedCCMs = [];
for (const ccmBytes of params.inboxUpdate.crossChainMessages) {
try {
const ccm = codec.decode<CCMsg>(ccmSchema, ccmBytes);
validateFormat(ccm);
decodedCCMs.push(ccm);
if (!ccm.sendingChainID.equals(params.sendingChainID)) {
throw new Error('CCM is not from the sending chain.');
}
if (ccm.sendingChainID.equals(ccm.receivingChainID)) {
throw new Error('Sending and receiving chains must differ.');
}
if (isMainchain && ccm.status === CCMStatusCode.CHANNEL_UNAVAILABLE) {
throw new Error('CCM status channel unavailable can only be set on the mainchain.');
}
} catch (error) {
await this.internalMethod.terminateChainInternal(context, params.sendingChainID);
const ccmID = utils.hash(ccmBytes);
this.events.get(CcmProcessedEvent).log(context, params.sendingChainID, context.chainID, {
ccmID,
code: CCMProcessedCode.INVALID_CCM_VALIDATION_EXCEPTION,
result: CCMProcessedResult.DISCARDED,
});
return [[], false];
}
}

const sendingChainValidators = await this.stores
.get(ChainValidatorsStore)
.get(context, params.sendingChainID);
if (
params.activeValidatorsUpdate.length > 0 ||
params.certificateThreshold !== sendingChainValidators.certificateThreshold
) {
await this.internalMethod.updateValidators(context, params);
}
if (params.certificate.length > 0) {
await this.internalMethod.updateCertificate(context, params);
}
if (!isInboxUpdateEmpty(params.inboxUpdate)) {
await this.stores
.get(ChannelDataStore)
.updatePartnerChainOutboxRoot(
context,
params.sendingChainID,
params.inboxUpdate.messageWitnessHashes,
);
}
return [decodedCCMs, true];
}

protected async apply(context: CrossChainMessageContext): Promise<void> {
const { ccm, logger } = context;
const encodedCCM = codec.encode(ccmSchema, ccm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ import {
sidechainTerminatedCCMParamsSchema,
} from '../../schemas';
import { ChainAccount, ChainAccountStore, ChainStatus } from '../../stores/chain_account';
import { ChainValidatorsStore } from '../../stores/chain_validators';
import { ChannelDataStore } from '../../stores/channel_data';
import { CCMsg, CrossChainMessageContext, CrossChainUpdateTransactionParams } from '../../types';
import { getMainchainID, isInboxUpdateEmpty, validateFormat } from '../../utils';
import { CrossChainMessageContext, CrossChainUpdateTransactionParams } from '../../types';
import { getMainchainID, isInboxUpdateEmpty } from '../../utils';
import { MainchainInteroperabilityInternalMethod } from '../internal_method';

export class MainchainCCUpdateCommand extends BaseCrossChainUpdateCommand<MainchainInteroperabilityInternalMethod> {
Expand Down Expand Up @@ -76,25 +74,7 @@ export class MainchainCCUpdateCommand extends BaseCrossChainUpdateCommand<Mainch
this._verifyLivenessConditionForRegisteredChains(context);
}

if (sendingChainAccount.status === ChainStatus.REGISTERED && params.certificate.length === 0) {
throw new Error('The first CCU 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(ChainValidatorsStore)
.get(context, params.sendingChainID);
if (
params.activeValidatorsUpdate.length > 0 ||
params.certificateThreshold !== sendingChainValidators.certificateThreshold
) {
await this.internalMethod.verifyValidatorsUpdate(context, params);
}

if (!isInboxUpdateEmpty(params.inboxUpdate)) {
await this.internalMethod.verifyPartnerChainOutboxRoot(context, params);
}
await this.verifyCommon(context);

return {
status: VerifyStatus.OK,
Expand All @@ -104,76 +84,11 @@ export class MainchainCCUpdateCommand extends BaseCrossChainUpdateCommand<Mainch
public async execute(
context: CommandExecuteContext<CrossChainUpdateTransactionParams>,
): Promise<void> {
const { params, transaction } = context;

await this.internalMethod.verifyCertificateSignature(context, params);

if (!isInboxUpdateEmpty(params.inboxUpdate)) {
// Initialize the relayer account for the message fee token.
// This is necessary to ensure that the relayer can receive the CCM fees
// If the account already exists, nothing is done.
const messageFeeTokenID = await this._interopsMethod.getMessageFeeTokenID(
context,
params.sendingChainID,
);
// FIXME: When updating fee logic, the fix value should be removed.
await this._tokenMethod.initializeUserAccount(
context,
transaction.senderAddress,
messageFeeTokenID,
transaction.senderAddress,
BigInt(500000000),
);
}

const decodedCCMs = [];
for (const ccmBytes of params.inboxUpdate.crossChainMessages) {
try {
const ccm = codec.decode<CCMsg>(ccmSchema, ccmBytes);
validateFormat(ccm);
decodedCCMs.push(ccm);
if (!ccm.sendingChainID.equals(params.sendingChainID)) {
throw new Error('CCM is not from the sending chain.');
}
if (ccm.sendingChainID.equals(ccm.receivingChainID)) {
throw new Error('Sending and receiving chains must differ.');
}
if (ccm.status === CCMStatusCode.CHANNEL_UNAVAILABLE) {
throw new Error('CCM status channel unavailable can only be set on the mainchain.');
}
} catch (error) {
await this.internalMethod.terminateChainInternal(context, params.sendingChainID);
const ccmID = utils.hash(ccmBytes);
this.events.get(CcmProcessedEvent).log(context, params.sendingChainID, context.chainID, {
ccmID,
code: CCMProcessedCode.INVALID_CCM_VALIDATION_EXCEPTION,
result: CCMProcessedResult.DISCARDED,
});
return;
}
}

const sendingChainValidators = await this.stores
.get(ChainValidatorsStore)
.get(context, params.sendingChainID);
if (
params.activeValidatorsUpdate.length > 0 ||
params.certificateThreshold !== sendingChainValidators.certificateThreshold
) {
await this.internalMethod.updateValidators(context, params);
}
if (params.certificate.length > 0) {
await this.internalMethod.updateCertificate(context, params);
}
if (!isInboxUpdateEmpty(params.inboxUpdate)) {
await this.stores
.get(ChannelDataStore)
.updatePartnerChainOutboxRoot(
context,
params.sendingChainID,
params.inboxUpdate.messageWitnessHashes,
);
const [decodedCCMs, ok] = await this.executeCommon(context, true);
if (!ok) {
return;
}
const { params } = context;

for (let i = 0; i < decodedCCMs.length; i += 1) {
const ccm = decodedCCMs[i];
Expand Down
Loading