Skip to content

Commit

Permalink
dedir: Add script to stress test for large entries
Browse files Browse the repository at this point in the history
Signed-off-by: Shreevatsa N <[email protected]>
  • Loading branch information
vatsa287 committed Nov 26, 2024
1 parent ccc4c7a commit fb69143
Showing 1 changed file with 341 additions and 0 deletions.
341 changes: 341 additions & 0 deletions demo/src/dedi/dedi-stress-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
import * as Cord from '@cord.network/sdk'
import { createAccount } from '../utils/createAccount'
import { SubmittableExtrinsic } from '@polkadot/api/types';

import moment from "moment";

import {
BN
} from 'bn.js';
import { UUID } from '@cord.network/utils';

/*
* NOTE/ README:
* maxOuterBatches: Maximum number of times the outer batch is refreshed.
* Outer batch function calls `batchTransactions` method `maxOuterBatches` times.
*
* txCount: Number of total transactions to be packed as batches in `batchTransactions`.
*
* perBatch: Number of transactions to be pushed to a single batch.
*
* Example:
* maxOuterBatches = 1, txCount = 1_00_000, perBatch = 10_000
* The method `batchTransactions` is called for `maxOuterBatches` i.e 1 times in a loop.
* Since `perBatch` is set to `10_000`. There shall be 10 batches with each containing,
* `10_000` transactions.
* Every batch is signed and sent to the chain sequentially.
*
*/

async function main() {
try {
const networkAddress = process.env.NETWORK_ADDRESS
? process.env.NETWORK_ADDRESS
: 'ws://127.0.0.1:9944'

Cord.ConfigService.set({ submitTxResolveOn: Cord.Chain.IS_IN_BLOCK })
await Cord.connect(networkAddress)

const api = Cord.ConfigService.get('api');

// Step 1: Setup Membership
// Setup transaction author account - CORD Account.
console.log(`\n❄️ New Network Member`)
const authorityAuthorIdentity = Cord.Utils.Crypto.makeKeypairFromUri(
process.env.ANCHOR_URI ? process.env.ANCHOR_URI : '//Alice',
'sr25519'
)

// Setup network member account.
const { account: authorIdentity } = await createAccount()
console.log(`🏦 Member (${authorIdentity.type}): ${authorIdentity.address}`)

try {
// Note:
// Balance consumed per tx: 1732334381294 or 954747546518800000 * 36 % for 200K tx.

let tx = await api.tx.balances.transferAllowDeath(authorIdentity.address, new BN('999999930086000000'));
await Cord.Chain.signAndSubmitTx(tx, authorityAuthorIdentity);
console.log("Balance transferred successfully!");
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`Error in main function: ${error.message}`);
}
}

const initialBalance = await getBalance(api, authorIdentity.address);
console.log(`Initial Balance: ${initialBalance.toString()}`);

// Create a Schema
console.log(`\n❄️ Schema Creation `)
let newSchemaContent = require('../../res/schema.json')
let newSchemaName = newSchemaContent.title + ':' + Cord.Utils.UUID.generate()
newSchemaContent.title = newSchemaName

let schemaProperties = Cord.SchemaAccounts.buildFromProperties(
newSchemaContent,
authorIdentity.address,
)
console.dir(schemaProperties, {
depth: null,
colors: true,
})
const schemaUri = await Cord.SchemaAccounts.dispatchToChain(
schemaProperties.schema,
authorIdentity,
)
console.log(`βœ… Schema - ${schemaUri} - added!`)

console.log(`\n❄️ Query From Chain - Schema `)
const schemaFromChain = await Cord.SchemaAccounts.fetchFromChain(
schemaProperties.schema.$id
)
console.dir(schemaFromChain, {
depth: null,
colors: true,
})
console.log('βœ… Schema Functions Completed!')

