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

Commit

Permalink
Merge branch '8151-update-with-terminatedStateAccounts-and-terminated…
Browse files Browse the repository at this point in the history
…OutboxAccounts' of github.com:LiskHQ/lisk-sdk into 8151-update-genesis-asset-schema-interoperability-sidechain
  • Loading branch information
Phanco committed Mar 21, 2023
2 parents 6dbf6f3 + 0122cb2 commit 1cf6ef9
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 137 deletions.
39 changes: 37 additions & 2 deletions examples/pos-mainchain/config/default/genesis_assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"properties": {
"name": {
"dataType": "string",
"minLength": 1,
"maxLength": 32,
"fieldNumber": 1
},
"lastCertificate": {
Expand Down Expand Up @@ -90,6 +92,7 @@
"type": "object",
"required": [
"inbox",
"outbox",
"partnerChainOutboxRoot",
"messageFeeTokenID",
"minReturnFeePerByte"
Expand All @@ -98,8 +101,41 @@
"inbox": {
"type": "object",
"fieldNumber": 1,
"required": ["size", "root"],
"required": ["appendPath", "size", "root"],
"properties": {
"appendPath": {
"type": "array",
"items": {
"dataType": "bytes",
"length": 32
},
"fieldNumber": 1
},
"size": {
"fieldNumber": 2,
"dataType": "uint32"
},
"root": {
"fieldNumber": 3,
"dataType": "bytes",
"minLength": 32,
"maxLength": 32
}
}
},
"outbox": {
"type": "object",
"fieldNumber": 2,
"required": ["appendPath", "size", "root"],
"properties": {
"appendPath": {
"type": "array",
"items": {
"dataType": "bytes",
"length": 32
},
"fieldNumber": 1
},
"size": {
"fieldNumber": 2,
"dataType": "uint32"
Expand All @@ -112,7 +148,6 @@
}
}
},

"partnerChainOutboxRoot": {
"dataType": "bytes",
"minLength": 32,
Expand Down
160 changes: 120 additions & 40 deletions framework/src/modules/interoperability/mainchain/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,19 @@ import { RecoverStateCommand } from './commands/recover_state';
import { CcmSentFailedEvent } from '../events/ccm_send_fail';
import { InvalidRegistrationSignatureEvent } from '../events/invalid_registration_signature';
import { GenesisBlockExecuteContext } from '../../../state_machine';
import { MODULE_NAME_INTEROPERABILITY, CHAIN_NAME_MAINCHAIN, EMPTY_HASH } from '../constants';
import { getMainchainID, isValidName, validNameCharset } from '../utils';
import {
MODULE_NAME_INTEROPERABILITY,
CHAIN_NAME_MAINCHAIN,
MIN_RETURN_FEE_PER_BYTE_BEDDOWS,
EMPTY_HASH,
} from '../constants';
import {
getMainchainID,
isValidName,
validNameCharset,
getMainchainTokenID,
computeValidatorsHash,
} from '../utils';

export class MainchainInteroperabilityModule extends BaseInteroperabilityModule {
public crossChainMethod = new MainchainCCMethod(this.stores, this.events);
Expand Down Expand Up @@ -278,42 +289,47 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
if (ownChainNonce !== BigInt(0)) {
throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`);
}
} else {
// If chainInfos is non-empty
// ownChainNonce > 0
if (ownChainNonce <= 0) {
throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`);
}
}

// Each entry chainInfo in chainInfos has a unique chainInfo.chainID
const chainIDs = chainInfos.map(info => info.chainID);
if (!bufferArrayUniqueItems(chainIDs)) {
throw new Error(`chainInfos doesn't hold unique chainID.`);
}
// If chainInfos is non-empty
// ownChainNonce > 0
if (ownChainNonce <= 0) {
throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`);
}

// Each entry chainInfo in chainInfos has a unique chainInfo.chainID
const chainIDs = chainInfos.map(info => info.chainID);
if (!bufferArrayUniqueItems(chainIDs)) {
throw new Error(`chainInfos doesn't hold unique chainID.`);
}

// chainInfos is 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.');
}
// 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.');
}
}

