-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dedir: Add script to stress test for large entries
Signed-off-by: Shreevatsa N <[email protected]>
- Loading branch information
Showing
1 changed file
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) |