// Create a Registry.
const blob = {
"name": "Companies Registry",
"description": "A centralized registry that tracks the registration, incorporation status, and key business details of companies across various industries.",
"metadata": {
"category": "business",
"totalCompaniesRegistered": 15000,
"industriesCovered": [
"Technology",
"Healthcare",
"Renewable Energy",
"Finance",
"Manufacturing"
],
"lastUpdated": "01-10-2024",
"regulatoryAuthority": "National Business Bureau",
"registrationRequirements": {
"documentsNeeded": [
"Incorporation Certificate",
"Tax Identification Number",
"Proof of Address",
"Board Resolution"
],
"feeStructure": {
"smallBusiness": "INR500",
"mediumBusiness": "INR1000",
"largeBusiness": "INR5000"
}
}
}
};
const stringified_blob = JSON.stringify(blob);
const digest = await Cord.Registries.getDigestFromRawData(stringified_blob);

// Crreate a Registry Property.
const registryDetails = await Cord.Registries.registryCreateProperties(
authorIdentity.address,
digest,
null,
stringified_blob,
);

console.log(`\n❄️ Registry Create Details `, registryDetails);

const registry = await Cord.Registries.dispatchCreateRegistryToChain(
registryDetails,
authorIdentity,
);

console.log("Registry URI", registryDetails.uri);

console.log('\nβœ… Registry created!');

let maxOuterBatches = 10;
let txCount = 1_00_000;
let perBatch = 10_000;

let outerBatchStartTime = moment();

for (let i = 0; i < maxOuterBatches; i++) {
console.log(`\nProcessing outer batch ${i + 1}...`);
await batchTransactions(api, authorIdentity, registry.uri, registry.authorizationUri, txCount, perBatch);

console.log(`\nNumber of transactions completed: ${(i+1) * 1_00_000}`);
}

let outerBatchEndTime = moment();

let outerBatchDurationInSeconds = outerBatchEndTime.diff(outerBatchStartTime, 'seconds');
console.log(`\nTotal time for ${maxOuterBatches} maximum outer batches: ${outerBatchDurationInSeconds} seconds`);

const finalBalance = await getBalance(api, authorIdentity.address);
console.log(`Final Balance: ${finalBalance.toString()}`);

const totalConsumed = initialBalance.sub(finalBalance);
console.log(`Total Balance Consumed: ${totalConsumed.toString()}`);

const totalTransactions = maxOuterBatches * (1_00_000);
const balancePerTransaction = totalConsumed.div(new BN(totalTransactions));
console.log(`Balance per Transaction: ${balancePerTransaction.toString()}`);

const remainingPercentage =
(Number(finalBalance) / Number(initialBalance)) * 100;
const consumedPercentage = 100 - remainingPercentage;

console.log(`Initial Balance: ${initialBalance}`);
console.log(`Final Balance: ${finalBalance}`);
console.log(`Total Balance Consumed: ${totalConsumed}`);
console.log(`Balance Per Transaction: ${balancePerTransaction}`);
console.log(`Remaining Balance Percentage: ${remainingPercentage.toFixed(2)}%`);
console.log(`Consumed Balance Percentage: ${consumedPercentage.toFixed(2)}%`);

} catch (error: unknown) {
if (error instanceof Error) {
console.error(`Error in main function: ${error.message}`);
}
} finally {
await Cord.disconnect();
console.log('\nBye! πŸ‘‹ πŸ‘‹ πŸ‘‹ ');
}
}

