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

Enhance interop example scripts #9109

Merged
merged 15 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 50 additions & 17 deletions examples/interop/common/mainchain_registration.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { codec, cryptography, apiClient, Transaction } from 'lisk-sdk';
import {
codec,
cryptography,
apiClient,
Transaction,
registrationSignatureMessageSchema,
mainchainRegParams as mainchainRegParamsSchema,
MESSAGE_TAG_CHAIN_REG,
MODULE_NAME_INTEROPERABILITY,
} from 'lisk-framework';
import { COMMAND_NAME_MAINCHAIN_REG } from 'lisk-framework/dist-node/modules/interoperability/constants';

export const registerMainchain = async (
num: string,
sidechainDevValidators: any[],
sidechainValidatorsKeys: any[],
) => {
} from 'lisk-sdk';

/**
* Registers the mainchain to a specific sidechain node.
*
* @example
* ```js
* // Update path to point to the dev-validators.json file of the sidechain which shall be registered on the mainchain
import { keys as sidechainDevValidators } from '../default/dev-validators.json';

* (async () => {
* await registerMainchain("lisk-core","my-lisk-app",sidechainDevValidators);
*})();
* ```
*
* @param mc mainchain alias of the mainchain to be registered.
* @param sc sidechain alias of the sidechain, where the mainchain shall be registered.
* @param sidechainDevValidators the `key` property of the `dev-validators.json` file.
* Includes all keys of the sidechain validators to create the aggregated signature.
*/

export const registerMainchain = async (mc: string, sc: string, sidechainDevValidators: any[]) => {
const { bls, address } = cryptography;

const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-${num}`);
const sidechainClient = await apiClient.createIPCClient(`~/.lisk/pos-sidechain-example-${num}`);
// Connect to the mainchain node
const mainchainClient = await apiClient.createIPCClient(`~/.lisk/${mc}`);
// Connect to the sidechain node
const sidechainClient = await apiClient.createIPCClient(`~/.lisk/${sc}`);

// Get node info data from sidechain and mainchain
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');
const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo');

Expand All @@ -28,15 +48,17 @@ export const registerMainchain = async (
height: mainchainNodeInfo.height,
});

// Sort validator list lexicographically after their BLS key
const paramsJSON = {
ownChainID: sidechainNodeInfo.chainID,
ownName: `sidechain_example_${num}`,
ownName: sc.replace(/-/g, '_'),
mainchainValidators: (mainchainActiveValidators as { blsKey: string; bftWeight: string }[])
.map(v => ({ blsKey: v.blsKey, bftWeight: v.bftWeight }))
.sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex'))),
mainchainCertificateThreshold,
};

// Define parameters for the mainchain registration
const params = {
ownChainID: Buffer.from(paramsJSON.ownChainID as string, 'hex'),
ownName: paramsJSON.ownName,
Expand All @@ -47,17 +69,19 @@ export const registerMainchain = async (
mainchainCertificateThreshold: paramsJSON.mainchainCertificateThreshold,
};

// Encode parameters
const message = codec.encode(registrationSignatureMessageSchema, params);

// Get active validators from sidechainChain
// Get active validators from sidechain
const { validators: sidechainActiveValidators } = await sidechainClient.invoke(
'consensus_getBFTParameters',
{ height: sidechainNodeInfo.height },
);

// Add validator private keys to the sidechain validator list
const activeValidatorsWithPrivateKey: { blsPublicKey: Buffer; blsPrivateKey: Buffer }[] = [];
for (const v of sidechainActiveValidators as { blsKey: string; bftWeight: string }[]) {
const validatorInfo = sidechainValidatorsKeys.find(
const validatorInfo = sidechainDevValidators.find(
configValidator => configValidator.plain.blsKey === v.blsKey,
);
if (validatorInfo) {
Expand All @@ -68,11 +92,13 @@ export const registerMainchain = async (
}
}
console.log('Total activeValidatorsWithPrivateKey:', activeValidatorsWithPrivateKey.length);
// Sort active validators from sidechainChain

// Sort active validators from sidechain lexicographically after their BLS public key
activeValidatorsWithPrivateKey.sort((a, b) => a.blsPublicKey.compare(b.blsPublicKey));

const sidechainValidatorsSignatures: { publicKey: Buffer; signature: Buffer }[] = [];
// Sign with each active validator

// Sign parameters with each active sidechain validator
for (const validator of activeValidatorsWithPrivateKey) {
const signature = bls.signData(
MESSAGE_TAG_CHAIN_REG,
Expand All @@ -92,30 +118,37 @@ export const registerMainchain = async (
sidechainValidatorsSignatures,
);

// Get public key and nonce of the sender account
const relayerKeyInfo = sidechainDevValidators[0];
const { nonce } = await sidechainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
});

// Add aggregated signature to the parameters of the mainchain registration
const mainchainRegParams = {
...paramsJSON,
signature: signature.toString('hex'),
aggregationBits: aggregationBits.toString('hex'),
};

// Create registerMainchain transaction
const tx = new Transaction({
module: MODULE_NAME_INTEROPERABILITY,
command: COMMAND_NAME_MAINCHAIN_REG,
command: 'registerMainchain',
fee: BigInt(2000000000),
params: codec.encodeJSON(mainchainRegParamsSchema, mainchainRegParams),
nonce: BigInt(nonce),
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
signatures: [],
});

// Sign the transaction
tx.sign(
Buffer.from(sidechainNodeInfo.chainID as string, 'hex'),
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
);

// Post the transaction to a sidechain node
const result = await sidechainClient.invoke<{
transactionId: string;
}>('txpool_postTransaction', {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,83 @@
import { apiClient, codec, cryptography, Transaction } from 'lisk-sdk';
import { apiClient, codec, sidechainRegParams, cryptography, Transaction } from 'lisk-sdk';
// Replace this with the path to a file storing the public and private key of a mainchain account who will send the sidechain registration transaction.
// (Can be any account with enough tokens).
import { keys } from '../default/dev-validators.json';
import { sidechainRegParams } from 'lisk-framework';

(async () => {
const { address } = cryptography;

const SIDECHAIN_ARRAY = ['one', 'two'];
// Replace this with alias of the sidechain node(s)
const SIDECHAIN_ARRAY = ['pos-sidechain-example-one', 'pos-sidechain-example-two'];
// Replace this with the alias of the mainchain node(s), e.g. lisk-core
// Note: Number of mainchain nodes should be equal to sidechain nodes, for this script to work properly.
const MAINCHAIN_ARRAY = ['mainchain-node-one', 'mainchain-node-two'];
let i = 0;
for (const nodeAlias of SIDECHAIN_ARRAY) {
const sidechainClient = await apiClient.createIPCClient(
`~/.lisk/pos-sidechain-example-${nodeAlias}`,
);
const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-${nodeAlias}`);
// Connect to the sidechain node
const sidechainClient = await apiClient.createIPCClient(`~/.lisk/${nodeAlias}`);
// Connect to the mainchain node
const mainchainClient = await apiClient.createIPCClient(`~/.lisk/${MAINCHAIN_ARRAY[i]}`);

