Skip to content

feat: add load-test payload worker for base-load-test integration#171

Open
meyer9 wants to merge 15 commits intomainfrom
feature/load-test-worker
Open

feat: add load-test payload worker for base-load-test integration#171
meyer9 wants to merge 15 commits intomainfrom
feature/load-test-worker

Conversation

@meyer9
Copy link
Copy Markdown
Collaborator

@meyer9 meyer9 commented Mar 27, 2026

Summary

Adds a new load-test payload worker type that integrates the Rust base-load-test binary as an external transaction generator for benchmarks.

  • New LoadTestPayloadWorker in runner/payload/loadtest/ — follows the same proxy-based pattern as tx-fuzz, generating a temp YAML config and spawning the binary
  • CLI flag --load-test-bin / BASE_BENCH_LOAD_TEST_BIN for binary path
  • Config interface extended with LoadTestBinary() method
  • Factory routes load-test payload type to the new worker
  • Bug fix: proxy.go switched from rlp.DecodeBytes to tx.UnmarshalBinary() to support typed (EIP-2718) transactions — the previous code only handled legacy transactions

Testing

Tested locally end-to-end: benchmark harness successfully started the load-test binary, captured transactions via the proxy, and built blocks with them. numSuccess=1 numFailure=0.

Companion PR

  • base/base: feature/load-test-benchmark (adds binary target + workflow)

meyer9 added 2 commits March 27, 2026 11:09
Add a new payload worker type 'load-test' that integrates the Rust
base-load-test binary as an external transaction generator. The worker
follows the same proxy-based pattern as the tx-fuzz integration:

- New LoadTestPayloadWorker spawns the binary with a generated YAML
  config pointing to a proxy server that captures transactions
- CLI flag --load-test-bin (env: BASE_BENCH_LOAD_TEST_BIN) for binary path
- Config interface extended with LoadTestBinary() method
- Factory routes 'load-test' payload type to the new worker
- Generated config supports configurable sender_count, target_gps,
  and duration via YAML payload params
The proxy server used rlp.DecodeBytes to decode captured transactions,
which only handles legacy transactions. Typed transactions (EIP-2718,
e.g. EIP-1559 type 2) have a type prefix byte before the RLP payload
that causes rlp.DecodeBytes to fail with 'typed transaction too short'.

Switch to Transaction.UnmarshalBinary() which handles both legacy and
typed transaction formats.

Also fix load-test worker config to include required fields for
calldata (max_size) and precompile (target) transaction types.
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Mar 27, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

meyer9 added 5 commits March 27, 2026 11:36
- sender_count and transactions passed from benchmark YAML payload params
- target_gps computed from gas_limit / block_time (no hardcoding)
- seed randomized using crypto/rand
- duration set to 99999s (process killed on Stop)
- Stop() kills the load-test process and reaps it
- transactions YAML passed through as raw yaml.Node to support the full
  Rust config schema (transfer, calldata, precompile, erc20, etc.)
DebugResponse unconditionally tried gzip decompression on every
proxied response, logging at Error level when it failed. Most
responses are plain JSON (not gzip-compressed), causing hundreds
of spurious error lines per second.

Fall back to logging the raw body at Debug level when the response
is not gzip-encoded.
@meyer9 meyer9 marked this pull request as ready for review March 27, 2026 18:52
- Add load-test.yaml workflow: triggers on PR and workflow_dispatch,
  builds binaries via _build-binaries.yaml, runs load-test benchmark,
  builds report, uploads output and report artifacts (always, even on
  failure)
- Update _build-binaries.yaml to build and upload base-load-test
  artifact alongside base-reth-node and builder
- Update build-base-reth-node.sh to also build base-load-test binary
- Add configs/examples/load-test.yml benchmark config (10 senders,
  70% transfer / 20% calldata / 10% precompile, 10 blocks, 1B gas)
Comment on lines +19 to +98
name: Run load test benchmark
runs-on: ubuntu-latest
needs: [build-binaries]
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0

- name: Download base-reth-node
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: base-reth-node
path: ${{ runner.temp }}/bin/

- name: Download builder
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: builder
path: ${{ runner.temp }}/bin/

- name: Download base-load-test
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: base-load-test
path: ${{ runner.temp }}/bin/

