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

Commit

Permalink
Update executeCommon & test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
sitetester committed Jan 19, 2023
1 parent 0bddf9d commit 5556a62
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
CrossChainMessageContext,
CrossChainUpdateTransactionParams,
TokenMethod,
EmptyCCM,
} from './types';
import { ChainAccountStore, ChainStatus } from './stores/chain_account';
import {
Expand All @@ -37,7 +38,6 @@ import {
validateFormat,
} from './utils';
import { ChainValidatorsStore } from './stores/chain_validators';
import { ChannelDataStore } from './stores/channel_data';

export abstract class BaseCrossChainUpdateCommand<
T extends BaseInteroperabilityInternalMethod,
Expand Down Expand Up @@ -83,6 +83,8 @@ export abstract class BaseCrossChainUpdateCommand<
isMainchain: boolean,
): Promise<[CCMsg[], boolean]> {
const { params, transaction } = context;

// Verify certificate signature. We do it here because if it fails, the transaction fails rather than being invalid.
await this.internalMethod.verifyCertificateSignature(context, params);

if (!isInboxUpdateEmpty(params.inboxUpdate)) {
Expand All @@ -100,68 +102,108 @@ export abstract class BaseCrossChainUpdateCommand<
);
}

const decodedCCMs = [];
const terminateChain = async (): Promise<void> => {
await this.internalMethod.terminateChainInternal(context, params.sendingChainID);
};

const ccms: CCMsg[] = [];
let ccm: CCMsg;

// Process cross-chain messages in inbox update.
// First process basic checks for all CCMs.
for (const ccmBytes of params.inboxUpdate.crossChainMessages) {
try {
const ccm = codec.decode<CCMsg>(ccmSchema, ccmBytes);
// Verify general format. Past this point, we can access ccm root properties.
ccm = codec.decode<CCMsg>(ccmSchema, ccmBytes);
} catch (error) {
await terminateChain();
this.events.get(CcmProcessedEvent).log(context, params.sendingChainID, context.chainID, {
ccm: EmptyCCM,
result: CCMProcessedResult.DISCARDED,
code: CCMProcessedCode.INVALID_CCM_DECODING_EXCEPTION,
});
// In this case, we do not even update the chain account with the new certificate.
return [[], false];
}

try {
validateFormat(ccm);
decodedCCMs.push(ccm);
} catch (error) {
await terminateChain();
ccm = { ...ccm, params: EMPTY_BYTES };
this.events
.get(CcmProcessedEvent)
.log(context, params.sendingChainID, ccm.receivingChainID, {
ccm,
result: CCMProcessedResult.DISCARDED,
code: CCMProcessedCode.INVALID_CCM_VALIDATION_EXCEPTION,
});
// In this case, we do not even update the chain account with the new certificate.
return [[], false];
}

if (!isMainchain && !context.chainID.equals(ccm.receivingChainID)) {
throw new Error('CCM is not directed to the sidechain.');
}
try {
// The CCM must come from the sending chain.
if (isMainchain && !ccm.sendingChainID.equals(params.sendingChainID)) {
throw new Error('CCM is not from the sending chain.');
}
if (ccm.sendingChainID.equals(ccm.receivingChainID)) {
// Sending and receiving chains must differ.
if (ccm.receivingChainID.equals(ccm.sendingChainID)) {
throw new Error('Sending and receiving chains must differ.');
}
// The CCM must come be directed to the sidechain, unless it was bounced on the mainchain.
if (!isMainchain && !context.chainID.equals(ccm.receivingChainID)) {
throw new Error('CCM is not directed to the sidechain.');
}
if (isMainchain && ccm.status === CCMStatusCode.CHANNEL_UNAVAILABLE) {
throw new Error('CCM status channel unavailable can only be set on the mainchain.');
}
ccms.push(ccm);
} catch (error) {
await this.internalMethod.terminateChainInternal(context, params.sendingChainID);
this.events.get(CcmProcessedEvent).log(context, params.sendingChainID, context.chainID, {
code: CCMProcessedCode.INVALID_CCM_VALIDATION_EXCEPTION,
result: CCMProcessedResult.DISCARDED,
// When failing decode, add event with zero values
ccm: {
crossChainCommand: '',
fee: BigInt(0),
module: '',
nonce: BigInt(0),
params: EMPTY_BYTES,
receivingChainID: EMPTY_BYTES,
sendingChainID: EMPTY_BYTES,
status: 0,
},
});
await terminateChain();
this.events
.get(CcmProcessedEvent)
.log(context, params.sendingChainID, ccm.receivingChainID, {
ccm,
result: CCMProcessedResult.DISCARDED,
code: CCMProcessedCode.INVALID_CCM_ROUTING_EXCEPTION,
});
// In this case, we do not even update the chain account with the new certificate.
return [[], false];
}
}

const sendingChainValidators = await this.stores
.get(ChainValidatorsStore)
.get(context, params.sendingChainID);
return [ccms, true];
}

