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

Implement sidechain registration verification and execution - Closes #7033 #7116

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2a5471a
Update schema.ts
mitsuaki-u Apr 21, 2022
8422991
Update types.ts
mitsuaki-u Apr 21, 2022
bbc71d0
Add name validation util functions
mitsuaki-u Apr 21, 2022
f134e6e
Implement sidechain registration verification
mitsuaki-u Apr 21, 2022
5b34a57
Create sidechain_registration.spec.ts
mitsuaki-u Apr 21, 2022
249f429
Update sidechain_registration.spec.ts
mitsuaki-u Apr 22, 2022
7ff95c0
Update sidechain_registration.spec.ts
mitsuaki-u Apr 25, 2022
583b3c6
Create base_interoperability_command.ts
mitsuaki-u Apr 28, 2022
85cd7b9
Add empty hash constant
mitsuaki-u Apr 28, 2022
e292336
Add schemas
mitsuaki-u Apr 28, 2022
0f915d8
Add hash hash input type
mitsuaki-u Apr 28, 2022
d77acf6
Implement computeValidatorsHash util
mitsuaki-u Apr 28, 2022
8b40465
Update types.ts
mitsuaki-u Apr 28, 2022
fae0901
Implement sidechain registration execution
mitsuaki-u Apr 28, 2022
cd9b22d
Add unit tests for sidechain registration execution
mitsuaki-u Apr 28, 2022
a4aa955
Update utils.ts
mitsuaki-u Apr 28, 2022
9e9861c
Update sidechain_registration.spec.ts
mitsuaki-u Apr 28, 2022
0b01d9b
Update sidechain_registration.ts
mitsuaki-u Apr 28, 2022
5c21bb0
Update sidechain_registration.spec.ts
mitsuaki-u Apr 28, 2022
98754f2
Update types.ts
mitsuaki-u Apr 28, 2022
194dd75
Update utils.ts
mitsuaki-u Apr 28, 2022
d70e328
Update transaction_context.ts
mitsuaki-u Apr 28, 2022
77cbcaf
Update sidechain_registration.spec.ts
mitsuaki-u Apr 28, 2022
e8ea068
Update sidechain_registration.spec.ts
mitsuaki-u Apr 28, 2022
84a7975
Update schema.ts
mitsuaki-u Apr 29, 2022
3a09dbe
Update schema.ts
mitsuaki-u Apr 29, 2022
9f6ddc2
Update sidechain_registration.ts
mitsuaki-u Apr 29, 2022
d58407c
Update schema.ts
mitsuaki-u Apr 29, 2022
af36a57
Update sidechain_registration.spec.ts
mitsuaki-u Apr 29, 2022
61693c5
Update sidechain_registration.ts
mitsuaki-u May 4, 2022
85769da
Update utils.ts
mitsuaki-u May 4, 2022
f40eb0c
Update sidechain_registration.spec.ts
mitsuaki-u May 4, 2022
78c2c37
Update sidechain_registration.ts
mitsuaki-u May 5, 2022
b2692d6
Update schema.ts
mitsuaki-u May 5, 2022
ff8711f
Update utils.ts
mitsuaki-u May 5, 2022
68cc1f8
Update sidechain_registration.spec.ts
mitsuaki-u May 5, 2022
039bf4e
Address feedback
mitsuaki-u May 6, 2022
274302e
Update schema.ts
mitsuaki-u May 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { BaseCommand } from '../base_command';
import { BaseInteroperabilityStore } from './base_interoperability_store';
import { BaseInteroperableAPI } from './base_interoperable_api';
import { BaseCCCommand } from './base_cc_command';
import { StoreCallback } from './types';

export abstract class BaseInteroperabilityCommand extends BaseCommand {
protected readonly interoperableCCAPIs = new Map<number, BaseInteroperableAPI>();
protected readonly ccCommands = new Map<number, BaseCCCommand[]>();

public constructor(
moduleID: number,
interoperableCCAPIs: Map<number, BaseInteroperableAPI>,
ccCommands: Map<number, BaseCCCommand[]>,
) {
super(moduleID);
this.interoperableCCAPIs = interoperableCCAPIs;
this.ccCommands = ccCommands;
}

protected abstract getInteroperabilityStore(getStore: StoreCallback): BaseInteroperabilityStore;
}
3 changes: 3 additions & 0 deletions framework/src/modules/interoperability/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { hash } from '@liskhq/lisk-cryptography';

export const MODULE_ID_INTEROPERABILITY = 64;
export const MODULE_NAME_INTEROPERABILITY = 'interoperability';

