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

_verifyTerminatedStateAccountsCommon doesn't need to be inside loop #9079

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,72 @@
"module": "interoperability",
"data": {
"ownChainName": "lisk_mainchain",
"ownChainNonce": 0,
"chainInfos": [],
"terminatedStateAccounts": [],
"terminatedOutboxAccounts": []
"ownChainNonce": "123",
"chainInfos": [
{
"chainID": "04123456",
"chainData": {
"name": "dummy",
"lastCertificate": {
"height": 567467,
"timestamp": 1000,
"stateRoot": "0000000000000000000000000000000000000000000000000000000000000000",
"validatorsHash": "a5a053d50182ea0c33bc03594cf4760f11d67cdba407d16bc0512fabb468253a"
},
"status": 2
},
"channelData": {
"inbox": {
"appendPath": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
],
"size": 18,
"root": "9e37ffe87f08f6b7952d82acdb3376046792533ebf2ba8908dda3ebc813dac9a"
},
"outbox": {
"appendPath": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
],
"size": 18,
"root": "221a0c844883022e3a54e78eb5dcdc2ed8faa85f648c40659cf5fd2054f36500"
},
"partnerChainOutboxRoot": "851faa36d87411d625fb4416c33c6a44795cb84155637f9991fcef06d1de7155",
"messageFeeTokenID": "0400000000000000",
"minReturnFeePerByte": "1000"
},
"chainValidators": {
"activeValidators": [
{
"blsKey": "3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835",
"bftWeight": "10"
}
],
"certificateThreshold": "10"
}
}
],
"terminatedStateAccounts": [
{
"chainID": "04123456",
"terminatedStateAccount": {
"stateRoot": "0000000000000000000000000000000000000000000000000000000000000000",
"mainchainStateRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"initialized": true
}
}
],
"terminatedOutboxAccounts": [
{
"chainID": "04123456",
"terminatedOutboxAccount": {
"outboxRoot": "0aed892d544980f5b806dbce4bcb65517acf57a7af012c55c0b2f80b188fa290",
"outboxSize": 1,
"partnerChainInboxSize": 1
}
}
]
},
"schema": {
"$id": "/interoperability/module/genesis",
Expand Down Expand Up @@ -916,11 +978,17 @@
"totalSupply": "10300000000000000"
}
],
"escrowSubstore": [],
"escrowSubstore": [
{
"escrowChainID": "04123456",
"tokenID": "0400000000000000",
"amount": "0"
}
],
"supportedTokensSubstore": [
{
"chainID": "",
"supportedTokenIDs": []
"chainID": "04123456",
"supportedTokenIDs": ["0412345600000000"]
}
]
},
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@ 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 {
ChainInfo,
GenesisInteroperability,
OwnChainAccount,
TerminatedStateAccountWithChainID,
} from './types';
import { ChainInfo, GenesisInteroperability, OwnChainAccount } from './types';
import { computeValidatorsHash, getTokenIDLSK } from './utils';
import { genesisInteroperabilitySchema } from './schemas';
import { CcmProcessedEvent } from './events/ccm_processed';
Expand Down Expand Up @@ -133,21 +128,18 @@ export abstract class BaseInteroperabilityModule extends BaseInteroperableModule
}