protected async afterExecuteCommon(
context: CommandExecuteContext<CrossChainUpdateTransactionParams>,
) {
const { params } = context;

// Update sidechain validators.
const chainValidatorsStore = this.stores.get(ChainValidatorsStore);
const validators = await chainValidatorsStore.get(context, params.sendingChainID);

const { blsKeysUpdate, bftWeightsUpdate, bftWeightsUpdateBitmap } =
params.activeValidatorsUpdate;

if (
!emptyActiveValidatorsUpdate(params.activeValidatorsUpdate) ||
params.certificateThreshold !== sendingChainValidators.certificateThreshold
blsKeysUpdate.length > 0 ||
bftWeightsUpdate.length > 0 ||
bftWeightsUpdateBitmap !== EMPTY_BYTES ||
params.certificateThreshold !== validators.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,
);

if (!isInboxUpdateEmpty(params.inboxUpdate) && params.certificate.length > 0) {
await this.internalMethod.updatePartnerChainOutboxRoot(context, params);
}
return [decodedCCMs, true];
}

protected async apply(context: CrossChainMessageContext): Promise<void> {
Expand Down
18 changes: 11 additions & 7 deletions framework/src/modules/interoperability/events/ccm_processed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,22 @@ export const enum CCMProcessedCode {
CROSS_CHAIN_COMMAND_NOT_SUPPORTED = 3,
// Value of code of CCM Processed Event if processing failed due to: exception in cross-chain command execution
FAILED_CCM = 4,
// Value of code of CCM Processed Event if processing failed due to: exception in validation
INVALID_CCM_VALIDATION_EXCEPTION = 5,
// Value of code of CCM Processed Event if processing failed due to: exception in ccm decoding
INVALID_CCM_DECODING_EXCEPTION = 5,
// Value of code of CCM Processed Event if processing failed due to: exception in format validation
INVALID_CCM_VALIDATION_EXCEPTION = 6,
// Value of code of CCM Processed Event if processing failed due to: exception in validation of ccm routing rules
INVALID_CCM_ROUTING_EXCEPTION = 7,
// Value of code of CCM Processed Event if processing failed due to: exception in CCM verification
INVALID_CCM_VERIFY_CCM_EXCEPTION = 6,
INVALID_CCM_VERIFY_CCM_EXCEPTION = 8,
// Value of code of CCM Processed Event if processing failed due to: exception in cross-chain command verification
INVALID_CCM_VERIFY_EXCEPTION = 7,
INVALID_CCM_VERIFY_EXCEPTION = 9,
// Value of code of CCM Processed Event if processing failed due to: exception in before cross-chain command execution
INVALID_CCM_BEFORE_CCC_EXECUTION_EXCEPTION = 8,
INVALID_CCM_BEFORE_CCC_EXECUTION_EXCEPTION = 10,
// Value of code of CCM Processed Event if processing failed due to: exception in after cross-chain command execution
INVALID_CCM_AFTER_CCC_EXECUTION_EXCEPTION = 9,
INVALID_CCM_AFTER_CCC_EXECUTION_EXCEPTION = 11,
// Value of code of CCM Processed Event if processing failed due to: exception in before cross-chain command forwarding
INVALID_CCM_BEFORE_CCC_FORWARDING_EXCEPTION = 10,
INVALID_CCM_BEFORE_CCC_FORWARDING_EXCEPTION = 12,
}

export interface CcmProcessedEventData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ export class SubmitMainchainCrossChainUpdateCommand extends BaseCrossChainUpdate
const { params } = context;

try {
// Update the context to indicate that now we start the CCM processing.
context.contextStore.set(CONTEXT_STORE_KEY_CCM_PROCESSING, true);

for (let i = 0; i < decodedCCMs.length; i += 1) {
const ccm = decodedCCMs[i];
const ccmBytes = params.inboxUpdate.crossChainMessages[i];
Expand All @@ -102,16 +104,25 @@ export class SubmitMainchainCrossChainUpdateCommand extends BaseCrossChainUpdate
eventQueue: context.eventQueue.getChildQueue(ccmID),
};

// If the receiving chain is the mainchain, apply the CCM
// This function never raises an error.
if (ccm.receivingChainID.equals(getMainchainID(context.chainID))) {
await this.apply(ccmContext);
} else {
await this._forward(ccmContext);
}

// We append at the very end. This implies that if the message leads to a chain termination,
// it is still possible to recover it (because the channel terminated message
// would refer to an inbox where the message has not been appended yet).
await this.internalMethod.appendToInboxTree(context, params.sendingChainID, ccmBytes);
}
} finally {
// Update the context to indicate that now we stop the CCM processing.
context.contextStore.delete(CONTEXT_STORE_KEY_CCM_PROCESSING);
}

