Skip to content

Commit 5a41d98

Browse files
authored
Merge branch 'main' into fix/pending-tx-drain-bottlenecks
2 parents d9ebe05 + 3fed700 commit 5a41d98

34 files changed

Lines changed: 2643 additions & 12 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ jobs:
8181
[
8282
{"name": "ev-node-evm", "dockerfile": "apps/evm/Dockerfile"},
8383
{"name": "ev-node-grpc", "dockerfile": "apps/grpc/Dockerfile"},
84+
{"name": "ev-node-loadgen", "dockerfile": "apps/loadgen/Dockerfile"},
8485
{"name": "ev-node-testapp", "dockerfile": "apps/testapp/Dockerfile"}
8586
]
8687

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ docs/.vitepress/cache
2626
*.log
2727
*.tgz
2828
.idea
29+
.junie
2930
.temp
3031
.vite_opt_cache
3132
.vscode
3233
.gocache
3334
.gomodcache
3435
/.cache
36+
*.diff

.just/build.just

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ build-all:
2828
@cd apps/evm && go build -ldflags "{{ ldflags }}" -o {{ build_dir }}/evm .
2929
@echo "--> Building grpc"
3030
@cd apps/grpc && go build -ldflags "{{ ldflags }}" -o {{ build_dir }}/evgrpc .
31+
@echo "--> Building loadgen"
32+
@cd apps/loadgen && go build -ldflags "{{ ldflags }}" -o {{ build_dir }}/ev-loadgen .
3133
@echo "--> Building local-da"
3234
@cd tools/local-da && go build -ldflags "{{ ldflags }}" -o {{ build_dir }}/local-da .
3335
@echo "--> All ev-node binaries built!"

.just/loadgen.just

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Build ev-loadgen binary
2+
[group('loadgen')]
3+
build-loadgen:
4+
@echo "--> Building ev-loadgen"
5+
@mkdir -p {{ build_dir }}
6+
@cd apps/loadgen && go build -o {{ build_dir }}/ev-loadgen .
7+
@echo " Check the binary with: {{ build_dir }}/ev-loadgen"
8+
9+
# Build ev-loadgen Docker image
10+
[group('loadgen')]
11+
docker-build-loadgen:
12+
@echo "--> Building ev-loadgen Docker image"
13+
@docker build -f apps/loadgen/Dockerfile -t ev-loadgen:dev .
14+
@echo "--> Docker image built: ev-loadgen:dev"

