Skip to content

Commit 9908c08

Browse files
authored
Metrics and load testing (#121)
* skeleton test setup * add basic tests and rpc client * add client * full e2e tests, with local node * Ignore test that requires node running * Mark full node tests with env flag * Finish merge * readme + fmt * yarn update * Update tests for clean fail * fmt * Add mock provider to avoid need to run node for e2e tests * Cargo * lint + clippy * Update env var flag and readme * run tests with full services * update integration tests filename * create runnable bin for load testing * Restructure for runnable binary * Separate load testing from integration testing * refine load testing * Fix toml * Cargo.toml update * Load test cleanup * fix readme * METRICS.md fix + fmt
1 parent ffb966f commit 9908c08

File tree

19 files changed

+5972
-41
lines changed

19 files changed

+5972
-41
lines changed

Cargo.lock

Lines changed: 208 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ homepage = "https://github.com/base/tips"
77
repository = "https://github.com/base/tips"
88

99
[workspace]
10-
members = ["crates/audit", "crates/ingress-rpc", "crates/core", "crates/account-abstraction-core"]
10+
members = ["crates/audit", "crates/ingress-rpc", "crates/core", "crates/account-abstraction-core", "crates/system-tests"]
1111
resolver = "2"
1212

1313
[workspace.dependencies]
1414
tips-audit = { path = "crates/audit" }
1515
tips-core = { path = "crates/core" }
16+
tips-system-tests = { path = "crates/system-tests" }
1617
account-abstraction-core = { path = "crates/account-abstraction-core" }
1718

1819
# Reth
@@ -27,8 +28,10 @@ alloy-primitives = { version = "1.4.1", default-features = false, features = [
2728
] }
2829
alloy-consensus = { version = "1.0.41" }
2930
alloy-provider = { version = "1.0.41" }
30-
alloy-serde = "1.0.41"
3131
alloy-rpc-types = "1.1.2"
32+
alloy-signer = { version = "1.0.41" }
33+
alloy-network = { version = "1.0.41" }
34+
alloy-serde = "1.0.41"
3235
alloy-sol-types = { version = "1.4.1", default-features = false }
3336

3437
# op-alloy

crates/system-tests/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ hex = "0.4.3"
4747
jsonrpsee = { workspace = true }
4848
op-revm = { workspace = true }
4949

50+
# Load test dependencies
51+
clap = { version = "4.5", features = ["derive", "env"] }
52+
indicatif = "0.17"
53+
rand = "0.8"
54+
rand_chacha = "0.3"
55+
dashmap = "6.0"
56+
5057
[dev-dependencies]
5158
tokio = { workspace = true, features = ["test-util"] }
5259
testcontainers = { workspace = true }

crates/system-tests/METRICS.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# TIPS Load Testing
2+
3+
Multi-wallet concurrent load testing tool for measuring TIPS performance.
4+
5+
## Quick Start
6+
7+
```bash
8+
# 1. Build
9+
cargo build --release --bin load-test
10+
11+
# 2. Setup wallets
12+
./target/release/load-test setup \
13+
--master-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
14+
--output wallets.json
15+
16+
# 3. Run load test
17+
./target/release/load-test load --wallets wallets.json
18+
```
19+
20+
---
21+
22+
## Configuration Options
23+
24+
### Setup Command
25+
26+
Create and fund test wallets from a master wallet. Test wallets are saved to allow test reproducibility and avoid the need to create new wallets for every test run.
27+
28+
**Usage:**
29+
```bash
30+
./target/release/load-test setup --master-key <KEY> --output <FILE> [OPTIONS]
31+
```
32+
33+
**Options:**
34+
35+
| Flag | Description | Default | Example |
36+
|------|-------------|---------|---------|
37+
| `--master-key` | Private key of funded wallet (required) | - | `0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d` |
38+
| `--output` | Save wallets to JSON file (required) | - | `wallets.json` |
39+
| `--sequencer` | L2 sequencer RPC URL | `http://localhost:8547` | `http://localhost:8547` |
40+
| `--num-wallets` | Number of wallets to create | `10` | `100` |
41+
| `--fund-amount` | ETH to fund each wallet | `0.1` | `0.5` |
42+
43+
**Environment Variables:**
44+
- `MASTER_KEY` - Alternative to `--master-key` flag
45+
- `SEQUENCER_URL` - Alternative to `--sequencer` flag
46+
47+
### Load Command
48+
49+
Run load test with funded wallets. Use the `--seed` flag to set the RNG seed for test reproducibility.
50+
51+
**Usage:**
52+
```bash
53+
./target/release/load-test load --wallets <FILE> [OPTIONS]
54+
```
55+
56+
**Options:**
57+
58+
| Flag | Description | Default | Example |
59+
|------|-------------|---------|---------|
60+
| `--wallets` | Path to wallets JSON file (required) | - | `wallets.json` |
61+
| `--target` | TIPS ingress RPC URL | `http://localhost:8080` | `http://localhost:8080` |
62+
| `--sequencer` | L2 sequencer RPC URL | `http://localhost:8547` | `http://localhost:8547` |
63+
| `--rate` | Target transaction rate (tx/s) | `100` | `500` |
64+
| `--duration` | Test duration in seconds | `60` | `100` |
65+
| `--tx-timeout` | Timeout for tx inclusion (seconds) | `60` | `120` |
66+
| `--seed` | Random seed for reproducibility | (none) | `42` |
67+
| `--output` | Save metrics to JSON file | (none) | `metrics.json` |
68+
69+
**Environment Variables:**
70+
- `INGRESS_URL` - Alternative to `--target` flag
71+
- `SEQUENCER_URL` - Alternative to `--sequencer` flag
72+
73+
---
74+
---
75+
76+
## Metrics Explained
77+
78+
### Output Example
79+
80+
```
81+
Load Test Results
82+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
83+
Configuration:
84+
Target: http://localhost:8080
85+
Sequencer: http://localhost:8547
86+
Wallets: 100
87+
Target Rate: 100 tx/s
88+
Duration: 60s
89+
TX Timeout: 60s
90+
91+
Throughput:
92+
Sent: 100.0 tx/s (6000 total)
93+
Included: 98.5 tx/s (5910 total)
94+
Success Rate: 98.5%
95+
96+
Transaction Results:
97+
Included: 5910 (98.5%)
98+
Reverted: 10 (0.2%)
99+
Timed Out: 70 (1.2%)
100+
Send Errors: 10 (0.1%)
101+
```
102+
103+
### Metrics Definitions
104+
105+
**Throughput:**
106+
- `Sent Rate` - Transactions sent to TIPS per second
107+
- `Included Rate` - Transactions included in blocks per second
108+
- `Success Rate` - Percentage of sent transactions that were included
109+
110+
**Transaction Results:**
111+
- `Included` - Successfully included in a block with status == true
112+
- `Reverted` - Included in a block but transaction reverted (status == false)
113+
- `Timed Out` - Not included within timeout period
114+
- `Send Errors` - Failed to send to TIPS RPC
115+
116+
---
117+
118+
## Architecture
119+
120+
```
121+
Sender Tasks (1 per wallet) Receipt Poller
122+
│ │
123+
▼ ▼
124+
Send to TIPS ──► Tracker ◄── Poll sequencer every 2s
125+
(retry 3x) (pending) │
126+
│ │ ├─ status=true → included
127+
│ │ ├─ status=false → reverted
128+
│ │ └─ timeout → timed_out
129+
▼ ▼
130+
rate/N tx/s Calculate Results → Print Summary
131+
```
132+
133+
---
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use anyhow::Result;
2+
use clap::Parser;
3+
use tips_system_tests::load_test::{config, load, setup};
4+
5+
#[tokio::main]
6+
async fn main() -> Result<()> {
7+
let cli = config::Cli::parse();
8+
9+
match cli.command {
10+
config::Commands::Setup(args) => setup::run(args).await,
11+
config::Commands::Load(args) => load::run(args).await,
12+
}
13+
}

crates/system-tests/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod client;
22
pub mod fixtures;
3+
pub mod load_test;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use clap::{Parser, Subcommand};
2+
use std::path::PathBuf;
3+
4+
#[derive(Parser)]
5+
#[command(name = "load-test")]
6+
#[command(about = "Load testing tool for TIPS ingress service", long_about = None)]
7+
pub struct Cli {
8+
#[command(subcommand)]
9+
pub command: Commands,
10+
}
11+
12+
#[derive(Subcommand)]
13+
pub enum Commands {
14+
/// Setup: Fund N wallets from a master wallet
15+
Setup(SetupArgs),
16+
/// Load: Run load test with funded wallets
17+
Load(LoadArgs),
18+
}
19+
20+
#[derive(Parser)]
21+
pub struct SetupArgs {
22+
/// Master wallet private key (must have funds)
23+
#[arg(long, env = "MASTER_KEY")]
24+
pub master_key: String,
25+
26+
/// Sequencer RPC URL
27+
#[arg(long, env = "SEQUENCER_URL", default_value = "http://localhost:8547")]
28+
pub sequencer: String,
29+
30+
/// Number of wallets to create and fund
31+
#[arg(long, default_value = "10")]
32+
pub num_wallets: usize,
33+
34+
/// Amount of ETH to fund each wallet
35+
#[arg(long, default_value = "0.1")]
36+
pub fund_amount: f64,
37+
38+
/// Output file for wallet data (required)
39+
#[arg(long)]
40+
pub output: PathBuf,
41+
}
42+
43+
#[derive(Parser)]
44+
pub struct LoadArgs {
45+
/// TIPS ingress RPC URL
46+
#[arg(long, env = "INGRESS_URL", default_value = "http://localhost:8080")]
47+
pub target: String,
48+
49+
/// Sequencer RPC URL (for nonce fetching and receipt polling)
50+
#[arg(long, env = "SEQUENCER_URL", default_value = "http://localhost:8547")]
51+
pub sequencer: String,
52+
53+
/// Path to wallets JSON file (required)
54+
#[arg(long)]
55+
pub wallets: PathBuf,
56+
57+
/// Target transaction rate (transactions per second)
58+
#[arg(long, default_value = "100")]
59+
pub rate: u64,
60+
61+
/// Test duration in seconds
62+
#[arg(long, default_value = "60")]
63+
pub duration: u64,
64+
65+
/// Timeout for transaction inclusion (seconds)
66+
#[arg(long, default_value = "60")]
67+
pub tx_timeout: u64,
68+
69+
/// Random seed for reproducibility
70+
#[arg(long)]
71+
pub seed: Option<u64>,
72+
73+
/// Output file for metrics (JSON)
74+
#[arg(long)]
75+
pub output: Option<PathBuf>,
76+
}

0 commit comments

Comments
 (0)