Expand All @@ -23,6 +25,7 @@ export const LIVENESS_LIMIT = 2592000; // 30*24*3600
export const MAX_CCM_SIZE = 10240;
export const EMPTY_FEE_ADDRESS = Buffer.alloc(0);
export const EMPTY_BYTES = Buffer.alloc(0);
export const EMPTY_HASH = hash(EMPTY_BYTES);
export const REGISTRATION_FEE = BigInt(1000000000);
export const MAX_NUM_VALIDATORS = 199;
export const MAX_LENGTH_NAME = 40;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,283 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { BaseCommand } from '../../../base_command';
import { COMMAND_ID_SIDECHAIN_REG } from '../../constants';
import { sidechainRegParams } from '../../schema';
import { VerificationResult } from '../../../../node/state_machine';
import { codec } from '@liskhq/lisk-codec';
import { hash, intToBuffer } from '@liskhq/lisk-cryptography';
import { validator, LiskValidationError } from '@liskhq/lisk-validator';
import { MainchainInteroperabilityStore } from '../store';
import { BaseInteroperabilityCommand } from '../../base_interoperability_command';
import {
CHAIN_REGISTERED,
COMMAND_ID_SIDECHAIN_REG,
CROSS_CHAIN_COMMAND_ID_REGISTRATION,
EMPTY_HASH,
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_CHAIN_DATA,
STORE_PREFIX_REGISTERED_NETWORK_IDS,
STORE_PREFIX_REGISTERED_NAMES,
MAX_UINT32,
MAX_UINT64,
STORE_PREFIX_CHANNEL_DATA,
STORE_PREFIX_CHAIN_VALIDATORS,
STORE_PREFIX_OUTBOX_ROOT,
MAINCHAIN_ID,
CCM_STATUS_OK,
EMPTY_FEE_ADDRESS,
} from '../../constants';
import {
chainAccountSchema,
chainIDSchema,
channelSchema,
outboxRootSchema,
registrationCCMParamsSchema,
sidechainRegParams,
validatorsSchema,
} from '../../schema';
import { SidechainRegistrationParams, StoreCallback } from '../../types';
import { computeValidatorsHash, isValidName } from '../../utils';
import {
CommandVerifyContext,
VerificationResult,
VerifyStatus,
CommandExecuteContext,
} from '../../../../node/state_machine/types';

export class SidechainRegistrationCommand extends BaseCommand {
export class SidechainRegistrationCommand extends BaseInteroperabilityCommand {
public id = COMMAND_ID_SIDECHAIN_REG;
public name = 'sidechainRegistration';
public schema = sidechainRegParams;

// TODO
// eslint-disable-next-line @typescript-eslint/require-await
public async verify(): Promise<VerificationResult> {
throw new Error('Method not implemented.');
public async verify(
context: CommandVerifyContext<SidechainRegistrationParams>,
): Promise<VerificationResult> {
const {
transaction,
params: { certificateThreshold, initValidators, genesisBlockID, name },
} = context;
const errors = validator.validate(sidechainRegParams, context.params);
mitsuaki-u marked this conversation as resolved.
Show resolved Hide resolved

if (errors.length > 0) {
return {
status: VerifyStatus.FAIL,
error: new LiskValidationError(errors),
};
}

// The sidechain name property has to contain only characters from the set [a-z0-9!@$&_.]
if (!isValidName(name)) {
return {
status: VerifyStatus.FAIL,
error: new Error(`Sidechain name is in an unsupported format: ${name}`),
};
}

// The sidechain name has to be unique with respect to the set of already registered sidechain names in the blockchain state
const nameSubstore = context.getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_REGISTERED_NAMES,
);
const nameExists = await nameSubstore.has(Buffer.from(name, 'utf8'));

if (nameExists) {
return {
status: VerifyStatus.FAIL,
error: new Error('Name substore must not have an entry for the store key name'),
};
}

const networkID = hash(Buffer.concat([genesisBlockID, transaction.senderAddress]));

// networkId has to be unique with respect to the set of already registered sidechain network IDs in the blockchain state.
const networkIDSubstore = context.getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_REGISTERED_NETWORK_IDS,
);
const networkIDExists = await networkIDSubstore.has(networkID);

if (networkIDExists) {
return {
status: VerifyStatus.FAIL,
error: new Error('Network ID substore must not have an entry for the store key networkID'),
};
}

let totalBftWeight = BigInt(0);
for (let i = 0; i < initValidators.length; i += 1) {
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
const currentValidator = initValidators[i];

// The blsKeys must be lexicographically ordered and unique within the array.
if (
mitsuaki-u marked this conversation as resolved.
Show resolved Hide resolved
initValidators[i + 1] &&
currentValidator.blsKey.compare(initValidators[i + 1].blsKey) > -1
) {
return {
status: VerifyStatus.FAIL,
error: new Error('Validators blsKeys must be unique and lexicographically ordered'),
};
}

if (currentValidator.bftWeight <= BigInt(0)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Validator bft weight must be greater than 0'),
};
}

totalBftWeight += currentValidator.bftWeight;
}

if (totalBftWeight > MAX_UINT64) {
return {
status: VerifyStatus.FAIL,
error: new Error(`Validator bft weight must not exceed ${MAX_UINT64}`),
};
}

// Minimum certificateThreshold value: floor(1/3 * totalWeight) + 1
// Note: BigInt truncates to floor
if (certificateThreshold < totalBftWeight / BigInt(3) + BigInt(1)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Certificate threshold below minimum bft weight '),
};
}