// Get node info data from sidechain and mainchain
const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo');
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');
// Get active validators from sidechainchain
const { validators: sidehcainActiveValidators, certificateThreshold } =

// Get info about the active sidechain validators and the certificate threshold
const { validators: sidechainActiveValidators, certificateThreshold } =
await sidechainClient.invoke('consensus_getBFTParameters', {
height: sidechainNodeInfo.height,
});

(sidehcainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) =>
// Sort validator list lexicographically after their BLS key
(sidechainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) =>
Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex')),
);

// Define parameters for the sidechain registration
const params = {
sidechainCertificateThreshold: certificateThreshold,
sidechainValidators: sidehcainActiveValidators,
sidechainValidators: sidechainActiveValidators,
chainID: sidechainNodeInfo.chainID,
name: `sidechain_example_${nodeAlias}`,
name: nodeAlias.replace(/-/g, '_'),
};

const relayerkeyInfo = keys[2];
// Get public key and nonce of the sender account
const relayerKeyInfo = keys[2];
const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')),
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
});

// Create registerSidechain transaction
const tx = new Transaction({
module: 'interoperability',
command: 'registerSidechain',
fee: BigInt(2000000000),
params: codec.encodeJSON(sidechainRegParams, params),
nonce: BigInt(nonce),
senderPublicKey: Buffer.from(relayerkeyInfo.publicKey, 'hex'),
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
signatures: [],
});

// Sign the transaction
tx.sign(
Buffer.from(mainchainNodeInfo.chainID as string, 'hex'),
Buffer.from(relayerkeyInfo.privateKey, 'hex'),
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
);