- name: Make binaries executable
run: chmod +x ${{ runner.temp }}/bin/*

- name: Run load test benchmark
run: |
mkdir -p ${{ runner.temp }}/data-dir
mkdir -p ${{ runner.temp }}/output

go run benchmark/cmd/main.go \
--log.level info \
run \
--config configs/examples/load-test.yml \
--root-dir ${{ runner.temp }}/data-dir \
--output-dir ${{ runner.temp }}/output \
--builder-bin ${{ runner.temp }}/bin/builder \
--base-reth-node-bin ${{ runner.temp }}/bin/base-reth-node \
--load-test-bin ${{ runner.temp }}/bin/base-load-test

- name: Setup Node.js
if: always()
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"

- name: Build Report
if: always()
run: |
cp -r ${{ runner.temp }}/output/ ./output/ || true
pushd report
npm install
npm run build
popd

- name: Upload Output
if: always()
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: load-test-output
path: ${{ runner.temp }}/output/
retention-days: 7

- name: Upload Report
if: always()
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: load-test-report
path: report/dist/
retention-days: 7

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 14 hours ago

In general, the fix is to add an explicit permissions block so that the GITHUB_TOKEN used by this workflow is restricted to the least privilege it needs. For a typical test/build workflow that only checks out code and works with artifacts, contents: read is usually sufficient. This matches CodeQL’s suggested minimal starting point and is unlikely to break existing behavior.

The single best fix here, without changing functionality, is to add a root-level permissions block just after the on: section in .github/workflows/load-test.yaml. This will apply to both jobs (build-binaries and load-test) unless they define their own permissions. Based on the visible steps, the workflow only needs read access to repository contents to allow actions/checkout to function, and no write scopes are required. Thus we can safely set:

permissions:
  contents: read

No additional imports, methods, or definitions are needed; this is purely a YAML configuration change within the workflow file.

Suggested changeset 1
.github/workflows/load-test.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml
--- a/.github/workflows/load-test.yaml
+++ b/.github/workflows/load-test.yaml
@@ -4,6 +4,9 @@
   pull_request:
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 concurrency:
   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
   cancel-in-progress: true
EOF
@@ -4,6 +4,9 @@
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
Copilot is powered by AI and may make mistakes. Always verify output.
Both examples.yaml and load-test.yaml now use the same base repo SHA
(38de4e038) which has the base-load-test binary target. The shared
_build-binaries.yaml and build script build all three binaries.
meyer9 added 3 commits March 27, 2026 12:33
setup-rust-toolchain runs cargo metadata to compute a cache key,
but the benchmark repo has no Cargo.toml (it's Go). This causes
exit code 101. Disable the built-in cache since we use our own
actions/cache step for the built binaries.
The short SHA was not fetchable because git clone only fetches the
default branch. Use the branch name instead and add a fetch of the
specific ref before checkout to handle both branch names and SHAs.
Update versions.env to point at feature/load-test-benchmark and
update the default in _build-binaries.yaml. Remove explicit version
overrides from load-test.yaml and examples.yaml.
Comment on lines +13 to +16
name: Build binaries
uses: ./.github/workflows/_build-binaries.yaml

load-test:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 14 hours ago

In general, the fix is to add an explicit permissions: block to the workflow (at the top level or per job) that grants only the minimal scopes required. Because this workflow only reads the repository and uses artifacts, it can safely use contents: read and no other write scopes. Defining permissions at the workflow root applies to all jobs that do not override it.

The best fix here is to add a root-level permissions: block just after the on: block, setting contents: read. This documents that the workflow only needs read access to the repo contents and ensures the GITHUB_TOKEN will not unexpectedly have broader privileges if organization or repository defaults change. No functional behavior of the workflow changes, since none of the steps require write access to repository contents or other resources.

Concretely:

  • Edit .github/workflows/load-test.yaml.
  • After line 5 (workflow_dispatch:) and before the concurrency: block, insert:
permissions:
  contents: read

No imports or additional methods are needed because this is purely a YAML configuration change for the workflow.

Suggested changeset 1
.github/workflows/load-test.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml
--- a/.github/workflows/load-test.yaml
+++ b/.github/workflows/load-test.yaml
@@ -4,6 +4,9 @@
   pull_request:
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 concurrency:
   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
   cancel-in-progress: true
EOF
@@ -4,6 +4,9 @@
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
Copilot is powered by AI and may make mistakes. Always verify output.
meyer9 added 2 commits March 27, 2026 15:25
cargo can't find the bin target without -p base-load-tests because
it's in a different workspace package than the default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants