11import type { ContainerDef , ContainerResult , Ctx , Ports } from "../utils/types.ts" ;
2+ import { RELAY_BUILDER_PUBKEY } from "./rbuilder.ts" ;
3+
4+ // Optimistic relaying is per-builder: the builder's pubkey must be present in
5+ // `builders:` with is_optimistic + collateral ≥ bid value. 1e24 wei (1M ETH)
6+ // clears any devnet bid. accept_optimistic itself already defaults true in helix.
7+ const DEFAULT_COLLATERAL = "1000000000000000000000000" ;
28
39const DEFAULT_API_PORT = 4040 ;
410const DEFAULT_TCP_PORT = 4041 ;
511const DEFAULT_WEBSITE_PORT = 9060 ;
12+ // helix serves prometheus metrics from start_metrics_server on $METRICS_PORT
13+ // (default 9500). Exposed so the bench's prometheus can scrape helix-native
14+ // metrics (helix_request_latency_secs, helix_submission_trace_latency_us, …).
15+ const DEFAULT_METRICS_PORT = 9500 ;
616const DEFAULT_RELAY_KEY = "0x64496d4e301e541a6e1237d6ef13a8f8b8b6cb82be9d8ac90073a833dfc2af11" ;
717const DEFAULT_ADMIN_TOKEN = "decker" ;
818const DEFAULT_POSTGRES_PASSWORD = "helix" ;
@@ -11,9 +21,72 @@ export const ports: Ports = {
1121 http : DEFAULT_API_PORT ,
1222 tcp : DEFAULT_TCP_PORT ,
1323 website : DEFAULT_WEBSITE_PORT ,
24+ metrics : DEFAULT_METRICS_PORT ,
25+ } ;
26+
27+ type HelixYamlOpts = {
28+ beaconUrl : string ;
29+ simUrl : string ;
30+ apiPort : number ;
31+ tcpPort : number ;
32+ headerDelay : boolean ;
33+ optimistic : boolean ;
34+ builderPubkey : string ;
35+ collateral : string ;
36+ coreCount : number ;
1437} ;
1538
16- function helixYaml ( beaconUrl : string , simUrl : string , apiPort : number , tcpPort : number ) : string {
39+ // helix hard-pins each tile thread to a CPU id via core_affinity, and `cores` is
40+ // a required config block — so the layout determines whether helix's pipeline runs
41+ // in parallel or all on one core. Spread the tiles across the host's cores: the
42+ // auctioneer (the single-threaded serial stage) gets its own core, and the async
43+ // HTTP runtime (serves submitBlock + getHeader) and the decoder get the largest
44+ // shares; background tiles share core 0. On a small host pinning to high core ids
45+ // would fail, so fall back to the original single-core layout. coreCount is the
46+ // host's logical CPUs (the devnet sets no cpuset, so the container sees them all).
47+ function helixCoresYaml ( coreCount : number ) : string {
48+ if ( coreCount < 8 ) {
49+ return `cores:
50+ auctioneer: 0
51+ tokio: [0]
52+ reg_workers: [0]
53+ tcp_bid_submissions_tile: 0
54+ decoder: [0]
55+ simulator: 0
56+ top_bid: 0` ;
57+ }
58+ const pool = Array . from ( { length : coreCount - 2 } , ( _ , i ) => i + 2 ) ; // cores 2 … n-1
59+ const tcp = pool . pop ( ) ! ;
60+ const sim = pool . pop ( ) ! ;
61+ const reg = [ pool . pop ( ) ! , pool . pop ( ) ! ] ;
62+ const half = Math . ceil ( pool . length / 2 ) ;
63+ const tokio = pool . slice ( 0 , half ) ; // bigger share → HTTP runtime
64+ const decoder = pool . slice ( half ) ; // remainder → decode/deserialize
65+ return `cores:
66+ auctioneer: 1
67+ tokio: [${ tokio . join ( ", " ) } ]
68+ reg_workers: [${ reg . join ( ", " ) } ]
69+ tcp_bid_submissions_tile: ${ tcp }
70+ decoder: [${ decoder . join ( ", " ) } ]
71+ simulator: ${ sim }
72+ top_bid: 0
73+ data_gatherer: 0` ;
74+ }
75+
76+ // Register the builder in both modes so the comparison is a known builder either
77+ // way; is_optimistic toggles the path: false = pessimistic (wait on sim), true =
78+ // fast path (accept before sim). An unregistered builder is a different scenario,
79+ // not the sync baseline we want.
80+ function buildersBlock ( o : HelixYamlOpts ) : string {
81+ return `builders:
82+ - pub_key: "${ o . builderPubkey } "
83+ builder_info:
84+ collateral: "${ o . collateral } "
85+ is_optimistic: ${ o . optimistic }
86+ is_optimistic_for_regional_filtering: false` ;
87+ }
88+
89+ function helixYaml ( o : HelixYamlOpts ) : string {
1790 return `\
1891postgres:
1992 hostname: 127.0.0.1
@@ -23,15 +96,15 @@ postgres:
2396 region: 0
2497 region_name: "LOCAL"
2598simulators:
26- - url: ${ simUrl }
99+ - url: ${ o . simUrl }
27100beacon_clients:
28- - url: ${ beaconUrl }
101+ - url: ${ o . beaconUrl }
29102relays: []
30- builders: []
103+ ${ buildersBlock ( o ) }
31104validator_preferences:
32105 filtering: global
33106 trusted_builders: null
34- header_delay: true
107+ header_delay: ${ o . headerDelay }
35108router_config:
36109 enabled_routes:
37110 - route: All
@@ -44,16 +117,9 @@ is_submission_instance: true
44117is_registration_instance: true
45118website:
46119 enabled: false
47- cores:
48- auctioneer: 0
49- tokio: [0]
50- reg_workers: [0]
51- tcp_bid_submissions_tile: 0
52- decoder: [0]
53- simulator: 0
54- top_bid: 0
55- api_port: ${ apiPort }
56- tcp_port: ${ tcpPort }
120+ ${ helixCoresYaml ( o . coreCount ) }
121+ api_port: ${ o . apiPort }
122+ tcp_port: ${ o . tcpPort }
57123` ;
58124}
59125
@@ -66,9 +132,18 @@ export function buildContainer(def: ContainerDef, ctx: Ctx): ContainerResult {
66132 const apiPort = ( def . config ?. apiPort as number | undefined ) ?? DEFAULT_API_PORT ;
67133 const tcpPort = ( def . config ?. tcpPort as number | undefined ) ?? DEFAULT_TCP_PORT ;
68134 const websitePort = ( def . config ?. websitePort as number | undefined ) ?? DEFAULT_WEBSITE_PORT ;
135+ const metricsPort = ( def . config ?. metricsPort as number | undefined ) ?? DEFAULT_METRICS_PORT ;
69136 const relayKey = ( def . config ?. relayKey as string | undefined ) ?? DEFAULT_RELAY_KEY ;
70137 const adminToken = ( def . config ?. adminToken as string | undefined ) ?? DEFAULT_ADMIN_TOKEN ;
71138 const postgresPassword = ( def . config ?. postgresPassword as string | undefined ) ?? DEFAULT_POSTGRES_PASSWORD ;
139+ // Bench knobs: optimistic enables the fast path for the builder; headerDelay
140+ // off measures raw getHeader serve latency (defaults preserve standalone behaviour).
141+ const optimistic = ( def . config ?. optimistic as boolean | undefined ) ?? false ;
142+ const headerDelay = ( def . config ?. headerDelay as boolean | undefined ) ?? true ;
143+ const builderPubkey = ( def . config ?. builderPubkey as string | undefined ) ?? RELAY_BUILDER_PUBKEY ;
144+ const collateral = ( def . config ?. collateral as string | undefined ) ?? DEFAULT_COLLATERAL ;
145+ // Distribute helix's tiles across the host's CPUs (override with config.coreCount).
146+ const coreCount = ( def . config ?. coreCount as number | undefined ) ?? navigator . hardwareConcurrency ;
72147
73148 return {
74149 container : {
@@ -78,8 +153,9 @@ export function buildContainer(def: ContainerDef, ctx: Ctx): ContainerResult {
78153 RELAY_KEY : relayKey ,
79154 ADMIN_TOKEN : adminToken ,
80155 POSTGRES_PASSWORD : postgresPassword ,
156+ METRICS_PORT : String ( metricsPort ) ,
81157 } ,
82- ports : { http : apiPort , tcp : tcpPort , website : websitePort } ,
158+ ports : { http : apiPort , tcp : tcpPort , website : websitePort , metrics : metricsPort } ,
83159 volumeMounts : [
84160 { name : "relay-logs" , mountPath : "/app/logs" } ,
85161 ] ,
@@ -90,7 +166,17 @@ export function buildContainer(def: ContainerDef, ctx: Ctx): ContainerResult {
90166 configs : [
91167 {
92168 filename : "helix.yml" ,
93- content : helixYaml ( ctx . url ( beacon , "http" ) , ctx . url ( sim , "rpc" ) , apiPort , tcpPort ) ,
169+ content : helixYaml ( {
170+ beaconUrl : ctx . url ( beacon , "http" ) ,
171+ simUrl : ctx . url ( sim , "rpc" ) ,
172+ apiPort,
173+ tcpPort,
174+ headerDelay,
175+ optimistic,
176+ builderPubkey,
177+ collateral,
178+ coreCount,
179+ } ) ,
94180 mountPath : "/config/helix.yml" ,
95181 } ,
96182 ] ,
0 commit comments