// Post the transaction to a mainchain node
const result = await mainchainClient.invoke<{
transactionId: string;
}>('txpool_postTransaction', {
transaction: tx.getBytes().toString('hex'),
});

console.log(
`Sent sidechain registration transaction on mainchain node ${nodeAlias}. Result from transaction pool is: `,
`Sent sidechain registration transaction on mainchain node ${MAINCHAIN_ARRAY[1]}. Result from transaction pool is: `,
result,
);
i += 1;

// Wait in case there are more elements in the SIDECHAIN_ARRAY, after performing another loop with the next element.
const wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
if (i < SIDECHAIN_ARRAY.length) {
const WAIT_PERIOD = 10000;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { apiClient, codec, cryptography, Schema, Transaction } from 'lisk-sdk';
// Replace this with the path to a file storing the public and private key of a mainchain account who will send the sidechain registration transaction.
// (Can be any account with enough tokens).
import { keys } from '../default/dev-validators.json';
type ModulesMetadata = [
{
Expand All @@ -12,25 +14,30 @@ type ModulesMetadata = [
const { address } = cryptography;

const nodeAlias = 'one';
// Update this with the Token ID of the token you wish to transfer
const tokenID = Buffer.from('0400000000000000', 'hex');
const sidechainID = Buffer.from('04000001', 'hex'); // Update this to send to another sidechain
// Update this with the chain ID of the receiving chain
const sidechainID = Buffer.from('04000001', 'hex');
// Update this with the recipient address
const recipientLSKAddress = 'lskxz85sur2yo22dmcxybe39uvh2fg7s2ezxq4ny9';
const recipientAddress = address.getAddressFromLisk32Address(recipientLSKAddress);

// Connect to the mainchain node
const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-one`);

// Get node info data from mainchain
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');

// Get schema for the transferCrossChain command
const { modules: modulesMetadata } = await mainchainClient.invoke<{
modules: ModulesMetadata;
}>('system_getMetadata');

const tokenMetadata = modulesMetadata.find(m => m.name === 'token');

const ccTransferCMDSchema = tokenMetadata?.commands.filter(
cmd => cmd.name == 'transferCrossChain',
)[0].params as Schema;

// Define parameters for the cc transfer
const params = {
tokenID,
amount: BigInt('10000000000'),
Expand All @@ -41,11 +48,13 @@ type ModulesMetadata = [
messageFeeTokenID: tokenID,
};

// Get public key and nonce of the sender account
const relayerkeyInfo = keys[2];
const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')),
});

// Create transferCrossChain transaction
const tx = new Transaction({
module: 'token',
command: 'transferCrossChain',
Expand All @@ -56,11 +65,13 @@ type ModulesMetadata = [
signatures: [],
});

// Sign the transaction
tx.sign(
Buffer.from(mainchainNodeInfo.chainID as string, 'hex'),
Buffer.from(relayerkeyInfo.privateKey, 'hex'),
);

// Post the transaction to a mainchain node
const result = await mainchainClient.invoke<{
transactionId: string;
}>('txpool_postTransaction', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"ccuFee": "500000000",
"receivingChainIPCPath": "~/.lisk/mainchain-node-one",
"registrationHeight": 10
},
"dashboard": {
"applicationUrl": "ws://127.0.0.1:7885/rpc-ws",
"port": 4006
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { keys as sidechainValidatorsKeys } from '../../config/default/dev-validators.json';
import { keys as sidechainDevValidators } from '../default/dev-validators.json';
import { registerMainchain } from '../../../common/mainchain_registration';

(async () => {
await registerMainchain('one', sidechainDevValidators, sidechainValidatorsKeys);
await registerMainchain(
'mainchain-node-one',
'pos-sidechain-example-one',
sidechainDevValidators,
);
})();
6 changes: 6 additions & 0 deletions examples/interop/pos-sidechain-example-one/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Application, PartialApplicationConfig } from 'lisk-sdk';
import { registerModules } from './modules';
import { registerPlugins } from './plugins';
import { HelloModule } from './modules/hello/module';

export const getApplication = (config: PartialApplicationConfig): Application => {
const { app } = Application.defaultApplication(config);

const helloModule = new HelloModule();
app.registerModule(helloModule);

app.registerInteroperableModule(helloModule);

registerModules(app);
registerPlugins(app);

Expand Down
Loading
Loading