// activeValidators must be ordered lexicographically by blsKey property
const sortedByBlsKeys = [...activeValidators].sort((a, b) => a.blsKey.compare(b.blsKey));
for (let i = 0; i < activeValidators.length; i += 1) {
if (!activeValidators[i].blsKey.equals(sortedByBlsKeys[i].blsKey)) {
throw new Error('activeValidators must be ordered lexicographically by blsKey property.');
}
const blsKeys = activeValidators.map(v => v.blsKey);
if (!objectUtils.isBufferArrayOrdered(blsKeys)) {
throw new Error('activeValidators must be ordered lexicographically by blsKey property.');
}

// all blsKey properties must be pairwise distinct
const blsKeys = activeValidators.map(v => v.blsKey);
if (!objectUtils.bufferArrayUniqueItems(blsKeys)) {
throw new Error(`All blsKey properties must be pairwise distinct.`);
}

// for each validator in activeValidators, validator.bftWeight > 0 must hold
if (activeValidators.filter(v => v.bftWeight <= 0).length > 0) {
if (activeValidators.filter(v => v.bftWeight <= BigInt(0)).length > 0) {
throw new Error(`validator.bftWeight must be > 0.`);
}

Expand Down Expand Up @@ -189,28 +181,15 @@ export abstract class BaseInteroperabilityModule extends BaseInteroperableModule
}
}

protected _verifyTerminatedStateAccountsCommon(
terminatedStateAccounts: TerminatedStateAccountWithChainID[],
mainchainID: Buffer,
) {
protected _verifyTerminatedStateAccountsIDs(chainIDs: Buffer[]) {
// Each entry stateAccount in terminatedStateAccounts has a unique stateAccount.chainID
const chainIDs = terminatedStateAccounts.map(a => a.chainID);
if (!objectUtils.bufferArrayUniqueItems(chainIDs)) {
throw new Error(`terminatedStateAccounts don't hold unique chainID.`);
}

// terminatedStateAccounts is ordered lexicographically by stateAccount.chainID
const sortedByChainID = [...terminatedStateAccounts].sort((a, b) =>
a.chainID.compare(b.chainID),
);

for (let i = 0; i < terminatedStateAccounts.length; i += 1) {
const stateAccountWithChainID = terminatedStateAccounts[i];
if (!stateAccountWithChainID.chainID.equals(sortedByChainID[i].chainID)) {
throw new Error('terminatedStateAccounts must be ordered lexicographically by chainID.');
}

this._verifyChainID(stateAccountWithChainID.chainID, mainchainID, 'stateAccount.');
if (!objectUtils.isBufferArrayOrdered(chainIDs)) {
throw new Error('terminatedStateAccounts must be ordered lexicographically by chainID.');
}
}

Expand Down
112 changes: 55 additions & 57 deletions framework/src/modules/interoperability/mainchain/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,11 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
// If chainInfos is non-empty, ownChainNonce > 0
if (chainInfos.length === 0 && ownChainNonce !== BigInt(0)) {
throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`);
} else if (chainInfos.length !== 0 && ownChainNonce <= 0) {
} else if (chainInfos.length !== 0 && ownChainNonce <= BigInt(0)) {
throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`);
}

this._verifyChainInfos(ctx, chainInfos);
this._verifyChainInfos(ctx, chainInfos, terminatedStateAccounts);
this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts, mainchainID);
this._verifyTerminatedOutboxAccounts(
chainInfos,
Expand All @@ -281,19 +281,20 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
}

// https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain
private _verifyChainInfos(ctx: GenesisBlockExecuteContext, chainInfos: ChainInfo[]) {
private _verifyChainInfos(
ctx: GenesisBlockExecuteContext,
chainInfos: ChainInfo[],
terminatedStateAccounts: TerminatedStateAccountWithChainID[],
) {
// Each entry chainInfo in chainInfos has a unique chainInfo.chainID
const chainIDs = chainInfos.map(info => info.chainID);
if (!objectUtils.bufferArrayUniqueItems(chainIDs)) {
throw new Error(`chainInfos doesn't hold unique chainID.`);
}

// chainInfos should be ordered lexicographically by chainInfo.chainID
const sortedByChainID = [...chainInfos].sort((a, b) => a.chainID.compare(b.chainID));
for (let i = 0; i < chainInfos.length; i += 1) {
if (!chainInfos[i].chainID.equals(sortedByChainID[i].chainID)) {
throw new Error('chainInfos is not ordered lexicographically by chainID.');
}
if (!objectUtils.isBufferArrayOrdered(chainIDs)) {
throw new Error('chainInfos is not ordered lexicographically by chainID.');
}

// The entries chainData.name must be pairwise distinct
Expand All @@ -307,13 +308,17 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
// verify root level properties
for (const chainInfo of chainInfos) {
this._verifyChainID(chainInfo.chainID, mainchainID, 'chainInfo.');
this._verifyChainData(ctx, chainInfo);
this._verifyChainData(ctx, chainInfo, terminatedStateAccounts);
this._verifyChannelData(ctx, chainInfo);
this._verifyChainValidators(chainInfo);
}
}

private _verifyChainData(ctx: GenesisBlockExecuteContext, chainInfo: ChainInfo) {
private _verifyChainData(
ctx: GenesisBlockExecuteContext,
chainInfo: ChainInfo,
terminatedStateAccounts: TerminatedStateAccountWithChainID[],
) {
const validStatuses = [ChainStatus.REGISTERED, ChainStatus.ACTIVE, ChainStatus.TERMINATED];

const { chainData } = chainInfo;
Expand All @@ -332,6 +337,17 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
if (!validStatuses.includes(chainData.status)) {
throw new Error(`chainData.status must be one of ${validStatuses.join(', ')}`);
}

if (chainData.status === ChainStatus.TERMINATED) {
const accountWithChainID = terminatedStateAccounts.find(accountWithChainIDTemp =>
accountWithChainIDTemp.chainID.equals(chainInfo.chainID),
);
if (!accountWithChainID) {
throw new Error(
'For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.',
);
}
}
}

// https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain
Expand All @@ -340,10 +356,14 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
terminatedStateAccounts: TerminatedStateAccountWithChainID[],
mainchainID: Buffer,
) {
this._verifyTerminatedStateAccountsIDs(terminatedStateAccounts.map(a => a.chainID));

// Sanity check to fulfill if-and-only-if situation
for (const account of terminatedStateAccounts) {
for (const terminatedStateAccountWithChainID of terminatedStateAccounts) {
this._verifyChainID(terminatedStateAccountWithChainID.chainID, mainchainID, 'stateAccount.');

const correspondingChainInfo = chainInfos.find(chainInfo =>
chainInfo.chainID.equals(account.chainID),
chainInfo.chainID.equals(terminatedStateAccountWithChainID.chainID),
);
if (
!correspondingChainInfo ||
Expand All @@ -353,46 +373,29 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
'For each terminatedStateAccount there should be a corresponding chainInfo at TERMINATED state.',
);
}
}

for (const chainInfo of chainInfos) {
// For each entry chainInfo in chainInfos, chainInfo.chainData.status == CHAIN_STATUS_TERMINATED
// if and only if a corresponding entry (i.e., with chainID == chainInfo.chainID) exists in terminatedStateAccounts.
if (chainInfo.chainData.status === ChainStatus.TERMINATED) {
const terminatedAccount = terminatedStateAccounts.find(tAccount =>
tAccount.chainID.equals(chainInfo.chainID),
const stateAccount = terminatedStateAccountWithChainID.terminatedStateAccount;
// For each entry stateAccount in terminatedStateAccounts holds
// stateAccount.stateRoot == chainData.lastCertificate.stateRoot,
// stateAccount.mainchainStateRoot == EMPTY_HASH, and
// stateAccount.initialized == True.
// Here chainData is the corresponding entry (i.e., with chainID == stateAccount.chainID) in chainInfos.
if (
!stateAccount.stateRoot.equals(correspondingChainInfo.chainData.lastCertificate.stateRoot)
) {
throw new Error(
"stateAccount.stateRoot doesn't match chainInfo.chainData.lastCertificate.stateRoot.",
);
}

if (!stateAccount.mainchainStateRoot.equals(EMPTY_HASH)) {
throw new Error(
`stateAccount.mainchainStateRoot is not equal to ${EMPTY_HASH.toString('hex')}.`,
);
if (!terminatedAccount) {
throw new Error(
'For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.',
);
}

this._verifyTerminatedStateAccountsCommon(terminatedStateAccounts, mainchainID);

// For each entry stateAccount in terminatedStateAccounts holds
// stateAccount.stateRoot == chainData.lastCertificate.stateRoot,
// stateAccount.mainchainStateRoot == EMPTY_HASH, and
// stateAccount.initialized == True.
// Here chainData is the corresponding entry (i.e., with chainID == stateAccount.chainID) in chainInfos.
const stateAccount = terminatedAccount.terminatedStateAccount;
if (stateAccount) {
if (!stateAccount.stateRoot.equals(chainInfo.chainData.lastCertificate.stateRoot)) {
throw new Error(
"stateAccount.stateRoot doesn't match chainInfo.chainData.lastCertificate.stateRoot.",
);
}

if (!stateAccount.mainchainStateRoot.equals(EMPTY_HASH)) {
throw new Error(
`stateAccount.mainchainStateRoot is not equal to ${EMPTY_HASH.toString('hex')}.`,
);
}

if (!stateAccount.initialized) {
throw new Error('stateAccount is not initialized.');
}
}
}

if (!stateAccount.initialized) {
throw new Error('stateAccount is not initialized.');
}
}
}
Expand All @@ -410,13 +413,8 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
}

// terminatedOutboxAccounts is ordered lexicographically by outboxAccount.chainID
const sortedByChainID = [...terminatedOutboxAccounts].sort((a, b) =>
a.chainID.compare(b.chainID),
);
for (let i = 0; i < terminatedOutboxAccounts.length; i += 1) {
if (!terminatedOutboxAccounts[i].chainID.equals(sortedByChainID[i].chainID)) {
throw new Error('terminatedOutboxAccounts must be ordered lexicographically by chainID.');
}
if (!objectUtils.isBufferArrayOrdered(chainIDs)) {
throw new Error('terminatedOutboxAccounts must be ordered lexicographically by chainID.');
}

// Furthermore, an entry outboxAccount in terminatedOutboxAccounts must have a corresponding entry
Expand Down
6 changes: 4 additions & 2 deletions framework/src/modules/interoperability/sidechain/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export class SidechainInteroperabilityModule extends BaseInteroperabilityModule
}
// mainchainInfo.chainID == getMainchainID();
const mainchainInfo = chainInfos[0];
const mainchainID = getMainchainID(mainchainInfo.chainID);
const mainchainID = getMainchainID(ctx.chainID);
if (!mainchainInfo.chainID.equals(mainchainID)) {
throw new Error(`mainchainInfo.chainID must be equal to ${mainchainID.toString('hex')}.`);
}
Expand Down Expand Up @@ -320,9 +320,11 @@ export class SidechainInteroperabilityModule extends BaseInteroperabilityModule
terminatedStateAccounts: TerminatedStateAccountWithChainID[],
mainchainID: Buffer,
) {
this._verifyTerminatedStateAccountsCommon(terminatedStateAccounts, mainchainID);
this._verifyTerminatedStateAccountsIDs(terminatedStateAccounts.map(a => a.chainID));

for (const stateAccount of terminatedStateAccounts) {
this._verifyChainID(stateAccount.chainID, mainchainID, 'stateAccount.');

// and stateAccount.chainID != OWN_CHAIN_ID.
if (stateAccount.chainID.equals(ctx.chainID)) {
throw new Error(`stateAccount.chainID must not be equal to OWN_CHAIN_ID.`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ must NOT have more than ${MAX_NUM_VALIDATORS} items`,
});
});

describe('_verifyTerminatedStateAccountsCommon', () => {
describe('_verifyTerminatedStateAccountsIDs', () => {
certificateThreshold = BigInt(10);
const validChainInfos = [
{
Expand Down
Loading