// Maximum certificateThreshold value: total bft weight
if (certificateThreshold > totalBftWeight) {
return {
status: VerifyStatus.FAIL,
error: new Error('Certificate threshold above maximum bft weight'),
};
}

return {
status: VerifyStatus.OK,
};
}

public async execute(context: CommandExecuteContext<SidechainRegistrationParams>): Promise<void> {
const {
header,
transaction,
params: { certificateThreshold, initValidators, genesisBlockID, name },
getStore,
} = context;

const networkID = hash(Buffer.concat([genesisBlockID, transaction.senderAddress]));

// Add an entry in the chain substore
const chainSubstore = getStore(MODULE_ID_INTEROPERABILITY, STORE_PREFIX_CHAIN_DATA);

// Find the latest chainID from db
const start = intToBuffer(0, 4);
const end = intToBuffer(MAX_UINT32, 4);
const chainIDs = await chainSubstore.iterate({ start, end, limit: 1, reverse: true });
if (!chainIDs.length) {
throw new Error('No existing entries found in chain store');
}
const chainID = chainIDs[0].key.readUInt32BE(0) + 1;
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
const chainIDBuffer = intToBuffer(chainID, 4);

await chainSubstore.setWithSchema(
chainIDBuffer,
{
name,
networkID,
lastCertificate: {
height: 0,
timestamp: 0,
stateRoot: EMPTY_HASH,
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
validatorsHash: computeValidatorsHash(initValidators, certificateThreshold),
},
status: CHAIN_REGISTERED,
},
chainAccountSchema,
);

// Add an entry in the channel substore
const channelSubstore = getStore(MODULE_ID_INTEROPERABILITY, STORE_PREFIX_CHANNEL_DATA);
await channelSubstore.setWithSchema(
chainIDBuffer,
{
inbox: { root: EMPTY_HASH, appendPath: [], size: 0 },
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
outbox: { root: EMPTY_HASH, appendPath: [], size: 0 },
partnerChainOutboxRoot: EMPTY_HASH,
messageFeeTokenID: { chainID: 1, localID: 0 },
},
channelSchema,
);

// sendInternal registration CCM
const interoperabilityStore = this.getInteroperabilityStore(getStore);

const encodedParams = codec.encode(registrationCCMParamsSchema, {
networkID,
name,
messageFeeTokenID: { chainID: MAINCHAIN_ID, localID: 0 },
});
const ccm = {
nonce: BigInt(0),
moduleID: MODULE_ID_INTEROPERABILITY,
crossChainCommandID: CROSS_CHAIN_COMMAND_ID_REGISTRATION,
sendingChainID: MAINCHAIN_ID,
receivingChainID: chainID,
fee: BigInt(0),
status: CCM_STATUS_OK,
params: encodedParams,
};

await interoperabilityStore.sendInternal({
moduleID: MODULE_ID_INTEROPERABILITY,
crossChainCommandID: CROSS_CHAIN_COMMAND_ID_REGISTRATION,
receivingChainID: chainID,
fee: BigInt(0),
status: CCM_STATUS_OK,
params: encodedParams,
timestamp: header.timestamp,
beforeSendContext: { ...context, ccm, feeAddress: EMPTY_FEE_ADDRESS },
});

// Add an entry in the chain validators substore
const chainValidatorsSubstore = getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_CHAIN_VALIDATORS,
);
await chainValidatorsSubstore.setWithSchema(
chainIDBuffer,
{ sidechainValidators: { activeValidators: initValidators, certificateThreshold } },
validatorsSchema,
);

// Add an entry in the outbox root substore
const outboxRootSubstore = getStore(MODULE_ID_INTEROPERABILITY, STORE_PREFIX_OUTBOX_ROOT);
await outboxRootSubstore.setWithSchema(chainIDBuffer, { root: EMPTY_HASH }, outboxRootSchema);

// Add an entry in the registered names substore
const registeredNamesSubstore = getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_REGISTERED_NAMES,
);
await registeredNamesSubstore.setWithSchema(
Buffer.from(name, 'utf-8'),
{ id: chainIDBuffer },
// Note: Uses chainIDSchema
chainIDSchema,
);

// Add an entry in the registered network IDs substore
const registeredNetworkIDsSubstore = getStore(
MODULE_ID_INTEROPERABILITY,
STORE_PREFIX_REGISTERED_NETWORK_IDS,
);
await registeredNetworkIDsSubstore.setWithSchema(
networkID,
{ id: chainIDBuffer },
// Note: Uses chainIDSchema
chainIDSchema,
);
}

// TODO
// eslint-disable-next-line @typescript-eslint/require-await
public async execute(): Promise<void> {
throw new Error('Method not implemented.');
protected getInteroperabilityStore(getStore: StoreCallback): MainchainInteroperabilityStore {
return new MainchainInteroperabilityStore(this.moduleID, getStore, this.interoperableCCAPIs);
}
}
Loading