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

Commit 2c17b70

Browse files
bobanmishantiw
andauthored
Improve API client usage in interoperability example app scripts (#9191)
* Improve API client usage in iterop example app scripts * 🌱 Create CCU transactions for testing --------- Co-authored-by: Ishan <[email protected]>
1 parent b9c299f commit 2c17b70

File tree

2 files changed

+296
-40
lines changed

2 files changed

+296
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/**
2+
* This script is intended to demonstrate how to register a sidechain and create/send CCUs.
3+
*/
4+
import {
5+
cryptography,
6+
testing,
7+
passphrase,
8+
apiClient,
9+
Transaction,
10+
codec,
11+
sidechainRegParams,
12+
ActiveValidator,
13+
validatorsHashInputSchema,
14+
UnsignedCertificate,
15+
MESSAGE_TAG_CERTIFICATE,
16+
unsignedCertificateSchema,
17+
certificateSchema,
18+
ccuParamsSchema,
19+
} from 'lisk-sdk';
20+
import * as fs from 'fs-extra';
21+
const { ed, bls, address, utils } = cryptography;
22+
import { keys } from '../default/dev-validators.json';
23+
24+
export interface Certifcate {
25+
readonly blockID: Buffer;
26+
readonly height: number;
27+
readonly timestamp: number;
28+
readonly stateRoot: Buffer;
29+
readonly validatorsHash: Buffer;
30+
aggregationBits: Buffer;
31+
signature: Buffer;
32+
}
33+
34+
interface ActiveValidatorsUpdate {
35+
blsKeysUpdate: Buffer[];
36+
bftWeightsUpdate: bigint[];
37+
bftWeightsUpdateBitmap: Buffer;
38+
}
39+
40+
interface OutboxRootWitness {
41+
bitmap: Buffer;
42+
siblingHashes: Buffer[];
43+
}
44+
45+
interface InboxUpdate {
46+
crossChainMessages: Buffer[];
47+
messageWitnessHashes: Buffer[];
48+
outboxRootWitness: OutboxRootWitness;
49+
}
50+
51+
export interface CrossChainUpdateTransactionParams {
52+
sendingChainID: Buffer;
53+
certificate: Buffer;
54+
activeValidatorsUpdate: ActiveValidatorsUpdate;
55+
certificateThreshold: bigint;
56+
inboxUpdate: InboxUpdate;
57+
}
58+
59+
interface ValidatorAccount {
60+
privateKey: Buffer;
61+
publicKey: Buffer;
62+
blsPrivateKey: Buffer;
63+
blsPublicKey: Buffer;
64+
}
65+
66+
export const computeValidatorsHash = (
67+
activeValidators: ActiveValidator[],
68+
certificateThreshold: bigint,
69+
) => {
70+
const input = {
71+
activeValidators,
72+
certificateThreshold,
73+
};
74+
const encodedValidatorsHashInput = codec.encode(validatorsHashInputSchema, input);
75+
return utils.hash(encodedValidatorsHashInput);
76+
};
77+
78+
const generateValidatorForSidechain = async (count: number, chainID: string, chainName: string) => {
79+
const phrase = passphrase.Mnemonic.generateMnemonic(256);
80+
const validators: ValidatorAccount[] = [];
81+
for (let i = 0; i < count; i += 1) {
82+
const accountKeyPath = `m/44'/134'/${i}'`;
83+
const blsKeyPath = `m/12381/134/${chainID}/${i}`;
84+
85+
const accountPrivateKey = await ed.getPrivateKeyFromPhraseAndPath(phrase, accountKeyPath);
86+
const accountPublicKey = ed.getPublicKeyFromPrivateKey(accountPrivateKey);
87+
const blsPrivateKey = await bls.getPrivateKeyFromPhraseAndPath(phrase, blsKeyPath);
88+
const blsPublicKey = bls.getPublicKeyFromPrivateKey(blsPrivateKey);
89+
90+
validators.push({
91+
privateKey: accountPrivateKey,
92+
publicKey: accountPublicKey,
93+
blsPrivateKey,
94+
blsPublicKey,
95+
});
96+
}
97+
const OWNER_READ_WRITE = 0o600;
98+
fs.writeJSONSync(`~/keys-${chainName}`, { keys }, { spaces: ' ', mode: OWNER_READ_WRITE });
99+
100+
return validators;
101+
};
102+
103+
const createAndSendTransaction = async (
104+
module: string,
105+
command: string,
106+
fee: bigint,
107+
params: Buffer,
108+
message: string,
109+
) => {
110+
const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-one`);
111+
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');
112+
113+
// Get public key and nonce of the sender account
114+
const relayerKeyInfo = keys[2];
115+
const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
116+
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
117+
});
118+
// Create registerSidechain transaction
119+
const tx = new Transaction({
120+
module,
121+
command,
122+
fee,
123+
params,
124+
nonce: BigInt(nonce),
125+
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
126+
signatures: [],
127+
});
128+
129+
// Sign the transaction
130+
tx.sign(
131+
Buffer.from(mainchainNodeInfo.chainID as string, 'hex'),
132+
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
133+
);
134+
135+
// Post the transaction to a mainchain node
136+
const result = await mainchainClient.invoke<{
137+
transactionId: string;
138+
}>('txpool_postTransaction', {
139+
transaction: tx.getBytes().toString('hex'),
140+
});
141+
142+
console.log(message, result);
143+
};
144+
145+
export const registerSidechain = async (
146+
chainID: string,
147+
name: string,
148+
sidechainValidators: { blsKey: Buffer; bftWeight: bigint }[],
149+
sidechainCertificateThreshold: bigint,
150+
) => {
151+
const params = {
152+
chainID: Buffer.from(chainID, 'hex'),
153+
name,
154+
sidechainValidators: sidechainValidators.sort((a, b) => a.blsKey.compare(b.blsKey)),
155+
sidechainCertificateThreshold,
156+
};
157+
158+
await createAndSendTransaction(
159+
'interoperability',
160+
'registerSidechain',
161+
BigInt(2000000000),
162+
codec.encodeJSON(sidechainRegParams, params),
163+
`Sent register sidechain transaction with chainID: ${chainID}`,
164+
);
165+
};
166+
167+
export const computeAndSendCCUTransaction = async (
168+
chainID: string,
169+
validators: ValidatorAccount[],
170+
certificateThreshold: bigint,
171+
timestamp: number,
172+
) => {
173+
const validatorBLSKeyAndWeight = validators.map(v => ({
174+
blsKey: v.blsPublicKey,
175+
bftWeight: BigInt(1),
176+
}));
177+
validatorBLSKeyAndWeight.sort((a, b) => a.blsKey.compare(b.blsKey));
178+
179+
const validatorsHash = computeValidatorsHash(validatorBLSKeyAndWeight, certificateThreshold);
180+
181+
const block = testing.createFakeBlockHeader({ validatorsHash, timestamp });
182+
const unsignedCertificate: UnsignedCertificate = {
183+
blockID: block.id,
184+
height: block.height,
185+
stateRoot: block.stateRoot as Buffer,
186+
timestamp: block.timestamp,
187+
validatorsHash: block.validatorsHash as Buffer,
188+
};
189+
const signatures: { signature: Buffer; publicKey: Buffer }[] = [];
190+
191+
for (const { blsPrivateKey, blsPublicKey } of validators.sort((a, b) =>
192+
a.blsPublicKey.compare(b.blsPublicKey),
193+
)) {
194+
signatures.push({
195+
signature: bls.signData(
196+
MESSAGE_TAG_CERTIFICATE,
197+
Buffer.from(chainID, 'hex'),
198+
codec.encode(unsignedCertificateSchema, unsignedCertificate),
199+
blsPrivateKey,
200+
),
201+
publicKey: blsPublicKey,
202+
});
203+
}
204+
205+
const { aggregationBits, signature } = bls.createAggSig(
206+
signatures.map(s => s.publicKey),
207+
signatures,
208+
);
209+
210+
const certificate = codec.encode(certificateSchema, {
211+
...unsignedCertificate,
212+
aggregationBits,
213+
signature,
214+
});
215+
216+
const params = codec.encode(ccuParamsSchema, {
217+
activeValidatorsUpdate: {
218+
bftWeightsUpdate: [],
219+
bftWeightsUpdateBitmap: Buffer.alloc(0),
220+
blsKeysUpdate: [],
221+
},
222+
certificate,
223+
certificateThreshold,
224+
inboxUpdate: {
225+
crossChainMessages: [],
226+
messageWitnessHashes: [],
227+
outboxRootWitness: {
228+
bitmap: Buffer.alloc(0),
229+
siblingHashes: [],
230+
},
231+
},
232+
sendingChainID: Buffer.from(chainID, 'hex'),
233+
});
234+
235+
await createAndSendTransaction(
236+
'interoperability',
237+
'submitMainchainCrossChainUpdate',
238+
BigInt(10000000),
239+
params,
240+
`Sent cross chain update transaction to mainchain from sendingChain with chainID: ${chainID}`,
241+
);
242+
};
243+
/**
244+
* 1. Register multiple sidechains on the receiving chain
245+
* 2. Generate a fake block for the sending chain with timestamp < lastblock.timestamp on the receiving chain
246+
* 3. Create aggreggate signature by signing the unsigned certificate using the same validators saved on the receivingChain validators store
247+
* 4. Keep InboxUpdate and ActiveValidatorsUpdate blank for this scenario to keep things simple
248+
*/
249+
(async () => {
250+
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
251+
// send register sidechainParams
252+
const chainID = '04000051';
253+
const chainName = 'sidechain_51';
254+
const sidechainValidators = await generateValidatorForSidechain(2, chainID, chainName);
255+
const sidechainValidatorsParam = sidechainValidators.map(v => ({
256+
blsKey: v.blsPublicKey,
257+
bftWeight: BigInt(1),
258+
}));
259+
const certificateThreshold = BigInt(2);
260+
261+
await registerSidechain(chainID, chainName, sidechainValidatorsParam, certificateThreshold);
262+
console.log('Registered sidechain!');
263+
264+
await wait(15000);
265+
266+
// Making sure the timestamp of the certificate is in the past but not older than 15 days.
267+
const timestampForCertificate = Math.floor((Date.now() - 100000) / 1000);
268+
// Send CCU after registration is done
269+
await computeAndSendCCUTransaction(
270+
chainID,
271+
sidechainValidators,
272+
certificateThreshold,
273+
timestampForCertificate,
274+
);
275+
})();

Diff for: examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts

+21-40
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { apiClient, codec, sidechainRegParams, cryptography, Transaction } from 'lisk-sdk';
1+
import { apiClient } from 'lisk-sdk';
22
// 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.
33
// (Can be any account with enough tokens).
44
import { keys } from '../default/dev-validators.json';
55

66
(async () => {
7-
const { address } = cryptography;
8-
97
// Replace this with alias of the sidechain node(s)
108
const SIDECHAIN_ARRAY = ['pos-sidechain-example-one', 'pos-sidechain-example-two'];
119
// Replace this with the alias of the mainchain node(s), e.g. lisk-core
@@ -20,7 +18,6 @@ import { keys } from '../default/dev-validators.json';
2018

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

2522
// Get info about the active sidechain validators and the certificate threshold
2623
const { validators: sidechainActiveValidators, certificateThreshold } =
@@ -33,56 +30,40 @@ import { keys } from '../default/dev-validators.json';
3330
Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex')),
3431
);
3532

36-
// Define parameters for the sidechain registration
37-
const params = {
38-
sidechainCertificateThreshold: certificateThreshold,
39-
sidechainValidators: sidechainActiveValidators,
40-
chainID: sidechainNodeInfo.chainID,
41-
name: nodeAlias.replace(/-/g, '_'),
42-
};
43-
44-
// Get public key and nonce of the sender account
45-
const relayerKeyInfo = keys[2];
46-
const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
47-
address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
48-
});
49-
50-
// Create registerSidechain transaction
51-
const tx = new Transaction({
33+
const unsignedTransaction = {
5234
module: 'interoperability',
5335
command: 'registerSidechain',
5436
fee: BigInt(2000000000),
55-
params: codec.encodeJSON(sidechainRegParams, params),
56-
nonce: BigInt(nonce),
57-
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
58-
signatures: [],
59-
});
37+
params: {
38+
sidechainCertificateThreshold: certificateThreshold,
39+
sidechainValidators: sidechainActiveValidators,
40+
chainID: sidechainNodeInfo.chainID,
41+
name: nodeAlias.replace(/-/g, '_'),
42+
},
43+
};
6044

61-
// Sign the transaction
62-
tx.sign(
63-
Buffer.from(mainchainNodeInfo.chainID as string, 'hex'),
64-
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
45+
const signedTransaction = await mainchainClient.transaction.create(
46+
unsignedTransaction,
47+
keys[2].privateKey,
6548
);
6649

67-
// Post the transaction to a mainchain node
68-
const result = await mainchainClient.invoke<{
69-
transactionId: string;
70-
}>('txpool_postTransaction', {
71-
transaction: tx.getBytes().toString('hex'),
72-
});
50+
try {
51+
const receipt = await mainchainClient.transaction.send(signedTransaction);
52+
console.log(
53+
`Sent sidechain '${nodeAlias}' registration transaction on mainchain node '${MAINCHAIN_ARRAY[i]}'. Tx ID:`,
54+
receipt.transactionId,
55+
);
56+
} catch (error) {
57+
console.log(error);
58+
}
7359

74-
console.log(
75-
`Sent sidechain registration transaction on mainchain node ${MAINCHAIN_ARRAY[1]}. Result from transaction pool is: `,
76-
result,
77-
);
7860
i += 1;
7961

8062
// Wait in case there are more elements in the SIDECHAIN_ARRAY, after performing another loop with the next element.
8163
const wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
8264
if (i < SIDECHAIN_ARRAY.length) {
8365
const WAIT_PERIOD = 10000;
8466
console.log(`Waiting for ${WAIT_PERIOD} ms to send another sidechain registration`);
85-
// Wait for 2 seconds before next registration
8667
await wait(WAIT_PERIOD);
8768
}
8869

0 commit comments

Comments
 (0)