feat(reth-bench): add --reth-new-payload flag to use reth_newPayload* endpoints #40
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Runs engine benchmarks by replaying real blocks via the Engine API against a reth | |
| # node backed by a local snapshot managed with schelk. | |
| # | |
| # Runs the same binary twice on the same block range (snapshot recovered between | |
| # runs) to measure variance and overlay both runs on charts. | |
| # | |
| # The self-hosted runner must have: | |
| # - schelk initialised (virgin + scratch volumes, ramdisk) | |
| # - A JWT secret at /reth-bench/jwt.hex | |
| # - The BENCH_RPC_URL secret set to a reference RPC endpoint | |
| # | |
| # See docs/repo/ci.md for runner setup instructions. | |
| name: bench-engine | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| blocks: | |
| description: "Number of blocks to benchmark" | |
| required: false | |
| default: "50" | |
| type: string | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTC_WRAPPER: sccache | |
| BENCH_BLOCKS: ${{ inputs.blocks || '1000' }} | |
| BENCH_RPC_URL: https://ethereum.reth.rs/rpc | |
| SCHELK_MOUNT: /reth-bench | |
| JWT_SECRET: /reth-bench/jwt.hex | |
| concurrency: | |
| group: bench-engine | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| engine-bench: | |
| name: engine-bench | |
| runs-on: [self-hosted, Linux, X64] | |
| timeout-minutes: 120 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: true | |
| fetch-depth: 0 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: mozilla-actions/sccache-action@v0.0.9 | |
| # ── Build binaries ───────────────────────────────────────────── | |
| - name: Fetch or build binaries | |
| run: | | |
| MC="mc --config-dir /home/ubuntu/.mc" | |
| COMMIT="${{ github.event.pull_request.head.sha || github.sha }}" | |
| BUCKET="minio/reth-binaries/${COMMIT}" | |
| if $MC stat "${BUCKET}/reth" &>/dev/null && $MC stat "${BUCKET}/reth-bench" &>/dev/null; then | |
| echo "Cache hit for ${COMMIT}, downloading binaries..." | |
| mkdir -p target/profiling | |
| $MC cp "${BUCKET}/reth" target/profiling/reth | |
| $MC cp "${BUCKET}/reth-bench" /home/ubuntu/.cargo/bin/reth-bench | |
| chmod +x target/profiling/reth /home/ubuntu/.cargo/bin/reth-bench | |
| else | |
| echo "Cache miss for ${COMMIT}, building from source..." | |
| rustup show active-toolchain || rustup default stable | |
| make profiling | |
| make install-reth-bench | |
| fi | |
| # ── Clean up any leftover state ─────────────────────────────── | |
| - name: Pre-flight cleanup | |
| run: | | |
| pkill -9 reth || true | |
| mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true | |
| # ── Run 1 ────────────────────────────────────────────────────── | |
| - name: Mount snapshot (run 1) | |
| run: | | |
| sudo schelk mount -y | |
| sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' | |
| - name: Start reth (run 1) | |
| run: | | |
| target/profiling/reth node \ | |
| --datadir "$SCHELK_MOUNT/datadir" \ | |
| --authrpc.jwtsecret "$JWT_SECRET" \ | |
| --debug.startup-sync-state-idle \ | |
| --engine.accept-execution-requests-hash \ | |
| --http \ | |
| --http.port 8545 \ | |
| --ws \ | |
| --ws.api all \ | |
| --authrpc.port 8551 \ | |
| > /tmp/reth-bench-node-run1.log 2>&1 & | |
| echo "RETH_PID=$!" >> "$GITHUB_ENV" | |
| for i in $(seq 1 60); do | |
| if curl -sf http://127.0.0.1:8545 -X POST \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ | |
| > /dev/null 2>&1; then | |
| echo "reth (run 1) is ready after ${i}s" | |
| break | |
| fi | |
| if [ "$i" -eq 60 ]; then | |
| echo "::error::reth (run 1) failed to start within 60s" | |
| cat /tmp/reth-bench-node-run1.log | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| - name: Warmup (run 1) | |
| run: | | |
| reth-bench new-payload-fcu \ | |
| --rpc-url "$BENCH_RPC_URL" \ | |
| --engine-rpc-url http://127.0.0.1:8551 \ | |
| --jwt-secret "$JWT_SECRET" \ | |
| --advance 50 \ | |
| --reth-new-payload | |
| - name: Run benchmark (run 1) | |
| run: | | |
| reth-bench new-payload-fcu \ | |
| --rpc-url "$BENCH_RPC_URL" \ | |
| --engine-rpc-url http://127.0.0.1:8551 \ | |
| --jwt-secret "$JWT_SECRET" \ | |
| --advance "$BENCH_BLOCKS" \ | |
| --reth-new-payload \ | |
| --output /tmp/bench-results-run1 | |
| - name: Stop reth (run 1) | |
| if: always() | |
| run: | | |
| if [ -n "${RETH_PID:-}" ] && kill -0 "$RETH_PID" 2>/dev/null; then | |
| kill "$RETH_PID" | |
| for i in $(seq 1 30); do | |
| kill -0 "$RETH_PID" 2>/dev/null || break | |
| sleep 1 | |
| done | |
| kill -9 "$RETH_PID" 2>/dev/null || true | |
| fi | |
| - name: Recover snapshot (run 1) | |
| if: always() | |
| run: mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true | |
| # ── Run 2 ────────────────────────────────────────────────────── | |
| - name: Mount snapshot (run 2) | |
| run: | | |
| sudo schelk mount -y | |
| sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' | |
| - name: Start reth (run 2) | |
| run: | | |
| target/profiling/reth node \ | |
| --datadir "$SCHELK_MOUNT/datadir" \ | |
| --authrpc.jwtsecret "$JWT_SECRET" \ | |
| --debug.startup-sync-state-idle \ | |
| --engine.accept-execution-requests-hash \ | |
| --http \ | |
| --http.port 8545 \ | |
| --ws \ | |
| --ws.api all \ | |
| --authrpc.port 8551 \ | |
| > /tmp/reth-bench-node-run2.log 2>&1 & | |
| echo "RETH_PID=$!" >> "$GITHUB_ENV" | |
| for i in $(seq 1 60); do | |
| if curl -sf http://127.0.0.1:8545 -X POST \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ | |
| > /dev/null 2>&1; then | |
| echo "reth (run 2) is ready after ${i}s" | |
| break | |
| fi | |
| if [ "$i" -eq 60 ]; then | |
| echo "::error::reth (run 2) failed to start within 60s" | |
| cat /tmp/reth-bench-node-run2.log | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| - name: Warmup (run 2) | |
| run: | | |
| reth-bench new-payload-fcu \ | |
| --rpc-url "$BENCH_RPC_URL" \ | |
| --engine-rpc-url http://127.0.0.1:8551 \ | |
| --jwt-secret "$JWT_SECRET" \ | |
| --advance 50 \ | |
| --reth-new-payload | |
| - name: Run benchmark (run 2) | |
| run: | | |
| reth-bench new-payload-fcu \ | |
| --rpc-url "$BENCH_RPC_URL" \ | |
| --engine-rpc-url http://127.0.0.1:8551 \ | |
| --jwt-secret "$JWT_SECRET" \ | |
| --advance "$BENCH_BLOCKS" \ | |
| --reth-new-payload \ | |
| --output /tmp/bench-results | |
| - name: Stop reth (run 2) | |
| if: always() | |
| run: | | |
| if [ -n "${RETH_PID:-}" ] && kill -0 "$RETH_PID" 2>/dev/null; then | |
| kill "$RETH_PID" | |
| for i in $(seq 1 30); do | |
| kill -0 "$RETH_PID" 2>/dev/null || break | |
| sleep 1 | |
| done | |
| kill -9 "$RETH_PID" 2>/dev/null || true | |
| fi | |
| - name: Recover snapshot (run 2) | |
| if: always() | |
| run: mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true | |
| # ── Results & charts ────────────────────────────────────────── | |
| - name: Parse results | |
| id: results | |
| if: success() | |
| run: | | |
| SUMMARY_ARGS="/tmp/bench-results/combined_latency.csv /tmp/bench-results/total_gas.csv" | |
| SUMMARY_ARGS="$SUMMARY_ARGS --output-summary /tmp/bench-summary.json" | |
| SUMMARY_ARGS="$SUMMARY_ARGS --output-markdown /tmp/bench-comment.md" | |
| if [ -f /tmp/bench-results-run1/combined_latency.csv ]; then | |
| SUMMARY_ARGS="$SUMMARY_ARGS --baseline-csv /tmp/bench-results-run1/combined_latency.csv" | |
| fi | |
| python3 .github/scripts/bench-engine-summary.py $SUMMARY_ARGS | |
| - name: Generate charts | |
| if: success() | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| CHART_ARGS="/tmp/bench-results/combined_latency.csv --output-dir /tmp/bench-charts" | |
| if [ -f /tmp/bench-results-run1/combined_latency.csv ]; then | |
| CHART_ARGS="$CHART_ARGS --baseline /tmp/bench-results-run1/combined_latency.csv" | |
| fi | |
| uv run --with matplotlib python3 .github/scripts/bench-engine-charts.py $CHART_ARGS | |
| - name: Upload results | |
| if: success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: bench-engine-results | |
| path: | | |
| /tmp/bench-results/ | |
| /tmp/bench-results-run1/ | |
| /tmp/bench-summary.json | |
| /tmp/bench-charts/ | |
| - name: Cache baseline | |
| if: github.ref == 'refs/heads/main' && success() | |
| run: cp /tmp/bench-summary.json /reth-bench/baseline.json | |
| - name: Push charts | |
| id: push-charts | |
| if: github.event_name == 'pull_request' && success() | |
| run: | | |
| PR_NUMBER=${{ github.event.pull_request.number }} | |
| RUN_ID=${{ github.run_id }} | |
| CHART_DIR="pr/${PR_NUMBER}/${RUN_ID}" | |
| if git fetch origin bench-charts 2>/dev/null; then | |
| git checkout bench-charts | |
| else | |
| git checkout --orphan bench-charts | |
| git rm -rf . 2>/dev/null || true | |
| fi | |
| mkdir -p "${CHART_DIR}" | |
| cp /tmp/bench-charts/*.png "${CHART_DIR}/" | |
| git add "${CHART_DIR}" | |
| git -c user.name="github-actions" -c user.email="github-actions@github.com" \ | |
| commit -m "bench charts for PR #${PR_NUMBER} run ${RUN_ID}" | |
| git push origin bench-charts | |
| echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Compare & comment | |
| if: github.event_name == 'pull_request' && success() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let comment = ''; | |
| try { | |
| comment = fs.readFileSync('/tmp/bench-comment.md', 'utf8'); | |
| } catch (e) { | |
| comment = '⚠️ Engine benchmark completed but failed to generate comparison.'; | |
| } | |
| const sha = '${{ steps.push-charts.outputs.sha }}'; | |
| const prNumber = context.issue.number; | |
| const runId = '${{ github.run_id }}'; | |
| const baseUrl = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${sha}/pr/${prNumber}/${runId}`; | |
| const charts = [ | |
| { file: 'latency_throughput.png', label: 'Latency & Throughput' }, | |
| { file: 'wait_breakdown.png', label: 'Wait Time Breakdown' }, | |
| { file: 'gas_vs_latency.png', label: 'Gas vs Latency' }, | |
| ]; | |
| let chartMarkdown = '\n\n### Charts\n\n'; | |
| for (const chart of charts) { | |
| chartMarkdown += `<details><summary>${chart.label}</summary>\n\n`; | |
| chartMarkdown += `\n\n`; | |
| chartMarkdown += `</details>\n\n`; | |
| } | |
| comment += chartMarkdown; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const marker = '<!-- bench-engine-results -->'; | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| const body = `${marker}\n${comment}`; | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Upload node log | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: reth-node-log | |
| path: | | |
| /tmp/reth-bench-node-run1.log | |
| /tmp/reth-bench-node-run2.log |