Snapshot Benchmarking
base-bench snapshot measures node performance against a prepared Base datadir, with no L1 in the loop. It exists for local performance investigation: you restore a snapshot, drive a workload at it, and publish the artifacts somewhere they can be compared.
The important property is repeatability. Two runs are only worth comparing if everything except the variable under test held still, and most of this guide is about the discipline that requires.
Before you start
Section titled “Before you start”You need an optimised base-bench binary. A debug build will not give you a meaningful number at 400M gas — it measures the build profile, not the node.
You also need a fully prepared Base datadir snapshot to restore from. Materialise a writable pair for one run using whatever snapshot and restore mechanism your environment provides, then point the paths at it:
RUN=blake2f-2s-run-1BUILDER_DATADIR=/path/to/restored/${RUN}-builderCLIENT_DATADIR=/path/to/restored/${RUN}-client
test -f "$BUILDER_DATADIR/db/mdbx.dat"test -f "$CLIENT_DATADIR/db/mdbx.dat"Run one benchmark
Section titled “Run one benchmark”Build the harness out of base/base and run it:
cargo build --release -p base-system-tests --bin base-bench
export BASE_BENCH_CLIENT_VERSION="base/$(git rev-parse --short HEAD)"
target/release/base-bench snapshot \ --chain sepolia \ --builder-datadir "$BUILDER_DATADIR" \ --client-datadir "$CLIENT_DATADIR" \ --load-test-config /path/to/blake2f-2s.yaml \ --benchmark-run snapshot-throughput \ --scenario blake2f-2s-run-1 \ --run-id blake2f-2s-<timestamp> \ --output-dir results/blake2f-2s-run-1--chain picks the network rules used to continue the snapshot. It takes the built-in Base aliases, including mainnet (the default) and sepolia, or a path to a Base genesis JSON file.
A JSON chain whose L2 chain ID matches a built-in Base network gets that network’s rollup configuration automatically, so --rollup-config is optional. A chain ID outside that set does not, and you have to supply it:
target/release/base-bench snapshot \ --chain /path/to/genesis.json \ --rollup-config /path/to/rollup.json \ --builder-datadir "$BUILDER_DATADIR" \ --client-datadir "$CLIENT_DATADIR" \ --load-test-config /path/to/load-test.yaml \ --benchmark-run custom-throughput \ --scenario custom-run-1 \ --output-dir results/custom-run-1One invocation starts the builder and validator, funds a throwaway load-test account, runs the workload, checks that both roles agree on canonical blocks across the measured window, and shuts down.
Each --output-dir comes out self-contained, ready to hand to base/benchmark:
<output-dir>/ benchmark-result.json load-test-result.json metadata.json metrics-sequencer.json metrics-validator.jsonAggregate selected runs
Section titled “Aggregate selected runs”Result directories are immutable inputs. To build a report index without disturbing them, pass the report root plus the direct-child run directories you want included:
target/release/base-bench aggregate \ --output-dir /absolute/path/to/results \ /absolute/path/to/results/run-a \ /absolute/path/to/results/run-bThis writes results/metadata.json atomically. For each unique set of report tags — which includes the scenario and the block-time tag — it keeps the entry with the latest createdAt. Distinct scenarios, cadences, and client versions all survive that rule; a repeat of an identical tag set does not, because the newer run replaces the older one. Source metadata and scenario labels are left alone. id stays the unique raw artifact identifier, and BenchmarkRun stays the report cohort key.
Choose comparable runs
Section titled “Choose comparable runs”Four flags and one environment variable look similar and do different jobs. Using them interchangeably is the fastest way to produce a comparison that means nothing.
| Field | Purpose | Comparison rule |
|---|---|---|
--benchmark-run | Report cohort | Use the same value for every candidate in one comparison, such as snapshot-throughput. |
--scenario | User-visible experiment/repetition label | Use a descriptive value for each selected run, such as blake2f-2s-run-1 or blake2f-200ms-run-1. |
--run-id | Immutable artifact identifier | Keep it unique. It does not group runs. |
--output-dir | Complete local artifact directory | Keep it unique. Never allow two runs to write to the same directory. |
BASE_BENCH_CLIENT_VERSION | Binary/build label | Keep it equal for cadence comparisons; deliberately vary it only for client-version comparisons. |
A fair 2-second against 200ms comparison
Section titled “A fair 2-second against 200ms comparison”- Restore both roles from the same immutable snapshot for every repetition.
- Use the same release binary, host, load parameters, seed, and funding policy.
- Configure the 2-second YAML with 30 measured blocks.
- Configure the 200-millisecond YAML with 300 measured blocks.
- Give both cases the same
--benchmark-run; use distinct scenarios and artifact IDs. - Alternate cadence order across repetitions (
2s,200ms, then200ms,2s) to reduce thermal and cache-order bias.
Both YAMLs should drive one fixed Blake2f precompile payload at 50,000 rounds, with sender count, in-flight limits, batching, funding, and seed held identical. The differing block counts are deliberate: 30 blocks at 2 seconds and 300 at 200ms both cover a 60-second window, so the two cases measure the same span of time.
Keep maximum-throughput tuning separate from fixed-offered-load comparisons; they answer different questions. Record whether each run used a warm or cold page cache, too. Restoring both datadirs from the same snapshot does not by itself put the page cache in the same state.
Snapshot roles persist every block, which is what leaves the disposable datadirs inspectable afterwards.
Compare in base/benchmark
Section titled “Compare in base/benchmark”Collect the finished output directories under one parent:
results/ blake2f-2s-run-1/ blake2f-200ms-run-1/From the paired base/benchmark repository, serve that directory and start the frontend in API mode:
make build-server./bin/report-server --local-dir /absolute/path/to/results --port 8080
cd reportyarn installVITE_DATA_SOURCE=api \ VITE_API_BASE_URL=http://127.0.0.1:8080/ \ VITE_ALLOWED_HOSTS=localhost \ yarn dev --host 127.0.0.1 --port 3000Open http://127.0.0.1:3000/#/run-comparison/snapshot-throughput. Filter Transaction Payload=blake2f, pick the Scenario values and the node role you want, then set Show Line Per to Block Time Milliseconds. That draws the 2-second and 200-millisecond series together while keeping their scenarios distinct.
The x-axis lines up because the metric files record BlockNumber in two-second-equivalent units. At 200ms, block 1 is 0.1 and block 10 is 1.0, so a 6,000-block run ends at 600.0 — directly comparable with a 600-block run at 2 seconds.
The report server can also synthesise extra comparison rows from the source runs. Those are views, not executions: cite the original run IDs when you refer to raw artifacts.
Cleanup
Section titled “Cleanup”Once the harness exits, keep the result directory and reset only the disposable datadirs through your environment’s snapshot lifecycle.
If a run was interrupted, look for leftover base-bench or base-devnet processes before you remove its datadirs. Never remove or modify the immutable source snapshot.