From 55090f513f52b478f0d9ab127d912ef61dca8db2 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:09:50 -0700 Subject: [PATCH 01/16] feat: add load-test payload worker for base-load-test binary integration 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 --- benchmark/flags/flags.go | 32 ++-- runner/config/config.go | 55 +++--- runner/payload/factory.go | 10 ++ runner/payload/loadtest/load_test_worker.go | 183 ++++++++++++++++++++ 4 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 runner/payload/loadtest/load_test_worker.go diff --git a/benchmark/flags/flags.go b/benchmark/flags/flags.go index 8f86508b..368ab569 100644 --- a/benchmark/flags/flags.go +++ b/benchmark/flags/flags.go @@ -15,22 +15,24 @@ func prefixEnvVars(name string) []string { } const ( - ConfigFlagName = "config" - RootDirFlagName = "root-dir" - OutputDirFlagName = "output-dir" - TxFuzzBinFlagName = "tx-fuzz-bin" - ProxyPortFlagName = "proxy-port" - BenchmarkRunIDFlagName = "benchmark-run-id" - MachineTypeFlagName = "machine-type" - MachineProviderFlagName = "machine-provider" - MachineRegionFlagName = "machine-region" - FileSystemFlagName = "file-system" + ConfigFlagName = "config" + RootDirFlagName = "root-dir" + OutputDirFlagName = "output-dir" + TxFuzzBinFlagName = "tx-fuzz-bin" + LoadTestBinFlagName = "load-test-bin" + ProxyPortFlagName = "proxy-port" + BenchmarkRunIDFlagName = "benchmark-run-id" + MachineTypeFlagName = "machine-type" + MachineProviderFlagName = "machine-provider" + MachineRegionFlagName = "machine-region" + FileSystemFlagName = "file-system" ParallelTxBatchesFlagName = "parallel-tx-batches" ) // TxFuzz defaults const ( - DefaultTxFuzzBin = "../tx-fuzz/cmd/livefuzzer/livefuzzer" + DefaultTxFuzzBin = "../tx-fuzz/cmd/livefuzzer/livefuzzer" + DefaultLoadTestBin = "./base-load-test" ) var ( @@ -62,6 +64,13 @@ var ( EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "TX_FUZZ_BIN"), } + LoadTestBinFlag = &cli.StringFlag{ + Name: LoadTestBinFlagName, + Usage: "Load test binary path", + Value: DefaultLoadTestBin, + EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "LOAD_TEST_BIN"), + } + ProxyPortFlag = &cli.IntFlag{ Name: "proxy-port", Usage: "Proxy port", @@ -116,6 +125,7 @@ var RunFlags = []cli.Flag{ RootDirFlag, OutputDirFlag, TxFuzzBinFlag, + LoadTestBinFlag, ProxyPortFlag, BenchmarkRunIDFlag, MachineTypeFlag, diff --git a/runner/config/config.go b/runner/config/config.go index c2890c42..0386f86f 100644 --- a/runner/config/config.go +++ b/runner/config/config.go @@ -20,6 +20,7 @@ type Config interface { DataDir() string OutputDir() string TxFuzzBinary() string + LoadTestBinary() string ProxyPort() int BenchmarkRunID() string MachineType() string @@ -30,36 +31,38 @@ type Config interface { } type config struct { - logConfig oplog.CLIConfig - configPath string - dataDir string - outputDir string - clientOptions ClientOptions - txFuzzBinary string - proxyPort int - benchmarkRunID string - machineType string - machineProvider string - machineRegion string - fileSystem string + logConfig oplog.CLIConfig + configPath string + dataDir string + outputDir string + clientOptions ClientOptions + txFuzzBinary string + loadTestBinary string + proxyPort int + benchmarkRunID string + machineType string + machineProvider string + machineRegion string + fileSystem string parallelTxBatches int } func NewConfig(ctx *cli.Context) Config { return &config{ - logConfig: oplog.ReadCLIConfig(ctx), - configPath: ctx.String(appFlags.ConfigFlagName), - dataDir: ctx.String(appFlags.RootDirFlagName), - outputDir: ctx.String(appFlags.OutputDirFlagName), - txFuzzBinary: ctx.String(appFlags.TxFuzzBinFlagName), - proxyPort: ctx.Int(appFlags.ProxyPortFlagName), - benchmarkRunID: ctx.String(appFlags.BenchmarkRunIDFlagName), - machineType: ctx.String(appFlags.MachineTypeFlagName), - machineProvider: ctx.String(appFlags.MachineProviderFlagName), - machineRegion: ctx.String(appFlags.MachineRegionFlagName), - fileSystem: ctx.String(appFlags.FileSystemFlagName), + logConfig: oplog.ReadCLIConfig(ctx), + configPath: ctx.String(appFlags.ConfigFlagName), + dataDir: ctx.String(appFlags.RootDirFlagName), + outputDir: ctx.String(appFlags.OutputDirFlagName), + txFuzzBinary: ctx.String(appFlags.TxFuzzBinFlagName), + loadTestBinary: ctx.String(appFlags.LoadTestBinFlagName), + proxyPort: ctx.Int(appFlags.ProxyPortFlagName), + benchmarkRunID: ctx.String(appFlags.BenchmarkRunIDFlagName), + machineType: ctx.String(appFlags.MachineTypeFlagName), + machineProvider: ctx.String(appFlags.MachineProviderFlagName), + machineRegion: ctx.String(appFlags.MachineRegionFlagName), + fileSystem: ctx.String(appFlags.FileSystemFlagName), parallelTxBatches: ctx.Int(appFlags.ParallelTxBatchesFlagName), - clientOptions: ReadClientOptions(ctx), + clientOptions: ReadClientOptions(ctx), } } @@ -112,6 +115,10 @@ func (c *config) TxFuzzBinary() string { return c.txFuzzBinary } +func (c *config) LoadTestBinary() string { + return c.loadTestBinary +} + func (c *config) BenchmarkRunID() string { return c.benchmarkRunID } diff --git a/runner/payload/factory.go b/runner/payload/factory.go index dd1d3102..dd621a99 100644 --- a/runner/payload/factory.go +++ b/runner/payload/factory.go @@ -7,6 +7,7 @@ import ( clienttypes "github.com/base/base-bench/runner/clients/types" benchtypes "github.com/base/base-bench/runner/network/types" "github.com/base/base-bench/runner/payload/contract" + "github.com/base/base-bench/runner/payload/loadtest" "github.com/base/base-bench/runner/payload/simulator" "github.com/base/base-bench/runner/payload/transferonly" "github.com/base/base-bench/runner/payload/txfuzz" @@ -31,6 +32,13 @@ func NewPayloadWorker(ctx context.Context, log log.Logger, testConfig *benchtype case "tx-fuzz": worker, err = txfuzz.NewTxFuzzPayloadWorker( log, sequencerClient.ClientURL(), params, privateKey, amount, config.TxFuzzBinary(), genesis.Config.ChainID) + case "load-test": + def, _ := definition.Params.(*loadtest.LoadTestPayloadDefinition) + if def == nil { + def = &loadtest.LoadTestPayloadDefinition{} + } + worker, err = loadtest.NewLoadTestPayloadWorker( + log, sequencerClient.ClientURL(), params, privateKey, amount, config.LoadTestBinary(), genesis.Config.ChainID, *def) case "transfer-only": worker, err = transferonly.NewTransferPayloadWorker( ctx, log, sequencerClient.ClientURL(), params, privateKey, amount, &genesis, definition.Params) @@ -77,6 +85,8 @@ func (t *Definition) UnmarshalYAML(node *yaml.Node) error { params = &transferonly.TransferOnlyPayloadDefinition{} case "tx-fuzz": params = &txfuzz.TxFuzzPayloadDefinition{} + case "load-test": + params = &loadtest.LoadTestPayloadDefinition{} case "contract": params = &contract.ContractPayloadDefinition{} case "simulator": diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go new file mode 100644 index 00000000..58fb7527 --- /dev/null +++ b/runner/payload/loadtest/load_test_worker.go @@ -0,0 +1,183 @@ +package loadtest + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "fmt" + "math/big" + "os" + "os/exec" + + "github.com/base/base-bench/runner/clients/common/proxy" + "github.com/base/base-bench/runner/network/mempool" + "github.com/base/base-bench/runner/network/types" + "github.com/base/base-bench/runner/payload/worker" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +const proxyPort = 8545 + +// LoadTestPayloadDefinition is the YAML payload params for the load-test type. +type LoadTestPayloadDefinition struct { + SenderCount uint64 `yaml:"sender_count"` + TargetGPS uint64 `yaml:"target_gps"` + Duration string `yaml:"duration"` +} + +// loadTestConfig is the YAML config written to a temp file for the load-test binary. +type loadTestConfig struct { + RPC string `yaml:"rpc"` + SenderCount uint64 `yaml:"sender_count"` + TargetGPS uint64 `yaml:"target_gps"` + Duration string `yaml:"duration"` + Seed uint64 `yaml:"seed"` + FundingAmount string `yaml:"funding_amount"` + Transactions []loadTestTransactionDef `yaml:"transactions"` +} + +type loadTestTransactionDef struct { + Type string `yaml:"type"` + Weight uint64 `yaml:"weight"` +} + +type loadTestPayloadWorker struct { + log log.Logger + prefundSK string + loadTestBin string + elRPCURL string + gasLimit uint64 + params LoadTestPayloadDefinition + mempool *mempool.StaticWorkloadMempool + proxyServer *proxy.ProxyServer + configFilePath string +} + +// NewLoadTestPayloadWorker creates a worker that runs the base-load-test binary +// as an external transaction generator, capturing transactions via a proxy server. +func NewLoadTestPayloadWorker( + log log.Logger, + elRPCURL string, + params types.RunParams, + prefundedPrivateKey ecdsa.PrivateKey, + prefundAmount *big.Int, + loadTestBin string, + chainID *big.Int, + definition LoadTestPayloadDefinition, +) (worker.Worker, error) { + mp := mempool.NewStaticWorkloadMempool(log, chainID) + ps := proxy.NewProxyServer(elRPCURL, log, proxyPort, mp) + + w := &loadTestPayloadWorker{ + log: log, + prefundSK: hex.EncodeToString(prefundedPrivateKey.D.Bytes()), + loadTestBin: loadTestBin, + elRPCURL: elRPCURL, + gasLimit: params.GasLimit, + params: definition, + mempool: mp, + proxyServer: ps, + } + + return w, nil +} + +func (w *loadTestPayloadWorker) Mempool() mempool.FakeMempool { + return w.mempool +} + +func (w *loadTestPayloadWorker) Setup(ctx context.Context) error { + if err := w.proxyServer.Run(ctx); err != nil { + return errors.Wrap(err, "failed to run proxy server") + } + + configPath, err := w.writeConfig() + if err != nil { + return errors.Wrap(err, "failed to write load-test config") + } + w.configFilePath = configPath + + w.log.Info("Starting load test", "binary", w.loadTestBin, "config", configPath) + + cmd := exec.CommandContext(ctx, w.loadTestBin, configPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + cmd.Env = append(os.Environ(), fmt.Sprintf("FUNDER_KEY=%s", w.prefundSK)) + + if err := cmd.Start(); err != nil { + return errors.Wrap(err, "failed to start load test binary") + } + + return nil +} + +func (w *loadTestPayloadWorker) Stop(ctx context.Context) error { + w.proxyServer.Stop() + + if w.configFilePath != "" { + os.Remove(w.configFilePath) + } + + return nil +} + +func (w *loadTestPayloadWorker) SendTxs(ctx context.Context) error { + w.log.Info("Collecting txs from load test") + pendingTxs := w.proxyServer.PendingTxs() + w.proxyServer.ClearPendingTxs() + + w.mempool.AddTransactions(pendingTxs) + return nil +} + +// writeConfig generates a temporary YAML config file for the load-test binary +// with the RPC URL pointing to the proxy server. +func (w *loadTestPayloadWorker) writeConfig() (string, error) { + senderCount := w.params.SenderCount + if senderCount == 0 { + senderCount = 10 + } + + targetGPS := w.params.TargetGPS + if targetGPS == 0 { + targetGPS = w.gasLimit / 2 + } + + duration := w.params.Duration + if duration == "" { + duration = "600s" + } + + config := loadTestConfig{ + RPC: fmt.Sprintf("http://localhost:%d", proxyPort), + SenderCount: senderCount, + TargetGPS: targetGPS, + Duration: duration, + Seed: 12345, + FundingAmount: "10000000000000000000", + Transactions: []loadTestTransactionDef{ + {Type: "transfer", Weight: 70}, + {Type: "calldata", Weight: 20}, + {Type: "precompile", Weight: 10}, + }, + } + + data, err := yaml.Marshal(&config) + if err != nil { + return "", errors.Wrap(err, "failed to marshal load-test config") + } + + tmpFile, err := os.CreateTemp("", "load-test-config-*.yaml") + if err != nil { + return "", errors.Wrap(err, "failed to create temp config file") + } + defer tmpFile.Close() + + if _, err := tmpFile.Write(data); err != nil { + return "", errors.Wrap(err, "failed to write temp config file") + } + + return tmpFile.Name(), nil +} From d17d2e84b3eea46044f904ae296f82ce8e93fd87 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:26:40 -0700 Subject: [PATCH 02/16] fix: use UnmarshalBinary for typed transaction decoding in proxy 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. --- runner/clients/common/proxy/proxy.go | 9 +++++---- runner/payload/loadtest/load_test_worker.go | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/runner/clients/common/proxy/proxy.go b/runner/clients/common/proxy/proxy.go index a8ab5f35..290e6fc4 100644 --- a/runner/clients/common/proxy/proxy.go +++ b/runner/clients/common/proxy/proxy.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) type ProxyServer struct { @@ -196,11 +195,13 @@ func (p *ProxyServer) OverrideRequest(method string, rawParams json.RawMessage) return false, nil, fmt.Errorf("failed to decode hex: %w", err) } - err = rlp.DecodeBytes(rawTxBytes, &tx) + // Use UnmarshalBinary to support both legacy and typed (EIP-2718) transactions. + // The previous rlp.DecodeBytes only handled legacy transactions. + err = tx.UnmarshalBinary(rawTxBytes) if err != nil { - p.log.Error("failed to decode RLP", "err", err) - return false, nil, fmt.Errorf("failed to decode RLP: %w", err) + p.log.Error("failed to decode transaction", "err", err) + return false, nil, fmt.Errorf("failed to decode transaction: %w", err) } p.pendingTxs = append(p.pendingTxs, &tx) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index 58fb7527..9c9bd42f 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -39,8 +39,10 @@ type loadTestConfig struct { } type loadTestTransactionDef struct { - Type string `yaml:"type"` - Weight uint64 `yaml:"weight"` + Type string `yaml:"type"` + Weight uint64 `yaml:"weight"` + MaxSize uint64 `yaml:"max_size,omitempty"` + Target string `yaml:"target,omitempty"` } type loadTestPayloadWorker struct { @@ -159,8 +161,8 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { FundingAmount: "10000000000000000000", Transactions: []loadTestTransactionDef{ {Type: "transfer", Weight: 70}, - {Type: "calldata", Weight: 20}, - {Type: "precompile", Weight: 10}, + {Type: "calldata", Weight: 20, MaxSize: 256}, + {Type: "precompile", Weight: 10, Target: "sha256"}, }, } From 6a6799c637b5928008dc96efedac1c9e4942ac3a Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:36:09 -0700 Subject: [PATCH 03/16] fix: handle errcheck lint errors in load-test worker --- runner/payload/loadtest/load_test_worker.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index 9c9bd42f..6e4ea769 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -119,7 +119,9 @@ func (w *loadTestPayloadWorker) Stop(ctx context.Context) error { w.proxyServer.Stop() if w.configFilePath != "" { - os.Remove(w.configFilePath) + if err := os.Remove(w.configFilePath); err != nil { + w.log.Warn("failed to remove load-test config", "path", w.configFilePath, "err", err) + } } return nil @@ -175,11 +177,15 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { if err != nil { return "", errors.Wrap(err, "failed to create temp config file") } - defer tmpFile.Close() if _, err := tmpFile.Write(data); err != nil { + tmpFile.Close() return "", errors.Wrap(err, "failed to write temp config file") } + if err := tmpFile.Close(); err != nil { + return "", errors.Wrap(err, "failed to close temp config file") + } + return tmpFile.Name(), nil } From c584a34b6f8ca52d35fcf6af0c11c4da9505e46f Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:42:35 -0700 Subject: [PATCH 04/16] feat: integrate load-test config into benchmark payload definition - 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.) --- runner/payload/loadtest/load_test_worker.go | 134 ++++++++++++++------ 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index 6e4ea769..aad1edf5 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -3,6 +3,7 @@ package loadtest import ( "context" "crypto/ecdsa" + cryptorand "crypto/rand" "encoding/hex" "fmt" "math/big" @@ -21,28 +22,24 @@ import ( const proxyPort = 8545 // LoadTestPayloadDefinition is the YAML payload params for the load-test type. +// Fields map directly to the Rust base-load-test config format. +// The `transactions` field is passed through as raw YAML to support the full +// Rust config schema (transfer, calldata, precompile, erc20, etc.). type LoadTestPayloadDefinition struct { - SenderCount uint64 `yaml:"sender_count"` - TargetGPS uint64 `yaml:"target_gps"` - Duration string `yaml:"duration"` + SenderCount uint64 `yaml:"sender_count"` + FundingAmount string `yaml:"funding_amount"` + Transactions yaml.Node `yaml:"transactions"` } // loadTestConfig is the YAML config written to a temp file for the load-test binary. type loadTestConfig struct { - RPC string `yaml:"rpc"` - SenderCount uint64 `yaml:"sender_count"` - TargetGPS uint64 `yaml:"target_gps"` - Duration string `yaml:"duration"` - Seed uint64 `yaml:"seed"` - FundingAmount string `yaml:"funding_amount"` - Transactions []loadTestTransactionDef `yaml:"transactions"` -} - -type loadTestTransactionDef struct { - Type string `yaml:"type"` - Weight uint64 `yaml:"weight"` - MaxSize uint64 `yaml:"max_size,omitempty"` - Target string `yaml:"target,omitempty"` + RPC string `yaml:"rpc"` + SenderCount uint64 `yaml:"sender_count"` + TargetGPS uint64 `yaml:"target_gps"` + Duration string `yaml:"duration"` + Seed uint64 `yaml:"seed"` + FundingAmount string `yaml:"funding_amount"` + Transactions yaml.Node `yaml:"transactions"` } type loadTestPayloadWorker struct { @@ -51,9 +48,11 @@ type loadTestPayloadWorker struct { loadTestBin string elRPCURL string gasLimit uint64 + blockTimeSec uint64 params LoadTestPayloadDefinition mempool *mempool.StaticWorkloadMempool proxyServer *proxy.ProxyServer + cmd *exec.Cmd configFilePath string } @@ -72,15 +71,21 @@ func NewLoadTestPayloadWorker( mp := mempool.NewStaticWorkloadMempool(log, chainID) ps := proxy.NewProxyServer(elRPCURL, log, proxyPort, mp) + blockTimeSec := uint64(params.BlockTime.Seconds()) + if blockTimeSec == 0 { + blockTimeSec = 1 + } + w := &loadTestPayloadWorker{ - log: log, - prefundSK: hex.EncodeToString(prefundedPrivateKey.D.Bytes()), - loadTestBin: loadTestBin, - elRPCURL: elRPCURL, - gasLimit: params.GasLimit, - params: definition, - mempool: mp, - proxyServer: ps, + log: log, + prefundSK: hex.EncodeToString(prefundedPrivateKey.D.Bytes()), + loadTestBin: loadTestBin, + elRPCURL: elRPCURL, + gasLimit: params.GasLimit, + blockTimeSec: blockTimeSec, + params: definition, + mempool: mp, + proxyServer: ps, } return w, nil @@ -111,11 +116,22 @@ func (w *loadTestPayloadWorker) Setup(ctx context.Context) error { if err := cmd.Start(); err != nil { return errors.Wrap(err, "failed to start load test binary") } + w.cmd = cmd return nil } func (w *loadTestPayloadWorker) Stop(ctx context.Context) error { + if w.cmd != nil && w.cmd.Process != nil { + w.log.Info("Stopping load test process", "pid", w.cmd.Process.Pid) + if err := w.cmd.Process.Kill(); err != nil { + w.log.Warn("failed to kill load test process", "err", err) + } else { + // Reap the process to avoid zombies. + _, _ = w.cmd.Process.Wait() + } + } + w.proxyServer.Stop() if w.configFilePath != "" { @@ -136,6 +152,40 @@ func (w *loadTestPayloadWorker) SendTxs(ctx context.Context) error { return nil } +// defaultTransactions returns the default transaction mix as a yaml.Node. +func defaultTransactions() yaml.Node { + var node yaml.Node + // Default: 70% transfer, 20% calldata, 10% precompile + defaultYAML := ` +- weight: 70 + type: transfer +- weight: 20 + type: calldata + max_size: 256 +- weight: 10 + type: precompile + target: sha256 +` + if err := yaml.Unmarshal([]byte(defaultYAML), &node); err != nil { + panic(fmt.Sprintf("failed to parse default transactions YAML: %v", err)) + } + // yaml.Unmarshal wraps in a document node; return the inner sequence + if node.Kind == yaml.DocumentNode && len(node.Content) > 0 { + return *node.Content[0] + } + return node +} + +// randomSeed returns a cryptographically random uint64 seed. +func randomSeed() uint64 { + var b [8]byte + if _, err := cryptorand.Read(b[:]); err != nil { + return 42 + } + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + // writeConfig generates a temporary YAML config file for the load-test binary // with the RPC URL pointing to the proxy server. func (w *loadTestPayloadWorker) writeConfig() (string, error) { @@ -144,28 +194,27 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { senderCount = 10 } - targetGPS := w.params.TargetGPS - if targetGPS == 0 { - targetGPS = w.gasLimit / 2 + fundingAmount := w.params.FundingAmount + if fundingAmount == "" { + fundingAmount = "10000000000000000000" } - duration := w.params.Duration - if duration == "" { - duration = "600s" + // Compute target GPS from gas limit and block time + targetGPS := w.gasLimit / w.blockTimeSec + + transactions := w.params.Transactions + if transactions.Kind == 0 { + transactions = defaultTransactions() } config := loadTestConfig{ RPC: fmt.Sprintf("http://localhost:%d", proxyPort), SenderCount: senderCount, TargetGPS: targetGPS, - Duration: duration, - Seed: 12345, - FundingAmount: "10000000000000000000", - Transactions: []loadTestTransactionDef{ - {Type: "transfer", Weight: 70}, - {Type: "calldata", Weight: 20, MaxSize: 256}, - {Type: "precompile", Weight: 10, Target: "sha256"}, - }, + Duration: "99999s", + Seed: randomSeed(), + FundingAmount: fundingAmount, + Transactions: transactions, } data, err := yaml.Marshal(&config) @@ -187,5 +236,12 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { return "", errors.Wrap(err, "failed to close temp config file") } + w.log.Info("Generated load-test config", + "sender_count", senderCount, + "target_gps", targetGPS, + "gas_limit", w.gasLimit, + "block_time_sec", w.blockTimeSec, + ) + return tmpFile.Name(), nil } From 0f641f4eadcfe0b21f2721c99a22eccaeec78fc0 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:48:31 -0700 Subject: [PATCH 05/16] Revert "fix: reduce gzip noise in proxy DebugResponse" This reverts commit 01d084df234b932d82f38c79c71b41e4d702b016. --- runner/clients/common/proxy/proxy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runner/clients/common/proxy/proxy.go b/runner/clients/common/proxy/proxy.go index 290e6fc4..f261c39e 100644 --- a/runner/clients/common/proxy/proxy.go +++ b/runner/clients/common/proxy/proxy.go @@ -218,21 +218,21 @@ func (p *ProxyServer) DebugResponse(method string, params json.RawMessage, respB p.log.Debug("method", "method", method) p.log.Debug("params", "params", params) - // Try gzip decompression; fall back to raw body if the response is plain JSON. gzipReader, err := gzip.NewReader(bytes.NewReader(respBody)) if err != nil { - p.log.Debug("Response body", "body", string(respBody)) + p.log.Error("Error creating gzip reader", "err", err) return } defer func() { if err := gzipReader.Close(); err != nil { - p.log.Debug("Error closing gzip reader", "err", err) + p.log.Error("Error closing gzip reader", "err", err) } }() uncompressedBody, err := io.ReadAll(gzipReader) + if err != nil { - p.log.Debug("Error reading uncompressed response body", "err", err) + p.log.Error("Error reading uncompressed response body", "err", err) return } p.log.Debug("Uncompressed body", "body", string(uncompressedBody)) From 8d306fd1d51bd2515f59128baac0e5ebd78b0af5 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 11:51:14 -0700 Subject: [PATCH 06/16] fix: suppress errcheck lint for tmpFile.Close in error path --- runner/payload/loadtest/load_test_worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index aad1edf5..5ea9f1cf 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -228,7 +228,7 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { } if _, err := tmpFile.Write(data); err != nil { - tmpFile.Close() + _ = tmpFile.Close() return "", errors.Wrap(err, "failed to write temp config file") } From 7ddffa45846ffd8c17284f3c03983200937f5419 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 12:04:28 -0700 Subject: [PATCH 07/16] feat: add load-test CI workflow and example config - 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) --- .github/workflows/_build-binaries.yaml | 12 +++- .github/workflows/load-test.yaml | 98 ++++++++++++++++++++++++++ clients/build-base-reth-node.sh | 15 ++-- configs/examples/load-test.yml | 29 ++++++++ 4 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/load-test.yaml create mode 100644 configs/examples/load-test.yml diff --git a/.github/workflows/_build-binaries.yaml b/.github/workflows/_build-binaries.yaml index a7bf7e5f..8e51c395 100644 --- a/.github/workflows/_build-binaries.yaml +++ b/.github/workflows/_build-binaries.yaml @@ -163,9 +163,10 @@ jobs: path: | ~/bin/base-reth-node ~/bin/builder - key: ${{ runner.os }}-base-reth-node-builder-${{ inputs.base_reth_node_version }} + ~/bin/base-load-test + key: ${{ runner.os }}-base-reth-node-builder-load-test-${{ inputs.base_reth_node_version }} - - name: Build base-reth-node and base-builder + - name: Build base-reth-node, base-builder, and base-load-test if: steps.cache-base-reth-node.outputs.cache-hit != 'true' run: | unset CI @@ -188,6 +189,13 @@ jobs: path: ~/bin/builder retention-days: 1 + - name: Upload base-load-test artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: base-load-test + path: ~/bin/base-load-test + retention-days: 1 + build-op-program: runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml new file mode 100644 index 00000000..096015a5 --- /dev/null +++ b/.github/workflows/load-test.yaml @@ -0,0 +1,98 @@ +name: Load Test + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build-binaries: + name: Build binaries + uses: ./.github/workflows/_build-binaries.yaml + with: + base_reth_node_version: main + + load-test: + 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 diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index 98d030bb..9eeb61f0 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -13,7 +13,7 @@ BASE_RETH_NODE_VERSION="${BASE_RETH_NODE_VERSION:-main}" BUILD_DIR="${BUILD_DIR:-./build}" OUTPUT_DIR="${OUTPUT_DIR:-../bin}" -echo "Building base-reth-node and base-builder binaries..." +echo "Building base-reth-node, base-builder, and base-load-test binaries..." echo "Repository: $BASE_RETH_NODE_REPO" echo "Version/Commit: $BASE_RETH_NODE_VERSION" echo "Build directory: $BUILD_DIR" @@ -43,9 +43,9 @@ echo "Checking out version: $BASE_RETH_NODE_VERSION" git checkout -f "$BASE_RETH_NODE_VERSION" # Build the binaries using cargo -echo "Building base-reth-node and base-builder with cargo..." +echo "Building base-reth-node, base-builder, and base-load-test with cargo..." # Build with maxperf profile -cargo build --bin base-reth-node --bin base-builder --profile maxperf +cargo build --bin base-reth-node --bin base-builder --bin base-load-test --profile maxperf # Copy binaries to output directory echo "Copying binaries to output directory..." @@ -74,4 +74,11 @@ else exit 1 fi -echo "base-reth-node and base-builder binaries built successfully and placed in $FINAL_OUTPUT_DIR/" +if [ -f "target/maxperf/base-load-test" ]; then + cp target/maxperf/base-load-test "$FINAL_OUTPUT_DIR/" +else + echo "No base-load-test binary found" + exit 1 +fi + +echo "base-reth-node, base-builder, and base-load-test binaries built successfully and placed in $FINAL_OUTPUT_DIR/" diff --git a/configs/examples/load-test.yml b/configs/examples/load-test.yml new file mode 100644 index 00000000..6743ca60 --- /dev/null +++ b/configs/examples/load-test.yml @@ -0,0 +1,29 @@ +name: Load test throughput test +description: Test builder throughput using base-load-test binary as transaction generator +payloads: + - name: Load Test + type: load-test + id: load-test + sender_count: 10 + transactions: + - weight: 70 + type: transfer + - weight: 20 + type: calldata + max_size: 256 + - weight: 10 + type: precompile + target: sha256 + +benchmarks: + - variables: + - type: payload + value: load-test + - type: node_type + value: builder + - type: validator_node_type + value: base-reth-node + - type: num_blocks + value: 10 + - type: gas_limit + value: 1000000000 From 4811b31ff02c8d10ef620e4748dde344234eb1a3 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 12:09:24 -0700 Subject: [PATCH 08/16] fix: point load-test workflow at feature branch for base-load-test binary --- .github/workflows/load-test.yaml | 2 +- clients/build-base-reth-node.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml index 096015a5..9edace1e 100644 --- a/.github/workflows/load-test.yaml +++ b/.github/workflows/load-test.yaml @@ -13,7 +13,7 @@ jobs: name: Build binaries uses: ./.github/workflows/_build-binaries.yaml with: - base_reth_node_version: main + base_reth_node_version: feature/load-test-benchmark load-test: name: Run load test benchmark diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index 9eeb61f0..04380062 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -44,7 +44,6 @@ git checkout -f "$BASE_RETH_NODE_VERSION" # Build the binaries using cargo echo "Building base-reth-node, base-builder, and base-load-test with cargo..." -# Build with maxperf profile cargo build --bin base-reth-node --bin base-builder --bin base-load-test --profile maxperf # Copy binaries to output directory From fbbf5a7e3c39b579a0629ea158074cd28073cf22 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 12:19:04 -0700 Subject: [PATCH 09/16] fix: point all workflows at base repo SHA with base-load-test binary 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. --- .github/workflows/examples.yaml | 2 +- .github/workflows/load-test.yaml | 2 +- clients/build-base-reth-node.sh | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 1a7c75eb..d7cf0596 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -18,7 +18,7 @@ jobs: with: optimism_version: 3019251e80aa248e91743addd3e833190acb26f1 geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 - base_reth_node_version: main + base_reth_node_version: 38de4e038 example-benchmarks: runs-on: ubuntu-latest diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml index 9edace1e..4dc9d85e 100644 --- a/.github/workflows/load-test.yaml +++ b/.github/workflows/load-test.yaml @@ -13,7 +13,7 @@ jobs: name: Build binaries uses: ./.github/workflows/_build-binaries.yaml with: - base_reth_node_version: feature/load-test-benchmark + base_reth_node_version: 38de4e038 load-test: name: Run load test benchmark diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index 04380062..ab0bd852 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -13,7 +13,7 @@ BASE_RETH_NODE_VERSION="${BASE_RETH_NODE_VERSION:-main}" BUILD_DIR="${BUILD_DIR:-./build}" OUTPUT_DIR="${OUTPUT_DIR:-../bin}" -echo "Building base-reth-node, base-builder, and base-load-test binaries..." +echo "Building base-reth-node and base-builder binaries..." echo "Repository: $BASE_RETH_NODE_REPO" echo "Version/Commit: $BASE_RETH_NODE_VERSION" echo "Build directory: $BUILD_DIR" @@ -80,4 +80,4 @@ else exit 1 fi -echo "base-reth-node, base-builder, and base-load-test binaries built successfully and placed in $FINAL_OUTPUT_DIR/" +echo "Binaries built successfully and placed in $FINAL_OUTPUT_DIR/" From 7c09461db2683bd3906ab3a00e406fd02c715faf Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 12:33:24 -0700 Subject: [PATCH 10/16] fix: disable Rust toolchain cache in _build-binaries 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. --- .github/workflows/_build-binaries.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/_build-binaries.yaml b/.github/workflows/_build-binaries.yaml index 8e51c395..0a9accc8 100644 --- a/.github/workflows/_build-binaries.yaml +++ b/.github/workflows/_build-binaries.yaml @@ -155,6 +155,8 @@ jobs: - name: Set up Rust uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 + with: + cache: false - name: Cache base-reth-node binaries uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3.4.3 From e507e7cc3e8d3a4ab68bcfdfe1265101b42c0879 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 14:08:10 -0700 Subject: [PATCH 11/16] fix: use branch name for base repo checkout and fetch remote refs 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. --- .github/workflows/examples.yaml | 2 +- .github/workflows/load-test.yaml | 2 +- clients/build-base-reth-node.sh | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index d7cf0596..1656537a 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -18,7 +18,7 @@ jobs: with: optimism_version: 3019251e80aa248e91743addd3e833190acb26f1 geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 - base_reth_node_version: 38de4e038 + base_reth_node_version: feature/load-test-benchmark example-benchmarks: runs-on: ubuntu-latest diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml index 4dc9d85e..9edace1e 100644 --- a/.github/workflows/load-test.yaml +++ b/.github/workflows/load-test.yaml @@ -13,7 +13,7 @@ jobs: name: Build binaries uses: ./.github/workflows/_build-binaries.yaml with: - base_reth_node_version: 38de4e038 + base_reth_node_version: feature/load-test-benchmark load-test: name: Run load test benchmark diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index ab0bd852..f998f854 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -40,7 +40,8 @@ fi # Checkout specified version/commit echo "Checking out version: $BASE_RETH_NODE_VERSION" -git checkout -f "$BASE_RETH_NODE_VERSION" +git fetch origin "$BASE_RETH_NODE_VERSION" || true +git checkout -f "$BASE_RETH_NODE_VERSION" || git checkout -f "origin/$BASE_RETH_NODE_VERSION" # Build the binaries using cargo echo "Building base-reth-node, base-builder, and base-load-test with cargo..." From 5e021c7503cfa2a8aaa7fabcbd2225171551520d Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 15:17:01 -0700 Subject: [PATCH 12/16] fix: use versions.env for base repo version 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. --- .github/workflows/_build-binaries.yaml | 2 +- .github/workflows/examples.yaml | 1 - .github/workflows/load-test.yaml | 2 -- clients/versions.env | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/_build-binaries.yaml b/.github/workflows/_build-binaries.yaml index 0a9accc8..4824d6f8 100644 --- a/.github/workflows/_build-binaries.yaml +++ b/.github/workflows/_build-binaries.yaml @@ -19,7 +19,7 @@ on: description: "Base Reth Node version to build" required: false type: string - default: "main" + default: "feature/load-test-benchmark" # Set minimal permissions for all jobs by default permissions: diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 1656537a..21823488 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -18,7 +18,6 @@ jobs: with: optimism_version: 3019251e80aa248e91743addd3e833190acb26f1 geth_version: 6cbfcd5161083bcd4052edc3022d9f99c6fe40e0 - base_reth_node_version: feature/load-test-benchmark example-benchmarks: runs-on: ubuntu-latest diff --git a/.github/workflows/load-test.yaml b/.github/workflows/load-test.yaml index 9edace1e..de9d06a7 100644 --- a/.github/workflows/load-test.yaml +++ b/.github/workflows/load-test.yaml @@ -12,8 +12,6 @@ jobs: build-binaries: name: Build binaries uses: ./.github/workflows/_build-binaries.yaml - with: - base_reth_node_version: feature/load-test-benchmark load-test: name: Run load test benchmark diff --git a/clients/versions.env b/clients/versions.env index 536ac9a8..3427491a 100644 --- a/clients/versions.env +++ b/clients/versions.env @@ -12,7 +12,7 @@ GETH_VERSION="v1.101604.0" # Base Reth Node Configuration BASE_RETH_NODE_REPO="https://github.com/base/base" -BASE_RETH_NODE_VERSION="main" +BASE_RETH_NODE_VERSION="feature/load-test-benchmark" # Build Configuration # BUILD_DIR="./build" From c8c37de7e6e61f8209b0b9cb53b057157483a2e7 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 15:25:34 -0700 Subject: [PATCH 13/16] fix: specify package for base-load-test binary target cargo can't find the bin target without -p base-load-tests because it's in a different workspace package than the default. --- clients/build-base-reth-node.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index f998f854..74fa7361 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -45,7 +45,7 @@ git checkout -f "$BASE_RETH_NODE_VERSION" || git checkout -f "origin/$BASE_RETH_ # Build the binaries using cargo echo "Building base-reth-node, base-builder, and base-load-test with cargo..." -cargo build --bin base-reth-node --bin base-builder --bin base-load-test --profile maxperf +cargo build --bin base-reth-node --bin base-builder -p base-load-tests --bin base-load-test --profile maxperf # Copy binaries to output directory echo "Copying binaries to output directory..." From b98b1a619417d9a38a776439f57d39e3be187cbf Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Fri, 27 Mar 2026 15:51:51 -0700 Subject: [PATCH 14/16] fix: split cargo build into two commands for separate packages --- clients/build-base-reth-node.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/build-base-reth-node.sh b/clients/build-base-reth-node.sh index 74fa7361..7341be5f 100755 --- a/clients/build-base-reth-node.sh +++ b/clients/build-base-reth-node.sh @@ -45,7 +45,8 @@ git checkout -f "$BASE_RETH_NODE_VERSION" || git checkout -f "origin/$BASE_RETH_ # Build the binaries using cargo echo "Building base-reth-node, base-builder, and base-load-test with cargo..." -cargo build --bin base-reth-node --bin base-builder -p base-load-tests --bin base-load-test --profile maxperf +cargo build --bin base-reth-node --bin base-builder --profile maxperf +cargo build -p base-load-tests --bin base-load-test --profile maxperf # Copy binaries to output directory echo "Copying binaries to output directory..." From f9c9f8e97c8b32d80889cb914bd4809be247456e Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Tue, 31 Mar 2026 12:17:01 -0700 Subject: [PATCH 15/16] fix: use proxy port from config --- runner/payload/factory.go | 4 ++-- runner/payload/loadtest/load_test_worker.go | 11 +++++------ runner/payload/txfuzz/tx_fuzz_worker.go | 9 +++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/runner/payload/factory.go b/runner/payload/factory.go index dd621a99..b8e7176e 100644 --- a/runner/payload/factory.go +++ b/runner/payload/factory.go @@ -31,14 +31,14 @@ func NewPayloadWorker(ctx context.Context, log log.Logger, testConfig *benchtype switch definition.Type { case "tx-fuzz": worker, err = txfuzz.NewTxFuzzPayloadWorker( - log, sequencerClient.ClientURL(), params, privateKey, amount, config.TxFuzzBinary(), genesis.Config.ChainID) + log, sequencerClient.ClientURL(), params, privateKey, amount, config, genesis.Config.ChainID) case "load-test": def, _ := definition.Params.(*loadtest.LoadTestPayloadDefinition) if def == nil { def = &loadtest.LoadTestPayloadDefinition{} } worker, err = loadtest.NewLoadTestPayloadWorker( - log, sequencerClient.ClientURL(), params, privateKey, amount, config.LoadTestBinary(), genesis.Config.ChainID, *def) + log, sequencerClient.ClientURL(), params, privateKey, amount, config, genesis.Config.ChainID, *def) case "transfer-only": worker, err = transferonly.NewTransferPayloadWorker( ctx, log, sequencerClient.ClientURL(), params, privateKey, amount, &genesis, definition.Params) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index 5ea9f1cf..bd942616 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -11,6 +11,7 @@ import ( "os/exec" "github.com/base/base-bench/runner/clients/common/proxy" + "github.com/base/base-bench/runner/config" "github.com/base/base-bench/runner/network/mempool" "github.com/base/base-bench/runner/network/types" "github.com/base/base-bench/runner/payload/worker" @@ -19,8 +20,6 @@ import ( "gopkg.in/yaml.v3" ) -const proxyPort = 8545 - // LoadTestPayloadDefinition is the YAML payload params for the load-test type. // Fields map directly to the Rust base-load-test config format. // The `transactions` field is passed through as raw YAML to support the full @@ -64,12 +63,12 @@ func NewLoadTestPayloadWorker( params types.RunParams, prefundedPrivateKey ecdsa.PrivateKey, prefundAmount *big.Int, - loadTestBin string, + cfg config.Config, chainID *big.Int, definition LoadTestPayloadDefinition, ) (worker.Worker, error) { mp := mempool.NewStaticWorkloadMempool(log, chainID) - ps := proxy.NewProxyServer(elRPCURL, log, proxyPort, mp) + ps := proxy.NewProxyServer(elRPCURL, log, cfg.ProxyPort(), mp) blockTimeSec := uint64(params.BlockTime.Seconds()) if blockTimeSec == 0 { @@ -79,7 +78,7 @@ func NewLoadTestPayloadWorker( w := &loadTestPayloadWorker{ log: log, prefundSK: hex.EncodeToString(prefundedPrivateKey.D.Bytes()), - loadTestBin: loadTestBin, + loadTestBin: cfg.LoadTestBinary(), elRPCURL: elRPCURL, gasLimit: params.GasLimit, blockTimeSec: blockTimeSec, @@ -208,7 +207,7 @@ func (w *loadTestPayloadWorker) writeConfig() (string, error) { } config := loadTestConfig{ - RPC: fmt.Sprintf("http://localhost:%d", proxyPort), + RPC: w.proxyServer.ClientURL(), SenderCount: senderCount, TargetGPS: targetGPS, Duration: "99999s", diff --git a/runner/payload/txfuzz/tx_fuzz_worker.go b/runner/payload/txfuzz/tx_fuzz_worker.go index bc49254c..b5167d42 100644 --- a/runner/payload/txfuzz/tx_fuzz_worker.go +++ b/runner/payload/txfuzz/tx_fuzz_worker.go @@ -9,6 +9,7 @@ import ( "os/exec" "github.com/base/base-bench/runner/clients/common/proxy" + "github.com/base/base-bench/runner/config" "github.com/base/base-bench/runner/network/mempool" "github.com/base/base-bench/runner/network/types" "github.com/base/base-bench/runner/payload/worker" @@ -34,16 +35,16 @@ func NewTxFuzzPayloadWorker( params types.RunParams, prefundedPrivateKey ecdsa.PrivateKey, prefundAmount *big.Int, - txFuzzBin string, + cfg config.Config, chainID *big.Int, ) (worker.Worker, error) { mempool := mempool.NewStaticWorkloadMempool(log, chainID) - proxyServer := proxy.NewProxyServer(elRPCURL, log, 8545, mempool) + proxyServer := proxy.NewProxyServer(elRPCURL, log, cfg.ProxyPort(), mempool) t := &txFuzzPayloadWorker{ log: log, prefundSK: hex.EncodeToString(prefundedPrivateKey.D.Bytes()), - txFuzzBin: txFuzzBin, + txFuzzBin: cfg.TxFuzzBinary(), elRPCURL: elRPCURL, mempool: mempool, proxyServer: proxyServer, @@ -65,7 +66,7 @@ func (t *txFuzzPayloadWorker) Setup(ctx context.Context) error { t.log.Info("Sending txs in tx-fuzz mode") - cmd := exec.CommandContext(ctx, t.txFuzzBin, "spam", "--sk", t.prefundSK, "--rpc", t.elRPCURL, "--slot-time", "1") + cmd := exec.CommandContext(ctx, t.txFuzzBin, "spam", "--sk", t.prefundSK, "--rpc", t.proxyServer.ClientURL(), "--slot-time", "1") cmd.Stdout = os.Stdout cmd.Stderr = os.Stdout From 93d3a253925c8926e1c16c3e0c05713eb3e29be3 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Wed, 1 Apr 2026 05:26:03 -0700 Subject: [PATCH 16/16] fix --- runner/payload/loadtest/load_test_worker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runner/payload/loadtest/load_test_worker.go b/runner/payload/loadtest/load_test_worker.go index bd942616..5b6d62ff 100644 --- a/runner/payload/loadtest/load_test_worker.go +++ b/runner/payload/loadtest/load_test_worker.go @@ -142,13 +142,13 @@ func (w *loadTestPayloadWorker) Stop(ctx context.Context) error { return nil } -func (w *loadTestPayloadWorker) SendTxs(ctx context.Context) error { +func (w *loadTestPayloadWorker) SendTxs(ctx context.Context, _ int) (int, error) { w.log.Info("Collecting txs from load test") pendingTxs := w.proxyServer.PendingTxs() w.proxyServer.ClearPendingTxs() w.mempool.AddTransactions(pendingTxs) - return nil + return len(pendingTxs), nil } // defaultTransactions returns the default transaction mix as a yaml.Node.