await this.afterExecuteCommon(context);
}

private async _forward(context: CrossChainMessageContext): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export class SubmitSidechainCrossChainUpdateCommand extends BaseCrossChainUpdate
const { params } = context;

try {
// Update the context to indicate that now we start the CCM processing.
context.contextStore.set(CONTEXT_STORE_KEY_CCM_PROCESSING, true);

for (let i = 0; i < decodedCCMs.length; i += 1) {
const ccm = decodedCCMs[i];
const ccmBytes = params.inboxUpdate.crossChainMessages[i];
Expand All @@ -71,10 +73,17 @@ export class SubmitSidechainCrossChainUpdateCommand extends BaseCrossChainUpdate
};

await this.apply(ccmContext);

// We append at the very end. This implies that if the message leads to a chain termination,
// it is still possible to recover it (because the channel terminated message
// would refer to an inbox where the message has not been appended yet).
await this.internalMethod.appendToInboxTree(context, params.sendingChainID, ccmBytes);
}
} finally {
// Update the context to indicate that now we stop the CCM processing.
context.contextStore.delete(CONTEXT_STORE_KEY_CCM_PROCESSING);
}

await this.afterExecuteCommon(context);
}
}
18 changes: 18 additions & 0 deletions framework/src/modules/interoperability/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import { OutboxRoot } from './stores/outbox_root';
import { ChainID } from './stores/registered_names';
import { TerminatedOutboxAccount } from './stores/terminated_outbox';
import { TerminatedStateAccount } from './stores/terminated_state';
import { EMPTY_BYTES } from './constants';

export type StoreCallback = (moduleID: Buffer, storePrefix: Buffer) => SubStore;
export type ImmutableStoreCallback = (moduleID: Buffer, storePrefix: Buffer) => ImmutableSubStore;

export interface CCMsg {
readonly nonce: bigint;
readonly module: string;
Expand All @@ -42,6 +44,17 @@ export interface CCMsg {
readonly params: Buffer;
}

export const EmptyCCM = {
crossChainCommand: '',
fee: BigInt(0),
module: '',
nonce: BigInt(0),
params: EMPTY_BYTES,
receivingChainID: EMPTY_BYTES,
sendingChainID: EMPTY_BYTES,
status: 0,
};

export interface ActiveValidatorsUpdate {
blsKeysUpdate: Buffer[];
bftWeightsUpdate: bigint[];
Expand Down Expand Up @@ -76,6 +89,7 @@ export interface CCUpdateParams {
certificateThreshold: bigint;
inboxUpdate: InboxUpdate;
}

export interface ImmutableCrossChainMessageContext {
getMethodContext: () => ImmutableMethodContext;
getStore: ImmutableStoreCallback;
Expand All @@ -100,9 +114,11 @@ export interface CrossChainMessageContext extends ImmutableCrossChainMessageCont
contextStore: Map<string, unknown>;
eventQueue: EventQueue;
}

export interface CCCommandExecuteContext<T> extends CrossChainMessageContext {
params: T;
}

export interface RecoverContext {
getMethodContext: () => MethodContext;
getStore: StoreCallback;
Expand Down Expand Up @@ -279,6 +295,7 @@ export interface ValidatorKeys {

export interface ValidatorsMethod {
getValidatorKeys(methodContext: ImmutableMethodContext, address: Buffer): Promise<ValidatorKeys>;

getValidatorsParams(
methodContext: ImmutableMethodContext,
): Promise<{ validators: Validator[]; certificateThreshold: bigint }>;
Expand Down Expand Up @@ -388,6 +405,7 @@ export interface CCMRegistrationParams {
name: string;
messageFeeTokenID: Buffer;
}

export interface TokenMethod {
initializeUserAccount(
methodContext: MethodContext,
Expand Down
Loading

0 comments on commit 5556a62

Please sign in to comment.