apps/evm/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ require (
118118
github.com/ipld/go-ipld-prime v0.23.0 // indirect
119119
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
120120
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
121-
github.com/klauspost/compress v1.18.0 // indirect
121+
github.com/klauspost/compress v1.18.5 // indirect
122122
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
123123
github.com/koron/go-ssdp v0.0.6 // indirect
124124
github.com/libp2p/go-buffer-pool v0.1.0 // indirect

apps/evm/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
480480
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
481481
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
482482
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
483-
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
484-
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
483+
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
484+
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
485485
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
486486
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
487487
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

apps/grpc/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ require (
100100
github.com/ipld/go-ipld-prime v0.23.0 // indirect
101101
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
102102
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
103-
github.com/klauspost/compress v1.18.0 // indirect
103+
github.com/klauspost/compress v1.18.5 // indirect
104104
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
105105
github.com/koron/go-ssdp v0.0.6 // indirect
106106
github.com/libp2p/go-buffer-pool v0.1.0 // indirect

apps/grpc/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
413413
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
414414
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
415415
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
416-
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
417-
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
416+
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
417+
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
418418
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
419419
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
420420
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

apps/loadgen/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM golang:1.25-alpine AS build-env
2+
3+
WORKDIR /src
4+
5+
COPY apps/loadgen/go.mod apps/loadgen/go.sum ./
6+
RUN go mod download
7+
8+
COPY apps/loadgen/ .
9+
RUN CGO_ENABLED=0 GOOS=linux go build -o ev-loadgen .
10+
11+
FROM alpine:3.22.2
12+
13+
ENV TZ=UTC
14+
15+
#hadolint ignore=DL3018
16+
RUN apk --no-cache add ca-certificates curl tzdata
17+
18+
RUN addgroup -S ev && adduser -S ev -G ev
19+
20+
WORKDIR /home/ev
21+
22+
COPY --from=build-env /src/ev-loadgen /usr/bin/ev-loadgen
23+
COPY apps/loadgen/matrices/baseline.json /home/ev/baseline.json
24+
COPY apps/loadgen/matrices/burst.json /home/ev/burst.json
25+
26+
USER ev
27+
28+
CMD ["ev-loadgen", "start"]

apps/loadgen/README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# loadgen
2+
3+
Standalone load generator for ev-node stress testing. Talks to a [spamoor-daemon](https://github.com/ethpandaops/spamoor) sidecar via HTTP API.
4+
5+
## Architecture
6+
7+
```text
8+
ev-loadgen (this binary) --> spamoor-daemon --> ev-reth RPC
9+
| |
10+
reads matrix JSON manages wallets,
11+
creates/polls spammers signs & sends txs
12+
```
13+
14+
- **spamoor-daemon** needs: a funded private key + ev-reth RPC URL
15+
- **ev-loadgen** needs: spamoor-daemon API URL + matrix JSON files
16+
17+
## Modes
18+
19+
### Daemon mode (`start`)
20+
21+
Runs a continuous scheduler with regular and burst workloads. Designed for long-running deployments.
22+
23+
```sh
24+
ev-loadgen start --spamoor-url=http://localhost:8080
25+
```
26+
27+
Regular workloads fire immediately at startup, then repeat at `--interval`. Per-run tx count = `tx-per-day / (24h / interval)`, overriding each matrix entry's `BENCH_COUNT_PER_SPAMMER`.
28+
29+
Bursts are randomly spaced throughout a rolling 24h window. Set `--burst-per-day=0` (the default) to disable bursts entirely.
30+
31+
#### start flags
32+
33+
| Flag | Env | Default | Description |
34+
|------|-----|---------|-------------|
35+
| `--tx-per-day` | `BENCH_TX_PER_DAY` | `1000000` | sustained txs/day |
36+
| `--interval` | `BENCH_INTERVAL` | `1h` | regular workload frequency |
37+
| `--burst-tx-count` | `BENCH_BURST_TX_COUNT` | `500000` | txs per burst |
38+
| `--burst-per-day` | `BENCH_BURST_PER_DAY` | `0` | bursts per day, randomly spaced (0 = disabled) |
39+
| `--regular-matrix` | `BENCH_REGULAR_MATRIX` | `/home/ev/baseline.json` | path to regular matrix JSON |
40+
| `--burst-matrix` | `BENCH_BURST_MATRIX` | `/home/ev/burst.json` | path to burst matrix JSON |
41+
42+
### CLI mode (one-shot commands)
43+
44+
#### `run` — execute a matrix file
45+
46+
Runs all entries from a matrix JSON file with probability filtering and sync waiting, then exits.
47+
48+
```sh
49+
ev-loadgen run matrices/baseline.json --spamoor-url=http://localhost:8080
50+
```
51+
52+
#### `burst` — trigger a single burst
53+
54+
Fires one burst workload immediately and exits.
55+
56+
```sh
57+
ev-loadgen burst --spamoor-url=http://localhost:8080
58+
```
59+
60+
| Flag | Env | Default | Description |
61+
|------|-----|---------|-------------|
62+
| `--tx-count` | `BENCH_BURST_TX_COUNT` | `500000` | total transactions for the burst |
63+
| `--burst-matrix` | `BENCH_BURST_MATRIX` | `/home/ev/burst.json` | path to burst matrix JSON |
64+
65+
### Global flag
66+
67+
`--spamoor-url` (or `BENCH_SPAMOOR_URL` env, default `http://spamoor-daemon:8080`)
68+
69+
## Quick Start
70+
71+
### 1. Start spamoor-daemon
72+
73+
```sh
74+
docker run -d --name spamoor -p 8080:8080 \
75+
ethpandaops/spamoor:latest /app/spamoor-daemon \
76+
--privkey=<funded-private-key> \
77+
--rpchost=http://<ev-reth-host>:8545 \
78+
--port=8080 --startup-delay=0
79+
```
80+
81+
### 2. Run loadgen
82+
83+
```sh
84+
# build
85+
cd apps/loadgen && go build -o ev-loadgen .
86+
87+
# one-shot matrix run
88+
./ev-loadgen run matrices/baseline.json --spamoor-url=http://localhost:8080
89+
90+
# continuous daemon (~1M tx/day, no bursts)
91+
./ev-loadgen start --spamoor-url=http://localhost:8080
92+
93+
# continuous daemon with bursts
94+
./ev-loadgen start \
95+
--spamoor-url=http://localhost:8080 \
96+
--tx-per-day=500000 \
97+
--interval=30m \
98+
--burst-tx-count=100000 \
99+
--burst-per-day=4
100+
```
101+
102+
### Docker Compose
103+
104+
Spins up both spamoor-daemon and loadgen together:
105+
106+
```sh
107+
export BENCH_PRIVATE_KEY=<funded-private-key>
108+
export BENCH_ETH_RPC_URL=http://<ev-reth-host>:8545
109+
docker compose -f apps/loadgen/docker-compose.yml up
110+
```
111+
112+
## Matrix Format
113+
114+
Each entry specifies a spamoor scenario, tx counts, and optional probability:
115+
116+
```json
117+
{
118+
"entries": [
119+
{
120+
"test_name": "EOATransfer",
121+
"scenario": "eoatx",
122+
"timeout": "15m",
123+
"env": {
124+
"BENCH_NUM_SPAMMERS": "4",
125+
"BENCH_COUNT_PER_SPAMMER": "10500",
126+
"BENCH_THROUGHPUT": "200",
127+
"BENCH_MAX_PENDING": "50000",
128+
"BENCH_MAX_WALLETS": "200",
129+
"BENCH_BASE_FEE": "500",
130+
"BENCH_TIP_FEE": "50"
131+
}
132+
}
133+
]
134+
}
135+
```
136+
137+
| Field | Description |
138+
|---|---|
139+
| `scenario` | spamoor scenario name (`eoatx`, `gasburnertx`, `erc20tx`, `uniswap-swaps`, etc.) |
140+
| `probability` | 0.0-1.0, chance of running per invocation (omit = always run) |
141+
| `timeout` | max duration per entry (default `15m`) |
142+
143+
When using `start` or `burst`, `BENCH_COUNT_PER_SPAMMER` is overridden by the computed per-run count. The matrix value is used as-is by `run`.
144+
145+
## Build
146+
147+
```sh
148+
# binary
149+
cd apps/loadgen && go build -o ev-loadgen .
150+
151+
# docker image
152+
docker build -f apps/loadgen/Dockerfile -t ev-loadgen:dev .
153+
154+
# via just
155+
just build-loadgen
156+
just docker-build-loadgen
157+
```

0 commit comments

Comments
 (0)