Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/ci.action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,122 @@ jobs:
instances:
- id: reth
client: reth

# Exercises the builder pipeline end-to-end: state-actor builds OSAKA
# snapshots for nethermind + besu, the eest_payloads builder fills the
# compute "bn128" subset with the besu filler, then the runner replays
# those fixtures on nethermind and besu. datadir_method: copy + a small
# 256 MB snapshot keep it within a GitHub-hosted runner.
state-actor:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# The client + fill images and the locally built benchmarkoor image are
# several GB; free the runner's preinstalled toolchains to be safe.
- name: Free disk space
shell: bash
run: |
sudo rm -rf /usr/local/lib/android /opt/ghc /usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL /usr/share/dotnet /usr/local/share/boost
df -h /

- name: Build snapshots + fill payloads, then run (nethermind + besu)
uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
git-ref: ${{ github.event.pull_request.head.sha }}
git-repo: ${{ github.event.pull_request.head.repo.clone_url }}
build-args: '--force'
build-config: |
builder:
state_actor:
images:
nethermind: ghcr.io/ethereum/state-actor-nethermind:main
besu: ghcr.io/ethereum/state-actor-besu:main
pull_policy: always
config:
seed: 1234
fork: osaka
chain: 1337
gas_limit: 300000000
target_size: 256MB
spec:
entities:
# Predeploy the Arachnid CREATE2 factory so the EEST fill
# bootstrap detects it and skips the keyless deploy.
- kind: contract
template: create2_factory
name: deterministic-deployment-proxy
address: 0x4e59b44847b379578588920cA78FbF26c0B4956C
# Pre-fund fill-stateful's seed (rpc_seed_key=0x..01 below)
# so it skips the withdrawal funding block.
- kind: eoa
name: fill-stateful-seed
address: 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
balance: "10000000000000000000000000000"
nonce: 0
targets:
- client: nethermind
output_dir: /tmp/benchmarkoor/state-actor/nethermind
- client: besu
output_dir: /tmp/benchmarkoor/state-actor/besu
eest_payloads:
# Fill image is built from the Dockerfile embedded in the binary.
pull_policy: always
eest_repo: https://github.com/ethereum/execution-specs.git
eest_ref: devnets/bal/7-bench
config:
fork: osaka
rpc_seed_key: "0x0000000000000000000000000000000000000000000000000000000000000001"
gas_benchmark_values: [10]
datadir_method: copy
tests:
- tests/benchmark/compute
filter: bn128
targets:
- name: payload-generator-besu
filler_client: besu
# besu image carrying testing_buildBlockV1 (drives fill-stateful).
filler_image: ethpandaops/besu:bal-devnet-7
source_dir: /tmp/benchmarkoor/state-actor/besu
genesis: /tmp/benchmarkoor/state-actor/besu/besu-chainspec.json
output_dir: /tmp/benchmarkoor/eest-fixtures/besu
run-config-urls: >-
https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.sha }}/.github/workflows/config/benchmarkoor-global-cleanup.yaml,
https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.sha }}/.github/workflows/config/benchmarkoor-global-clientstdout.yaml
run-config: |
runner:
benchmark:
tests:
source:
eest_fixtures:
local_fixtures_dir: /tmp/benchmarkoor/eest-fixtures/besu
fixtures_subdir: blockchain_tests_stateful_engine
client:
config:
bootstrap_fcu:
enabled: true
max_retries: 10
backoff: 1s
genesis:
nethermind: /tmp/benchmarkoor/state-actor/nethermind/parity-chainspec.json
besu: /tmp/benchmarkoor/state-actor/besu/besu-chainspec.json
datadirs:
nethermind:
source_dir: /tmp/benchmarkoor/state-actor/nethermind
method: copy
besu:
source_dir: /tmp/benchmarkoor/state-actor/besu
method: copy
instances:
- id: nethermind
client: nethermind
image: nethermindeth/nethermind:master
extra_args: [--Init.BaseDbPath=/data]
- id: besu
client: besu
image: ethpandaops/besu:bal-devnet-7
extra_args: [--p2p-enabled=true]
67 changes: 67 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ inputs:
required: false
default: ''

build-config-urls:
description: 'Comma-separated URLs for config files to download and pass to `benchmarkoor build` via --config (merged in order). Setting this or build-config enables the build phase (state-actor snapshots + eest payloads), which runs before the benchmark run.'
required: false
default: ''

build-config:
description: 'Raw YAML config content to pass to `benchmarkoor build` via --config (appended after URL config). Setting this or build-config-urls enables the build phase.'
required: false
default: ''

build-args:
description: 'Extra flags passed to the benchmarkoor build command (e.g. --force, --limit-state-actor-target=)'
required: false
default: ''

