diff --git a/.github/scripts/run-example-benchmarks.sh b/.github/scripts/run-example-benchmarks.sh index 84138b4..9e94667 100755 --- a/.github/scripts/run-example-benchmarks.sh +++ b/.github/scripts/run-example-benchmarks.sh @@ -9,8 +9,9 @@ BENCHMARK_CONFIGS=( configs/examples/erc20.yml configs/examples/simulator.yml configs/examples/sload.yml - # configs/examples/snapshot.yml + configs/examples/rbuilder.yml configs/examples/sstore.yml + # configs/examples/snapshot.yml # configs/examples/tx-fuzz-geth.yml ) @@ -26,5 +27,6 @@ for config in "${BENCHMARK_CONFIGS[@]}"; do --root-dir $TEMP_DIR/data-dir \ --output-dir $TEMP_DIR/output \ --reth-bin $TEMP_DIR/bin/reth \ - --geth-bin $TEMP_DIR/bin/geth + --geth-bin $TEMP_DIR/bin/geth \ + --rbuilder-bin $TEMP_DIR/bin/rbuilder done \ No newline at end of file diff --git a/.github/workflows/_build-binaries.yaml b/.github/workflows/_build-binaries.yaml new file mode 100644 index 0000000..2434f90 --- /dev/null +++ b/.github/workflows/_build-binaries.yaml @@ -0,0 +1,216 @@ +# Reusable workflow for building all binaries +# This workflow is called by other workflows to build reth, geth, rbuilder, op-program, and contracts +name: Build Binaries + +on: + workflow_call: + inputs: + reth_version: + description: "Reth version to build" + required: false + type: string + default: "27a8c0f5a6dfb27dea84c5751776ecabdd069646" + geth_version: + description: "Geth version to build" + required: false + type: string + default: "6cbfcd5161083bcd4052edc3022d9f99c6fe40e0" + rbuilder_version: + description: "Rbuilder version to build" + required: false + type: string + default: "23f42c8e78ba3abb45a8840df7037a27e196e601" + +# Set minimal permissions for all jobs by default +permissions: + contents: read + +jobs: + build-contracts: + runs-on: ubuntu-latest + permissions: + contents: read + actions: write # Required for artifact upload + 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: Install Foundry + uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0 + - name: Cache Forge artifacts + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + with: + path: | + contracts/out + contracts/cache + key: ${{ runner.os }}-forge-${{ hashFiles('**/foundry.toml', '**/*.sol') }} + restore-keys: | + ${{ runner.os }}-forge- + + - name: Build Contracts + run: | + forge build --force + + - name: Upload contract artifacts + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: contracts + path: contracts/out/ + retention-days: 1 + + build-reth: + runs-on: ubuntu-latest + permissions: + contents: read + actions: write # Required for artifact upload + 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 Rust + uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 + + - name: Cache reth binary + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + id: cache-reth + with: + path: ~/bin/reth + key: ${{ runner.os }}-reth-${{ inputs.reth_version }} + + - name: Build reth + if: steps.cache-reth.outputs.cache-hit != 'true' + run: | + unset CI + mkdir -p ~/bin + cd clients + RETH_VERSION=${{ inputs.reth_version }} OUTPUT_DIR=~/bin ./build-reth.sh + # Rename op-reth to reth for consistency + [ -f ~/bin/op-reth ] && mv ~/bin/op-reth ~/bin/reth || true + + - name: Upload reth artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: reth + path: ~/bin/reth + retention-days: 1 + + build-geth: + runs-on: ubuntu-latest + permissions: + contents: read + actions: write # Required for artifact upload + 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: Cache geth binary + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + id: cache-geth + with: + path: ~/bin/geth + key: ${{ runner.os }}-geth-${{ inputs.geth_version }} + + - name: Build geth + if: steps.cache-geth.outputs.cache-hit != 'true' + run: | + unset CI + mkdir -p ~/bin + cd clients + GETH_VERSION=${{ inputs.geth_version }} OUTPUT_DIR=~/bin ./build-geth.sh + + - name: Upload geth artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: geth + path: ~/bin/geth + retention-days: 1 + + build-rbuilder: + runs-on: ubuntu-latest + permissions: + contents: read + actions: write # Required for artifact upload + 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 Rust + uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 + + - name: Cache rbuilder binary + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + id: cache-rbuilder + with: + path: ~/bin/rbuilder + key: ${{ runner.os }}-rbuilder-${{ inputs.rbuilder_version }} + + - name: Build rbuilder + if: steps.cache-rbuilder.outputs.cache-hit != 'true' + run: | + unset CI + mkdir -p ~/bin + cd clients + RBUILDER_VERSION=${{ inputs.rbuilder_version }} OUTPUT_DIR=~/bin ./build-rbuilder.sh + # Rename op-rbuilder to rbuilder for consistency + [ -f ~/bin/op-rbuilder ] && mv ~/bin/op-rbuilder ~/bin/rbuilder || true + + - name: Upload rbuilder artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: rbuilder + path: ~/bin/rbuilder + retention-days: 1 + + build-op-program: + runs-on: ubuntu-latest + permissions: + contents: read + actions: write # Required for artifact upload + 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: Cache op-program binary + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + id: cache-op-program + with: + path: op-program/versions/v1.6.1-rc.1/op-program + key: ${{ runner.os }}-op-program-${{ hashFiles('op-program/**') }} + + - name: Build op-program + if: steps.cache-op-program.outputs.cache-hit != 'true' + run: | + curl https://mise.run | sh + pushd op-program + mise x -- ./build.sh + popd + + - name: Upload op-program artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: op-program + path: op-program/versions/v1.6.1-rc.1/op-program + retention-days: 1 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cff9991..b79cdb9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,9 +6,15 @@ on: branches: ["main"] pull_request: +# Set minimal permissions for all jobs by default +permissions: + contents: read + jobs: go-lint: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -35,6 +41,8 @@ jobs: outputs: COVERAGE: ${{ steps.unit.outputs.coverage }} runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -54,11 +62,23 @@ jobs: run: | go test -v -coverprofile=coverage.out ./... + # Call the reusable workflow that builds all binaries in parallel + build-binaries: + permissions: + contents: read + actions: write # Required for reusable workflow to upload artifacts + uses: ./.github/workflows/_build-binaries.yaml + with: + reth_version: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 + geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 + rbuilder_version: 23f42c8e78ba3abb45a8840df7037a27e196e601 + basic-benchmarks: runs-on: ubuntu-latest - env: - RETH_VERSION: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 - GETH_VERSION: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 + needs: build-binaries + permissions: + contents: read + actions: read # Required for artifact download steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -70,59 +90,52 @@ jobs: - name: Set up Go uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 - - name: Install project dependencies run: | go mod download - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0 - - - name: Cache binaries - uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 - id: cache-bin + - name: Download contract artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - path: ${{ runner.temp }}/bin - key: ${{ runner.os }}-binaries-reth-${{ env.RETH_VERSION }}-geth-${{ env.GETH_VERSION }} - - - name: Build Contracts - run: | - forge build --force - - - name: Download geth and reth - if: steps.cache-bin.outputs.cache-hit != 'true' - run: | - unset CI - mkdir -p ${{ runner.temp }}/bin + name: contracts + path: contracts/out/ - git clone https://github.com/paradigmxyz/reth - git -C reth checkout --force ${{ env.RETH_VERSION }} + - name: Download reth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: reth + path: ${{ runner.temp }}/bin/ - pushd reth - cargo build --features asm-keccak,jemalloc --profile release --bin op-reth --manifest-path crates/optimism/bin/Cargo.toml - cp ./target/release/op-reth ${{ runner.temp }}/bin/reth - popd - chmod +x ${{ runner.temp }}/bin/reth + - name: Download geth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: geth + path: ${{ runner.temp }}/bin/ - git clone https://github.com/ethereum-optimism/op-geth - git -C op-geth checkout --force ${{ env.GETH_VERSION }} + - name: Download rbuilder binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: rbuilder + path: ${{ runner.temp }}/bin/ - pushd op-geth - make geth - cp ./build/bin/geth ${{ runner.temp }}/bin/geth - chmod +x ${{ runner.temp }}/bin/geth - popd + - name: Download op-program binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: op-program + path: ${{ runner.temp }}/bin/ - echo "binaries compiled:" + - name: Make binaries executable + run: | + chmod +x ${{ runner.temp }}/bin/* + echo "Downloaded binaries:" ls -la ${{ runner.temp }}/bin + - name: Setup op-program directory + run: | + mkdir -p op-program/versions/v1.6.1-rc.1 + cp ${{ runner.temp }}/bin/op-program op-program/versions/v1.6.1-rc.1/ + du -h -d 2 . + - name: Run examples - id: op-program run: | - curl https://mise.run | sh - pushd op-program - mise x -- ./build.sh - popd ./.github/scripts/run-example-benchmarks.sh ${{ runner.temp }} diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index e5aab36..bd61aa7 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -4,13 +4,28 @@ on: push: branches: ["*"] +# Set minimal permissions for all jobs by default +permissions: + contents: read + jobs: + # Call the reusable workflow that builds all binaries in parallel + build-binaries: + permissions: + contents: read + actions: write # Required for reusable workflow to upload artifacts + uses: ./.github/workflows/_build-binaries.yaml + with: + reth_version: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 + geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 + rbuilder_version: 23f42c8e78ba3abb45a8840df7037a27e196e601 + example-benchmarks: runs-on: ubuntu-latest - env: - RETH_VERSION: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 - GETH_VERSION: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 - + needs: build-binaries + permissions: + contents: read + actions: write # Required for artifact download and upload steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -24,52 +39,38 @@ jobs: - name: Set up Go uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 - - name: Install project dependencies run: | go mod download - - name: Cache binaries - uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 - id: cache-bin + - name: Download contract artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - path: ${{ runner.temp }}/bin - key: ${{ runner.os }}-binaries-reth-${{ env.RETH_VERSION }}-geth-${{ env.GETH_VERSION }} - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0 - - - name: Build Contracts - run: | - forge build --force - - - name: Download geth and reth - if: steps.cache-bin.outputs.cache-hit != 'true' - run: | - unset CI - mkdir -p ${{ runner.temp }}/bin - - git clone https://github.com/paradigmxyz/reth - git -C reth checkout --force ${{ env.RETH_VERSION }} + name: contracts + path: contracts/out/ - pushd reth - cargo build --features asm-keccak,jemalloc --profile release --bin op-reth --manifest-path crates/optimism/bin/Cargo.toml - cp ./target/release/op-reth ${{ runner.temp }}/bin/reth - popd - chmod +x ${{ runner.temp }}/bin/reth + - name: Download reth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: reth + path: ${{ runner.temp }}/bin/ - git clone https://github.com/ethereum-optimism/op-geth - git -C op-geth checkout --force ${{ env.GETH_VERSION }} + - name: Download geth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: geth + path: ${{ runner.temp }}/bin/ - pushd op-geth - make geth - cp ./build/bin/geth ${{ runner.temp }}/bin/geth - chmod +x ${{ runner.temp }}/bin/geth - popd + - name: Download rbuilder binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: rbuilder + path: ${{ runner.temp }}/bin/ - echo "binaries compiled:" + - name: Make binaries executable + run: | + chmod +x ${{ runner.temp }}/bin/* + echo "Downloaded binaries:" ls -la ${{ runner.temp }}/bin - name: Run Basic Benchmarks diff --git a/.github/workflows/public-benchmarks.yaml b/.github/workflows/public-benchmarks.yaml index 533da63..7a4288f 100644 --- a/.github/workflows/public-benchmarks.yaml +++ b/.github/workflows/public-benchmarks.yaml @@ -4,13 +4,28 @@ on: push: branches: ["main"] +# Set minimal permissions for all jobs by default +permissions: + contents: read + jobs: + # Call the reusable workflow that builds all binaries in parallel + build-binaries: + permissions: + contents: read + actions: write # Required for reusable workflow to upload artifacts + uses: ./.github/workflows/_build-binaries.yaml + with: + reth_version: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 + geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 + rbuilder_version: 23f42c8e78ba3abb45a8840df7037a27e196e601 + basic-benchmarks: runs-on: ubuntu-latest - env: - RETH_VERSION: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 - GETH_VERSION: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 - + needs: build-binaries + permissions: + contents: read + actions: write # Required for artifact download and upload steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -24,52 +39,32 @@ jobs: - name: Set up Go uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 - - name: Install project dependencies run: | go mod download - - name: Cache binaries - uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 - id: cache-bin + - name: Download contract artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - path: ${{ runner.temp }}/bin - key: ${{ runner.os }}-binaries-reth-${{ env.RETH_VERSION }}-geth-${{ env.GETH_VERSION }} + name: contracts + path: contracts/out/ - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0 + - name: Download reth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: reth + path: ${{ runner.temp }}/bin/ - - name: Build Contracts - run: | - forge build --force + - name: Download geth binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: geth + path: ${{ runner.temp }}/bin/ - - name: Download geth and reth - if: steps.cache-bin.outputs.cache-hit != 'true' + - name: Make binaries executable run: | - unset CI - mkdir -p ${{ runner.temp }}/bin - - git clone https://github.com/paradigmxyz/reth - git -C reth checkout --force ${{ env.RETH_VERSION }} - - pushd reth - cargo build --features asm-keccak,jemalloc --profile release --bin op-reth --manifest-path crates/optimism/bin/Cargo.toml - cp ./target/release/op-reth ${{ runner.temp }}/bin/reth - popd - chmod +x ${{ runner.temp }}/bin/reth - - git clone https://github.com/ethereum-optimism/op-geth - git -C op-geth checkout --force ${{ env.GETH_VERSION }} - - pushd op-geth - make geth - cp ./build/bin/geth ${{ runner.temp }}/bin/geth - chmod +x ${{ runner.temp }}/bin/geth - popd - - echo "binaries compiled:" + chmod +x ${{ runner.temp }}/bin/* + echo "Downloaded binaries:" ls -la ${{ runner.temp }}/bin - name: Run Basic Benchmarks diff --git a/clients/README.md b/clients/README.md index fca1bbf..8f1208d 100644 --- a/clients/README.md +++ b/clients/README.md @@ -24,8 +24,8 @@ Builds the op-geth binary from the Ethereum Optimism op-geth repository using ju Builds the op-rbuilder binary from the op-rbuilder repository using Cargo. **Default Configuration:** -- Repository: `https://github.com/haardikk21/op-rbuilder` -- Version: `a8bb38693ece585e7fa98d52f51290e7dcececff` +- Repository: `https://github.com/base/op-rbuilder` +- Version: `main` - Build tool: `cargo` ## Usage @@ -108,7 +108,7 @@ RBUILDER_VERSION="main" ./build-rbuilder.sh #### For op-rbuilder (build-rbuilder.sh): - `RBUILDER_REPO`: Git repository URL (default: https://github.com/haardikk21/op-rbuilder) -- `RBUILDER_VERSION`: Git branch, tag, or commit hash (default: a8bb38693ece585e7fa98d52f51290e7dcececff) +- `RBUILDER_VERSION`: Git branch, tag, or commit hash (default: main) - `BUILD_DIR`: Directory for source code (default: ./build) - `OUTPUT_DIR`: Directory for built binaries (default: ../bin) diff --git a/clients/build-geth.sh b/clients/build-geth.sh index 5f0a74f..9ff627d 100755 --- a/clients/build-geth.sh +++ b/clients/build-geth.sh @@ -5,13 +5,12 @@ set -e # Source versions if available, otherwise use defaults if [ -f "versions.env" ]; then source versions.env -else - # Default values - GETH_REPO="${GETH_REPO:-https://github.com/ethereum-optimism/op-geth/}" - GETH_VERSION="${GETH_VERSION:-optimism}" - BUILD_DIR="${BUILD_DIR:-./build}" - OUTPUT_DIR="${OUTPUT_DIR:-../bin}" fi +# Default values +GETH_REPO="${GETH_REPO:-https://github.com/ethereum-optimism/op-geth/}" +GETH_VERSION="${GETH_VERSION:-optimism}" +BUILD_DIR="${BUILD_DIR:-./build}" +OUTPUT_DIR="${OUTPUT_DIR:-../bin}" echo "Building op-geth binary..." echo "Repository: $GETH_REPO" @@ -28,6 +27,10 @@ if [ -d "op-geth" ]; then echo "Updating existing op-geth repository..." cd op-geth git fetch origin + + # ensure remote matches the repository + git remote set-url origin "$GETH_REPO" + git fetch origin else echo "Cloning op-geth repository..." git clone "$GETH_REPO" op-geth @@ -36,7 +39,7 @@ fi # Checkout specified version/commit echo "Checking out version: $GETH_VERSION" -git checkout "$GETH_VERSION" +git checkout -f "$GETH_VERSION" # Build the binary using Go echo "Building op-geth with Go..." @@ -44,16 +47,24 @@ go run build/ci.go install -static ./cmd/geth # Copy binary to output directory echo "Copying binary to output directory..." -mkdir -p "../../$OUTPUT_DIR" +# Handle absolute paths correctly +if [[ "$OUTPUT_DIR" == /* ]]; then + # Absolute path - use directly + FINAL_OUTPUT_DIR="$OUTPUT_DIR" +else + # Relative path - resolve from current location (clients/build/op-geth) + FINAL_OUTPUT_DIR="../../$OUTPUT_DIR" +fi +mkdir -p "$FINAL_OUTPUT_DIR" # The binary is typically built in the build directory if [ -f "build/bin/geth" ]; then - cp build/bin/geth "../../$OUTPUT_DIR/geth" + cp build/bin/geth "$FINAL_OUTPUT_DIR/geth" elif [ -f "bin/geth" ]; then - cp bin/geth "../../$OUTPUT_DIR/geth" + cp bin/geth "$FINAL_OUTPUT_DIR/geth" else - echo "Looking for geth binary..." - find . -name "geth" -type f -executable | head -1 | xargs -I {} cp {} "../../$OUTPUT_DIR/geth" + echo "No geth binary found" + exit 1 fi -echo "op-geth binary built successfully and placed in $OUTPUT_DIR/geth" +echo "op-geth binary built successfully and placed in $FINAL_OUTPUT_DIR/geth" diff --git a/clients/build-rbuilder.sh b/clients/build-rbuilder.sh index b475465..99bab2c 100755 --- a/clients/build-rbuilder.sh +++ b/clients/build-rbuilder.sh @@ -5,14 +5,14 @@ set -e # Source versions if available, otherwise use defaults if [ -f "versions.env" ]; then source versions.env -else - # Default values - RBUILDER_REPO="${RBUILDER_REPO:-https://github.com/haardikk21/op-rbuilder}" - RBUILDER_VERSION="${RBUILDER_VERSION:-main}" - BUILD_DIR="${BUILD_DIR:-./build}" - OUTPUT_DIR="${OUTPUT_DIR:-../bin}" fi +# Default values +RBUILDER_REPO="${RBUILDER_REPO:-https://github.com/base/op-rbuilder}" +RBUILDER_VERSION="${RBUILDER_VERSION:-main}" +BUILD_DIR="${BUILD_DIR:-./build}" +OUTPUT_DIR="${OUTPUT_DIR:-../bin}" + echo "Building op-rbuilder binary..." echo "Repository: $RBUILDER_REPO" echo "Version/Commit: $RBUILDER_VERSION" @@ -27,6 +27,9 @@ cd "$BUILD_DIR" if [ -d "op-rbuilder" ]; then echo "Updating existing op-rbuilder repository..." cd op-rbuilder + + # ensure remote matches the repository + git remote set-url origin "$RBUILDER_REPO" git fetch origin else echo "Cloning op-rbuilder repository..." @@ -36,24 +39,32 @@ fi # Checkout specified version/commit echo "Checking out version: $RBUILDER_VERSION" -git checkout "$RBUILDER_VERSION" +git checkout -f "$RBUILDER_VERSION" # Build the binary using cargo echo "Building op-rbuilder with cargo..." -cargo build --release +cargo build -p op-rbuilder --bin op-rbuilder --release # Copy binary to output directory echo "Copying binary to output directory..." -mkdir -p "../../$OUTPUT_DIR" +# Handle absolute paths correctly +if [[ "$OUTPUT_DIR" == /* ]]; then + # Absolute path - use directly + FINAL_OUTPUT_DIR="$OUTPUT_DIR" +else + # Relative path - resolve from current location (clients/build/op-rbuilder) + FINAL_OUTPUT_DIR="../../$OUTPUT_DIR" +fi +mkdir -p "$FINAL_OUTPUT_DIR" # Find the built binary and copy it if [ -f "target/release/op-rbuilder" ]; then - cp target/release/op-rbuilder "../../$OUTPUT_DIR/" + cp target/release/op-rbuilder "$FINAL_OUTPUT_DIR/" elif [ -f "target/release/rbuilder" ]; then - cp target/release/rbuilder "../../$OUTPUT_DIR/op-rbuilder" + cp target/release/rbuilder "$FINAL_OUTPUT_DIR/op-rbuilder" else - echo "Looking for rbuilder binary..." - find target/release -name "*rbuilder*" -type f -executable | head -1 | xargs -I {} cp {} "../../$OUTPUT_DIR/op-rbuilder" + echo "No op-rbuilder binary found" + exit 1 fi -echo "op-rbuilder binary built successfully and placed in $OUTPUT_DIR/op-rbuilder" \ No newline at end of file +echo "op-rbuilder binary built successfully and placed in $FINAL_OUTPUT_DIR/op-rbuilder" \ No newline at end of file diff --git a/clients/build-reth.sh b/clients/build-reth.sh index 35aa985..c8a1610 100755 --- a/clients/build-reth.sh +++ b/clients/build-reth.sh @@ -5,14 +5,14 @@ set -e # Source versions if available, otherwise use defaults if [ -f "versions.env" ]; then source versions.env -else - # Default values - RETH_REPO="${RETH_REPO:-https://github.com/paradigmxyz/reth/}" - RETH_VERSION="${RETH_VERSION:-main}" - BUILD_DIR="${BUILD_DIR:-./build}" - OUTPUT_DIR="${OUTPUT_DIR:-../bin}" fi +# Default values +RETH_REPO="${RETH_REPO:-https://github.com/paradigmxyz/reth/}" +RETH_VERSION="${RETH_VERSION:-main}" +BUILD_DIR="${BUILD_DIR:-./build}" +OUTPUT_DIR="${OUTPUT_DIR:-../bin}" + echo "Building reth binary..." echo "Repository: $RETH_REPO" echo "Version/Commit: $RETH_VERSION" @@ -28,6 +28,10 @@ if [ -d "reth" ]; then echo "Updating existing reth repository..." cd reth git fetch origin + + # ensure remote matches the repository + git remote set-url origin "$RETH_REPO" + git fetch origin else echo "Cloning reth repository..." git clone "$RETH_REPO" reth @@ -36,15 +40,24 @@ fi # Checkout specified version/commit echo "Checking out version: $RETH_VERSION" -git checkout "$RETH_VERSION" +git checkout -f "$RETH_VERSION" # Build the binary using cargo echo "Building reth with cargo..." -cargo build --bin op-reth --profile maxperf --manifest-path crates/optimism/bin/Cargo.toml +# Build with performance features matching CI workflow +cargo build --features asm-keccak,jemalloc --bin op-reth --profile maxperf --manifest-path crates/optimism/bin/Cargo.toml # Copy binary to output directory echo "Copying binary to output directory..." -mkdir -p "../../$OUTPUT_DIR" -cp target/maxperf/op-reth "../../$OUTPUT_DIR/" +# Handle absolute paths correctly +if [[ "$OUTPUT_DIR" == /* ]]; then + # Absolute path - use directly + FINAL_OUTPUT_DIR="$OUTPUT_DIR" +else + # Relative path - resolve from current location (clients/build/reth) + FINAL_OUTPUT_DIR="../../$OUTPUT_DIR" +fi +mkdir -p "$FINAL_OUTPUT_DIR" +cp target/maxperf/op-reth "$FINAL_OUTPUT_DIR/" -echo "reth binary built successfully and placed in $OUTPUT_DIR/reth" +echo "reth binary built successfully and placed in $FINAL_OUTPUT_DIR/op-reth" diff --git a/clients/versions.env b/clients/versions.env index 345145d..67c6ece 100644 --- a/clients/versions.env +++ b/clients/versions.env @@ -11,9 +11,9 @@ GETH_REPO="https://github.com/ethereum-optimism/op-geth/" GETH_VERSION="v1.101604.0" # Op-Rbuilder Configuration -RBUILDER_REPO="https://github.com/haardikk21/op-rbuilder" -RBUILDER_VERSION="a8bb38693ece585e7fa98d52f51290e7dcececff" +RBUILDER_REPO="https://github.com/base/op-rbuilder" +RBUILDER_VERSION="main" # Build Configuration -BUILD_DIR="./build" -OUTPUT_DIR="../bin" \ No newline at end of file +# BUILD_DIR="./build" +# OUTPUT_DIR="../bin" \ No newline at end of file diff --git a/configs/examples/rbuilder.yml b/configs/examples/rbuilder.yml new file mode 100644 index 0000000..cfff7ed --- /dev/null +++ b/configs/examples/rbuilder.yml @@ -0,0 +1,20 @@ +name: Rbuilder transfer throughput test +description: Test rbuilder transfer throughput with different number of hot accounts (meant to stress test parallelism) +payloads: + - name: Transfer Only + type: transfer-only + id: transfer-only + +benchmarks: + - variables: + - type: payload + values: + - transfer-only + - type: node_type + values: + - rbuilder + - type: num_blocks + value: 10 + - type: gas_limit + values: + - 1000000000 diff --git a/go.mod b/go.mod index 263a01b..c5dd0a7 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,14 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/ethereum-optimism/optimism v1.16.2 github.com/ethereum/go-ethereum v1.16.5 + github.com/gorilla/websocket v1.5.3 github.com/holiman/uint256 v1.3.2 github.com/pkg/errors v0.9.1 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.67.4 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v2 v2.27.7 + golang.org/x/sync v0.18.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -77,7 +79,6 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect github.com/graph-gophers/graphql-go v1.8.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.15 // indirect @@ -153,7 +154,6 @@ require ( golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect diff --git a/runner/clients/geth/client.go b/runner/clients/geth/client.go index 00a82d9..e1b54b8 100644 --- a/runner/clients/geth/client.go +++ b/runner/clients/geth/client.go @@ -270,3 +270,8 @@ func (g *GethClient) SetHead(ctx context.Context, blockNumber uint64) error { g.logger.Info("Successfully reset blockchain head", "blockNumber", blockNumber, "blockHex", blockHex) return nil } + +// FlashblocksClient returns nil as geth does not support flashblocks. +func (g *GethClient) FlashblocksClient() types.FlashblocksClient { + return nil +} diff --git a/runner/clients/rbuilder/client.go b/runner/clients/rbuilder/client.go index 790e334..7c07339 100644 --- a/runner/clients/rbuilder/client.go +++ b/runner/clients/rbuilder/client.go @@ -24,7 +24,8 @@ type RbuilderClient struct { ports portmanager.PortManager websocketPort uint64 - elClient types.ExecutionClient + elClient types.ExecutionClient + flashblocksClient types.FlashblocksClient metricsCollector metrics.Collector } @@ -59,6 +60,10 @@ func (r *RbuilderClient) Run(ctx context.Context, cfg *types.RuntimeConfig) erro if r.metricsCollector == nil { return errors.New("failed to create metrics collector") } + + // Create flashblocks client + r.flashblocksClient = NewFlashblocksClient(r.logger, r.websocketPort) + return nil } @@ -68,6 +73,13 @@ func (r *RbuilderClient) MetricsCollector() metrics.Collector { // Stop stops the reth client. func (r *RbuilderClient) Stop() { + // Stop flashblocks client if it exists + if r.flashblocksClient != nil { + if err := r.flashblocksClient.Stop(); err != nil { + r.logger.Warn("Failed to stop flashblocks client", "err", err) + } + } + r.ports.ReleasePort(r.websocketPort) r.elClient.Stop() } @@ -102,3 +114,8 @@ func (r *RbuilderClient) SetHead(ctx context.Context, blockNumber uint64) error // Rbuilder is based on reth, so delegate to the underlying reth client return r.elClient.SetHead(ctx, blockNumber) } + +// FlashblocksClient returns the flashblocks websocket client for collecting flashblock payloads. +func (r *RbuilderClient) FlashblocksClient() types.FlashblocksClient { + return r.flashblocksClient +} diff --git a/runner/clients/rbuilder/flashblocks_client.go b/runner/clients/rbuilder/flashblocks_client.go new file mode 100644 index 0000000..5932285 --- /dev/null +++ b/runner/clients/rbuilder/flashblocks_client.go @@ -0,0 +1,213 @@ +package rbuilder + +import ( + "context" + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/base/base-bench/runner/clients/types" + "github.com/ethereum/go-ethereum/log" + "github.com/gorilla/websocket" +) + +// flashblocksClient implements the FlashblocksClient interface for collecting +// flashblock payloads from rbuilder via websocket and broadcasting them to listeners. +type flashblocksClient struct { + log log.Logger + port uint64 + conn *websocket.Conn + listeners []types.FlashblockListener + mu sync.RWMutex + stopChan chan struct{} + stopOnce sync.Once +} + +// NewFlashblocksClient creates a new flashblocks websocket client. +func NewFlashblocksClient(log log.Logger, port uint64) types.FlashblocksClient { + return &flashblocksClient{ + log: log, + port: port, + listeners: make([]types.FlashblockListener, 0), + stopChan: make(chan struct{}), + } +} + +// Start begins collecting flashblocks from the websocket connection. +// This method connects to the websocket in a goroutine to avoid blocking. +func (f *flashblocksClient) Start(ctx context.Context) error { + url := fmt.Sprintf("ws://localhost:%d", f.port) + f.log.Info("Connecting to flashblocks websocket", "url", url) + + // Use a separate context with timeout for the connection attempt + connectCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + dialer := websocket.DefaultDialer + conn, _, err := dialer.DialContext(connectCtx, url, nil) + if err != nil { + return err + } + + f.mu.Lock() + f.conn = conn + f.mu.Unlock() + + f.log.Info("Connected to flashblocks websocket", "url", url) + + // Read messages in this goroutine + go f.readMessages(ctx) + + return nil +} + +// readMessages reads flashblock messages from the websocket in a blocking loop. +// It exits on any error or when the context is cancelled. +func (f *flashblocksClient) readMessages(ctx context.Context) { + defer func() { + f.mu.Lock() + if f.conn != nil { + err := f.conn.Close() + if err != nil { + f.log.Warn("Error closing flashblocks websocket connection", "err", err) + } + f.conn = nil + } + f.mu.Unlock() + }() + + // Channel to signal when a message is received or error occurs + type readResult struct { + message []byte + err error + } + readChan := make(chan readResult, 1) + + // Start a goroutine to read from the websocket + go func() { + for { + _, message, err := f.conn.ReadMessage() + readChan <- readResult{message: message, err: err} + if err != nil { + // Exit on any error to avoid panic on repeated reads + return + } + } + }() + + for { + select { + case <-ctx.Done(): + f.log.Debug("Context cancelled, stopping flashblocks client") + return + case <-f.stopChan: + f.log.Debug("Stop signal received, stopping flashblocks client") + return + case result := <-readChan: + if result.err != nil { + // Check if this is a normal closure + if websocket.IsCloseError(result.err, websocket.CloseNormalClosure, websocket.CloseGoingAway) { + f.log.Debug("Flashblocks websocket closed normally") + } + return + } + + // Deserialize flashblock payload + var flashblock types.FlashblocksPayloadV1 + if err := json.Unmarshal(result.message, &flashblock); err != nil { + f.log.Warn("Failed to deserialize flashblock payload", "err", err) + continue + } + + // Log flashblock details at DEBUG level + txCount := len(flashblock.Diff.Transactions) + f.log.Debug("Received flashblock", + "payload_id", fmt.Sprintf("%x", flashblock.PayloadID), + "index", flashblock.Index, + "tx_count", txCount, + "gas_used", flashblock.Diff.GasUsed, + "block_hash", flashblock.Diff.BlockHash.Hex(), + ) + + // Broadcast to all listeners + f.broadcastFlashblock(flashblock) + } + } +} + +// Stop stops collection and closes the websocket connection. +// This method is idempotent and can be called multiple times safely. +func (f *flashblocksClient) Stop() error { + // Use sync.Once to ensure cleanup only happens once + var stopErr error + f.stopOnce.Do(func() { + f.log.Info("Stopping flashblocks client") + + // Signal the read goroutine to stop + close(f.stopChan) + + f.mu.Lock() + defer f.mu.Unlock() + + if f.conn != nil { + // Send close message + err := f.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + f.log.Warn("Error sending close message to flashblocks websocket", "err", err) + } + + // Close the connection + err = f.conn.Close() + if err != nil { + f.log.Warn("Error closing flashblocks websocket connection", "err", err) + stopErr = err + } + f.conn = nil + } + + f.log.Info("Flashblocks client stopped") + }) + + return stopErr +} + +// AddListener registers a listener to receive flashblock payloads. +func (f *flashblocksClient) AddListener(listener types.FlashblockListener) { + f.mu.Lock() + defer f.mu.Unlock() + f.listeners = append(f.listeners, listener) + f.log.Debug("Added flashblock listener", "total_listeners", len(f.listeners)) +} + +// RemoveListener unregisters a listener. +func (f *flashblocksClient) RemoveListener(listener types.FlashblockListener) { + f.mu.Lock() + defer f.mu.Unlock() + + for i, l := range f.listeners { + if l == listener { + f.listeners = append(f.listeners[:i], f.listeners[i+1:]...) + f.log.Debug("Removed flashblock listener", "total_listeners", len(f.listeners)) + return + } + } +} + +// broadcastFlashblock sends the flashblock to all registered listeners. +func (f *flashblocksClient) broadcastFlashblock(flashblock types.FlashblocksPayloadV1) { + f.mu.RLock() + defer f.mu.RUnlock() + + for _, listener := range f.listeners { + // Call each listener (synchronously for now) + listener.OnFlashblock(flashblock) + } +} + +// IsConnected returns true if the websocket connection is active. +func (f *flashblocksClient) IsConnected() bool { + f.mu.RLock() + defer f.mu.RUnlock() + return f.conn != nil +} diff --git a/runner/clients/reth/client.go b/runner/clients/reth/client.go index f013f8e..fc9460a 100644 --- a/runner/clients/reth/client.go +++ b/runner/clients/reth/client.go @@ -278,3 +278,8 @@ func (r *RethClient) SetHead(ctx context.Context, blockNumber uint64) error { r.logger.Info("Successfully reset blockchain head", "blockNumber", blockNumber, "blockHex", blockHex) return nil } + +// FlashblocksClient returns nil as reth does not support flashblocks. +func (r *RethClient) FlashblocksClient() types.FlashblocksClient { + return nil +} diff --git a/runner/clients/types/flashblock.go b/runner/clients/types/flashblock.go new file mode 100644 index 0000000..93541ec --- /dev/null +++ b/runner/clients/types/flashblock.go @@ -0,0 +1,102 @@ +package types + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// ExecutionPayloadFlashblockDeltaV1 represents the modified portions of an execution payload +// within a flashblock. This structure contains only the fields that can be updated during +// block construction, such as state root, receipts, logs, and new transactions. +type ExecutionPayloadFlashblockDeltaV1 struct { + // StateRoot is the state root of the block + StateRoot common.Hash `json:"state_root"` + // ReceiptsRoot is the receipts root of the block + ReceiptsRoot common.Hash `json:"receipts_root"` + // LogsBloom is the logs bloom of the block + LogsBloom types.Bloom `json:"logs_bloom"` + // GasUsed is the gas used of the block + GasUsed hexutil.Uint64 `json:"gas_used"` + // BlockHash is the block hash of the block + BlockHash common.Hash `json:"block_hash"` + // Transactions are the transactions of the block + Transactions []hexutil.Bytes `json:"transactions"` + // Withdrawals are the withdrawals enabled with V2 + Withdrawals []Withdrawal `json:"withdrawals"` + // WithdrawalsRoot is the withdrawals root of the block + WithdrawalsRoot common.Hash `json:"withdrawals_root"` + // BlobGasUsed is the blob gas used + BlobGasUsed *hexutil.Uint64 `json:"blob_gas_used,omitempty"` +} + +// Withdrawal represents a validator withdrawal +type Withdrawal struct { + Index hexutil.Uint64 `json:"index"` + Validator hexutil.Uint64 `json:"validator"` + Address common.Address `json:"address"` + Amount hexutil.Uint64 `json:"amount"` +} + +// ExecutionPayloadBaseV1 represents the base configuration of an execution payload that +// remains constant throughout block construction. This includes fundamental block properties +// like parent hash, block number, and other header fields that are determined at block +// creation and cannot be modified. +type ExecutionPayloadBaseV1 struct { + // ParentBeaconBlockRoot is the Ecotone parent beacon block root + ParentBeaconBlockRoot common.Hash `json:"parent_beacon_block_root"` + // ParentHash is the parent hash of the block + ParentHash common.Hash `json:"parent_hash"` + // FeeRecipient is the fee recipient of the block + FeeRecipient common.Address `json:"fee_recipient"` + // PrevRandao is the previous randao of the block + PrevRandao common.Hash `json:"prev_randao"` + // BlockNumber is the block number + BlockNumber hexutil.Uint64 `json:"block_number"` + // GasLimit is the gas limit of the block + GasLimit hexutil.Uint64 `json:"gas_limit"` + // Timestamp is the timestamp of the block + Timestamp hexutil.Uint64 `json:"timestamp"` + // ExtraData is the extra data of the block + ExtraData hexutil.Bytes `json:"extra_data"` + // BaseFeePerGas is the base fee per gas of the block + BaseFeePerGas *hexutil.Big `json:"base_fee_per_gas"` +} + +// PayloadID is a unique identifier for a payload +type PayloadID [8]byte + +// MarshalJSON implements json.Marshaler for PayloadID +func (p PayloadID) MarshalJSON() ([]byte, error) { + return json.Marshal(hexutil.Bytes(p[:])) +} + +// UnmarshalJSON implements json.Unmarshaler for PayloadID +func (p *PayloadID) UnmarshalJSON(data []byte) error { + var b hexutil.Bytes + if err := json.Unmarshal(data, &b); err != nil { + return err + } + if len(b) != 8 { + return json.Unmarshal(data, &b) + } + copy(p[:], b) + return nil +} + +// FlashblocksPayloadV1 represents a flashblock payload containing the base execution +// payload configuration and the delta/diff containing modified portions. +type FlashblocksPayloadV1 struct { + // PayloadID is the payload id of the flashblock + PayloadID PayloadID `json:"payload_id"` + // Index is the index of the flashblock in the block + Index uint64 `json:"index"` + // Base is the base execution payload configuration (optional, only present in first flashblock) + Base *ExecutionPayloadBaseV1 `json:"base,omitempty"` + // Diff is the delta/diff containing modified portions of the execution payload + Diff ExecutionPayloadFlashblockDeltaV1 `json:"diff"` + // Metadata is additional metadata associated with the flashblock + Metadata json.RawMessage `json:"metadata"` +} diff --git a/runner/clients/types/flashblocks_client.go b/runner/clients/types/flashblocks_client.go new file mode 100644 index 0000000..f54be47 --- /dev/null +++ b/runner/clients/types/flashblocks_client.go @@ -0,0 +1,29 @@ +package types + +import "context" + +// FlashblockListener receives flashblock payloads as they are received from the websocket. +type FlashblockListener interface { + // OnFlashblock is called when a new flashblock payload is received + OnFlashblock(flashblock FlashblocksPayloadV1) +} + +// FlashblocksClient is an interface for collecting flashblock payloads from a websocket connection. +// Only clients that support flashblocks (e.g., rbuilder) will provide a non-nil implementation. +// It uses a broadcast pattern where listeners can subscribe to receive flashblock updates. +type FlashblocksClient interface { + // Start begins collecting flashblocks from the websocket connection + Start(ctx context.Context) error + + // Stop stops collection and closes the websocket connection + Stop() error + + // AddListener registers a listener to receive flashblock payloads + AddListener(listener FlashblockListener) + + // RemoveListener unregisters a listener + RemoveListener(listener FlashblockListener) + + // IsConnected returns true if the websocket connection is active + IsConnected() bool +} diff --git a/runner/clients/types/types.go b/runner/clients/types/types.go index 1ca1c3f..4ef2862 100644 --- a/runner/clients/types/types.go +++ b/runner/clients/types/types.go @@ -27,4 +27,5 @@ type ExecutionClient interface { MetricsCollector() metrics.Collector GetVersion(ctx context.Context) (string, error) SetHead(ctx context.Context, blockNumber uint64) error + FlashblocksClient() FlashblocksClient // returns nil for clients that don't support flashblocks } diff --git a/runner/network/network_benchmark.go b/runner/network/network_benchmark.go index 15bc844..a5ba05d 100644 --- a/runner/network/network_benchmark.go +++ b/runner/network/network_benchmark.go @@ -108,14 +108,14 @@ func (nb *NetworkBenchmark) benchmarkSequencer(ctx context.Context, l1Chain *l1C }() benchmark := newSequencerBenchmark(nb.log, *nb.testConfig, sequencerClient, l1Chain, nb.transactionPayload) - executionData, lastBlock, err := benchmark.Run(ctx, metricsCollector) + payloadResult, lastBlock, err := benchmark.Run(ctx, metricsCollector) if err != nil { sequencerClient.Stop() return nil, 0, nil, fmt.Errorf("failed to run sequencer benchmark: %w", err) } - return executionData, lastBlock, sequencerClient, nil + return payloadResult.ExecutablePayloads, lastBlock, sequencerClient, nil } func (nb *NetworkBenchmark) benchmarkValidator(ctx context.Context, payloads []engine.ExecutableData, lastSetupBlock uint64, l1Chain *l1Chain, sequencerClient types.ExecutionClient) error { diff --git a/runner/network/sequencer_benchmark.go b/runner/network/sequencer_benchmark.go index fa594df..daf4cb8 100644 --- a/runner/network/sequencer_benchmark.go +++ b/runner/network/sequencer_benchmark.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" "math/rand" + "sync" "time" "github.com/base/base-bench/runner/clients/types" @@ -122,7 +123,7 @@ func (nb *sequencerBenchmark) fundTestAccount(ctx context.Context, mempool mempo return nil } -func (nb *sequencerBenchmark) Run(ctx context.Context, metricsCollector metrics.Collector) ([]engine.ExecutableData, uint64, error) { +func (nb *sequencerBenchmark) Run(ctx context.Context, metricsCollector metrics.Collector) (*benchtypes.PayloadResult, uint64, error) { transactionWorker, err := payload.NewPayloadWorker(ctx, nb.log, &nb.config, nb.sequencerClient, nb.transactionPayload) if err != nil { return nil, 0, err @@ -148,6 +149,26 @@ func (nb *sequencerBenchmark) Run(ctx context.Context, metricsCollector metrics. setupComplete := make(chan struct{}) chainReady := make(chan struct{}) + // Check if client supports flashblocks and start collection if available + var flashblockCollector *flashblockCollector + flashblocksClient := sequencerClient.FlashblocksClient() + if flashblocksClient != nil { + nb.log.Info("Starting flashblocks collection") + flashblockCollector = newFlashblockCollector() + flashblocksClient.AddListener(flashblockCollector) + + if err := flashblocksClient.Start(benchmarkCtx); err != nil { + nb.log.Warn("Failed to start flashblocks client", "err", err) + // Don't fail the benchmark if flashblocks collection fails + } else { + defer func() { + if err := flashblocksClient.Stop(); err != nil { + nb.log.Warn("Failed to stop flashblocks client", "err", err) + } + }() + } + } + go func() { // allow one block to pass before sending txs to set the gas limit <-chainReady @@ -253,6 +274,48 @@ func (nb *sequencerBenchmark) Run(ctx context.Context, metricsCollector metrics. case err := <-errChan: return nil, 0, err case payloads := <-payloadResult: - return payloads, payloads[0].Number - 1, nil + // Collect flashblocks if available + var flashblocks []types.FlashblocksPayloadV1 + if flashblockCollector != nil { + flashblocks = flashblockCollector.GetFlashblocks() + nb.log.Info("Collected flashblocks", "count", len(flashblocks)) + } + + result := &benchtypes.PayloadResult{ + ExecutablePayloads: payloads, + Flashblocks: flashblocks, + } + return result, payloads[0].Number - 1, nil + } +} + +// flashblockCollector implements FlashblockListener to collect flashblocks. +type flashblockCollector struct { + flashblocks []types.FlashblocksPayloadV1 + mu sync.Mutex +} + +// newFlashblockCollector creates a new flashblock collector. +func newFlashblockCollector() *flashblockCollector { + return &flashblockCollector{ + flashblocks: make([]types.FlashblocksPayloadV1, 0), } } + +// OnFlashblock implements FlashblockListener. +func (c *flashblockCollector) OnFlashblock(flashblock types.FlashblocksPayloadV1) { + c.mu.Lock() + defer c.mu.Unlock() + c.flashblocks = append(c.flashblocks, flashblock) +} + +// GetFlashblocks returns all collected flashblocks. +func (c *flashblockCollector) GetFlashblocks() []types.FlashblocksPayloadV1 { + c.mu.Lock() + defer c.mu.Unlock() + + // Return a copy to avoid race conditions + result := make([]types.FlashblocksPayloadV1, len(c.flashblocks)) + copy(result, c.flashblocks) + return result +} diff --git a/runner/network/types/payload_result.go b/runner/network/types/payload_result.go new file mode 100644 index 0000000..2a6b892 --- /dev/null +++ b/runner/network/types/payload_result.go @@ -0,0 +1,21 @@ +package types + +import ( + clientTypes "github.com/base/base-bench/runner/clients/types" + "github.com/ethereum/go-ethereum/beacon/engine" +) + +// PayloadResult contains the results from a sequencer benchmark run, including +// both the executable payloads and any flashblock payloads that were collected. +type PayloadResult struct { + // ExecutablePayloads are the execution payloads generated during the benchmark + ExecutablePayloads []engine.ExecutableData + + // Flashblocks are the flashblock payloads collected during the benchmark (if available) + Flashblocks []clientTypes.FlashblocksPayloadV1 +} + +// HasFlashblocks returns true if flashblock payloads were collected. +func (p *PayloadResult) HasFlashblocks() bool { + return len(p.Flashblocks) > 0 +}