From 050a0050b265416783250aa5cf990427de6565e1 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:43:59 -0700 Subject: [PATCH 1/2] add --contender.tps flag --- main.go | 11 +++++++- playground/components.go | 46 +++++++++++++++++++-------------- playground/manifest.go | 10 ++++++- playground/recipe_buildernet.go | 6 +++-- playground/recipe_l1.go | 6 +++-- playground/recipe_opstack.go | 21 ++++++++------- 6 files changed, 64 insertions(+), 36 deletions(-) diff --git a/main.go b/main.go index be63f77..5be9497 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ var labels playground.MapStringFlag var disableLogs bool var platform string var contenderEnabled bool +var contenderTps uint64 var rootCmd = &cobra.Command{ Use: "playground", @@ -181,6 +182,7 @@ func main() { recipeCmd.Flags().BoolVar(&disableLogs, "disable-logs", false, "disable logs") recipeCmd.Flags().StringVar(&platform, "platform", "", "docker platform to use") recipeCmd.Flags().BoolVar(&contenderEnabled, "contender", false, "spam nodes with contender") + recipeCmd.Flags().Uint64Var(&contenderTps, "contender.tps", 20, "txs/sec to send from contender") cookCmd.AddCommand(recipeCmd) } @@ -226,7 +228,14 @@ func runIt(recipe playground.Recipe) error { return err } - svcManager := recipe.Apply(&playground.ExContext{LogLevel: logLevel, ContenderEnabled: contenderEnabled}, artifacts) + // if contender.tps is set, assume contender is enabled + svcManager := recipe.Apply(&playground.ExContext{ + LogLevel: logLevel, + Contender: &playground.ContenderContext{ + Enabled: contenderEnabled, + Tps: &contenderTps, + }, + }, artifacts) if err := svcManager.Validate(); err != nil { return fmt.Errorf("failed to validate manifest: %w", err) } diff --git a/playground/components.go b/playground/components.go index 38a0c61..71b8884 100644 --- a/playground/components.go +++ b/playground/components.go @@ -101,8 +101,8 @@ func (o *OpRbuilder) Name() string { type FlashblocksRPC struct { FlashblocksWSService string - BaseOverlay bool - UseWebsocketProxy bool // Whether to add /ws path for websocket proxy + BaseOverlay bool + UseWebsocketProxy bool // Whether to add /ws path for websocket proxy } func (f *FlashblocksRPC) Run(service *Service, ctx *ExContext) { @@ -130,19 +130,19 @@ func (f *FlashblocksRPC) Run(service *Service, ctx *ExContext) { ) } service.WithArgs( - "--authrpc.port", `{{Port "authrpc" 8551}}`, - "--authrpc.addr", "0.0.0.0", - "--authrpc.jwtsecret", "/data/jwtsecret", - "--http", - "--http.addr", "0.0.0.0", - "--http.port", `{{Port "http" 8545}}`, - "--chain", "/data/l2-genesis.json", - "--datadir", "/data_op_reth", - "--disable-discovery", - "--color", "never", - "--metrics", `0.0.0.0:{{Port "metrics" 9090}}`, - "--port", `{{Port "rpc" 30303}}`, - ). + "--authrpc.port", `{{Port "authrpc" 8551}}`, + "--authrpc.addr", "0.0.0.0", + "--authrpc.jwtsecret", "/data/jwtsecret", + "--http", + "--http.addr", "0.0.0.0", + "--http.port", `{{Port "http" 8545}}`, + "--chain", "/data/l2-genesis.json", + "--datadir", "/data_op_reth", + "--disable-discovery", + "--color", "never", + "--metrics", `0.0.0.0:{{Port "metrics" 9090}}`, + "--port", `{{Port "rpc" 30303}}`, + ). WithArtifact("/data/jwtsecret", "jwtsecret"). WithArtifact("/data/l2-genesis.json", "l2-genesis.json"). WithVolume("data", "/data_flashblocks_rpc") @@ -159,13 +159,13 @@ func (f *FlashblocksRPC) Name() string { } type BProxy struct { - TargetAuthrpc string - Peers []string - Flashblocks bool + TargetAuthrpc string + Peers []string + Flashblocks bool FlashblocksBuilderURL string } -func (f* BProxy) Run(service *Service, ctx *ExContext) { +func (f *BProxy) Run(service *Service, ctx *ExContext) { peers := []string{} for _, peer := range f.Peers { peers = append(peers, Connect(peer, "authrpc")) @@ -813,6 +813,7 @@ func (n *nullService) Name() string { } type Contender struct { + Tps *uint64 // txs per second, defaults to 20 } func (c *Contender) Name() string { @@ -820,12 +821,17 @@ func (c *Contender) Name() string { } func (c *Contender) Run(service *Service, ctx *ExContext) { + tps := uint64(20) + if c.Tps != nil { + tps = *c.Tps + } + args := []string{ "spam", "-l", // loop indefinitely "--min-balance", "10 ether", // give each spammer 10 ether (sender must have 100 ether because default number of spammers is 10) "-r", Connect("el", "http"), // connect to whatever EL node is available - "--tps", "20", // send 20 txs per second + "--tps", strconv.FormatUint(uint64(tps), 10), // send tps txs per second as string } service.WithImage("flashbots/contender"). WithTag("latest"). diff --git a/playground/manifest.go b/playground/manifest.go index 030002d..fd25e73 100644 --- a/playground/manifest.go +++ b/playground/manifest.go @@ -70,6 +70,14 @@ func (l *LogLevel) Unmarshal(s string) error { return nil } +type ContenderContext struct { + // Run `contender spam` automatically once all playground services are running. + Enabled bool + + // Determines txs/sec for contender spam. + Tps *uint64 +} + // Execution context type ExContext struct { LogLevel LogLevel @@ -83,7 +91,7 @@ type ExContext struct { // TODO: Extend for CL nodes too Bootnode *BootnodeRef - ContenderEnabled bool + Contender *ContenderContext } type BootnodeRef struct { diff --git a/playground/recipe_buildernet.go b/playground/recipe_buildernet.go index ce7af20..c8d1b77 100644 --- a/playground/recipe_buildernet.go +++ b/playground/recipe_buildernet.go @@ -59,8 +59,10 @@ func (b *BuilderNetRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest }) } - if ctx.ContenderEnabled { - svcManager.AddService("contender", &Contender{}) + if ctx.Contender.Enabled { + svcManager.AddService("contender", &Contender{ + Tps: ctx.Contender.Tps, + }) } return svcManager diff --git a/playground/recipe_l1.go b/playground/recipe_l1.go index d8b00f1..36d7f85 100644 --- a/playground/recipe_l1.go +++ b/playground/recipe_l1.go @@ -107,8 +107,10 @@ func (l *L1Recipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { }) } - if ctx.ContenderEnabled { - svcManager.AddService("contender", &Contender{}) + if ctx.Contender.Enabled { + svcManager.AddService("contender", &Contender{ + Tps: ctx.Contender.Tps, + }) } return svcManager diff --git a/playground/recipe_opstack.go b/playground/recipe_opstack.go index 9da5652..ba7ae33 100644 --- a/playground/recipe_opstack.go +++ b/playground/recipe_opstack.go @@ -32,7 +32,7 @@ type OpRecipe struct { flashblocksBuilderURL string // Indicates that flashblocks-rpc should use base image - baseOverlay bool + baseOverlay bool // whether to enable websocket proxy enableWebsocketProxy bool @@ -108,13 +108,13 @@ func (o *OpRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { if o.flashblocks { peers = append(peers, "flashblocks-rpc") } - + // Only enable bproxy if flashblocks is enabled (since flashblocks-rpc is the only service that needs it) if o.flashblocks { svcManager.AddService("bproxy", &BProxy{ - TargetAuthrpc: externalBuilderRef, - Peers: peers, - Flashblocks: o.flashblocks, + TargetAuthrpc: externalBuilderRef, + Peers: peers, + Flashblocks: o.flashblocks, FlashblocksBuilderURL: flashblocksBuilderURLRef, }) } @@ -146,7 +146,6 @@ func (o *OpRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { }) } - if o.flashblocks { // Determine which service to use for flashblocks websocket connection flashblocksWSService := "rollup-boost" @@ -158,8 +157,8 @@ func (o *OpRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { svcManager.AddService("flashblocks-rpc", &FlashblocksRPC{ FlashblocksWSService: flashblocksWSService, - BaseOverlay: o.baseOverlay, - UseWebsocketProxy: useWebsocketProxy, + BaseOverlay: o.baseOverlay, + UseWebsocketProxy: useWebsocketProxy, }) } @@ -176,8 +175,10 @@ func (o *OpRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { MaxChannelDuration: o.batcherMaxChannelDuration, }) - if ctx.ContenderEnabled { - svcManager.AddService("contender", &Contender{}) + if ctx.Contender.Enabled { + svcManager.AddService("contender", &Contender{ + Tps: ctx.Contender.Tps, + }) } return svcManager From 985249d37210ebcb7995caf633239cd3c32945b1 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:04:57 -0700 Subject: [PATCH 2/2] replace --contender.tps flag w/ --contender.arg[] --- main.go | 8 +-- playground/components.go | 104 +++++++++++++++++++++++++++++--- playground/manifest.go | 4 +- playground/recipe_buildernet.go | 2 +- playground/recipe_l1.go | 2 +- playground/recipe_opstack.go | 2 +- 6 files changed, 103 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 5be9497..144fa24 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ var labels playground.MapStringFlag var disableLogs bool var platform string var contenderEnabled bool -var contenderTps uint64 +var contenderArgs []string var rootCmd = &cobra.Command{ Use: "playground", @@ -182,7 +182,7 @@ func main() { recipeCmd.Flags().BoolVar(&disableLogs, "disable-logs", false, "disable logs") recipeCmd.Flags().StringVar(&platform, "platform", "", "docker platform to use") recipeCmd.Flags().BoolVar(&contenderEnabled, "contender", false, "spam nodes with contender") - recipeCmd.Flags().Uint64Var(&contenderTps, "contender.tps", 20, "txs/sec to send from contender") + recipeCmd.Flags().StringArrayVar(&contenderArgs, "contender.arg", []string{}, "add/override contender CLI flags") cookCmd.AddCommand(recipeCmd) } @@ -232,8 +232,8 @@ func runIt(recipe playground.Recipe) error { svcManager := recipe.Apply(&playground.ExContext{ LogLevel: logLevel, Contender: &playground.ContenderContext{ - Enabled: contenderEnabled, - Tps: &contenderTps, + Enabled: contenderEnabled, + ExtraArgs: contenderArgs, }, }, artifacts) if err := svcManager.Validate(); err != nil { diff --git a/playground/components.go b/playground/components.go index 71b8884..753ad4f 100644 --- a/playground/components.go +++ b/playground/components.go @@ -813,26 +813,110 @@ func (n *nullService) Name() string { } type Contender struct { - Tps *uint64 // txs per second, defaults to 20 + ExtraArgs []string } func (c *Contender) Name() string { return "contender" } +// parse "key=value" OR "key value"; remainder after first space is the value (may contain spaces) +func parseKV(s string) (name, val string, hasVal, usedEq bool) { + s = strings.TrimSpace(s) + if s == "" { + return "", "", false, false + } + eq := strings.IndexByte(s, '=') + ws := indexWS(s) + + // prefer '=' if it appears before any whitespace + if eq > 0 && (ws == -1 || eq < ws) { + return strings.TrimSpace(s[:eq]), strings.TrimSpace(s[eq+1:]), true, true + } + if ws == -1 { + return s, "", false, false + } + return strings.TrimSpace(s[:ws]), strings.TrimSpace(s[ws+1:]), true, false +} + +func indexWS(s string) int { + for i, r := range s { + if r == ' ' || r == '\t' { + return i + } + } + return -1 +} + func (c *Contender) Run(service *Service, ctx *ExContext) { - tps := uint64(20) - if c.Tps != nil { - tps = *c.Tps + type opt struct { + name string + val string + hasVal bool + } + defaults := []opt{ + {name: "-l"}, + {name: "--min-balance", val: "10 ether", hasVal: true}, + {name: "-r", val: Connect("el", "http"), hasVal: true}, + {name: "--tps", val: "20", hasVal: true}, } - args := []string{ - "spam", - "-l", // loop indefinitely - "--min-balance", "10 ether", // give each spammer 10 ether (sender must have 100 ether because default number of spammers is 10) - "-r", Connect("el", "http"), // connect to whatever EL node is available - "--tps", strconv.FormatUint(uint64(tps), 10), // send tps txs per second as string + // Parse extras and track seen flags + type extra struct { + name string + val string + hasVal bool + usedEq bool + } + var extras []extra + seen := map[string]bool{} + + for _, s := range c.ExtraArgs { + name, val, hasVal, usedEq := parseKV(s) + if name == "" { + continue + } + extras = append(extras, extra{name, val, hasVal, usedEq}) + seen[name] = true } + + // Minimal conflict example: --loops overrides default "-l" + conflict := func(flag string) bool { + if seen[flag] { + return true + } + if flag == "-l" && seen["--loops"] { + return true + } + return false + } + + args := []string{"spam"} + + // Add defaults unless overridden + for _, d := range defaults { + if conflict(d.name) { + continue + } + args = append(args, d.name) + if d.hasVal { + args = append(args, d.val) + } + } + + // Append extras verbatim, preserving "=" vs space + for _, e := range extras { + if !e.hasVal { + args = append(args, e.name) + continue + } + if e.usedEq { + args = append(args, e.name+"="+e.val) + } else { + args = append(args, e.name, e.val) + } + } + service.WithImage("flashbots/contender"). WithTag("latest"). WithArgs(args...). diff --git a/playground/manifest.go b/playground/manifest.go index fd25e73..ee49f9a 100644 --- a/playground/manifest.go +++ b/playground/manifest.go @@ -74,8 +74,8 @@ type ContenderContext struct { // Run `contender spam` automatically once all playground services are running. Enabled bool - // Determines txs/sec for contender spam. - Tps *uint64 + // Provide additional args to contender's CLI + ExtraArgs []string } // Execution context diff --git a/playground/recipe_buildernet.go b/playground/recipe_buildernet.go index c8d1b77..e5216fb 100644 --- a/playground/recipe_buildernet.go +++ b/playground/recipe_buildernet.go @@ -61,7 +61,7 @@ func (b *BuilderNetRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest if ctx.Contender.Enabled { svcManager.AddService("contender", &Contender{ - Tps: ctx.Contender.Tps, + ExtraArgs: ctx.Contender.ExtraArgs, }) } diff --git a/playground/recipe_l1.go b/playground/recipe_l1.go index 36d7f85..9d4cfb5 100644 --- a/playground/recipe_l1.go +++ b/playground/recipe_l1.go @@ -109,7 +109,7 @@ func (l *L1Recipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { if ctx.Contender.Enabled { svcManager.AddService("contender", &Contender{ - Tps: ctx.Contender.Tps, + ExtraArgs: ctx.Contender.ExtraArgs, }) } diff --git a/playground/recipe_opstack.go b/playground/recipe_opstack.go index ba7ae33..98cf50f 100644 --- a/playground/recipe_opstack.go +++ b/playground/recipe_opstack.go @@ -177,7 +177,7 @@ func (o *OpRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest { if ctx.Contender.Enabled { svcManager.AddService("contender", &Contender{ - Tps: ctx.Contender.Tps, + ExtraArgs: ctx.Contender.ExtraArgs, }) }