--- /dev/null +++ b/tests/loadtester-src/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file --- /dev/null +++ b/tests/loadtester-src/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "loadtester" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2018" + +[dependencies] +ink_primitives = { version = "3.0.0-rc2", default-features = false } +ink_metadata = { version = "3.0.0-rc2", default-features = false, features = ["derive"], optional = true } +ink_env = { version = "3.0.0-rc2", default-features = false } +ink_storage = { version = "3.0.0-rc2", default-features = false } +ink_lang = { version = "3.0.0-rc2", default-features = false } +ink_prelude = { version = "3.0.0-rc2", default-features = false } + +scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } +scale-info = { version = "0.4.1", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "loadtester" +path = "lib.rs" +crate-type = [ + # Used for normal contract Wasm blobs. + "cdylib", +] + +[features] +default = ["std"] +std = [ + "ink_metadata/std", + "ink_env/std", + "ink_storage/std", + "ink_primitives/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] --- /dev/null +++ b/tests/loadtester-src/lib.rs @@ -0,0 +1,53 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod loadtester { + use ink_storage::collections::Vec as InkVec; + + #[ink(storage)] + pub struct LoadTester { + vector: InkVec, + } + + impl LoadTester { + #[ink(constructor)] + pub fn new() -> Self { + Self { + vector: InkVec::new(), + } + } + + #[ink(message)] + pub fn bloat(&mut self, count: u64){ + for i in 1..count+1 { + self.vector.push(i); + } + } + + #[ink(message)] + pub fn get(&self) -> u128 { + let mut sum: u128 = 0; + for num in self.vector.iter() { + sum += *num as u128; + } + sum + } + } + + #[cfg(test)] + mod tests { + + use super::*; + + #[test] + fn it_works() { + let mut lt = LoadTester::new(); + lt.bloat(4); + assert_eq!(lt.get(), [1,2,3,4]); + lt.bloat(3); + assert_eq!(lt.get(), [1,2,3,4,1,2,3]); + } + } +} --- a/tests/package.json +++ b/tests/package.json @@ -16,7 +16,8 @@ "typescript": "^3.9.7" }, "scripts": { - "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts" + "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts", + "load": "mocha --timeout 9999999 -r ts-node/register ./**/*.load.ts" }, "author": "", "license": "Apache 2.0", --- /dev/null +++ b/tests/src/load_test_sc/metadata.json @@ -0,0 +1,125 @@ +{ + "metadataVersion": "0.1.0", + "source": { + "hash": "0x168cc3cba9657ad3950fb506e568751f99b90fb097685107f6101675662a8303", + "language": "ink! 3.0.0-rc2", + "compiler": "rustc 1.49.0-nightly" + }, + "contract": { + "name": "loadtester", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "docs": [], + "name": [ + "new" + ], + "selector": "0xd183512b" + } + ], + "docs": [], + "events": [], + "messages": [ + { + "args": [ + { + "name": "count", + "type": { + "displayName": [ + "u64" + ], + "type": 2 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "bloat" + ], + "payable": false, + "returnType": null, + "selector": "0x49891c2a" + }, + { + "args": [], + "docs": [], + "mutates": false, + "name": [ + "get" + ], + "payable": false, + "returnType": { + "displayName": [ + "u128" + ], + "type": 3 + }, + "selector": "0x1e5ca456" + } + ] + }, + "storage": { + "struct": { + "fields": [ + { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "cell": { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "ty": 1 + } + }, + "name": "len" + }, + { + "layout": { + "array": { + "cellsPerElem": 1, + "layout": { + "cell": { + "key": "0x0000000001000000000000000000000000000000000000000000000000000000", + "ty": 2 + } + }, + "len": 4294967295, + "offset": "0x0100000000000000000000000000000000000000000000000000000000000000" + } + }, + "name": "elems" + } + ] + } + }, + "name": "vector" + } + ] + } + }, + "types": [ + { + "def": { + "primitive": "u32" + } + }, + { + "def": { + "primitive": "u64" + } + }, + { + "def": { + "primitive": "u128" + } + } + ] +} \ No newline at end of file --- /dev/null +++ b/tests/src/rpc.load.ts @@ -0,0 +1,145 @@ +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import { expect, assert } from "chai"; +import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api"; +import { IKeyringPair } from "@polkadot/types/types"; +import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types"; +import { BigNumber } from 'bignumber.js'; +import { findUnusedAddress } from './util/helpers' +import fs from "fs"; +import privateKey from "./substrate/privateKey"; + +const value = 0; +const gasLimit = 500000n * 1000000n; +const endowment = `1000000000000000`; + + +function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise { + return new Promise(async (resolve, reject) => { + const unsub = await code + .createBlueprint() + .signAndSend(alice, (result) => { + if (result.status.isInBlock || result.status.isFinalized) { + // here we have an additional field in the result, containing the blueprint + resolve(result.blueprint); + unsub(); + } + }) + }); +} + +function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise { + return new Promise(async (resolve, reject) => { + const unsub = await blueprint.tx + .new(endowment, gasLimit) + .signAndSend(alice, (result) => { + if (result.status.isInBlock || result.status.isFinalized) { + unsub(); + resolve(result); + } + }); + }); +} + +async function prepareDeployer(api: ApiPromise) { + // Find unused address + const deployer = await findUnusedAddress(api); + + // Transfer balance to it + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri(`//Alice`); + let amount = new BigNumber(endowment); + amount = amount.plus(1e15); + const tx = api.tx.balances.transfer(deployer.address, amount.toFixed()); + await submitTransactionAsync(alice, tx); + + return deployer; +} + +async function deployLoadTester(api: ApiPromise): Promise<[Contract, IKeyringPair]> { + const metadata = JSON.parse(fs.readFileSync('./src/load_test_sc/metadata.json').toString('utf-8')); + const abi = new Abi(metadata); + + const deployer = await prepareDeployer(api); + + const wasm = fs.readFileSync('./src/load_test_sc/loadtester.wasm'); + + const code = new CodePromise(api, abi, wasm); + + const blueprint = await deployBlueprint(deployer, code); + const contract = (await deployContract(deployer, blueprint))['contract'] as Contract; + + return [contract, deployer]; +} + +async function getScData(contract: Contract, deployer: IKeyringPair) { + const result = await contract.query.get(deployer.address, value, gasLimit); + + if(!result.result.isSuccess) { + throw `Failed to get value`; + } + return result.result.asSuccess.data; +} + + +describe('RPC Tests', () => { + it('Simple RPC Load Test', async () => { + await usingApi(async api => { + let count = 0; + let hrTime = process.hrtime(); + let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000; + let rate = 0; + const checkPoint = 1000; + while (true) { + await api.rpc.system.chain(); + count++; + process.stdout.write(`RPC reads: ${count} times at rate ${rate} r/s \r`); + + if (count % checkPoint == 0) { + hrTime = process.hrtime(); + let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000; + rate = 1000000*checkPoint/(microsec2 - microsec1); + microsec1 = microsec2; + } + } + }); + }); + + it.only('Smart Contract RPC Load Test', async () => { + await usingApi(async api => { + + // Deploy smart contract + const [contract, deployer] = await deployLoadTester(api); + + // Fill smart contract up with data + const bob = privateKey("//Bob"); + const tx = contract.tx.bloat(value, gasLimit, 200); + await submitTransactionAsync(bob, tx); + + // Run load test + let count = 0; + let hrTime = process.hrtime(); + let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000; + let rate = 0; + const checkPoint = 10; + while (true) { + await getScData(contract, deployer); + count++; + process.stdout.write(`SC reads: ${count} times at rate ${rate} r/s \r`); + + if (count % checkPoint == 0) { + hrTime = process.hrtime(); + let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000; + rate = 1000000*checkPoint/(microsec2 - microsec1); + microsec1 = microsec2; + } + } + }); + }); + +});