this._verifyChainInfos(ctx, chainInfos);
this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts);
this._verifyTerminatedOutboxAccounts(
chainInfos,
terminatedStateAccounts,
terminatedOutboxAccounts,
);
// The entries chainData.name must be pairwise distinct
const chainDataNames = chainInfos.map(info => info.chainData.name);
if (new Set(chainDataNames).size !== chainDataNames.length) {
throw new Error(`chainData.name must be pairwise distinct.`);
}

this._verifyChainInfos(ctx, chainInfos);
this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts);
this._verifyTerminatedOutboxAccounts(
chainInfos,
terminatedStateAccounts,
terminatedOutboxAccounts,
);
}
}

// https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain
private _verifyChainInfos(ctx: GenesisBlockExecuteContext, chainInfos: ChainInfo[]) {
const mainchainID = getMainchainID(ctx.chainID);
const chainDataNames = chainInfos.map(info => info.chainData.name);

// verify root level properties
for (const chainInfo of chainInfos) {
Expand All @@ -329,28 +345,19 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
throw new Error(`chainID[0] doesn't match ${mainchainID[0]}.`);
}

this._verifyChainData(ctx, chainInfo, chainDataNames);
this._verifyChainData(ctx, chainInfo);
this._verifyChannelData(ctx, chainInfo);
this._verifyChainValidators(chainInfo);
}
}

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

// The entries chainData.name must be pairwise distinct
if (new Set(chainDataNames).size !== chainDataNames.length) {
throw new Error(`chainData.name must be pairwise distinct.`);
}

const { chainData } = chainInfo;

// chainData.lastCertificate.timestamp < g.header.timestamp;
if (chainData.lastCertificate.timestamp > ctx.header.timestamp) {
if (chainData.lastCertificate.timestamp >= ctx.header.timestamp) {
throw new Error(`chainData.lastCertificate.timestamp must be less than header.timestamp.`);
}

Expand All @@ -365,6 +372,73 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
}
}

private _verifyChannelData(ctx: GenesisBlockExecuteContext, chainInfo: ChainInfo) {
const mainchainTokenID = getMainchainTokenID(ctx.chainID);

const { channelData } = chainInfo;

// channelData.messageFeeTokenID == Token.getTokenIDLSK();
if (!channelData.messageFeeTokenID.equals(mainchainTokenID)) {
throw new Error(`channelData.messageFeeTokenID is not equal to Token.getTokenIDLSK().`);
}

// channelData.minReturnFeePerByte == MIN_RETURN_FEE_PER_BYTE_LSK.
if (channelData.minReturnFeePerByte !== MIN_RETURN_FEE_PER_BYTE_BEDDOWS) {
throw new Error(
`channelData.minReturnFeePerByte is not equal to MIN_RETURN_FEE_PER_BYTE_BEDDOWS.`,
);
}
}

private _verifyChainValidators(chainInfo: ChainInfo) {
const { chainValidators, chainData } = chainInfo;
const { activeValidators, certificateThreshold } = chainValidators;

// 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.');
}
}

// all blsKey properties must be pairwise distinct
const blsKeys = activeValidators.map(v => v.blsKey);
if (!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) {
throw new Error(`validator.bftWeight must be > 0.`);
}

// let totalWeight be the sum of the bftWeight property of every element in activeValidators.
// Then totalWeight has to be less than or equal to MAX_UINT64
const totalWeight = activeValidators.reduce(
(accumulator, v) => accumulator + v.bftWeight,
BigInt(0),
);
if (totalWeight > MAX_UINT64) {
throw new Error(`totalWeight has to be less than or equal to MAX_UINT64.`);
}

// check that totalWeight//3 + 1 <= certificateThreshold <= totalWeight, where // indicates integer division
if (
totalWeight / BigInt(3) + BigInt(1) > certificateThreshold ||
certificateThreshold > totalWeight
) {
throw new Error('Invalid certificateThreshold input.');
}

// check that the corresponding validatorsHash stored in chainInfo.chainData.lastCertificate.validatorsHash
// matches with the value computed from activeValidators and certificateThreshold
const { validatorsHash } = chainData.lastCertificate;
if (!validatorsHash.equals(computeValidatorsHash(activeValidators, certificateThreshold))) {
throw new Error('Invalid validatorsHash from chainData.lastCertificate.');
}
}