async function batchTransactions(
api: Cord.ApiPromise,
authorIdentity: Cord.CordKeyringPair,
registryUri: Cord.RegistryUri,
registryAuthUri: Cord.RegistryAuthorizationUri,
txCount: number, perBatch: number) {

let startTxPrep = moment();

// const initialNonce = (await api.query.system.account(authorIdentity.address)).nonce.toNumber();
// let currentNonce = initialNonce;

console.log(`\nPreparing and submitting ${txCount} transactions in batches of ${perBatch}...`);

const txBatch: SubmittableExtrinsic<'promise'>[] = [];

try {
for (let j = 0; j < Math.ceil(txCount / perBatch); j++) {
txBatch.length = 0;

for (let k = 0; k < perBatch && j * perBatch + k < txCount; k++) {

const registryEntryDetails = await Cord.Entries.createEntriesProperties(
authorIdentity.address,
registryUri,
registryAuthUri,
null,
`${UUID.generate()}`
);

const tx = api.tx.entries.create(
registryEntryDetails.uri.split(":")[2],
registryEntryDetails.authorizationUri.replace('registryauth:cord:', ''),
registryEntryDetails.digest,
registryEntryDetails.blob
);

txBatch.push(tx);

process.stdout.write(
` πŸ”– Prepared ${(j * perBatch + k + 1)} transactions in ${moment
.duration(moment().diff(startTxPrep))
.asSeconds()
.toFixed(3)}s\r`
);
}

console.log(`\nSubmitting batch ${j + 1}...`);

const batchExtrinsic = api.tx.utility.batchAll(txBatch);

// This is working and having no spikes in memory usage, as observed from chrome dev inspect.
await batchExtrinsic.signAndSend(authorIdentity, { nonce: -1 });

// Sign the batch with the correct nonce
// await batchExtrinsic.signAsync(authorIdentity, { nonce: new BN(currentNonce) });

// Memory spikes in below submission attempts:
// try {
// const unsub = await batchExtrinsic.signAndSend(
// authorIdentity,
// async (result) => {
// if (result.status.isInBlock) {
// unsub();
// console.log(`Batch included in block.`);
// } else if (result.status.isFinalized) {
// unsub();
// console.log(`Batch finalized.`); }
// // } else if (result.isError) {
// // console.error(`Batch failed:`, result.toHuman());
// // }
// }
// );
// } catch (error: unknown) {
// if (error instanceof Error) {
// console.error(`Batch submission error: ${error.message}`);
// }
// }

// await new Promise<void>((resolve, reject) => {
// const batchExtrinsic = api.tx.utility.batchAll(txBatch);
// batchExtrinsic
// .signAndSend(authorIdentity, { nonce: currentNonce }, (result) => {
// if (result.status.isInBlock) {
// console.log(`Batch ${j + 1} included in block.`);
// } else if (result.status.isFinalized) {
// console.log(`Batch ${j + 1} finalized.`);
// resolve();
// } else if (result.isError) {
// console.error(`Batch ${j + 1} failed:`, result.toHuman());
// reject(new Error(`Batch ${j + 1} failed`));
// }
// })
// .catch((error) => {
// console.error(`Error submitting batch ${j + 1}:`, error.message);
// reject(error);
// });
// });

// Increment nonce for the next batch
// currentNonce++;

// Clear the txBatch array after submission
txBatch.length = 0;

if (typeof global.gc === "function") {
global.gc();
} else {
console.warn(
"Garbage collection is not exposed. Run the script with 'npx tsx --node-arg=--expose-gc dedi-load-test.ts'."
);
}

await new Promise((resolve) => setImmediate(resolve));
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`Error during transaction preparation or submission: ${e.message}`);
}
return
}

const batchDuration = moment.duration(moment().diff(startTxPrep)).asSeconds();
console.log(`\n 🎁 Anchoring ${txCount} transactions took ${batchDuration.toFixed(3)}s`);
console.log(` πŸ™Œ Block TPS (batch transactions) - ${Math.round(txCount / batchDuration)} `);
}

async function getBalance(api: Cord.ApiPromise, address: string) {
const { data: balance } = await api.query.system.account(address);
return balance.free;
}

main()
.then(() => console.log('\nBye! πŸ‘‹ πŸ‘‹ πŸ‘‹ '))
.finally(Cord.disconnect)

process.on('SIGINT', async () => {
console.log('\nBye! πŸ‘‹ πŸ‘‹ πŸ‘‹ \n')
Cord.disconnect()
process.exit(0)
})

0 comments on commit fb69143

Please sign in to comment.