diff --git a/.github/scripts/run-example-benchmarks.sh b/.github/scripts/run-example-benchmarks.sh index 9e94667..5cac7bc 100755 --- a/.github/scripts/run-example-benchmarks.sh +++ b/.github/scripts/run-example-benchmarks.sh @@ -10,6 +10,7 @@ BENCHMARK_CONFIGS=( configs/examples/simulator.yml configs/examples/sload.yml configs/examples/rbuilder.yml + configs/examples/base-reth-node.yml configs/examples/sstore.yml # configs/examples/snapshot.yml # configs/examples/tx-fuzz-geth.yml @@ -28,5 +29,6 @@ for config in "${BENCHMARK_CONFIGS[@]}"; do --output-dir $TEMP_DIR/output \ --reth-bin $TEMP_DIR/bin/reth \ --geth-bin $TEMP_DIR/bin/geth \ - --rbuilder-bin $TEMP_DIR/bin/rbuilder + --rbuilder-bin $TEMP_DIR/bin/rbuilder \ + --base-reth-node-bin $TEMP_DIR/bin/base-reth-node done \ No newline at end of file diff --git a/.github/workflows/_build-binaries.yaml b/.github/workflows/_build-binaries.yaml index 2434f90..a2b8e00 100644 --- a/.github/workflows/_build-binaries.yaml +++ b/.github/workflows/_build-binaries.yaml @@ -20,6 +20,11 @@ on: required: false type: string default: "23f42c8e78ba3abb45a8840df7037a27e196e601" + base_reth_node_version: + description: "Base Reth Node version to build" + required: false + type: string + default: "main" # Set minimal permissions for all jobs by default permissions: @@ -180,6 +185,44 @@ jobs: path: ~/bin/rbuilder retention-days: 1 + build-base-reth-node: + 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 base-reth-node binary + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 + id: cache-base-reth-node + with: + path: ~/bin/base-reth-node + key: ${{ runner.os }}-base-reth-node-${{ inputs.base_reth_node_version }} + + - name: Build base-reth-node + if: steps.cache-base-reth-node.outputs.cache-hit != 'true' + run: | + unset CI + mkdir -p ~/bin + cd clients + BASE_RETH_NODE_VERSION=${{ inputs.base_reth_node_version }} OUTPUT_DIR=~/bin ./build-base-reth-node.sh + + - name: Upload base-reth-node artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: base-reth-node + path: ~/bin/base-reth-node + retention-days: 1 + build-op-program: runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b79cdb9..cb781b0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -72,6 +72,7 @@ jobs: reth_version: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 rbuilder_version: 23f42c8e78ba3abb45a8840df7037a27e196e601 + base_reth_node_version: main basic-benchmarks: runs-on: ubuntu-latest @@ -118,6 +119,12 @@ jobs: name: rbuilder path: ${{ runner.temp }}/bin/ + - name: Download base-reth-node binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: base-reth-node + path: ${{ runner.temp }}/bin/ + - name: Download op-program binary uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index bd61aa7..27c90a7 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -19,6 +19,7 @@ jobs: reth_version: 27a8c0f5a6dfb27dea84c5751776ecabdd069646 geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 rbuilder_version: 23f42c8e78ba3abb45a8840df7037a27e196e601 + base_reth_node_version: main example-benchmarks: runs-on: ubuntu-latest @@ -67,6 +68,12 @@ jobs: name: rbuilder path: ${{ runner.temp }}/bin/ + - name: Download base-reth-node binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: base-reth-node + path: ${{ runner.temp }}/bin/ + - name: Make binaries executable run: | chmod +x ${{ runner.temp }}/bin/* diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh new file mode 100755 index 0000000..8ea4a40 --- /dev/null +++ b/clients/build-base-reth-node.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -e + +# Source versions if available, otherwise use defaults +if [ -f "versions.env" ]; then + source versions.env +fi + +# Default values +BASE_RETH_NODE_REPO="${BASE_RETH_NODE_REPO:-https://github.com/base/base}" +BASE_RETH_NODE_VERSION="${BASE_RETH_NODE_VERSION:-main}" +BUILD_DIR="${BUILD_DIR:-./build}" +OUTPUT_DIR="${OUTPUT_DIR:-../bin}" + +echo "Building base-reth-node binary..." +echo "Repository: $BASE_RETH_NODE_REPO" +echo "Version/Commit: $BASE_RETH_NODE_VERSION" +echo "Build directory: $BUILD_DIR" +echo "Output directory: $OUTPUT_DIR" + +# Create build directory if it doesn't exist +mkdir -p "$BUILD_DIR" +cd "$BUILD_DIR" + +# Clone or update repository +if [ -d "base" ]; then + echo "Updating existing base repository..." + cd base + git fetch origin + + # ensure remote matches the repository + git remote set-url origin "$BASE_RETH_NODE_REPO" + git fetch origin +else + echo "Cloning base repository..." + git clone "$BASE_RETH_NODE_REPO" base + cd base +fi + +# Checkout specified version/commit +echo "Checking out version: $BASE_RETH_NODE_VERSION" +git checkout -f "$BASE_RETH_NODE_VERSION" + +# Build the binary using cargo +echo "Building base-reth-node with cargo..." +# Build with maxperf profile +cargo build --bin base-reth-node --profile maxperf + +# Copy binary to output directory +echo "Copying binary to output directory..." +# 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/base) + FINAL_OUTPUT_DIR="../../$OUTPUT_DIR" +fi +mkdir -p "$FINAL_OUTPUT_DIR" + +# Find the built binary and copy it +if [ -f "target/maxperf/base-reth-node" ]; then + cp target/maxperf/base-reth-node "$FINAL_OUTPUT_DIR/" +else + echo "No base-reth-node binary found" + exit 1 +fi + +echo "base-reth-node binary built successfully and placed in $FINAL_OUTPUT_DIR/base-reth-node" diff --git a/clients/versions.env b/clients/versions.env index 67c6ece..e3920bc 100644 --- a/clients/versions.env +++ b/clients/versions.env @@ -14,6 +14,10 @@ GETH_VERSION="v1.101604.0" RBUILDER_REPO="https://github.com/base/op-rbuilder" RBUILDER_VERSION="main" +# Base Reth Node Configuration +BASE_RETH_NODE_REPO="https://github.com/base/base" +BASE_RETH_NODE_VERSION="main" + # Build Configuration # BUILD_DIR="./build" # OUTPUT_DIR="../bin" \ No newline at end of file diff --git a/configs/examples/base-reth-node-flashblocks-test.yml b/configs/examples/base-reth-node-flashblocks-test.yml new file mode 100644 index 0000000..c2c9f71 --- /dev/null +++ b/configs/examples/base-reth-node-flashblocks-test.yml @@ -0,0 +1,23 @@ +name: Base Reth Node flashblocks metrics test +description: Test base-reth-node flashblock metrics collection with rbuilder as sequencer and base-reth-node as validator +payloads: + - name: Transfer Only + type: transfer-only + id: transfer-only + +benchmarks: + - variables: + - type: payload + values: + - transfer-only + - type: node_type + values: + - rbuilder + - type: validator_node_type + values: + - base-reth-node + - type: num_blocks + value: 5 + - type: gas_limit + values: + - 1000000000 diff --git a/configs/examples/base-reth-node.yml b/configs/examples/base-reth-node.yml new file mode 100644 index 0000000..8dbb7ed --- /dev/null +++ b/configs/examples/base-reth-node.yml @@ -0,0 +1,23 @@ +name: Base Reth Node transfer throughput test +description: Test base-reth-node transfer throughput using flashblocks +payloads: + - name: Transfer Only + type: transfer-only + id: transfer-only + +benchmarks: + - variables: + - type: payload + values: + - transfer-only + - type: node_type + values: + - base-reth-node + - type: validator_node_type + values: + - reth + - type: num_blocks + value: 10 + - type: gas_limit + values: + - 1000000000 diff --git a/runner/clients/baserethnode/client.go b/runner/clients/baserethnode/client.go new file mode 100644 index 0000000..c93cf89 --- /dev/null +++ b/runner/clients/baserethnode/client.go @@ -0,0 +1,277 @@ +package baserethnode + +import ( + "context" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + + "github.com/base/base-bench/runner/benchmark/portmanager" + "github.com/base/base-bench/runner/clients/common" + "github.com/base/base-bench/runner/clients/types" + "github.com/base/base-bench/runner/config" + "github.com/base/base-bench/runner/metrics" +) + +// BaseRethNodeClient handles the lifecycle of a base-reth-node client. +// This client is configured to receive flashblocks via websocket. +type BaseRethNodeClient struct { + logger log.Logger + options *config.InternalClientOptions + + client *ethclient.Client + clientURL string + authClient client.RPC + process *exec.Cmd + + ports portmanager.PortManager + metricsPort uint64 + rpcPort uint64 + p2pPort uint64 + authRPCPort uint64 + + stdout io.WriteCloser + stderr io.WriteCloser + + metricsCollector metrics.Collector +} + +// NewBaseRethNodeClient creates a new client for base-reth-node. +func NewBaseRethNodeClient(logger log.Logger, options *config.InternalClientOptions, ports portmanager.PortManager) types.ExecutionClient { + return &BaseRethNodeClient{ + logger: logger, + options: options, + ports: ports, + } +} + +func (r *BaseRethNodeClient) MetricsCollector() metrics.Collector { + return r.metricsCollector +} + +// Run runs the base-reth-node client with the given runtime config. +func (r *BaseRethNodeClient) Run(ctx context.Context, cfg *types.RuntimeConfig) error { + args := make([]string, 0) + args = append(args, "node") + args = append(args, "--color", "never") + args = append(args, "--chain", r.options.ChainCfgPath) + args = append(args, "--datadir", r.options.DataDirPath) + + r.rpcPort = r.ports.AcquirePort("base-reth-node", portmanager.ELPortPurpose) + r.p2pPort = r.ports.AcquirePort("base-reth-node", portmanager.P2PPortPurpose) + r.authRPCPort = r.ports.AcquirePort("base-reth-node", portmanager.AuthELPortPurpose) + r.metricsPort = r.ports.AcquirePort("base-reth-node", portmanager.ELMetricsPortPurpose) + + args = append(args, "--http") + args = append(args, "--http.port", fmt.Sprintf("%d", r.rpcPort)) + args = append(args, "--http.api", "eth,net,web3,miner") + args = append(args, "--authrpc.port", fmt.Sprintf("%d", r.authRPCPort)) + args = append(args, "--authrpc.jwtsecret", r.options.JWTSecretPath) + args = append(args, "--metrics", fmt.Sprintf("%d", r.metricsPort)) + args = append(args, "--engine.state-provider-metrics") + args = append(args, "--disable-discovery") + args = append(args, "--port", fmt.Sprintf("%d", r.p2pPort)) + args = append(args, "-vvv") + + // increase mempool size + args = append(args, "--txpool.pending-max-count", "100000000") + args = append(args, "--txpool.queued-max-count", "100000000") + args = append(args, "--txpool.pending-max-size", "100") + args = append(args, "--txpool.queued-max-size", "100") + + args = append(args, "--db.read-transaction-timeout", "0") + args = append(args, cfg.Args...) + + // Add flashblocks websocket URL if provided + if cfg.FlashblocksURL != nil && *cfg.FlashblocksURL != "" { + r.logger.Info("Configuring base-reth-node with flashblocks websocket URL", "url", *cfg.FlashblocksURL) + args = append(args, "--websocket-url", *cfg.FlashblocksURL) + } + + // delete datadir/txpool-transactions-backup.rlp if it exists + txpoolBackupPath := fmt.Sprintf("%s/txpool-transactions-backup.rlp", r.options.DataDirPath) + if _, err := os.Stat(txpoolBackupPath); err == nil { + if err := os.Remove(txpoolBackupPath); err != nil { + return errors.Wrap(err, "failed to remove txpool backup") + } + } + + // read jwt secret + jwtSecretStr, err := os.ReadFile(r.options.JWTSecretPath) + if err != nil { + return errors.Wrap(err, "failed to read jwt secret") + } + + jwtSecretBytes, err := hex.DecodeString(string(jwtSecretStr)) + if err != nil { + return err + } + + if len(jwtSecretBytes) != 32 { + return errors.New("jwt secret must be 32 bytes") + } + + jwtSecret := [32]byte{} + copy(jwtSecret[:], jwtSecretBytes[:]) + + if r.stdout != nil { + _ = r.stdout.Close() + } + + if r.stderr != nil { + _ = r.stderr.Close() + } + + r.stdout = cfg.Stdout + r.stderr = cfg.Stderr + + r.logger.Debug("starting base-reth-node", "args", strings.Join(args, " ")) + + r.process = exec.Command(r.options.BaseRethNodeBin, args...) + r.process.Stdout = r.stdout + r.process.Stderr = r.stderr + err = r.process.Start() + if err != nil { + return err + } + + r.clientURL = fmt.Sprintf("http://127.0.0.1:%d", r.rpcPort) + rpcClient, err := rpc.DialOptions(ctx, r.clientURL, rpc.WithHTTPClient(&http.Client{ + Timeout: 30 * time.Second, + })) + if err != nil { + return errors.Wrap(err, "failed to dial rpc") + } + + r.client = ethclient.NewClient(rpcClient) + r.metricsCollector = newMetricsCollector(r.logger, r.client, int(r.metricsPort)) + + err = common.WaitForRPC(ctx, r.client) + if err != nil { + return errors.Wrap(err, "base-reth-node rpc failed to start") + } + + l2Node, err := client.NewRPC(ctx, r.logger, fmt.Sprintf("http://127.0.0.1:%d", r.authRPCPort), client.WithGethRPCOptions(rpc.WithHTTPAuth(node.NewJWTAuth(jwtSecret))), client.WithCallTimeout(240*time.Second)) + if err != nil { + return err + } + + r.authClient = l2Node + + return nil +} + +// Stop stops the base-reth-node client. +func (r *BaseRethNodeClient) Stop() { + if r.process == nil || r.process.Process == nil { + return + } + err := r.process.Process.Signal(os.Interrupt) + if err != nil { + r.logger.Error("failed to stop base-reth-node", "err", err) + } + + r.process.WaitDelay = 5 * time.Second + + err = r.process.Wait() + if err != nil { + r.logger.Error("failed to wait for base-reth-node", "err", err) + } + + _ = r.stdout.Close() + _ = r.stderr.Close() + + // Release the ports + r.ports.ReleasePort(r.rpcPort) + r.ports.ReleasePort(r.authRPCPort) + r.ports.ReleasePort(r.metricsPort) + r.ports.ReleasePort(r.p2pPort) + + r.stdout = nil + r.stderr = nil + r.process = nil +} + +// Client returns the ethclient client. +func (r *BaseRethNodeClient) Client() *ethclient.Client { + return r.client +} + +// ClientURL returns the raw client URL for transaction generators. +func (r *BaseRethNodeClient) ClientURL() string { + return r.clientURL +} + +// AuthClient returns the auth client used for CL communication. +func (r *BaseRethNodeClient) AuthClient() client.RPC { + return r.authClient +} + +func (r *BaseRethNodeClient) MetricsPort() int { + return int(r.metricsPort) +} + +// GetVersion returns the version of the base-reth-node client +func (r *BaseRethNodeClient) GetVersion(ctx context.Context) (string, error) { + cmd := exec.CommandContext(ctx, r.options.BaseRethNodeBin, "--version") + output, err := cmd.Output() + if err != nil { + return "", errors.Wrap(err, "failed to get base-reth-node version") + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.Contains(line, "Version:") { + parts := strings.Split(line, "Version:") + if len(parts) >= 2 { + versionPart := strings.TrimSpace(parts[1]) + versionFields := strings.Fields(versionPart) + if len(versionFields) > 0 { + return versionFields[0], nil + } + } + } + } + return "unknown", nil +} + +// SetHead resets the blockchain to a specific block using debug.setHead +func (r *BaseRethNodeClient) SetHead(ctx context.Context, blockNumber uint64) error { + if r.client == nil { + return errors.New("client not initialized") + } + + blockHex := fmt.Sprintf("0x%x", blockNumber) + + var result interface{} + err := r.client.Client().CallContext(ctx, &result, "debug_setHead", blockHex) + if err != nil { + return errors.Wrap(err, "failed to call debug_setHead") + } + + r.logger.Info("Successfully reset blockchain head", "blockNumber", blockNumber, "blockHex", blockHex) + return nil +} + +// FlashblocksClient returns nil as base-reth-node receives flashblocks but doesn't produce them. +func (r *BaseRethNodeClient) FlashblocksClient() types.FlashblocksClient { + return nil +} + +// SupportsFlashblocks returns true as base-reth-node supports receiving flashblock payloads. +func (r *BaseRethNodeClient) SupportsFlashblocks() bool { + return true +} diff --git a/runner/clients/baserethnode/metrics.go b/runner/clients/baserethnode/metrics.go new file mode 100644 index 0000000..6b13a65 --- /dev/null +++ b/runner/clients/baserethnode/metrics.go @@ -0,0 +1,92 @@ +package baserethnode + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "github.com/base/base-bench/runner/metrics" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" +) + +type metricsCollector struct { + log log.Logger + client *ethclient.Client + metrics []metrics.BlockMetrics + metricsPort int +} + +func newMetricsCollector(log log.Logger, client *ethclient.Client, metricsPort int) metrics.Collector { + return &metricsCollector{ + log: log, + client: client, + metricsPort: metricsPort, + metrics: make([]metrics.BlockMetrics, 0), + } +} + +func (r *metricsCollector) GetMetricsEndpoint() string { + return fmt.Sprintf("http://localhost:%d/metrics", r.metricsPort) +} + +func (r *metricsCollector) GetMetrics() []metrics.BlockMetrics { + return r.metrics +} + +func (r *metricsCollector) GetMetricTypes() map[string]bool { + return map[string]bool{ + "reth_sync_execution_execution_duration": true, + "reth_sync_block_validation_state_root_duration": true, + "reth_sync_state_provider_storage_fetch_latency": true, + "reth_sync_state_provider_account_fetch_latency": true, + "reth_sync_state_provider_code_fetch_latency": true, + "reth_sync_state_provider_total_storage_fetch_latency": true, + "reth_sync_state_provider_total_account_fetch_latency": true, + "reth_sync_state_provider_total_code_fetch_latency": true, + } +} + +func (r *metricsCollector) Collect(ctx context.Context, m *metrics.BlockMetrics) error { + resp, err := http.Get(r.GetMetricsEndpoint()) + if err != nil { + return fmt.Errorf("failed to get metrics: %w", err) + } + defer func() { + _ = resp.Body.Close() + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read metrics response: %w", err) + } + + txtParser := expfmt.NewTextParser(model.LegacyValidation) + metrics, err := txtParser.TextToMetricFamilies(bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("failed to parse metrics: %w", err) + } + + metricTypes := r.GetMetricTypes() + + for _, metric := range metrics { + name := metric.GetName() + if metricTypes[name] { + metricVal := metric.GetMetric() + if len(metricVal) != 1 { + r.log.Warn("expected 1 metric, got %d for metric %s", len(metricVal), name) + } + err = m.UpdatePrometheusMetric(name, metricVal[0]) + if err != nil { + r.log.Warn("failed to add metric %s: %s", name, err) + } + } + } + + r.metrics = append(r.metrics, *m.Copy()) + return nil +} diff --git a/runner/clients/baserethnode/options/options.go b/runner/clients/baserethnode/options/options.go new file mode 100644 index 0000000..a43cccc --- /dev/null +++ b/runner/clients/baserethnode/options/options.go @@ -0,0 +1,11 @@ +package baserethnode + +import "github.com/base/base-bench/runner/clients/reth/options" + +// BaseRethNodeOptions contains the options for the base-reth-node client determined by the test. +type BaseRethNodeOptions struct { + options.RethOptions + + // BaseRethNodeBin is the path to the base-reth-node binary. + BaseRethNodeBin string +} diff --git a/runner/clients/interface.go b/runner/clients/interface.go index d437a1c..88aba2f 100644 --- a/runner/clients/interface.go +++ b/runner/clients/interface.go @@ -4,6 +4,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/base/base-bench/runner/benchmark/portmanager" + "github.com/base/base-bench/runner/clients/baserethnode" "github.com/base/base-bench/runner/clients/geth" "github.com/base/base-bench/runner/clients/rbuilder" "github.com/base/base-bench/runner/clients/reth" @@ -20,6 +21,8 @@ func NewClient(client Client, logger log.Logger, options *config.InternalClientO return geth.NewGethClient(logger, options, portManager) case Rbuilder: return rbuilder.NewRbuilderClient(logger, options, portManager) + case BaseRethNode: + return baserethnode.NewBaseRethNodeClient(logger, options, portManager) default: panic("unknown client") } @@ -32,4 +35,5 @@ const ( Reth Client = iota Geth Rbuilder + BaseRethNode ) diff --git a/runner/config/client.go b/runner/config/client.go index dba28ac..aa27491 100644 --- a/runner/config/client.go +++ b/runner/config/client.go @@ -4,6 +4,7 @@ import ( "github.com/urfave/cli/v2" "github.com/base/base-bench/runner/benchmark/portmanager" + baserethnode "github.com/base/base-bench/runner/clients/baserethnode/options" gethoptions "github.com/base/base-bench/runner/clients/geth/options" rbuilderoptions "github.com/base/base-bench/runner/clients/rbuilder/options" rethoptions "github.com/base/base-bench/runner/clients/reth/options" @@ -16,6 +17,7 @@ type ClientOptions struct { rethoptions.RethOptions gethoptions.GethOptions rbuilderoptions.RbuilderOptions + baserethnode.BaseRethNodeOptions PortOverrides PortOverrides } @@ -58,6 +60,9 @@ func ReadClientOptions(ctx *cli.Context) ClientOptions { RbuilderOptions: rbuilderoptions.RbuilderOptions{ RbuilderBin: ctx.String(flags.RbuilderBin), }, + BaseRethNodeOptions: baserethnode.BaseRethNodeOptions{ + BaseRethNodeBin: ctx.String(flags.BaseRethNodeBin), + }, } return options diff --git a/runner/flags/flags.go b/runner/flags/flags.go index 139449f..054d6af 100644 --- a/runner/flags/flags.go +++ b/runner/flags/flags.go @@ -7,9 +7,10 @@ import ( ) const ( - RethBin = "reth-bin" - RbuilderBin = "rbuilder-bin" - GethBin = "geth-bin" + RethBin = "reth-bin" + RbuilderBin = "rbuilder-bin" + GethBin = "geth-bin" + BaseRethNodeBin = "base-reth-node-bin" ) func CLIFlags(envPrefix string) []cli.Flag { @@ -32,5 +33,11 @@ func CLIFlags(envPrefix string) []cli.Flag { Value: "rbuilder", EnvVars: opservice.PrefixEnvVar(envPrefix, "RBUILDER_BIN"), }, + &cli.StringFlag{ + Name: BaseRethNodeBin, + Usage: "Base Reth Node binary path", + Value: "base-reth-node", + EnvVars: opservice.PrefixEnvVar(envPrefix, "BASE_RETH_NODE_BIN"), + }, } } diff --git a/runner/network/flashblocks/replay_server.go b/runner/network/flashblocks/replay_server.go index 451256f..0feef0c 100644 --- a/runner/network/flashblocks/replay_server.go +++ b/runner/network/flashblocks/replay_server.go @@ -225,7 +225,8 @@ func (s *ReplayServer) broadcastFlashblock(flashblock types.FlashblocksPayloadV1 var lastErr error for _, conn := range connections { - if err := conn.WriteMessage(websocket.TextMessage, data); err != nil { + // Use BinaryMessage - base-reth-node requires binary websocket messages + if err := conn.WriteMessage(websocket.BinaryMessage, data); err != nil { s.log.Warn("Failed to send flashblock to client", "err", err) lastErr = err } diff --git a/runner/network/network_benchmark.go b/runner/network/network_benchmark.go index 9f3e497..26f5cd8 100644 --- a/runner/network/network_benchmark.go +++ b/runner/network/network_benchmark.go @@ -266,6 +266,8 @@ func setupNode(ctx context.Context, l log.Logger, nodeTypeStr string, params ben nodeType = clients.Reth case "rbuilder": nodeType = clients.Rbuilder + case "base-reth-node": + nodeType = clients.BaseRethNode default: return nil, fmt.Errorf("unsupported node type: %s", nodeTypeStr) }