// https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain
private _verifyTerminatedStateAccounts(
chainInfos: ChainInfo[],
Expand All @@ -373,7 +447,13 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
for (const chainInfo of chainInfos) {
const { chainID } = chainInfo;

if (terminatedStateAccounts.filter(a => a.chainID.equals(chainID)).length > 0) {
const filteredTerminatedStateAccounts = terminatedStateAccounts.filter(a =>
a.chainID.equals(chainID),
);
if (terminatedStateAccounts.length > 0 && filteredTerminatedStateAccounts.length === 0) {
throw new Error('there can not be a terminated account if there is no chain account');
}
if (filteredTerminatedStateAccounts.length > 0) {
// 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) {
Expand Down
45 changes: 6 additions & 39 deletions framework/src/modules/interoperability/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const ccmSchema = {
},
};

const chainValidators = {
const activeChainValidatorsSchema = {
type: 'array',
items: {
type: 'object',
Expand All @@ -109,7 +109,7 @@ const chainValidators = {
},
},
minItems: 1,
maxItems: MAX_NUM_VALIDATORS,
// maxItems: MAX_NUM_VALIDATORS,
};

export const sidechainRegParams = {
Expand All @@ -130,8 +130,9 @@ export const sidechainRegParams = {
maxLength: MAX_CHAIN_NAME_LENGTH,
},
sidechainValidators: {
...chainValidators,
...activeChainValidatorsSchema,
fieldNumber: 3,
maxItems: MAX_NUM_VALIDATORS,
},
sidechainCertificateThreshold: {
dataType: 'uint64',
Expand Down Expand Up @@ -165,25 +166,8 @@ export const mainchainRegParams = {
maxLength: MAX_CHAIN_NAME_LENGTH,
},
mainchainValidators: {
type: 'array',
...activeChainValidatorsSchema,
fieldNumber: 3,
items: {
type: 'object',
required: ['blsKey', 'bftWeight'],
properties: {
blsKey: {
dataType: 'bytes',
fieldNumber: 1,
minLength: BLS_PUBLIC_KEY_LENGTH,
maxLength: BLS_PUBLIC_KEY_LENGTH,
},
bftWeight: {
dataType: 'uint64',
fieldNumber: 2,
},
},
},
minItems: 1,
maxItems: NUMBER_ACTIVE_VALIDATORS_MAINCHAIN,
},
mainchainCertificateThreshold: {
Expand Down Expand Up @@ -460,25 +444,8 @@ export const registrationSignatureMessageSchema = {
maxLength: MAX_CHAIN_NAME_LENGTH,
},
mainchainValidators: {
type: 'array',
...activeChainValidatorsSchema,
fieldNumber: 3,
items: {
type: 'object',
required: ['blsKey', 'bftWeight'],
properties: {
blsKey: {
dataType: 'bytes',
fieldNumber: 1,
minLength: BLS_PUBLIC_KEY_LENGTH,
maxLength: BLS_PUBLIC_KEY_LENGTH,
},
bftWeight: {
dataType: 'uint64',
fieldNumber: 2,
},
},
},
minItems: 1,
maxItems: NUMBER_ACTIVE_VALIDATORS_MAINCHAIN,
},
mainchainCertificateThreshold: {
Expand Down
10 changes: 9 additions & 1 deletion framework/src/modules/interoperability/stores/chain_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
*/
import { utils } from '@liskhq/lisk-cryptography';
import { BaseStore, ImmutableStoreGetter } from '../../base_store';
import { HASH_LENGTH, MAX_UINT32, STORE_PREFIX } from '../constants';
import {
HASH_LENGTH,
MAX_CHAIN_NAME_LENGTH,
MAX_UINT32,
MIN_CHAIN_NAME_LENGTH,
STORE_PREFIX,
} from '../constants';

// Chain status
export const enum ChainStatus {
Expand Down Expand Up @@ -48,6 +54,8 @@ const chainDataJSONSchema = {
properties: {
name: {
dataType: 'string',
minLength: MIN_CHAIN_NAME_LENGTH,
maxLength: MAX_CHAIN_NAME_LENGTH,
fieldNumber: 1,
},
lastCertificate: {
Expand Down
Loading

0 comments on commit 1cf6ef9

Please sign in to comment.