Load testing framework for EVM-based blockchains using k6 with custom Ethereum extensions. Measures transaction throughput, latency, and time-to-mine metrics.
# Install dependencies
make setup
# Configure environment
cp .env.example .env
# Start a local node (or use a remote RPC)
anvil --block-time 0.2 --gas-price 1
# Run a benchmark
make run-erc20-transfer- Go 1.24+ - For building the k6 extension
- Foundry - For smart contract compilation and local testing with anvil.
- Bun - For utility scripts that support node.js packages.
curl -L https://foundry.paradigm.xyz | bash
foundryup --install v1.5.0
curl -fsSL https://bun.com/install | bash
bun installmake setupThis initializes git submodules, installs the xk6 CLI, and prepares dependencies.
Unit tests (no node required):
make test-unitIntegration tests (requires Anvil at http://127.0.0.1:8545):
make test-integrationscripts/ # k6 benchmark scenarios (Goja runtime)
bun-scripts/ # Utility scripts, supports node.js packages (Bun runtime)
xk6-ethereum/ # k6 Ethereum extension (Go)
contracts/ # Solidity contracts to benchmark
helpers/ # Shared k6 scenario utilities
The project contains two types of TypeScript files with different runtimes:
scripts/ - k6 benchmark scenarios executed by the custom k6 binary. These
run in k6's Goja JavaScript runtime, which is isolated from Node.js. They cannot
use npm packages or Node.js APIs. Instead, they use k6's built-in modules and
the xk6 extensions, like our xk6-ethereum extension.
bun-scripts/ - Utility scripts executed with Bun (Node.js compatible).
These can use any npm package (like viem) and are used for tasks that don't
require k6's load testing capabilities, such as refunding test accounts,
preparing the environment before running a benchmark, etc.
make run-eth-transfer # Native ETH transfers
make run-erc20-transfer # ERC20 token transfers
make run-arbitrary-execution # Configurable contract execution
make run-user-decrypt # FHE user decryption
make run-public-decrypt # FHE public decryption
make run-allow-public-decrypt # FHE allowPublicDecryptBenchmarks are configured via environment variables:
| Variable | Default | Description |
|---|---|---|
RATE |
500 | Target transactions per second |
DURATION |
120 | Test duration in seconds |
BATCH_SIZE |
1 | Transactions per iteration (Multicall3) |
SCENARIO_TYPE |
stress | load (constant) or stress (ramping) |
RATE=100 DURATION=60 SCENARIO_TYPE=load make run-erc20-transferLoad testing (SCENARIO_TYPE=load) maintains a constant transaction rate
throughout the test. Use this to validate that a system handles a specific
sustained load.
Stress testing (SCENARIO_TYPE=stress) ramps from 30% to 200% of the target
rate. Use this to find the system's breaking point. Tests abort if p95 latency
exceeds 2.5 seconds.
Start the monitoring stack for real-time metrics:
docker compose up -dAccess Grafana at http://localhost:3000. Metrics are exported to InfluxDB automatically during benchmark runs.
Create a new file in scripts/:
import { initializeClient } from "../helpers/init.ts";
import { monitoringLoop } from "../helpers/monitoring.ts";
import { getScenarios } from "../helpers/scenarios.ts";
export const options = getScenarios("my-benchmark");
const client = initializeClient();
export function setup() {
// Runs once before VUs start
return { gasPrice: client.gasPrice() };
}
export default function (data: { gasPrice: number }) {
// Each VU iteration executes this function
const receipt = client.sendTransactionSync({
to: "0x...",
value: 1000,
gasPrice: data.gasPrice,
});
}
export function monitor() {
// Block monitoring for TPS metrics
monitoringLoop();
}Add a Makefile target:
.PHONY: run-my-benchmark
run-my-benchmark: build
$(K6_OUTPUT) run --out xk6-influxdb scripts/my-benchmark.tsSee xk6-ethereum/README.md for the full API reference.
Utility scripts run with Bun and can use npm packages:
make refund # Recover ETH from test accounts back to the root accountdocker compose down # Stop containers
docker compose down -v # Stop and remove data