tmp-dir:
description: 'Temporary directory path used for benchmarkoor data (overlayfs) and cache'
required: false
Expand Down Expand Up @@ -109,6 +124,56 @@ runs:

echo "binary=${BIN}" >> "$GITHUB_OUTPUT"

- name: Build Benchmarkoor artifacts
id: build-benchmarkoor
if: inputs.build-config != '' || inputs.build-config-urls != ''
shell: bash
env:
INPUT_BUILD_CONFIG: ${{ inputs.build-config }}
INPUT_BUILD_CONFIG_URLS: ${{ inputs.build-config-urls }}
run: |
BIN="${{ steps.extract-binary.outputs.binary }}"

# Download URL configs if provided (comma-separated)
if [ -n "$INPUT_BUILD_CONFIG_URLS" ]; then
IFS=',' read -ra CONFIG_URLS <<< "$INPUT_BUILD_CONFIG_URLS"
for i in "${!CONFIG_URLS[@]}"; do
URL=$(echo "${CONFIG_URLS[$i]}" | xargs) # trim whitespace
echo "Downloading build config from ${URL}"
curl -fsSL "${URL}" -o "${{ runner.temp }}/benchmarkoor-build-config-url-${i}.yaml"
done
fi

# Write inline config if provided
if [ -n "$INPUT_BUILD_CONFIG" ]; then
echo "$INPUT_BUILD_CONFIG" > "${{ runner.temp }}/benchmarkoor-build-config-inline.yaml"
fi

# Build argv as an array so quoted values survive intact. Config files
# apply in order: URL configs, then inline config (last wins). This step
# runs from the workspace, so any relative paths in the config (e.g. a
# custom fill_dockerfile) resolve against the checkout.
ARGS=("build")
for cfg in "${{ runner.temp }}"/benchmarkoor-build-config-url-*.yaml; do
[ -f "$cfg" ] && ARGS+=("--config=$cfg")
done
if [ -f "${{ runner.temp }}/benchmarkoor-build-config-inline.yaml" ]; then
ARGS+=("--config=${{ runner.temp }}/benchmarkoor-build-config-inline.yaml")
fi

# Append extra build args if provided (whitespace-split, same as run).
if [ -n "${{ inputs.build-args }}" ]; then
# shellcheck disable=SC2206 # intentional word-split for argv
EXTRA_ARGS=(${{ inputs.build-args }})
ARGS+=("${EXTRA_ARGS[@]}")
fi

# `sudo -E` for the same reasons as the run step: keeps the built
# snapshots/fixtures root-owned (so the rootful run phase can read
# them) and propagates caller env (e.g. STATE_DIR) into the build.
echo "Running: sudo -E $BIN ${ARGS[*]}"
sudo -E "$BIN" "${ARGS[@]}"

- name: Get Job ID from GH API
id: get-job-id
shell: bash
Expand Down Expand Up @@ -253,5 +318,7 @@ runs:
rm -rf "${{ runner.temp }}/results"
rm -f "${{ runner.temp }}/benchmarkoor-config-url-"*.yaml
rm -f "${{ runner.temp }}/benchmarkoor-config-inline.yaml"
rm -f "${{ runner.temp }}/benchmarkoor-build-config-url-"*.yaml
rm -f "${{ runner.temp }}/benchmarkoor-build-config-inline.yaml"
rm -f "${{ runner.temp }}/benchmarkoor-results.tar.gz"
rm -f "${{ runner.temp }}/benchmarkoor"
12 changes: 4 additions & 8 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,15 +275,11 @@ runner:
# # Boots a filler client on a copy of a snapshot datadir (e.g. a state_actor
# # output_dir above), records engine-API payloads, and writes fixtures that
# # `run` consumes via tests.source.eest_fixtures. Runs after state_actor.
# # Only geth supports fill-stateful's testing_buildBlockV1 today. Build the
# # fill image from the repo's Dockerfile.eest-filler (execution-specs #2637):
# # docker build -f Dockerfile.eest-filler -t ghcr.io/your-org/eest-fill-stateful:latest .
# # The fill image (uv/python toolchain) is built from a Dockerfile embedded in
# # the binary by default — nothing to publish. Optionally pull or customise it.
# eest_payloads:
# fill_image: ghcr.io/your-org/eest-fill-stateful:latest
# # Instead of a pre-built fill_image, point at a Dockerfile and benchmarkoor
# # builds it (with the container runtime) at build time, tagging it fill_image
# # if set or a default local tag. One of fill_image / fill_dockerfile is required.
# # fill_dockerfile: Dockerfile.eest-filler
# # fill_image: ghcr.io/your-org/eest-fill-stateful:latest # pull a pre-built image instead
# # fill_dockerfile: pkg/builder/Dockerfile.eest-filler # or build from a custom Dockerfile
# # pull_policy: always # always | if-not-present | never
# # container_runtime: docker # defaults to runner.container_runtime, then docker
# # jwt: <hex> # Engine API secret shared with the filler (default: built-in)
Expand Down
15 changes: 8 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1646,16 +1646,17 @@ builder:

> **Filler client:** `geth` (`ethpandaops/geth:master`) is the production-ready filler. `nethermind` (`nethermindeth/nethermind:master`) also works — it implements `testing_buildBlockV1` with correct EIP-7928 block-access-lists, and `fill-stateful`'s per-test rewind falls back to `debug_resetHead` for it (nethermind has no `debug_setHead`). `besu` works too with an image carrying the merged `TestingBuildBlockV1` coinbase fix (e.g. `ethpandaops/besu:bal-devnet-7`); benchmarkoor auto-pins its session priority fee.
>
> **Fill image:** there is no published `fill-stateful` image yet (the command lands in execution-specs [#2637](https://github.com/ethereum/execution-specs/pull/2637)). Build one from the repo's `Dockerfile.eest-filler` (it bundles `uv` + execution-specs) and point `fill_image` at it:
> **Fill image:** by default benchmarkoor builds the fill image (the `uv`/python toolchain that runs `fill-stateful`) from a Dockerfile **embedded in the binary** — nothing to publish or pass. To pull a pre-built image instead, set `fill_image`; to build from a custom Dockerfile, set `fill_dockerfile`. The embedded Dockerfile lives at `pkg/builder/Dockerfile.eest-filler`; to build it by hand:
> ```bash
> docker build -f Dockerfile.eest-filler -t ghcr.io/your-org/eest-fill-stateful:latest .
> docker build -f pkg/builder/Dockerfile.eest-filler -t ghcr.io/your-org/eest-fill-stateful:latest .
> ```

```yaml
builder:
eest_payloads:
fill_image: ghcr.io/your-org/eest-fill-stateful:latest # image carrying `uv run fill-stateful`
# fill_dockerfile: Dockerfile.eest-filler # build the fill image instead of pulling one
# Fill image defaults to a Dockerfile embedded in the binary. Optionally:
# fill_image: ghcr.io/your-org/eest-fill-stateful:latest # pull a pre-built image instead
# fill_dockerfile: pkg/builder/Dockerfile.eest-filler # or build from a custom Dockerfile
pull_policy: always # always | if-not-present | never (default: always)
container_runtime: docker # docker | podman (default: inherits runner.container_runtime, then docker)
# jwt: <hex> # Engine API secret, shared with the filler (default: benchmarkoor's DefaultJWT)
Expand Down Expand Up @@ -1684,9 +1685,9 @@ builder:

| Option | Type | Default | Description |
|---|---|---|---|
| `fill_image` | string | – | Pre-built container image carrying the uv/python toolchain that runs `fill-stateful`. Required unless `fill_dockerfile` is set. |
| `fill_dockerfile` | string | – | Path to a Dockerfile (e.g. `Dockerfile.eest-filler`) that benchmarkoor builds with the container runtime at build time, instead of pulling a pre-built image. Tagged `fill_image` when set, else `benchmarkoor-eest-fill:local`. Requires the runtime's `build` CLI (docker/podman) on the host. One of `fill_image` / `fill_dockerfile` is required. |
| `pull_policy` | string | `always` | One of `always`, `if-not-present`, `never`. Applies to both the fill image and the filler image (ignored for a locally built `fill_dockerfile`). |
| `fill_image` | string | – | Pre-built container image carrying the uv/python toolchain that runs `fill-stateful`. Optional: when neither this nor `fill_dockerfile` is set, benchmarkoor builds the fill image from a Dockerfile embedded in the binary. |
| `fill_dockerfile` | string | – | Path to a custom Dockerfile that benchmarkoor builds with the container runtime at build time, instead of pulling a pre-built image or using the embedded default. Tagged `fill_image` when set, else `benchmarkoor-eest-fill:local`. Requires the runtime's `build` CLI (docker/podman) on the host. |
| `pull_policy` | string | `always` | One of `always`, `if-not-present`, `never`. Applies to both the fill image and the filler image (ignored for a locally built fill image). |
| `container_runtime` | string | runner's runtime, then `docker` | Container runtime for the filler + fill containers. |
| `jwt` | string | benchmarkoor's `DefaultJWT` | Engine API JWT secret; shared between the filler client and `fill-stateful`. |
| `fill_command` | []string | `[uv, run, fill-stateful]` | argv prefix invoked inside `fill_image` before the `fill-stateful` flags. Override if your image exposes the command differently. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ builder:

## Stage 2: Building EEST bloatnet/repricing fixtures
eest_payloads:
fill_dockerfile: Dockerfile.eest-filler
# Fill image defaults to a Dockerfile embedded in the binary; set fill_image
# to pull a pre-built one, or fill_dockerfile to build from a custom Dockerfile.
pull_policy: always
eest_repo: https://github.com/ethereum/execution-specs.git
# devnets/bal/7-bench carries the BAL benchmark tooling plus the fill-stateful
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ builder:

## Stage 2: Building EEST compute fixtures
eest_payloads:
# Build the fill image (uv/python toolchain) from the repo's Dockerfile instead
# of pulling a pre-built one. Alternatively set fill_image: <pre-built image>.
fill_dockerfile: Dockerfile.eest-filler
# By default benchmarkoor builds the fill image (uv/python toolchain) from a
# Dockerfile embedded in the binary. Set fill_image: <pre-built image> to pull
# one instead, or fill_dockerfile: <path> to build from a custom Dockerfile.
pull_policy: always
eest_repo: https://github.com/ethereum/execution-specs.git
# devnets/bal/7-bench carries the BAL benchmark tooling plus the fill-stateful
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ builder:

## Stage 2: Building EEST stateful (EIP-7928 / BAL) fixtures
eest_payloads:
# Build the fill image (uv/python toolchain) from the repo's Dockerfile instead
# of pulling a pre-built one. Alternatively set fill_image: <pre-built image>.
fill_dockerfile: Dockerfile.eest-filler
# By default benchmarkoor builds the fill image (uv/python toolchain) from a
# Dockerfile embedded in the binary. Set fill_image: <pre-built image> to pull
# one instead, or fill_dockerfile: <path> to build from a custom Dockerfile.
pull_policy: always
eest_repo: https://github.com/ethereum/execution-specs.git
# devnets/bal/7-bench carries the BAL benchmark tooling plus the fill-stateful
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ builder:

## Stage 2: Building EEST stateful fixtures
eest_payloads:
# Build the fill image (uv/python toolchain) from the repo's Dockerfile instead
# of pulling a pre-built one. Alternatively set fill_image: <pre-built image>.
fill_dockerfile: Dockerfile.eest-filler
# By default benchmarkoor builds the fill image (uv/python toolchain) from a
# Dockerfile embedded in the binary. Set fill_image: <pre-built image> to pull
# one instead, or fill_dockerfile: <path> to build from a custom Dockerfile.
pull_policy: always
eest_repo: https://github.com/ethereum/execution-specs.git
eest_ref: forks/amsterdam
Expand Down
22 changes: 13 additions & 9 deletions Dockerfile.eest-filler → pkg/builder/Dockerfile.eest-filler
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# Dockerfile.eest-filler
#
# Builds the toolchain image referenced by `builder.eest_payloads.fill_image`:
# it carries only `uv` + python + the build toolchain — NOT the execution-specs
# repo. benchmarkoor clones execution-specs at build time (at the configured
# `eest_ref`, caching it on disk) and mounts the checkout into this container at
# /eest, then runs `uv run fill-stateful`. uv builds the venv into the mounted
# checkout on first use (cached across runs). This keeps the EEST version in
# config and changeable without rebuilding the image.
# The default fill-image Dockerfile: it carries only `uv` + python + the build
# toolchain — NOT the execution-specs repo. benchmarkoor clones execution-specs
# at build time (at the configured `eest_ref`, caching it on disk) and mounts the
# checkout into this container at /eest, then runs `uv run fill-stateful`. uv
# builds the venv into the mounted checkout on first use (cached across runs).
# This keeps the EEST version in config and changeable without rebuilding the image.
#
# This file is embedded into the benchmarkoor binary (see pkg/builder), so by
# default the fill image is built from it with no Dockerfile needed on disk. Set
# builder.eest_payloads.fill_image to pull a pre-built image, or fill_dockerfile
# to build from a custom one.
#
# fill-stateful drives a *live* EL client over testing_buildBlockV1, so — unlike
# the t8n-based `fill` — it needs NO evmone/eels transition-tool binary.
#
# Build:
# docker build -f Dockerfile.eest-filler -t ghcr.io/your-org/eest-fill-stateful:latest .
# Build it by hand (from the repo root):
# docker build -f pkg/builder/Dockerfile.eest-filler -t ghcr.io/your-org/eest-fill-stateful:latest .
# # (podman build works identically)
#
# Then point your config at it:
Expand Down
Loading
Loading