123456import {ApiPromise} from '@polkadot/api';7import {IKeyringPair} from '@polkadot/types/types';8import privateKey from './substrate/privateKey';9import usingApi, {submitTransactionAsync} from './substrate/substrate-api';10import waitNewBlocks from './substrate/wait-new-blocks';11import {findUnusedAddresses} from './util/helpers';12import * as cluster from 'cluster';13import os from 'os';141516const FEE = 10n ** 8n;1718let counters: { [key: string]: number } = {};19function increaseCounter(name: string, amount: number) {20 if (!counters[name]) {21 counters[name] = 0;22 }23 counters[name] += amount;24}25function flushCounterToMaster() {26 if (Object.keys(counters).length === 0) {27 return;28 }29 process.send!(counters);30 counters = {};31}3233async function distributeBalance(source: IKeyringPair, api: ApiPromise, totalAmount: bigint, stages: number) {34 const accounts = [source];35 36 const failedAccounts = [0];3738 const finalUserAmount = 2 ** stages - 1;39 accounts.push(...await findUnusedAddresses(api, finalUserAmount));40 41 increaseCounter('requests', finalUserAmount);4243 for (let stage = 0; stage < stages; stage++) {44 const usersWithBalance = 2 ** stage;45 const amount = totalAmount / (2n ** BigInt(stage)) - FEE * BigInt(stage);46 47 const txs = [];48 for (let i = 0; i < usersWithBalance; i++) {49 const newUser = accounts[i + usersWithBalance];50 51 const tx = api.tx.balances.transfer(newUser.address, amount);52 txs.push(submitTransactionAsync(accounts[i], tx).catch(() => {53 failedAccounts.push(i + usersWithBalance);54 increaseCounter('txFailed', 1);55 }));56 increaseCounter('tx', 1);57 }58 await Promise.all(txs);59 }6061 for (const account of failedAccounts.reverse()) {62 accounts.splice(account, 1);63 }64 return accounts;65}6667if (cluster.isMaster) {68 let testDone = false;69 usingApi(async (api) => {70 const prevCounters: { [key: string]: number } = {};71 while (!testDone) {72 for (const name in counters) {73 if (!(name in prevCounters)) {74 prevCounters[name] = 0;75 }76 if(counters[name] === prevCounters[name]) {77 continue;78 }79 console.log(`${name.padEnd(15)} = ${counters[name] - prevCounters[name]}`);80 prevCounters[name] = counters[name];81 }82 await waitNewBlocks(api, 1);83 }84 });85 const waiting: Promise<void>[] = [];86 console.log(`Starting ${os.cpus().length} workers`);87 usingApi(async (api) => {88 const alice = privateKey('//Alice');89 for (const id in os.cpus()) {90 const WORKER_NAME = `//LoadWorker${id}_${Date.now()}`;91 const workerAccount = privateKey(WORKER_NAME);92 const tx = api.tx.balances.transfer(workerAccount.address, 400n * 10n ** 23n);93 await submitTransactionAsync(alice, tx);9495 const worker = cluster.fork({96 WORKER_NAME,97 STAGES: id + 2,98 });99 worker.on('message', msg => {100 for (const key in msg) {101 increaseCounter(key, msg[key]);102 }103 });104 waiting.push(new Promise(res => worker.on('exit', res)));105 }106 await Promise.all(waiting);107 testDone = true;108 });109} else {110 increaseCounter('startedWorkers', 1);111 usingApi(async (api) => {112 await distributeBalance(privateKey(process.env.WORKER_NAME as string), api, 400n * 10n ** 22n, 10);113 });114 const interval = setInterval(() => {115 flushCounterToMaster();116 }, 100);117 interval.unref();118}