From 25e4012e9bc6d1b26e7e7a3abb3e0b2d7c4d04c2 Mon Sep 17 00:00:00 2001 From: shuse2 Date: Tue, 15 Nov 2022 15:13:28 +0100 Subject: [PATCH 1/2] :recycle: Update interoperability initGenesisState --- .../base_interoperability_endpoint.ts | 2 +- .../base_interoperability_internal_methods.ts | 5 +- .../base_interoperability_module.ts | 283 ++++- .../interoperability/mainchain/module.ts | 6 - .../src/modules/interoperability/schemas.ts | 2 +- .../interoperability/sidechain/module.ts | 6 - .../stores/chain_validators.ts | 4 +- .../stores/terminated_state.ts | 4 +- .../src/modules/interoperability/types.ts | 2 +- .../src/modules/interoperability/utils.ts | 306 +---- framework/src/testing/create_contexts.ts | 3 +- .../base_interoperability_module.spec.ts | 1012 +++++++++++++++++ .../modules/interoperability/endpoint.spec.ts | 2 +- .../interoperability/internal_method.spec.ts | 18 +- .../modules/interoperability/utils.spec.ts | 914 +-------------- 15 files changed, 1318 insertions(+), 1251 deletions(-) create mode 100644 framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts diff --git a/framework/src/modules/interoperability/base_interoperability_endpoint.ts b/framework/src/modules/interoperability/base_interoperability_endpoint.ts index 353f0600a0f..e0cc6f0cdf3 100644 --- a/framework/src/modules/interoperability/base_interoperability_endpoint.ts +++ b/framework/src/modules/interoperability/base_interoperability_endpoint.ts @@ -102,7 +102,7 @@ export abstract class BaseInteroperabilityEndpoint extends BaseEndpoint { return { stateRoot: stateRoot.toString('hex'), initialized, - mainchainStateRoot: mainchainStateRoot?.toString('hex'), + mainchainStateRoot: mainchainStateRoot.toString('hex'), }; } diff --git a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts index 7810569ca94..36e7cf7d045 100644 --- a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts +++ b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts @@ -24,6 +24,7 @@ import { MODULE_NAME_INTEROPERABILITY, CHAIN_ID_MAINCHAIN, MESSAGE_TAG_CERTIFICATE, + EMPTY_HASH, } from './constants'; import { ccmSchema } from './schemas'; import { CCMsg, CrossChainUpdateTransactionParams, ChainAccount } from './types'; @@ -188,7 +189,7 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet terminatedState = { stateRoot: stateRoot ?? chainAccount.lastCertificate.stateRoot, - mainchainStateRoot: EMPTY_BYTES, + mainchainStateRoot: EMPTY_HASH, initialized: true, }; this.events @@ -206,7 +207,7 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet // State root is not available, set it to empty bytes temporarily. // This should only happen on a sidechain. terminatedState = { - stateRoot: EMPTY_BYTES, + stateRoot: EMPTY_HASH, mainchainStateRoot: mainchainAccount.lastCertificate.stateRoot, initialized: false, }; diff --git a/framework/src/modules/interoperability/base_interoperability_module.ts b/framework/src/modules/interoperability/base_interoperability_module.ts index d4fb71c64b5..ff1ab2108cb 100644 --- a/framework/src/modules/interoperability/base_interoperability_module.ts +++ b/framework/src/modules/interoperability/base_interoperability_module.ts @@ -12,11 +12,17 @@ * Removal or modification of this copyright notice is prohibited. */ +import { codec } from '@liskhq/lisk-codec'; +import { dataStructures } from '@liskhq/lisk-utils'; +import { MAX_UINT64, validator } from '@liskhq/lisk-validator'; +import { GenesisBlockExecuteContext } from '../../state_machine'; +import { splitTokenID } from '../token/utils'; import { BaseCCCommand } from './base_cc_command'; import { BaseCCMethod } from './base_cc_method'; import { BaseInteroperableModule } from './base_interoperable_module'; -import { MODULE_NAME_INTEROPERABILITY } from './constants'; -import { ChainAccountStore } from './stores/chain_account'; +import { EMPTY_HASH, MODULE_NAME_INTEROPERABILITY } from './constants'; +import { genesisInteroperabilitySchema } from './schemas'; +import { ChainAccountStore, ChainStatus } from './stores/chain_account'; import { ChainValidatorsStore } from './stores/chain_validators'; import { ChannelDataStore } from './stores/channel_data'; import { OutboxRootStore } from './stores/outbox_root'; @@ -24,6 +30,8 @@ import { OwnChainAccountStore } from './stores/own_chain_account'; import { RegisteredNamesStore } from './stores/registered_names'; import { TerminatedOutboxStore } from './stores/terminated_outbox'; import { TerminatedStateStore } from './stores/terminated_state'; +import { GenesisInteroperability } from './types'; +import { getMainchainID, getMainchainTokenID } from './utils'; export abstract class BaseInteroperabilityModule extends BaseInteroperableModule { protected interoperableCCCommands = new Map(); @@ -50,4 +58,275 @@ export abstract class BaseInteroperabilityModule extends BaseInteroperableModule this.interoperableCCMethods.set(module.name, module.crossChainMethod); this.interoperableCCCommands.set(module.name, module.crossChainCommand); } + + public async initGenesisState(ctx: GenesisBlockExecuteContext): Promise { + const assetBytes = ctx.assets.getAsset(MODULE_NAME_INTEROPERABILITY); + if (!assetBytes) { + return; + } + const mainchainID = getMainchainID(ctx.chainID); + const mainchainTokenID = getMainchainTokenID(ctx.chainID); + + const genesisStore = codec.decode( + genesisInteroperabilitySchema, + assetBytes, + ); + validator.validate(genesisInteroperabilitySchema, genesisStore); + + const outboxRootStoreKeySet = new dataStructures.BufferSet(); + const outboxRootStore = this.stores.get(OutboxRootStore); + for (const outboxRootData of genesisStore.outboxRootSubstore) { + if (outboxRootStoreKeySet.has(outboxRootData.storeKey)) { + throw new Error( + `Outbox root store key ${outboxRootData.storeKey.toString('hex')} is duplicated.`, + ); + } + outboxRootStoreKeySet.add(outboxRootData.storeKey); + await outboxRootStore.set(ctx, outboxRootData.storeKey, outboxRootData.storeValue); + } + + const channelDataStoreKeySet = new dataStructures.BufferSet(); + const channelDataStore = this.stores.get(ChannelDataStore); + for (const channelData of genesisStore.channelDataSubstore) { + if (channelDataStoreKeySet.has(channelData.storeKey)) { + throw new Error( + `Channel data store key ${channelData.storeKey.toString('hex')} is duplicated.`, + ); + } + channelDataStoreKeySet.add(channelData.storeKey); + + const channel = channelData.storeValue; + const [chainID] = splitTokenID(channel.messageFeeTokenID); + + if ( + !channel.messageFeeTokenID.equals(mainchainTokenID) && // corresponding to the LSK token + !chainID.equals(channelData.storeKey) && // Token.getChainID(channel.messageFeeTokenID) must be equal to channelData.storeKey + !chainID.equals(ctx.chainID) // the message fee token must be a native token of either chains + ) { + throw new Error( + `messageFeeTokenID corresponding to the channel data store key ${channelData.storeKey.toString( + 'hex', + )} is not valid.`, + ); + } + + await channelDataStore.set(ctx, channelData.storeKey, channelData.storeValue); + } + + const chainValidatorsStoreKeySet = new dataStructures.BufferSet(); + const chainValidatorsStore = this.stores.get(ChainValidatorsStore); + for (const chainValidators of genesisStore.chainValidatorsSubstore) { + if (chainValidatorsStoreKeySet.has(chainValidators.storeKey)) { + throw new Error( + `Chain validators store key ${chainValidators.storeKey.toString('hex')} is duplicated.`, + ); + } + chainValidatorsStoreKeySet.add(chainValidators.storeKey); + + const { activeValidators, certificateThreshold } = chainValidators.storeValue; + + let totalWeight = BigInt(0); + for (let j = 0; j < activeValidators.length; j += 1) { + const activeValidator = activeValidators[j]; + + const { blsKey } = activeValidator; + if ( + j < activeValidators.length - 1 && + blsKey.compare(activeValidators[j + 1].blsKey) >= 0 + ) { + throw new Error( + 'Active validators must be ordered lexicographically by blsKey property and pairwise distinct.', + ); + } + const { bftWeight } = activeValidator; + if (bftWeight <= BigInt(0)) { + throw new Error('BFTWeight must be a positive integer.'); + } + totalWeight += bftWeight; + } + + if (totalWeight > MAX_UINT64) { + throw new Error( + 'The total BFT weight of all active validators has to be less than or equal to MAX_UINT64.', + ); + } + + const checkBftWeightValue = totalWeight / BigInt(3) + BigInt(1); + if (certificateThreshold > totalWeight || checkBftWeightValue > certificateThreshold) { + throw new Error('The total BFT weight of all active validators is not valid.'); + } + + await chainValidatorsStore.set(ctx, chainValidators.storeKey, chainValidators.storeValue); + } + + const chainDataStoreKeySet = new dataStructures.BufferSet(); + const chainDataStore = this.stores.get(ChainAccountStore); + let hasSidechainAccount = false; + for (const chainData of genesisStore.chainDataSubstore) { + const chainDataStoreKey = chainData.storeKey; + if (chainDataStoreKeySet.has(chainDataStoreKey)) { + throw new Error(`Chain data store key ${chainDataStoreKey.toString('hex')} is duplicated.`); + } + chainDataStoreKeySet.add(chainDataStoreKey); + + const chainAccountStatus = chainData.storeValue.status; + if (chainAccountStatus === ChainStatus.TERMINATED) { + if (outboxRootStoreKeySet.has(chainDataStoreKey)) { + throw new Error('Outbox root store cannot have entry for a terminated chain account.'); + } + if ( + !channelDataStoreKeySet.has(chainDataStoreKey) || + !chainValidatorsStoreKeySet.has(chainDataStoreKey) + ) { + throw new Error( + `Chain data store key ${chainDataStoreKey.toString( + 'hex', + )} missing in some or all of channel data and chain validators stores.`, + ); + } + } else if ( + !outboxRootStoreKeySet.has(chainDataStoreKey) || + !channelDataStoreKeySet.has(chainDataStoreKey) || + !chainValidatorsStoreKeySet.has(chainDataStoreKey) + ) { + throw new Error( + `Chain data store key ${chainDataStoreKey.toString( + 'hex', + )} missing in some or all of outbox root, channel data and chain validators stores.`, + ); + } + + if (!(chainDataStoreKey.equals(ctx.chainID) || chainDataStoreKey.equals(mainchainID))) { + hasSidechainAccount = true; + } + + await chainDataStore.set(ctx, chainData.storeKey, chainData.storeValue); + } + + if ( + hasSidechainAccount && + !(chainDataStoreKeySet.has(mainchainID) && chainDataStoreKeySet.has(ctx.chainID)) + ) { + throw new Error( + 'If a chain account for another sidechain is present, then a chain account for the mainchain must be present, as well as the own chain account.', + ); + } + + for (const storeKey of outboxRootStoreKeySet) { + if (!chainDataStoreKeySet.has(storeKey)) { + throw new Error( + `Outbox root store key ${storeKey.toString('hex')} is missing in chain data store.`, + ); + } + } + + for (const storeKey of channelDataStoreKeySet) { + if (!chainDataStoreKeySet.has(storeKey)) { + throw new Error( + `Channel data store key ${storeKey.toString('hex')} is missing in chain data store.`, + ); + } + } + + for (const storeKey of chainValidatorsStoreKeySet) { + if (!chainDataStoreKeySet.has(storeKey)) { + throw new Error( + `Chain validators store key ${storeKey.toString('hex')} is missing in chain data store.`, + ); + } + } + + const ownChainAccountStore = this.stores.get(OwnChainAccountStore); + const ownChainDataStoreKeySet = new dataStructures.BufferSet(); + for (const ownChainData of genesisStore.ownChainDataSubstore) { + if (ownChainDataStoreKeySet.has(ownChainData.storeKey)) { + throw new Error( + `Own chain data store key ${ownChainData.storeKey.toString('hex')} is duplicated.`, + ); + } + ownChainDataStoreKeySet.add(ownChainData.storeKey); + + await ownChainAccountStore.set(ctx, ownChainData.storeKey, ownChainData.storeValue); + } + + const terminatedOutboxStoreKeySet = new dataStructures.BufferSet(); + const terminatedOutboxStore = this.stores.get(TerminatedOutboxStore); + for (const terminatedOutbox of genesisStore.terminatedOutboxSubstore) { + if (terminatedOutboxStoreKeySet.has(terminatedOutbox.storeKey)) { + throw new Error( + `Terminated outbox store key ${terminatedOutbox.storeKey.toString('hex')} is duplicated.`, + ); + } + terminatedOutboxStoreKeySet.add(terminatedOutbox.storeKey); + + await terminatedOutboxStore.set(ctx, terminatedOutbox.storeKey, terminatedOutbox.storeValue); + } + + const terminatedStateStoreKeySet = new dataStructures.BufferSet(); + const terminatedStateStore = this.stores.get(TerminatedStateStore); + for (const terminatedState of genesisStore.terminatedStateSubstore) { + const terminatedStateStoreKey = terminatedState.storeKey; + if (terminatedStateStoreKeySet.has(terminatedStateStoreKey)) { + throw new Error( + `Terminated state store key ${terminatedStateStoreKey.toString('hex')} is duplicated.`, + ); + } + terminatedStateStoreKeySet.add(terminatedStateStoreKey); + + const terminatedStateStoreValue = terminatedState.storeValue; + if (!terminatedStateStoreValue.initialized) { + if (terminatedOutboxStoreKeySet.has(terminatedStateStoreKey)) { + throw new Error( + `Uninitialized account associated with terminated state store key ${terminatedStateStoreKey.toString( + 'hex', + )} cannot be present in terminated outbox store.`, + ); + } + if ( + !terminatedStateStoreValue.stateRoot.equals(EMPTY_HASH) || + terminatedStateStoreValue.mainchainStateRoot.equals(EMPTY_HASH) + ) { + throw new Error( + `For the uninitialized account associated with terminated state store key ${terminatedStateStoreKey.toString( + 'hex', + )} the stateRoot must be set to empty hash and mainchainStateRoot to a 32-bytes value.`, + ); + } + } else if ( + terminatedStateStoreValue.stateRoot.equals(EMPTY_HASH) || + !terminatedStateStoreValue.mainchainStateRoot.equals(EMPTY_HASH) + ) { + throw new Error( + `For the initialized account associated with terminated state store key ${terminatedStateStoreKey.toString( + 'hex', + )} the mainchainStateRoot must be set empty value and stateRoot to a 32-bytes value.`, + ); + } + + await terminatedStateStore.set(ctx, terminatedState.storeKey, terminatedState.storeValue); + } + + for (const storeKey of terminatedOutboxStoreKeySet) { + if (!terminatedStateStoreKeySet.has(storeKey)) { + throw new Error( + `Terminated outbox store key ${storeKey.toString( + 'hex', + )} missing in terminated state store.`, + ); + } + } + + const registeredNamesStoreKeySet = new dataStructures.BufferSet(); + const registeredNamesStore = this.stores.get(RegisteredNamesStore); + for (const registeredNames of genesisStore.registeredNamesSubstore) { + if (registeredNamesStoreKeySet.has(registeredNames.storeKey)) { + throw new Error( + `Registered names store key ${registeredNames.storeKey.toString('hex')} is duplicated.`, + ); + } + registeredNamesStoreKeySet.add(registeredNames.storeKey); + + await registeredNamesStore.set(ctx, registeredNames.storeKey, registeredNames.storeValue); + } + } } diff --git a/framework/src/modules/interoperability/mainchain/module.ts b/framework/src/modules/interoperability/mainchain/module.ts index aa470807a0b..5c001a6138c 100644 --- a/framework/src/modules/interoperability/mainchain/module.ts +++ b/framework/src/modules/interoperability/mainchain/module.ts @@ -24,8 +24,6 @@ import { getTerminatedStateAccountRequestSchema, getTerminatedOutboxAccountRequestSchema, } from '../schemas'; -import { GenesisBlockExecuteContext } from '../../../state_machine'; -import { initGenesisStateUtil } from '../utils'; import { chainAccountSchema, allChainAccountsSchema, @@ -140,8 +138,4 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule ], }; } - - public async initGenesisState(context: GenesisBlockExecuteContext): Promise { - await initGenesisStateUtil(context, this.stores); - } } diff --git a/framework/src/modules/interoperability/schemas.ts b/framework/src/modules/interoperability/schemas.ts index 9c32d2e1c5b..9caa5e2f835 100644 --- a/framework/src/modules/interoperability/schemas.ts +++ b/framework/src/modules/interoperability/schemas.ts @@ -533,7 +533,7 @@ export const getTerminatedStateAccountRequestSchema = getChainAccountRequestSche export const getTerminatedOutboxAccountRequestSchema = getChainAccountRequestSchema; -export const genesisInteroperabilityInternalMethodSchema = { +export const genesisInteroperabilitySchema = { $id: '/interoperability/module/genesis', type: 'object', required: [ diff --git a/framework/src/modules/interoperability/sidechain/module.ts b/framework/src/modules/interoperability/sidechain/module.ts index 29f50f4f825..0d9549b8195 100644 --- a/framework/src/modules/interoperability/sidechain/module.ts +++ b/framework/src/modules/interoperability/sidechain/module.ts @@ -25,8 +25,6 @@ import { getTerminatedStateAccountRequestSchema, getTerminatedOutboxAccountRequestSchema, } from '../schemas'; -import { GenesisBlockExecuteContext } from '../../../state_machine'; -import { initGenesisStateUtil } from '../utils'; import { chainAccountSchema, allChainAccountsSchema, @@ -143,10 +141,6 @@ export class SidechainInteroperabilityModule extends BaseInteroperabilityModule }; } - public async initGenesisState(context: GenesisBlockExecuteContext): Promise { - await initGenesisStateUtil(context, this.stores); - } - // eslint-disable-next-line @typescript-eslint/require-await public async init(_args: ModuleInitArgs) { this._mainchainRegistrationCommand.addDependencies(this._validatorsMethod); diff --git a/framework/src/modules/interoperability/stores/chain_validators.ts b/framework/src/modules/interoperability/stores/chain_validators.ts index 7a9288e98b6..072e2ba4aaf 100644 --- a/framework/src/modules/interoperability/stores/chain_validators.ts +++ b/framework/src/modules/interoperability/stores/chain_validators.ts @@ -14,7 +14,7 @@ import { dataStructures } from '@liskhq/lisk-utils'; import { BaseStore, StoreGetter } from '../../base_store'; import { ActiveValidator } from '../types'; -import { BLS_PUBLIC_KEY_LENGTH } from '../constants'; +import { BLS_PUBLIC_KEY_LENGTH, MAX_NUM_VALIDATORS } from '../constants'; export interface ChainValidators { activeValidators: ActiveValidator[]; @@ -30,6 +30,8 @@ export const chainValidatorsSchema = { activeValidators: { type: 'array', fieldNumber: 1, + minItems: 1, + maxItems: MAX_NUM_VALIDATORS, items: { type: 'object', required: ['blsKey', 'bftWeight'], diff --git a/framework/src/modules/interoperability/stores/terminated_state.ts b/framework/src/modules/interoperability/stores/terminated_state.ts index 936bfa1368b..ab208ed8abf 100644 --- a/framework/src/modules/interoperability/stores/terminated_state.ts +++ b/framework/src/modules/interoperability/stores/terminated_state.ts @@ -16,13 +16,13 @@ import { HASH_LENGTH } from '../constants'; export interface TerminatedStateAccount { stateRoot: Buffer; - mainchainStateRoot?: Buffer; + mainchainStateRoot: Buffer; initialized?: boolean; } export interface TerminatedStateAccountJSON { stateRoot: string; - mainchainStateRoot?: string; + mainchainStateRoot: string; initialized?: boolean; } diff --git a/framework/src/modules/interoperability/types.ts b/framework/src/modules/interoperability/types.ts index da237793b72..2100ef515a9 100644 --- a/framework/src/modules/interoperability/types.ts +++ b/framework/src/modules/interoperability/types.ts @@ -346,7 +346,7 @@ export interface ChainValidatorsJSON { certificateThreshold: string; } -export interface GenesisInteroperabilityInternalMethod { +export interface GenesisInteroperability { outboxRootSubstore: { storeKey: Buffer; storeValue: OutboxRoot; diff --git a/framework/src/modules/interoperability/utils.ts b/framework/src/modules/interoperability/utils.ts index 31b0f31db0c..5506fbab747 100644 --- a/framework/src/modules/interoperability/utils.ts +++ b/framework/src/modules/interoperability/utils.ts @@ -16,7 +16,6 @@ import { regularMerkleTree, sparseMerkleTree } from '@liskhq/lisk-tree'; import { codec } from '@liskhq/lisk-codec'; import { utils } from '@liskhq/lisk-cryptography'; import { validator } from '@liskhq/lisk-validator'; -import { dataStructures } from '@liskhq/lisk-utils'; import { NAME_REGEX } from '@liskhq/lisk-chain'; import { ActiveValidators, @@ -27,48 +26,31 @@ import { CrossChainUpdateTransactionParams, ChainValidators, InboxUpdate, - GenesisInteroperabilityInternalMethod, } from './types'; import { EMPTY_BYTES, LIVENESS_LIMIT, - MAINCHAIN_ID_BUFFER, MAX_CCM_SIZE, - MAX_NUM_VALIDATORS, - MAX_UINT64, - MODULE_NAME_INTEROPERABILITY, SMT_KEY_LENGTH, - HASH_LENGTH, - TOKEN_ID_LSK, CCMStatusCode, CHAIN_ID_LENGTH, } from './constants'; import { ccmSchema, - genesisInteroperabilityInternalMethodSchema, sidechainTerminatedCCMParamsSchema, validatorsHashInputSchema, } from './schemas'; -import { - BlockHeader, - GenesisBlockExecuteContext, - VerificationResult, - VerifyStatus, -} from '../../state_machine'; +import { BlockHeader, VerificationResult, VerifyStatus } from '../../state_machine'; import { Certificate } from '../../engine/consensus/certificate_generation/types'; import { certificateSchema } from '../../engine/consensus/certificate_generation/schema'; import { CommandExecuteContext } from '../../state_machine/types'; import { certificateToJSON } from './certificates'; import { NamedRegistry } from '../named_registry'; import { OutboxRootStore } from './stores/outbox_root'; -import { OwnChainAccountStore } from './stores/own_chain_account'; import { ChannelDataStore } from './stores/channel_data'; import { ChainValidatorsStore, calculateNewActiveValidators } from './stores/chain_validators'; import { ChainAccountStore, ChainStatus } from './stores/chain_account'; -import { TerminatedOutboxAccount, TerminatedOutboxStore } from './stores/terminated_outbox'; -import { TerminatedStateStore } from './stores/terminated_state'; -import { RegisteredNamesStore } from './stores/registered_names'; -import { splitTokenID } from '../token/utils'; +import { TerminatedOutboxAccount } from './stores/terminated_outbox'; interface CommonExecutionLogicArgs { stores: NamedRegistry; @@ -493,290 +475,6 @@ export const commonCCUExecutelogic = async (args: CommonExecutionLogicArgs) => { await partnerChannelStore.set(context, chainIDBuffer, partnerChannelData); }; -export const initGenesisStateUtil = async ( - ctx: GenesisBlockExecuteContext, - stores: NamedRegistry, -) => { - const assetBytes = ctx.assets.getAsset(MODULE_NAME_INTEROPERABILITY); - if (!assetBytes) { - return; - } - - const genesisStore = codec.decode( - genesisInteroperabilityInternalMethodSchema, - assetBytes, - ); - validator.validate(genesisInteroperabilityInternalMethodSchema, genesisStore); - - const outboxRootStoreKeySet = new dataStructures.BufferSet(); - const outboxRootStore = stores.get(OutboxRootStore); - for (const outboxRootData of genesisStore.outboxRootSubstore) { - if (outboxRootStoreKeySet.has(outboxRootData.storeKey)) { - throw new Error( - `Outbox root store key ${outboxRootData.storeKey.toString('hex')} is duplicated.`, - ); - } - outboxRootStoreKeySet.add(outboxRootData.storeKey); - await outboxRootStore.set(ctx, outboxRootData.storeKey, outboxRootData.storeValue); - } - - const ownChainAccountStore = stores.get(OwnChainAccountStore); - const ownChainAccount = await ownChainAccountStore.get(ctx, MAINCHAIN_ID_BUFFER); - const channelDataStoreKeySet = new dataStructures.BufferSet(); - const channelDataStore = stores.get(ChannelDataStore); - for (const channelData of genesisStore.channelDataSubstore) { - if (channelDataStoreKeySet.has(channelData.storeKey)) { - throw new Error( - `Channel data store key ${channelData.storeKey.toString('hex')} is duplicated.`, - ); - } - channelDataStoreKeySet.add(channelData.storeKey); - - const channel = channelData.storeValue; - const chainID = splitTokenID(channel.messageFeeTokenID)[0]; - - if ( - !channel.messageFeeTokenID.equals(TOKEN_ID_LSK) && // corresponding to the LSK token - !chainID.equals(channelData.storeKey) && // Token.getChainID(channel.messageFeeTokenID) must be equal to channelData.storeKey - !chainID.equals(ownChainAccount.chainID) // the message fee token must be a native token of either chains - ) { - throw new Error( - `messageFeeTokenID corresponding to the channel data store key ${channelData.storeKey.toString( - 'hex', - )} is not valid.`, - ); - } - - await channelDataStore.set(ctx, channelData.storeKey, channelData.storeValue); - } - - const chainValidatorsStoreKeySet = new dataStructures.BufferSet(); - const chainValidatorsStore = stores.get(ChainValidatorsStore); - for (const chainValidators of genesisStore.chainValidatorsSubstore) { - if (chainValidatorsStoreKeySet.has(chainValidators.storeKey)) { - throw new Error( - `Chain validators store key ${chainValidators.storeKey.toString('hex')} is duplicated.`, - ); - } - chainValidatorsStoreKeySet.add(chainValidators.storeKey); - - const { activeValidators, certificateThreshold } = chainValidators.storeValue; - if (activeValidators.length < 1 || activeValidators.length > MAX_NUM_VALIDATORS) { - throw new Error( - `Active validators must have at least 1 element and at most ${MAX_NUM_VALIDATORS} elements.`, - ); - } - - let totalWeight = BigInt(0); - for (let j = 0; j < activeValidators.length; j += 1) { - const activeValidator = activeValidators[j]; - - const { blsKey } = activeValidator; - if (j < activeValidators.length - 1 && blsKey.compare(activeValidators[j + 1].blsKey) >= 0) { - throw new Error( - 'Active validators must be ordered lexicographically by blsKey property and pairwise distinct.', - ); - } - - const { bftWeight } = activeValidator; - totalWeight += bftWeight; - } - - if (totalWeight > MAX_UINT64) { - throw new Error( - 'The total BFT weight of all active validators has to be less than or equal to MAX_UINT64.', - ); - } - - const checkBftWeightValue = totalWeight / BigInt(3) + BigInt(1); - if (checkBftWeightValue > totalWeight || checkBftWeightValue > certificateThreshold) { - throw new Error('The total BFT weight of all active validators is not valid.'); - } - - await chainValidatorsStore.set(ctx, chainValidators.storeKey, chainValidators.storeValue); - } - - const chainDataStoreKeySet = new dataStructures.BufferSet(); - const chainDataStore = stores.get(ChainAccountStore); - let isAnotherSidechainAccount = 0; - for (const chainData of genesisStore.chainDataSubstore) { - const chainDataStoreKey = chainData.storeKey; - if (chainDataStoreKeySet.has(chainDataStoreKey)) { - throw new Error(`Chain data store key ${chainDataStoreKey.toString('hex')} is duplicated.`); - } - chainDataStoreKeySet.add(chainDataStoreKey); - - const chainAccountStatus = chainData.storeValue.status; - if (chainAccountStatus === ChainStatus.TERMINATED) { - if (outboxRootStoreKeySet.has(chainDataStoreKey)) { - throw new Error('Outbox root store cannot have entry for a terminated chain account.'); - } - if ( - !channelDataStoreKeySet.has(chainDataStoreKey) || - !chainValidatorsStoreKeySet.has(chainDataStoreKey) - ) { - throw new Error( - `Chain data store key ${chainDataStoreKey.toString( - 'hex', - )} missing in some or all of channel data and chain validators stores.`, - ); - } - } - if ( - !outboxRootStoreKeySet.has(chainDataStoreKey) || - !channelDataStoreKeySet.has(chainDataStoreKey) || - !chainValidatorsStoreKeySet.has(chainDataStoreKey) - ) { - throw new Error( - `Chain data store key ${chainDataStoreKey.toString( - 'hex', - )} missing in some or all of outbox root, channel data and chain validators stores.`, - ); - } - - if ( - !( - chainDataStoreKey.equals(ownChainAccount.chainID) || - chainDataStoreKey.equals(MAINCHAIN_ID_BUFFER) - ) - ) { - isAnotherSidechainAccount = 1; - } - - await chainDataStore.set(ctx, chainData.storeKey, chainData.storeValue); - } - - if ( - isAnotherSidechainAccount && - !( - chainDataStoreKeySet.has(MAINCHAIN_ID_BUFFER) && - chainDataStoreKeySet.has(ownChainAccount.chainID) - ) - ) { - throw new Error( - 'If a chain account for another sidechain is present, then a chain account for the mainchain must be present, as well as the own chain account.', - ); - } - - for (const storeKey of outboxRootStoreKeySet) { - if (!chainDataStoreKeySet.has(storeKey)) { - throw new Error( - `Outbox root store key ${storeKey.toString('hex')} is missing in chain data store.`, - ); - } - } - - for (const storeKey of channelDataStoreKeySet) { - if (!chainDataStoreKeySet.has(storeKey)) { - throw new Error( - `Channel data store key ${storeKey.toString('hex')} is missing in chain data store.`, - ); - } - } - - for (const storeKey of chainValidatorsStoreKeySet) { - if (!chainDataStoreKeySet.has(storeKey)) { - throw new Error( - `Chain validators store key ${storeKey.toString('hex')} is missing in chain data store.`, - ); - } - } - - const ownChainDataStoreKeySet = new dataStructures.BufferSet(); - for (const ownChainData of genesisStore.ownChainDataSubstore) { - if (ownChainDataStoreKeySet.has(ownChainData.storeKey)) { - throw new Error( - `Own chain data store key ${ownChainData.storeKey.toString('hex')} is duplicated.`, - ); - } - ownChainDataStoreKeySet.add(ownChainData.storeKey); - - await ownChainAccountStore.set(ctx, ownChainData.storeKey, ownChainData.storeValue); - } - - const terminatedOutboxStoreKeySet = new dataStructures.BufferSet(); - const terminatedOutboxStore = stores.get(TerminatedOutboxStore); - for (const terminatedOutbox of genesisStore.terminatedOutboxSubstore) { - if (terminatedOutboxStoreKeySet.has(terminatedOutbox.storeKey)) { - throw new Error( - `Terminated outbox store key ${terminatedOutbox.storeKey.toString('hex')} is duplicated.`, - ); - } - terminatedOutboxStoreKeySet.add(terminatedOutbox.storeKey); - - await terminatedOutboxStore.set(ctx, terminatedOutbox.storeKey, terminatedOutbox.storeValue); - } - - const terminatedStateStoreKeySet = new dataStructures.BufferSet(); - const terminatedStateStore = stores.get(TerminatedStateStore); - for (const terminatedState of genesisStore.terminatedStateSubstore) { - const terminatedStateStoreKey = terminatedState.storeKey; - if (terminatedStateStoreKeySet.has(terminatedStateStoreKey)) { - throw new Error( - `Terminated state store key ${terminatedStateStoreKey.toString('hex')} is duplicated.`, - ); - } - terminatedStateStoreKeySet.add(terminatedStateStoreKey); - - const terminatedStateStoreValue = terminatedState.storeValue; - if (terminatedStateStoreValue.initialized === false) { - if (terminatedOutboxStoreKeySet.has(terminatedStateStoreKey)) { - throw new Error( - `Uninitialized account associated with terminated state store key ${terminatedStateStoreKey.toString( - 'hex', - )} cannot be present in terminated outbox store.`, - ); - } - if ( - !terminatedStateStoreValue.stateRoot.equals(EMPTY_BYTES) || - terminatedStateStoreValue.mainchainStateRoot?.length !== 32 - ) { - throw new Error( - `For the uninitialized account associated with terminated state store key ${terminatedStateStoreKey.toString( - 'hex', - )} the stateRoot must be set to empty bytes and mainchainStateRoot to a 32-bytes value.`, - ); - } - } else if (terminatedStateStoreValue.initialized === true) { - if ( - terminatedStateStoreValue.stateRoot.length !== HASH_LENGTH || - terminatedStateStoreValue.mainchainStateRoot?.length !== HASH_LENGTH - ) { - throw new Error( - `For the initialized account associated with terminated state store key ${terminatedStateStoreKey.toString( - 'hex', - )} the mainchainStateRoot must be set to a 32-bytes value and stateRoot to a 32-bytes value.`, - ); - } - } - - await terminatedStateStore.set(ctx, terminatedState.storeKey, terminatedState.storeValue); - } - - for (const storeKey of terminatedOutboxStoreKeySet) { - if (!terminatedStateStoreKeySet.has(storeKey)) { - throw new Error( - `Terminated outbox store key ${storeKey.toString( - 'hex', - )} missing in terminated state store.`, - ); - } - } - - const registeredNamesStoreKeySet = new dataStructures.BufferSet(); - const registeredNamesStore = stores.get(RegisteredNamesStore); - for (const registeredNames of genesisStore.registeredNamesSubstore) { - if (registeredNamesStoreKeySet.has(registeredNames.storeKey)) { - throw new Error( - `Registered names store key ${registeredNames.storeKey.toString('hex')} is duplicated.`, - ); - } - registeredNamesStoreKeySet.add(registeredNames.storeKey); - - await registeredNamesStore.set(ctx, registeredNames.storeKey, registeredNames.storeValue); - } -}; - export const chainAccountToJSON = (chainAccount: ChainAccount) => { const { lastCertificate, name, status } = chainAccount; diff --git a/framework/src/testing/create_contexts.ts b/framework/src/testing/create_contexts.ts index a51e69d93a7..473a0c3c3fe 100644 --- a/framework/src/testing/create_contexts.ts +++ b/framework/src/testing/create_contexts.ts @@ -95,6 +95,7 @@ export const createGenesisBlockContext = (params: { export const createBlockContext = (params: { stateStore?: PrefixedStateReadWriter; eventQueue?: EventQueue; + chainID?: Buffer; logger?: Logger; header?: BlockHeader; assets?: BlockAssets; @@ -112,7 +113,7 @@ export const createBlockContext = (params: { transactions: params.transactions ?? [], header, assets: params.assets ?? new BlockAssets(), - chainID: utils.getRandomBytes(32), + chainID: params.chainID ?? utils.getRandomBytes(4), }); return ctx; }; diff --git a/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts b/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts new file mode 100644 index 00000000000..3c2c95a3f71 --- /dev/null +++ b/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts @@ -0,0 +1,1012 @@ +/* + * Copyright © 2022 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +import { BlockAssets } from '@liskhq/lisk-chain'; +import { codec } from '@liskhq/lisk-codec'; +import { utils } from '@liskhq/lisk-cryptography'; +import { MainchainInteroperabilityModule } from '../../../../src'; +import { BaseInteroperabilityModule } from '../../../../src/modules/interoperability/base_interoperability_module'; +import { + BLS_PUBLIC_KEY_LENGTH, + CHAIN_ID_LENGTH, + EMPTY_HASH, + HASH_LENGTH, + MAINCHAIN_ID_BUFFER, + MAX_NUM_VALIDATORS, + MAX_UINT64, + MODULE_NAME_INTEROPERABILITY, +} from '../../../../src/modules/interoperability/constants'; +import { genesisInteroperabilitySchema } from '../../../../src/modules/interoperability/schemas'; +import { + ChainAccountStore, + ChainStatus, +} from '../../../../src/modules/interoperability/stores/chain_account'; +import { ChainValidatorsStore } from '../../../../src/modules/interoperability/stores/chain_validators'; +import { ChannelDataStore } from '../../../../src/modules/interoperability/stores/channel_data'; +import { OutboxRootStore } from '../../../../src/modules/interoperability/stores/outbox_root'; +import { OwnChainAccountStore } from '../../../../src/modules/interoperability/stores/own_chain_account'; +import { RegisteredNamesStore } from '../../../../src/modules/interoperability/stores/registered_names'; +import { TerminatedOutboxStore } from '../../../../src/modules/interoperability/stores/terminated_outbox'; +import { TerminatedStateStore } from '../../../../src/modules/interoperability/stores/terminated_state'; +import { PrefixedStateReadWriter } from '../../../../src/state_machine/prefixed_state_read_writer'; +import { createGenesisBlockContext } from '../../../../src/testing'; +import { InMemoryPrefixedStateDB } from '../../../../src/testing/in_memory_prefixed_state'; + +describe('BaseInteroperabilityModule', () => { + let interopMod: BaseInteroperabilityModule; + + const mainchainID = Buffer.from([0, 0, 0, 0]); + const ownChainID = Buffer.from([0, 0, 1, 0]); + const mainchainTokenID = Buffer.concat([mainchainID, Buffer.alloc(4)]); + + beforeEach(() => { + interopMod = new MainchainInteroperabilityModule(); + }); + + describe('initGenesisState', () => { + const timestamp = 2592000 * 100; + const chainAccount = { + name: 'account1', + chainID: Buffer.alloc(CHAIN_ID_LENGTH), + lastCertificate: { + height: 567467, + timestamp: timestamp - 500000, + stateRoot: Buffer.alloc(HASH_LENGTH), + validatorsHash: Buffer.alloc(HASH_LENGTH), + }, + status: 2739, + }; + const sidechainChainAccount = { + name: 'sidechain1', + chainID: Buffer.alloc(CHAIN_ID_LENGTH), + lastCertificate: { + height: 10, + stateRoot: utils.getRandomBytes(32), + timestamp: 100, + validatorsHash: utils.getRandomBytes(32), + }, + status: ChainStatus.TERMINATED, + }; + const ownChainAccount = { + name: 'mainchain', + chainID: MAINCHAIN_ID_BUFFER, + nonce: BigInt('0'), + }; + const channelData = { + inbox: { + appendPath: [Buffer.alloc(HASH_LENGTH), Buffer.alloc(HASH_LENGTH)], + root: utils.getRandomBytes(HASH_LENGTH), + size: 18, + }, + messageFeeTokenID: mainchainTokenID, + outbox: { + appendPath: [Buffer.alloc(HASH_LENGTH), Buffer.alloc(HASH_LENGTH)], + root: utils.getRandomBytes(HASH_LENGTH), + size: 18, + }, + partnerChainOutboxRoot: utils.getRandomBytes(HASH_LENGTH), + }; + const outboxRoot = { root: utils.getRandomBytes(HASH_LENGTH) }; + const validatorsHashInput = { + activeValidators: [ + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + { + blsKey: Buffer.from( + '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + certificateThreshold: BigInt(10), + }; + const terminatedStateAccount = { + stateRoot: sidechainChainAccount.lastCertificate.stateRoot, + mainchainStateRoot: EMPTY_HASH, + initialized: true, + }; + const terminatedOutboxAccount = { + outboxRoot: utils.getRandomBytes(HASH_LENGTH), + outboxSize: 1, + partnerChainInboxSize: 1, + }; + const registeredNameId = { chainID: Buffer.alloc(CHAIN_ID_LENGTH) }; + const registeredChainId = { chainID: Buffer.alloc(CHAIN_ID_LENGTH) }; + const validData = { + outboxRootSubstore: [ + { storeKey: mainchainID, storeValue: outboxRoot }, + { storeKey: ownChainID, storeValue: outboxRoot }, + ], + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: ownChainID, storeValue: chainAccount }, + ], + channelDataSubstore: [ + { storeKey: mainchainID, storeValue: channelData }, + { storeKey: ownChainID, storeValue: channelData }, + ], + chainValidatorsSubstore: [ + { storeKey: mainchainID, storeValue: validatorsHashInput }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + ownChainDataSubstore: [ + { storeKey: mainchainID, storeValue: ownChainAccount }, + { storeKey: ownChainID, storeValue: ownChainAccount }, + ], + terminatedStateSubstore: [ + { storeKey: mainchainID, storeValue: terminatedStateAccount }, + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + ], + terminatedOutboxSubstore: [ + { storeKey: mainchainID, storeValue: terminatedOutboxAccount }, + { storeKey: ownChainID, storeValue: terminatedOutboxAccount }, + ], + registeredNamesSubstore: [ + { storeKey: mainchainID, storeValue: registeredNameId }, + { storeKey: ownChainID, storeValue: registeredNameId }, + ], + registeredChainIDsSubstore: [ + { storeKey: mainchainID, storeValue: registeredChainId }, + { storeKey: ownChainID, storeValue: registeredChainId }, + ], + }; + + const invalidData = { + ...validData, + outboxRootSubstore: [ + { storeKey: mainchainID, storeValue: { root: utils.getRandomBytes(37) } }, + { storeKey: ownChainID, storeValue: { root: utils.getRandomBytes(5) } }, + ], + }; + + let stateStore: PrefixedStateReadWriter; + let channelDataSubstore: ChannelDataStore; + let outboxRootSubstore: OutboxRootStore; + let terminatedOutboxSubstore: TerminatedOutboxStore; + let chainDataSubstore: ChainAccountStore; + let terminatedStateSubstore: TerminatedStateStore; + let chainValidatorsSubstore: ChainValidatorsStore; + let ownChainDataSubstore: OwnChainAccountStore; + let registeredNamesSubstore: RegisteredNamesStore; + + beforeEach(() => { + stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); + ownChainDataSubstore = interopMod.stores.get(OwnChainAccountStore); + channelDataSubstore = interopMod.stores.get(ChannelDataStore); + chainValidatorsSubstore = interopMod.stores.get(ChainValidatorsStore); + outboxRootSubstore = interopMod.stores.get(OutboxRootStore); + terminatedOutboxSubstore = interopMod.stores.get(TerminatedOutboxStore); + chainDataSubstore = interopMod.stores.get(ChainAccountStore); + terminatedStateSubstore = interopMod.stores.get(TerminatedStateStore); + registeredNamesSubstore = interopMod.stores.get(RegisteredNamesStore); + }); + + it('should not throw error if asset does not exist', async () => { + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + }).createInitGenesisStateContext(); + jest.spyOn(context, 'getStore'); + + await expect(interopMod.initGenesisState(context)).toResolve(); + expect(context.getStore).not.toHaveBeenCalled(); + }); + + it('should throw if the asset object is invalid', async () => { + const encodedAsset = codec.encode(genesisInteroperabilitySchema, invalidData); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Lisk validator found 2 error', + ); + }); + + it('should throw if outbox root store key is duplicated', async () => { + const validData1 = { + ...validData, + outboxRootSubstore: [ + { storeKey: ownChainID, storeValue: outboxRoot }, + { storeKey: ownChainID, storeValue: outboxRoot }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow('Outbox root store key'); + }); + + it('should throw if chain data store key is duplicated', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: ownChainID, storeValue: chainAccount }, + { storeKey: ownChainID, storeValue: chainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow('Chain data store key'); + }); + + it('should throw if channel data store key is duplicated', async () => { + const validData1 = { + ...validData, + channelDataSubstore: [ + { storeKey: ownChainID, storeValue: channelData }, + { storeKey: ownChainID, storeValue: channelData }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow('Channel data store key'); + }); + + it('should throw if chain validators store key is duplicated', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { storeKey: ownChainID, storeValue: validatorsHashInput }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Chain validators store key', + ); + }); + + it('should throw if own chain store key is duplicated', async () => { + const validData1 = { + ...validData, + ownChainDataSubstore: [ + { storeKey: ownChainID, storeValue: ownChainAccount }, + { storeKey: ownChainID, storeValue: ownChainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Own chain data store key', + ); + }); + + it('should throw if terminated state store key is duplicated', async () => { + const validData1 = { + ...validData, + terminatedStateSubstore: [ + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Terminated state store key', + ); + }); + + it('should throw if terminated outbox store key is duplicated', async () => { + const validData1 = { + ...validData, + terminatedOutboxSubstore: [ + { storeKey: ownChainID, storeValue: terminatedOutboxAccount }, + { storeKey: ownChainID, storeValue: terminatedOutboxAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Terminated outbox store key', + ); + }); + + it('should throw if registered names store key is duplicated', async () => { + const validData1 = { + ...validData, + registeredNamesSubstore: [ + { storeKey: ownChainID, storeValue: registeredNameId }, + { storeKey: ownChainID, storeValue: registeredNameId }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Registered names store ke', + ); + }); + + it('should throw if some store key in chain data substore is missing in outbox root substore and the corresponding chain account is not inactive', async () => { + const validData1 = { + ...validData, + outboxRootSubstore: [{ storeKey: mainchainID, storeValue: outboxRoot }], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in chain data substore is present in outbox root substore but the corresponding chain account is inactive', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: { ...chainAccount, status: 2 } }, + { storeKey: ownChainID, storeValue: chainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Outbox root store cannot have entry for a terminated chain acco', + ); + }); + + it('should throw if some store key in chain data substore is missing in channel data substore', async () => { + const validData1 = { + ...validData, + channelDataSubstore: [{ storeKey: mainchainID, storeValue: channelData }], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in chain data substore is missing in chain validators substore', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [{ storeKey: mainchainID, storeValue: validatorsHashInput }], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in outbox data substore is missing in chain data substore', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in channel data substore is missing in chain data substore', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in chain validators substore is missing in chain data substore', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stores', + ); + }); + + it('should throw if some store key in terminated outbox substore is missing in the terminated state substore', async () => { + const validData1 = { + ...validData, + terminatedStateSubstore: [{ storeKey: mainchainID, storeValue: terminatedStateAccount }], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in terminated state store', + ); + }); + + it('should throw if some store key in terminated state substore is present in the terminated outbox substore but the property initialized is set to false', async () => { + const validData1 = { + ...validData, + terminatedStateSubstore: [ + { + storeKey: mainchainID, + storeValue: { ...terminatedStateAccount, initialized: false }, + }, + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Uninitialized account associated with terminated state store key ', + ); + }); + + it('should throw if some store key in terminated state substore has the property initialized set to false but stateRoot is not set to empty bytes and mainchainStateRoot not set to a 32-bytes value', async () => { + const validData1 = { + ...validData, + terminatedStateSubstore: [ + { + storeKey: mainchainID, + storeValue: { ...terminatedStateAccount, initialized: false }, + }, + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Uninitialized account associated with terminated state store key ', + ); + }); + + it('should throw if some store key in terminated state substore has the property initialized set to true but mainchainStateRoot is not set to empty hash and stateRoot not set to a 32-bytes value', async () => { + const validData1 = { + ...validData, + terminatedStateSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...terminatedStateAccount, + initialized: true, + mainchainStateRoot: utils.getRandomBytes(32), + stateRoot: utils.getRandomBytes(32), + }, + }, + { storeKey: ownChainID, storeValue: terminatedStateAccount }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'For the initialized account associated with terminated state store', + ); + }); + + it('should throw if active validators have less than 1 element', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { ...validatorsHashInput, activeValidators: [] }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'must NOT have fewer than 1 items', + ); + }); + + it('should throw if active validators have more than MAX_NUM_VALIDATORS elements', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: new Array(MAX_NUM_VALIDATORS + 1).fill(0).map((_, i) => ({ + blsKey: Buffer.alloc(BLS_PUBLIC_KEY_LENGTH, i), + bftWeight: BigInt(1), + })), + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'must NOT have more than 199 items', + ); + }); + + it('should throw if active validators are not ordered lexicographically by blsKey', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: Buffer.from( + '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Active validators must be ordered lexicographically by blsKey property and pairwise distinct', + ); + }); + + it('should throw if some active validators have blsKey which is not 48 bytes', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: utils.getRandomBytes(21), + bftWeight: BigInt(10), + }, + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow('minLength not satisfied'); + }); + + it('should throw if some active validators have blsKey which is not pairwise distinct', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'Active validators must be ordered lexicographically by blsKey property and pairwise distinct', + ); + }); + + it('should throw if some active validators have bftWeight which is not a positive integer', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(0), + }, + { + blsKey: Buffer.from( + '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'BFTWeight must be a positive integer', + ); + }); + + it('should throw if total bft weight of active validators is greater than MAX_UINT64', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(MAX_UINT64), + }, + { + blsKey: Buffer.from( + '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(10), + }, + ], + }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'The total BFT weight of all active validators has to be less than or equal to MAX_UINT64', + ); + }); + + it('should throw if total bft weight of active validators is less than the value check', async () => { + const validatorsHashInput1 = { + ...validatorsHashInput, + activeValidators: [ + { + blsKey: Buffer.from( + '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', + 'hex', + ), + bftWeight: BigInt(1), + }, + ], + }; + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { storeKey: mainchainID, storeValue: validatorsHashInput1 }, + { storeKey: ownChainID, storeValue: validatorsHashInput1 }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'The total BFT weight of all active validators is not valid', + ); + }); + + it('should throw if certificateThreshold is less than the value check', async () => { + const validData1 = { + ...validData, + chainValidatorsSubstore: [ + { + storeKey: mainchainID, + storeValue: { ...validatorsHashInput, certificateThreshold: BigInt(1) }, + }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'The total BFT weight of all active validators is not valid', + ); + }); + + it('should throw if a chain account for another sidechain is present but chain account for mainchain is not present', async () => { + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: ownChainID, storeValue: chainAccount }, + { storeKey: Buffer.from([0, 7, 7, 7]), storeValue: chainAccount }, + ], + outboxRootSubstore: [ + { storeKey: mainchainID, storeValue: outboxRoot }, + { storeKey: ownChainID, storeValue: outboxRoot }, + ], + channelDataSubstore: [ + { storeKey: mainchainID, storeValue: channelData }, + { storeKey: ownChainID, storeValue: channelData }, + ], + chainValidatorsSubstore: [ + { storeKey: mainchainID, storeValue: validatorsHashInput }, + { storeKey: ownChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'missing in some or all of outbox root, channel data and chain validators stor', + ); + }); + + it('should throw if a chain account for another sidechain is present but chain account for ownchain is not present', async () => { + const otherChainID = Buffer.from([0, 2, 2, 2]); + const validData1 = { + ...validData, + chainDataSubstore: [ + { storeKey: mainchainID, storeValue: chainAccount }, + { storeKey: otherChainID, storeValue: chainAccount }, + ], + outboxRootSubstore: [ + { storeKey: mainchainID, storeValue: outboxRoot }, + { storeKey: otherChainID, storeValue: outboxRoot }, + ], + channelDataSubstore: [ + { storeKey: mainchainID, storeValue: channelData }, + { storeKey: otherChainID, storeValue: channelData }, + ], + chainValidatorsSubstore: [ + { storeKey: mainchainID, storeValue: validatorsHashInput }, + { storeKey: otherChainID, storeValue: validatorsHashInput }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + 'If a chain account for another sidechain is present, then a chain account for the mainchain must be present', + ); + }); + + it('should not throw if some chain id corresponding to message fee token id of a channel is not 1 but is corresponding native token id of either chains', async () => { + const validData1 = { + ...validData, + channelDataSubstore: [ + { storeKey: mainchainID, storeValue: channelData }, + { + storeKey: ownChainID, + storeValue: { + ...channelData, + messageFeeTokenID: Buffer.from([0, 0, 1, 0, 0, 0, 1, 0]), + }, + }, + ], + }; + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData1); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + await expect(interopMod.initGenesisState(context)).resolves.toBeUndefined(); + }); + + it('should create all the corresponding entries in the interoperability module state for every substore for valid input', async () => { + const encodedAsset = codec.encode(genesisInteroperabilitySchema, validData); + const context = createGenesisBlockContext({ + stateStore, + chainID: ownChainID, + assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), + }).createInitGenesisStateContext(); + + await expect(interopMod.initGenesisState(context)).toResolve(); + + channelDataSubstore = interopMod.stores.get(ChannelDataStore); + chainValidatorsSubstore = interopMod.stores.get(ChainValidatorsStore); + outboxRootSubstore = interopMod.stores.get(OutboxRootStore); + terminatedOutboxSubstore = interopMod.stores.get(TerminatedOutboxStore); + chainDataSubstore = interopMod.stores.get(ChainAccountStore); + terminatedStateSubstore = interopMod.stores.get(TerminatedStateStore); + registeredNamesSubstore = interopMod.stores.get(RegisteredNamesStore); + ownChainDataSubstore = interopMod.stores.get(OwnChainAccountStore); + + for (const data of validData.chainDataSubstore) { + await expect(chainDataSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.chainValidatorsSubstore) { + await expect(chainValidatorsSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.outboxRootSubstore) { + await expect(outboxRootSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.terminatedOutboxSubstore) { + await expect(terminatedOutboxSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.channelDataSubstore) { + await expect(channelDataSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.terminatedStateSubstore) { + await expect(terminatedStateSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.registeredNamesSubstore) { + await expect(registeredNamesSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + for (const data of validData.ownChainDataSubstore) { + await expect(ownChainDataSubstore.has(stateStore, data.storeKey)).resolves.toBeTrue(); + } + }); + }); +}); diff --git a/framework/test/unit/modules/interoperability/endpoint.spec.ts b/framework/test/unit/modules/interoperability/endpoint.spec.ts index bd959477e8d..2054ba44e95 100644 --- a/framework/test/unit/modules/interoperability/endpoint.spec.ts +++ b/framework/test/unit/modules/interoperability/endpoint.spec.ts @@ -151,7 +151,7 @@ describe('Test interoperability endpoint', () => { const terminatedStateAccountJSON: TerminatedStateAccountJSON = { stateRoot: terminateStateAccount.stateRoot.toString('hex'), initialized: terminateStateAccount.initialized, - mainchainStateRoot: terminateStateAccount.mainchainStateRoot?.toString('hex'), + mainchainStateRoot: terminateStateAccount.mainchainStateRoot.toString('hex'), }; const terminatedOutboxAccount: TerminatedOutboxAccount = { diff --git a/framework/test/unit/modules/interoperability/internal_method.spec.ts b/framework/test/unit/modules/interoperability/internal_method.spec.ts index 01a3396c1a7..9869a94d151 100644 --- a/framework/test/unit/modules/interoperability/internal_method.spec.ts +++ b/framework/test/unit/modules/interoperability/internal_method.spec.ts @@ -18,7 +18,7 @@ import { regularMerkleTree } from '@liskhq/lisk-tree'; import { codec } from '@liskhq/lisk-codec'; import { BLS_PUBLIC_KEY_LENGTH, - EMPTY_BYTES, + EMPTY_HASH, HASH_LENGTH, MAINCHAIN_ID, MAINCHAIN_ID_BUFFER, @@ -281,7 +281,7 @@ describe('Base interoperability internal method', () => { ), ).resolves.toStrictEqual({ stateRoot, - mainchainStateRoot: EMPTY_BYTES, + mainchainStateRoot: EMPTY_HASH, initialized: true, }); expect(chainAccountUpdatedEvent.log).toHaveBeenCalledWith( @@ -294,7 +294,7 @@ describe('Base interoperability internal method', () => { chainId, { stateRoot, - mainchainStateRoot: EMPTY_BYTES, + mainchainStateRoot: EMPTY_HASH, initialized: true, }, ); @@ -315,7 +315,7 @@ describe('Base interoperability internal method', () => { ), ).resolves.toStrictEqual({ stateRoot: chainAccount.lastCertificate.stateRoot, - mainchainStateRoot: EMPTY_BYTES, + mainchainStateRoot: EMPTY_HASH, initialized: true, }); }); @@ -350,10 +350,8 @@ describe('Base interoperability internal method', () => { chainIdNew, ); - await expect( - terminatedStateSubstore.get(createStoreGetter(crossChainMessageContext as any), chainIdNew), - ).resolves.toStrictEqual({ - stateRoot: EMPTY_BYTES, + await expect(terminatedStateSubstore.get(crossChainMessageContext, chainIdNew)).resolves.toStrictEqual({ + stateRoot: EMPTY_HASH, mainchainStateRoot: chainAccount.lastCertificate.stateRoot, initialized: false, }); @@ -361,8 +359,8 @@ describe('Base interoperability internal method', () => { { eventQueue: crossChainMessageContext.eventQueue }, chainIdNew, { - stateRoot: chainAccount.lastCertificate.stateRoot, - mainchainStateRoot: EMPTY_BYTES, + stateRoot: EMPTY_HASH, + mainchainStateRoot: chainAccount.lastCertificate.stateRoot, initialized: false, }, ); diff --git a/framework/test/unit/modules/interoperability/utils.spec.ts b/framework/test/unit/modules/interoperability/utils.spec.ts index ccf650f108c..54eacfefdb0 100644 --- a/framework/test/unit/modules/interoperability/utils.spec.ts +++ b/framework/test/unit/modules/interoperability/utils.spec.ts @@ -16,7 +16,6 @@ import { utils } from '@liskhq/lisk-cryptography'; import { codec } from '@liskhq/lisk-codec'; import * as cryptography from '@liskhq/lisk-cryptography'; import * as merkleTree from '@liskhq/lisk-tree'; -import { BlockAssets } from '@liskhq/lisk-chain'; import { MainchainInteroperabilityModule, VerifyStatus } from '../../../../src'; import { CCMStatusCode, @@ -25,17 +24,10 @@ import { EMPTY_BYTES, HASH_LENGTH, LIVENESS_LIMIT, - MAINCHAIN_ID_BUFFER, MAX_CCM_SIZE, - MAX_NUM_VALIDATORS, - MAX_UINT64, MODULE_NAME_INTEROPERABILITY, - TOKEN_ID_LSK, } from '../../../../src/modules/interoperability/constants'; -import { - ccmSchema, - genesisInteroperabilityInternalMethodSchema, -} from '../../../../src/modules/interoperability/schemas'; +import { ccmSchema } from '../../../../src/modules/interoperability/schemas'; import { ChainAccount, ChannelData, @@ -53,7 +45,6 @@ import { commonCCUExecutelogic, computeValidatorsHash, getIDAsKeyForStore, - initGenesisStateUtil, validateFormat, verifyLivenessConditionForRegisteredChains, } from '../../../../src/modules/interoperability/utils'; @@ -61,20 +52,12 @@ import { certificateSchema } from '../../../../src/engine/consensus/certificate_ import { Certificate } from '../../../../src/engine/consensus/certificate_generation/types'; import { PrefixedStateReadWriter } from '../../../../src/state_machine/prefixed_state_read_writer'; import { InMemoryPrefixedStateDB } from '../../../../src/testing/in_memory_prefixed_state'; -import { createGenesisBlockContext } from '../../../../src/testing'; import { ChainAccountStore, ChainStatus, } from '../../../../src/modules/interoperability/stores/chain_account'; -import { RegisteredNamesStore } from '../../../../src/modules/interoperability/stores/registered_names'; import { ChainValidatorsStore } from '../../../../src/modules/interoperability/stores/chain_validators'; -import { TerminatedStateStore } from '../../../../src/modules/interoperability/stores/terminated_state'; -import { TerminatedOutboxStore } from '../../../../src/modules/interoperability/stores/terminated_outbox'; -import { OutboxRootStore } from '../../../../src/modules/interoperability/stores/outbox_root'; import { ChannelDataStore } from '../../../../src/modules/interoperability/stores/channel_data'; -import { OwnChainAccountStore } from '../../../../src/modules/interoperability/stores/own_chain_account'; -import { createStoreGetter } from '../../../../src/testing/utils'; -import { CHAIN_ID_LENGTH } from '../../../../src/modules/token/constants'; jest.mock('@liskhq/lisk-cryptography', () => ({ ...jest.requireActual('@liskhq/lisk-cryptography'), @@ -977,901 +960,6 @@ describe('Utils', () => { }); }); - describe('initGenesisStateUtil', () => { - const { getRandomBytes } = cryptography.utils; - const timestamp = 2592000 * 100; - const chainAccount = { - name: 'account1', - chainID: Buffer.alloc(CHAIN_ID_LENGTH), - lastCertificate: { - height: 567467, - timestamp: timestamp - 500000, - stateRoot: Buffer.alloc(HASH_LENGTH), - validatorsHash: Buffer.alloc(HASH_LENGTH), - }, - status: 2739, - }; - const sidechainChainAccount = { - name: 'sidechain1', - chainID: Buffer.alloc(CHAIN_ID_LENGTH), - lastCertificate: { - height: 10, - stateRoot: utils.getRandomBytes(32), - timestamp: 100, - validatorsHash: utils.getRandomBytes(32), - }, - status: ChainStatus.TERMINATED, - }; - const ownChainAccount = { - name: 'mainchain', - chainID: MAINCHAIN_ID_BUFFER, - nonce: BigInt('0'), - }; - const channelData = { - inbox: { - appendPath: [Buffer.alloc(HASH_LENGTH), Buffer.alloc(HASH_LENGTH)], - root: cryptography.utils.getRandomBytes(HASH_LENGTH), - size: 18, - }, - messageFeeTokenID: TOKEN_ID_LSK, - outbox: { - appendPath: [Buffer.alloc(HASH_LENGTH), Buffer.alloc(HASH_LENGTH)], - root: cryptography.utils.getRandomBytes(HASH_LENGTH), - size: 18, - }, - partnerChainOutboxRoot: cryptography.utils.getRandomBytes(HASH_LENGTH), - }; - const outboxRoot = { root: getRandomBytes(HASH_LENGTH) }; - const validatorsHashInput = { - activeValidators: [ - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - { - blsKey: Buffer.from( - '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - certificateThreshold: BigInt(10), - }; - const terminatedStateAccount = { - stateRoot: sidechainChainAccount.lastCertificate.stateRoot, - mainchainStateRoot: Buffer.alloc(HASH_LENGTH), - initialized: true, - }; - const terminatedOutboxAccount = { - outboxRoot: getRandomBytes(HASH_LENGTH), - outboxSize: 1, - partnerChainInboxSize: 1, - }; - const registeredNameId = { chainID: Buffer.alloc(CHAIN_ID_LENGTH) }; - const registeredChainId = { chainID: Buffer.alloc(CHAIN_ID_LENGTH) }; - const validData = { - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: outboxRoot }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - ], - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - ], - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: channelData }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - ], - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: validatorsHashInput }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - ownChainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: ownChainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: ownChainAccount }, - ], - terminatedStateSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: terminatedStateAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - terminatedOutboxSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: terminatedOutboxAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedOutboxAccount }, - ], - registeredNamesSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: registeredNameId }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: registeredNameId }, - ], - registeredChainIDsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: registeredChainId }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: registeredChainId }, - ], - }; - - const invalidData = { - ...validData, - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: { root: getRandomBytes(37) } }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: { root: getRandomBytes(5) } }, - ], - }; - - let channelDataSubstore: ChannelDataStore; - let outboxRootSubstore: OutboxRootStore; - let terminatedOutboxSubstore: TerminatedOutboxStore; - let stateStore: PrefixedStateReadWriter; - let chainDataSubstore: ChainAccountStore; - let terminatedStateSubstore: TerminatedStateStore; - let chainValidatorsSubstore: ChainValidatorsStore; - let ownChainDataSubstore: OwnChainAccountStore; - let registeredNamesSubstore: RegisteredNamesStore; - - beforeEach(async () => { - stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB()); - ownChainDataSubstore = interopMod.stores.get(OwnChainAccountStore); - await ownChainDataSubstore.set( - createStoreGetter(stateStore), - MAINCHAIN_ID_BUFFER, - ownChainAccount, - ); - channelDataSubstore = interopMod.stores.get(ChannelDataStore); - chainValidatorsSubstore = interopMod.stores.get(ChainValidatorsStore); - outboxRootSubstore = interopMod.stores.get(OutboxRootStore); - terminatedOutboxSubstore = interopMod.stores.get(TerminatedOutboxStore); - chainDataSubstore = interopMod.stores.get(ChainAccountStore); - terminatedStateSubstore = interopMod.stores.get(TerminatedStateStore); - registeredNamesSubstore = interopMod.stores.get(RegisteredNamesStore); - }); - - it('should not throw error if asset does not exist', async () => { - const context = createGenesisBlockContext({ stateStore }).createInitGenesisStateContext(); - jest.spyOn(context, 'getStore'); - - await expect(initGenesisStateUtil(context, interopMod.stores)).toResolve(); - expect(context.getStore).not.toHaveBeenCalled(); - }); - - it('should throw if the asset object is invalid', async () => { - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, invalidData); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if outbox root store key is duplicated', async () => { - const validData1 = { - ...validData, - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if chain data store key is duplicated', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if channel data store key is duplicated', async () => { - const validData1 = { - ...validData, - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if chain validators store key is duplicated', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if own chain store key is duplicated', async () => { - const validData1 = { - ...validData, - ownChainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: ownChainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: ownChainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if terminated state store key is duplicated', async () => { - const validData1 = { - ...validData, - terminatedStateSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if terminated outbox store key is duplicated', async () => { - const validData1 = { - ...validData, - terminatedOutboxSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedOutboxAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedOutboxAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if registered names store key is duplicated', async () => { - const validData1 = { - ...validData, - registeredNamesSubstore: [ - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: registeredNameId }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: registeredNameId }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in chain data substore is missing in outbox root substore and the corresponding chain account is not inactive', async () => { - const validData1 = { - ...validData, - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: outboxRoot }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in chain data substore is present in outbox root substore but the corresponding chain account is inactive', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: { ...chainAccount, status: 2 } }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in chain data substore is missing in channel data substore', async () => { - const validData1 = { - ...validData, - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: channelData }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in chain data substore is missing in chain validators substore', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: validatorsHashInput }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in outbox data substore is missing in chain data substore', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in channel data substore is missing in chain data substore', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in chain validators substore is missing in chain data substore', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 1]), storeValue: chainAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in terminated outbox substore is missing in the terminated state substore', async () => { - const validData1 = { - ...validData, - terminatedStateSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: terminatedStateAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in terminated state substore is present in the terminated outbox substore but the property initialized is set to false', async () => { - const validData1 = { - ...validData, - terminatedStateSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { ...terminatedStateAccount, initialized: false }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in terminated state substore has the property initialized set to false but stateRoot is not set to empty bytes and mainchainStateRoot not set to a 32-bytes value', async () => { - const validData1 = { - ...validData, - terminatedStateSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 0]), - storeValue: { ...terminatedStateAccount, initialized: false }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some store key in terminated state substore has the property initialized set to true but mainchainStateRoot is not set to empty bytes and stateRoot not set to a 32-bytes value', async () => { - const validData1 = { - ...validData, - terminatedStateSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...terminatedStateAccount, - initialized: true, - mainchainStateRoot: getRandomBytes(32), - stateRoot: EMPTY_BYTES, - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: terminatedStateAccount }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if active validators have less than 1 element', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { ...validatorsHashInput, activeValidators: [] }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if active validators have more than MAX_NUM_VALIDATORS elements', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: new Array(MAX_NUM_VALIDATORS + 1).fill(0), - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if active validators are not ordered lexicographically by blsKey', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: Buffer.from( - '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some active validators have blsKey which is not 48 bytes', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: getRandomBytes(21), - bftWeight: BigInt(10), - }, - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some active validators have blsKey which is not pairwise distinct', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if some active validators have bftWeight which is not a positive integer', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(-1), - }, - { - blsKey: Buffer.from( - '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if total bft weight of active validators is greater than MAX_UINT64', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(MAX_UINT64), - }, - { - blsKey: Buffer.from( - '4c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(10), - }, - ], - }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if total bft weight of active validators is less than the value check', async () => { - const validatorsHashInput1 = { - ...validatorsHashInput, - activeValidators: [ - { - blsKey: Buffer.from( - '3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835', - 'hex', - ), - bftWeight: BigInt(0), - }, - ], - }; - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: validatorsHashInput1 }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput1 }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if certificateThreshold is less than the value check', async () => { - const validData1 = { - ...validData, - chainValidatorsSubstore: [ - { - storeKey: Buffer.from([0, 0, 0, 1]), - storeValue: { ...validatorsHashInput, certificateThreshold: BigInt(1) }, - }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if a chain account for another sidechain is present but chain account for mainchain is not present', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - ], - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: outboxRoot }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - ], - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: channelData }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - ], - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: validatorsHashInput }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should throw if a chain account for another sidechain is present but chain account for ownchain is not present', async () => { - const validData1 = { - ...validData, - chainDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: chainAccount }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: chainAccount }, - ], - outboxRootSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: outboxRoot }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: outboxRoot }, - ], - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: channelData }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: channelData }, - ], - chainValidatorsSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 0]), storeValue: validatorsHashInput }, - { storeKey: Buffer.from([0, 0, 1, 0]), storeValue: validatorsHashInput }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).rejects.toThrow(); - }); - - it('should not throw if some chain id corresponding to message fee token id of a channel is not 1 but is corresponding native token id of either chains', async () => { - const validData1 = { - ...validData, - channelDataSubstore: [ - { storeKey: Buffer.from([0, 0, 0, 1]), storeValue: channelData }, - { - storeKey: Buffer.from([0, 0, 1, 0]), - storeValue: { - ...channelData, - messageFeeTokenID: TOKEN_ID_LSK, - }, - }, - ], - }; - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData1); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - await expect(initGenesisStateUtil(context, interopMod.stores)).toResolve(); - }); - - it('should create all the corresponding entries in the interoperability module state for every substore for valid input', async () => { - const encodedAsset = codec.encode(genesisInteroperabilityInternalMethodSchema, validData); - const context = createGenesisBlockContext({ - stateStore, - assets: new BlockAssets([{ module: MODULE_NAME_INTEROPERABILITY, data: encodedAsset }]), - }).createInitGenesisStateContext(); - - await expect(initGenesisStateUtil(context, interopMod.stores)).toResolve(); - - channelDataSubstore = interopMod.stores.get(ChannelDataStore); - chainValidatorsSubstore = interopMod.stores.get(ChainValidatorsStore); - outboxRootSubstore = interopMod.stores.get(OutboxRootStore); - terminatedOutboxSubstore = interopMod.stores.get(TerminatedOutboxStore); - chainDataSubstore = interopMod.stores.get(ChainAccountStore); - terminatedStateSubstore = interopMod.stores.get(TerminatedStateStore); - registeredNamesSubstore = interopMod.stores.get(RegisteredNamesStore); - ownChainDataSubstore = interopMod.stores.get(OwnChainAccountStore); - - for (const data of validData.chainDataSubstore) { - await expect( - chainDataSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.chainValidatorsSubstore) { - await expect( - chainValidatorsSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.outboxRootSubstore) { - await expect( - outboxRootSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.terminatedOutboxSubstore) { - await expect( - terminatedOutboxSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.channelDataSubstore) { - await expect( - channelDataSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.terminatedStateSubstore) { - await expect( - terminatedStateSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.registeredNamesSubstore) { - await expect( - registeredNamesSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - for (const data of validData.ownChainDataSubstore) { - await expect( - ownChainDataSubstore.has(createStoreGetter(stateStore), data.storeKey), - ).resolves.toBeTrue(); - } - }); - }); - describe('validateFormat', () => { const buildCCM = (obj: Partial) => ({ crossChainCommand: obj.crossChainCommand ?? CROSS_CHAIN_COMMAND_NAME_SIDECHAIN_TERMINATED, From e5afb7a9864ea977a6c244d568c5f0be2e5d3019 Mon Sep 17 00:00:00 2001 From: shuse2 Date: Thu, 17 Nov 2022 14:58:51 +0100 Subject: [PATCH 2/2] :nail_care: Fix format --- .../unit/modules/interoperability/internal_method.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/test/unit/modules/interoperability/internal_method.spec.ts b/framework/test/unit/modules/interoperability/internal_method.spec.ts index 9869a94d151..0b8a1319eca 100644 --- a/framework/test/unit/modules/interoperability/internal_method.spec.ts +++ b/framework/test/unit/modules/interoperability/internal_method.spec.ts @@ -350,7 +350,9 @@ describe('Base interoperability internal method', () => { chainIdNew, ); - await expect(terminatedStateSubstore.get(crossChainMessageContext, chainIdNew)).resolves.toStrictEqual({ + await expect( + terminatedStateSubstore.get(crossChainMessageContext, chainIdNew), + ).resolves.toStrictEqual({ stateRoot: EMPTY_HASH, mainchainStateRoot: chainAccount.lastCertificate.stateRoot, initialized: false,